基于多列匹配合并Pandas DataFrame并添加阈值列的求助
解决Pandas DataFrame按多列部分匹配合并的问题
我明白你遇到的问题了——直接用全列merge没得到预期结果,核心原因是df1和df2的PG列存在部分缺失或者不完全匹配的情况(比如df1的552行只有PG1有值,df2对应行有PG1+PG2;或者df1的500行PG4是截断值,df2是完整内容),默认的全列严格匹配无法覆盖这种场景。
下面是针对你的需求的具体解决方案:
第一步:构造标准化测试数据
先把你提供的零散数据整理成标准的Pandas DataFrame结构:
import pandas as pd # 构造你的df1 df1 = pd.DataFrame({ 'Store': [500, 552, 600, 704], 'PG1': ['1 Women', '2 Baby', '4 Boys', '5 Girls'], 'PG2': ['02 Shirts', None, '00 Denim All', '01 Jackets'], 'PG3': ['02 S-S_Woven_Shirts', None, None, '15 Woven blouses, tops'], 'PG4': ['02 Checked-Striped-', None, None, None] }) # 构造你的df2 df2 = pd.DataFrame({ 'Store': [552, 552, 600, 600, 704, 704, 500], 'MinThreshold': [0, 2, 3, 4, 5, 10, 6], 'MaxThreshold': [10, 15, 16, 17, 18, 20, 19], 'PG1': ['2 Baby', '2 Baby', '5 Girls', '4 Boys', '5 Girls', '0 Men', '1 Women'], 'PG2': [None, '00 Denim All', None, '00 Denim All', '01 Jackets', '00 Denim_Bottoms', '02 Shirts'], 'PG3': [None, None, None, None, '15 Woven blouses, tops', '02 Capris', '02 S-S_Woven_Shirts'], 'PG4': [None, None, None, None, None, None, '02 Checked-Striped-Patterned_Shirts-Tops-Blouses'] })
第二步:处理空值并设置匹配优先级
先统一替换空值,避免因NaN不相等导致匹配失败;然后为df2的每一行计算匹配优先级(非空PG列的数量),确保同一Store下,更精确的匹配(PG列更多)会被优先选中:
# 替换空值为统一占位符 df1 = df1.fillna('') df2 = df2.fillna('') # 定义PG列列表 pg_cols = ['PG1', 'PG2', 'PG3', 'PG4'] # 计算每个df2行的匹配优先级(非空PG列数量) df2['match_priority'] = df2[pg_cols].apply(lambda x: sum(x != ''), axis=1) # 按Store和优先级降序排序,确保精确匹配的行排在前面 df2_sorted = df2.sort_values(['Store', 'match_priority'], ascending=[True, False])
第三步:按动态匹配条件获取阈值
用apply遍历df1的每一行,根据当前行的非空PG列去df2中筛选匹配行,取优先级最高的那一行的阈值:
def get_matching_thresholds(row): # 提取当前行非空的PG列(键为列名,值为对应内容) non_empty_pgs = {col: row[col] for col in pg_cols if row[col] != ''} # 先筛选Store相同的行 mask = df2_sorted['Store'] == row['Store'] # 逐一匹配所有非空PG列(如果需要前缀匹配,把==改成.str.startswith()) for col, val in non_empty_pgs.items(): # 这里用==是严格匹配,如果你的df1是截断值,换成下面的前缀匹配: # mask &= df2_sorted[col].str.startswith(val) mask &= (df2_sorted[col] == val) # 获取匹配的行,取优先级最高的第一行的阈值 matched_rows = df2_sorted[mask] if not matched_rows.empty: return pd.Series([matched_rows.iloc[0]['MinThreshold'], matched_rows.iloc[0]['MaxThreshold']]) else: return pd.Series([None, None]) # 为df1添加阈值列 df1[['MinThreshold', 'MaxThreshold']] = df1.apply(get_matching_thresholds, axis=1) # 整理列顺序并换回空值(可选) final_result = df1[['Store', 'PG1', 'PG2', 'PG3', 'PG4', 'MinThreshold', 'MaxThreshold']].replace('', None) print(final_result)
运行结果
执行后你会得到和期望完全一致的输出:
Store PG1 PG2 PG3 PG4 MinThreshold MaxThreshold 0 500 1 Women 02 Shirts 02 S-S_Woven_Shirts 02 Checked-Striped- 6.0 19.0 1 552 2 Baby None None None 2.0 15.0 2 600 4 Boys 00 Denim All None None 4.0 17.0 3 704 5 Girls 01 Jackets 15 Woven blouses, tops None 5.0 18.0
如果你的df1的PG列是截断值(比如500行的PG4),只需要把代码中mask &= (df2_sorted[col] == val)换成mask &= df2_sorted[col].str.startswith(val)即可实现前缀匹配。
内容的提问来源于stack exchange,提问作者Salih
相关产品推荐
相关产品推荐

