Pandas教程 | 超好用的Groupby用法详解
在日常的数据分析中,经常需要将数据
根据某个(多个)字段划分为不同的群体(group)
进行分析,如电商领域将全国的总销售额根据省份进行划分,分析各省销售额的变化情况,社交领域将用户根据画像(性别、年龄)进行细分,研究用户的使用情况和偏好等。在Pandas中,上述的数据处理操作主要运用
groupby
完成,这篇文章就介绍一下
groupby
的基本原理及对应的
agg
、
transform
和
apply
操作。
为了后续图解的方便,采用模拟生成的10个样本数据,代码和数据如下:
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)
)
一、Groupby的基本原理
在pandas中,实现分组操作的代码很简单,仅需一行代码,在这里,将上面的数据集按照
company
字段进行划分:
In [5]: group = data.groupby("company")
将上述代码输入
ipython
后,会得到一个
DataFrameGroupBy
对象
In [6]: group
Out[6]: <pandas.core.groupby.generic.DataFrameGroupBy object at 0x000002B7E2650240>
那这个生成的
DataFrameGroupBy
是啥呢?对
data
进行了
groupby
后发生了什么?
ipython
所返回的结果是其内存地址,并不利于直观地理解,为了看看
group
内部究竟是什么,这里把
group
转换成
list
的形式来看一看:
In [8]: list(group)
Out[8]:
[('A', company salary age
3 A 20 22
6 A 23 33),
('B', company salary age
4 B 10 17
5 B 21 40
8 B 8 30),
('C', company salary age
0 C 43 35
1 C 17 25
2 C 8 30
7 C 49 19)]
转换成列表的形式后,可以看到,列表由三个元组组成,每个元组中,第一个元素是组别(这里是按照
company
进行分组,所以最后分为了
A
,
B
,
C
),第二个元素的是对应组别下的
DataFrame
,整个过程可以图解如下:
总结来说,
groupby
的过程就是将原有的
DataFrame
按照
groupby
的字段(这里是
company
),划分为若干个
分组DataFrame
,被分为多少个组就有多少个
分组DataFrame
。
所以说,在
groupby
之后的一系列操作(如
agg
、
apply
等),均是基于
子DataFrame
的操作。
理解了这点,也就基本摸清了Pandas中
groupby
操作的主要原理。下面来讲讲
groupby
之后的常见操作。
二、agg 聚合操作
聚合操作是
groupby
后非常常见的操作,会写
SQL
的朋友对此应该是非常熟悉了。聚合操作可以用来求和、均值、最大值、最小值等,下面的表格列出了Pandas中常见的聚合操作。
针对样例数据集,如果我想求不同公司员工的平均年龄和平均薪水,可以按照下方的代码进行:
In [12]: data.groupby("company").agg('mean')
Out[12]:
salary age
company
A 21.50 27.50
B 13.00 29.00
C 29.25 27.25
如果想对针对不同的列求不同的值,比如要计算不同公司员工的平均年龄以及薪水的中位数,可以利用字典进行聚合操作的指定:
In [17]: data.groupby('company').agg({'salary':'median','age':'mean'})
Out[17]:
salary age
company
A 21.5 27.50
B 10.0 29.00
C 30.0 27.25
agg
聚合过程可以图解如下(第二个例子为例):
三、transform
transform
是一种什么数据操作?和
agg
有什么区别呢?为了更好地理解
transform
和
agg
的不同,下面从实际的应用场景出发进行对比。
在上面的
agg
中,我们学会了如何求不同公司员工的平均薪水,如果现在需要在原数据集中新增一列
avg_salary
,代表
员工所在的公司的平均薪水(相同公司的员工具有一样的平均薪水)
,该怎么实现呢?如果按照正常的步骤来计算,需要先求得不同公司的平均薪水,然后按照员工和公司的对应关系填充到对应的位置,不用
transform
的话,实现代码如下:
In [21]: avg_salary_dict = data.groupby('company')['salary'].mean().to_dict()
In [22]: data['avg_salary'] = data['company'].map(avg_salary_dict)
In [23]: data
Out[23]:
company salary age avg_salary
0 C 43 35 29.25
1 C 17 25 29.25
2 C 8 30 29.25
3 A 20 22 21.50
4 B 10 17 13.00
5 B 21 40 13.00
6 A 23 33 21.50
7 C 49 19 29.25
8 B 8 30 13.00
如果使用
transform
的话,仅需要一行代码:
In [24]: data['avg_salary'] = data.groupby('company')['salary'].transform('mean')
In [25]: data
Out[25]:
company salary age avg_salary
0 C 43 35 29.25
1 C 17 25 29.25
2 C 8 30 29.25
3 A 20 22 21.50
4 B 10 17 13.00
5 B 21 40 13.00
6 A 23 33 21.50
7 C 49 19 29.25
8 B 8 30 13.00
还是以图解的方式来看看进行
groupby
后
transform
的实现过程(为了更直观展示,图中加入了
company
列,实际按照上面的代码只有
salary
列):
图中的大方框是
transform
和
agg
所不一样的地方,对
agg
而言,会计算得到
A
,
B
,
C
公司对应的均值并直接返回,但对
transform
而言,则会
对每一条数据求得相应的结果,同一组内的样本会有相同的值
,组内求完均值后会
按照原索引的顺序
返回结果,如果有不理解的可以拿这张图和
agg
那张对比一下。
四、apply
apply
应该是大家的老朋友了,它相比
agg
和
transform
而言更加灵活,能够传入任意自定义的函数,实现复杂的数据操作。在
Pandas数据处理三板斧——map、apply、applymap详解
中,介绍了
apply
的使用,那在
groupby
后使用
apply
和之前所介绍的有什么区别呢?
区别是有的,但是整个实现原理是基本一致的。两者的区别在于,对于
groupby
后的
apply
,以分组后的
子DataFrame
作为参数传入指定函数的,基本操作单位是
DataFrame
,而之前介绍的
apply
的基本操作单位是
Series
。还是以一个案例来介绍
groupby
后的
apply
用法。
假设我现在需要获取各个公司年龄最大的员工的数据,该怎么实现呢?可以用以下代码实现:
In [38]: def get_oldest_staff(x):
...: df = x.sort_values(by = 'age',ascending=True)
...: return df.iloc[-1,:]
In [39]: oldest_staff = data.groupby('company',as_index=False).apply(get_oldest_staff)