r какие строки имеют самое длинное частичное совпадение строк между двумя векторами

У меня есть два вектора, которые содержат названия городов, оба в разных форматах, и мне нужно сопоставить названия водных округов (вода) с соответствующими данными переписи (города). По сути, для каждого ряда в воде мне нужно найти наилучшее соответствие в городах, поскольку большинство из них содержат похожие слова, такие как город. Еще одна проблема, которую я вижу, заключается в том, что слова в одном наборе данных пишутся с заглавной буквы, а в другом - без заглавной. Вот мой пример данных:

towns= c("Acalanes Ridge CDP, Contra Costa County", "Bellflower city, Los Angeles County", "Arvin city, Kern County", "Alturas city, Modoc County")

water=c("Alturas City of","Casitas Municipal Water District","California Water Service Company Bellflower City", "Contra Costa City of Public Works")

person Chris Heckman    schedule 28.04.2016    source источник
comment
Если у вас есть список со всеми городами априори, решить эту проблему очень легко. Я думаю, что лучше постараться по возможности избегать частичного совпадения   -  person Sotos    schedule 28.04.2016
comment
К сожалению, у меня этого нет. Если бы я собирался составить этот список, вероятно, было бы проще просто пройти через 400 водных округов и вручную сопоставить их с любым из 1500 городов.   -  person Chris Heckman    schedule 28.04.2016


Ответы (2)


Это менее наивный подход с использованием пакетов tm и slam, который включает в себя методы обработки текста:

## load the requisite libraries
library(tm)
library(slam)

Во-первых, создайте корпус из объединенных городов и водных векторов. В конечном итоге мы рассчитаем расстояние между каждым городом и каждым водоемом на основе текста.

corpus <- Corpus(VectorSource((c(towns, water))))

Здесь я выполняю стандартную предварительную обработку, удаляя знаки препинания и останавливая «документы». Stemming находит общие основные части слов. Например, город и города имеют одну основу: citi.

corpus <- tm_map(corpus, removePunctuation)
corpus <- tm_map(corpus, stemDocument)

Стандартная матрица терминологического документа имеет двоичные индикаторы того, какие слова в каких документах. Мы также хотим закодировать дополнительную информацию о том, насколько часто слово встречается во всем корпусе. Например, нас не волнует, как часто в документе появляется «the», потому что это невероятно часто.

tdm <- weightTfIdf(TermDocumentMatrix(corpus))

Наконец, мы вычисляем косинусное расстояние между каждым документом. Пакет tm создает разреженные матрицы, которые обычно очень эффективны с точки зрения памяти. В пакете slam есть матричные математические функции для разреженных матриц.

cosine_dist <- function(tdm) {
  crossprod_simple_triplet_matrix(tdm)/(sqrt(col_sums(tdm^2) %*% t(col_sums(tdm^2))))
}

d <- cosine_dist(tdm)
> d
    Docs
Docs          1           2           3           4          5         6           7           8
   1 1.00000000 0.034622992 0.038063800 0.044272011 0.00000000 0.0000000 0.000000000 0.260626250
   2 0.03462299 1.000000000 0.055616255 0.064687275 0.01751883 0.0000000 0.146145917 0.006994714
   3 0.03806380 0.055616255 1.000000000 0.071115850 0.01925984 0.0000000 0.006633427 0.007689843
   4 0.04427201 0.064687275 0.071115850 1.000000000 0.54258275 0.0000000 0.007715340 0.008944058
   5 0.00000000 0.017518827 0.019259836 0.542582752 1.00000000 0.0000000 0.014219656 0.016484228
   6 0.00000000 0.000000000 0.000000000 0.000000000 0.00000000 1.0000000 0.121137618 0.000000000
   7 0.00000000 0.146145917 0.006633427 0.007715340 0.01421966 0.1211376 1.000000000 0.005677459
   8 0.26062625 0.006994714 0.007689843 0.008944058 0.01648423 0.0000000 0.005677459 1.000000000

Теперь у нас есть матрица оценок сходства между всеми городами и водоемами в одной матрице. Однако нас интересуют расстояния только для половины этой матрицы. Следовательно, обозначение индексации в функции apply ниже:

best.match <- apply(d[5:8,1:4], 1, function(row) if(all(row == 0)) NA else which.max(row))

И вот результат:

> cbind(water, towns[best.match])
     water                                                                                       
[1,] "Alturas City of"                                  "Alturas city, Modoc County"             
[2,] "Casitas Municipal Water District"                 NA                                       
[3,] "California Water Service Company Bellflower City" "Bellflower city, Los Angeles County"    
[4,] "Contra Costa City of Public Works"                "Acalanes Ridge CDP, Contra Costa County"

Обратите внимание на значение NA. NA возвращается, если нет ни одного совпадения слов между водоемом и всеми городами.

person Zelazny7    schedule 28.04.2016
comment
@ Zelanzy7 Спасибо за ответ. Мне не удалось обойтись без создания tdm. Возможно, мне стоит упомянуть, что мои данные о воде составляют всего 400 строк по сравнению с 1500 в городах. Ошибка, которую я получил: Ошибка в simple_triplet_matrix (i = i, j = j, v = as.numeric (v), nrow = length (allTerms),: 'i, j, v' разной длины Дополнительно: Предупреждающие сообщения : 1: В mclapply (unname (content (x)), termFreq, control): все запланированные ядра обнаружили ошибки в пользовательском коде 2: В simple_triplet_matrix (i = i, j = j, v = as.numeric (v), nrow = length (allTerms),: NA, введенные принуждением - person Chris Heckman; 28.04.2016
comment
Я не уверен, что происходит с вашим примером. Если вы запустите мой код на своих фиктивных данных, он будет работать нормально. Вы используете векторы? Или данные хранятся иначе, как в data.frames? - person Zelazny7; 28.04.2016
comment
@ Zelanzy7 Я пытался вернуться к этому, но до сих пор не могу заставить его работать. Я знаю, что приведенный мной пример работает, но могу понять это с моими реальными данными. Данные, которые у меня есть, хранятся в data.frames, но я пробовал делать Corpus (VectorSource ((c (example $ example, example2 $ example2)))). Я также пробовал сделать tmp1 = as.vector (пример $ example), а затем добавил tmp1 и tmp2 в команду corpus. Я пробовал проделать то же самое с as.list. Я не понимаю, почему это может не работать. И снова, единственная ошибка, которую я получаю, - это когда я пытаюсь сделать tdm, и это не удается. Я мог бы опубликовать свои данные - person Chris Heckman; 18.05.2016
comment
Вы даже можете опубликовать вывод dput(head(example)), и этого может быть достаточно для устранения неполадок. Однако, если бы вы могли опубликовать где-нибудь весь data.frame, это было бы идеально. - person Zelazny7; 18.05.2016
comment
@ Zelanzy7 Вот два набора данных и код: drive.google.com/ - person Chris Heckman; 18.05.2016
comment
Я не уверен, что происходит с вашей настройкой. Мне удалось без проблем выполнить ваш код. Я загрузил для вас набор данных с соответствующими названиями городов: drive.google.com/open ? id = 0B-tqBaUiJLbPV3lFUHVxRGVOYm8 - person Zelazny7; 18.05.2016
comment
@ Zelanzy7 Итак, я исправил проблему запуска объекта tdm, преобразовав текст в utf-8 и удалив строки со специальными символами. Я все еще не могу получить такой же матч, как ты. Я предполагаю, что вы изменили обозначение индексации в best.match, но я недостаточно хорошо понимаю, что происходит, чтобы знать, как это изменить. Я должен использовать это, чтобы объединить пару разных наборов данных, поэтому я буду очень признателен за быстрое объяснение того, как это сделать. Прямо сейчас я просто показываю все NA во втором столбце без совпадений. - person Chris Heckman; 19.05.2016
comment
Попробуйте это: n <- length(tmp1); best.match <- apply(d[1:n,(n+1):ncol(d)], 1, function(row) if(all(row == 0)) NA else which.max(row)); out <- as.data.frame(cbind(tmp1, tmp2[best.match])) - person Zelazny7; 19.05.2016

Другой возможный способ сделать это, используя только базу R. Мы отделяем строки от water с помощью strsplit, создавая таким образом список, и проверяем, какие из этих строк находятся в towns с помощью grepl. Теперь у нас есть список из 4 логических матриц. Применяя rowSums, мы получаем сумму «ИСТИНА» для каждой строки. Мы используем which.max, чтобы идентифицировать строку с большинством «ИСТИННЫХ» значений. Наконец, мы используем эти значения для индексации towns.

lst <- lapply(strsplit(water, ' '), function(i)
                       sapply(tolower(i), function(j)
                                 grepl(j, tolower(towns))))

ind <- unlist(as.numeric(lapply(lst, function(i)
                   which.max(rowSums(i)[!is.na(match(TRUE, i))]))))

cbind(water, towns[ind])
#            water                                                                                       
#[1,] "Alturas City of"                                  "Alturas city, Modoc County"             
#[2,] "Casitas Municipal Water District"                 NA                                       
#[3,] "California Water Service Company Bellflower City" "Bellflower city, Los Angeles County"    
#[4,] "Contra Costa City of Public Works"                "Acalanes Ridge CDP, Contra Costa County"

Боковое примечание: я использовал [!is.na(match(TRUE, i))] только для вычисления rowSums, когда в матрице действительно есть «ИСТИННЫЕ» значения. В противном случае rowSums логической матрицы 4 x 4 со всеми 'FALSE' будет 0, 0, 0, 0, а взятие which.max(c(0, 0, 0, 0)) даст 1, что довольно интересно.

person Sotos    schedule 28.04.2016