如何在Polars中基于groupid与thresholds列对数据列执行众数聚合?
如何在Polars中基于groupid与thresholds列对数据列执行众数聚合?
我在Polars中加载了如下格式的数据:
| uid | groupid | thresholds | class | data1 | data2 | data3 | data4 |
|---|---|---|---|---|---|---|---|
| X1 | X | 0.0 | 0 | 1 | 1 | 1 | |
| X2 | X | 0.0 | 0 | 1 | 1 | 1 | |
| X3 | X | 0.0 | 0 | 1 | 1 | 1 | |
| Y1 | Y | 0.0 | 1 | 1 | 1 | 1 | |
| Y2 | Y | 0.0 | 1 | 1 | 1 | 1 | |
| Y3 | Y | 0.0 | 1 | 1 | 1 | 1 | |
| X1 | X | 1.0 | 0 | 0 | 0 | 0 | |
| X2 | X | 1.0 | 0 | 0 | 0 | 0 | |
| X3 | X | 1.0 | 0 | 0 | 0 | 0 | |
| Y1 | Y | 1.0 | 1 | 0 | 0 | 0 | |
| Y2 | Y | 1.0 | 1 | 0 | 0 | 0 | |
| Y3 | Y | 1.0 | 1 | 0 | 0 | 0 | |
| X1 | X | 2.0 | 0 | 0 | 0 | 0 | |
| X2 | X | 2.0 | 0 | 0 | 0 | 0 | |
| X3 | X | 2.0 | 0 | 0 | 0 | 0 | |
| Y1 | Y | 2.0 | 1 | 0 | 0 | 0 | |
| Y2 | Y | 2.0 | 1 | 0 | 0 | 0 | |
| Y3 | Y | 2.0 | 1 | 0 | 0 | 0 |
我的需求是:按照groupid和thresholds分组,对所有data开头的列计算众数,最终得到如下的聚合结果:
| groupid | thresholds | class | data1 | data2 | data3 | data4 |
|---|---|---|---|---|---|---|
| X | 0.0 | 0 | 1 | 1 | 1 | |
| Y | 0.0 | 1 | 1 | 1 | 1 | |
| X | 1.0 | 0 | 0 | 0 | 0 | |
| Y | 1.0 | 1 | 0 | 0 | 0 | |
| X | 2.0 | 0 | 0 | 0 | 0 | |
| Y | 2.0 | 1 | 0 | 0 | 0 |
我一开始尝试用Polars的窗口函数来实现:
lf.with_column( col("^data[0-9]*$") .mode() .over([col("thresholds"), col("groupid")]), )
但运行后出现了以下错误:
the length of the window expression did not match that of the group Error originated in expression: 'col("data1").mode().over([col("thresholds"), col("groupid")])'
我还试过用over_with_options方法搭配WindowMapping::Join选项(之后再展开结果列),但在大数据集上这种方式速度太慢,而且Polars文档也提到这个操作内存开销很大。我现在不太理解这个错误到底是什么意思,也需要更高效的实现方式。
问题分析与解决方案
你遇到的错误核心原因是:窗口函数期望每个分组的计算结果能广播到该分组的所有行,但mode()聚合在某些情况下会返回多个值的列表(比如某个分组的data列有多个众数),这就导致了返回结果的长度和分组内的行数不匹配,从而抛出错误。
而你的需求是得到每个分组的聚合结果,最适合的方式不是用窗口函数,而是直接使用分组聚合——这也是Polars优化最充分的操作之一,效率远高于窗口函数的方式。
正确的分组聚合实现(Rust版本)
lf.group_by([col("thresholds"), col("groupid"), col("class")]) .agg( col("^data[0-9]*$") .mode() .alias(|col_name| col_name.to_string()) // 保持原列名 )
代码说明:
- 指定分组键:通过
group_by把thresholds、groupid和class作为分组依据(因为你的目标结果里class和groupid是一一对应的,必须加入分组才能得到正确的结果结构) - 执行众数聚合:用
agg方法对所有匹配^data[0-9]*$的列执行mode()聚合,并用alias保持原列名不变 - 高效性:分组聚合是Polars的核心优化场景,在大数据集上的内存开销和执行速度都会远优于窗口函数的方案,也不会出现长度不匹配的错误。
如果确实需要用窗口函数(给原表每行附加分组众数)
如果你需要保留原表的所有行,同时给每行添加对应分组的众数,可以对mode()的结果做处理,确保返回单个值:
lf.with_columns( col("^data[0-9]*$") .mode() .list() // 确保结果转为列表类型 .first() // 取列表中的第一个众数,保证返回单个值 .over([col("thresholds"), col("groupid")]) )
这种方式会把每个分组的众数(取第一个)广播到该分组的所有行,解决长度不匹配的问题,但还是那句话:如果你的需求只是得到分组后的聚合结果,优先用分组聚合的方案。
备注:内容来源于stack exchange,提问作者Ganrelka
相关产品推荐
相关产品推荐

