多条件关联下更新Pandas DataFrame列的最佳实践探讨
Pandas条件更新DataFrame的最优实现方式
先明确我们的数据源:
import pandas as pd df1 = pd.DataFrame([["SAP ", 1, 'CHANGE_NEEDED', 10, 11], ["SAP123", 1, 'CORRECT_VALUE', 10, 11], ["NOTSAP123", 1, 'INCORRECT_VALUE', 10, 11], ["NOTSAP456", 1, 'INCORRECT_VALUE', 10, 11]], columns = ["RCD_ORIG_CD", "VEN_ID", "VLC_VEN_ID", "CNTRY_CD", "CO_CD"]) df2 = pd.DataFrame([["SAP_CORRECT_VALUE", 1, 'CORRECT_VALUE', 12, 13], ["SAB123",1, "THIS_WONT_BE_USED", 12,13], ["NOTSAP123", 1, 'CORRECT_VALUE', 10, 11], ["NOTSAP457", 1, 'INCORRECT_VALUE', 10, 11]], columns = ["RCD_ORIG_CD", "VEN_ID", "VLC_VEN_ID", "CNTRY_CD", "CO_CD"])
df1初始状态:
RCD_ORIG_CD VEN_ID VLC_VEN_ID CNTRY_CD CO_CD 0 SAP 1 CHANGE_NEEDED 10 11 1 SAP123 1 CORRECT_VALUE 10 11 2 NOTSAP123 1 INCORRECT_VALUE 10 11 3 NOTSAP456 1 INCORRECT_VALUE 10 11
df2初始状态:
RCD_ORIG_CD VEN_ID VLC_VEN_ID CNTRY_CD CO_CD 0 SAP_CORRECT_VALUE 1 CORRECT_VALUE 12 13 1 SAB123 1 THIS_WONT_BE_USED 12 13 2 NOTSAP123 1 CORRECT_VALUE 10 11 3 NOTSAP457 1 INCORRECT_VALUE 10 11
需要执行的更新规则整理如下:
- 如果df1的
RCD_ORIG_CD前3位是"SAP":- 找到df2中
VEN_ID相同且RCD_ORIG_CD前3位也是"SAP"的行,将df1的VLC_VEN_ID替换为该行的对应值;若df1的RCD_ORIG_CD是带空格的"SAP ",同时把该字段替换为df2对应的RCD_ORIG_CD - 找不到符合条件的df2行,就把df1的
VLC_VEN_ID设为8个空格
- 找到df2中
- 如果df1的
RCD_ORIG_CD不是SAP开头,但和df2的RCD_ORIG_CD、VEN_ID、CNTRY_CD、CO_CD完全匹配:- 将df1的
VLC_VEN_ID替换为df2对应的VLC_VEN_ID
- 将df1的
- 其他情况,把df1的
VLC_VEN_ID设为8个空格
方案分析与最佳实践
为什么别用iterrows()
直接用iterrows()逐行循环是最直观但最糟糕的选择——Pandas的核心优势是向量化操作,底层基于numpy的C实现,而逐行循环相当于把Pandas当成普通列表用,数据量稍微大一点就会慢到离谱,完全浪费了Pandas的性能优势,绝对不推荐。
推荐方案:向量化关联+条件判断
核心思路是通过预筛选+多条件关联,把需要匹配的行先对应起来,再用np.where或者loc批量更新,全程避免循环。
代码实现
import pandas as pd import numpy as np # 1. 添加辅助列,标记是否为SAP开头的行 df1['is_sap'] = df1['RCD_ORIG_CD'].str[:3] == 'SAP' df2['is_sap'] = df2['RCD_ORIG_CD'].str[:3] == 'SAP' # 2. 准备SAP场景的匹配:按VEN_ID关联df2的SAP行(去重避免行膨胀) sap_df2 = df2[df2['is_sap']].drop_duplicates('VEN_ID') df1 = df1.merge(sap_df2[['VEN_ID', 'VLC_VEN_ID', 'RCD_ORIG_CD']], on='VEN_ID', how='left', suffixes=('', '_sap')) # 3. 准备非SAP场景的匹配:按多键完全匹配 non_sap_df2 = df2[~df2['is_sap']] df1 = df1.merge(non_sap_df2[['RCD_ORIG_CD', 'VEN_ID', 'CNTRY_CD', 'CO_CD', 'VLC_VEN_ID']], on=['RCD_ORIG_CD', 'VEN_ID', 'CNTRY_CD', 'CO_CD'], how='left', suffixes=('', '_non_sap')) # 4. 按规则更新VLC_VEN_ID df1['VLC_VEN_ID'] = np.where( df1['is_sap'], np.where(df1['VLC_VEN_ID_sap'].notna(), df1['VLC_VEN_ID_sap'], ' '), np.where(df1['VLC_VEN_ID_non_sap'].notna(), df1['VLC_VEN_ID_non_sap'], ' ') ) # 5. 更新带空格的SAP开头行的RCD_ORIG_CD df1.loc[(df1['is_sap']) & (df1['RCD_ORIG_CD'] == 'SAP '), 'RCD_ORIG_CD'] = df1['RCD_ORIG_CD_sap'] # 6. 清理辅助列 df1 = df1.drop(['is_sap', 'VLC_VEN_ID_sap', 'RCD_ORIG_CD_sap', 'VLC_VEN_ID_non_sap'], axis=1) print(df1)
执行后得到的df1结果:
RCD_ORIG_CD VEN_ID VLC_VEN_ID CNTRY_CD CO_CD 0 SAP_CORRECT_VALUE 1 CORRECT_VALUE 10 11 1 SAP123 1 CORRECT_VALUE 10 11 2 NOTSAP123 1 CORRECT_VALUE 10 11 3 NOTSAP456 1 10 11
关键细节说明
- 合并时用
drop_duplicates是为了避免一对多匹配导致的行膨胀,如果df2中同一个VEN_ID对应多个SAP行,需要先确定取哪一行(比如最新/最早) - 用
np.where实现嵌套条件判断,完全是向量化操作,性能比循环高几个数量级 - 最后清理辅助列,保持原DataFrame的结构整洁
内容的提问来源于stack exchange,提问作者Looz
相关产品推荐
相关产品推荐

