pandas进阶 运用pandas进行报表分析
本文详细介绍了如何利用 Pandas 在数据中执行复杂的条件性分组计算,特别是当统计结果需要根据组内特定条件(如唯一月份数)进行筛选时,将计算值广播回原始行的场景。教程将重点讲解 groupby() 结合 transform() 方法,以及如何利用 where() 进行条件性赋值,最终实现且灵活的数据处理。 1. 引言与问题背景
在数据分析中,我们经常需要对数据进行分组,计算每组的统计量。然而,有时需求比较复杂:不仅要计算统计量,还需要根据组内的某些条件来决定是否应用这些统计量,然后结果返回原始数据例如,在一个包含交易记录的数据集中,可能需要按用户id和年份分组,计算组每个的平均收益和中增量收益,但只有当某个用户在该年份的活跃月份数达到一定阈值时才计算这些统计量,否则为空。
传统的每一行。 groupby().agg() 方法会返回一个聚合后的数据框,其行数缺少原始数据框。最终聚合结果“广播”回原始行,通常需要额外的合并操作。而当引入条件判断时,问题会聚合更多。Pandas 提供了transform() 复杂方法,结合where() 函数,能够优雅地解决此类问题。2. 数据准备
首先,我们创建一个示例 Pandas DataFrame,它包含 CALDT (日期)、ID (用户 ID) 和 Return (收益) 等列。为了后续操作,我们将从 CALDT 列提取中年份。
import pandas as pdimport numpy as np#实例 DataFramedf = pd.DataFrame( {quot;CALDTquot;: [quot;1980-01-31quot;, quot;1980-02-28quot;, quot;1980-03-31quot;, quot;1980-01-31quot;, quot;1980-02-28quot;, quot;1980-03-31quot;, quot;1980-01-31quot;], quot;IDquot;: [1, 1, 1, 2, 2, 2, 3], quot;返回quot;: [0.02, 0.05, 0.10, 0.05, -0.02, 0.03, -0.03] })# 将计算机辅助诊断技术 列转换为日期时间类型 df['CALDT'] = pd.to_datetime(df['CALDT'])# 导出年份,虽然可以直接在 groupby 中使用,但为了清楚起见,这里有显式创建 df['Year'] = df['CALDT'].dt.yearprint(quot;原始 DataFrame:quot;)print(df) 登录后复制
原始 DataFrame 示例输出: CALDT ID Return Year0 1980-01-31 1 0.02 19801 1980-02-28 1 0.05 19802 1980-03-31 1 0.10 19803 1980-01-31 2 0.05 19804 1980-02-28 2 -0.02 19805 1980-03-31 2 0.03 19806 1980-01-31 3 -0.03 1980登录后
我们的目标是:对于每个ID和年份的组合(即每个分组),如果该分组的唯一活跃则月份数(通过CALDT的唯一值大小)大于约2个月,计算该分组的复制的年化指令(均值* 12)和年化中位数(中位数* 12),把这些值赋值回原始数据框的相应行;否则,这些统计量应为NaN。 使用groupby()和transform()进行条件性计算
解决这个问题的核心在于groupby()和transform()的结合使用。3.1 groupby()分组
首先,我们可以需要根据ID和Year对数据进行分组。在Pandas中,直接在groupby()中指定多个列:#按ID和Year 分组g = df.groupby([quot;IDquot;,, df.CALDT.dt.year])登录后复制
这里,我们直接使用 df.CALDT.dt.year 作为分区键之一,避免了额外创建年份列的步骤(如果该列仅用于分区)。
3.2 transform() 广播统计量
transform()方法在groupby()对象上调用时,会执行聚合操作,但其结果会被“广播”回原始DataFrame的形状,即每个分组内的所有行都会获得相同的聚合值。这与agg()不同,agg()返回的是每个分组的单体聚合值。
我们将返回计算的熟练和中点,并乘以12进行年化:#计算年化和中缀,并使用transform广播回原始行mean_return_transformed = g[quot;Returnquot;].transform(quot;meanquot;).mul(12)median_return_transformed = g[quot;Returnquot;].transform(quot;medianquot;).mul(12)#将这些结果组合成一个临时的DataFramereturn_stats = pd.DataFrame({ quot;Mean_Returnquot;:mean_return_transformed, quot;Median_Returnquot;: median_return_transformed})print(quot;\n初步计算的统计量(未应用条件):quot;)print(return_stats)登录后复制
初步计算的统计量示例输出:Mean_Return Median_Return0 0.68 0.601 0.68 0.602 0.68 0.603 0.24 0.364 0.24 0.365 0.24 0.366 -0.36 -0.36登录后复制
可以看到,ID=3的行也计算生长统计量,但是我们在不满足条件时显示NaN。3.3应用:计算唯一月份数并条件使用where()
现在,我们需要实现“如果唯一活跃月份数大于等于2”的条件。我们再次可以利用transform()来计算每个分组的唯一CALDT 值(即唯一月份数)。nunique() 是一个非常有用的聚合函数,用于计算唯一值的数量。
# 计算每个条件分组的唯一CALDT数量,并广播回原始行unique_months_per_group = g[quot;CALDTquot;].transform(quot;nuniquequot;)#一个布尔系列,表示哪些行满足(唯一月份数gt;= 2)condition = unique_months_per_group.ge(2) # .ge()表示quot;大于等于quot;#使用 .where()方法根据条件地保留或替换值#当条件为False时,return_stats中的对应值会被替换为NaNreturn_stats_conditional = return_stats.where(condition)print(quot;\n应用条件后的统计量:quot;)print(return_stats_conditional)登录后复制
应用条件后的统计量示例输出:Mean_Return Median_Return0 0.68 0.601 0.68 0.602 0.68 0.603 0.24 0.364 0.24 0.365 0.24 0.366 NaN NaN登录后复制
现在,ID=3的行由于只有一个月份(1980-01-31),其统计量被正确设置为NaN。3.4 校准结果
最后一步均匀计算出的条件统计量校准回原始的df数据框。 return_stats_conditional 已经与 df 具有相同的索引,我们可以直接使用 join() 方法。
# 将条件性统计量合并回原始 DataFramedf_final = df.join(return_stats_conditional)print(quot;\n最终结果 DataFrame:quot;)print(df_final)登录后复制
最终结果 DataFrame 样本输出: CALDT ID Return Year Mean_Return Median_Return0 1980-01-31 1 0.02 1980 0.68 0.601 1980-02-28 1 0.05 1980 0.68 0.602 1980-03-31 1 0.10 1980 0.68 0.603 1980-01-31 2 0.05 1980 0.24 0.364 1980-02-28 2 -0.02 1980 0.24 0.365 1980-03-31 2 0.03 1980 0.24 0.366 1980-01-31 3 -0.03 1980 NaN NaN登录后复制
这与我们期望的输出完全一致。
4. 完整代码示例
将上述步骤整合在一起,形成一个完整的解决方案:import pandas as pdimport numpy as np# 1. Datareadydf = pd.DataFrame( {quot;CALDTquot;: [quot;1980-01-31quot;, quot;1980-02-28quot;, quot;1980-03-31quot;, ”1980-01-31”;,“1980-02-28”;,“1980-03-31”;,“1980-01-31”;],“ID”;:[1,1,1,2,2,2,3],“返回”;:[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 # 显式创建Year列,理解,也可以直接在groupby中使用dt.yearprint(quot;--- 原始DataFrame ---quot;)print(df)print(quot;-quot; * 30)# 2.分组并计算条件性统计量#分组对象 g = df.groupby([quot;IDquot;创建 df.CALDT.dt.year])# 使用转换计算年化工具和中缀,广播并回原始行return_stats = pd.DataFrame({ quot;Mean_Returnquot;: g[quot;Returnquot;].transform(quot;meanquot;).mul(12), quot;Median_Returnquot;: g[quot;Returnquot;].transform(quot;medianquot;).mul(12)})# 计算每个分组的唯一CALDT数量,并广播回原始行#然后创建条件:唯一CALDT数量gt;= 2condition_met = g[quot;CALDTquot;].transform(quot;nuniquequot;).ge(2)#使用.where()方法根据条件保留或替换统计量#当condition_met为 False 时,return_stats 中的响应值会被替换为 NaNreturn_stats_conditional = return_stats.where(condition_met)print(quot;--- 应用条件后的统计量 DataFrame ---quot;)print(return_stats_conditional)print(quot;-quot; * 30)# 3. 合并结果到原始 DataFramedf_final = df.join(return_stats_conditional)print(qu
ot;---最终结果DataFrame ---quot;)print(df_final)登录后复制5. 注意事项与CommonTransform()的作用:transform()是实现将分组聚合结果“广播”回原始DataFrame的关键。它返回一个与原始DataFrame具有相同索引的Series或DataFrame,这使得后续的合并或直接赋值非常方便。agg()与transform()的选择:如果你只需要每个分组的单体聚合值(例如,每个ID和分值的平均收益),使用agg()更合适。如果你需要将聚合值应用回原始的每一行(例如,新列),则transform()是更优的选择。条件赋值where():where()作为方法非常强大,它根据一个布尔条件来单独地保留或替换DataFrame中的值。当条件为False时,对应位置的值会被替换(默认为NaN)。这比使用apply()结合简洁自定义函数通常更且代码更。直接在groupby()中使用dt.year: 在 groupby(["ID", df.CALDT.dt.year]) 中直接使用 df.CALDT.dt.year 是一种简洁的方式,避免了创建额外的 Year 效率:这种基于 Pandas 内置函数和方法的链式操作通常比使用 apply() 结合 Python 循环的自定义函数更加高效,尤其是在处理大型数据集时。
通过本教程,您应该已经掌握了如何使用 Pandas 的 groupby()、transform() 和 where()方法,灵活地处理数据框复杂的条件性分组计算任务。高效计算这种模式在金融、经济、科学计算等领域的数据分析中非常常见。
以上就是利用 Pandas 实现分组数据框的条件性分组计算的详细内容,更多请关注乐哥常识网其他相关文章!
