9.使用groupby对数据分组和计算组内统计量
经常会遇到这样的场景:首先把数据分组,然后处理下组内数据,最后把处理结果组合起来。groupby就可以满足需求。
9.1 分组
只要提供一个分组依据,groupby就可以对数据分组了。
In [1]: df = pd.DataFrame(
...: [
...: ("bird", "Falconiformes", 389.0),
...: ("bird", "Psittaciformes", 24.0),
...: ("mammal", "Carnivora", 80.2),
...: ("mammal", "Primates", np.nan),
...: ("mammal", "Carnivora", 58),
...: ],
...: index=["falcon", "parrot", "lion", "monkey", "leopard"],
...: columns=("class", "order", "max_speed"),
...: )
...:
In [2]: df
Out[2]:
class order max_speed
falcon bird Falconiformes 389.0
parrot bird Psittaciformes 24.0
lion mammal Carnivora 80.2
monkey mammal Primates NaN
leopard mammal Carnivora 58.0
# default is axis=0
In [3]: grouped = df.groupby("class")
In [4]: grouped = df.groupby("order", axis="columns")
In [5]: grouped = df.groupby(["class", "order"])
分组依据可以是:
- 函数,会在各个索引值上被调用一次,其返回值就会被用作分组名称
- numpy array
- 字典或Series
- 列名
- 以上类型的混合组成的列表
下例以函数作为分组依据,把列分为元音和辅音2个组。
In [6]: 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),
...: }
...: )
...:
In [7]: df
Out[7]:
A B C D
0 foo one 0.469112 -0.861849
1 bar one -0.282863 -2.104569
2 foo two -1.509059 -0.494929
3 bar three -1.135632 1.071804
4 foo two 1.212112 0.721555
5 bar two -0.173215 -0.706771
6 foo one 0.119209 -1.039575
7 foo three -1.044236 0.271860
In [13]: def get_letter_type(letter):
....: if letter.lower() in 'aeiou':
....: return 'vowel'
....: else:
....: return 'consonant'
....:
In [14]: grouped = df.groupby(get_letter_type, axis=1)
9.1.2 多层索引的分组
传入level参数就可以选择分组依据的索引。
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
9.2 处理组内数据
sum, mean, groups, 选列,迭代, get_group,
通常,我们有3种方式处理数据:
- 聚合:计算统计量,把组内数据聚合成一个标量,比如计算组内均值
- 转换:比如标准化组内数据
- 过滤:根据计算结果丢弃一些组,比如根据组内均值过滤掉一些组
9.2.1 聚合
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
In [79]: grouped.size()
Out[79]:
A
bar 3
foo 5
dtype: int64
In [80]: grouped.count()
Out[80]:
B C D
A
bar 3 3 3
foo 5 4 5
常用的聚合函数:
函数 | 说明 |
mean() | |
sum() | |
size() | 返回组内数据个数 |
count() | 返回每个位置数据的个数,具体的看上边的演示 |
std() | |
var() | |
sem() | 标准误,即样本均数的标准差,是描述均数抽样分布的离散程度及衡量均数抽样误差大小的尺度,反映的是样本均数之间的变异。标准误不是标准差,是多个样本平均数的标准差。标准误用来衡量抽样误差。标准误越小,表明样本统计量与总体参数的值越接近,样本对总体越有代表性,用样本统计量推断总体参数的可靠度越大。因此,标准误是统计推断可靠性的指标。 |
describe() | |
first() | 取每个分组的第一条数据 |
last() | |
nth() | 取每个分组的第n条数据,如果n是列表则返回一个子集 |
min() | |
max() | |
nunique() | 返回每个位置unique元素的个数 |
9.2.2 一次应用多个函数
向agg函数传入函数组成的列表就可以一次应用多个函数。
In [83]: grouped.agg([np.sum, np.mean, np.std])
Out[83]:
C D
sum mean std sum mean std
A
bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330
foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
9.2.3 分别对每一列上应用不同函数
给agg传入字典(形如{“列名”:函数引用或“函数名”}),就可以在列上应用不同的函数:
In [94]: grouped.agg({"C": np.sum, "D": lambda x: np.std(x, ddof=1)})
Out[94]:
C D
A
bar 0.392940 1.366330
foo -1.796421 0.884785
In [95]: grouped.agg({"C": "sum", "D": "std"})
Out[95]:
C D
A
bar 0.392940 1.366330
foo -1.796421 0.884785
这样做的缺点是每一列只能应用一个函数。具名聚合可以弥补这一缺憾,只需为agg函数传入pandas.NamedAgg。 pandas.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
9.2.4 转换
转换会返回一个和分组大小相同的对象。不要在分组对象上原地操作,因为改变分组对象可能会产生意外的效果。
In
[112]: ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min())
In [113]: max = ts.groupby(lambda x: x.year).transform("max")
pass
9.2.5 过滤
filter 函数返回原始对象的子集。filter函数的参数必须是应# 9.使用groupby对数据分组和计算组内统计量
经常会遇到这样的场景:首先把数据分组,然后处理下组内数据,最后把处理结果组合起来。groupby就可以满足需求。
9.1 分组
只要提供一个分组依据,groupby就可以对数据分组了。
In [1]: df = pd.DataFrame(
...: [
...: ("bird", "Falconiformes", 389.0),
...: ("bird", "Psittaciformes", 24.0),
...: ("mammal", "Carnivora", 80.2),
...: ("mammal", "Primates", np.nan),
...: ("mammal", "Carnivora", 58),
...: ],
...: index=["falcon", "parrot", "lion", "monkey", "leopard"],
...: columns=("class", "order", "max_speed"),
...: )
...:
In [2]: df
Out[2]:
class order max_speed
falcon bird Falconiformes 389.0
parrot bird Psittaciformes 24.0
lion mammal Carnivora 80.2
monkey mammal Primates NaN
leopard mammal Carnivora 58.0
# default is axis=0
In [3]: grouped = df.groupby("class")
In [4]: grouped = df.groupby("order", axis="columns")
In [5]: grouped = df.groupby(["class", "order"])
分组依据可以是:
- 函数,会在各个索引值上被调用一次,其返回值就会被用作分组名称
- numpy array
- 字典或Series
- 列名
- 以上类型的混合组成的列表
下例以函数作为分组依据,把列分为元音和辅音2个组。
In [6]: 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),
...: }
...: )
...:
In [7]: df
Out[7]:
A B C D
0 foo one 0.469112 -0.861849
1 bar one -0.282863 -2.104569
2 foo two -1.509059 -0.494929
3 bar three -1.135632 1.071804
4 foo two 1.212112 0.721555
5 bar two -0.173215 -0.706771
6 foo one 0.119209 -1.039575
7 foo three -1.044236 0.271860
In [13]: def get_letter_type(letter):
....: if letter.lower() in 'aeiou':
....: return 'vowel'
....: else:
....: return 'consonant'
....:
In [14]: grouped = df.groupby(get_letter_type, axis=1)
9.1.2 多层索引的分组
传入level参数就可以选择分组依据的索引。
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
9.2 处理组内数据
sum, mean, groups, 选列,迭代, get_group,
通常,我们有3种方式处理数据:
- 聚合:计算统计量,把组内数据聚合成一个标量,比如计算组内均值
- 转换:比如标准化组内数据
- 过滤:根据计算结果丢弃一些组,比如根据组内均值过滤掉一些组
9.2.1 聚合
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
In [79]: grouped.size()
Out[79]:
A
bar 3
foo 5
dtype: int64
In [80]: grouped.count()
Out[80]:
B C D
A
bar 3 3 3
foo 5 4 5
常用的聚合函数:
函数 | 说明 |
mean() | |
sum() | |
size() | 返回组内数据个数 |
count() | 返回每个位置数据的个数,具体的看上边的演示 |
std() | |
var() | |
sem() | 标准误,即样本均数的标准差,是描述均数抽样分布的离散程度及衡量均数抽样误差大小的尺度,反映的是样本均数之间的变异。标准误不是标准差,是多个样本平均数的标准差。标准误用来衡量抽样误差。标准误越小,表明样本统计量与总体参数的值越接近,样本对总体越有代表性,用样本统计量推断总体参数的可靠度越大。因此,标准误是统计推断可靠性的指标。 |
describe() | |
first() | 取每个分组的第一条数据 |
last() | |
nth() | 取每个分组的第n条数据,如果n是列表则返回一个子集 |
min() | |
max() | |
nunique() | 返回每个位置unique元素的个数 |
9.2.2 一次应用多个函数
向agg函数传入函数组成的列表就可以一次应用多个函数。
In [83]: grouped.agg([np.sum, np.mean, np.std])
Out[83]:
C D
sum mean std sum mean std
A
bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330
foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
9.2.3 分别对每一列上应用不同函数
给agg传入字典(形如{“列名”:函数引用或“函数名”}),就可以在列上应用不同的函数:
In [94]: grouped.agg({"C": np.sum, "D": lambda x: np.std(x, ddof=1)})
Out[94]:
C D
A
bar 0.392940 1.366330
foo -1.796421 0.884785
In [95]: grouped.agg({"C": "sum", "D": "std"})
Out[95]:
C D
A
bar 0.392940 1.366330
foo -1.796421 0.884785
这样做的缺点是每一列只能应用一个函数。具名聚合可以弥补这一缺憾,只需为agg函数传入pandas.NamedAgg。 pandas.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
9.2.4 转换
转换会返回一个和分组大小相同的对象。不要在分组对象上原地操作,因为改变分组对象可能会产生意外的效果。
In
[112]: ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min())
In [113]: max = ts.groupby(lambda x: x.year).transform("max")
pass
9.2.5 过滤
filter 函数返回原始对象的子集。filter函数的参数必须是应用在每个分组,返回True或False的函数。
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
自动排除讨厌的特征
pipe
9.3 合并数据
参考:
Group by: split-apply-combine用在每个分组,返回True或False的函数。
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