为了账号安全,请及时绑定邮箱和手机立即绑定

如何匹配两个数据集中的模糊匹配字符串?

/ 猿问

如何匹配两个数据集中的模糊匹配字符串?

收到一只叮咚 2019-11-29 14:35:29

我一直在研究一种基于不完善的字符串(例如公司名称)来联接两个数据集的方法。过去,我必须匹配两个非常脏的列表,一个列表包含名称和财务信息,另一个列表包含名称和地址。都没有唯一的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))

}


查看完整描述

3 回答

?
噜噜哒

该解决方案取决于您的匹配的期望基数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")


查看完整回答
反对 2019-11-29
?
慕田峪7331174

这是使用该fuzzyjoin包装的解决方案。它使用类似dplyr语法,并stringdist作为模糊匹配的可能类型之一。


如C8H10N4O2 所建议,stringdistmethod =“ jw”为您的示例创建最佳匹配。


作为建议由dgrtwo,fuzzyjoin的开发商,我用了一个大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


查看完整回答
反对 2019-11-29
?
蓝山帝景

约翰·安德鲁斯(John Andrews),我不确定这是否对您有用,但是它为您提供了另一个工具(来自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


查看完整回答
反对 2019-11-29
  • 3 回答
  • 0 关注
  • 188 浏览

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信