groupby 机制可以让你轻松地对数据进行分组,然后对每个组应用聚合函数、转换函数或过滤操作,从而提取出有意义的统计信息。
import pandas as pd
import numpy as np
# 创建销售数据
df = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=12, freq='M'),
'产品': ['A', 'B', 'A', 'C', 'B', 'A', 'C', 'B', 'A', 'C', 'B', 'A'],
'区域': ['华北', '华北', '华东', '华南', '华北', '华东', '华南', '华北', '华东', '华南', '华北', '华东'],
'销售额': [100, 150, 120, 180, 130, 110, 200, 140, 125, 210, 135, 115],
'利润': [20, 30, 25, 35, 28, 22, 40, 30, 26, 42, 29, 24]
})
print(df)
groupby() 创建了一个分组对象,你可以在这个对象上调用聚合方法。
# 按产品分组,计算平均销售额
df.groupby('产品')['销售额'].mean()
# 按产品和区域分组,求和
df.groupby(['产品', '区域'])['销售额'].sum()
Pandas 为 GroupBy 对象提供了多种内置聚合方法:
# 单列单聚合
df.groupby('产品')['利润'].sum()
# 多列多聚合
df.groupby('产品')[['销售额', '利润']].mean()
# 对整个 DataFrame 分组后聚合所有数值列
df.groupby('产品').mean(numeric_only=True)
agg() 方法agg() 允许你对不同列应用不同的聚合函数,甚至自定义函数。
# 对不同列应用不同聚合
result = df.groupby('产品').agg({
'销售额': ['sum', 'mean'],
'利润': ['sum', 'max']
})
print(result)
# 使用字典重命名列(通过命名聚合)
result = df.groupby('产品').agg(
总销售额=('销售额', 'sum'),
平均销售额=('销售额', 'mean'),
总利润=('利润', 'sum')
)
print(result)
# 对同一列应用多个函数
result = df.groupby('产品')['销售额'].agg(['sum', 'mean', 'max'])
print(result)
你可以将任何函数传递给 agg() 或 apply()。
# 自定义函数
def range_func(x):
return x.max() - x.min()
df.groupby('产品')['销售额'].agg(range_func)
# 使用 lambda
df.groupby('产品')['销售额'].agg(lambda x: x.max() - x.min())
# 多个自定义函数
df.groupby('产品')['销售额'].agg(['mean', range_func])
除了聚合,groupby 还可以进行变换(transform)和过滤(filter)。
transform 返回与原始数据相同形状的结果,常用于计算组内比例或标准化。
# 计算每个产品的销售额占该产品总销售额的比例
df['销售额占比'] = df.groupby('产品')['销售额'].transform(lambda x: x / x.sum())
print(df)
# 计算组内均值,并添加到原 DataFrame
df['组内平均销售额'] = df.groupby('产品')['销售额'].transform('mean')
print(df)
filter 根据组级别的条件筛选组,返回原 DataFrame 的子集。
# 只保留平均销售额 > 150 的产品组
df_filtered = df.groupby('产品').filter(lambda x: x['销售额'].mean() > 150)
print(df_filtered)
apply()apply() 是 GroupBy 最灵活的方法,可以对每个组执行任意操作,返回各种形状的结果。
# 对每个组应用函数,返回标量、Series 或 DataFrame
def top_product(group):
return group.nlargest(2, '销售额')[['产品', '销售额']]
df.groupby('区域').apply(top_product)
pivot_table()pivot_table() 是 Excel 风格的数据透视表,本质上是多索引分组聚合的便捷方式。
# 基本透视表:行=产品,列=区域,值=销售额,聚合=sum
pivot = pd.pivot_table(df, values='销售额', index='产品', columns='区域', aggfunc='sum', fill_value=0)
print(pivot)
# 多值、多聚合
pivot = pd.pivot_table(df,
values=['销售额', '利润'],
index='产品',
columns='区域',
aggfunc={'销售额': 'sum', '利润': 'mean'})
print(pivot)
import pandas as pd
# 创建销售数据
sales = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=20, freq='D'),
'产品': np.random.choice(['A', 'B', 'C'], 20),
'区域': np.random.choice(['华北', '华东', '华南'], 20),
'销售额': np.random.randint(100, 500, 20),
'利润': np.random.randint(20, 100, 20)
})
print("原始数据:")
print(sales.head(10))
# 1. 按产品和区域分组,计算销售额总和
group1 = sales.groupby(['产品', '区域'])['销售额'].sum().reset_index()
print("\n各产品在各区域的总销售额:")
print(group1)
# 2. 按产品分组,计算销售额和利润的统计量
group2 = sales.groupby('产品').agg({
'销售额': ['sum', 'mean', 'max'],
'利润': ['sum', 'mean']
})
print("\n各产品的销售统计:")
print(group2)
# 3. 计算每个产品每天的销售额占比(按天分组?这里演示组内比例)
sales['日销售额占比'] = sales.groupby('日期')['销售额'].transform(lambda x: x / x.sum())
print("\n添加每日销售额占比:")
print(sales.head())
# 4. 只保留平均利润 > 50 的产品组
high_profit_products = sales.groupby('产品').filter(lambda g: g['利润'].mean() > 50)
print("\n平均利润>50的产品记录:")
print(high_profit_products)
# 5. 创建透视表:产品为行,区域为列,值为销售额总和
pivot_table = pd.pivot_table(sales, values='销售额', index='产品', columns='区域', aggfunc='sum', fill_value=0)
print("\n销售额透视表:")
print(pivot_table)
groupby(..., as_index=True) 会将分组键作为索引。若希望分组键保留为列,设置 as_index=False 或之后使用 reset_index()。
groupby 时,尽量在聚合前选择必要的列,减少内存占用。
reset_index() 将其转为普通列。
groupby 分组,再用 agg() 聚合,代码清晰且高效。agg() 传入字典或命名聚合,避免用 apply() 循环,后者可能较慢。dropna() 处理。GroupBy 对象,需要调用聚合方法才能得到结果。