高效处理Pandas DataFrame跨行列重复手机号置为NaN的方法
Pandas 高效处理跨行列重复手机号
问题背景
我有个Pandas DataFrame,包含acct_id(账号ID)、home(家庭电话)、work(工作电话)、mobile(移动电话)字段,所有值都是字符串。需求是:
- 同行内重复手机号:优先保留
home,其次work,剩下的置为NaN - 跨行列重复手机号:只保留首次出现的实例,其余置为
NaN
已经搞定了同行内的去重,现在需要高效处理跨行列的重复问题。
初始数据
import pandas as pd import numpy as np df_initial = pd.DataFrame({ 'acct_id': ['A1', 'A2', 'A3'], 'home': ['123-456-7890', '987-654-3210', '123-456-7890'], 'work': ['123-456-7890', '555-555-5555', '987-654-3210'], 'mobile': ['444-444-4444', '987-654-3210', '555-555-5555'] })
目标结果
df_target = pd.DataFrame({ 'acct_id': ['A1', 'A2', 'A3'], 'home': ['123-456-7890', '987-654-3210', np.nan], 'work': [np.nan, '555-555-5555', np.nan], 'mobile': ['444-444-4444', np.nan, np.nan] })
已完成的同行去重代码
# 同行内按home→work→mobile优先级去重 for idx, row in df_initial.iterrows(): seen = [] for col in ['home', 'work', 'mobile']: phone = row[col] if phone not in seen: seen.append(phone) else: df_initial.loc[idx, col] = np.nan df_interim = df_initial.copy() # 同行去重后的中间结果
低效的暴力解法(仅作反面示例)
# 遍历所有单元格,记录已出现的手机号,重复就置NaN seen_phones = set() for col in ['home', 'work', 'mobile']: for idx in df_interim.index: phone = df_interim.loc[idx, col] if pd.notna(phone) and phone in seen_phones: df_interim.loc[idx, col] = np.nan else: seen_phones.add(phone)
高效跨行列去重方案
用Pandas的长格式转换+分组标记实现,全程向量化操作,比循环快N倍:
代码实现
# 基于同行去重后的中间df_interim处理 col_priority = {'home': 0, 'work': 1, 'mobile': 2} # 1. 转长格式,同时记录行顺序和列优先级 df_melted = df_interim.melt(id_vars='acct_id', var_name='type', value_name='phone') # 标记每一行的原始顺序,保证先出现的行优先 df_melted['row_idx'] = df_melted['acct_id'].map(df_interim.reset_index().set_index('acct_id')['index']) # 标记列的优先级,保证同一行内home优先 df_melted['col_rank'] = df_melted['type'].map(col_priority) # 2. 按手机号分组,标记每个手机号的首次出现项 # 首次出现的判断:行号最小,且同一行内列优先级最高 df_melted['is_first'] = df_melted.groupby('phone').apply( lambda x: (x['row_idx'] == x['row_idx'].min()) & (x['col_rank'] == x['col_rank'].min()) ).reset_index(level=0, drop=True) # 3. 非首次出现的手机号置为NaN df_melted['phone'] = np.where((df_melted['is_first']) & pd.notna(df_melted['phone']), df_melted['phone'], np.nan) # 4. 转回宽格式,恢复原列顺序 df_final = df_melted.pivot(index='acct_id', columns='type', values='phone').reset_index() df_final.columns.name = None df_final = df_final[['acct_id', 'home', 'work', 'mobile']]
为什么这个方案高效?
- 避免了显式循环,用Pandas内置的分组、向量化操作处理,大数据集下性能碾压暴力循环
- 严格遵循了行顺序和列优先级的规则,不会出现逻辑偏差
- 代码结构清晰,后续调整优先级或字段时只需修改
col_priority字典即可
内容的提问来源于stack exchange,提问作者Noah Petrasko
相关产品推荐
相关产品推荐

