Come abbinare quasi due vettori di stringhe (in R)?


36

Non sono sicuro di come debba essere definito, quindi correggimi se conosci un termine migliore.

Ho due liste. Uno di 55 elementi (ad esempio: un vettore di stringhe), l'altro di 92. I nomi degli elementi sono simili ma non identici.

Vorrei trovare il candidato migliore s nella lista 92 alle voci nella lista 55 (Io poi passare attraverso di essa e scegliere il raccordo corretto).

Come si può fare?

Idee che avevo dove:

  1. Vedi tutti quelli che corrispondono (usando qualcosa nell'elenco? Match)
  2. Prova una matrice di distanza tra i vettori delle stringhe, ma non sono sicuro di come definirla meglio (numero di lettere identiche, che dire dell'ordine delle stringhe?)

Quindi quale pacchetto / funzioni / campo di ricerca si occupa di tale compito e come?

Aggiornamento: ecco un esempio dei vettori che desidero abbinare

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
Ciao Tal:> Dato che questi sembrano essere nomi scientifici senza errori di battitura, proverei prima la metrica di Levenshtein (nel contesto di una matrice di distanza 92 per 55) e vedrei come viene fuori.
user603

2
Qualche tempo dopo, il stringdistpacchetto sembra la migliore risorsa per questo tipo di cose.
Shabbychef,

Risposte:


19

Ho avuto problemi simili. (visto qui: https://stackoverflow.com/questions/2231993/merging-two-data-frames-using-fuzzy-approximate-string-matching-in-r )

La maggior parte delle raccomandazioni che ho ricevuto riguardava:

pmatch()E agrep(), grep(), grepl()sono tre funzioni che, se si prende il tempo di guardare attraverso vi fornirà qualche informazione in corrispondenza approssimativa stringa o da stringa approssimativo o regex approssimativa.

Senza vedere le stringhe, è difficile fornirti un duro esempio di come abbinarle. Se potessi fornirci alcuni dati di esempio, sono sicuro che potremmo trovare una soluzione.

Un'altra opzione che ho trovato funziona bene è appiattire le stringhe tolower(), guardando la prima lettera di ogni parola all'interno della stringa e quindi confrontandola. A volte funziona senza intoppi. Poi ci sono cose più complicate come le distanze menzionate in altre risposte. A volte funzionano, a volte sono orribili - dipende davvero dalle stringhe.

Possiamo vederli?

Aggiornare

Sembra che agrep () farà il trucco per la maggior parte di questi. Si noti che agrep () è solo l'implementazione di R della distanza di Levenshtein.

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

Alcuni non calcolano, tuttavia, non sono nemmeno sicuro che il Ferroplasm acidaramus sia lo stesso del Ferroglobus placidus DSM 10642, ad esempio:

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

Penso che potresti essere un po 'SOL per alcuni di questi e forse creare un indice da zero è la soluzione migliore. Per esempio ,. Creare una tabella con i numeri ID per vec55, quindi creare manualmente un riferimento agli ID in vec55 in vec91. Doloroso, lo so, ma molto può essere fatto con agrep ().


Ciao Brandon, ho aggiunto un campione di dati. Grazie!
Tal Galili,

Ciao Brandon - la tua soluzione ha funzionato alla grande - grazie.
Tal Galili,

+1 per il collegamento alla domanda precedente sull'argomento in SE (grazie per il puntatore a agrep ()).
user603

15

Esistono molti modi per misurare le distanze tra due stringhe. Due importanti approcci (standard) ampiamente implementati in R sono il Levenshtein e la distanza di Hamming. Il primo è disponibile nel pacchetto "MiscPsycho" e il secondo in "e1071". Usando questi, vorrei semplicemente calcolare una matrice 92 per 55 di distanze a coppie, quindi procedere da lì (cioè la migliore corrispondenza candidata per la stringa "1" nell'elenco 1 è la stringa "x" dall'elenco 2 con la distanza minima dalla stringa "1 ").

In alternativa, c'è una funzione compare () nel pacchetto RecordLinkage che sembra essere progettato per fare ciò che vuoi e usa la cosiddetta distanza Jaro-Winkler che sembra più appropriata per l'attività a portata di mano, ma non ne ho avuto esperienza .

EDIT: sto modificando la mia risposta per includere il commento di Brandon e il codice Tal, per trovare una corrispondenza con "Aeropyrum pernix", la prima voce di vec55 :

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

8
+1. Inoltre, nel caso sia utile, il termine per google quando si confrontano le stringhe è "modifica distanza": en.wikipedia.org/wiki/Edit_distance
ars

@ars:> grazie, questo è un comodo elenco per alimentare un motore di ricerca R e vedere cosa viene fuori!
user603

2
La distanza di modifica di Levenshtein viene implementata come parte del pacchetto base tramite agrep ()
Brandon Bertelsen,

Ottima risposta Kwak - Lo guarderò in futuro!
Tal Galili,

Personalmente, ritengo che questa sia una risposta più completa alla domanda di Tal. +1 per indicare il nostro RecordLinkage - Dovrò assolutamente provarlo.
Brandon Bertelsen,

7

Per integrare la risposta utile di Kwak, permettimi di aggiungere alcuni semplici principi e idee. Un buon modo per determinare la metrica è considerare come le stringhe potrebbero variare rispetto al loro target. "Modifica distanza" è utile quando la variazione è una combinazione di errori tipografici come trasporre i vicini o digitare male una singola chiave.

Un altro approccio utile (con una filosofia leggermente diversa) è quello di mappare ogni stringa in un rappresentante di una classe di stringhe correlate. Il metodo " Soundex " fa questo: il codice Soundex per una parola è una sequenza di quattro caratteri che codificano la consonante principale e gruppi di conseguenze interne dal suono simile. È usato quando le parole sono errori ortografici fonetici o varianti l'una dell'altra. Nell'applicazione di esempio dovresti recuperare tutte le parole target il cui codice Soundex è uguale al codice Soundex per ogni parola sonda. (Potrebbero essere stati recuperati zero o più target in questo modo.)


3

Vorrei anche suggerire di controllare N-grammi e la distanza Damerau – Levenshtein oltre agli altri suggerimenti di Kwak.

Questo documento confronta l'accuratezza di alcune diverse distanze di modifica menzionate qui (ed è altamente citata secondo lo studioso di Google).

Come puoi vedere ci sono molti modi diversi di affrontarlo e puoi anche combinare metriche diverse (il documento che ho collegato parla di questo piccolo pezzo). Penso che il Levenshtein e le metriche basate correlate abbiano il senso più intuitivo, specialmente se si verificano errori a causa della tipizzazione umana. Gli N-grammi sono anche semplici e hanno senso per i dati che non sono nomi o parole per dire.

Mentre soundex è un'opzione, il po 'di lavoro che ho visto (che è certamente una quantità molto piccola) soundex non funziona così come Levenshstein o altre distanze di modifica per i nomi corrispondenti. E il Soundex è limitato alle frasi fonetiche probabilmente introdotte da dattilografi umani, in cui Levenshtein e N-grammi hanno un ambito potenzialmente più ampio (specialmente N-grammo, ma mi aspetterei che la distanza di Levenshtein funzionasse meglio anche per le non parole).

Non posso fare a meno dei pacchetti, ma il concetto di N-grammi è piuttosto semplice (di recente ho creato una macro SPSS per fare gli N-grammi, ma per un progetto così piccolo andrei semplicemente con i pacchetti già realizzati in R gli altri poster hanno suggerito). Ecco un esempio di calcolo della distanza di Levenshtein in pitone.


Grazie Andy - Lo guarderò in futuro.
Tal Galili,

1

Ho studiato alcuni pacchetti e modi per risolvere questo problema e penso che il miglior candidato sia il fuzzywuzzyRpacchetto.

Il pacchetto fuzzywuzzyR è una stringa fuzzy che corrisponde all'implementazione del pacchetto python fuzzywuzzy . Usa la distanza di Levenshtein per calcolare le differenze tra le sequenze. Maggiori dettagli sulla funzionalità di fuzzywuzzyR sono disponibili nel post sul blog e nel pacchetto Vignette.

Ho fatto la soluzione semplice per il tuo problema, ma c'è un piccolo problema. Devi installare Python e se usi winodows devi anche installare alcuni strumenti di compilazione per Visual Studio . Devi scegliere questi:

  • Windows 10 sdk 10.0.17763.0 e MSVC v140
  • VS 2015 C ++ build tools (v 14v00)

La soluzione è semplice La funzione principale ExtractOnerestituisce un elenco di due valori. Il primo è una corrispondenza di stringa e il secondo è il punteggio corrispondente (nell'intervallo 0 - 100). Il fuzzywuzzyRpacchetto fornisce anche altre funzioni che possono essere utili. La documentazione principale è disponibile qui . Spero che questo codice aiuti a risolvere il problema.

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)   
}

Produzione:

[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

Basato sulla funzione adist

Calcola la distanza approssimativa della stringa tra i vettori di caratteri. La distanza è una distanza generalizzata di Levenshtein (modifica), che fornisce il numero minimo possibilmente ponderato di inserzioni, eliminazioni e sostituzioni necessarie per trasformare una stringa in un'altra

La funzione stringdistdi un pacchetto con lo stesso nome ha diversi metodi (vedi ?stringdist):

method = c ("osa", "lv", "dl", "hamming", "lcs", "qgram", "cosine", "jaccard", "jw", "soundex")

Con questo, puoi selezionare la massima divergenza (soglia):

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

Questo frame di dati mostra il primo vettore nel primo vettore di colonna, la migliore corrispondenza del secondo vettore nella corrispondenza di colonna, la sua distanza nella divergenza di colonna e tutte le corrispondenze significative ordinate in corrispondenza delle colonne come nell'OP.


2
Sebbene l'implementazione sia spesso mescolata a contenuti sostanziali nelle domande, dovremmo essere un sito per fornire informazioni su statistiche, apprendimento automatico, ecc., Non codice. Può essere utile fornire anche il codice, ma si prega di elaborare la risposta sostanziale nel testo per le persone che non leggono abbastanza bene questa lingua per riconoscere ed estrarre la risposta dal codice.
gung - Ripristina Monica
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.