
在数据分析中,我们经常需要根据一个或多个变量对数据进行分组并计算组的大小。然而,有时业务逻辑要求我们对分组行为进行更精细的控制:如果某个分组的成员数量过少(例如,低于某个预设阈值),我们就不希望继续在该分组内进行更细粒度的划分,而是将其视为一个整体,将其统计结果向上汇总到其父级分组中。对于那些成员数量足够大的分组,我们则希望继续按照所有指定的分组键进行细致的划分。
考虑以下示例数据集:
import pandas as pd
import numpy as np
df = pd.DataFrame({
'a': [1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2],
'b': [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
'c': [1, 1, 1, 2, 2, 2, 3, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2]
})
print("原始数据框:")
print(df)我们的目标是根据列 a, b, c 进行分组。假设阈值为3。
期望的输出结果如下,其中 NaN 表示在该层级不再进行细分:
a b c group_size 0 1 1 1.0 3 1 1 1 2.0 3 2 1 2 NaN 3 3 2 2 2.0 9
Pandas的 groupby() 函数通常会将数据一直分组到所有指定键的最细粒度。例如,df.groupby(['a', 'b', 'c']).size() 会直接计算每个 (a, b, c) 组合的行数,这无法实现动态停止分组的需求。如果一个 (a, b, c) 组合的行数很少,我们无法在计算时自动将其向上合并到 (a, b) 层面。
解决这种动态分组问题的核心思想是采用迭代式的向上聚合方法。其基本流程如下:
这种方法避免了复杂的条件判断和多次数据框合并操作,利用了Pandas groupby 在多级索引上的灵活性。
下面是实现上述策略的详细步骤和代码:
import pandas as pd
import numpy as np
df = pd.DataFrame({
'a': [1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2],
'b': [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
'c': [1, 1, 1, 2, 2, 2, 3, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2]
})
thresh = 3 # 设定阈值
# 1. 初始化:获取最细粒度计数
# df.value_counts() 比 df.groupby(list(df)).size() 更高效,尤其在数据量大时
s = df.value_counts()
print("\n初始最细粒度计数 (value_counts()):")
print(s)
out = [] # 用于存储最终符合阈值的分组结果
cols = list(df.columns) # 初始分组键列表:['a', 'b', 'c']
# 2. 迭代处理:逐层向上合并
while cols and len(s):
# 根据当前 cols 进行分组并求和(s 已经是计数,这里是聚合计数)
# level=cols 表示按多级索引的指定层级进行分组
s = s.groupby(level=cols).sum()
print(f"\n当前分组键: {cols}, 聚合结果:\n{s}")
# 找出行数低于阈值的组 (m)
m = s < thresh
print(f"低于阈值 ({thresh}) 的组:\n{s[m]}")
# 将符合阈值的组(非 m)添加到结果列表 out
# s[~m] 是那些计数 >= thresh 的组
out.append(s[~m])
print(f"符合阈值的组(已提取):\n{s[~m]}")
# 更新 s,只保留低于阈值的组,供下一轮迭代处理(向上合并)
s = s[m]
print(f"待下一轮处理的组(低于阈值):\n{s}")
# 移除最右边的分组键,准备进行更粗粒度的分组
if cols: # 避免列表为空时pop()报错
cols.pop()
print(f"移除最右侧键后,剩余分组键: {cols}")
# 循环结束后,如果 s 中还有剩余的组(即所有键都移除后仍有组),则将其添加到结果中
# 这通常发生在即使只按第一个键分组,其计数仍低于阈值的情况下
if len(s):
out.append(s)
# 3. 结果整合与格式化
# 将所有结果 Series 转换为 DataFrame 并重置索引
# reset_index() 会将多级索引转换为列
out_df = pd.concat([x.reset_index() for x in out])
# 默认的计数列名为 'count',可以根据需要重命名
out_df = out_df.rename(columns={'count': 'group_size'})
# 排序以便于查看,并处理 NaN
out_df = out_df.sort_values(by=list(df.columns)).reset_index(drop=True)
print("\n最终结果:")
print(out_df)代码解析:
本教程介绍了一种在Pandas中实现动态阈值条件分组的有效策略。通过迭代地从最细粒度向上聚合,并根据预设阈值分离已完成的分组和待处理的分组,我们能够优雅地解决常规 groupby 无法满足的复杂场景。这种方法不仅逻辑清晰,而且在性能上具有优势,是处理此类条件分组问题的推荐方案。
以上就是Pandas:基于动态阈值的条件分组与聚合策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号