Pandas DataFrame高级分组聚合:条件计算与结果映射

聖光之護
发布: 2025-08-25 14:56:31
原创
1027人浏览过

Pandas DataFrame高级分组聚合:条件计算与结果映射

本教程将详细介绍如何在Pandas DataFrame中执行高级分组聚合操作。我们将学习如何根据ID和年份对数据进行分组,并仅对满足特定条件(例如,组内数据点数量不小于2)的组计算指定统计量(如均值和中位数),然后将这些结果高效地广播回原始DataFrame的相应行中,确保数据处理的准确性和效率。

在数据分析实践中,我们经常需要对dataframe进行分组操作,并对每个组内的特定列计算聚合统计量。然而,有时这些统计量的计算需要满足额外的条件,例如,只有当组内记录数达到一定阈值时才进行计算,并将计算结果映射回原始dataframe的每一行。本教程将以一个具体的场景为例,演示如何使用pandas的groupby()、transform()、nunique()和where()等功能组合,优雅地解决这类问题。

场景描述

假设我们有一个包含交易日期(CALDT)、实体ID(ID)和收益(Return)的DataFrame。我们的目标是:

  1. 根据ID和CALDT的年份对数据进行分组。
  2. 对于每个分组,如果该组的唯一CALDT月份数量大于或等于2(即该ID在当年至少“存活”了2个月),则计算该组内Return列的年化均值和年化中位数(即均值/中位数乘以12)。
  3. 将计算出的年化均值和年化中位数作为新列添加回原始DataFrame,对于不满足条件的组,对应行的这些新列值应为NaN。

准备数据

首先,我们创建示例DataFrame并进行初步的数据类型转换和年份提取:

import pandas as pd
import numpy as np

# 创建示例DataFrame
df = pd.DataFrame(
         {"CALDT": ["1980-01-31", "1980-02-28", "1980-03-31",
                    "1980-01-31", "1980-02-28", "1980-03-31",
                    "1980-01-31"],
          "ID": [1, 1, 1,
                 2, 2, 2,
                 3],
          "Return": [0.02, 0.05, 0.10,
                     0.05, -0.02, 0.03,
                     -0.03]
          })

# 将CALDT列转换为日期时间类型,并提取年份
df['CALDT'] = pd.to_datetime(df['CALDT'])
df['Year'] = df['CALDT'].dt.year

print("原始DataFrame:")
print(df)
登录后复制

输出:

原始DataFrame:
       CALDT  ID  Return  Year
0 1980-01-31   1    0.02  1980
1 1980-02-28   1    0.05  1980
2 1980-03-31   1    0.10  1980
3 1980-01-31   2    0.05  1980
4 1980-02-28   2   -0.02  1980
5 1980-03-31   2    0.03  1980
6 1980-01-31   3   -0.03  1980
登录后复制

解决方案

解决此问题的关键在于正确使用groupby()结合transform(),并利用where()进行条件筛选。

1. 分组与transform的应用

首先,我们根据ID和CALDT的年份进行分组。这里,我们不需要显式创建Year列来分组,可以直接在groupby中使用df.CALDT.dt.year。

接着,我们使用transform()方法计算每个组的Return均值和中位数,并将其乘以12进行年化。transform()的优点在于它会返回一个与原始DataFrame(或分组前的Series)长度相同的Series,将聚合结果“广播”回原始行,而不是像agg()那样返回一个聚合后的较小DataFrame。

# 根据ID和CALDT的年份进行分组
g = df.groupby(["ID", df.CALDT.dt.year])

# 使用transform计算年化均值和中位数
# transform会确保计算结果的索引与原始DataFrame对齐
mean_return_transformed = g["Return"].transform("mean").mul(12)
median_return_transformed = g["Return"].transform("median").mul(12)

# 将计算结果组合成一个新的DataFrame
return_stats = pd.DataFrame({
    "Mean_Return": mean_return_transformed,
    "Median_Return": median_return_transformed
})

print("初步计算的统计量(未应用条件):")
print(return_stats)
登录后复制

输出:

初步计算的统计量(未应用条件):
   Mean_Return  Median_Return
0         0.68           0.60
1         0.68           0.60
2         0.68           0.60
3         0.24           0.36
4         0.24           0.36
5         0.24           0.36
6        -0.36          -0.36
登录后复制

可以看到,ID=3的行也计算出了统计量,但根据需求,它应该被排除。

2. 应用条件筛选

现在我们需要应用条件:只有当每个分组的唯一CALDT数量大于或等于2时,才保留计算出的统计量。Pandas的where()方法非常适合这种场景。where(condition, other=NaN)会根据condition布尔Series来选择值:如果条件为True,则保留原值;如果条件为False,则替换为other(默认为NaN)。

我们可以再次利用groupby()和transform()来获取每个组的唯一CALDT数量,并将其与2进行比较。

Kits AI
Kits AI

Kits.ai 是一个为音乐家提供一站式AI音乐创作解决方案的网站,提供AI语音生成和免费AI语音训练

Kits AI 413
查看详情 Kits AI
# 计算每个组的唯一CALDT数量,并判断是否大于等于2
# transform("nunique") 会将每个组的唯一值数量广播回原始DataFrame的形状
condition = g["CALDT"].transform("nunique").ge(2) # .ge(2) 等同于 >= 2

# 使用where方法应用条件
return_stats_conditional = return_stats.where(condition)

print("\n应用条件后的统计量:")
print(return_stats_conditional)
登录后复制

输出:

应用条件后的统计量:
   Mean_Return  Median_Return
0         0.68           0.60
1         0.68           0.60
2         0.68           0.60
3         0.24           0.36
4         0.24           0.36
5         0.24           0.36
6          NaN            NaN
登录后复制

现在,ID=3对应的统计量已经正确地变成了NaN。

3. 合并结果

最后一步是将计算出的条件性统计量合并回原始DataFrame。由于return_stats_conditional的索引与原始df的索引是匹配的,我们可以直接使用join()方法。

# 将条件性统计量合并回原始DataFrame
df_final = df.join(return_stats_conditional)

print("\n最终结果DataFrame:")
print(df_final)
登录后复制

输出:

最终结果DataFrame:
       CALDT  ID  Return  Year  Mean_Return  Median_Return
0 1980-01-31   1    0.02  1980         0.68           0.60
1 1980-02-28   1    0.05  1980         0.68           0.60
2 1980-03-31   1    0.10  1980         0.68           0.60
3 1980-01-31   2    0.05  1980         0.24           0.36
4 1980-02-28   2   -0.02  1980         0.24           0.36
5 1980-03-31   2    0.03  1980         0.24           0.36
6 1980-01-31   3   -0.03  1980          NaN            NaN
登录后复制

这与预期的输出完全一致。

完整代码

为了清晰起见,我们将上述步骤整合到一起:

import pandas as pd
import numpy as np

# 1. 准备数据
df = pd.DataFrame(
         {"CALDT": ["1980-01-31", "1980-02-28", "1980-03-31",
                    "1980-01-31", "1980-02-28", "1980-03-31",
                    "1980-01-31"],
          "ID": [1, 1, 1,
                 2, 2, 2,
                 3],
          "Return": [0.02, 0.05, 0.10,
                     0.05, -0.02, 0.03,
                     -0.03]
          })

df['CALDT'] = pd.to_datetime(df['CALDT'])
df['Year'] = df['CALDT'].dt.year # 实际上这一步不是必须的,因为可以在groupby中直接使用dt.year

# 2. 分组并计算条件统计量
# 创建分组对象
g = df.groupby(["ID", df.CALDT.dt.year])

# 使用transform计算年化均值和中位数
# transform会自动将聚合结果广播到原始DataFrame的行数
return_stats = pd.DataFrame({
                     "Mean_Return": g["Return"].transform("mean").mul(12),
                     "Median_Return": g["Return"].transform("median").mul(12)
                  })

# 创建条件:判断每个组的唯一CALDT数量是否大于等于2
# g["CALDT"].transform("nunique") 同样将每个组的唯一值数量广播
condition_met = g["CALDT"].transform("nunique").ge(2)

# 应用条件:不满足条件的行将统计量设为NaN
return_stats_conditional = return_stats.where(condition_met)

# 3. 合并结果到原始DataFrame
df_final = df.join(return_stats_conditional)

print(df_final)
登录后复制

注意事项与最佳实践

  1. transform() vs. agg():

    • transform():当您需要将组级别的聚合结果“广播”回原始DataFrame的每一行,使其保持原始形状时,transform()是理想选择。它返回一个与原始Series或DataFrame具有相同索引和长度的Series/DataFrame。
    • agg()(或apply()):当您需要一个聚合后的、行数减少的DataFrame(例如,每个组只有一行结果)时,使用agg()。
    • 在本场景中,由于我们需要将结果添加到原始DataFrame的每一行,transform()是正确的选择。
  2. 链式操作与可读性: 虽然可以将所有操作链式写在一起,但为了提高代码的可读性和调试便利性,将其分解为几个逻辑步骤(如本教程所示)通常是更好的实践。

  3. 性能: Pandas的groupby().transform()操作通常是高度优化的,尤其对于内置的聚合函数(如mean, median, nunique)。它比使用循环或apply自定义函数通常更高效。

  4. 条件复杂性: 如果条件逻辑变得非常复杂,无法直接通过transform()和内置函数实现,您可以考虑使用apply()配合自定义函数。但在这种情况下,需要手动确保返回的Series长度与组内元素数量匹配,以便正确地广播回原始DataFrame。

总结

本教程展示了如何利用Pandas强大的groupby()结合transform()和where()方法,在DataFrame中实现复杂的条件性分组聚合,并将结果高效地映射回原始数据。这种模式在处理需要基于组内特征进行条件判断的统计分析任务时非常有用,能够帮助我们编写出既高效又简洁的数据处理代码。理解transform()的工作原理及其与agg()的区别,是掌握Pandas高级数据操作的关键。

以上就是Pandas DataFrame高级分组聚合:条件计算与结果映射的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号