我一直致力于根据不完美的字符串(例如公司名称)加入两个数据集。在过去,我必须匹配两个非常脏的列表,一个列表有名称和财务信息,另一个列表有名称和地址。没有唯一的ID匹配! 认为清洁已经应用,可能有类型和插入。
到目前为止,AGREP是我发现可能有效的最接近的工具。我可以在AGREP包中使用levenshtein距离,它测量两个字符串之间的删除,插入和替换的数量。 AGREP将返回距离最小(最相似)的字符串。
但是,我一直无法从单个值转换此命令以将其应用于整个数据帧。我粗略地使用for循环来重复AGREP功能,但是这样做会更容易。
请参阅以下代码:
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))
}
答案 0 :(得分:11)
解决方案取决于匹配a
到b
的所需基数。如果它是一对一的,你将得到上面三个最接近的匹配。如果它是多对一的,你将获得六个。
一对一案例(需要分配算法):
当我不得不这样做之前,我把它当作一个带有距离矩阵和赋值启发式(下面使用的贪婪赋值)的赋值问题。如果您想要“最佳”解决方案,最好使用optim
。
不熟悉AGREP,但这里是使用stringdist
作为距离矩阵的示例。
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))
产生作业:
a b d
1 Ace Co Ace Co. 0.04762
2 Bayes Bayes Inc. 0.16667
3 asd asdf 0.08333
我确信有一种更优雅的方式来做贪婪的任务启发式,但上面的方法对我有用。
多对一案例(不是分配问题):
do.call(rbind, unname(by(d, d$a_name, function(x) x[x$dist == min(x$dist),])))
产生结果:
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
修改:使用method="jw"
生成所需的结果。见help("stringdist-package")
答案 1 :(得分:8)
以下是使用fuzzyjoin
包的解决方案。它使用dplyr
- 类似语法和stringdist
作为模糊匹配的可能类型之一。
C8H10N4O2为suggested,stringdist
方法=&#34; jw&#34;为您的示例创建最佳匹配。
作为模糊连接的开发者dgrtwo的suggested,我使用了大的max_dist,然后使用dplyr::group_by
和dplyr::top_n
来获得最小距离的最佳匹配。
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
答案 2 :(得分:2)
我不确定这对你来说是否是一个有用的方向,约翰安德鲁斯,但它为你提供了另一个工具(来自RecordLinkage
包),可能会有帮助。
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
答案 3 :(得分:1)
Agreed with above answer "Not familiar with AGREP but here's example using stringdist for your distance matrix." but add-on the signature function as below from Merging Data Sets Based on Partially Matched Data Elements will be more accurate since the calculation of LV is based on position/addition/deletion
##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)
}
答案 4 :(得分:1)
我在这些情况下使用?.OS_Process.status
:
lapply
然后把它写成csv它不是那么简单:
yournewvector: lapply(yourvector$yourvariable, agrep, yourothervector$yourothervariable, max.distance=0.01),
答案 5 :(得分:0)
Approximate String Matching 近似匹配一个字符串到另一个。例如banana
和 bananas
。
Fuzzy Matching 正在寻找字符串中的近似模式。例如banana
内的 bananas in pyjamas
。
方法 | R 实现 | |
---|---|---|
基础 | Bitap≈Levenshtein | b$name <- lapply(b$name, agrep, a$name, value=TRUE); merge(a,b) |
高级 | ?stringdist::stringdist-metrics |
fuzzyjoin::stringdist_join(a, b, mode='full', by=c('name'), method='lv') |
模糊匹配 | TRE | agrep2 <- function(pattern, x) x[which.min(adist(pattern, x, partial=TRUE))]; b$name <- lapply(b$name, agrep2, a$name); merge(a, b) |
# Data
a <- data.frame(name=c('Ace Co.', 'Bayes Inc.', 'asdf'), qty=c(9,99,10))
b <- data.frame(name=c('Ace Company', 'Bayes', 'asd', 'Bcy', 'Baes', 'Bays'), price=c(10,13,2,1,15,1))
# Basic
c <- b
c$name.b <- c$name
c$name <- lapply(c$name, agrep, a$name, value=TRUE)
merge(a, c, all.x=TRUE)
# Advanced
fuzzyjoin::stringdist_join(a, b, mode='full')
# Fuzzy Match
c <- b
c$name.b <- c$name
c$name <- lapply(c$name, function(pattern, x) x[which.min(adist(pattern, x, partial=TRUE))], a$name)
merge(a, c)
答案 6 :(得分:-1)
以下是我用于获取公司在列表中出现次数的情况,尽管公司名称是不精确的匹配,
step.1 安装拼音包
step.2 在“mylistofcompanynames”中创建一个名为“soundexcodes”的新列
step.3 使用soundex函数在“soundexcodes”中返回公司名称的soundex代码
step.4 将公司名称和相应的soundex代码复制到名为“companysoundexcodestrainingfile”的新文件(名为“companynames”和“soundexcode”的2列)中
step.5 在“companysoundexcodestrainingfile”中删除重复的soundexcodes
step.6 浏览剩余公司名称列表,并根据您希望在原始公司中显示的名称进行更改
<强> 例如: 强> 亚马逊公司的A625可以是亚马逊A625 埃森哲有限公司A455可以是埃森哲A455
step.6 通过“soundexcodes”在companysysdexcodestrainingfile $ soundexcodes和mylistofcompanynames $ soundexcodes之间执行left_join或(简单vlookup)
step.7 结果应该包含一个名为“co.y”的新列的原始列表,该列的名称与您在培训文件中保留的名称相同。
step.8 对“co.y”进行排序并检查大多数公司名称是否正确匹配,如果是,请将旧公司名称替换为soundex代码的vlookup给出的新公司名称。