pandas中的groupby函数用于根据一个或者多个字段划分分组。
首先了解一下groupby函数的定义:
chipo.groupby(
by=None, # 进行分组的字段
axis: 'Axis' = 0, # 轴,axis=0表示按照行进行分组,axis=1表示按照列进行分组,默认按行
level: 'IndexLabel | None' = None, # 指定分组的层级,处理多层索引时有用
as_index: 'bool' = True, # 用于控制分组后的结果是否以分组列作为索引。
sort: 'bool' = True, # 用于指定分组后的结果是否按照分组键进行排序
group_keys: 'bool | lib.NoDefault' = <no_default>, # 用于控制分组键是否显示在结果中。
squeeze: 'bool | lib.NoDefault' = <no_default>, # 控制返回结果的形状,True时只保留每个组中的一列,而将其他列作为索引,False 或未指定时,表示保留所有聚合后的列
observed: 'bool' = False, # 控制分组键的观察方式。False时,对于不存在的一个分组将会在结果中显示,True时,对于不存在的一个分组将会在结果中忽略,注意:observed参数仅仅在`Categorical`类型的分组键上有用,对于其他类型的键,将忽略`observed`参数的设置。
dropna: 'bool' = True, # 控制在分组过程中是否删除包含缺失值(NaN)的分组
) -> 'DataFrameGroupBy'
对各个参数的解释:
by=None
, 进行分组的字段
axis: 'Axis' = 0
, 轴,axis=0表示按照行进行分组,axis=1表示按照列进行分组,默认按行
level: 'IndexLabel | None' = None
, 指定分组的层级,处理多层索引时有用
as_index: 'bool' = True
, 用于控制分组后的结果是否以分组列作为索引。
sort: 'bool' = True
, 用于指定分组后的结果是否按照分组键进行排序
group_keys: 'bool | lib.NoDefault' =
, 用于控制分组键是否显示在结果中。
squeeze: 'bool | lib.NoDefault' =
, 控制返回结果的形状,True时只保留每个组中的一列,而将其他列作为索引,False 或未指定时,表示保留所有聚合后的列,将被弃用。
observed: 'bool' = False
, 控制分组键的观察方式。False时,对于不存在的一个分组将会在结果中显示,True时,对于不存在的一个分组将会在结果中忽略,注意:observed参数仅仅在Categorical
类型的分组键上有用,对于其他类型的键,将忽略observed
参数的设置
dropna: 'bool' = True
, 控制在分组过程中是否删除包含缺失值(NaN)的分组
接下来我将分别针对每一个参数给出例子:
axis参数,它用于指定在哪个轴上进行分组操作。
axis参数可以取两个值:0或1。默认情况下,axis参数的取值为0,表示按行进行分组。取1表示按列进行分组。
需要注意的是,axis=1
参数通常需要和groupby函数的其他参数配合使用,以指定需要进行聚合操作的列。
我找不到axis=1
时的例子,难受…
level参数用于指定分组的层级,处理多层索引时非常有用。
假设我们有一个包含多层索引的数据框(DataFrame),其中有两个层级:A和B。我们想要按照层级A进行分组操作。
import pandas as pd
# 创建一个包含多层索引的数据框
df = pd.DataFrame({
'A': ['foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'foo'],
'B': ['one', 'one', 'two', 'two', 'two', 'one', 'two', 'one'],
'C': [1, 2, 3, 4, 5, 6, 7, 8],
'D': [10, 20, 30, 40, 50, 60, 70, 80]
})
df = df.set_index(['A', 'B']) # 设置A,B列为索引列,set_index()用于将一个或多个列设置为索引(index)
下面是我们生成的数据框:
C D
A B
foo one 1 10
bar one 2 20
foo two 3 30
bar two 4 40
foo two 5 50
bar one 6 60
foo two 7 70
one 8 80
现在,我们将使用groupby函数以及level参数按照层级A对数据进行分组:
grouped = df.groupby(level='A')
这将返回一个GroupBy对象,它表示按照层级A进行分组后的数据。我们打印一下GroupBy对象,并且使用for循环打印GroupBy对象的每一个分组:
print(grouped) # 打印grouped对象,(打印出来的是内存地址)
for group_name,group_data in grouped: # 遍历grouped中的每一个分组,并打印
print(f'Group: {group_name}')
print(group_data)
print()
输出结果如下:
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000002A3E8DC7520>
Group: bar
C D
A B
bar one 2 20
two 4 40
one 6 60
Group: foo
C D
A B
foo one 1 10
two 3 30
two 5 50
two 7 70
one 8 80
我们可以通过应用聚合函数(例如sum、mean等)来进一步处理分组后的数据。例如,我们计算每个分组的总和:
grouped_sum = grouped.sum()
这将返回以下结果:
C D
A
bar 12 120
foo 24 220
我们也可以通过指定多个层级来进行多级分组。例如,我们按照层级A和层级B进行分组:
grouped = df.groupby(level=['A', 'B'])
这将返回一个按照层级A和层级B进行分组后的GroupBy对象。
我们计算每个分组的总和:
grouped_sum = grouped.sum()
这将返回以下结果:
C D
A B
bar one 8 80
two 4 40
foo one 9 90
two 15 150
as_index参数用于控制分组后的结果是否以分组列作为索引。默认为True,即分组列将作为结果 DataFrame 的索引。
首先使用下面的代码生成样例数据:
import random
import pandas as pd
import numpy as np
df = pd.DataFrame(
{
'name': ['bob' + str(i) for i in range(1, 11)],
'company': [random.choice(['A', 'B', 'C']) for i in range(10)],
'salary': np.random.randint(5,50,10),
'age':np.random.randint(15,50,10)
}
)
print(df)
生成的样本数据如下:
name company salary age
0 bob1 B 8 33
1 bob2 C 16 16
2 bob3 B 20 38
3 bob4 C 34 48
4 bob5 A 11 25
5 bob6 A 46 35
6 bob7 C 18 23
7 bob8 A 18 49
8 bob9 B 7 18
9 bob10 A 23 19
接下来根据company分组,并设置as_index=True
,并且计算每一个分组的salary的和:
grouped = df.groupby('company',as_index=True)
salary_sum = grouped['salary'].sum()
print(salary_sum)
A_salary = salary_sum.loc['A'] # 此时分组列将作为结果 DataFrame 的索引,可以通过索引'A'输出A的总工资
print(A_salary)
得到的结果如下:
company
A 154
B 156
Name: salary, dtype: int32
154
可以看到,此时分组列将作为结果 DataFrame 的索引,可以通过索引’A’输出A的总工资,但是聚合列的列名丢失了。
接下来还是根据company分组,但是设置as_index=False
,并且计算每一个分组的salary的和:
grouped = df.groupby('company',as_index=True)
salary_sum = grouped['salary'].sum()
print(salary_sum)
A_salary = salary_sum.loc['A'] # 此时分组列将不会作为结果 DataFrame 的索引,也就不可以通过索引'A'输出A的总工资了
print(A_salary)
得到的结果如下:
company salary
0 A 235
1 B 16
2 C 5
KeyError: 'A' # 报错
可以看到,此时分组列将不会作为结果 DataFrame 的索引,并且不可以通过索引’A’输出A的总工资,并且聚合列的列名没有丢失。
此时可以使用set_index()函数将’company’列重新变成索引,这样就可以再次使用company索引了:
salary_sum = salary_sum.set_index('company')
print(salary_sum)
print(salary_sum.loc['A'])
运行结果如下:
salary
company
A 137
B 87
C 32
salary 137
Name: A, dtype: int32
sort
参数用于指定分组后的结果是否按照分组键进行排序,默认为 True
,表示按照分组键排序(升序排序)。当 sort
参数设置为 False
时,分组结果将按照原始数据的顺序保留。
当sort=False
时的一个例子:
import random
import pandas as pd
import numpy as np
df = pd.DataFrame(
{
'name': ['bob' + str(i) for i in range(1, 11)],
'company': [random.choice(['A', 'B', 'C']) for i in range(10)],
'salary': np.random.randint(5,50,10),
'age':np.random.randint(15,50,10)
}
)
grouped = df.groupby('company',sort=False)
for name,group in grouped:
print(name)
print(group)
结果如下:
B
name company salary age
0 bob1 B 45 48
1 bob2 B 10 16
2 bob3 B 42 15
5 bob6 B 25 18
C
name company salary age
3 bob4 C 19 40
7 bob8 C 23 36
8 bob9 C 23 48
9 bob10 C 49 49
A
name company salary age
4 bob5 A 49 15
6 bob7 A 35 41
可以看到分组结果并没有按照分组键company
进行排序(键的排列顺序是:B,C,A)。
还是使用上面的数据样本,当sort=True
时的一个例子:
grouped = df.groupby('company',sort=True)
for name,group in grouped:
print(name)
print(group)
运行结果如下:
A
name company salary age
1 bob2 A 14 36
2 bob3 A 29 37
7 bob8 A 12 25
8 bob9 A 37 18
9 bob10 A 28 35
B
name company salary age
0 bob1 B 40 41
3 bob4 B 24 16
4 bob5 B 49 38
5 bob6 B 12 37
C
name company salary age
6 bob7 C 41 22
进程已结束,退出代码0
可以看到分组结果已经按照分组键company
进行排序(键的排列顺序是:A,B,C)。
group_keys
参数用于控制分组键是否显示在结果中,group_keys
参数的默认值为True
,这意味着在分组结果中会显示分组键。也就是说,对于每个分组,将在结果中添加一个带有分组键的索引层级。这对于快速查看分组结果以及后续操作是很有用的。
我找了很多资料和例子,发现group_keys
无论是只在分组的时候使用,还是配合聚合函数使用,全部不起作用。
比如group_keys
配合聚合函数使用的结果如下:
import pandas as pd
# 创建一个包含多层索引的数据框
df = pd.DataFrame({
'A': ['foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'foo'],
'B': ['one', 'one', 'two', 'two', 'two', 'one', 'two', 'one'],
'C': [1, 2, 3, 4, 5, 6, 7, 8],
'D': [10, 20, 30, 40, 50, 60, 70, 80]
})
df = df.set_index(['A', 'B']) # 设置A,B列为索引列,set_index()用于将一个或多个列设置为索引(index)
# 对Category列进行分组,并计算每个分组的平均值
grouped = df.groupby(['A', 'B'], group_keys=True).mean()
print(grouped)
print('----------------')
grouped = df.groupby(['A', 'B'], group_keys=False).mean()
print(grouped)
group_keys=True
和 group_keys=False
的运行结果都是一样的:
C D
A B
bar one 4.0 40.0
two 4.0 40.0
foo one 4.5 45.0
two 5.0 50.0
----------------
C D
A B
bar one 4.0 40.0
two 4.0 40.0
foo one 4.5 45.0
two 5.0 50.0
进程已结束,退出代码0
又或者group_keys
仅仅在分组的时候使用,而不配合聚合函数使用时的结果:
import random
import pandas as pd
import numpy as np
df = pd.DataFrame(
{
'name': ['bob' + str(i) for i in range(1, 11)],
'company': [random.choice(['A', 'B', 'C']) for i in range(10)],
'salary': np.random.randint(5,50,10),
'age':np.random.randint(15,50,10)
}
)
grouped = df.groupby('company',group_keys=True)
for name,group in grouped:
print(name)
print(group)
print()
print('-------------------------------')
grouped = df.groupby('company',group_keys=False)
for name,group in grouped:
print(name)
print(group)
print()
group_keys=True
和 group_keys=False
的运行结果仍然都是一样的:
A
name company salary age
1 bob2 A 43 45
7 bob8 A 46 25
8 bob9 A 37 20
9 bob10 A 16 37
B
name company salary age
0 bob1 B 12 22
2 bob3 B 35 16
3 bob4 B 45 26
4 bob5 B 19 41
5 bob6 B 39 17
C
name company salary age
6 bob7 C 33 21
-------------------------------
A
name company salary age
1 bob2 A 43 45
7 bob8 A 46 25
8 bob9 A 37 20
9 bob10 A 16 37
B
name company salary age
0 bob1 B 12 22
2 bob3 B 35 16
3 bob4 B 45 26
4 bob5 B 19 41
5 bob6 B 39 17
C
name company salary age
6 bob7 C 33 21
最后我在下面的文章找到了一个例子:
python - pandas.groupby 的 group_keys 参数实际上是做什么的?
根据上面的文章描述,group_keys函数只有在和apply()函数配合时才有可能起作用,为什么是有可能呢?先来看看下面这个例子:
apply() 函数用于在 DataFrame 或 Series 的每个元素上应用一个函数,并返回一个新的 DataFrame 或 Series 对象
groupby()方法对数据进行分组之后,使用apply()方法可以用于在每个组上应用自定义函数
我首先自定义了一个sum_salary()函数,实现了聚合函数sum()的功能,然后使用apply()函数将这个自定义函数应用到groupby之后的分组中:
import random
import pandas as pd
import numpy as np
df = pd.DataFrame(
{
'name': ['bob' + str(i) for i in range(1, 11)],
'company': [random.choice(['A', 'B', 'C']) for i in range(10)],
'salary': np.random.randint(5, 50, 10),
'age': np.random.randint(15, 50, 10)
}
)
def sum_salary(group): # 自定义函数,传入一个分组对象,计算每一个组的salary的和,(当然也可以使用lambda函数代替)
return group['salary'].sum()
grouped = df.groupby('company', group_keys=True)
result1 = grouped.apply(sum_salary)
print(result1)
print('===================================')
grouped = df.groupby('company', group_keys=False)
result2 = grouped.apply(sum_salary)
print(result2)
运行结果如下:
company
A 52
B 89
C 32
dtype: int64
===================================
company
A 52
B 89
C 32
dtype: int64
可以发现group_keys=True
和 group_keys=False
的运行结果仍然都是一样的。
接下来,我重新定义了自定义函数,使用自定义函数输出每一个分组的前两行:
import random
import pandas as pd
import numpy as np
df = pd.DataFrame(
{
'name': ['bob' + str(i) for i in range(1, 11)],
'company': [random.choice(['A', 'B', 'C']) for i in range(10)],
'salary': np.random.randint(5, 50, 10),
'age': np.random.randint(15, 50, 10)
}
)
grouped = df.groupby('company', group_keys=True)
result1 = grouped.apply(lambda x:x.iloc[:2]) # 匿名函数的作用是输出每一个分组的前两行
print(result1)
print('===================================')
grouped = df.groupby('company', group_keys=False) # 匿名函数的作用是输出每一个分组的前两行
result2 = grouped.apply(lambda x:x.iloc[:2])
print(result2)
得到的结果如下:
name company
company
A 1 bob2 A
2 bob3 A
B 0 bob1 B
5 bob6 B
C 3 bob4 C
8 bob9 C
===================================
name company
1 bob2 A
2 bob3 A
0 bob1 B
5 bob6 B
3 bob4 C
8 bob9 C
通过上面的结果可以发现group_keys=True
和 group_keys=False
的运行结果终于不一样了,
group_keys=True
时,分组键保留在结果中,并且作为结果的索引。group_keys=False
时,分组键保留没有在结果中,并且不能作为结果的索引。
比如下面分别以分组键’A’来查询相应的分组的例子,group_keys=True
时可以查询和,group_keys=False
时不可以查询:
import random
import pandas as pd
import numpy as np
df = pd.DataFrame(
{
'name': ['bob' + str(i) for i in range(1, 11)],
'company': [random.choice(['A', 'B', 'C']) for i in range(10)],
'salary': np.random.randint(5, 50, 10),
'age': np.random.randint(15, 50, 10)
}
)
grouped = df.groupby('company', group_keys=True)
result1 = grouped.apply(lambda x:x.iloc[:2]) # 匿名函数的作用是输出每一个分组的前两行
print(result1.loc['A']) # 通过索引A取A分组
print('===================================')
grouped = df.groupby('company', group_keys=False) # 匿名函数的作用是输出每一个分组的前两行
result2 = grouped.apply(lambda x:x.iloc[:2])
print(result2.loc['A']) # 通过索引A取A分组
运行结果:
name company salary age
6 bob7 A 6 27
===================================
KeyError: 'A' # 报错信息
通过上面的分析可以做出如下总结:
group_keys
参数不可以和聚合函数配合使用,当调用聚合函数时,其本身的索引会失效,此时传递group_keys=False无效(与group_keys=True一样)。
通过前面对as_index
参数和group_keys
参数的说明可以发现它们两者具有相似之处,
当as_index和group_keys的值都是True时,它们两个的作用相同,产出的结果一摸一样.当as_index和group_keys的值都是False时,group_keys仅仅消除分组键而没有生成新的索引,as_index在消除分组键的同时又给每一个分组生成了一个新的索引。如下所示:
当as_index和group_keys的值都是False时,as_index的为结果生成了新的索引:
group_keys=False:
name company salary age
1 bob2 A 12 19
2 bob3 A 47 40
8 bob9 B 19 48
0 bob1 C 14 26
3 bob4 C 28 17
=====================================
as_index=False:
name company salary age
0 1 bob2 A 12 19
2 bob3 A 47 40
1 8 bob9 B 19 48
2 0 bob1 C 14 26
3 bob4 C 28 17
当as_index和group_keys的值都是True时,两者没有区别:
group_keys=True:
name company salary age
company
A 0 bob1 A 32 26
1 bob2 A 9 47
B 3 bob4 B 42 47
4 bob5 B 45 43
C 2 bob3 C 8 40
5 bob6 C 28 34
=====================================
as_index=True:
name company salary age
company
A 0 bob1 A 32 26
1 bob2 A 9 47
B 3 bob4 B 42 47
4 bob5 B 45 43
C 2 bob3 C 8 40
5 bob6 C 28 34
squeeze 参数是 groupby 函数的一个可选参数,用于控制返回结果的形状。
当 squeeze
参数设置为 True
时,表示只保留每个组中的一列,而将其他列作为索引。这样可以将结果矩阵从二维压缩为一维,使结果更加简洁易读。
当 squeeze
参数设置为 False
或未指定时,表示保留所有聚合后的列,返回一个二维矩阵。
官方文档说squeeze
参数在未来的pandas版本中会被弃用,那就不在这个参数上浪费时间了。
当使用 Categorical
分组器(作为单个分组器,或作为多个分组器的一部分)时, observed=False
是返回所有可能的分组器值的笛卡尔积, observed=True
是仅返回那些被观察到的分组器。
需要注意的是,observed
参数只在Categorical
类型的分组键上有用,对于其他类型的键,将忽略observed
参数的设置。
Categorical
是Pandas中的分类类型数据,当数据只有少数几种可能取值但有大量重复字符串字段,利用categorical类型可有效节省内存,提高数据分析的效率。常见的分类如性别、职业等取值有限的类别数据。
下面给出ovservled
的具体例子:
import pandas as pd
import numpy as np
df = pd.DataFrame(
{
'company': pd.Categorical(['A','A','B','B','A','B','B','A','C','C']), # 只有将company转换成Categorical类型observed才会起作用。
'class':pd.Categorical(['foo',None,'bar',None,'bar','bar',None,'foo','foo',None]),
'salary': np.random.randint(5,50,10),
'age':np.random.randint(15,50,10)
}
)
grouped = df.groupby(['company','class'],observed=False).count()
print(grouped)
grouped = df.groupby(['company','class'],observed=True).count()
print(grouped)
运行结果如下:
observed=False:
salary age
company class
A bar 1 1
foo 2 2
B bar 2 2
foo 0 0
C bar 0 0
foo 1 1
observed=True:
salary age
company class
A foo 2 2
bar 1 1
B bar 2 2
C foo 1 1
通过上面的结果可以发现对于不存在的分组B-foo
和C-bar
,当observed=False时,在结果中并没有被忽略。当observed=True时,在结果中被忽略掉了。
dropna
参数用于控制在分组过程中是否删除含有缺失值(NAN)的分组.当dropna
为True时,任何包含缺失值的分组都会被从结果中排除掉。当dropna
为False时,包含缺失值的分组将被保留在结果中,默认为True。
现在给出下面的例子:
import pandas as pd
import numpy as np
df = pd.DataFrame(
{
'company': ['A','A','B','B','A','B','B','A','C','C'],
'class':['foo',None,'bar',None,'bar','bar',None,'foo','foo',None],
'salary': np.random.randint(5,50,10),
'age':np.random.randint(15,50,10)
}
)
grouped = df.groupby(['company','class'],dropna=False).count()
print(grouped)
grouped = df.groupby(['company','class'],dropna=True).count()
print(grouped)
运行结果如下:
dropna=False:
salary age
company class
A bar 1 1
foo 2 2
NaN 1 1
B bar 2 2
NaN 2 2
C foo 1 1
NaN 1 1
dropna=True:
salary age
company class
A bar 1 1
foo 2 2
B bar 2 2
C foo 1 1
通过上面的运行结果可以发现,当dropna=False时,对于含有控制的分组键class,pandas也将其作为一个分组输出。当dropna=True时,对于含有控制的分组键class,pandas将其忽略掉了。
通过观察可以发现dropna和observed也有相似和不同之处。
相同的是当dropna
和observed
都是True时,它们都将含有空值的键的分组忽略掉了。
不同的是observed
仅仅对Categorical
类型的分组键上有用,对于其他类型的键,将忽略observed
参数的设置。dropna
两者皆可。并且当dropna
和observed
都是Falsse时,以上面生成的数据为例子,对于observed
参数,pandas会计算[A,B,C]和[bar,foo]的笛卡尔积;对于dropna
参数,pandas会计算[A,B,C]和[bar,foo,NAN]的笛卡尔积。
如下:
dropna=False:
salary age
company class
A bar 1 1
foo 2 2
NaN 1 1
B bar 2 2
NaN 2 2
C foo 1 1
NaN 1 1
observed=False:
salary age
company class
A bar 1 1
foo 2 2
B bar 2 2
foo 0 0
C bar 0 0
foo 1 1