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 и т. Д. И т. Д.)