求助:如何在Pandas分组(groupby)内高效实现笛卡尔积
分组内高效生成笛卡尔积的优化方案
原循环+merge(cross)的方案性能瓶颈在于:每次循环拼接都会生成新DataFrame,内存开销大;且单个分组的merge(cross)未利用向量运算优势。以下是两种更高效的实现方式:
方案一:基于Numpy向量化生成索引对
利用Numpy的meshgrid快速生成分组内的行对索引,再批量从原表取数,避免多次拼接和merge的额外开销,适合大分组场景:
import pandas as pd import numpy as np # 给每个分组内的行添加序号 df['group_idx'] = df.groupby('owner').cumcount() # 获取每个分组的行数 group_sizes = df.groupby('owner')['group_idx'].max() + 1 # 生成所有分组的笛卡尔积索引对 idx_pairs = [] for size in group_sizes: x, y = np.meshgrid(np.arange(size), np.arange(size)) # 若不需要自身配对,可添加过滤条件:[x.ravel() != y.ravel()] idx_pairs.append(np.column_stack([x.ravel(), y.ravel()])) # 生成重复的owner列表,对应每个索引对 owner_repeat = np.repeat(group_sizes.index, group_sizes ** 2) # 合并所有索引对 all_idx = np.concatenate(idx_pairs) # 按索引对提取左右表数据 left_df = df.set_index(['owner', 'group_idx']).loc[zip(owner_repeat, all_idx[:,0]), :].reset_index() right_df = df.set_index(['owner', 'group_idx']).loc[zip(owner_repeat, all_idx[:,1]), :].reset_index() # 重命名右表列名避免冲突 right_df = right_df.rename(columns={col: f"{col}_right" for col in right_df.columns if col != 'owner'}) # 合并结果 result = pd.merge(left_df, right_df, on='owner').drop(['group_idx', 'group_idx_right'], axis=1)
方案二:Groupby结合MultiIndex生成笛卡尔积
利用Pandas的MultiIndex.from_product直接生成分组内的行对索引,适合分组数量多但单个分组较小的场景:
import pandas as pd def cross_group(group): n_rows = len(group) # 生成分组内所有行对的索引 pair_idx = pd.MultiIndex.from_product([group.index, group.index], names=['left_idx', 'right_idx']) # 若不需要自身配对,添加过滤:pair_idx = pair_idx[pair_idx.get_level_values(0) != pair_idx.get_level_values(1)] # 提取左右表数据并拼接 left = group.loc[pair_idx.get_level_values(0)].reset_index(drop=True) right = group.loc[pair_idx.get_level_values(1)].reset_index(drop=True).rename(columns=lambda x: f"{x}_right") return pd.concat([left, right], axis=1) # 分组处理并合并结果 result = df.groupby('owner', group_keys=False).apply(cross_group).reset_index(drop=True)
性能优化提示
- 如果不需要行与自身的配对,在生成索引对时添加过滤条件,可大幅减少最终数据量和计算时间;
- 若数据集远超内存,可考虑分块处理分组,避免一次性加载所有数据。
内容的提问来源于stack exchange,提问作者Samuel T
相关产品推荐
相关产品推荐

