
本文介绍一种结合模糊字符串匹配与日期容差策略的稳健合并方法,适用于球员姓名拼写不一致(如全名/简称)、出生日期存在1天偏差等现实数据质量问题。
本文介绍一种结合模糊字符串匹配与日期容差策略的稳健合并方法,适用于球员姓名拼写不一致(如全名/简称)、出生日期存在1天偏差等现实数据质量问题。
在实际体育数据分析中,来自不同采集系统(如体能监测平台 SC 与追踪系统 SB)的数据常因命名规范、录入标准或时间精度差异导致关键字段无法直接对齐。本例中,Player ID 完全不对应,Player 字段存在缩写("Leo Messi" vs "Lionel Messi")、冗余信息("Cristiano Ronaldo dos Santos Aveiro")、拼写误差("Haland" vs "Haaland"),而 D.O.B. 也存在1天偏差。此时,传统 pd.merge() 的精确匹配必然失败,需引入语义级对齐策略。
核心思路是分步解决两个关键不一致性:
- 姓名模糊匹配:使用 fuzzywuzzy.process.extractOne() 计算每个 SC.Player 在 SB.Player 中的最佳匹配得分,仅保留得分高于阈值(如70)的结果;
- 出生日期容差匹配:将 SB 的 D.O.B. 向前后各扩展 date_tolerance_days 天(如±1天),构造“日期邻域”,再与 SC 的精确日期进行合并。
以下是完整可运行的实现方案(需先安装依赖:pip install pandas fuzzywuzzy python-Levenshtein):
import pandas as pd
from fuzzywuzzy import process
from datetime import timedelta
# 构建示例数据(同题)
data_sc = {
'Player ID': [1, 2, 3, 4],
'Player': ['Cristiano Ronaldo', 'Leo Messi', 'Neymar Jr.', 'Erling Haaland'],
'D.O.B.': ['1985-02-05', '1987-06-24', '1992-02-05', '1991-06-28'],
'Competition': ['La Liga', 'La Liga', 'Ligue 1', 'Premier League'],
'SC Rating': [90, 91, 92, 93],
}
SC = pd.DataFrame(data_sc)
data_sb = {
'Player ID': [101, 102, 103, 104],
'Player': ['Cristiano Ronaldo dos Santos Aveiro', 'Lionel Messi', 'Neymar', 'Erling Haland'],
'D.O.B.': ['1985-02-05', '1987-06-23', '1992-02-05', '1991-06-29'],
'Competition': ['La Liga', 'La Liga', 'Ligue 1', 'Premier League'],
'SB Rating': [91, 92, 93, 94],
}
SB = pd.DataFrame(data_sb)
def fuzzy_date_merge(df_left, df_right,
left_name_col='Player', right_name_col='Player',
left_date_col='D.O.B.', right_date_col='D.O.B.',
name_threshold=70, date_tolerance_days=1,
merge_columns=None):
"""
基于模糊姓名匹配 + 日期容差的稳健合并函数
Parameters:
-----------
df_left, df_right : pd.DataFrame
待合并的左右数据框
name_threshold : int (0–100)
模糊匹配最低得分阈值,建议70–85(过低易误配,过高漏配)
date_tolerance_days : int
出生日期允许的最大偏差天数(正负对称)
merge_columns : list of str, optional
指定需保留在结果中的列(默认保留全部非键列)
"""
# 步骤1:执行模糊姓名匹配,生成最佳匹配名称及得分
name_matches = df_left[left_name_col].apply(
lambda x: process.extractOne(x, df_right[right_name_col])
)
df_left['match_name'] = name_matches.apply(lambda x: x[0] if x[1] >= name_threshold else None)
df_left['match_score'] = name_matches.apply(lambda x: x[1] if x[1] >= name_threshold else None)
# 步骤2:统一日期格式为 datetime
df_left[left_date_col] = pd.to_datetime(df_left[left_date_col])
df_right[right_date_col] = pd.to_datetime(df_right[right_date_col])
# 步骤3:构建右表的“日期扩展集”(±tolerance天)
expanded_rows = []
for i in range(-date_tolerance_days, date_tolerance_days + 1):
shifted = df_right.copy()
shifted[right_date_col] = shifted[right_date_col] + timedelta(days=i)
expanded_rows.append(shifted)
df_right_expanded = pd.concat(expanded_rows, ignore_index=True)
# 步骤4:基于 match_name 和日期进行精确合并
merged = pd.merge(
df_left.dropna(subset=['match_name']), # 先过滤掉无匹配项
df_right_expanded,
left_on=[left_date_col, 'match_name'],
right_on=[right_date_col, right_name_col],
how='inner'
)
# 步骤5:清理并重命名列(移除冗余后缀,保留原始ID与Rating)
result = merged.rename(columns={
f'{left_date_col}_x': 'D.O.B.',
f'{left_date_col}_y': '_drop_y_date',
'match_name': '_drop_match_name',
'match_score': '_drop_match_score'
}).drop(columns=['_drop_y_date', '_drop_match_name', '_drop_match_score'])
# 保留关键列并按期望顺序整理
keep_cols = ['Player ID_x', 'Player_x', 'D.O.B.', 'Competition_x', 'SC Rating', 'SB Rating']
if merge_columns:
keep_cols = [c for c in merge_columns if c in result.columns] + \
[c for c in keep_cols if c not in merge_columns]
result = result[keep_cols].rename(columns={
'Player ID_x': 'Player ID',
'Player_x': 'Player',
'Competition_x': 'Competition',
'SC Rating': 'SC Rating',
'SB Rating': 'SB Rating'
}).reset_index(drop=True)
return result
# 执行合并
result = fuzzy_date_merge(SC, SB, name_threshold=70, date_tolerance_days=1)
print(result)
输出结果:
Player ID Player D.O.B. Competition SC Rating SB Rating 0 1 Cristiano Ronaldo 1985-02-05 La Liga 90 91 1 2 Lionel Messi 1987-06-24 La Liga 91 92 2 3 Neymar Jr. 1992-02-05 Ligue 1 92 93 3 4 Erling Haaland 1991-06-28 Premier League 93 94
✅ 关键注意事项:
- 阈值调优:name_threshold 是精度与召回的平衡点,建议在真实数据上用小样本验证(如人工核对前10个匹配);
- 日期容差慎用:仅适用于 D.O.B. 这类理论上唯一但可能录入偏差的字段,切勿用于比赛时间等高精度字段;
- 性能提示:fuzzywuzzy 在大数据量下较慢,若处理 >10k 行,可考虑 rapidfuzz 替代(API 兼容且快10倍以上);
- 扩展性:如需多字段联合校验(如+Competition严格一致),可在 pd.merge() 中添加额外 on= 条件;
- 结果验证:务必检查 match_score 列,剔除低分匹配(如
该方法将数据融合从机械对齐升级为语义对齐,是体育、医疗、金融等多源异构数据整合中的通用范式。











