3

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

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

我已经查看amatchstringdist函数以及agrep,但是这些都需要一个主名称列表,用于匹配另一个名称列表。就我而言,我没有这样的主列表,所以我想通过识别具有高度相似模式的名称来从数据中创建一个,这样我就可以查看它们并确定它们是否是同一个人(在许多他们是)。我想要一个新列中的输出,它可以帮助我知道这些是可能的匹配项,并且可能是基于 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

这样的事情可能吗?

4

2 回答 2

7

根据此处找到的帖子,我发现分层文本聚类可以满足我的要求。

  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)

集群 2

我可以通过将替换值的成本增加到 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

在此处输入图像描述

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)

在此处输入图像描述

尽管有一些方法可以让数据整理出最佳聚类数,而不是手动尝试选择数字,但我发现使用试错法最容易,尽管这里有关于该方法的信息。

于 2014-12-15T09:18:16.813 回答
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这里的填写是一个 hack,如果您决定走这条路,您需要用您的样本的平均 ascii 代码替换它。

对于与总体人口的相似度得分,您可以取其他词的平均距离的倒数。

于 2014-12-14T11:42:49.890 回答