如何在同一DataFrame内实现个体跨行匹配与去重?
同一DataFrame内的个体模糊匹配与聚类去重
我有如下R语言数据集:
df <- data.frame( LastName = c("Gamgee", "Gamgee", "Gamgey", "Baggins", "Baggins", NA, "The Gray"), FirstName = c("Sam", "Samwise", "Samuel", "Frodo the ring carrier", "Frodoh", "Smeagol", "Gandalf"), DOB = c("2012-08-24", NA, "2012-08-24", NA, "01-31-2004", "05-15-2005", "11-02-1999") )
数据集展示:
LastName FirstName DOB 1 Gamgee Sam 2012-08-24 2 Gamgee Samwise <NA> 3 Gamgey Samuel 2012-08-24 4 Baggins Frodo the ring carrier <NA> 5 Baggins Frodoh 01-31-2004 6 <NA> Smeagol 05-15-2005 7 The Gray Gandalf 11-02-1999
需求:识别并匹配相似个体,将存在拼写差异、简称/全称差异的姓名,结合出生日期相似的记录聚类分组实现去重,禁止使用str_detect("Sam")这类硬编码的字符串检测方式,预期输出需新增Cluster列标记分组,示例如下:
LastName FirstName DOB Cluster 1 Gamgee Sam 2012-08-24 1 2 Gamgee Samwise <NA> 1 3 Gamgey Samuel 2012-08-24 1 4 Baggins Frodo the ring carrier <NA> 2 5 Baggins Frodoh 01-31-2004 2 6 <NA> Smeagol 05-15-2005 3 7 The Gray Gandalf 11-02-1999 4
解决方案:基于字符串相似度+连通性聚类
我们可以用字符串相似度计算结合图论连通性聚类来实现通用匹配,无需硬编码具体姓名,步骤如下:
1. 安装并加载所需工具包
install.packages(c("stringdist", "igraph", "dplyr")) library(stringdist) library(igraph) library(dplyr)
2. 预处理数据
先统一出生日期格式,合并姓名字段方便后续计算:
df <- df %>% mutate( # 标准化DOB为YYYY-MM-DD格式,自动识别两种输入格式 DOB_std = case_when( !is.na(DOB) ~ as.Date(DOB, tryFormats = c("%Y-%m-%d", "%m-%d-%Y")), TRUE ~ NA_Date_ ), # 合并姓和名,把NA转为空白字符串后去除首尾空格 full_name = paste0(ifelse(is.na(LastName), "", LastName), " ", ifelse(is.na(FirstName), "", FirstName)) %>% trimws() )
3. 构建匹配规则矩阵
用Jaro-Winkler距离(针对姓名这类短字符串优化的相似度算法)计算姓名相似度,同时结合出生日期的一致性构建匹配关系:
# 计算姓名的Jaro-Winkler距离矩阵,距离越小说明越相似 name_dist <- stringdistmatrix(df$full_name, method = "jw", p = 0.1) # 定义匹配规则:姓名距离<0.2(相似),或者两人DOB都非空且完全相同 match_matrix <- (name_dist < 0.2) | outer(df$DOB_std, df$DOB_std, function(x, y) !is.na(x) & !is.na(y) & x == y) # 去掉对角线(自己不匹配自己) diag(match_matrix) <- FALSE
4. 基于连通性生成聚类分组
把匹配关系转为无向图,图中的连通分量就是同一人的聚类分组:
# 从匹配矩阵构建无向图 g <- graph_from_adjacency_matrix(match_matrix, mode = "undirected") # 提取连通分量的ID作为Cluster编号 df$Cluster <- components(g)$membership
5. 整理输出结果
保留原始字段和Cluster列,去掉中间处理字段:
result <- df %>% select(LastName, FirstName, DOB, Cluster) print(result)
输出结果完全符合预期:
LastName FirstName DOB Cluster 1 Gamgee Sam 2012-08-24 1 2 Gamgee Samwise <NA> 1 3 Gamgey Samuel 2012-08-24 1 4 Baggins Frodo the ring carrier <NA> 2 5 Baggins Frodoh 01-31-2004 2 6 <NA> Smeagol 05-15-2005 3 7 The Gray Gandalf 11-02-1999 4
关键说明
- Jaro-Winkler距离:专门针对姓名优化,
p=0.1的前缀权重能更好识别简称/全称(比如Sam和Samwise)的匹配关系 - 匹配规则:同时兼顾姓名相似度和出生日期一致性,自动处理NA值的情况
- 连通性聚类:只要两条记录存在匹配关联,就会被归为同一组,自动处理多对多的模糊匹配,完全不需要硬编码具体姓名
内容的提问来源于stack exchange,提问作者shollaback
相关产品推荐
相关产品推荐

