为了比较您的记录之间的相似性,我认为您可能正在寻找一种方法来将模糊逻辑匹配的度量应用于您的名称比较任务。又名:应用String Distance Function
在执行您的Record Linkage 任务时。 (如果您已经知道这一切,请原谅我 - 但这些关键字在一开始对我有很大帮助。)
有一个很棒的包stringdist 非常适合这些应用程序,但recordlinkage 可能会帮助您以最快的速度对齐数据框。
如果您希望查看最相似的名字和姓氏的值,直到最不同的值,您可以使用如下代码:
library(RecordLinkage)
library(dplyr)
id <- c(1:5) # added in to allow joining of data tables & comparison results
firstName <- c("Chris","Doug","Shintaro","Bubbles","Elsa")
lastName <- c("MacDougall","Shapiro","Yamazaki","Murphy","Elizabeth Ray")
pet <- c("Cat","Dog","Cat","Dog","Cat")
Names1 <- data.frame(id, firstName, lastName, pet)
id <- c(1:5) # added in to allow joining of data tables & comparison results
firstName2 <- c("Chris","Doug","Shintaro","Bubbles","Elsa")
lastName2 <- c("MacDougal","Shapiro","Yamazaku","Murphy","Elizabeth")
dwelling <- c("House","House","Apartment","Condo","House")
Names2 <- data.frame(id, firstName2, lastName2, dwelling)
# RecordLinkage function that calculates string distance b/w records in two data frames
Results <- compare.linkage(Names1, Names2, blockfld = 1, strcmp = T, exclude = 4)
Results
# $data1
# firstName lastName pet
# 1 Chris MacDougall Cat
# 2 Doug Shapiro Dog
# 3 Shintaro Yamazaki Cat
# 4 Bubbles Murphy Dog
# 5 Elsa Elizabeth Ray Cat
# $data2
# firstName2 lastName2 dwelling
# 1 Chris MacDougal House
# 2 Doug Shapiro House
# 3 Shintaro Yamazaku Apartment
# 4 Bubbles Murphy Condo
# 5 Elsa Elizabeth House
# $pairs
# id1 id2 id firstName lastName is_match
# 1 1 1 1 1 0.9800000 NA
# 2 2 2 1 1 1.0000000 NA
# 3 3 3 1 1 0.9500000 NA
# 4 4 4 1 1 1.0000000 NA
# 5 5 5 1 1 0.9384615 NA
# $frequencies
# id firstName lastName
# 0.200 0.200 0.125
# $type
# [1] "linkage"
# attr(,"class")
# [1] "RecLinkData"
# Trim $pairs dataframe (seen above) to contain just id's & similarity measures
PairsSelect <-
Results$pairs %>%
select(id = id1, firstNameSim = firstName, lastNameSim = lastName)
# Join original data & string comparison results together
# reorganize data to facilitate review
JoinedResults <-
left_join(Names1, Names2) %>%
left_join(PairsSelect) %>%
select(id, firstNameSim, firstName, firstName2, lastNameSim, lastName, lastName2) %>%
arrange(desc(lastNameSim), desc(firstNameSim), id)
JoinedResults
# id firstNameSim firstName firstName2 lastNameSim lastName lastName2
# 1 2 1 Doug Doug 1.0000000 Shapiro Shapiro
# 2 4 1 Bubbles Bubbles 1.0000000 Murphy Murphy
# 3 1 1 Chris Chris 0.9800000 MacDougall MacDougal
# 4 3 1 Shintaro Shintaro 0.9500000 Yamazaki Yamazaku
# 5 5 1 Elsa Elsa 0.9384615 Elizabeth Ray Elizabeth
# If you want to collect just the perfect matches
PerfectMatches <-
JoinedResults %>%
filter(firstNameSim == 1 & lastNameSim == 1) %>%
select(id, firstName, lastName)
PerfectMatches
# id firstName lastName
# 1 2 Doug Shapiro
# 2 4 Bubbles Murphy
# To collect the matches that are going to need alignment
ImperfectMatches <-
JoinedResults %>%
filter(firstNameSim < 1 | lastNameSim < 1) %>%
mutate(flgFrstNm = 0, flgLstNm = 0)
ImperfectMatches
# id firstNameSim firstName firstName2 lastNameSim lastName lastName2 flgFrstNm flgLstNm
# 1 1 1 Chris Chris 0.9800000 MacDougall MacDougal 0 0
# 2 3 1 Shintaro Shintaro 0.9500000 Yamazaki Yamazaku 0 0
# 3 5 1 Elsa Elsa 0.9384615 Elizabeth Ray Elizabeth 0 0
#
# If you want to enter your column preference in a flag column to facilitate faster rectification...
write.csv(ImperfectMatches, "ImperfectMatches.csv", na = "", row.names = F)
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
# Flag data externally - save file to new name with '_reviewed' appended to filename
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
#reload results
FlaggedMatches <- read.csv("ImperfectMatches_reviewed.csv", stringsAsFactors = F)
FlaggedMatches
## Where a 1 is the 1st data set preferred and 0 (or 2 if that is easier for the 'data processor') means the 2nd data set is preferred.
# id firstNameSim firstName firstName2 lastNameSim lastName lastName2 flgFrstNm flgLstNm
# 1 1 1 Chris Chris 0.9800000 MacDougall MacDougal 1 0
# 2 3 1 Shintaro Shintaro 0.9500000 Yamazaki Yamazaku 1 1
# 3 5 1 Elsa Elsa 0.9384615 Elizabeth Ray Elizabeth 1 0
## Executing Assembly of preferred/rectified firstName and lastName columns
ResolvedMatches <-
FlaggedMatches %>%
mutate(rectifiedFirstName = ifelse(flgFrstNm == 1,firstName, firstName2),
rectifiedLastName = ifelse(flgLstNm == 1, lastName, lastName2)) %>%
select(id, starts_with("rectified"))
ResolvedMatches
# id rectifiedFirstName rectifiedLastName
# 1 1 Chris MacDougal
# 2 3 Shintaro Yamazaki
# 3 5 Elsa Elizabeth
dplyr 非常直观,但compare.linkage() 函数可以使用一些解释。
前两个参数很明显:您要比较的两个数据帧(dataframe1 和 dataframe2)。 [如果您只想将 onedataframe 中的记录与它们自己进行比较(以对记录集进行重复数据删除),那么您可以改用 compare.dedup(),并且只引用一个 dataframe。
在这种情况下,将blockfld 设置为 1 或 2 将指定名字或姓氏的匹配必须分别为 100%。相反,您可能希望在数据集中包含主键/外键并在 blckfld 参数中引用该列。或者,如果您的记录实际上并非如此等价地构造,您可以完全忽略此参数(默认为 FALSE),然后将比较所有可能的组合 [数据帧的叉积]。
strcmp 到 TRUE 为您提供一个字符串距离函数,该函数应用于您正在比较的数据列;如果您将其保留为 false,那么它只会测试精确的 1:1 字符串对应关系。
exclude 也是一种避免构建中间数据帧并仅选择您希望相互比较的列的好方法:排除3 只是允许我们从结果中删除 Pets 和 Dwelling 比较。
上面代码中的 4 列,keyed,dataframes(不是原始问题的 3 column dataframes)产生的结果如下:
# $data1
# firstName lastName pet
# 1 Chris MacDougall Cat
# 2 Doug Shapiro Dog
# 3 Shintaro Yamazaki Cat
# 4 Bubbles Murphy Dog
# 5 Elsa Elizabeth Ray Cat
# $data2
# firstName2 lastName2 dwelling
# 1 Chris MacDougal House
# 2 Doug Shapiro House
# 3 Shintaro Yamazaku Apartment
# 4 Bubbles Murphy Condo
# 5 Elsa Elizabeth House
# $pairs
# id1 id2 id firstName lastName is_match
# 1 1 1 1 1 0.9800000 NA
# 2 2 2 1 1 1.0000000 NA
# 3 3 3 1 1 0.9500000 NA
# 4 4 4 1 1 1.0000000 NA
# 5 5 5 1 1 0.9384615 NA
# $frequencies
# id firstName lastName
# 0.200 0.200 0.125
# $type
# [1] "linkage"
# attr(,"class")
# [1] "RecLinkData"
上面的每个部分(例如 $pairs)都是它自己的数据框。
添加一个键,您可以将它们全部连接在一起,然后引用并使用成对的值 df 作为切换级别门,然后甚至将 data1 值复制到 data2 框架中,例如,当配对评级中的值 > 0.95 时.
(注意:is_match 看起来很重要,但它是用于训练匹配工具的,与我们这里的任务无关。)
无论如何,我希望您发现这些库的突然增强功能将使您能够像我第一次遇到它们时一样兴奋地投入工作。
顺便说一句:我还发现 Comparison of String Distance Algorithms 是对当前可用的字符串距离指标的一个很好的调查。