在两个DataFrame中匹配多值并提取目标列(Python/Pandas)
解决DataFrame多字段匹配失败的方案
一、先统一关键匹配字段的格式
匹配失败的核心原因是phone/email/poc_lastname的格式或内容不统一,先做标准化清洗:
1. 处理Phone字段
df2的phone是科学计数法格式,df1是纯数字字符串/整数,统一转成无格式的字符串:
import pandas as pd # 处理df1的phone:转字符串,替换NaN为空值 df1['phone'] = df1['phone'].astype(str).replace('nan', '') # 处理df2的phone:先转整数去除科学计数法,再转字符串 df2['phone'] = df2['phone'].apply(lambda x: str(int(x)) if pd.notna(x) else '')
2. 处理Email字段
邮箱大小写不敏感,可能存在前后空格,统一标准化:
# 转小写、去空格、替换NaN为空值 df1['email'] = df1['email'].str.lower().str.strip().fillna('') df2['email'] = df2['email'].str.lower().str.strip().fillna('')
3. 处理poc_lastname字段
姓名存在大小写、空格差异,统一转小写并去空格:
df1['poc_lastname'] = df1['poc_lastname'].str.lower().str.strip().fillna('') df2['poc_lastname'] = df2['poc_lastname'].str.lower().str.strip().fillna('')
二、多字段精准匹配
清洗完成后,用三个字段作为匹配键执行merge,提取目标列:
# 以poc_lastname/phone/email为键合并,保留df2的所有行,匹配df1的affiliate_code merged_df = pd.merge( df2, df1[['affiliate_code', 'poc_lastname', 'phone', 'email']], on=['poc_lastname', 'phone', 'email'], how='left' ) # 提取需要的列,过滤掉未匹配到的行(可选) result = merged_df[['affiliate_code', 'poc_lastname', 'phone', 'email']].dropna(subset=['affiliate_code'])
三、模糊匹配处理拼写差异
如果仍有匹配不上的行(比如姓名拼写错误:Sweat vs Sweet,邮箱前缀差异),可以用模糊匹配补充:
1. 姓名模糊匹配(依赖fuzzywuzzy库)
先安装依赖:pip install fuzzywuzzy python-Levenshtein
from fuzzywuzzy import process # 给df2匹配相似度达标的姓氏 def match_lastname(lastname, df1_lastnames, threshold=80): match = process.extractOne(lastname, df1_lastnames) return match[0] if match[1] >= threshold else None df2['matched_lastname'] = df2['poc_lastname'].apply(lambda x: match_lastname(x, df1['poc_lastname'].unique())) # 用匹配后的姓氏+phone+email合并 merged_fuzzy = pd.merge( df2, df1[['affiliate_code', 'poc_lastname', 'phone', 'email']], left_on=['matched_lastname', 'phone', 'email'], right_on=['poc_lastname', 'phone', 'email'], how='left' )
2. 邮箱模糊匹配
拆分邮箱前缀和域名,针对前缀做模糊匹配:
from fuzzywuzzy import fuzz # 拆分邮箱前缀和域名 def split_email(email): if '@' in email: return email.split('@', 1) return '', '' df1[['email_prefix', 'email_domain']] = df1['email'].apply(split_email).apply(pd.Series) df2[['email_prefix', 'email_domain']] = df2['email'].apply(split_email).apply(pd.Series) # 匹配同域名下前缀相似度达标的记录 def find_affiliate(row, df1): candidates = df1[df1['email_domain'] == row['email_domain']] if len(candidates) == 0: return None match_scores = candidates.apply(lambda x: fuzz.ratio(x['email_prefix'], row['email_prefix']), axis=1) best_idx = match_scores.idxmax() return candidates.loc[best_idx, 'affiliate_code'] if match_scores.max() >= 85 else None df2['affiliate_code'] = df2.apply(find_affiliate, df1=df1, axis=1)
四、验证匹配结果
查看未匹配的行,针对性调整规则:
# 输出未匹配的记录,分析原因 unmatched = merged_df[merged_df['affiliate_code'].isna()] print(unmatched)
内容的提问来源于stack exchange,提问作者Astro_raf
相关产品推荐
相关产品推荐

