
本文介绍在 Polars 中对 DataFrame 进行“补全扩展”(complete expansion)的两种主流方法:一种是利用 cross join + right join 的显式组合,另一种是更简洁、可扩展的 unique().implode().explode() 链式方案,并对比其可读性、性能与通用性。
本文介绍在 polars 中对 dataframe 进行“补全扩展”(complete expansion)的两种主流方法:一种是利用 `cross join` + `right join` 的显式组合,另一种是更简洁、可扩展的 `unique().implode().explode()` 链式方案,并对比其可读性、性能与通用性。
在数据分析中,常需将稀疏观测补全为完整网格——例如,确保所有 (A, B) 组合都存在,缺失项以 null 填充。Polars 当前(v1.0+)尚未原生支持类似 pandas janitor.complete() 或 R tidyr::complete() 的语法,但可通过组合操作高效实现。
以下是最推荐的通用写法:
import polars as pl
df = pl.DataFrame({
'A': [0, 1, 1],
'B': [1, 1, 2],
'C': [6, 7, 8]
})
# ✅ 推荐:简洁、可扩展、语义清晰
expanded = (
df.select(pl.col(['A', 'B']).unique().sort().implode())
.explode('A')
.explode('B')
.join(df, how='left', on=['A', 'B'])
)
print(expanded)输出:
shape: (4, 3) ┌─────┬─────┬──────┐ │ A ┆ B ┆ C │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 │ ╞═════╪═════╪══════╡ │ 0 ┆ 1 ┆ 6 │ │ 0 ┆ 2 ┆ null │ │ 1 ┆ 1 ┆ 7 │ │ 1 ┆ 2 ┆ 8 │ └─────┴─────┴──────┘
该方案优势明显:
-
可扩展性强:只需修改
select()中的列名列表(如['A','B','D']),即可支持任意多列的联合笛卡尔补全; -
逻辑清晰:
unique().implode()构建各列唯一值集合 →explode()展开为全组合 →left join关联原始数据; - 性能稳健:避免中间 DataFrame 复制,充分利用 Polars 的惰性计算与列式优化。
相比之下,原始方案:
df.join(df.select('A').unique().join(df.select('B').unique(), how='cross'), on=['A','B'], how='right')虽可行,但存在两点不足:
① 列顺序错乱(结果中 C 被提前,A/B 后置),需额外 select() 调整;
② 扩展至三列时需嵌套多次 join,代码冗长且易出错。
⚠️ 注意事项:
-
explode()要求被展开列为List类型,因此必须先implode();sort()非必需,但能保证输出顺序稳定,便于调试; - 若某列含
null,unique()默认保留它;如需排除空值,可加.filter(pl.col('A').is_not_null()); - 对超大规模数据(如百万级唯一值组合),笛卡尔积本身会产生
O(n×m)行,务必确认内存与业务合理性。
综上,unique().implode().explode().join() 是当前 Polars 中实现“补全扩展”的最佳实践——兼顾简洁性、可维护性与工程鲁棒性。

















