Come posso abbinare le stringhe di corrispondenza fuzzy da due dataset?

Ho lavorato su un modo per unire due set di dati basati su una stringa imperfetta, come il nome di un’azienda. In passato dovevo abbinare due liste molto sporche, una lista aveva nomi e informazioni finanziarie, un’altra lista aveva nomi e indirizzi. Nessuno dei due aveva ID univoci da abbinare! ASSUMETE CHE LA PULIZIA È GIÀ STATA APPLICATA E CHE POSSONO ESSERE INSERITI E INSERTI.

Finora AGREP è lo strumento più vicino che ho trovato che potrebbe funzionare. Posso usare le distanze di levenshtein nel pacchetto AGREP, che misurano il numero di cancellazioni, inserzioni e sostituzioni tra due stringhe. AGREP restituirà la stringa con la distanza più piccola (la più simile).

Tuttavia, ho avuto difficoltà a trasformare questo comando da un singolo valore per applicarlo a un intero frame di dati. Ho usato crudamente un ciclo for per ripetere la funzione AGREP, ma deve esserci un modo più semplice.

Vedere il seguente codice:

a<-data.frame(name=c('Ace Co','Bayes', 'asd', 'Bcy', 'Baes', 'Bays'),price=c(10,13,2,1,15,1)) b<-data.frame(name=c('Ace Co.','Bayes Inc.','asdf'),qty=c(9,99,10)) for (i in 1:6){ a$x[i] = agrep(a$name[i], b$name, value = TRUE, max = list(del = 0.2, ins = 0.3, sub = 0.4)) a$Y[i] = agrep(a$name[i], b$name, value = FALSE, max = list(del = 0.2, ins = 0.3, sub = 0.4)) } 

La soluzione dipende dalla cardinalità desiderata della tua corrispondenza da a a b . Se è uno-a-uno, otterrai le tre partite più vicine sopra. Se è molti-a-uno, ne riceverai sei.

Caso uno a uno (richiede un algoritmo di assegnazione):

Quando ho dovuto fare questo prima di trattarlo come un problema di assegnazione con una matrice di distanza e un assegnamento euristico (incarico avido usato di seguito). Se vuoi una soluzione “ottimale” staresti meglio con l’ optim .

Non ho familiarità con AGREP ma ecco un esempio di utilizzo di stringdist per la tua matrice di distanza.

 library(stringdist) d <- expand.grid(a$name,b$name) # Distance matrix in long form names(d) <- c("a_name","b_name") d$dist <- stringdist(d$a_name,d$b_name, method="jw") # String edit distance (use your favorite function here) # Greedy assignment heuristic (Your favorite heuristic here) greedyAssign <- function(a,b,d){ x <- numeric(length(a)) # assgn variable: 0 for unassigned but assignable, # 1 for already assigned, -1 for unassigned and unassignable while(any(x==0)){ min_d <- min(d[x==0]) # identify closest pair, arbitrarily selecting 1st if multiple pairs a_sel <- a[d==min_d & x==0][1] b_sel <- b[d==min_d & a == a_sel & x==0][1] x[a==a_sel & b == b_sel] <- 1 x[x==0 & (a==a_sel|b==b_sel)] <- -1 } cbind(a=a[x==1],b=b[x==1],d=d[x==1]) } data.frame(greedyAssign(as.character(d$a_name),as.character(d$b_name),d$dist)) 

Produce il compito:

  abd 1 Ace Co Ace Co. 0.04762 2 Bayes Bayes Inc. 0.16667 3 asd asdf 0.08333 

Sono sicuro che c'è un modo molto più elegante per fare l'ingorda assegnazione euristica, ma quanto sopra funziona per me.

Caso many-to-one (non un problema di assegnazione):

 do.call(rbind, unname(by(d, d$a_name, function(x) x[x$dist == min(x$dist),]))) 

Produce il risultato:

  a_name b_name dist 1 Ace Co Ace Co. 0.04762 11 Baes Bayes Inc. 0.20000 8 Bayes Bayes Inc. 0.16667 12 Bays Bayes Inc. 0.20000 10 Bcy Bayes Inc. 0.37778 15 asd asdf 0.08333 

Modifica: usa method="jw" per produrre i risultati desiderati. Vedi help("stringdist-package")

Non sono sicuro che questa sia una direzione utile per te, John Andrews, ma ti offre un altro strumento (dal pacchetto RecordLinkage ) e potrebbe aiutarti.

 install.packages("ipred") install.packages("evd") install.packages("RSQLite") install.packages("ff") install.packages("ffbase") install.packages("ada") install.packages("~/RecordLinkage_0.4-1.tar.gz", repos = NULL, type = "source") require(RecordLinkage) # it is not on CRAN so you must load source from Github, and there are 7 dependent packages, as per above compareJW <- function(string, vec, cutoff) { require(RecordLinkage) jarowinkler(string, vec) > cutoff } a<-data.frame(name=c('Ace Co','Bayes', 'asd', 'Bcy', 'Baes', 'Bays'),price=c(10,13,2,1,15,1)) b<-data.frame(name=c('Ace Co.','Bayes Inc.','asdf'),qty=c(9,99,10)) a$name <- as.character(a$name) b$name <- as.character(b$name) test <- compareJW(string = a$name, vec = b$name, cutoff = 0.8) # pick your level of cutoff, of course data.frame(name = a$name, price = a$price, test = test) > data.frame(name = a$name, price = a$price, test = test) name price test 1 Ace Co 10 TRUE 2 Bayes 13 TRUE 3 asd 2 TRUE 4 Bcy 1 FALSE 5 Baes 15 TRUE 6 Bays 1 FALSE 

Concordato con la risposta di cui sopra ” Non ho familiarità con AGREP ma ecco un esempio di utilizzo di stringdist per la tua matrice di distanze. ” Ma la funzione di firma come di seguito dall’unione di insiemi di dati basati su elementi di dati parzialmente abbinati sarà più accurata dal momento che il calcolo di LV è basato in posizione / aggiunta / cancellazione

 ##Here's where the algorithm starts... ##I'm going to generate a signature from country names to reduce some of the minor differences between strings ##In this case, convert all characters to lower case, sort the words alphabetically, and then concatenate them with no spaces. ##So for example, United Kingdom would become kingdomunited ##We might also remove stopwords such as 'the' and 'of'. signature=function(x){ sig=paste(sort(unlist(strsplit(tolower(x)," "))),collapse='') return(sig) } 

Io uso lapply per queste circostanze:

 yournewvector: lapply(yourvector$yourvariable, agrep, yourothervector$yourothervariable, max.distance=0.01), 

quindi scriverlo come csv non è così semplice:

 write.csv(matrix(yournewvector, ncol=1), file="yournewvector.csv", row.names=FALSE) 

Ecco una soluzione che utilizza il pacchetto fuzzyjoin . Usa syntax e stringdist come uno dei possibili tipi di corrispondenza fuzzy.

Come suggerito da C8H10N4O2, il metodo stringdist = “jw” crea le corrispondenze migliori per il tuo esempio.

Come suggerito da dgrtwo, lo sviluppatore di fuzzyjoin, ho usato un grande max_dist e poi dplyr::group_by usato dplyr::group_by e dplyr::top_n per ottenere solo la migliore corrispondenza con la distanza minima.

 a <- data.frame(name = c('Ace Co', 'Bayes', 'asd', 'Bcy', 'Baes', 'Bays'), price = c(10, 13, 2, 1, 15, 1)) b <- data.frame(name = c('Ace Co.', 'Bayes Inc.', 'asdf'), qty = c(9, 99, 10)) library(fuzzyjoin) library(dplyr) stringdist_join(a, b, by = "name", mode = "left", ignore_case = FALSE, method = "jw", max_dist = 99, distance_col = "dist" ) %>% group_by(name.x) %>% top_n(1, -dist) #> # A tibble: 6 x 5 #> # Groups: name.x [6] #> name.x price name.y qty dist #>      #> 1 Ace Co 10 Ace Co. 9 0.04761905 #> 2 Bayes 13 Bayes Inc. 99 0.16666667 #> 3 asd 2 asdf 10 0.08333333 #> 4 Bcy 1 Bayes Inc. 99 0.37777778 #> 5 Baes 15 Bayes Inc. 99 0.20000000 #> 6 Bays 1 Bayes Inc. 99 0.20000000 

Ecco cosa ho usato per ottenere il numero di volte che una società appare in una lista anche se i nomi delle società sono partite inesatte,

step.1 Installa il pacchetto phonics

step.2 crea una nuova colonna chiamata “soundexcodes” in “mylistofcompanynames”

step.3 Usa la funzione soundex per restituire i codici soundex dei nomi delle società in “soundexcodes”

step.4 Copia i nomi delle aziende AND il codice soundex corrispondente in un nuovo file (2 colonne chiamate “companynames” e “soundexcode”) chiamato “companysoundexcodestrainingfile”

step.5 Rimuovere i duplicati di soundexcodes in “companysoundexcodestrainingfile”

step.6 Passare attraverso l’elenco dei nomi di società rimanenti e modificare i nomi come si desidera che compaia nella società originale

esempio: Amazon Inc A625 può essere Amazon A625 Accenture Limited A455 può essere Accenture A455

step.6 Eseguire un left_join o (simple vlookup) tra companysoundexcodestrainingfile $ soundexcodes e mylistofcompanynames $ soundexcodes di “soundexcodes”

step.7 Il risultato dovrebbe avere la lista originale con una nuova colonna chiamata “co.y” che ha il nome della compagnia nel modo in cui l’hai lasciata nel file di allenamento.

step.8 Ordinare “co.y” e verificare se la maggior parte dei nomi di società sono abbinati correttamente, in tal caso sostituire i vecchi nomi di società con quelli nuovi forniti da vlookup del codice soundex.