J'ai travaillé sur un moyen de joindre deux ensembles de données basés sur une chaîne imparfaite, comme le nom d'une entreprise. Dans le passé, je devais faire correspondre deux listes très sales, une liste avait des noms et des informations financières, une autre liste avait des noms et une adresse. Ni l'un ni l'autre n'avait d'identifiants uniques pour correspondre! ASSUMEZ QUE LE NETTOYAGE A DÉJÀ ÉTÉ APPLIQUÉ ET IL PEUT ÊTRE TYPOS ET INSERTIONS.
Jusqu'à présent, AGREP est l'outil le plus proche que j'ai trouvé qui pourrait fonctionner. Je peux utiliser des distances de levenshtein dans le package AGREP, qui mesurent le nombre de suppressions, insertions et substitutions entre deux chaînes. AGREP retournera la chaîne avec la plus petite distance (la plus similaire).
Cependant, j'ai eu du mal à transformer cette commande à partir d'une seule valeur pour l'appliquer à un bloc de données entier. J'ai grossièrement utilisé une boucle for pour répéter la fonction AGREP, mais il doit y avoir un moyen plus simple.
Voir le code suivant:
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 solution dépend de la cardinalité souhaitée de votre a
à b
correspondant. Si c'est un contre un, vous obtiendrez les trois matchs les plus proches ci-dessus. Si c'est plusieurs contre un, vous en aurez six.
Cas individuel (nécessite un algorithme d'affectation):
Quand j'ai dû le faire avant de le traiter comme un problème d'affectation avec une matrice de distance et une heuristique d'affectation (affectation gourmande utilisée ci-dessous). Si vous voulez une solution "optimale", vous feriez mieux avec optim
.
Pas familier avec AGREP mais voici un exemple utilisant stringdist
pour votre matrice de distance.
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))
Produit l'affectation:
a b d
1 Ace Co Ace Co. 0.04762
2 Bayes Bayes Inc. 0.16667
3 asd asdf 0.08333
Je suis sûr qu'il existe une façon beaucoup plus élégante de faire l'heuristique d'affectation gourmande, mais ce qui précède fonctionne pour moi.
Cas plusieurs-à-un (pas un problème d'affectation):
do.call(rbind, unname(by(d, d$a_name, function(x) x[x$dist == min(x$dist),])))
Produit le résultat:
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
Edit: utilisez method="jw"
Pour produire les résultats souhaités. Voir help("stringdist-package")
Voici une solution utilisant le package fuzzyjoin
. Il utilise la syntaxe semblable à dplyr
et stringdist
comme l'un des types possibles de correspondance floue.
Comme suggéré par C8H10N4O2, la méthode stringdist
= "jw" crée les meilleures correspondances pour votre exemple.
Comme suggéré par dgrtwo, le développeur de fuzzyjoin, j'ai utilisé un grand max_dist puis j'ai utilisé dplyr::group_by
et dplyr::top_n
pour obtenir uniquement la meilleure correspondance avec une distance minimale.
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
#> <fctr> <dbl> <fctr> <dbl> <dbl>
#> 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
Je ne sais pas si c'est une direction utile pour vous, John Andrews, mais cela vous donne un autre outil (à partir du package RecordLinkage
) et pourrait aider.
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
D'accord avec la réponse ci-dessus " Pas familier avec AGREP mais voici un exemple utilisant stringdist pour votre matrice de distance." mais ajoutez- sur la fonction de signature comme ci-dessous de Fusionner des ensembles de données basés sur des éléments de données partiellement appariés sera plus précis car le calcul de LV est basé sur position/ajout/suppression
##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)
}
J'utilise lapply
pour ces circonstances:
yournewvector: lapply(yourvector$yourvariable, agrep, yourothervector$yourothervariable, max.distance=0.01),
puis l'écrire en csv ce n'est pas si simple:
write.csv(matrix(yournewvector, ncol=1), file="yournewvector.csv", row.names=FALSE)