本文介绍一种结合模糊字符串匹配与日期容差策略的稳健合并方法,适用于球员姓名拼写不一致(如全名/简称)、出生日期存在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() 的精确匹配必然失败,需引入语义级对齐策略。
核心思路是分步解决两个关键不一致性:
以下是完整可运行的实现方案(需先安装依赖:pip install pandas fuzzywuzzy python-Levenshtein):
import pandas as pdfrom fuzzywuzzy import processfrom 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 Rating0 1 Cristiano Ronaldo 1985-02-05 La Liga 90 911 2 Lionel Messi 1987-06-24 La Liga 91 922 3 Neymar Jr. 1992-02-05 Ligue 1 92 933 4 Erling Haaland 1991-06-28 Premier League 93 94
✅ 关键注意事项:
该方法将数据融合从机械对齐升级为语义对齐,是体育、医疗、金融等多源异构数据整合中的通用范式。