跨4个Dataframe标准化存在细微差异的足球俱乐部名称
我需要一个优雅且计算成本低的解决方案:手上有4个爬虫获取的DataFrame,都包含足球俱乐部名称,其中1-2个DataFrame里的名称存在细微差异,示例如下:
df1 = pd.DataFrame({'Home Team': ['France', 'Italy', 'Spain', 'Palmeiras SE']}) df2 = pd.DataFrame({'Home Team': ['France Woman', 'Italy', 'Spain', 'Palmeiras']}) df3 = pd.DataFrame({'Home Team': ['France', 'Italy', 'Spain Woman', 'Palmeiras SE']}) df4 = pd.DataFrame({'Home Team': ['France', 'Italy', 'Spain Woman', 'Palmeiras']})
实际场景中每个DataFrame有50-100条数据,且每日更新。试过fuzzywuzzy、difflib库,也考虑过引入主数据作为参考,但这些方案计算量都偏高。
已尝试的可行方案
用fuzzymatcher处理两个DataFrame
import fuzzymatcher result = fuzzymatcher.fuzzy_left_join(whoscored, olbg_data, 'Home Team', 'Home Team') output = result[['Home Team_left', 'Predicted Result_left', 'Predicted Result_right']] output.columns = ['Home Team', 'Predicted Result (Whoscored)', 'Predicted Result (OLBG Data)']
这个方案能识别像Talleres与CA Talleres de Cordoba、Germany与Germany Women这类匹配。
处理四个DataFrame时的问题
当扩展到4个DataFrame时,代码如下:
import fuzzymatcher result = fuzzymatcher.fuzzy_left_join(whoscored, olbg_data, 'Home Team', 'Home Team') output = result[['Home Team_left', 'Predicted Result_left', 'Predicted Result_right']] output.columns = ['Home Team', 'Predicted Result (Whoscored)', 'Predicted Result (OLBG Data)'] result_predictz = fuzzymatcher.fuzzy_left_join(whoscored, predictz_output, 'Home Team', 'Home Team') output = output.copy() output.loc[:, 'Predicted Result (Predictz)'] = result_predictz['Predicted Result_right'] result_vitibet = fuzzymatcher.fuzzy_left_join(whoscored, vitibet, 'Home Team', 'Home Team') output = output.copy() output.loc[:, 'Predicted Result (Vitibet)'] = result_vitibet['Predicted Result_right']
结果勉强可用,但部分存在细微差异的名称匹配后返回NaN,比如Vitibet的Predicted Result列第7行,明明对应名称差异很小却无法匹配。
更优解决方案
1. 先统一名称清洗规则,降低模糊匹配压力
先对所有DataFrame的俱乐部名称做标准化清洗,减少后续匹配的复杂度:
- 移除后缀/前缀:比如
SE、Woman/Women、CA这类常见固定标识 - 统一大小写:全部转为小写或大写
- 去除特殊字符/多余空格
示例代码:
import re def clean_team_name(name): # 转小写 name = name.lower() # 移除常见后缀/前缀 remove_patterns = [' se', ' woman', ' women', ' ca '] for pattern in remove_patterns: name = name.replace(pattern, '') # 去除多余空格和特殊字符 name = re.sub(r'\s+', ' ', name).strip() name = re.sub(r'[^a-z0-9 ]', '', name) return name # 对所有DataFrame应用清洗函数 for df in [df1, df2, df3, df4]: df['Cleaned Team'] = df['Home Team'].apply(clean_team_name)
清洗后,大部分细微差异会被抹平,后续匹配可以直接用精确匹配,计算成本大幅降低。
2. 建立轻量级映射字典,处理特殊情况
对于清洗后仍无法匹配的少数特殊名称,手动维护一个小型映射字典(每日更新数据量小,维护成本低):
team_mapping = { 'talleres': 'ca talleres de cordoba', 'germany': 'germany women' # 可根据每日新增的不匹配项补充 } # 应用映射 def map_team_name(cleaned_name): return team_mapping.get(cleaned_name, cleaned_name) for df in [df1, df2, df3, df4]: df['Mapped Team'] = df['Cleaned Team'].apply(map_team_name)
这种方式比全量模糊匹配效率高很多,仅处理少数特殊案例。
3. 基于清洗后的名称做合并,替代多次模糊连接
当所有DataFrame都有了统一的Mapped Team列后,用精确连接合并所有数据,避免多次模糊匹配的高计算量:
# 给每个DataFrame添加来源标识,方便后续列命名 df1['Source'] = 'Whoscored' df2['Source'] = 'OLBG' df3['Source'] = 'Predictz' df4['Source'] = 'Vitibet' # 合并所有DataFrame到一个大表 all_data = pd.concat([df1, df2, df3, df4], ignore_index=True) # 按统一名称 pivot,得到最终结果 final_output = all_data.pivot( index='Mapped Team', columns='Source', values='Predicted Result' ).reset_index() # 重命名列,还原可读性 final_output.columns = ['Home Team', 'Predicted Result (Whoscored)', 'Predicted Result (OLBG)', 'Predicted Result (Predictz)', 'Predicted Result (Vitibet)']
这种方式计算量极低,且结果更稳定,不会出现模糊匹配的NaN问题。
4. 可选:用快速模糊匹配工具处理剩余不匹配项
如果清洗和映射后仍有极个别不匹配,可选择用rapidfuzz(fuzzywuzzy的更快替代版)处理这部分数据,而非全量模糊匹配:
from rapidfuzz import process, fuzz # 获取所有唯一的清洗后名称 all_teams = pd.concat([df['Cleaned Team'] for df in [df1, df2, df3, df4]]).unique() # 为每个找不到匹配的名称找最相似的 def find_best_match(name, team_list): match, score, _ = process.extractOne(name, team_list, scorer=fuzz.token_set_ratio) return match if score > 80 else name # 设置匹配阈值,比如80分以上才算匹配 # 仅对有缺失的行应用匹配 final_output['Home Team'] = final_output['Home Team'].apply(lambda x: find_best_match(x, all_teams) if pd.isna(x) else x)
rapidfuzz比传统fuzzywuzzy快很多,且只处理少量数据,计算成本可控。
内容的提问来源于stack exchange,提问作者EscadeSupremo

