Матрица расстояний строк по критериям
Я написал скрипт для нечеткого сопоставления названий компаний. Я сопоставляю несколько не всегда полностью правильных названий компаний (т. Е. Могут быть небольшие орфографические ошибки или отсутствует суффикс "inc.") С набором "правильных" названий компаний и идентификаторов. Очевидно, что смысл состоит в том, чтобы правильно прикрепить идентификаторы к не всегда корректным названиям компаний.
Вот несколько сильно упрощенных версий наборов данных, которые я сопоставляю (я пока не использую zip-часть, но вернусь к ней позже):
df <- data.frame(zip = c("4760","5445", "2200"), company = c("company x", "company y", "company z"))
corpus <- data.frame(zip = c("4760","5445", "2200", "2200", "2200"), company = c("company x inc.", "company y inc.", "company z inc.", "company a inc.", "company b inc."), id = c(12121212, 23232323, 34343434, 56565656, 67676767))
df
zip company
1 4760 company x
2 5445 company y
3 2200 company z
corpus
zip company id
1 4760 company x inc. 12121212
2 5445 company y inc. 23232323
3 2200 company z inc. 34343434
4 2200 company a inc. 56565656
5 2200 company b inc. 67676767
Затем я использую следующий фрагмент кода для создания матрицы расстояния строки
library(stringdist)
distance.method <- c("jw")
string.dist.matrix <- stringdistmatrix(tolower(corpus$company),
tolower(df$company),
method = distance.method,
nthread = getOption("sd_num_thread"))
string.dist.matrix
[,1] [,2] [,3]
[1,] 0.1190476 0.1798942 0.1798942
[2,] 0.1798942 0.1190476 0.1798942
[3,] 0.1798942 0.1798942 0.1190476
[4,] 0.1798942 0.1798942 0.1798942
[5,] 0.1798942 0.1798942 0.1798942
Затем я иду вперед и сопоставляю пары минимальной дистанции. Обычно я хочу сопоставить, может быть, 4000 компаний с корпусом в 4,5 миллиона. компании, которые требуют некоторой вычислительной мощности, чтобы не сказать больше. У меня была идея, что вместо вычисления расстояния между строками между всеми возможными парами я бы рассчитывал его только для тех, кто использует почтовый индекс. На мой взгляд, результатом будет гораздо меньшее количество вычислений и еще большая точность нечеткого сопоставления для более сложных случаев, чем те, которые я иллюстрировал здесь с моими упрощенными данными.
Короче итоговая матрица, которую я хотел бы, будет выглядеть примерно так:
[,1] [,2] [,3]
[1,] 0.1190476 NA NA
[2,] NA 0.1190476 NA
[3,] NA NA 0.1190476
[4,] NA NA 0.1798942
[5,] NA NA 0.1798942
Я просто не могу найти способ сделать это. Есть идеи?
3 ответа
Подходы ниже используют dplyr
и начинается с подхода Фивера joining
два кадра данных, но затем продолжает производить либо кадр данных, аналогичный вашему string.dist.matrix
или фрейм данных в сжатой форме "ключ-значение". Я добавил еще одну компанию в ваш df
фрейм данных, чтобы включить случай нескольких компаний с одинаковым df zip
,
Версия матрицы расстояний:
df <- data.frame(zip = c("4760","5445", "2200","2200"), company = c("company x", "company y", "company z","company a"))
corpus <- data.frame(zip = c("4760","5445", "2200", "2200", "2200"), company = c("company x inc.", "company y inc.", "company z inc.", "company a inc.", "company b inc."),
id = c(12121212, 23232323, 34343434, 56565656, 67676767))
# large matrix version
library(dplyr)
dist_mat <- inner_join(corpus, df, by = "zip") %>%
mutate(corpus_co=tolower(as.character(company.x)), df_co=tolower(as.character(company.y)), company.x=NULL, company.y=NULL) %>%
group_by(zip) %>%
do( { dist_df=data.frame(unique(.$corpus_co),
stringdistmatrix(unique(.$corpus_co), unique(.$df_co), method=distance.method), stringsAsFactors=FALSE);
colnames(dist_df) = c("corpus_co", unique(.$df_co));
dist_df})
с результатом
zip corpus_co company z company a company x company y
(fctr) (chr) (dbl) (dbl) (dbl) (dbl)
1 2200 company z inc. 0.1190476 0.1798942 NA NA
2 2200 company a inc. 0.1798942 0.1190476 NA NA
3 2200 company b inc. 0.1798942 0.1798942 NA NA
4 4760 company x inc. NA NA 0.1190476 NA
5 5445 company y inc. NA NA NA 0.1190476
Тем не менее, с 4000 строк в вашем df
матрица, полная матрица расстояния строки очень большая со многими NA. Более эффективная версия использует gather
функция от tidyr
пакет для получения результата в key value
формат. При таком подходе некоторые переменные образуют уникальные ключи, которые затем имеют связанные значения. Виньетка для tidyr
Пакет объясняет это более подробно. В вашем случае corpus
название компании и df
название компании от key
и расстояние строки между их именами value
, Это делается для каждого почтового индекса, поэтому полная матрица расстояний между строками никогда не сохраняется. Вам также может оказаться, что с этим проще работать для последующего анализа. Код отличается от предыдущей версии только последней строкой.
library(tidyr)
dist_keyval <- inner_join(corpus, df, by = "zip") %>%
mutate(corpus_co=tolower(as.character(company.x)), df_co=tolower(as.character(company.y)), company.x=NULL, company.y=NULL) %>%
group_by(zip) %>%
do( { dist_df=data.frame(unique(.$corpus_co),
stringdistmatrix(unique(.$corpus_co), unique(.$df_co), method=distance.method), stringsAsFactors=FALSE);
colnames(dist_df) = c("corpus_co", unique(.$df_co));
gather(dist_df, key=df_co, value=str_dist, -corpus_co)})
который дает результат
zip corpus_co df_co str_dist
(fctr) (chr) (chr) (dbl)
1 2200 company z inc. company z 0.1190476
2 2200 company a inc. company z 0.1798942
3 2200 company b inc. company z 0.1798942
4 2200 company z inc. company a 0.1798942
5 2200 company a inc. company a 0.1190476
6 2200 company b inc. company a 0.1798942
7 4760 company x inc. company x 0.1190476
8 5445 company y inc. company y 0.1190476
отредактированный
Код для поиска corpus_co
что минимальное расстояние от каждого df_co
является:
dist_min <- dist_keyval %>% group_by(zip, df_co) %>%
slice(which.min(str_dist))
Чтобы добавить столбцы к окончательному результату, вы можете присоединиться к форме названий компаний, которая использовалась для вычисления расстояния между строками (т. Е. Строчных имен), следующим образом:
final_result <- corpus %>% mutate(lower_co = tolower(as.character(company))) %>%
right_join(dist_min, by = c("zip", "lower_co" = "corpus_co") ) %>%
select(c(df_co, company, id), everything(), -lower_co)
который дает
df_co company id zip str_dist
1 company a company a inc. 56565656 2200 0.1190476
2 company z company z inc. 34343434 2200 0.1190476
3 company x company x inc. 12121212 4760 0.1190476
4 company y company y inc. 23232323 5445 0.1190476
Последний select
показывает, как переставить столбцы в определенный порядок.
У меня есть несколько идей. Если вам не нужна матрица расстояний, вы можете решить ее следующим образом. Я использовал dplyr, потому что знаю этот лучше. Вы можете разбить код на части вместо одной команды dplyr. Или используйте data.table. Это может быть даже быстрее.
Предпринятые шаги:
- Соедините df и корпус с внутренним соединением на молнии. Это удаляет все ненужные записи, и у вас есть названия компаний рядом друг с другом.
- рассчитать расстояние между названиями компаний
- группа по оригинальной компании
- фильтр на минимальном расстоянии
Эти шаги позволяют избежать использования сначала создания матрицы, а затем поиска минимального значения или установки других значений в NA.
library(stringdist)
library(dplyr)
df <- data.frame(zip = c("4760","5445", "2200"), company = c("company x", "company y", "company z"))
corpus <- data.frame(zip = c("4760","5445", "2200", "2200", "2200"), company = c("company x inc.", "company y inc.", "company z inc.", "company a inc.", "company b inc."), id = c(12121212, 23232323, 34343434, 56565656, 67676767))
distance.method <- c("jw")
combined_min_distance <- inner_join(df, corpus, by = "zip" ) %>%
mutate(distance = stringdist(tolower(combined$company.x),
tolower(combined$company.y),
method = distance.method,
nthread = getOption("sd_num_thread"))) %>%
group_by(company.x) %>%
filter(distance == min(distance))
combined_min_distance
zip company.x company.y id distance
(fctr) (fctr) (fctr) (dbl) (dbl)
1 2200 company z company z inc. 34343434 0.1190476
2 4760 company x company x inc. 12121212 0.1190476
3 5445 company y company y inc. 23232323 0.1190476
Ты можешь использовать stringdist::amatch
и избегать вычисления полной матрицы строковых данных.
df <- data.frame(zip = c("4760","5445", "2200"), company = c("company x", "company y", "company z"))
corpus <- data.frame(zip = c("4760","5445", "2200", "2200", "2200"), company = c("company x inc.", "company y inc.", "company z inc.", "company a inc.", "company b inc."), id = c(12121212, 23232323, 34343434, 56565656, 67676767))
i <- stringdist::amatch(df$company,corpus$company,maxDist=5)
merged <- data.frame(df$company,corpus$company[i])
merged
> merged
df.company corpus.company.i.
1 company x company x inc.
2 company y company y inc.
3 company z company z inc.
Еще лучше выполнить некоторую очистку строки, так что вы знаете, что расстояния вызваны только фактическими опечатками (обратите внимание на maxDist
).
lookup <- gsub(" inc.$","",corpus$company)
i2 <- stringdist::amatch(df$company,lookup,maxDist=2)
merged2 <- data.frame(df$company,corpus$company[i2])