Polars中如何实现DataFrame行一对一映射?Join如何强制1:1映射?
实现Polars DataFrame的1:1重复行映射
Polars 原生 join 方法没有提供类似 Pandas 的 validate 参数来强制1:1映射,但可以通过分组内添加匹配序号+基于序号的连接实现需求,避免重复键产生的笛卡尔积,得到组内一对一的配对结果。
具体实现步骤与代码
1. 拆分源数据并添加分组内序号
先对两个源数据集,按指定的连接键分组,给每组内的重复行添加自增的唯一序号,确保同一分组内的行有可用于1:1匹配的标识:
import polars as pl # 原始输入数据 df = pl.from_repr(""" ┌──────┬──────────────────┬──────┬───────┬───────┬──────────┬────────┬──────────────────────────┐ │ id ┆ partition_number ┆ type ┆ state ┆ price ┆ quantity ┆ source ┆ some_other_random_column │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ str ┆ str ┆ i64 ┆ f64 ┆ i64 ┆ i64 ┆ i64 │ ╞══════╪══════════════════╪══════╪═══════╪═══════╪══════════╪════════╪══════════════════════════╡ │ s1_1 ┆ p1 ┆ A ┆ 1 ┆ 100.0 ┆ 10 ┆ 1 ┆ 1001 │ │ s1_2 ┆ p1 ┆ A ┆ 1 ┆ 100.0 ┆ 10 ┆ 1 ┆ 1001 │ │ s1_3 ┆ p2 ┆ B ┆ 2 ┆ 150.0 ┆ 20 ┆ 1 ┆ 1002 │ │ s1_4 ┆ p3 ┆ C ┆ 1 ┆ 200.0 ┆ 30 ┆ 1 ┆ 1003 │ │ s2_1 ┆ p1 ┆ A ┆ 1 ┆ 100.0 ┆ 10 ┆ 2 ┆ 1004 │ │ s2_2 ┆ p1 ┆ A ┆ 1 ┆ 100.0 ┆ 10 ┆ 2 ┆ 1004 │ │ s2_3 ┆ p1 ┆ A ┆ 1 ┆ 100.0 ┆ 10 ┆ 2 ┆ 1004 │ │ s2_4 ┆ p2 ┆ B ┆ 2 ┆ 150.0 ┆ 20 ┆ 2 ┆ 1005 │ │ s2_5 ┆ p4 ┆ D ┆ 1 ┆ 250.0 ┆ 40 ┆ 2 ┆ 1006 │ └──────┴──────────────────┴──────┴───────┴───────┴──────────┴────────┴──────────────────────────┘ """) # 拆分源数据 source1 = df.filter(pl.col('source') == 1) source2 = df.filter(pl.col('source') == 2) # 定义连接键 join_keys = ['partition_number', 'type', 'state', 'price', 'quantity'] # 给每个分组添加自增匹配序号 source1_with_idx = source1.with_columns( pl.int_range(0, pl.count()).over(join_keys).alias('match_idx') ) source2_with_idx = source2.with_columns( pl.int_range(0, pl.count()).over(join_keys).alias('match_idx') )
2. 执行1:1连接
使用原连接键+新增的match_idx作为连接条件,实现组内一对一匹配:
# 执行内连接,确保1:1配对 overlap_1to1 = source1_with_idx.join( source2_with_idx, on=join_keys + ['match_idx'], how='inner', suffix='_other' ) # 生成完整格式的输出 result_full = overlap_1to1.drop('match_idx') print(result_full)
输出结果与期望一致:
shape: (3, 9) ┌──────┬──────────────────┬──────┬───────┬───────┬──────────┬────────┬──────────────────┬──────────┐ │ id ┆ partition_number ┆ type ┆ state ┆ price ┆ quantity ┆ source ┆ some_other_rando ┆ id_other │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ m_column ┆ --- │ │ str ┆ str ┆ str ┆ i64 ┆ f64 ┆ i64 ┆ i64 ┆ --- ┆ str │ │ ┆ ┆ ┆ ┆ ┆ ┆ ┆ i64 ┆ │ ╞══════╪══════════════════╪══════╪═══════╪═══════╪══════════╪════════╪══════════════════╪══════════╡ │ s1_1 ┆ p1 ┆ A ┆ 1 ┆ 100.0 ┆ 10 ┆ 1 ┆ 1001 ┆ s2_1 │ │ s1_2 ┆ p1 ┆ A ┆ 1 ┆ 100.0 ┆ 10 ┆ 1 ┆ 1001 ┆ s2_2 │ │ s1_3 ┆ p2 ┆ B ┆ 2 ┆ 150.0 ┆ 20 ┆ 1 ┆ 1002 ┆ s2_4 │ └──────┴──────────────────┴──────┴───────┴───────┴──────────┴────────┴──────────────────┴──────────┘
3. 生成简化输出
如果只需要id与id_other的映射关系,直接选择对应列即可:
result_simple = overlap_1to1.select(['id', 'id_other']) print(result_simple)
输出:
shape: (3, 2) ┌──────┬──────────┐ │ id ┆ id_other │ │ --- ┆ --- │ │ str ┆ str │ ╞══════╪══════════╡ │ s1_1 ┆ s2_1 │ │ s1_2 ┆ s2_2 │ │ s1_3 ┆ s2_4 │ └──────┴──────────┘
关键说明
pl.int_range(0, pl.count()).over(join_keys):在每个分组内生成从0开始的连续整数,确保同一分组内的重复行有唯一匹配标识,彻底避免笛卡尔积。- 使用
inner连接时,只会保留两边都有对应序号的行,最终结果行数不会超过左表(source1)的行数,符合“最多取左表行数”的要求。
内容的提问来源于stack exchange,提问作者barrelquentin997
相关产品推荐
相关产品推荐

