R: создание списка близких совпадений с stringdist и stringdistmatrix

Я обнаружил отличный пакет "stringdist" и теперь хочу использовать его для вычисления расстояний между строками. В частности, у меня есть набор слов, и я хочу распечатать близкие совпадения, где "близкое совпадение" происходит через некоторый алгоритм, такой как расстояние Левенштейна.

У меня очень медленно работающий код в сценарии оболочки, и я смог загрузить в stringdist и создать матрицу с метриками. Теперь я хочу свести эту матрицу к меньшей матрице, которая имеет только близкие совпадения, например, где метрика не равна нулю, но меньше некоторого порогового значения.

kp <-  c('leaflet','leafletr','lego','levenshtein-distance','logo')
kpm <- stringdistmatrix(kp,useNames="strings",method="lv")
> kpm
                     leaflet leafletr lego levenshtein-distance
leafletr                   1                                   
lego                       5        6                          
levenshtein-distance      16       16   18                     
logo                       6        7    1                   19
m = as.matrix(kpm)
close = apply(m, 1, function(x) x>0 & x<5)
>  close
                     leaflet leafletr  lego levenshtein-distance  logo
 leaflet                FALSE     TRUE FALSE                FALSE FALSE
 leafletr                TRUE    FALSE FALSE                FALSE FALSE
 lego                   FALSE    FALSE FALSE                FALSE  TRUE
 levenshtein-distance   FALSE    FALSE FALSE                FALSE FALSE
 logo                   FALSE    FALSE  TRUE                FALSE FALSE

Хорошо, теперь у меня есть (большой) dist, как мне уменьшить его до списка, где вывод будет что-то вроде

leafletr,leaflet,1
logo,lego,1

только для случаев, когда метрика не равна нулю и меньше чем n=5? Я нашел "apply()", который позволяет мне делать тест, теперь мне нужно разобраться, как его использовать.

Проблема не специфична для stringdist и stringdistmatrix и является очень элементарной R, но все же я застрял. Я подозреваю, что ответ включает в себя subset(), но я не знаю, как преобразовать "dist" во что-то еще.

2 ответа

Решение

Вы можете сделать это:

library(reshape2)
d <- unique(melt(m))
out <- subset(d, value > 0 & value < 5)

Вот, melt приносит m в длинную форму (2 столбца с именами строк и один столбец со значением). Однако, поскольку мы расплавили симметричную матрицу, мы используем unique для дедупликации.

Другой способ заключается в использовании dplyr (так как все классные дети используют dplyr с трубками сейчас)

library(dlpyr)
library(reshape2)
library(magrittr)

out <- melt(m) %>% distinct() %>% filter(value > 0 & value < 5)

Этот второй вариант, вероятно, быстрее, но я его не рассчитал.

Настройте свои данные:

library('stringdist')
library('dplyr')
kp <-  c('leaflet','leafletr','lego','levenshtein-distance','logo')
kpm <- stringdistmatrix(kp,useNames="strings",method="lv")

Здесь мы можем изменить kpm в кадр данных:

kpm <- data.frame(as.matrix(kpm))

Это способ получить фрейм данных, у которого есть '1', чтобы отметить, где слова достаточно близки:

idx <- apply(kpm, 2, function(x) x >0 & x<5)
idx <- apply(idx, 1:2, function(x) if(isTRUE(x)) x<-1 else x<-NA)
#> idx
#                     leaflet leafletr lego levenshtein.distance logo
#  leaflet                   NA        1   NA                   NA   NA
#  leafletr                   1       NA   NA                   NA   NA
#  lego                      NA       NA   NA                   NA    1
#  levenshtein-distance      NA       NA   NA                   NA   NA
#  logo                      NA       NA    1                   NA   NA

Чтобы упростить задачу, расплавьте фрейм данных, отфильтруйте его и избавьтесь от последнего столбца:

final <- melt(idx) %>%
        filter(value==1) %>%
        select(Var1, Var2)

Не забудьте превратить все обратно в персонажей, а не в факторы! (Иногда это как битая запись в R...)

final[] <- lapply(final, as.character)
#> final
#      Var1     Var2
#  leafletr  leaflet
#   leaflet leafletr
#      logo     lego
#      lego     logo

Теперь мы избавимся от дубликатов:

final <- final[!duplicated(data.frame(list(do.call(pmin,final),do.call(pmax,final)))),]

Прикрепите несколько хороших имен, и вы готовы идти.

names(final) <- c('string 1', 'string 2')
#> final
# string 1 string 2
# leafletr  leaflet
#     logo     lego

(Несмотря на то, что вы запросили список, это фрейм данных. Отсюда довольно легко конвертировать все, что вы хотите, в зависимости от ваших потребностей, например, запись в CSV и т. Д. И т. Д.)

Другие вопросы по тегам