groupby
的基本原理及对应的 agg
、 transform
和 apply
等操作,总共包含25个代码示例。
首先,模拟生成10个样本数据:
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),10)],
"salary":np.random.randint(5,50,10),
"age":np.random.randint(15,50,10)
}
)
在pandas中,实现分组操作的代码很简单,仅需一行代码,在这里,将上面的数据集按照company
字段进行划分:
这个生成的DataFrameGroupBy
是啥呢?对data进行了groupby后发生了什么?ipython所返回的结果是其内存地址,并不利于直观地理解,为了看看group内部究竟是什么,这里把group转换成list的形式来看一看:
转换成列表的形式后,可以看到,列表由三个元组组成,每个元组中,第一个元素是组别(这里是按照company进行分组,所以最后分为了A,B,C),第二个元素的是对应组别下的DataFrame,整个过程可以图解如下:
总结来说,groupby的过程就是将原有的DataFrame按照groupby的字段(这里是company),划分为若干个分组DataFrame,被分为多少个组就有多少个分组DataFrame。所以说,在groupby之后的一系列操作(如agg、apply等),均是基于子DataFrame的操作。理解了这点,也就基本摸清了Pandas中groupby操作的主要原理。
聚合操作是groupby操作之后非常常见的操作。聚合操作可以用来求和、均值、最大值、最小值等,下面的表格列出了Pandas中常见的聚合操作。
针对样例数据集,如果想求不同公司员工的平均年龄和平均薪水,可以按照下方的代码进行:
如果想对针对不同的列求不同的值,比如要计算不同公司员工的平均年龄以及薪水的中位数,可以利用字典进行聚合操作的指定:
agg聚合过程可以图解如下(第二个例子为例):
transform是一种什么数据操作?和agg有什么区别呢?为了更好地理解transform和agg的不同,下面从实际的应用场景出发进行对比。
在上面的agg中,我们知道了如何求不同公司员工的平均薪水,如果现在需要在原数据集中新增一列avg_salary,代表员工所在的公司的平均薪水(相同公司的员工具有一样的平均薪水),该怎么实现呢?如果按照正常的步骤来计算,需要先求得不同公司的平均薪水,然后按照员工和公司的对应关系填充到对应的位置,不用transform的话,实现代码如下:
如果使用transform
的话,仅需要一行代码:
还是以图解的方式来看看进行groupby后transform的实现过程(为了更直观展示,图中加入了company列,实际按照上面的代码只有salary列):
图中的大方框是transform和agg所不一样的地方,对agg而言,会计算得到A,B,C公司对应的均值并直接返回,但对transform而言,则会对每一条数据求得相应的结果,同一组内的样本会有相同的值,组内求完均值后会按照原索引的顺序返回结果,如果有不理解的可以拿这张图和agg那张对比一下。
apply应该是大家的老朋友了,它相比agg和transform而言更加灵活,能够传入任意自定义的函数,实现复杂的数据操作。那么问题来了,在groupby后使用apply和groupby之前使用有什么区别呢?
区别是有的,但是整个实现原理是基本一致的。两者的区别在于,对于groupby后的apply,以分组后的子DataFrame作为参数传入指定函数的,基本操作单位是DataFrame,而之前介绍的apply的基本操作单位是Series。还是以一个案例来介绍groupby后的apply用法。
假设现在需要获取各个公司年龄最大的员工的数据,该怎么实现呢?可以用以下代码实现:
这样便得到了每个公司年龄最大的员工的数据,整个流程图解如下:
最后,关于apply的使用,这里有个小建议,虽然说apply拥有更大的灵活性,但apply的运行效率会比agg和transform更慢。所以,groupby之后能用agg和transform解决的问题还是优先使用这两个方法,实在解决不了了才考虑使用apply进行操作。
在一个操作中进行多个聚合。下面是计算每个company的平均薪资和年龄:
注意,这里使用的是双方括号“[[]]”。
如果groupby操作的输出是DataFrame,可以使用as_index参数使它们成为DataFrame中的一列。
就像可以聚合多个列一样,也可以使用多个列进行分组。
sales.groupby(["store","product_group"], as_index=False).agg(avg_sales = ("last_week_sales", "mean")).head()
可以使用sort_values
函数根据聚合列对输出进行排序.
sales.groupby(["store","product_group"], as_index=False).agg(avg_sales = ("last_week_sales", "mean")).sort_values(by='avg_sales', ascending=False).head()
max函数返回每个组的最大值。如果需要n个最大的值,可以用下面的方法:
还可以找到一组中的第n个值,例如,找到每个company中年龄第2的薪水:
也可以用负的第n项。例如," nth(-2) "返回从末尾开始的第二行。
unique
函数可用于查找每组中唯一的值。例如,可以找到每个company中唯一的年龄,代码如下:
可以在agg函数中使用lambda表达式作为自定义聚合操作。
使用apply函数将Lambda表达式应用到每个组,如下所示:
缺省情况下,groupby
函数忽略缺失值。如果用于分组的列中缺少一个值,那么它将不包含在任何组中,也不会单独显示。所以可以使用dropna参数来改变这个行为。
首先添加一个缺少存储值的新行:
然后计算带有dropna参数和不带有dropna参数的每个company的平均age,以查看差异。
get_group
函数可获取特定组并且返回DataFrame。
rank
函数用于根据给定列中的值为行分配秩。可以使用rank和groupby函数分别对每个组中的行进行排序。
可以计算出每组的累计总和:
先创建一个示例dataframe:
然后单独创建一个列,包含值列的累计总和,如下所示:
expanding
函数提供展开转换。但是对于展开以后的操作还是需要一个累计函数来对其操作。例如它与cumsum 函数一起使用,结果将与与sum函数相同。
可以使用expand和max函数记录组当前最大值:
在Pandas中groupby函数与aggregate函数共同构成了高效的数据分析工具。