pandas groupby 详解
在日常的数据分析中,我们常常需要根据**某个字段或多个字段把数据划分为不同的群体进行分析,**比如我们分析全国工资水平,可能就需要分析不同地区工资的情况,在pandas中实现划分功能的函数便是groupby。这篇文章介绍了groupby的基本原理、常用属性及搭配属性(agg、transform、apply),每个知识点均配有示例、部分知识点通过图示加以说明,相信看完之后大家会有所收获。
一、Groupby的基本原理
Groupby的功能用通俗的话来讲,可以分为三步:分组、数据处理和汇总。具体解释为使用指定的规则对DataFrame数据分组,这个规则可以是一个字段(如电商领域将全国的总销售额根据省份字段进行划分,分析各省销售额的变化情况),也可以是函数、字典等。分组后对每个分组的数据做处理(比如求和、求平均值、自定义函数等),最后将处理后的数据汇总合并。
为了加深理解,来看一个使用groupby实现求平均的全过程:
使用的数据如下:
company=["A","B","C"]
data=pd.DataFrame({
"company":[company[x] for x in np.random.randint(0,len(company),5)],
"salary":np.random.randint(5,50,5),
"age":np.random.randint(15,50,5)})
结果:
下面代码实现了求每个公司的平均工资:
data.groupby('company')['salary'].mean()
结果:
company
A 41.5
B 13.5
C 38.0
Name: salary, dtype: float64
整个过程的图解如下如,先将数据以公司分组,分为A、B、C三个公司,接着对每个分组的salary列求平均,最后将结果拼接起来。
接下来,再看一些例子,帮助理解。
用字段分组
用字段分组的规则为,以该字段为标准,将字段中相同的值作为一个分组来划分DataFrame数据集,该字段共有多少个不同的值,便会有多少分组。
company=["A","B","C"]
data=pd.DataFrame({
"company":[company[x] for x in np.random.randint(0,len(company),10)],
"salary":np.random.randint(5,50,10),
"age":np.random.randint(15,50,10),
'height':np.random.randint(5,200,10),
'weight':np.random.randint(5,200,10)
}
)
数据:
![
假如我们需要根据公司名称分组该如何实现?
示例 1
#使用company列对数据进行分组
group = data.groupby("company")
print(group)
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000017FA1F02DC0>
group = data.groupby(“company”)实现用公司名对DataFrame数据分组,返回的group是一个DataFrameGroupBy 对象,不便于观察,list(group)可以将所有分组以列表展示。
list(group)
[('A',
company salary age height weight
0 A 37 29 138 155
8 A 27 44 41 185),
('B',
company salary age height weight
2 B 47 27 111 96
4 B 13 19 144 23
6 B 28 42 189 44
7 B 44 40 144 90
9 B 36 35 158 104),
('C',
company salary age height weight
1 C 35 48 154 110
3 C 30 42 189 24
5 C 20 39 27 47)]
可以看出groupby,将数据按不同公司名(company字段共有三个不同的值A,B,C),分成了三组。
使用函数分组
用函数进行分组,那么分组规则需要在函数中写明。需要注意的是,分组函数(传入groupby的函数)用来决定分组的依据是DataFrame的index值或者columns值(axis=1则columns作为依据,axis=0则index作为依据)
看起来是不是有点迷糊,看完下面的示例,便会明白。
axis=1时的分组介绍
数据同上示例,假如我们需要以列名中是否含有a字符作为分组标准,将数据集分成good和bad组,该如何实现?
示例 2
#分组函数
def gb(x):
if 'a' in x :
return 'good'
else:
return 'bad'
group_function = data.groupby(gb,axis=1)
list(group_function)
结果:
[('bad',
height weight
0 138 155
1 154 110
2 111 96
3 189 24
4 144 23
5 27 47
6 189 44
7 144 90
8 41 185
9 158 104),
('good',
company salary age
0 A 37 29
1 C 35 48
2 B 47 27
3 C 30 42
4 B 13 19
5 C 20 39
6 B 28 42
7 B 44 40
8 A 27 44
9 B 36 35)]
函数gb(x)根据传入参数是否含有‘a’,返回不同值,参数中有‘a’字符则返回good,反之则返回bad。
data.groupby(gb,axis=1)将data中的columns传入gb(x)中进行分组,从结果得知,company ,salary 和age中含有a,为good组,而height和weight,为bad组。
axis=0时的分组情况
假如我们需要以索引数值是否大于5作为分组标准,将数据集分成max和min组,该如何实现?
示例 3
def gb(x):
if x > 5 :
return 'max'
else:
return 'min'
group_function = data.groupby(gb,axis=0)
list(group_function)
结果:
[('max',
company salary age height weight
6 B 28 42 189 44
7 B 44 40 144 90
8 A 27 44 41 185
9 B 36 35 158 104),
('min',
company salary age height weight
0 A 37 29 138 155
1 C 35 48 154 110
2 B 47 27 111 96
3 C 30 42 189 24
4 B 13 19 144 23
函数gb(x)根据传入参数是否大于5,返回不同值,大于则返回max,反之则返回min。
data.groupby(gb,axis=0)将data中的index传入gb(x)中进行分组,从结果得知,0-4,分为了min组,而6-9分为了bad组。
使用字典分组
使用字典进行分时,利用键值对的映射关系作为分组规则。例如我想实现将列1-3分为组1,列4-6分为组2,将如下字典{’列1‘:’组1’,’列2‘:’组1’,’列3‘:’组1’,’列4‘:’组2’,’列5‘:’组2’,’列6‘:’组2’}传入groupby,便可实现。
示例:
示例 4
gb_dict = {0:'min',1:'min',2:'min',3:'min',4:'midle',5:'midle',6:'midle',7:'max',8:'max',9:'max'}
group_dict = data.groupby(gb_dict,axis=0)
list(group_dict)
结果:
[('max',
company salary age height weight
7 B 44 40 144 90
8 A 27 44 41 185
9 B 36 35 158 104),
('midle',
company salary age height weight
4 B 13 19 144 23
5 C 20 39 27 47
6 B 28 42 189 44),
('min',
company salary age height weight
0 A 37 29 138 155
1 C 35 48 154 110
2 B 47 27 111 96
3 C 30 42 189 24)]
通过gb_dict,将index0-3分为min组,4-6分为midle组,7-9分为max组。
使用list分组
利用列表(list)分组,与利用DataFrame中的字段进行分组的规则基本一样,分组时将list中相同的值作为一个组,list中有多少个不同的值,便会有多少个组。注意list的长度必须与被分组的DataFrame长度一致。
示例 5
gb_list = ['1','2','3','2','3','1','2','3','2','3']
group_list = data.groupby(gb_list,axis=0)
list(group_list)
结果:
[('1',
company salary age height weight
0 A 37 29 138 155
5 C 20 39 27 47),
('2',
company salary age height weight
1 C 35 48 154 110
3 C 30 42 189 24
6 B 28 42 189 44
8 A 27 44 41 185),
('3',
company salary age height weight
2 B 47 27 111 96
4 B 13 19 144 23
7 B 44 40 144 90
9 B 36 35 158 104)]
gb_list中有1、2、3三个不同的值,DataFrame数据被分成了对应的三组。
二、Groupby常用方法和属性
方法 | 功能 |
---|---|
mean() | 求均值 |
sort_values() | 排序 |
value_counts() | 计算频率 |
rename_index() | 修改索引名 |
count() | 计数 |
groups | 获取所有分组 |
get_group() | 获取指定分组 |
本节所使用的数据集:
company=["西瓜科技","苹果集团","老虎信息","金刚传媒","狗子通信","快乐外卖"]
name = ["张三","李四","王五","张飞","陈六","徐三","欧燕","黄十","六六"]
district = ['长沙','武汉','北京']
data=pd.DataFrame({
"company":[company[x] for x in np.random.randint(0,len(company),20)],
"name":[name[x] for x in np.random.randint(0,len(name),20)] ,
"district":[district[x] for x in np.random.randint(0,len(district),20)] ,
"salary":np.random.randint(5000,10000,20),
"age":np.random.randint(15,50,20),
'height':np.random.randint(5,200,20),
'weight':np.random.randint(5,200,20)
})
data.head()
结果 :
1、计算每个地区的平均工资
data.groupby('district')['salary'].mean().apply(lambda x:'%0.2f'%x)
结果:
district
北京 8580.50
武汉 7875.38
长沙 6338.67
Name: salary, dtype: object
2、计算每个地区的平均工资,不使用group键作为index
data.groupby('district',as_index=Fasle)['salary'].mean()
3、计算每个地区的平均工资,并将index改为“城市”
data.groupby('district')['salary'].mean().rename_axis('城市')
结果:
城市
北京 8580.500000
武汉 7875.375000
长沙 6338.666667
Name: salary, dtype: float64
4、计算每个地区的平均工资并排序
data.groupby('district')['salary'].mean().sort_values()
结果:
district
长沙 6338.666667
武汉 7875.375000
北京 8580.500000
Name: salary, dtype: float64
5、计算每个地区公司的数量
data.groupby('district')['company'].count()
结果:
district
北京 6
武汉 8
长沙 6
Name: company, dtype: int64
6、计算每个地区公司出现的频次
data.groupby('district')['company'].value_counts()
结果:
district company
北京 金刚传媒 2
快乐外卖 1
老虎信息 1
苹果集团 1
西瓜科技 1
武汉 狗子通信 2
老虎信息 2
快乐外卖 1
苹果集团 1
西瓜科技 1
金刚传媒 1
长沙 金刚传媒 3
快乐外卖 2
老虎信息 1
Name: count, dtype: int64
7、根据district和salary分组,并查看所有分组的情况
data.groupby(['district','salary']).groups
结果:
{('北京', 6329): [4], ('北京', 8032): [17], ('北京', 8799): [8], ('北京', 8930): [3], ('北京', 9512): [0], ('北京', 9881): [13], ('武汉', 5382): [16], ('武汉', 6719): [18], ('武汉', 6850): [6], ('武汉', 8052): [9], ('武汉', 8395): [11], ('武汉', 8904): [2], ('武汉', 9324): [19], ('武汉', 9377): [5], ('长沙', 5259): [12], ('长沙', 5961): [14], ('长沙', 5996): [7], ('长沙', 6011): [10], ('长沙', 6333): [1], ('长沙', 8472): [15]}
8、根据district和salary分组,并查看所有分组的情况,并查看北京薪资为 6329的工作
data.groupby(['district','salary']).get_group(('北京', 6329))
结果:
三、Groupby的“最佳拍档”
现实情况中有很多问题单独使用Groupby以及它自带的方法无法解决或者解决起来十分繁琐,为此,pandas官方给Groupby配备了许多“最佳拍档“,如agg、tramsform、apply。
3.1 agg
agg是一个聚合函数,能够完成常见的数学统计,如min、max、mean、sum、median、std、var、count等。比如我们要计算不同公司的薪资平均值。
data.groupby('company')['salary'].agg('mean')
结果:
company
快乐外卖 7143.750000
狗子通信 7379.500000
老虎信息 8055.250000
苹果集团 7890.000000
西瓜科技 9138.000000
金刚传媒 7151.166667
Name: salary, dtype: float64
求平均值groupby自带的mean()方法也可以实现,为什么还需要多此一举搞个agg函数?我们接着看。
假如我们不仅要计算不同公司的薪资平均值,同时还要计算最小值、最大值,如果我们用groupby自带的函数实现,需要先分别将需求值求出后再拼接起来,而agg的实现过程就比较简捷。
data.groupby('company')['salary'].agg(['mean','min','max'])
结果:
利用agg还可以针对不同字段,做不同的计算。假如我们需要计算薪资的平均值、最小值和最大值的同时,还需要计算年龄的最小值和最大值。
data.groupby('company').agg({'salary':['mean','min','max'],'age':['min','max']})
结果:
3.2 transform
transform的功能和agg类似,区别在于返回值的形状不同。假如单个series使用agg,那么返回是每个分组聚合后的值,值的数量等于分组数,而transfrom返回值的形状和原series一样(DataFrame数据类型类似)。具体理解,请看示例:
假如我们现在需要计算每个公司的平均值,并需要将计算值新增到原始数据集中。使用agg计算平均值,因数据长度问题,返回的结果无法直接插入到原始数据集。所以整个实现过程相对繁琐,过程如下:
salary_avg_dict = data.groupby('company')['salary'].mean().to_dict()
data['salary_avg'] = data['company'].map(salary_avg_dict)
data
结果:
使用transform来实现,一行代码搞定
data['salary_avg'] = data.groupby('company')['salary'].transform('mean')
结果:
3.3 apply
apply应该是大家的老朋友了,它相比agg和transform而言更加灵活,能够传入任意自定义的函数,实现复杂的数据操作。介绍了apply的使用,那在groupby后使用apply和之前所介绍的有什么区别呢?
区别是有的,但是整个实现原理是基本一致的。两者的区别在于,对于groupby后的apply,以分组后的子DataFrame作为参数传入指定函数的,基本操作单位是DataFrame,而之前介绍的apply的基本操作单位是Series。还是以一个案例来介绍groupby后的apply用法。
假设我现在需要获取各个公司年龄最大的员工的数据,该怎么实现呢?可以用以下代码实现:
import pandas as pd
import numpy as np
company=["A","B","C"]
data=pd.DataFrame({
"company":[company[x] for x in np.random.randint(0,len(company),5)],
"salary":np.random.randint(5,50,5),
"age":np.random.randint(15,50,5)})
def get_oldest_staff(x):
df = x.sort_values(by = 'age',ascending=True)
return df.iloc[-1,:]
数据:
oldest_staff = data.groupby('company',as_index=False).apply(get_oldest_staff)
结果:
图解过程:
先将DataFrame数据集按公司名分组,得到三个DataFrame子集后,将各子集传入get_oldest_staff(),筛选出每个子集age最大的数据作拼接得到最后的结果。
关于apply的使用,这里有个小建议,虽然说apply拥有更大的灵活性,但apply的运行效率会比agg和transform更慢。所以,groupby之后能用agg和transform解决的问题还是优先使用这两个方法,实在解决不了了才考虑使用apply进行操作。
四、Groupby的参数
看完groupby的基本原理后,对groupby有了一定的了解,让我们来看看Groupby的参数,作进一步的学习。
groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, [squeeze](https://so.csdn.net/so/search?q=squeeze&spm=1001.2101.3001.7020)=False)
参数说明:
by是指分组依据(列表、字典、函数,元组,Series)
axis:是作用维度(0为行,1为列)
level:根据索引级别分组
sort:对groupby分组后新的dataframe中索引进行排序,sort默认为True代表升序
as_index:在groupby中使用的键是否成为新的dataframe中的索引,默认as_index=True
group_keys:在调用apply,将group键添加到索引中以识别片段
squeeze :减少返回类型的维数,否则返回一个一致的类型
参考:
https://zhuanlan.zhihu.com/p/101284491?utm_source=wechat_session