Pandas GroupBy 高级用法详解:从分组到聚合转换
本文深入解析 Pandas 中的 GroupBy 操作,涵盖数据分割、聚合、转换与过滤四大核心环节。详细讲解如何按单列或多列分组,处理 NaN 值,利用 as_index 控制索引,以及应用 sum、mean 等通用聚合函数。此外,还介绍了 NamedAgg 的精准定义、transform 数据转换技巧及 apply 灵活操作,助力高效数据分析。
简介
在 Pandas 中,DataFrame 数据类型支持类似数据库的 groupby 操作。一般而言,groupby 流程包含三个关键步骤:首先分割数据,其次应用变换或聚合,最后合并结果。
本文将详细阐述 Pandas 中 GroupBy 的具体实现与应用。
分割数据
分割数据的旨在将 DataFrame 划分为多个组。执行 groupby 操作时,需在创建 DataFrame 时指定相应的标签作为分组依据。
df = pd.DataFrame(
...: {
...: "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
...: "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
...: "C": np.random.randn(8),
...: "D": np.random.randn(8),
...: }
...: )
...:
df
Out[61]:
A B C D
0 foo one -0.490565 -0.233106
1 bar one 0.430089 1.040789
2 foo two 0.653449 -1.155530
3 bar three -0.610380 -0.447735
4 foo two -0.934961 0.256358
5 bar two -0.256263 -0.661954
6 foo one -1.132186 -0.304330
7 foo three 2.129757 0.445744
默认情况下,groupby 沿 x 轴(即行方向)进行分组。用户可以选择单列或多列进行分组操作。
In [8]: grouped = df.groupby("A")
In [9]: grouped = df.groupby(["A", "B"])
多索引处理
自 0.24 版本起,若数据包含多级索引(MultiIndex),用户可从中选取特定的索引层级进行分组。
In [10]: df2 = df.set_index(["A", "B"])
In [11]: grouped = df2.groupby(level=df2.index.names.difference(["B"]))
In [12]: grouped.sum()
Out[12]:
C D
A
bar -1.591710 -1.739537
foo -0.752861 -1.402938
获取分组数据
利用 get_group 方法,可以提取特定分组后的数据子集。
In [24]: df3 = pd.DataFrame({"X": ["A", "B", "A", "B"], "Y": [1, 4, 3, 2]})
In [25]: df3.groupby(["X"]).get_group("A")
Out[25]:
X Y
0 A 1
2 A 3
In [26]: df3.groupby(["X"]).get_group("B")
Out[26]:
X Y
1 B 4
3 B 2
处理缺失值
默认情况下,groupby 会排除键值中的 NaN 数据。若需将 NaN 纳入分组,可通过设置 dropna=False 参数来实现。
In [27]: df_list = [[1, 2, 3], [1, None, 4], [2, 1, 3], [1, 2, 2]]
In [28]: df_dropna = pd.DataFrame(df_list, columns=["a", "b", "c"])
In [29]: df_dropna
Out[29]:
a b c
0 1 2.0 3
1 1 NaN 4
2 2 1.0 3
3 1 2.0 2
# Default ``dropna`` is set to True, which will exclude NaNs in keys
In [30]: df_dropna.groupby(by=["b"], dropna=True).sum()
Out[30]:
a c
b
1.0 2 3
2.0 2 5
# In order to allow NaN in keys, set ``dropna`` to False
In [31]: df_dropna.groupby(by=["b"], dropna=False).sum()
Out[31]:
a c
b
1.0 2 3
2.0 2 5
NaN 1 4
groups 属性
groupby 对象提供了一个 groups 属性,该属性返回一个键值对字典。其中,key 为分类依据,value 为对应分类数据的索引列表。
In [34]: grouped = df.groupby(["A", "B"])
In [35]: grouped.groups
Out[35]: {('bar', 'one'): [1], ('bar', 'three'): [3], ('bar', 'two'): [5], ('foo', 'one'): [0, 6], ('foo', 'three'): [7], ('foo', 'two'): [2, 4]}
In [36]: len(grouped)
Out[36]: 6
索引层级分组
针对多级索引对象,groupby 允许指定具体的索引层级进行分组操作。
In [40]: arrays = [
....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
....: ["one", "two", "one", "two", "one", "two", "one", "two"],
....: ]
....:
In [41]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"])
In [42]: s = pd.Series(np.random.randn(8), index=index)
In [43]: s
Out[43]:
first second
bar one -0.919854
two -0.042379
baz one 1.247642
two -0.009920
foo one 0.290213
two 0.495767
qux one 0.362949
two 1.548106
dtype: float64
按第一级索引分组示例:
In [44]: grouped = s.groupby(level=0)
In [45]: grouped.sum()
Out[45]:
first
bar -0.962232
baz 1.237723
foo 0.785980
qux 1.911055
dtype: float64
按第二级索引分组示例:
In [46]: s.groupby(level="second").sum()
Out[46]:
second
one 0.980950
two 1.991575
dtype: float64
遍历分组
获取分组对象后,可通过 for 循环遍历各个组及其数据。
In [62]: grouped = df.groupby('A')
In [63]: for name, group in grouped:
....: print(name)
....: print(group)
....:
bar
A B C D
1 bar one 0.254161 1.511763
3 bar three 0.215897 -0.990582
5 bar two -0.077118 1.211526
foo
A B C D
0 foo one -0.575247 1.346061
2 foo two -1.143704 1.627081
4 foo two 1.193555 -0.441652
6 foo one -0.408530 0.268520
7 foo three -0.862495 0.024580
若进行多字段分组,分组名称将表现为元组形式。
In [64]: for name, group in df.groupby(['A', 'B']):
....: print(name)
....: print(group)
....:
('bar', 'one')
A B C D
1 bar one 0.254161 1.511763
('bar', 'three')
A B C D
3 bar three 0.215897 -0.990582
('bar', 'two')
A B C D
5 bar two -0.077118 1.211526
('foo', 'one')
A B C D
0 foo one -0.575247 1.346061
6 foo one -0.408530 0.268520
('foo', 'three')
A B C D
7 foo three -0.862495 0.02458
('foo', 'two')
A B C D
2 foo two -1.143704 1.627081
4 foo two 1.193555 -0.441652
聚合操作
完成分组后,即可对数据进行聚合计算。
In [67]: grouped = df.groupby("A")
In [68]: grouped.aggregate(np.sum)
Out[68]:
C D
A
bar 0.392940 1.732707
foo -1.796421 2.824590
In [69]: grouped = df.groupby(["A", "B"])
In [70]: grouped.aggregate(np.sum)
Out[70]:
C D
A B
bar one 0.254161 1.511763
three 0.215897 -0.990582
two -0.077118 1.211526
foo one -0.983776 1.614581
three -0.862495 0.024580
two 0.049851 1.185429
对于多级索引数据,默认返回结果仍保留多级索引结构。若希望使用新的单级索引,可添加 as_index=False 参数。
In [71]: grouped = df.groupby(["A", "B"], as_index=False)
In [72]: grouped.aggregate(np.sum)
Out[72]:
A B C D
0 bar one 0.254161 1.511763
1 bar three 0.215897 -0.990582
2 bar two -0.077118 1.211526
3 foo one -0.983776 1.614581
4 foo three -0.862495 0.024580
5 foo two 0.049851 1.185429
In [73]: df.groupby("A", as_index=False).sum()
Out[73]:
A C D
0 bar 0.392940 1.732707
1 foo -1.796421 2.824590
上述操作的效果等同于调用 reset_index 方法。
In [74]: df.groupby(["A", "B"]).sum().reset_index()
使用 grouped.size() 可计算每个分组的大小。
In [75]: grouped.size()
Out[75]:
A B size
0 bar one 1
1 bar three 1
2 bar two 1
3 foo one 2
4 foo three 1
5 foo two 2
使用 grouped.describe() 可获取分组的统计描述信息。
In [76]: grouped.describe()
Out[76]:
C ... D
count mean std min 25% 50% ... std min 25% 50% 75% max
0 1.0 0.254161 NaN 0.254161 0.254161 0.254161 ... NaN 1.511763 1.511763 1.511763 1.511763 1.511763
1 1.0 0.215897 NaN 0.215897 0.215897 0.215897 ... NaN -0.990582 -0.990582 -0.990582 -0.990582 -0.990582
2 1.0 -0.077118 NaN -0.077118 -0.077118 -0.077118 ... NaN 1.211526 1.211526 1.211526 1.211526 1.211526
3 2.0 -0.491888 0.117887 -0.575247 -0.533567 -0.491888 ... 0.761937 0.268520 0.537905 0.807291 1.076676 1.346061
4 1.0 -0.862495 NaN -0.862495 -0.862495 -0.862495 ... NaN 0.024580 0.024580 0.024580 0.024580 0.024580
5 2.0 0.024925 1.652692 -1.143704 -0.559389 0.024925 ... 1.462816 -0.441652 0.075531 0.592714 1.109898 1.627081
[6 rows x 16 columns]
通用聚合方法
以下是 Pandas 支持的通用聚合方法列表。
| 函数 | 描述 |
|---|---|
mean() |
平均值 |
sum() |
求和 |
size() |
计算size |
count() |
group的统计 |
std() |
标准差 |
var() |
方差 |
sem() |
均值的标准误 |
describe() |
统计信息描述 |
first() |
第一个group值 |
last() |
最后一个group值 |
nth() |
第n个group值 |
min() |
最小值 |
max() |
最大值 |
用户可同时指定多个聚合方法以获取多维度统计结果。
In [81]: grouped = df.groupby("A")
In [82]: grouped["C"].agg([np.sum, np.mean, np.std])
Out[82]:
sum mean std
A
bar 0.392940 0.130980 0.181231
foo -1.796421 -0.359284 0.912265
聚合结果支持重命名,以便更清晰地标识各列含义。
In [84]: (
....: grouped["C"]
....: .agg([np.sum, np.mean, np.std])
....: .rename(columns={"sum": "foo", "mean": "bar", "std": "baz"})
....: )
....:
Out[84]:
foo bar baz
A
bar 0.392940 0.130980 0.181231
foo -1.796421 -0.359284 0.912265
NamedAgg 用法
NamedAgg 允许对聚合操作进行更精确的定义,它包含 column 和 aggfunc 两个定制字段,分别指定目标列和聚合函数。
In [88]: animals = pd.DataFrame(
....: {
....: "kind": ["cat", "dog", "cat", "dog"],
....: "height": [9.1, 6.0, 9.5, 34.0],
....: "weight": [7.9, 7.5, 9.9, 198.0],
....: }
....: )
....:
In [89]: animals
Out[89]:
kind height weight
0 cat 9.1 7.9
1 dog 6.0 7.5
2 cat 9.5 9.9
3 dog 34.0 198.0
In [90]: animals.groupby("kind").agg(
....: min_height=pd.NamedAgg(column="height", aggfunc="min"),
....: max_height=pd.NamedAgg(column="height", aggfunc="max"),
....: average_weight=pd.NamedAgg(column="weight", aggfunc=np.mean),
....: )
....:
Out[90]:
min_height max_height average_weight
kind
cat 9.1 9.5 8.90
dog 6.0 34.0 102.75
或者,用户可以直接使用元组形式来指定聚合规则。
In [91]: animals.groupby("kind").agg(
....: min_height=("height", "min"),
....: max_height=("height", "max"),
....: average_weight=("weight", np.mean),
....: )
....:
Out[91]:
min_height max_height average_weight
kind
cat 9.1 9.5 8.90
dog 6.0 34.0 102.75
不同列指定不同聚合方法
通过向 agg 方法传入字典,可以为不同的列指定不同的聚合策略。
In [95]: grouped.agg({"C": "sum", "D": "std"})
Out[95]:
C D
A
bar 0.392940 1.366330
foo -1.796421 0.884785
转换操作
转换操作旨在生成与原对象大小相同的对象。在数据分析过程中,数据转换是常见且重要的环节。
转换操作可结合 lambda 表达式使用,以实现自定义逻辑。
In [112]: ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min())
此外,转换还可用于填充 NaN 值,确保数据完整性。
In [121]: transformed = grouped.transform(lambda x: x.fillna(x.mean()))
过滤操作
filter 方法允许通过 lambda 表达式过滤掉不符合条件的数据。
In [136]: sf = pd.Series([1, 1, 2, 3, 3, 3])
In [137]: sf.groupby(sf).filter(lambda x: x.sum() > 2)
Out[137]:
3 3
4 3
5 3
dtype: int64
Apply 操作
部分数据可能不适合直接进行聚合或转换,Pandas 提供了 apply 方法,用于执行更加灵活和复杂的转换操作。
In [156]: df
Out[156]:
A B C D
0 foo one -0.575247 1.346061
1 bar one 0.254161 1.511763
2 foo two -1.143704 1.627081
3 bar three 0.215897 -0.990582
4 foo two 1.193555 -0.441652
5 bar two -0.077118 1.211526
6 foo one -0.408530 0.268520
7 foo three -0.862495 0.024580
In [157]: grouped = df.groupby("A")
# could also just call .describe()
In [158]: grouped["C"].apply(lambda x: x.describe())
Out[158]:
A
bar count 3.000000
mean 0.130980
std 0.181231
min -0.077118
25% 0.069390
...
foo min -1.143704
25% -0.862495
50% -0.575247
75% -0.408530
max 1.193555
Name: C, Length: 16, dtype: float64
用户可以外接自定义函数,通过 apply 方法应用于数据。
In [159]: grouped = df.groupby('A')['C']
In [160]: def f(group):
.....: return pd.DataFrame({'original': group,
.....: 'demeaned': group - group.mean()})
.....:
In [161]: grouped.apply(f)
Out[161]:
original demeaned
0 -0.575247 -0.215962
1 0.254161 0.123181
2 -1.143704 -0.784420
3 0.215897 0.084917
4 1.193555 1.552839
5 -0.077118 -0.208098
6 -0.408530 -0.049245
7 -0.862495 -0.503211
本文已收录于 http://www.flydean.com/11-python-pandas-groupby/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
到此这篇关于 Pandas 高级教程之 Pandas 中的 GroupBy 操作的文章就介绍到这了,更多相关 Pandas GroupBy 用法内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









