十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Polars DataFrame 聚合方法全解析:逐列归约与水平聚合的 API 语义与底层实现

Polars DataFrame 聚合方法全解析:逐列归约与水平聚合的 API 语义与底层实现 Polars DataFrame 聚合方法全解析逐列归约与水平聚合的 API 语义与底层实现【免费下载链接】polarsExtremely fast Query Engine for DataFrames, written in Rust项目地址: https://gitcode.com/GitHub_Trending/po/polarsPolars 为DataFrame提供了一整套将整张表归约为单行的聚合 API覆盖计数、最值、求和、均值、中位数、方差、标准差、连乘与分位数等统计量并提供按列聚合与按行横向聚合两种维度。本文以官方 API 参考文档 aggregation.rst 所列的 13 个方法为主线逐一讲解调用方式、空值与类型语义并结合 Python 包装层与 Rust 内核源码说明这些聚合是如何被执行的帮助你写出既正确又高效的数据归约代码。一、聚合 API 全景13 个方法分三类Polars 中DataFrame的聚合 API 覆盖三个维度。整份参考索引来自 aggregation.rst其 autosummary 列表如下逐列vertical / per-column归约结果为单行 DataFramecount、max、min、sum、mean、median、std、var、product、quantile横向horizontal / per-row归约结果为与行数等长的 Seriesmax_horizontal、min_horizontal、sum_horizontal、mean_horizontal两种维度都能直接以df.方法名()的方式无参数或极少参数调用逐列聚合对每一列独立计算后拼成一行横向聚合则对每一行跨列计算。全部方法的 Python 实现集中在 frame.py其中每个方法的 docstring 都附有完整可复现的输出示例。二、逐列聚合把每一列压缩成一个标量逐列聚合的共同形状约定是输入是 N 行 DataFrame输出永远是(1, C)的单行 DataFrame列名保持不变列数与输入一致。这组方法对应LazyFrame上同名方法见 lazyframe/frame.py并经由self.lazy().xxx()._collect_eager(...)在构建逻辑计划后立即执行详见下文执行路径一节。2.1 count按列统计非空值数量与直觉不同count统计的不是行数而是每列中非 null 元素的个数import polars as pl df pl.DataFrame({a: [1, 2, 3, 4], b: [1, 2, 1, None], c: [None] * 4}) print(df.count())输出shape: (1, 3) ┌─────┬─────┬─────┐ │ a ┆ b ┆ c │ │ --- ┆ --- ┆ --- │ │ u32 ┆ u32 ┆ u32 │ ╞═════╪═════╪═════╡ │ 4 ┆ 3 ┆ 0 │ └─────┴─────┴─────┘注意输出列类型是u32全 null 的c列计数为 0 而非 nulldocstring 见 frame.py。如果需要的是行数 / 每列长度应改用df.height或pl.len()与count的语义区分清楚。2.2 min / max数值与字符串都适用的最值min与max是唯二天然支持字符串列的数值型归约按字典序取最值也可用于数值、布尔列。以max为例df pl.DataFrame({foo: [1, 2, 3], bar: [6, 7, 8], ham: [a, b, c]}) print(df.max())shape: (1, 3) ┌─────┬─────┬─────┐ │ foo ┆ bar ┆ ham │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ str │ ╞═════╪═════╪═════╡ │ 3 ┆ 8 ┆ c │ └─────┴─────┴─────┘df.min()得到1 / 6 / a列类型同样保持不变。如果列内含有 null归约会忽略它们全 null 列结果为 null。2.3 sum求和布尔按 0/1 参与sum对数值列求和布尔列视为 0/1 参与并输出整数字符串列无法求和结果为 null见 frame.py 的示例输出中ham列值为nulldf pl.DataFrame({foo: [1, 2, 3], bar: [6, 7, 8], ham: [a, b, c]}) print(df.sum())shape: (1, 3) ┌─────┬─────┬──────┐ │ foo ┆ bar ┆ ham │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ str │ ╞═════╪═════╪══════╡ │ 6 ┆ 21 ┆ null │ └─────┴─────┴──────┘2.4 mean平均值统一输出浮点mean对数值列取算术平均并统一转换为f64有意思的是布尔列也会参与——把True视为 1、False视为 0null 被忽略因此[True, False, None]的均值是 0.5。字符串列输出 nulldf pl.DataFrame( {foo: [1, 2, 3], bar: [6, 7, 8], ham: [a, b, c], spam: [True, False, None]} ) print(df.mean())shape: (1, 4) ┌─────┬─────┬──────┬──────┐ │ foo ┆ bar ┆ ham ┆ spam │ │ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ str ┆ f64 │ ╞═════╪═════╪══════╪══════╡ │ 2.0 ┆ 7.0 ┆ null ┆ 0.5 │ └─────┴─────┴──────┴──────┘2.5 median中位数median取每列中位数并输出f64。对奇数个元素中位数是排序后正中间的值如[1, 2, 3]的中位数为 2.0字符串列输出 nulldf pl.DataFrame({foo: [1, 2, 3], bar: [6, 7, 8], ham: [a, b, c]}) print(df.median())shape: (1, 3) ┌─────┬─────┬──────┐ │ foo ┆ bar ┆ ham │ │ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ str │ ╞═════╪═════╪══════╡ │ 2.0 ┆ 7.0 ┆ null │ └─────┴─────┴──────┘2.6 std / var标准差与方差可调 ddofstd与var都接受一个仅有关键词参数ddofDelta Degrees of Freedom默认 1公式中分母为N - ddof。这意味着默认结果对应样本标准差/样本方差传ddof0则得到总体标准差/总体方差df pl.DataFrame({foo: [1, 2, 3], bar: [6, 7, 8], ham: [a, b, c]}) print(df.std()) # ddof1 → foo: 1.0, bar: 1.0, ham: null print(df.std(ddof0)) # foo: 0.816497, bar: 0.816497 print(df.var()) # foo: 1.0, bar: 1.0 print(df.var(ddof0)) # foo: 0.666667, bar: 0.666667字符串列两类方法都输出 null。实现层面ddof以整数传入并最终落到 Rust 的std_reduce(ddof)/var_reduce(ddof)见下文 Rust reducer 说明。2.7 product连乘仅数值与布尔product对每列求连乘返回列类型与输入一致。它的特殊性在于实现是逐列构造表达式遍历 schema 时只对数值与布尔列调用F.col(name).product()其余类型的列用F.lit(None).alias(name)生成占位 null见 frame.pydf pl.DataFrame({a: [1, 2, 3], b: [0.5, 4, 10], c: [True, True, False]}) print(df.product())shape: (1, 3) ┌─────┬──────┬─────┐ │ a ┆ b ┆ c │ │ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ i64 │ ╞═════╪══════╪═════╡ │ 6 ┆ 20.0 ┆ 0 │ └─────┴──────┴─────┘布尔列按 0/1 参与连乘因此只要出现过False0结果即为 0输出提升为整数类型。仓库测试用例 test_df.py 的test_product覆盖了该行为。2.8 quantile任意分位数 六种插值策略quantile(quantile, interpolation)返回输入列在指定分位处的取值支持范围0.0 ~ 1.0的浮点分位数。第二个参数interpolation的合法取值在类型别名 QuantileMethod 中定义共六种interpolation含义nearest取最接近的分位观测值默认higher取不小于目标的最近观测值lower取不大于目标的最近观测值midpoint取上下两个最近观测值的中点linear在相邻观测值间线性插值equiprobable等概率分位策略当分位数恰好命中排序后的某个位置时如0.5与奇数个元素各策略结果一致。文档中的基准示例即用quantile(0.5, nearest)模拟中位数结果列统一为f64字符串列输出 nulldf pl.DataFrame({foo: [1, 2, 3], bar: [6, 7, 8], ham: [a, b, c]}) print(df.quantile(0.5, nearest))shape: (1, 3) ┌─────┬─────┬──────┐ │ foo ┆ bar ┆ ham │ │ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ str │ ╞═════╪═════╪══════╡ │ 2.0 ┆ 7.0 ┆ null │ └─────┴─────┴──────┘Rust 端对应的插值枚举定义在 rolling/mod.rs默认策略即Nearest与 Python 层签名interpolation: QuantileMethod nearest保持一致。三、水平聚合按行跨列归约产出 Series水平聚合与逐列聚合互补它们对每一行在多个列上计算归约因此行数不变返回值是一根Series列名分别为max/min/sum/mean。在DataFrame上它们通过select(F.max_horizontal(F.all()))之类的调用把所有列传入函数级 API 后再to_series()取回单列见 frame.py。四个函数级入口定义在 horizontal.py均接受可迭代的表达式/列名/字面量参数。3.1 max_horizontal / min_horizontal逐行比较各列取最大/最小值null 视为缺失并被忽略整行全为 null 时结果才为 null。混合整型与浮点列时结果按 supertype 提升为f64df pl.DataFrame({foo: [1, 2, 3], bar: [4.0, 5.0, 6.0]}) print(df.max_horizontal()) # [4.0, 5.0, 6.0] print(df.min_horizontal()) # [1.0, 2.0, 3.0]函数级用法允许只挑选部分列例如pl.max_horizontal(a, b)或传入任意表达式。空值与边界行为含 NaN 与全 null 列由仓库测试 test_horizontal.py 中的test_max_min_nulls_consistency、test_min_max_horizontal_with_nan_28682等用例覆盖。3.2 sum_horizontal / mean_horizontalignore_nulls 开关这两个方法多一个仅有关键词参数ignore_nulls默认True。其为True时跳过 null 只对非空值聚合置为False后只要某行出现任一 null该行结果即为 null——这是横向聚合中最需要留意的空值传播开关df pl.DataFrame({foo: [1, 2, 3], bar: [4.0, 5.0, 6.0]}) print(df.sum_horizontal()) # [5.0, 7.0, 9.0] print(df.mean_horizontal()) # [2.5, 3.5, 4.5] df2 pl.DataFrame({a: [1, 8, 3], b: [4, 5, None]}) print(df2.sum_horizontal()) # 第 3 行为 3忽略 null print(df2.sum_horizontal(ignore_nullsFalse)) # 第 3 行为 nullmean 的示例来自 horizontal.py。需要额外说明的是若全部列均为 null 且ignore_nullsTruesum_horizontal返回 0 而mean_horizontal返回 null对应的全部 null/无列等边界场景见 test_horizontal.py 的test_mean_horizontal_all_null、test_sum_null_dtype。混合不同类型时水平归约会先做 supertype 提升如整型 浮点 → 浮点字符串参与sum_horizontal时行为不同建议先select出数值列再聚合相关讨论可参照 test_horizontal.py 的字符串用例。四、执行路径从 DataFrame 方法到 Rust 内核理解这些方法快在哪里有助于在正确场景使用它们。以df.max()为例其 Python 实现并非在 Python 层逐列循环而是return self.lazy().max()._collect_eager(optimizationsQueryOptFlags._eager())即先把 DataFrame 提升为LazyFrame、挂载聚合节点再以 eager 方式立即收集执行见 frame.py。min / sum / mean / median / std / var / quantile / count全部走同一套lazy 计划 立即执行的通路从而复用查询优化器对聚合的规划能力product与水平聚合则通过构造表达式后在select中求值。在内核一侧每种逐列聚合最终对应Column上的一个归约函数。仓库在 column/mod.rs 中集中提供了min_reduce、max_reduce、median_reduce、mean_reduce、std_reduce(ddof)、var_reduce(ddof)、sum_reduce、product、quantile_reduce(quantile, method)等方法且同时处理普通 Series 与 Scalar常量列两种内部表示——对 Scalar 列会先物化为极小的 Series 再归约以保证数值语义一致。换句话说Python 层 13 个方法把用户意图翻译为聚合表达式真正的归约计算全部在 Rust 中以向量化、SIMD 友好的方式完成。五、实用要点速查形状记忆逐列聚合输出单行 DataFrame1 × C水平聚合输出与行数等长的 Seriescount统计非空数而非行数。字符串列min/max支持字符串字典序sum/mean/median/std/var/quantile/product对字符串列产出 nullproduct通过占位 null 表达式实现。布尔列sum、mean、product会将其当作 0/1 参与计算mean结果提升为f64。ddof 语义std/var默认ddof1样本统计量设 0 即为总体统计量。quantile 插值六种策略中nearest为默认效果与median在 0.5 分位点一致奇数样本时。水平聚合空值max/min_horizontal忽略 nullsum/mean_horizontal默认忽略 null置ignore_nullsFalse可让任何 null 扩散到结果行。性能路径DataFrame 聚合均经由 LazyFrame 计划与_collect_eager执行适合将整列压缩为标量的批量统计任务若后续还要分组或追加其他变换应优先使用 lazy APILazyFrame.max/sum/...把聚合与变换合并成一次查询。六、继续深入聚合方法的全部实现与 docstringframe.pyLazyFrame 侧的等价聚合 API用于链式查询lazyframe/frame.py水平聚合函数级入口可指定任意列组合horizontal.pyRust 内核归约实现min_reduce等column/mod.rs分位插值策略的 Rust 枚举 rolling/mod.rs横向聚合行为测试test_horizontal.py聚合相关回归测试test_aggregations.py掌握这 13 个方法的语义与空值/类型规则后再遇到给每列算一个统计量或按行拼出总分/最大值这类需求你就可以直接用最简短、可读的 DataFrame 聚合调用完成同时放心它们底层跑在 Polars 的 Rust 归约内核上。【免费下载链接】polarsExtremely fast Query Engine for DataFrames, written in Rust项目地址: https://gitcode.com/GitHub_Trending/po/polars创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表