groupby
高级聚合:agg
, transform
, filter
的深度解析(一场 Pandas 魔法秀)
各位观众,各位程序员,各位数据科学界的魔法师们!欢迎来到 Pandas 魔法学院,我是今天的首席大法师——你的老朋友(或者即将成为老朋友)程序员小P。🧙♂️
今天,我们要解开 Pandas 中 groupby
的高级聚合咒语,让你的数据分析能力瞬间提升一个维度! 别害怕,虽然听起来高深莫测,但只要跟着我的节奏,保证你学会 agg
, transform
, 和 filter
三大绝技,成为数据分析界的 Gandalf!
1. groupby
的基础回顾:让数据井然有序
在我们深入魔法核心之前,先来回顾一下 groupby
的基础。groupby
就像一个超级整理员,它能根据你指定的列,将 DataFrame 分组,然后你就可以对每个组进行操作了。
想象一下,你有一堆扑克牌,groupby
就是能帮你按照花色(红桃、方块、梅花、黑桃)把它们分开放好的工具。
import pandas as pd
data = {'花色': ['红桃', '方块', '红桃', '梅花', '黑桃', '方块', '黑桃', '梅花'],
'点数': [2, 3, 4, 5, 6, 7, 8, 9]}
df = pd.DataFrame(data)
grouped = df.groupby('花色')
print(grouped) # <pandas.core.groupby.generic.DataFrameGroupBy object at 0x...> 别慌,这是个分组对象
现在,grouped
对象里就包含了按照花色分组后的数据。你可以使用它来做一些基础的操作,比如求和、平均值等等。
print(grouped.sum())
# 点数
# 花色
# 方块 10
# 梅花 14
# 红桃 6
# 黑桃 14
print(grouped.mean())
# 点数
# 花色
# 方块 5.0
# 梅花 7.0
# 红桃 3.0
# 黑桃 7.0
这些都是小儿科,真正的魔法还在后面!
2. agg
:聚合的艺术,化繁为简的利器
agg
(aggregate) 函数就像一个多才多艺的厨师,它允许你对每个分组应用一个或多个聚合函数,将复杂的数据精炼成简洁的摘要。
2.1 单个聚合函数:简单直接,一目了然
最简单的用法是传入一个函数名,agg
会将这个函数应用到每个分组的指定列上。
print(grouped['点数'].agg('sum'))
# 花色
# 方块 10
# 梅花 14
# 红桃 6
# 黑桃 14
# Name: 点数, dtype: int64
print(grouped['点数'].agg(['sum', 'mean', 'max']))
# sum mean max
# 花色
# 方块 10 5.0 7
# 梅花 14 7.0 9
# 红桃 6 3.0 4
# 黑桃 14 7.0 8
看到没?一行代码搞定求和、均值和最大值!简直是效率神器!
2.2 多个聚合函数:百花齐放,各显神通
agg
的真正威力在于它可以同时应用多个聚合函数。你可以传入一个列表,每个元素都是一个函数名,agg
会分别应用这些函数,并将结果以 DataFrame 的形式返回。
import numpy as np
print(grouped['点数'].agg([np.sum, np.mean, np.std]))
# sum mean std
# 花色
# 方块 10 5.0 2.828427
# 梅花 14 7.0 2.828427
# 红桃 6 3.0 1.414214
# 黑桃 14 7.0 1.414214
除了内置的函数,你还可以使用自定义函数!这才是真正的魔法!
2.3 自定义聚合函数:量身定制,独一无二
你可以定义自己的聚合函数,然后将它们传递给 agg
。这让你可以根据具体的需求,进行高度定制化的聚合操作。
def my_sum_plus_one(x):
return x.sum() + 1
print(grouped['点数'].agg(my_sum_plus_one))
# 花色
# 方块 11
# 梅花 15
# 红桃 7
# 黑桃 15
# Name: 点数, dtype: int64
这个自定义函数将每个分组的点数求和,然后加 1。虽然这个例子很简单,但你可以根据实际情况,定义更加复杂的函数。
2.4 字典式聚合:指哪打哪,精准打击
更高级的用法是使用字典来指定不同的列应用不同的聚合函数。字典的键是列名,值是函数名或函数名列表。
print(grouped.agg({'点数': ['sum', 'mean'], '花色': 'count'}))
# 点数 花色
# sum mean count
# 花色
# 方块 10 5.0 2
# 梅花 14 7.0 2
# 红桃 6 3.0 2
# 黑桃 14 7.0 2
在这个例子中,我们对 ‘点数’ 列应用了 sum
和 mean
函数,对 ‘花色’ 列应用了 count
函数。
总结:agg
的优点
- 简洁高效: 一行代码完成复杂的聚合操作。
- 灵活定制: 支持内置函数、自定义函数和字典式聚合。
- 可读性强: 代码逻辑清晰,易于理解。
3. transform
:变形的艺术,保持原样的魔法
transform
函数就像一个神奇的镜子,它可以对每个分组应用一个函数,并将结果广播回原始 DataFrame 的大小。也就是说,transform
会返回一个与原始 DataFrame 具有相同索引和形状的新列。
3.1 标准化:让数据站在同一起跑线
一个常见的用法是标准化数据,将每个分组的数据转换为均值为 0,标准差为 1 的分布。
def standardize(x):
return (x - x.mean()) / x.std()
df['标准化点数'] = grouped['点数'].transform(standardize)
print(df)
# 花色 点数 标准化点数
# 0 红桃 2 -0.707107
# 1 方块 3 -0.707107
# 2 红桃 4 0.707107
# 3 梅花 5 -0.707107
# 4 黑桃 6 -0.707107
# 5 方块 7 0.707107
# 6 黑桃 8 0.707107
# 7 梅花 9 0.707107
在这个例子中,我们定义了一个 standardize
函数,它计算每个分组的均值和标准差,然后将数据标准化。transform
函数将这个函数应用到每个分组,并将结果添加到了原始 DataFrame 中。
3.2 填充缺失值:亡羊补牢,犹未晚矣
transform
还可以用于填充缺失值。你可以使用分组的均值、中位数或其他统计量来填充缺失值。
data = {'花色': ['红桃', '方块', '红桃', '梅花', '黑桃', '方块', '黑桃', '梅花'],
'点数': [2, 3, 4, 5, 6, None, 8, 9]}
df = pd.DataFrame(data)
grouped = df.groupby('花色')
df['点数'] = grouped['点数'].transform(lambda x: x.fillna(x.mean()))
print(df)
# 花色 点数
# 0 红桃 2.0
# 1 方块 3.0
# 2 红桃 4.0
# 3 梅花 5.0
# 4 黑桃 6.0
# 5 方块 7.0
# 6 黑桃 8.0
# 7 梅花 9.0
在这个例子中,我们使用 transform
函数将每个分组的缺失值填充为该分组的均值。
3.3 计算排名:谁是第一,一目了然
transform
还可以用于计算每个分组的排名。
df['排名'] = grouped['点数'].transform('rank')
print(df)
# 花色 点数 排名
# 0 红桃 2.0 1.0
# 1 方块 3.0 1.0
# 2 红桃 4.0 2.0
# 3 梅花 5.0 1.0
# 4 黑桃 6.0 1.0
# 5 方块 7.0 2.0
# 6 黑桃 8.0 2.0
# 7 梅花 9.0 2.0
在这个例子中,我们使用 transform
函数计算每个分组的点数排名。
总结:transform
的优点
- 保持形状: 返回与原始 DataFrame 具有相同索引和形状的新列。
- 灵活应用: 可以应用各种函数,包括标准化、填充缺失值、计算排名等。
- 易于集成: 可以将
transform
的结果添加到原始 DataFrame 中,方便后续分析。
4. filter
:过滤的艺术,沙里淘金的秘诀
filter
函数就像一个严苛的守门员,它根据你指定的条件,过滤掉不符合条件的分组。只有当整个分组满足条件时,该分组的数据才会被保留。
4.1 筛选大小:留下精华,剔除糟粕
一个常见的用法是筛选出分组大小大于某个阈值的分组。
data = {'花色': ['红桃', '方块', '红桃', '梅花', '黑桃', '方块', '黑桃', '梅花', '草花'],
'点数': [2, 3, 4, 5, 6, 7, 8, 9, 10]}
df = pd.DataFrame(data)
grouped = df.groupby('花色')
filtered_df = grouped.filter(lambda x: len(x) > 1)
print(filtered_df)
# 花色 点数
# 0 红桃 2
# 1 方块 3
# 2 红桃 4
# 3 梅花 5
# 4 黑桃 6
# 5 方块 7
# 6 黑桃 8
# 7 梅花 9
在这个例子中,我们使用 filter
函数筛选出大小大于 1 的分组。只有 ‘红桃’、’方块’、’梅花’ 和 ‘黑桃’ 这几个分组满足条件,所以只有这些分组的数据被保留了下来。 ‘草花’因为只有一个数据,所以被无情地过滤掉了。
4.2 筛选总和:留下高富帅,剔除屌丝
你还可以根据分组的总和、均值或其他统计量来过滤分组。
filtered_df = grouped.filter(lambda x: x['点数'].sum() > 10)
print(filtered_df)
# 花色 点数
# 1 方块 3
# 3 梅花 5
# 4 黑桃 6
# 5 方块 7
# 6 黑桃 8
# 7 梅花 9
# 8 草花 10
在这个例子中,我们使用 filter
函数筛选出点数总和大于 10 的分组。
4.3 筛选均值:留下学霸,剔除学渣
你也可以根据分组的均值来筛选分组。
filtered_df = grouped.filter(lambda x: x['点数'].mean() > 6)
print(filtered_df)
# 花色 点数
# 3 梅花 5
# 6 黑桃 8
# 7 梅花 9
# 8 草花 10
在这个例子中,我们使用 filter
函数筛选出点数均值大于 6 的分组。
总结:filter
的优点
- 分组过滤: 根据分组的整体特征进行过滤,而不是单个数据点。
- 条件灵活: 可以使用各种条件,包括分组大小、总和、均值等。
- 数据清洗: 可以用于清洗数据,去除不符合条件的分组。
5. 实战案例:用魔法解决实际问题
理论讲完了,现在让我们用一个实战案例来巩固一下所学的知识。
假设你是一家电商公司的数据分析师,你有一份用户订单数据,包含用户ID、订单金额和下单时间。
import pandas as pd
import numpy as np
# 生成模拟数据
np.random.seed(42) # 设置随机种子,保证结果可重复
num_users = 10
num_orders = 50
user_ids = np.random.randint(1, num_users + 1, num_orders)
order_amounts = np.random.randint(50, 500, num_orders)
order_dates = pd.to_datetime('2023-01-01') + pd.to_timedelta(np.random.randint(0, 365, num_orders), unit='D')
data = {'用户ID': user_ids,
'订单金额': order_amounts,
'下单时间': order_dates}
df = pd.DataFrame(data)
print(df.head())
# 用户ID 订单金额 下单时间
# 0 3 306 2023-08-02
# 1 8 101 2023-04-25
# 2 6 176 2023-12-15
# 3 4 398 2023-09-14
# 4 6 257 2023-03-20
现在,你需要回答以下几个问题:
- 每个用户的总订单金额是多少?(使用
agg
) - 每个订单金额占该用户总订单金额的比例是多少?(使用
transform
) - 筛选出总订单金额大于 1000 的用户。(使用
filter
)
解答:
# 1. 每个用户的总订单金额是多少?
grouped = df.groupby('用户ID')
user_total_amount = grouped['订单金额'].agg('sum')
print("每个用户的总订单金额:n", user_total_amount)
# 2. 每个订单金额占该用户总订单金额的比例是多少?
df['订单金额占比'] = grouped['订单金额'].transform(lambda x: x / x.sum())
print("n每个订单金额占该用户总订单金额的比例:n", df.head())
# 3. 筛选出总订单金额大于 1000 的用户。
filtered_df = grouped.filter(lambda x: x['订单金额'].sum() > 1000)
print("n总订单金额大于 1000 的用户的订单数据:n", filtered_df.head())
通过这个案例,你应该对 agg
, transform
, 和 filter
的用法有了更深入的理解。
6. 总结:掌握魔法,走向巅峰
恭喜你,完成了今天的 Pandas 魔法课程! 🎉
通过学习 agg
, transform
, 和 filter
,你已经掌握了 groupby
的高级聚合技巧,可以更加灵活地处理和分析数据。
记住,练习是掌握魔法的关键。多尝试不同的案例,多思考不同的用法,你就能将这些魔法咒语运用自如,成为真正的数据分析大师!
下次再见,祝你编程愉快! 🚀