0

0

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

聖光之護

聖光之護

发布时间:2025-08-25 14:56:31

|

1033人浏览过

|

来源于php中文网

原创

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进行比较。

Videoleap
Videoleap

Videoleap是一个一体化的视频编辑平台

下载
# 计算每个组的唯一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高级数据操作的关键。

相关专题

更多
Python 时间序列分析与预测
Python 时间序列分析与预测

本专题专注讲解 Python 在时间序列数据处理与预测建模中的实战技巧,涵盖时间索引处理、周期性与趋势分解、平稳性检测、ARIMA/SARIMA 模型构建、预测误差评估,以及基于实际业务场景的时间序列项目实操,帮助学习者掌握从数据预处理到模型预测的完整时序分析能力。

49

2025.12.04

数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

216

2025.10.31

C++类型转换方式
C++类型转换方式

本专题整合了C++类型转换相关内容,想了解更多相关内容,请阅读专题下面的文章。

290

2025.07.15

数据分析的方法
数据分析的方法

数据分析的方法有:对比分析法,分组分析法,预测分析法,漏斗分析法,AB测试分析法,象限分析法,公式拆解法,可行域分析法,二八分析法,假设性分析法。php中文网为大家带来了数据分析的相关知识、以及相关文章等内容。

454

2023.07.04

数据分析方法有哪几种
数据分析方法有哪几种

数据分析方法有:1、描述性统计分析;2、探索性数据分析;3、假设检验;4、回归分析;5、聚类分析。本专题为大家提供数据分析方法的相关的文章、下载、课程内容,供大家免费下载体验。

264

2023.08.07

网站建设功能有哪些
网站建设功能有哪些

网站建设功能包括信息发布、内容管理、用户管理、搜索引擎优化、网站安全、数据分析、网站推广、响应式设计、社交媒体整合和电子商务等功能。这些功能可以帮助网站管理员创建一个具有吸引力、可用性和商业价值的网站,实现网站的目标。

718

2023.10.16

数据分析网站推荐
数据分析网站推荐

数据分析网站推荐:1、商业数据分析论坛;2、人大经济论坛-计量经济学与统计区;3、中国统计论坛;4、数据挖掘学习交流论坛;5、数据分析论坛;6、网站数据分析;7、数据分析;8、数据挖掘研究院;9、S-PLUS、R统计论坛。想了解更多数据分析的相关内容,可以阅读本专题下面的文章。

499

2024.03.13

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

61

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 0.8万人学习

光速学会docker容器
光速学会docker容器

共33课时 | 1.8万人学习

时间管理,自律给我自由
时间管理,自律给我自由

共5课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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