如何基于一列包含另一列值的条件合并两个Pandas DataFrame
问题场景
假设我有两个DataFrame A和B,结构如下:
A: | B: Code Price | Name ID 1 ABC1210 128.14 | 1 TEXTABC1211987654 351 (包含A的第2行Code) 2 ABC1211 3620.10 | 2 SAMPLE12345 20 (不包含A中任何Code) 3 ABC1212 96.44 | 3 ABC1220 SAMPLETEXT 164 (包含A的第4行Code) 4 ABC1220 35.78 | 4 Sample ABC1210 Text 776 (包含A的第1行Code)
我想要把A的Code列和B的Name列关联起来,进而合并两个DataFrame,但问题在于,Code的值是作为子字符串出现在Name里的,不是精确匹配,常规的方法好像都不好用。
我之前查过df.str.contains(value)和df.isin(value)这两个方法,但它们好像每次只能处理单个值——比如我没法直接用A["Code"].isin(B["Name"])来实现批量匹配。
我目前能想到的笨办法是:遍历A中Code列的每一个值,去B的Name列里找有没有包含这个Code的行,然后把找到的行关联到对应的Code上,最后用df.merge()合并。但这种循环的方式不仅写起来麻烦,速度肯定也很慢,完全违背了Pandas矢量化操作的初衷。
有没有用Pandas或者Numpy就能实现的高效方法?是不是我漏了什么更巧妙的实现方式?
解决方案
完全不用循环!这里给你几个符合Pandas矢量化思想的高效实现思路:
方法1:正则提取+合并(最推荐,速度最快)
我们可以把A的所有Code拼接成一个正则备选模式,直接从B的Name列里提取匹配的Code,然后再合并:
import pandas as pd # 先构造示例DataFrame data_a = { "Code": ["ABC1210", "ABC1211", "ABC1212", "ABC1220"], "Price": [128.14, 3620.10, 96.44, 35.78] } data_b = { "Name": ["TEXTABC1211987654", "SAMPLE12345", "ABC1220 SAMPLETEXT", "Sample ABC1210 Text"], "ID": [351, 20, 164, 776] } A = pd.DataFrame(data_a, index=[1,2,3,4]) B = pd.DataFrame(data_b, index=[1,2,3,4]) # 把A的Code拼接成正则表达式:匹配任意一个完整的Code子串 code_pattern = '|'.join(A["Code"]) # 从B的Name中提取匹配的Code,无匹配则返回NaN B["Matched_Code"] = B["Name"].str.extract(f'({code_pattern})', expand=False) # 现在直接用merge合并即可 merged_df = pd.merge(B, A, left_on="Matched_Code", right_on="Code", how="left") print(merged_df)
运行后会自动把匹配到的Code对应的Price列合并进来,无匹配的Price会显示NaN,全程都是Pandas矢量化操作,速度比循环快N倍。
方法2:自定义匹配函数(解决Code子串重叠问题)
如果你的Code存在类似ABC12和ABC1210这种包含关系,正则可能会匹配到更短的子串,这时候可以用apply配合自定义函数,确保匹配到完整的目标Code:
# 定义函数:从Name中找出第一个出现在A的Code列表里的子串 def find_matching_code(name, code_list): for code in code_list: if code in name: return code return None # 给B添加匹配的Code列 B["Matched_Code"] = B["Name"].apply(lambda x: find_matching_code(x, A["Code"].tolist())) # 合并DataFrame merged_df = pd.merge(B, A, left_on="Matched_Code", right_on="Code", how="left")
这个方法虽然用了apply,但只是在Code列表层面循环,比逐行遍历DataFrame高效得多,还能避免子串重叠的匹配错误。
方法3:Numpy广播匹配(极致性能)
如果数据量不大,也可以用Numpy的广播特性生成匹配矩阵,全程矢量化无Python循环:
import numpy as np # 生成布尔矩阵:A的每个Code是否在B的每个Name中 match_matrix = np.array([B["Name"].str.contains(code).values for code in A["Code"]]) # 找到每个B行对应的A的Code索引,无匹配则设为-1 match_indices = np.where(match_matrix.any(axis=0), match_matrix.argmax(axis=0), -1) # 给B添加匹配的Code列 B["Matched_Code"] = np.where(match_indices != -1, A["Code"].iloc[match_indices].values, np.nan) # 合并 merged_df = pd.merge(B, A, left_on="Matched_Code", right_on="Code", how="left")
这个方法完全依赖Numpy的底层优化,性能拉满,适合对速度要求极高的小数据集场景。
备注:内容来源于stack exchange,提问作者Corsaka

