单个列表中的近似字符串匹配 - r

时间:2014-12-14 11:04:52

标签: r string-matching agrep

我在长列表中有数千个名称的数据框中有一个列表。许多名称在它们之间存在细微差别,这使它们略有不同。我想找到一种匹配这些名称的方法。例如:

names <- c('jon smith','jon, smith','Jon Smith','jon smith et al','bob seger','bob, seger','bobby seger','bob seger jr.')

我查看了amatch函数中的stringdist以及agrep,但这些都需要master list of names来匹配另一个名称列表反对。在我的情况下,我没有这样的主列表,所以我想通过识别具有高度相似模式的名称从数据中创建一个,这样我就可以查看它们并确定它们是否是同一个人(在许多人中)他们是的情况)。我想在新专栏中输出一些信息,帮助我知道这些是可能的匹配,也许是基于Levenshtein距离的某种相似性得分。也许是这样的:

            names   match      SimilarityScore
1       jon smith     a               9
2      jon, smith     a               8
3       Jon Smith     a               9
4 jon smith et al     a               5
5       bob seger     b               9
6      bob, seger     b               8
7     bobby seger     b               7
8   bob seger jr.     b               5

这样的事情可能吗?

2 个答案:

答案 0 :(得分:4)

利用找到的帖子here,我发现分层文本聚类将完成我正在寻找的内容。

  names <- c('jon smith','jon, smith','Jon Smith','jon smith et al','bob seger','bob, seger','bobby seger','bob seger jr.','jake','jakey','jack','jakeyfied')

# Levenshtein Distance
e  <- adist(names)
rownames(e) <- names
hc <- hclust(as.dist(e))
plot(hc)
rect.hclust(hc,k=3) #the k value provides the number of clusters
df <- data.frame(names,cutree(hc,k=3))

如果您选择正确数量的聚类(在这种情况下为三个),输出看起来非常好:

                       names             cutree.hc..k...3.
jon smith             jon smith                 1
jon, smith           jon, smith                 1
Jon Smith             Jon Smith                 1
jon smith et al jon smith et al                 1
bob seger             bob seger                 2
bob, seger           bob, seger                 2
bobby seger         bobby seger                 2
bob seger jr.     bob seger jr.                 2
jake                       jake                 3
jakey                     jakey                 3
jack                       jack                 3
jakeyfied             jakeyfied                 3

但是,名称通常比这更复杂,在添加一些更难的名称后,我发现默认的adist选项没有提供最佳的聚类:

names <- c('jon smith','jon, smith','Jon Smith','jon smith et al','bob seger','bob, seger','bobby seger','bob seger jr.','jake','jakey','jack','jakeyfied','1234 ranch','5678 ranch','9983','7777')

d  <- adist(names)
rownames(d) <- names
hc <- hclust(as.dist(d))
plot(hc)
rect.hclust(hc,k=6)

Cluster2

我能够通过将替换值的成本增加到2并将插入和删除成本保留为1并忽略大小写来改进这一点。这有助于最大限度地减少错误分组完全不同的四个字符数字字符串,我不想分组:

d  <- adist(names,ignore.case=TRUE, costs=c(i=1,d=1,s=2)) #i=insertion, d=deletion s=substitution
rownames(d) <- names
hc <- hclust(as.dist(d))
plot(hc)
rect.hclust(hc,k=6

enter image description here

我通过删除诸如&#34; ranch&#34;等常用术语来进一步微调聚类。和&#34;等人&#34;使用gsub包中的grep工具并将群集数量增加一个:

names<-gsub("ranch","",names)
names<-gsub("et al","",names)
d  <- adist(names,ignore.case=TRUE, costs=c(i=1,d=1,s=2))
rownames(d) <- names
hc <- hclust(as.dist(d))
plot(hc)
rect.hclust(hc,k=7)

enter image description here

尽管有一些方法可以让数据整理出最佳数量的群集,而不是手动尝试选择数字,但我发现尽管有信息here about that approach,但最容易使用反复试验。

答案 1 :(得分:2)

Roman在自然语言处理评论中的建议可能是最好的起点。但是对于背面信封类型的方法,你可以用ascii代码来看待距离:

mynames = c("abcd efghijkl mn","zbcd efghijkl mn","bbcd efghijkl mn","erqe")
asc <- function(x) { strtoi(charToRaw(x),16L) }
namesToChar= sapply(mynames, asc)
maxLength= max(unlist(lapply(namesToChar,length)))
namesToChar =lapply(namesToChar, function(x) { c(x, rep(-1, times = maxLength-length(x) )) } )
namesToChar = do.call("rbind",namesToChar)
dist(namesToChar,method="euclidean")
dist(namesToChar,method="canberra")

虽然它似乎为样本提供了足够的数字,

> dist(namesToChar,method="manhattan")
                 abcd efghijkl mn zbcd efghijkl mn bbcd efghijkl mn
zbcd efghijkl mn               25                                  
bbcd efghijkl mn                1               24                 
erqe                          257              274              256

这种方法的缺点是,对于你想要做的事情,dist函数似乎没有足够的距离方法。元素二进制比较后跟一个更标准的距离('曼哈顿'似乎最接近你的需要)?你当然可以自己实现它。另外-1填写是一个黑客攻击,如果你决定走这条路,你需要用你的样本的平均ascii代码替换它。

对于相对于整体人口的相似性得分,您可以将平均距离与每个其他单词的倒数相反。