pandas groupby 学习指南 – wiki基地


Pandas groupby 学习指南:数据分组聚合的终极武器

在数据分析的世界里,我们经常需要对数据进行分组,然后针对每个分组执行计算(比如求和、平均值、计数等)。想象一下,你有一份销售记录,你想知道每个地区的总销售额、每个产品的平均价格、或者每个销售员的最大销售量。这些任务的核心操作就是“分组”和“聚合”。

在 Python 的数据分析库 Pandas 中,groupby() 方法正是解决这类问题的终极武器。它基于“Split-Apply-Combine”(拆分-应用-组合)的思想,能够高效、灵活地完成复杂的数据分组和聚合任务。

本篇文章将带你深入理解 Pandas groupby 的方方面面,从基础用法到高级技巧,帮助你掌握这一强大的工具。

目录

  1. 理解 Split-Apply-Combine 思想
  2. groupby() 的基本用法
    • 创建一个示例 DataFrame
    • 按单列分组
    • 理解 GroupBy 对象
  3. 应用聚合函数 (Aggregation)
    • 常用聚合函数
    • 选择特定列进行聚合
    • 按多列分组并聚合
    • 使用 agg() 应用多个聚合函数
      • 应用多个函数到所有聚合列
      • 应用不同的函数到不同的列
      • 使用命名聚合 (Named Aggregation)
    • size() vs count()
  4. 使用 apply() 应用自定义函数
    • apply() 的灵活性
    • 示例:计算组内排名、选择组内 Top N
  5. 使用 transform() 应用转换函数
    • transform() 的特点:保留原始索引
    • 示例:标准化数据、填充组内平均值
  6. 迭代 GroupBy 对象
  7. groupby 的其他参数 (as_index, sort, dropna)
  8. 链式操作和效率考虑
  9. 总结与实践

1. 理解 Split-Apply-Combine 思想

groupby() 的核心是 Split-Apply-Combine 策略,由 Hadley Wickham 在 R 语言中推广。它描述了大多数数据聚合和分组操作的通用过程:

  1. Split(拆分):根据分组键(一个或多个列/索引)将数据拆分成若干个独立的小块(或称分组)。每个小块包含分组键具有相同值的所有行。
  2. Apply(应用):对每个独立的小块应用一个函数。这个函数可以是聚合函数(如求和、平均值),转换函数(如标准化),或者过滤函数(如选取 Top N)。
  3. Combine(组合):将每个小块应用函数后的结果组合成一个统一的、有意义的结构(通常是 Pandas Series 或 DataFrame)。

你可以想象这个过程就像整理一堆不同颜色和大小的积木:

  • Split: 你根据颜色将积木分成不同的堆(红色堆、蓝色堆、绿色堆)。
  • Apply: 对于每一堆积木,你数一下有多少块(计数),或者称一下这一堆的总重量(求和)。
  • Combine: 你把每堆积木的计数或总重量记录下来,形成一个新的表格,表格里列出了每种颜色对应的计数/总重量。

Pandas 的 groupby() 方法就是负责高效地完成这个 Split 和 Combine 的过程,而你需要告诉它在 Apply 阶段要做什么。

2. groupby() 的基本用法

创建一个示例 DataFrame

为了演示 groupby 的用法,我们首先创建一个简单的示例 DataFrame:

“`python
import pandas as pd
import numpy as np

创建一个示例 DataFrame

data = {
‘Category’: [‘A’, ‘B’, ‘A’, ‘C’, ‘B’, ‘C’, ‘A’, ‘B’, ‘C’, ‘A’],
‘Subcategory’: [‘X’, ‘Y’, ‘Z’, ‘X’, ‘Y’, ‘Z’, ‘X’, ‘Y’, ‘Z’, ‘Z’],
‘Value1’: [10, 15, 12, 18, 20, 25, 11, 16, 22, 14],
‘Value2’: [100, 150, 120, 180, 200, 250, 110, 160, 220, 140],
‘TextCol’: [‘foo’, ‘bar’, ‘baz’, ‘foo’, ‘bar’, ‘baz’, ‘foo’, ‘bar’, ‘baz’, ‘foo’]
}
df = pd.DataFrame(data)

print(“原始 DataFrame:”)
print(df)
“`

输出:

原始 DataFrame:
Category Subcategory Value1 Value2 TextCol
0 A X 10 100 foo
1 B Y 15 150 bar
2 A Z 12 120 baz
3 C X 18 180 foo
4 B Y 20 200 bar
5 C Z 25 250 baz
6 A X 11 110 foo
7 B Y 16 160 bar
8 C Z 22 220 baz
9 A Z 14 140 foo

按单列分组

使用 groupby() 方法非常简单,只需要指定作为分组键的列名(或多个列名组成的列表):

“`python

按 ‘Category’ 列分组

grouped_by_category = df.groupby(‘Category’)

print(“\n按 ‘Category’ 分组后的 GroupBy 对象:”)
print(grouped_by_category)
print(type(grouped_by_category))
“`

输出:

按 'Category' 分组后的 GroupBy 对象:
<pandas.core.groupby.generic.DataFrameGroupBy object at ...>
<class 'pandas.core.groupby.generic.DataFrameGroupBy'>

可以看到,df.groupby('Category') 返回的并不是一个 DataFrame,而是一个 DataFrameGroupBy 对象。这个对象本身并没有执行任何计算,它只是一个描述了如何对数据进行分组的对象。真正的计算(Apply 和 Combine 步骤)会在你调用聚合、转换或过滤方法时发生。这种设计的好处是效率高,因为它避免了创建中间的临时数据结构。

理解 GroupBy 对象

GroupBy 对象包含了分组信息以及对每个组进行操作所需的方法。你可以通过一些属性来了解分组的情况:

“`python

查看分组键

print(“\n分组键:”)
print(grouped_by_category.keys) # 注意:keys 是属性,不是方法

查看分组数量

print(“\n分组数量:”)
print(grouped_by_category.ngroups)

查看每个分组的索引

print(“\n每个分组的索引:”)
print(grouped_by_category.groups)

查看特定分组的数据(不常用,但有助于理解)

print(“\n’A’ 分组的数据:”)
print(grouped_by_category.get_group(‘A’))
“`

输出:

“`
分组键:
[‘Category’]

分组数量:
3

每个分组的索引:
{‘A’: Index([0, 2, 6, 9], dtype=’int64′), ‘B’: Index([1, 4, 7], dtype=’int64′), ‘C’: Index([3, 5, 8], dtype=’int64′)}

‘A’ 分组的数据:
Category Subcategory Value1 Value2 TextCol
0 A X 10 100 foo
2 A Z 12 120 baz
6 A X 11 110 foo
9 A Z 14 140 foo
“`

groups 属性显示了每个分组名称(这里是 Category 的值 ‘A’, ‘B’, ‘C’)对应的原始 DataFrame 中的行索引。get_group('A') 则直接返回 ‘A’ 组对应的所有原始行数据,结果是一个 DataFrame。

3. 应用聚合函数 (Aggregation)

一旦创建了 GroupBy 对象,就可以在其上调用聚合函数。聚合函数会对每个分组的数据进行计算,并返回一个单一的结果。

常用聚合函数

Pandas GroupBy 对象提供了许多内置的常用聚合函数,例如:

  • sum(): 计算总和
  • mean(): 计算平均值
  • median(): 计算中位数
  • min(): 计算最小值
  • max(): 计算最大值
  • std(): 计算标准差
  • var(): 计算方差
  • count(): 计算非 NaN 值的数量
  • size(): 计算分组的大小(行数),包括 NaN
  • first(): 返回每个分组的第一个值
  • last(): 返回每个分组的最后一个值
  • nunique(): 计算唯一值的数量

对 GroupBy 对象直接调用聚合函数时,它会尝试对所有数值型列进行聚合:

“`python

计算每个 Category 的 Value1 和 Value2 的总和

category_sums = grouped_by_category.sum()
print(“\n按 Category 分组并求和:”)
print(category_sums)

计算每个 Category 的 Value1 和 Value2 的平均值

category_means = grouped_by_category.mean()
print(“\n按 Category 分组并求平均值:”)
print(category_means)

计算每个 Category 的行数 (包括 NaN)

category_sizes = grouped_by_category.size()
print(“\n按 Category 分组并计算大小:”)
print(category_sizes) # size() 返回 Series
“`

输出:

“`
按 Category 分组并求和:
Value1 Value2
Category
A 47 470
B 51 510
C 65 650

按 Category 分组并求平均值:
Value1 Value2
Category
A 11.75 117.5
B 17.00 170.0
C 21.67 216.666667

按 Category 分组并计算大小:
Category
A 4
B 3
C 3
dtype: int64
“`

注意,size() 返回的是一个 Series,其索引是分组键,值是每个组的大小。其他聚合函数通常返回一个 DataFrame,其索引也是分组键,列是原始 DataFrame 中被聚合的列。非数值型的列(如 ‘TextCol’)通常会被忽略,除非聚合函数对它们有意义(如 first(), last(), count(), size(), nunique(), apply with string operations)。

选择特定列进行聚合

如果你只想对特定的列进行聚合,可以在 groupby() 之后,但在调用聚合函数之前,使用方括号 [] 选择列:

“`python

只计算每个 Category 的 Value1 的总和

category_value1_sum = grouped_by_category[‘Value1’].sum()
print(“\n按 Category 分组,只对 Value1 求和:”)
print(category_value1_sum) # 返回 Series

只计算每个 Category 的 Value1 和 Value2 的平均值

category_values_mean = grouped_by_category[[‘Value1’, ‘Value2’]].mean()
print(“\n按 Category 分组,只对 Value1 和 Value2 求平均值:”)
print(category_values_mean) # 返回 DataFrame
“`

输出:

“`
按 Category 分组,只对 Value1 求和:
Category
A 47
B 51
C 65
Name: Value1, dtype: int64

按 Category 分组,只对 Value1 和 Value2 求平均值:
Value1 Value2
Category
A 11.75 117.500000
B 17.00 170.000000
C 21.67 216.666667
“`

使用单个方括号 ['ColumnName'] 选择列会得到一个 SeriesGroupBy 对象,对其应用聚合函数通常返回一个 Series。使用双层方括号 [['ColumnName1', 'ColumnName2']] 选择列会得到一个 DataFrameGroupBy 对象,对其应用聚合函数返回一个 DataFrame。

按多列分组并聚合

groupby() 也可以接受一个列表,以便按多列进行分组。此时,分组键将由这些列的唯一组合决定:

“`python

按 ‘Category’ 和 ‘Subcategory’ 列分组

grouped_multi = df.groupby([‘Category’, ‘Subcategory’])

计算每个组合的 Value1 的总和

multi_level_sum = grouped_multi[‘Value1’].sum()
print(“\n按 Category 和 Subcategory 分组并求和:”)
print(multi_level_sum)
“`

输出:

按 Category 和 Subcategory 分组并求和:
Category Subcategory
A X 21
Z 26
B Y 51
C X 18
Z 47
Name: Value1, dtype: int64

此时,结果的索引是一个多级索引 (MultiIndex),由分组键的唯一组合构成。

使用 agg() 应用多个聚合函数

agg() (或 aggregate(), 它们是同一个方法) 是 groupby 中非常重要的一个方法,它允许你一次性对一个或多个列应用一个或多个聚合函数。这比多次调用单一聚合函数更高效且代码更简洁。

应用多个函数到所有聚合列

你可以给 agg() 传递一个聚合函数名称的字符串列表:

“`python

对 Value1 列应用多个聚合函数

category_value1_agg = grouped_by_category[‘Value1’].agg([‘sum’, ‘mean’, ‘std’, ‘count’])
print(“\n按 Category 分组,对 Value1 应用多个函数:”)
print(category_value1_agg)
“`

输出:

按 Category 分组,对 Value1 应用多个函数:
sum mean std count
Category
A 47 11.750000 1.707825 4
B 51 17.000000 2.645751 3
C 65 21.666667 3.511885 3

结果 DataFrame 的列名是聚合函数的名称。

应用不同的函数到不同的列

更常见和强大的是使用字典传递给 agg(),字典的键是你想要聚合的输出列名,字典的值可以是:

  1. 一个函数名字符串。
  2. 一个函数名字符串列表,对该列应用多个函数。
  3. 一个元组 ('原始列名', '函数名'),用于指定对哪个原始列应用函数并指定输出列名(更推荐的命名聚合方式)。

方式 1&2: 使用字典映射输出列名到函数/函数列表 (旧风格)

“`python

对 Value1 求和,对 Value2 求平均值和最大值

category_custom_agg_old = grouped_by_category.agg({
‘Value1’: ‘sum’, # 输出列名为 Value1
‘Value2’: [‘mean’, ‘max’] # 输出列名为 MultiIndex (Value2, mean) 和 (Value2, max)
})
print(“\n按 Category 分组,使用 agg 字典 (旧风格):”)
print(category_custom_agg_old)
“`

输出:

按 Category 分组,使用 agg 字典 (旧风格):
Value1 Value2
sum mean max
Category
A 47 117.500000 140
B 51 170.000000 200
C 65 216.666667 250

这种旧风格的字典方式在应用多个函数到一个列时会生成一个 MultiIndex 的列,可能不太方便。

使用命名聚合 (Named Aggregation)

Pandas 0.25.0 引入了命名聚合,这是通过在 agg() 中使用关键字参数来实现的,每个关键字参数的名字就是最终结果中的列名。其值是一个元组 ('原始列名', '函数名')

“`python

使用命名聚合,对 Value1 求总和,对 Value2 求平均值和最大值

category_custom_agg_named = grouped_by_category.agg(
Total_Value1=(‘Value1’, ‘sum’),
Average_Value2=(‘Value2’, ‘mean’),
Max_Value2=(‘Value2’, ‘max’)
)
print(“\n按 Category 分组,使用命名聚合:”)
print(category_custom_agg_named)
“`

输出:

按 Category 分组,使用命名聚合:
Total_Value1 Average_Value2 Max_Value2
Category
A 47 117.500000 140
B 51 170.000000 200
C 65 216.666667 250

命名聚合的结果列名清晰明了,强烈推荐使用这种方式进行复杂的聚合。

size() vs count()

这是一个常见的混淆点:

  • count(): 计算分组中非 NaN 值的数量。当应用于整个 GroupBy 对象时,它会按列统计每个分组中非 NaN 的数量。
  • size(): 计算分组的总行数,包括 NaN 值。它总是返回一个 Series。

示例:

“`python

引入 NaN 值进行演示

df_with_nan = df.copy()
df_with_nan.loc[0, ‘Value1’] = np.nan
df_with_nan.loc[4, ‘Value2’] = np.nan
df_with_nan.loc[8, ‘TextCol’] = np.nan

grouped_with_nan = df_with_nan.groupby(‘Category’)

print(“\n带有 NaN 值的 DataFrame:”)
print(df_with_nan)

print(“\n按 Category 分组,使用 count():”)
print(grouped_with_nan.count()) # 统计非 NaN 数量

print(“\n按 Category 分组,使用 size():”)
print(grouped_with_nan.size()) # 统计总行数
“`

输出:

“`
带有 NaN 值的 DataFrame:
Category Subcategory Value1 Value2 TextCol
0 A X NaN 100.0 foo
1 B Y 15.0 150.0 bar
2 A Z 12.0 120.0 baz
3 C X 18.0 180.0 foo
4 B Y 20.0 NaN bar
5 C Z 25.0 250.0 baz
6 A X 11.0 110.0 foo
7 B Y 16.0 160.0 bar
8 C Z 22.0 NaN NaN
9 A Z 14.0 140.0 foo

按 Category 分组,使用 count():
Subcategory Value1 Value2 TextCol
Category
A 4 3 4 4
B 3 3 2 3
C 3 3 2 2

按 Category 分组,使用 size():
Category
A 4
B 3
C 3
dtype: int64
“`

可以看到,’A’ 组的 Value1 少了一个 NaNcount() 结果是 3,而 size() 结果是 4。’B’ 组的 Value2 少了一个 NaNcount() 结果是 2,而 size() 结果是 3。’C’ 组的 Value2TextCol 各少了一个 NaNcount() 结果相应减少,而 size() 结果依然是 3。

4. 使用 apply() 应用自定义函数

虽然 agg() 及其内置函数能处理很多常见的聚合需求,但有时你需要执行更复杂的操作,而这些操作没有内置函数可以直接完成。这时就可以使用 apply() 方法。

apply() 方法会将分组的每个小块(一个 DataFrame)作为输入,传递给一个函数,然后将函数的返回值组合起来。

“`python

定义一个函数,计算每个分组 Value1 的范围 (max – min)

def range_of_value1(group):
return group[‘Value1’].max() – group[‘Value1’].min()

对每个 Category 应用 range_of_value1 函数

category_value1_range = grouped_by_category.apply(range_of_value1)
print(“\n按 Category 分组,计算 Value1 的范围:”)
print(category_value1_range)
“`

输出:

按 Category 分组,计算 Value1 的范围:
Category
A 4.0
B 5.0
C 7.0
dtype: float64

apply() 函数的灵活性非常高。它可以返回:

  • 一个 Series:如上面的例子,返回一个标量结果(范围)。Pandas 会将这些 Series 组合成一个 Series,索引是分组键。
  • 一个 DataFrame:如果你的函数对每个分组返回一个 DataFrame,Pandas 会将这些 DataFrame 行绑定 (row-bind) 起来,并保留原始分组的索引作为多级索引的第一层。

示例:找出每个 Category 中 Value1 最大的行:

“`python

定义一个函数,返回分组中 Value1 最大的行

def get_row_with_max_value1(group):
# idxmax() 返回最大值所在行的索引
idx_max = group[‘Value1’].idxmax()
return group.loc[[idx_max]] # 返回该行作为一个 DataFrame

对每个 Category 应用函数

max_value_rows = grouped_by_category.apply(get_row_with_max_value1)
print(“\n按 Category 分组,获取 Value1 最大的行:”)
print(max_value_rows)
“`

输出:

按 Category 分组,获取 Value1 最大的行:
Category Subcategory Value1 Value2 TextCol
Category
A 9 A Z 14 140 foo
B 4 B Y 20 200 bar
C 5 C Z 25 250 baz

这里的结果是一个 DataFrame,其索引是多级的:第一层是 Category (分组键),第二层是原始 DataFrame 的索引。

apply() 非常强大,几乎可以对分组数据执行任何操作。然而,相比于优化的聚合函数 (sum, mean 等) 或 transformapply 可能效率较低,因为它需要为每个分组实例化一个 DataFrame 并调用函数。对于简单的聚合,优先使用内置方法或 agg()

5. 使用 transform() 应用转换函数

有时候,你需要在保留原始 DataFrame 结构和索引的情况下,基于分组信息创建一个新的列。例如,你想计算每个销售额与该销售员平均销售额的差值,或者将每个值标准化到其所属分组的平均值和标准差。transform() 方法就是为这种“转换”任务设计的。

transform() 方法必须返回一个与其对应的分组具有相同索引的 Series 或 DataFrame。然后 Pandas 会将这些结果组合起来,形成一个与原始 DataFrame 具有相同索引的 Series 或 DataFrame。

“`python

计算每个 Category 的 Value1 的平均值,并将结果添加到原始 DataFrame 中

df[‘Category_Value1_Mean’] = grouped_by_category[‘Value1’].transform(‘mean’)

计算每个 Category 的 Value1 相对于其分组平均值的差值

df[‘Value1_Diff_From_Mean’] = grouped_by_category[‘Value1’].transform(lambda x: x – x.mean())

print(“\n使用 transform 添加分组平均值和差值列:”)
print(df)
“`

输出:

使用 transform 添加分组平均值和差值列:
Category Subcategory Value1 Value2 TextCol Category_Value1_Mean Value1_Diff_From_Mean
0 A X 10 100 foo 11.75 -1.75
1 B Y 15 150 bar 17.00 -2.00
2 A Z 12 120 baz 11.75 0.25
3 C X 18 180 foo 21.67 -3.67
4 B Y 20 200 bar 17.00 3.00
5 C Z 25 250 baz 21.67 3.33
6 A X 11 110 foo 11.75 -0.75
7 B Y 16 160 bar 17.00 -1.00
8 C Z 22 220 baz 21.67 0.33
9 A Z 14 140 foo 11.75 2.25

正如所见,transform() 的结果可以直接赋值给原始 DataFrame 的新列,因为它们拥有相同的索引。

transform() 可以接受:

  • 聚合函数名字符串 (‘sum’, ‘mean’, ‘std’ 等)。
  • 一个函数对象,该函数接受一个 Series 或 DataFrame(取决于你是在 GroupBy 对象上调用还是在 GroupBy 后选择了列),并返回一个具有相同索引的 Series 或 DataFrame。

示例:对 Value1 按 Category 进行 Z-score 标准化:

“`python

定义 Z-score 标准化函数

def zscore(x):
return (x – x.mean()) / x.std(ddof=0) # ddof=0 for population std dev

df[‘Value1_Zscore’] = grouped_by_category[‘Value1’].transform(zscore)

print(“\n使用 transform 进行 Z-score 标准化:”)
print(df)
“`

输出:

使用 transform 进行 Z-score 标准化:
Category Subcategory Value1 Value2 TextCol Category_Value1_Mean Value1_Diff_From_Mean Value1_Zscore
0 A X 10 100 foo 11.75 -1.75 -1.028857
1 B Y 15 150 bar 17.00 -2.00 -0.816497
2 A Z 12 120 baz 11.75 0.25 0.146984
3 C X 18 180 foo 21.67 -3.67 -1.042578
4 B Y 20 200 bar 17.00 3.00 1.224745
5 C Z 25 250 baz 21.67 3.33 0.943912
6 A X 11 110 foo 11.75 -0.75 -0.439936
7 B Y 16 160 bar 17.00 -1.00 -0.408248
8 C Z 22 220 baz 21.67 0.33 0.098666
9 A Z 14 140 foo 11.75 2.25 1.321808

transform 是进行组内计算并广播结果回原始数据框的理想选择。

6. 迭代 GroupBy 对象

虽然大多数情况下你会使用 agg, apply, 或 transform 来处理所有分组,但有时你可能需要逐个访问每个分组进行特殊处理(例如,保存每个分组到单独的文件,或者可视化每个分组的数据)。你可以像迭代器一样遍历 GroupBy 对象:

“`python

迭代 GroupBy 对象

print(“\n迭代 GroupBy 对象:”)
for name, group_df in grouped_by_category:
print(f”\n— 分组: {name} —“)
print(group_df)
“`

输出:

“`
迭代 GroupBy 对象:

— 分组: A —
Category Subcategory Value1 Value2 TextCol Category_Value1_Mean Value1_Diff_From_Mean Value1_Zscore
0 A X 10 100 foo 11.75 -1.75 -1.028857
2 A Z 12 120 baz 11.75 0.25 0.146984
6 A X 11 110 foo 11.75 -0.75 -0.439936
9 A Z 14 140 foo 11.75 2.25 1.321808

— 分组: B —
Category Subcategory Value1 Value2 TextCol Category_Value1_Mean Value1_Diff_From_Mean Value1_Zscore
1 B Y 15 150 bar 17.00 -2.00 -0.816497
4 B Y 20 200 bar 17.00 3.00 1.224745
7 B Y 16 160 bar 17.00 -1.00 -0.408248

— 分组: C —
Category Subcategory Value1 Value2 TextCol Category_Value1_Mean Value1_Diff_From_Mean Value1_Zscore
3 C X 18 180 foo 21.67 -3.67 -1.042578
5 C Z 25 250 baz 21.67 3.33 0.943912
8 C Z 22 220 baz 21.67 0.33 0.098666
“`

每次迭代返回一个元组 (name, group_df),其中 name 是当前分组的值(如果是多级分组则是元组),group_df 是该分组对应的 DataFrame。虽然迭代功能强大,但在处理大型数据集时应谨慎使用,因为它可能比矢量化操作效率低。

7. groupby 的其他参数

groupby() 方法还有一些其他有用的参数:

  • as_index=True (默认): 分组键将成为结果 DataFrame 的索引。
  • as_index=False: 分组键将作为普通列保留在结果 DataFrame 中,结果索引是默认的整数索引。这通常在你不想处理多级索引时很有用。
  • sort=True (默认): 按分组键对结果进行排序。设置为 False 可以稍微提高性能,如果分组键的顺序不重要的话。
  • dropna=True (默认): 默认情况下,分组键中包含 NaN 的行会被排除。设置为 False 会将 NaN 视为一个特殊的分组。

示例 as_index=False:

“`python

按 Category 分组并求和,不将 Category 作为索引

category_sums_no_index = df.groupby(‘Category’, as_index=False)[‘Value1’, ‘Value2’].sum()
print(“\n按 Category 分组求和 (as_index=False):”)
print(category_sums_no_index)
“`

输出:

按 Category 分组求和 (as_index=False):
Category Value1 Value2
0 A 47 470
1 B 51 510
2 C 65 650

使用 as_index=False 后,结果更像一个常规的二维表,有时更方便后续处理或写入文件。

示例 dropna=False (需要分组键中有 NaN):

“`python
df_nan_groupkey = pd.DataFrame({
‘Group’: [‘A’, ‘B’, ‘A’, np.nan, ‘B’, ‘A’, np.nan],
‘Value’: [1, 2, 3, 4, 5, 6, 7]
})

print(“\n包含 NaN 分组键的 DataFrame:”)
print(df_nan_groupkey)

默认行为 (dropna=True)

print(“\nGroupBy 默认行为 (dropna=True):”)
print(df_nan_groupkey.groupby(‘Group’)[‘Value’].sum())

保留 NaN 分组 (dropna=False)

print(“\nGroupBy 保留 NaN 分组 (dropna=False):”)
print(df_nan_groupkey.groupby(‘Group’, dropna=False)[‘Value’].sum())
“`

输出:

“`
包含 NaN 分组键的 DataFrame:
Group Value
0 A 1
1 B 2
2 A 3
3 NaN 4
4 B 5
5 A 6
6 NaN 7

GroupBy 默认行为 (dropna=True):
Group
A 10
B 7
Name: Value, dtype: int64

GroupBy 保留 NaN 分组 (dropna=False):
Group
A 10.0
B 7.0
NaN 11.0
Name: Value, dtype: float64
“`

可以看到,当 dropna=False 时,NaN 值被视为一个独立的分组键,并包含了对应的行数据。

8. 链式操作和效率考虑

Pandas 的方法通常可以进行链式操作,groupby 也不例外。这有助于编写更简洁的代码:

“`python

链式操作:按 Category 分组,选择 Value1 列,计算平均值

chain_example = df.groupby(‘Category’)[‘Value1’].mean()
print(“\n链式操作示例:”)
print(chain_example)
“`

在处理大型数据集时,效率是一个重要的考虑因素:

  • 优先使用优化的聚合函数或 agg():Pandas 对 sum, mean, count 等内置函数进行了高度优化。agg() 使用这些内置函数的字符串名称时也是高效的。
  • 谨慎使用 apply():虽然 apply() 最灵活,但如果可以用 agg()transform() 实现相同功能,通常后者会更高效。
  • 避免不必要的迭代:逐个处理分组通常比矢量化操作慢。
  • 考虑数据类型:数值型列的聚合通常比字符串或对象列更快。
  • 处理大型数据时,考虑内存使用和性能:对于海量数据,可能需要 Dask 或 Spark 等分布式计算框架,它们提供了类似 Pandas groupby 的接口。

9. 总结与实践

Pandas groupby 是数据分析中最常用和最强大的工具之一。掌握它意味着你可以轻松地对数据进行分组、汇总、计算描述性统计量、执行组内转换等复杂任务。

关键概念回顾:

  • Split-Apply-Combine 思想是 groupby 的基础。
  • groupby() 返回一个 GroupBy 对象,它是一个延迟计算的对象。
  • 聚合函数 (Aggregation):使用 sum(), mean(), count(), agg() 等将每个分组 réduit 成一个或少数几个值。结果通常是索引为分组键的 Series 或 DataFrame。
  • 转换函数 (Transformation):使用 transform() 对每个分组应用一个函数,该函数返回与原始分组具有相同索引的结果。结果会组合回原始 DataFrame 的结构。
  • apply():最通用的方法,对每个分组应用一个任意函数。它可以返回 Series, DataFrame 或标量,结果的组合方式取决于返回类型。
  • as_index=False 可以避免将分组键设置为索引。
  • 理解 size()count() 的区别。

实践建议:

  • 从简单的分组聚合开始 (groupby().sum())。
  • 练习使用 agg() 应用不同的函数到不同的列,特别是使用命名聚合。
  • 尝试用 transform() 创建新的特征列,比如组内平均值、组内排名等。
  • 当遇到复杂的组内计算时,考虑使用 apply(),但也要思考是否有更优化的 transform 或矢量化方法。
  • 阅读 Pandas 官方文档中关于 GroupBy 的部分,那里有更深入的细节和示例。

数据分析的很大一部分工作就是理解和处理数据中的分组结构。精通 groupby 将极大地提升你的数据处理能力。不断练习,将其融入你的数据分析工作流中,你会发现它能解决许多看似复杂的问题。

希望这篇详细指南能帮助你全面理解和掌握 Pandas groupby 的强大功能!祝你数据分析愉快!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部