Як квазі відповідати двом векторам струн (в R)?


36

Я не впевнений, як це слід називати, тому, будь ласка, виправте мене, якщо ви знаєте кращий термін.

У мене є два списки. Один із 55 елементів (наприклад, вектор рядків), інший із 92. Назви елементів схожі, але не тотожні.

Я хочу , щоб знайти кращий кандидат S в 92 списку елементів в списку 55 (я потім пройти через нього і вибрати правильну установку).

Як це можна зробити?

Ідеї ​​у мене було де:

  1. Переглянути всі відповідність (використовуючи щось із списку?
  2. Спробуйте матрицю відстані між векторами рядків, але я не впевнений, як найкраще її визначити (кількість однакових букв, як щодо порядку рядків?)

Отже, який пакет / функції / поле дослідження займається таким завданням, і як?

Оновлення: Ось приклад векторів, з якими я хочу відповідати

vec55 <- c("Aeropyrum pernix", "Archaeoglobus fulgidus", "Candidatus_Korarchaeum_cryptofilum", 
"Candidatus_Methanoregula_boonei_6A8", "Cenarchaeum_symbiosum", 
"Desulfurococcus_kamchatkensis", "Ferroplasma acidarmanus", "Haloarcula_marismortui_ATCC_43049", 
"Halobacterium sp.", "Halobacterium_salinarum_R1", "Haloferax volcanii", 
"Haloquadratum_walsbyi", "Hyperthermus_butylicus", "Ignicoccus_hospitalis_KIN4", 
"Metallosphaera_sedula_DSM_5348", "Methanobacterium thermautotrophicus", 
"Methanobrevibacter_smithii_ATCC_35061", "Methanococcoides_burtonii_DSM_6242"
)
vec91 <- c("Acidilobus saccharovorans 345-15", "Aciduliprofundum boonei T469", 
"Aeropyrum pernix K1", "Archaeoglobus fulgidus DSM 4304", "Archaeoglobus profundus DSM 5631", 
"Caldivirga maquilingensis IC-167", "Candidatus Korarchaeum cryptofilum OPF8", 
"Candidatus Methanoregula boonei 6A8", "Cenarchaeum symbiosum A", 
"Desulfurococcus kamchatkensis 1221n", "Ferroglobus placidus DSM 10642", 
"Halalkalicoccus jeotgali B3", "Haloarcula marismortui ATCC 43049", 
"Halobacterium salinarum R1", "Halobacterium sp. NRC-1", "Haloferax volcanii DS2", 
"Halomicrobium mukohataei DSM 12286", "Haloquadratum walsbyi DSM 16790", 
"Halorhabdus utahensis DSM 12940", "Halorubrum lacusprofundi ATCC 49239", 
"Haloterrigena turkmenica DSM 5511", "Hyperthermus butylicus DSM 5456", 
"Ignicoccus hospitalis KIN4/I", "Ignisphaera aggregans DSM 17230", 
"Metallosphaera sedula DSM 5348", "Methanobrevibacter ruminantium M1", 
"Methanobrevibacter smithii ATCC 35061", "Methanocaldococcus fervens AG86", 
"Methanocaldococcus infernus ME", "Methanocaldococcus jannaschii DSM 2661", 
"Methanocaldococcus sp. FS406-22", "Methanocaldococcus vulcanius M7", 
"Methanocella paludicola SANAE", "Methanococcoides burtonii DSM 6242", 
"Methanococcus aeolicus Nankai-3", "Methanococcus maripaludis C5", 
"Methanococcus maripaludis C6", "Methanococcus maripaludis C7", 
"Methanococcus maripaludis S2", "Methanococcus vannielii SB", 
"Methanococcus voltae A3", "Methanocorpusculum labreanum Z", 
"Methanoculleus marisnigri JR1", "Methanohalobium evestigatum Z-7303", 
"Methanohalophilus mahii DSM 5219", "Methanoplanus petrolearius DSM 11571", 
"Methanopyrus kandleri AV19", "Methanosaeta thermophila PT", 
"Methanosarcina acetivorans C2A", "Methanosarcina barkeri str. Fusaro", 
"Methanosarcina mazei Go1", "Methanosphaera stadtmanae DSM 3091", 
"Methanosphaerula palustris E1-9c", "Methanospirillum hungatei JF-1", 
"Methanothermobacter marburgensis str. Marburg", "Methanothermobacter thermautotrophicus str. Delta H", 
"Nanoarchaeum equitans Kin4-M", "Natrialba magadii ATCC 43099", 
"Natronomonas pharaonis DSM 2160", "Nitrosopumilus maritimus SCM1", 
"Picrophilus torridus DSM 9790", "Pyrobaculum aerophilum str. IM2", 
"Pyrobaculum arsenaticum DSM 13514", "Pyrobaculum calidifontis JCM 11548", 
"Pyrobaculum islandicum DSM 4184", "Pyrococcus abyssi GE5", "Pyrococcus furiosus DSM 3638", 
"Pyrococcus horikoshii OT3", "Staphylothermus hellenicus DSM 12710", 
"Staphylothermus marinus F1", "Sulfolobus acidocaldarius DSM 639", 
"Sulfolobus islandicus L.D.8.5", "Sulfolobus islandicus L.S.2.15", 
"Sulfolobus islandicus M.14.25", "Sulfolobus islandicus M.16.27", 
"Sulfolobus islandicus M.16.4", "Sulfolobus islandicus Y.G.57.14", 
"Sulfolobus islandicus Y.N.15.51", "Sulfolobus solfataricus P2", 
"Sulfolobus tokodaii str. 7", "Thermococcus gammatolerans EJ3", 
"Thermococcus kodakarensis KOD1", "Thermococcus onnurineus NA1", 
"Thermococcus sibiricus MM 739", "Thermofilum pendens Hrk 5", 
"Thermoplasma acidophilum DSM 1728", "Thermoplasma volcanium GSS1", 
"Thermoproteus neutrophilus V24Sta", "Thermosphaera aggregans DSM 11486", 
"Vulcanisaeta distributa DSM 14429", "uncultured methanogenic archaeon RC-I"
) 

2
Привіт Тал:> Враховуючи, що це, здається, не друкарські наукові назви, я спробував би спершу метрику Левенштайна (в контексті матриці відстані 92 на 55) і побачив, як це виходить.
user603

2
Через деякий час stringdistпакет здається найкращим ресурсом для подібних речей.
shabbychef

Відповіді:


19

У мене були подібні проблеми. (дивись тут: https://stackoverflow.com/questions/2231993/merging-two-data-frames-using-fuzzy-approximate-string-matching-in-r )

Більшість рекомендацій, які я отримав, були порушені:

pmatch(), і agrep(), grep()- grepl()це три функції, які, якщо ви знайдете час для розгляду, забезпечать вам деяке уявлення про приблизну відповідність рядків або через приблизний рядок, або приблизний регулярний вираз.

Не бачачи рядків, важко надати вам важкий приклад того, як їх відповідати. Якщо ви могли б надати нам деякі приклади даних, я впевнений, що ми могли б знайти рішення.

Ще один варіант, який я знайшов добре, - це вирівняти рядки, tolower()дивлячись на першу букву кожного слова в рядку, а потім порівнюючи. Іноді це працює без сучка. Потім з’являються складніші речі, такі як відстані, зазначені в інших відповідях. Іноді ці роботи, іноді вони жахливі - це дійсно залежить від струн.

Чи можемо ми їх побачити?

Оновлення

Схоже, agrep () зробить трюк для більшості з них. Зауважимо, що agrep () - це просто реалізація R на відстані Левенштейна.

agrep(vec55[1],vec91,value=T)

Деякі не обчислюють, хоча, я навіть не впевнений, чи Ferroplasm acidaramus такий же, як Ferroglobus placidus DSM 10642, наприклад:

agrep(vec55[7],vec91,value=T) 

Я думаю, що ви можете бути трохи SOL для деяких із них, і, можливо, створення індексу з нуля - найкраща ставка. тобто. Створіть таблицю з ідентифікаційними номерами для vec55, а потім вручну створіть посилання на id в vec55 в vec91. Болісно, ​​я знаю, але багато з цим можна зробити за допомогою agrep ().


Привіт, Брендон - я додав зразок даних. Спасибі!
Тал Галілі

Привіт Брендоне - ваше рішення спрацювало чудово - дякую.
Тал Галілі

+1 за посилання на попереднє запитання з цього приводу в SE (thaks для покажчика на узгодження ()).
user603

15

Існує багато способів вимірювання відстаней між двома рядками. Два важливих (стандартних) підходу, широко впроваджених в R, - Левенштейн та відстань Хеммінга. Перший доступний в пакеті "MiscPsycho", а другий в "e1071". Використовуючи ці, я просто обчислить матрицю 92 на 55 попарних відстаней, а потім перейду звідти (тобто найкращим відповідним кандидатом для рядка "1" у списку 1 є рядок "х" зі списку 2 з найменшим відстанню до рядка "1 ").

Крім того , існує функція порівняння () в пакеті RecordLinkage , що , здається, розроблений , щоб зробити те , що ви хочете , і використовує так званий Яро-Вінклер відстань , яке здається більш відповідним для виконання цього завдання під рукою, але я не мав досвіду роботи з ним .

EDIT: я редагую свою відповідь, щоб включити коментар Брендона, а також код Тала, щоб знайти відповідність "Aeropyrum pernix", перший запис vec55 :

agrep(vec55[1],vec91,ignore.case=T,value=T,max.distance = 0.1, useBytes = FALSE)
[1] "Aeropyrum pernix K1"

8
+1. Крім того , в разі , якщо це корисно, термін Google при порівнянні рядків є «відстань редагування»: en.wikipedia.org/wiki/Edit_distance
АРС

@ars:> спасибі, це зручний список, який можна подати до пошукової системи R і подивитися, що вийде!
user603

2
Відстань редагування Левенштейна реалізована як частина базового пакету через agrep ()
Брендон Бертелсен

Чудова відповідь Квак - я буду дивитись на це в майбутньому!
Тал Галілі

Особисто я відчуваю, що це більш повна відповідь на питання Тала. +1 для вказівки на нашу RecordLinkage - я обов'язково повинен це спробувати.
Брендон Бертелсен

7

Щоб доповнити корисну відповідь Квака, дозвольте мені додати кілька простих принципів та ідей. Хороший спосіб визначити показник - розглянути, як рядки можуть відрізнятися від цілі. "Відредагувати відстань" корисно, коли варіант - це поєднання друкарських помилок, таких як переміщення сусідів або неправильне введення одного ключа.

Ще один корисний підхід (з дещо іншою філософією) - це зіставлення кожної рядки в один представник класу споріднених рядків. Метод " Soundex " робить це так: код Soundex для слова - це послідовність з чотирьох символів, що кодують головний приголосний і групи подібних за звучанням внутрішніх наслідків. Він використовується, коли слова є фонетичними написаннями або варіантами один одного. У прикладі програми ви отримаєте всі цільові слова, чий код Soundex дорівнює коду Soundex для кожного слова зонду. (Таким чином може бути нульове чи кілька цілей.)


3

Я б також запропонував вам перевірити N-грам і відстань Дамерау-Левенштейн, крім інших пропозицій Квака.

У цьому документі порівнюється точність кількох різних відстаней редагування, згаданих тут (і це дуже цитується за даними вченого Google).

Як ви бачите, існує багато різних способів наблизитись до цього, і ви навіть можете комбінувати різні показники (стаття, яку я пов’язував, розповідає про цей біт alittle). Я думаю, що показники на основі Левенштейна та пов'язані з ними метри мають найбільш інтуїтивний сенс, особливо якщо помилки трапляються через людський друк. N-грами також прості і мають сенс для даних, які не є іменами чи словами на вимову.

Хоча soundex є можливим варіантом, я мало бачив роботи (що, правда, дуже мала), soundex не виконує так само, як Левеншштейн чи інші відстані редагування для відповідності імен. І Soundex обмежується фонетичними фразами, які, ймовірно, вводяться людськими машинописами, де, як і Левенштейн, і N-грами мають потенційно ширший обсяг (особливо N-грам, але я б очікував, що відстань Левенштейна буде кращою і для не-слів).

Я не можу допомогти, що стосується пакунків, але концепція N-грамів є досить простою (я зробив макрос SPSS, щоб робити N-грами нещодавно, але для такого невеликого проекту я б просто пішов з уже зробленими пакетами в Р інші афіші запропонували). Ось приклад обчислення відстані Левенштейна в пітоні.


Дякую Енді - я буду дивитись на це в майбутньому.
Тал Галілі

1

Я дослідив деякі пакети та способи вирішення цієї проблеми, і вважаю, що найкращим кандидатом є fuzzywuzzyRпакет.

Пакет fuzzywuzzyR - це нечітка струна, що відповідає реалізації пакету нечітких пітонів. Він використовує відстань Левенштейна для обчислення різниць між послідовностями. Більш детально про функціональність fuzzywuzzyR можна прочитати в блозі-пості та в пакеті Vignette.

Я вирішив просту проблему для вашої проблеми, але улов є небагато. Ви повинні встановити python, і якщо ви використовуєте winodows, вам також доведеться встановити деякі інструменти побудови для візуальної студії . Ви повинні вибрати наступне:

  • Windows 10 sdk 10.0.17763.0 та MSVC v140
  • Інструменти збирання VS 2015 C ++ (v 14v00)

Рішення просте. Основна функція ExtractOneповертає список двох значень. Перший - це одна відповідність рядків, а другий - відповідна оцінка (в діапазоні 0 - 100). fuzzywuzzyRПакет забезпечує також інші функції , які можуть бути корисні. Основну документацію можна знайти тут . Я сподіваюся, що цей код допоможе вирішити проблему.

library(fuzzywuzzyR)

# The Fuzzy initialization
init_proc = FuzzUtils$new()
PROC = init_proc$Full_process # class process-method
PROC1 = tolower # base R function
init_scor = FuzzMatcher$new()
SCOR = init_scor$WRATIO    
init <- FuzzExtract$new()

match_strings <- function(vector_to_process, base_vector){  
  new_vec = c()
  for(i in 1:length(vector_to_process)){      
    new_word <- init$ExtractOne(string = vector_to_process[i], sequence_strings = base_vector, processor = PROC1, scorer = SCOR, score_cutoff = 0L)
    new_vec[i] <- new_word[[1]]
  }     
  return(new_vec)
}

# Check if all python modules are available
if (check_availability()){    
  new_vec <- match_strings(vec55, vec91)
  print(new_vec)   
}

Вихід:

[1] "Aeropyrum pernix K1"                                 "Archaeoglobus fulgidus DSM 4304"                    
[3] "Candidatus Korarchaeum cryptofilum OPF8"             "Candidatus Methanoregula boonei 6A8"                
[5] "Cenarchaeum symbiosum A"                             "Desulfurococcus kamchatkensis 1221n"                
[7] "Thermoplasma volcanium GSS1"                         "Haloarcula marismortui ATCC 43049"                  
[9] "Halobacterium sp. NRC-1"                             "Halobacterium salinarum R1"                         
[11] "Haloferax volcanii DS2"                              "Haloquadratum walsbyi DSM 16790"                    
[13] "Hyperthermus butylicus DSM 5456"                     "Ignicoccus hospitalis KIN4/I"                       
[15] "Metallosphaera sedula DSM 5348"                      "Methanothermobacter thermautotrophicus str. Delta H"
[17] "Methanobrevibacter smithii ATCC 35061"               "Methanococcoides burtonii DSM 6242"       

0

На основі функції adist

Обчисліть приблизну відстань між рядками символів. Відстань - це узагальнена відстань Левенштейна (редагувати), що дає мінімальну, можливо зважену кількість вставок, видалень та підстановок, необхідних для перетворення однієї струни в іншу

Функція stringdistз однойменного пакету має кілька методів (див. ?stringdist):

метод = c ("osa", "lv", "dl", "hamming", "lcs", "qgram", "косинус", "жакард", "jw", "soundex")

За допомогою цього ви можете вибрати максимальну розбіжність (поріг):

firstvector<-vec55
secondvector<-vec91

match<-character()
threshold<-14 # max 14 characters of divergence
mindist<-integer()
sortedmatches<-character()

for (i in 1:length(firstvector) ) {
  matchdist<-adist(firstvector[i],secondvector)[1,]
  # matchdist<-stringdist(firstvector[i],secondvector) # several methods available

  matchdist<-ifelse(matchdist>threshold,NA,matchdist)
  sortedmatches[i]<-paste(secondvector[order(matchdist, na.last=NA)], collapse = ", ")
  mindist[i]<- tryCatch(ifelse(is.integer(which.min(matchdist)),matchdist[which.min(matchdist)],NA), error = function(e){NA})
  match[i]<-ifelse(length(secondvector[which.min(matchdist)])==0,NA,
                  secondvector[which.min(matchdist)] )
}
res<-data.frame(firstvector=firstvector,match=match,divergence=mindist, sortedmatches=sortedmatches, stringsAsFactors = F)
res

Цей фрейм даних показує перший вектор у стовпчику першого вектора, найкращий збіг другого вектора у збігу стовпців, його відстань у розбіжності стовпців та всі значущі збіги, упорядковані у сортованих збігах стовпців, як у ОП.


2
Хоча реалізація часто змішується з основним змістом у питаннях, ми, як передбачається, є сайтом для надання інформації про статистику, машинне навчання тощо, а не кодом. Буде добре також надати код, але, будь ласка, докладіть детальну відповідь у тексті для людей, які недостатньо добре читають цю мову, щоб розпізнати та витягнути відповідь з коду.
gung - Відновіть Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.