pandas groupby 基础介绍与用法 – wiki基地


Pandas groupby:数据分组与聚合的利器

在数据分析领域,我们经常需要对数据进行分组统计。例如,我们可能想知道不同产品的总销售额、不同地区的平均气温,或者不同班级的学生数量。Pandas 库中的 groupby() 方法就是用来解决这类问题的强大工具。它允许你根据一个或多个键(keys)将 DataFrame 拆分成若干组,然后对每个组独立地执行一些操作(如计算总和、平均值、计数等),最后将结果合并起来。

理解 groupby 的核心在于其背后的“拆分-应用-合并”(Split-Apply-Combine)范式。掌握了这个范式,你就能游刃有余地使用 groupby 来处理各种复杂的数据分析任务。

本文将详细介绍 groupby 的基础概念、核心原理以及常见用法。

1. 理解“拆分-应用-合并”(Split-Apply-Combine)范式

Pandas groupby 操作的核心思想可以分解为以下三个步骤:

  1. 拆分 (Split):根据某个或多个键,将原始 DataFrame 拆分成若干个子 DataFrame。每个子 DataFrame 包含原始数据中具有相同键值的所有行。
  2. 应用 (Apply):对每个拆分出来的子 DataFrame,独立地应用一个函数。这个函数可以是聚合函数(如求和、平均值)、转换函数(如标准化、填充缺失值)或过滤函数(如选取满足条件的组)。
  3. 合并 (Combine):将所有子 DataFrame 应用函数后得到的结果合并成一个统一的输出结构(通常是一个 Series 或 DataFrame)。

举个例子:

假设你有一个包含学生姓名、班级和分数的数据表。你想计算每个班级的平均分数。

  • 拆分: 根据“班级”这一列,将数据拆分成“一班”、“二班”、“三班”等多个小组的数据。
  • 应用: 对“一班”的数据计算平均分数,对“二班”的数据计算平均分数,以此类推。
  • 合并: 将所有班级计算出的平均分数合并成一个结果表,显示每个班级及其对应的平均分数。

Pandas 的 groupby() 方法就是负责执行第一步“拆分”,它返回一个 GroupBy 对象,这个对象知道如何对各个组执行后续的“应用”和“合并”操作。

2. groupby() 的基本用法

groupby() 方法通常作用于一个 DataFrame,其最基本的语法是:

python
df.groupby(key)

这里的 key 指定了用于分组的依据。key 可以是以下几种类型:

  • 单个列名 (字符串):根据某一列的唯一值进行分组。
  • 多个列名组成的列表 ([str1, str2, …]):根据多个列的组合唯一值进行分组。
  • 一个 Series 或数组:其长度必须与 DataFrame 的行数相同,用于为每行指定其所属的组。
  • 一个字典或 Series:将索引值映射到组名。
  • 一个函数:作用于 DataFrame 的索引,根据函数返回值进行分组。

df.groupby(key) 的结果并不是一个 DataFrame,而是一个 GroupBy 对象。这个对象包含了分组信息,可以对其进一步调用各种方法来执行“应用”和“合并”操作。

“`python
import pandas as pd
import numpy as np

创建一个示例 DataFrame

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

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

按 ‘Category’ 列进行分组

grouped = df.groupby(‘Category’)

print(“\n分组后的 GroupBy 对象:”)
print(grouped) # 输出

可以遍历 GroupBy 对象,查看每个组的数据(虽然不常用)

print(“\n遍历分组:”)

for name, group in grouped:

print(f”组名: {name}”)

print(group)

print(“-” * 20)

“`

从上面的例子可以看出,直接 groupby() 返回的是一个 GroupBy 对象,你需要对这个对象调用后续的方法来获取具体的结果。

3. 应用函数:聚合(Aggregation)

聚合是 groupby 最常见的应用场景。聚合操作会将每个组的多行数据聚合成一个单一的值。常用的聚合函数包括:

  • sum():计算总和
  • mean():计算平均值
  • count():计算非 NaN 值的数量
  • size():计算组的大小(行数,包括 NaN)
  • min():计算最小值
  • max():计算最大值
  • std():计算标准差
  • var():计算方差
  • first():选取组的第一个值
  • last():选取组的最后一个值

你可以直接在 GroupBy 对象上调用这些函数:

“`python

计算每个类别的 Value1 总和

category_sum = grouped[‘Value1’].sum()
print(“\n每个类别的 Value1 总和:”)
print(category_sum)

输出:

Category

A 47

B 51

C 65

Name: Value1, dtype: int64

计算每个类别的 Value2 平均值

category_mean = grouped[‘Value2’].mean()
print(“\n每个类别的 Value2 平均值:”)
print(category_mean)

输出:

Category

A 117.5

B 170.0

C 210.0

Name: Value2, dtype: float64

计算每个类别的行数 (使用 size() 或 count())

size() 包括 NaN,count() 不包括 NaN

category_size = grouped.size()
category_count_v1 = grouped[‘Value1’].count() # Count non-NaNs in Value1
print(“\n每个类别的行数 (size):”)
print(category_size)

输出:

Category

A 4

B 3

C 3

dtype: int64

print(“\n每个类别的 Value1 非NaN计数 (count):”)
print(category_count_v1)

输出:

Category

A 4

B 3

C 3

Name: Value1, dtype: int64

对所有数值列应用相同的聚合函数

all_agg = grouped.sum()
print(“\n每个类别所有数值列的总和:”)
print(all_agg)

输出:

Value1 Value2

Category

A 47 470

B 51 510

C 65 650

“`

注意,当对整个 GroupBy 对象应用聚合函数时(如 grouped.sum()),Pandas 会自动对所有非分组键数值列执行聚合操作。如果想对特定列进行聚合,可以通过先选择列再聚合的方式(如 grouped['Value1'].sum())。

4. 应用函数:使用 agg()aggregate() 进行多种聚合

当你想对同一个分组执行多种不同的聚合操作,或者对不同的列应用不同的聚合函数时,agg() (或 aggregate(), 它们是等价的) 方法就非常有用了。

agg() 方法非常灵活,可以接受多种形式的参数:

  • 单个函数名 (字符串或函数对象):对所有适用的列应用同一个聚合函数。
  • 函数名列表 ([func1, func2, …]):对所有适用的列应用多个聚合函数,结果是带有 MultiIndex 列的 DataFrame。
  • 字典 ({column: func} 或 {column: [func1, func2]}):对指定的列应用指定的聚合函数或函数列表。

示例:

“`python

使用 agg() 对同一列进行多种聚合

agg_multi_func = grouped[‘Value1’].agg([‘sum’, ‘mean’, ‘count’])
print(“\n每个类别的 Value1 的总和、平均值和计数:”)
print(agg_multi_func)

输出:

sum mean count

Category

A 47 11.75 4

B 51 17.00 3

C 65 21.67 3

使用 agg() 对不同列应用不同聚合函数

agg_diff_cols = grouped.agg({‘Value1’: ‘sum’, ‘Value2’: ‘mean’})
print(“\n每个类别的 Value1 总和和 Value2 平均值:”)
print(agg_diff_cols)

输出:

Value1 Value2

Category

A 47 117.5

B 51 170.0

C 65 210.0

使用 agg() 对不同列应用函数列表

agg_complex = grouped.agg({
‘Value1’: [‘sum’, ‘mean’],
‘Value2’: [‘min’, ‘max’, ‘count’]
})
print(“\n每个类别的 Value1 (sum, mean) 和 Value2 (min, max, count):”)
print(agg_complex)

输出结果列会带有 MultiIndex:

Value1 Value2

sum mean min max count

Category

A 47 11.75 100.0 140.0 4

B 51 17.00 150.0 200.0 3

C 65 21.67 180.0 250.0 3

“`

使用字典方式的 agg() 非常强大和灵活,你可以精确控制对哪些列应用哪些聚合函数。

5. 应用函数:转换(Transformation)

转换操作不同于聚合,它不会将每个组的数据聚合成一个单一的值。相反,转换操作会返回一个与原始组具有相同索引和大小的对象(Series 或 DataFrame),其结果的形状与原始 DataFrame 在分组维度上是对应的。转换通常用于在组内进行标准化、填充缺失值或计算排名等操作。

转换操作主要使用 transform() 方法。传递给 transform() 的函数必须返回一个 Series 或 DataFrame,其索引与输入组的索引相同。

示例:

“`python

计算每个类别中 Value1 相对于该类别平均值的偏差

grouped[‘Value1’] 是一个 GroupBy Series 对象

.transform(‘mean’) 会计算每个组的平均值,并将结果广播回原始 DataFrame 的形状

df[‘Value1_Deviation’] = df[‘Value1’] – grouped[‘Value1’].transform(‘mean’)
print(“\n添加了 Value1 相对于类别平均值的偏差列:”)
print(df)

输出 (部分):

Category Value1 Value2 Value1_Deviation

0 A 10 100 -1.75

1 B 15 150 -2.00

2 A 12 120 0.25

填充每个类别中 Value2 的缺失值,使用该类别的平均值

假设我们有一些缺失值

df_with_nan = df.copy()
df_with_nan.loc[[0, 4, 8], ‘Value2’] = np.nan
print(“\n带有缺失值的 DataFrame:”)
print(df_with_nan)

df_filled = df_with_nan.copy()
df_filled[‘Value2’] = df_with_nan.groupby(‘Category’)[‘Value2’].transform(lambda x: x.fillna(x.mean())) # 使用 lambda 函数或内置的 fillna

或者更简洁地:

df_filled[‘Value2’] = df_with_nan.groupby(‘Category’)[‘Value2’].transform(‘mean’) # 注意这里是计算平均值再用它来填充

fillna 的 transform 应用方式通常是 lambda

df_filled[‘Value2’] = df_with_nan.groupby(‘Category’)[‘Value2’].transform(lambda x: x.fillna(x.mean()))

print(“\n使用类别平均值填充缺失值后的 DataFrame:”)
print(df_filled)

比较原始 DataFrame 和填充后的 DataFrame,观察 NaN 是否被对应类别的平均值替换

“`

transform() 方法非常强大,因为它允许你在进行组内计算后,将结果“无缝”地添加到原始 DataFrame 中作为新列,这在特征工程中非常常见。

6. 应用函数:过滤(Filtering)

过滤操作用于根据组的属性来丢弃或保留整个组。例如,你可能只想分析那些数据量大于一定阈值的组,或者那些某个指标(如平均值)超过特定标准的组。

过滤操作使用 filter() 方法。传递给 filter() 的函数必须返回一个布尔值(True 表示保留该组,False 表示丢弃该组)。这个函数会接收整个组的数据(一个 DataFrame)作为输入。

示例:

“`python

保留那些 Value1 总和大于 50 的类别

传递给 filter 的函数接收一个组的 DataFrame 作为参数

filtered_df = grouped.filter(lambda x: x[‘Value1’].sum() > 50)
print(“\n保留 Value1 总和大于 50 的类别:”)
print(filtered_df)

只有 Category B 和 C 的 Value1 总和大于 50

输出:

Category Value1 Value2 Value1_Deviation

1 B 15 150 -2.00

4 B 20 200 3.00

5 C 22 220 0.33

7 B 16 160 -1.00

8 C 25 250 3.33

保留那些行数少于 4 的类别

filtered_by_size = grouped.filter(lambda x: len(x) < 4)
print(“\n保留行数少于 4 的类别:”)
print(filtered_by_size)

Category B 和 C 各有 3 行,Category A 有 4 行

输出:

Category Value1 Value2 Value1_Deviation

1 B 15 150 -2.00

3 C 18 180 -3.67

4 B 20 200 3.00

5 C 22 220 0.33

7 B 16 160 -1.00

8 C 25 250 3.33

“`

使用 filter() 时,需要确保你的过滤条件函数能够返回一个布尔值。

7. 按多列进行分组

你可以通过传递一个列名列表给 groupby() 方法来按多个列进行分组。这将根据这些列的组合唯一值来创建组。

“`python
data_multi = {
‘City’: [‘New York’, ‘Paris’, ‘New York’, ‘London’, ‘Paris’, ‘London’, ‘New York’],
‘Year’: [2020, 2020, 2021, 2020, 2021, 2021, 2021],
‘Sales’: [100, 150, 120, 200, 180, 220, 130]
}
df_multi = pd.DataFrame(data_multi)

print(“\n原始多列分组 DataFrame:”)
print(df_multi)

按 ‘City’ 和 ‘Year’ 进行分组

grouped_multi = df_multi.groupby([‘City’, ‘Year’])

计算每个城市每年的总销售额

city_year_sales = grouped_multi[‘Sales’].sum()
print(“\n每个城市每年的总销售额:”)
print(city_year_sales)

输出结果的索引将是 MultiIndex:

City Year

London 2020 200

2021 220

New York 2020 100

2021 250

Paris 2020 150

2021 180

Name: Sales, dtype: int64

你也可以对多列分组结果应用多种聚合

multi_agg = grouped_multi.agg({‘Sales’: [‘sum’, ‘mean’]})
print(“\n每个城市每年的销售总和和平均值:”)
print(multi_agg)

输出:

Sales

sum mean

City Year

London 2020 200.0 200.0

2021 220.0 220.0

New York 2020 100.0 100.0

2021 250.0 125.0

Paris 2020 150.0 150.0

2021 180.0 180.0

“`

按多列分组的结果的索引通常是一个 MultiIndex (分层索引),这在使用 agg() 等方法时尤其明显。你可以使用 .reset_index() 方法将 MultiIndex 转换为普通列。

“`python

将 MultiIndex 转换为普通列

multi_agg_flat = multi_agg.reset_index()
print(“\n将 MultiIndex 转换为普通列:”)
print(multi_agg_flat)

输出:

City Year Sales sum mean

0 London 2020 200.0 200.0

1 London 2021 220.0 220.0

2 New York 2020 100.0 100.0

3 New York 2021 250.0 125.0

4 Paris 2020 150.0 150.0

5 Paris 2021 180.0 180.0

“`

另一种避免 MultiIndex 的方法是在 groupby() 中设置 as_index=False

“`python

使用 as_index=False 避免 MultiIndex

grouped_no_index = df_multi.groupby([‘City’, ‘Year’], as_index=False)
city_year_sales_no_index = grouped_no_index[‘Sales’].sum()
print(“\n使用 as_index=False 的分组总销售额:”)
print(city_year_sales_no_index)

输出 (结果是一个 DataFrame):

City Year Sales

0 London 2020 200

1 London 2021 220

2 New York 2020 100

3 New York 2021 250

4 Paris 2020 150

5 Paris 2021 180

“`

使用 as_index=False 可以让分组键变成结果 DataFrame 的普通列,这在某些情况下更方便后续的数据处理。

8. 其他分组键类型

除了列名,groupby() 还可以使用其他类型作为分组键:

  • Series 或数组:与 DataFrame 长度相同的 Series 或数组,其值用于分组。

    “`python

    使用 Series 作为分组键

    group_keys = pd.Series([‘Group1’, ‘Group2’, ‘Group1’, ‘Group1’, ‘Group2’, ‘Group2’, ‘Group1’, ‘Group2’, ‘Group1’, ‘Group1’])
    grouped_by_series = df.groupby(group_keys)
    print(“\n使用 Series 作为分组键的总和:”)
    print(grouped_by_series[‘Value1’].sum())

    输出:

    Group1 97

    Group2 71

    Name: Value1, dtype: int64

    “`

  • 函数:函数会作用于 DataFrame 的索引,其返回值用于分组。

    “`python

    假设 DataFrame 索引是日期,按星期几分组

    dates = pd.date_range(‘2023-01-01’, periods=10)
    df_date = pd.DataFrame({‘Value’: np.random.rand(10) * 100}, index=dates)
    print(“\n日期索引 DataFrame:”)
    print(df_date)

    按索引的星期几分组 (0=周一, 6=周日)

    grouped_by_weekday = df_date.groupby(lambda x: x.weekday())
    print(“\n按星期几分组的平均值:”)
    print(grouped_by_weekday[‘Value’].mean())

    输出类似:

    0 某个平均值 (周一)

    1 某个平均值 (周二)

    “`

  • 字典:将索引值映射到组名。

    “`python

    使用字典将索引映射到组

    index_groups = {0: ‘X’, 1: ‘Y’, 2: ‘X’, 3: ‘Y’, 4: ‘X’, 5: ‘Y’, 6: ‘X’, 7: ‘Y’, 8: ‘X’, 9: ‘Y’}
    grouped_by_dict = df.groupby(index_groups)
    print(“\n使用字典将索引映射到分组的总和:”)
    print(grouped_by_dict[‘Value1’].sum())

    输出:

    X 57

    Y 106

    Name: Value1, dtype: int64

    “`

9. 总结

Pandas 的 groupby() 方法是进行分组数据分析的核心工具,其“拆分-应用-合并”范式简洁而强大。通过对 GroupBy 对象应用聚合 (agg/aggregate)、转换 (transform) 和过滤 (filter) 等操作,你可以轻松地完成各种复杂的统计和数据处理任务。

掌握 groupby 的关键在于:

  1. 理解 Split-Apply-Combine 原理。
  2. 知道 groupby() 返回的是一个 GroupBy 对象。
  3. 熟练使用 GroupBy 对象上的聚合、转换和过滤方法。
  4. 掌握按单列、多列以及其他灵活方式(如函数、Series)进行分组的方法。
  5. 了解 agg() 在执行多种聚合时的强大灵活性。

通过多加练习,你会发现 groupby 能够极大地提高你处理结构化数据的效率。从基础的求和计数到复杂的分组特征工程,groupby 都是你的得力助手。现在就开始动手尝试吧!


发表评论

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

滚动至顶部