
在数据分析实践中,我们经常需要对dataframe进行分组操作,并对每个组内的特定列计算聚合统计量。然而,有时这些统计量的计算需要满足额外的条件,例如,只有当组内记录数达到一定阈值时才进行计算,并将计算结果映射回原始dataframe的每一行。本教程将以一个具体的场景为例,演示如何使用pandas的groupby()、transform()、nunique()和where()等功能组合,优雅地解决这类问题。
假设我们有一个包含交易日期(CALDT)、实体ID(ID)和收益(Return)的DataFrame。我们的目标是:
首先,我们创建示例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()进行条件筛选。
首先,我们根据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的行也计算出了统计量,但根据需求,它应该被排除。
现在我们需要应用条件:只有当每个分组的唯一CALDT数量大于或等于2时,才保留计算出的统计量。Pandas的where()方法非常适合这种场景。where(condition, other=NaN)会根据condition布尔Series来选择值:如果条件为True,则保留原值;如果条件为False,则替换为other(默认为NaN)。
我们可以再次利用groupby()和transform()来获取每个组的唯一CALDT数量,并将其与2进行比较。
# 计算每个组的唯一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。
最后一步是将计算出的条件性统计量合并回原始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)transform() vs. agg():
链式操作与可读性: 虽然可以将所有操作链式写在一起,但为了提高代码的可读性和调试便利性,将其分解为几个逻辑步骤(如本教程所示)通常是更好的实践。
性能: Pandas的groupby().transform()操作通常是高度优化的,尤其对于内置的聚合函数(如mean, median, nunique)。它比使用循环或apply自定义函数通常更高效。
条件复杂性: 如果条件逻辑变得非常复杂,无法直接通过transform()和内置函数实现,您可以考虑使用apply()配合自定义函数。但在这种情况下,需要手动确保返回的Series长度与组内元素数量匹配,以便正确地广播回原始DataFrame。
本教程展示了如何利用Pandas强大的groupby()结合transform()和where()方法,在DataFrame中实现复杂的条件性分组聚合,并将结果高效地映射回原始数据。这种模式在处理需要基于组内特征进行条件判断的统计分析任务时非常有用,能够帮助我们编写出既高效又简洁的数据处理代码。理解transform()的工作原理及其与agg()的区别,是掌握Pandas高级数据操作的关键。
以上就是Pandas DataFrame高级分组聚合:条件计算与结果映射的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号