一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

如何稳健合并存在姓名和出生日期不一致的Pandas数据框

时间:2026-07-12 09:13:27 编辑:袖梨 来源:一聚教程网

本文介绍一种结合模糊字符串匹配与日期容差策略的稳健合并方法,适用于球员姓名拼写不一致(如全名/简称)、出生日期存在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() 的精确匹配必然失败,需引入语义级对齐策略

核心思路是分步解决两个关键不一致性:

  1. 姓名模糊匹配:使用 fuzzywuzzy.process.extractOne() 计算每个 SC.Player 在 SB.Player 中的最佳匹配得分,仅保留得分高于阈值(如70)的结果;
  2. 出生日期容差匹配:将 SB 的 D.O.B. 向前后各扩展 date_tolerance_days 天(如±1天),构造“日期邻域”,再与 SC 的精确日期进行合并。

以下是完整可运行的实现方案(需先安装依赖: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

关键注意事项:

  • 阈值调优:name_threshold 是精度与召回的平衡点,建议在真实数据上用小样本验证(如人工核对前10个匹配);
  • 日期容差慎用:仅适用于 D.O.B. 这类理论上唯一但可能录入偏差的字段,切勿用于比赛时间等高精度字段;
  • 性能提示:fuzzywuzzy 在大数据量下较慢,若处理 >10k 行,可考虑 rapidfuzz 替代(API 兼容且快10倍以上);
  • 扩展性:如需多字段联合校验(如+Competition严格一致),可在 pd.merge() 中添加额外 on= 条件;
  • 结果验证:务必检查 match_score 列,剔除低分匹配(如<65)并人工复核,避免“伪匹配”。

该方法将数据融合从机械对齐升级为语义对齐,是体育、医疗、金融等多源异构数据整合中的通用范式。

热门栏目