Pandas条件合并:遇NaN时两列合并,否则三列合并
条件化合并两个DataFrame
需求:合并两个DataFrame时,根据第一个DataFrame(df1)中Name列是否为NaN,选择不同的合并键:
- 当Name列值为NaN时,仅通过
Number、Number2两列进行左合并 - 当Name列值不为NaN时,通过
Number、Number2、Name(对应第二个DataFrame的Name2列)三列进行左合并
示例数据
DataFrame 1(df1)
| Number | Number2 | Name |
|---|---|---|
| 1 | 2 | One |
| 2 | 2 | |
| 3 | 2 | Three |
DataFrame 2(df2)
| Number | Number2 | Name2 |
|---|---|---|
| 1 | 2 | One |
| 2 | 2 | Two |
| 2 | 2 | Two.5 |
| 3 | 2 | Three |
| 3 | 2 | Three.5 |
| 4 | 2 | Four |
预期合并结果
| Number | Number2 | Name | Name2 |
|---|---|---|---|
| 1 | 2 | One | One |
| 2 | 2 | Two | |
| 2 | 2 | Two.5 | |
| 3 | 2 | Three | Three |
当前尝试的代码(存在问题)
import pandas as pd import numpy as np def merge_three_or_two(row): if row['Name'] == np.nan: row = pd.merge(row, df2, how='left', left_on=['Number','Number2'], right_on = ['Number','Number2']) else: row = pd.merge(row, df2, how='left', left_on=['Number','Number2','Name'], right_on = ['Number','Number2','Name2']) df1 = df1.apply(merge_three_or_two, axis=1)
代码问题分析
- NaN判断逻辑错误:
row['Name'] == np.nan永远返回False,因为NaN和任何值比较结果都是False,正确判断方式应为pd.isna(row['Name']) - 逐行处理效率极低:
apply逐行合并会严重拖慢大数据集的处理速度,应采用批量处理方式替代
优化后的解决方案
方法一:分数据集合并后拼接
将df1拆分为Name非NaN和Name为NaN的两部分,分别按对应规则合并后再拼接,逻辑清晰且效率高:
import pandas as pd import numpy as np # 拆分df1为两个子集 df1_non_nan = df1[~pd.isna(df1['Name'])] df1_nan = df1[pd.isna(df1['Name'])] # 非NaN子集:三键左合并 merge_non_nan = pd.merge(df1_non_nan, df2, how='left', left_on=['Number', 'Number2', 'Name'], right_on=['Number', 'Number2', 'Name2']) # NaN子集:两键左合并 merge_nan = pd.merge(df1_nan, df2, how='left', left_on=['Number', 'Number2'], right_on=['Number', 'Number2']) # 拼接结果并恢复原索引顺序 result = pd.concat([merge_non_nan, merge_nan]).sort_index().reset_index(drop=True) print(result)
方法二:先全量合并再补全匹配失败行
先执行三键合并,再针对Name为NaN且匹配失败的行,用两键合并补全结果:
import pandas as pd import numpy as np # 先执行三键合并,保留匹配状态标识 temp_merge = pd.merge(df1, df2, how='left', left_on=['Number', 'Number2', 'Name'], right_on=['Number', 'Number2', 'Name2'], indicator=True) # 筛选出需要补全的行:Name为NaN且三键合并无匹配 mask = pd.isna(temp_merge['Name']) & (temp_merge['_merge'] == 'left_only') # 对需要补全的行执行两键合并 nan_merge = pd.merge(temp_merge[mask], df2, how='left', left_on=['Number', 'Number2'], right_on=['Number', 'Number2']) # 合并最终结果,清理临时标识列 result = pd.concat([temp_merge[~mask], nan_merge]).drop(columns=['_merge']).sort_index().reset_index(drop=True) print(result)
两种方法均能得到预期结果,且批量处理的效率远高于逐行合并方案,适合处理各类规模的数据集。
内容的提问来源于stack exchange,提问作者AvocadoToast
相关产品推荐
相关产品推荐

