
Polars 原生不提供 median_horizontal(),但可通过 pl.concat_list() 构建每行数值列表,再调用 .list.median() 高效实现行级中位数计算,自动忽略 null 值,语义清晰且性能优异。
polars 原生不提供 `median_horizontal()`,但可通过 `pl.concat_list()` 构建每行数值列表,再调用 `.list.median()` 高效实现行级中位数计算,自动忽略 null 值,语义清晰且性能优异。
在 Polars 中进行水平(行方向)中位数计算,即对 DataFrame 每一行中指定数值列(如所有整数列)独立求中位数,是常见但原生未直接封装的操作。与已有的 pl.mean_horizontal() 不同,Polars 当前(v1.0+)尚未提供 median_horizontal() 表达式,但可通过组合现有表达式优雅、高效地实现。
✅ 推荐方案:concat_list + list.median()
这是最符合 Polars 函数式风格、纯惰性执行、零 Python 循环的首选方法:
import polars as pl
df = pl.DataFrame({
"ABC": ["foo", "bar", "foo"],
"A": [1, 2, 3],
"B": [2, 1, None],
"C": [1, 2, 3]
})
result = df.with_columns(
pl.concat_list(pl.col(pl.Int64)).list.median().alias("Horizontal Median")
)
print(result)
输出:
shape: (3, 5) ┌─────┬─────┬──────┬─────┬───────────────────┐ │ ABC ┆ A ┆ B ┆ C ┆ Horizontal Median │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 ┆ f64 │ ╞═════╪═════╪══════╪═════╪═══════════════════╡ │ foo ┆ 1 ┆ 2 ┆ 1 ┆ 1.0 │ │ bar ┆ 2 ┆ 1 ┆ 2 ┆ 2.0 │ │ foo ┆ 3 ┆ null ┆ 3 ┆ 3.0 │ └─────┴─────┴──────┴─────┴───────────────────┘
✅ 优势说明:
-
pl.col(pl.Int64)自动选取所有整数类型列(可替换为pl.col(["A","B","C"])精确指定); -
pl.concat_list()将每行对应列值聚合成一个list[i64]列,null 值被保留为null元素; -
.list.median()内置处理 null:自动过滤 null 后计算中位数(行为等价于np.nanmedian),无需额外清洗; - 完全惰性、可优化、支持流式处理,性能接近底层 Rust 实现。
⚠️ 注意事项与边界情况
- 若某行所有指定列为 null,
.list.median()返回null(符合预期); - 中位数结果统一为
f64类型(即使输入全为整数),这是 Polars 的设计约定; - 列表长度为偶数时,取中间两数的算术平均(标准中位数定义),例如
[1, 2, 3, 4] → 2.5; - 避免使用
pl.col(pl.NUMERIC_DTYPES)泛选——pl.Float64和pl.Int64混合时,concat_list会自动向上转型为list[f64],不影响.list.median()正确性,但需留意精度一致性。
? 替代方案:NumPy 集成(仅作参考)
import numpy as np
df.with_columns(
pl.Series(
"Horizontal Median",
np.nanmedian(df.select(pl.col(pl.Int64)).to_numpy(), axis=1)
)
)
⚠️ 不推荐用于生产环境:
-
.to_numpy()触发 eager 计算并拷贝数据,丧失 Polars 惰性优势; - 大数据集下内存与性能开销显著;
- 无法利用 Polars 查询优化器,且
axis=1在 NumPy 中本身较慢。
✅ 总结
| 方法 | 是否推荐 | 关键特性 |
|---|---|---|
concat_list(...).list.median() |
✅ 强烈推荐 | 惰性、高效、null 安全、语义清晰、可组合 |
np.nanmedian(...) |
❌ 不推荐 | eager、低效、破坏查询优化、耦合外部依赖 |
掌握 concat_list + list.* 模式,不仅能解决水平中位数问题,还可拓展至行级众数(.list.mode())、分位数(.list.quantile())、自定义聚合等场景,是 Polars 高级数据操作的核心范式之一。










