【魏先生搞定Python系列】Pandas使用奇淫巧技(4)——一文搞定gruopby的使用

在进行数据分析时,无论是使用excel、数据库还是其他方式,对数据进行聚合计算是最为常见的,例如excel中的vlookup函数,sql中的GROUPBY语句等等。在pandas中,也支持对数据进行聚合计算,这就是groupby。

1. groupby的工作机制

要想描述groupby的工作原理,拢共分三步:第一步拆分,第二部应用,第三部联合(或者说合并)。直观的说, 首先根据提供的key对数据进行划分,其次以划分好的组为单位,计算每一组的值(可以使求和、求平均、求方差、计数等等均可),最后再将每一组的值合并在一起。使用《利用Python进行数据分析》中的示意图,可以很直观的展现这三步过程:

 

2.  GroupBy基本操作

2.1  groupby分组的实现

2.1.1 基本分组方式

下面开始聊groupby技术的第一个知识点:进行分组的键是什么形式的?

答案是列表或者数组,其长度与待分组的轴一样即可。

初看这个答案,不疼不痒,不明觉厉。但细细品来,这恰恰是pandas高明或者说强大的地方。我们来分析下,首先键是一个列表或者数组(ndarray),这里没有说必须是DataFrame中的哪一列, 而是只要长度与带分组的轴一样即可。这样的话自由度就很高了。我们来用一个例子来验证下:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

group_key = [1,1,2,3,1] #确定分组的key,不一定是dataframe里的一列。
grouped = df.groupby(group_key)
print(grouped)

运行结果如下:

这里可以看到,使用group_key进行了分组,其结果为pandas.core.groupby.generic.DataFrameGroupBy对象。属于一个中间态,后面使用该对象可以实现求平均、求和等操作。

除了额外指定一个列表的group_key外,还可以:

  • 表示DateFrame某个列名的值
  • 字典或者Series,给出待分组轴上的值与分组名之间的对应关系
  • 函数,用于处理轴索引中的各个标签
grouped1 = df.groupby(df['key1'])
grouped2 = df.groupby('key1')
grouped3 = df.groupby([df[key1],df['key2']]) #注意最后是一个list
grouped4 = df.groupby(['key1','key2'])

2.1.2 字典或Series进行分组

 

2.1.3 使用函数进行分组

 

2.2  groupby基本操作

2.2.1 计算groupby分组的个数

通过调用groupby的 size 方法实现,这是一个很有用的功能,返回有分组大小的Series。在能想到的应用中,至少可以通过该方法实现对key的统计计数,示例如下:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped1 = df.groupby([df['key1'],df['key2']])
print(grouped1.size())

输出结果为:

 

这里还要提一句,为什么说size方法这么有用呢,因为在下面章节中我们会提到count这个聚合方法,但是在使用count时,作为key的列是不进行任何计算的,除非对数据块中的key列复制出来,再作为groupby的key,这样相对要麻烦许多。 

 2.2.2 对分组进行迭代

groupby对象是可迭代的,可以通过key将原数据进行拆分,具体示例如下:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped = df.groupby('key1')

for name,group in grouped:
    print(name)
    print(group)

运行结果如下:

可以看出,DataFrameGroupBy对象迭代后有返回两个值,一个是key的名字,一个是key对应的数组。如果key是一个复合键,则第一个变量使用元组表示,如:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped = df.groupby(['key1','key2'])

for (n1,n2),group in grouped:
    print(n1,n2)
    print(group)

运行结果如下:

2.2.3 对数据块中的一部分数据进行groupby

在普遍情况下,需要分析的数据块很大,如有几十列的数据,在大部分时候,我们只是想对其中一列或者几列进行分组,以提高运算效率,此时可以有按照以下2种方式实现:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped1 = df.groupby([df['key1'],df['key2']])
print(grouped1.size())


d1 = df.groupby(['key1'])['data1']
print(d1.mean())
print(d1)

d2 = df['data1'].groupby(df['key1']).mean()
print(d2)

d3 = df[['data1','data2']].groupby([df['key1'],df['key2']])
d4 = df.groupby(df['key1'])[['data1','data2']]
print(d4.mean())

在这里,要强调一点,对于groupby列的选取,或者是key的选取,往往涉及到多列或者多key的情况,具体引用时很是头疼,比如是df['data1','data2']还是df[['data2','data2']],或者groupby([df['key1'],df['key2']])还是groupby(df['key1'],df['key2'])?经过多次测试,就记住不论是多列还是多key,[]里面都是一个list准没错,所以不管是多少数据,都加个[]准没错。

 

3 groupby的聚合操作

3.1 基本聚合操作

我们做groupby操作,肯定不是仅仅为了得到一个DataFrameGroupBy对象,那么,groupby可以做什么呢?简单说,可以用来做组别内的计算。具体实现可以参见Pandas使用奇淫巧技(3)的内容,这里我们举几个普遍使用的例子:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

#使用单键进行分组
grouped1 = df.groupby(df['key1'])
print(grouped1.mean())
print(grouped1.sum())
print(grouped1.count())

#使用键值对进行分组
grouped2 = df.groupby(['key1','key2'])
print(grouped2.mean())
print(grouped2.sum())
print(grouped2.count())

在上面的例子中,使用grouped1 求mean和求sum的结果格式如下:

而计算count时,得到的结果格式如下:

发现问题没有?是的,相较于count的计数运算,sum和mean这种数值运算结果少了一列‘key2’,这里就涉及到我们第三个知识点:

对于非数值列,或者可以说是麻烦列,在进行数值计算的时候会自动忽略。这是一个非常好的特性,可以避免对原始数据的反复规整操作。

这一节的标题是基本聚合操作,之所以基本,是因为截至目前我们都是在用pandas的内置函数进行操作和分析,而事实上,除了内置函数外,我们可以使用自己发明的任何聚合函数进行计算,这些将在groupby的高阶应用中进行说明。对于基本的groupby聚合方法,经过常见优化的见下表:

3.2 高级聚合操作

上面我们已经学习了groupby的一般内置函数调用,不过这显然是不够的,下面我们来看看groupby的一些高阶操作。其实,这个标题不是很确切,所谓的高阶操作,不仅仅是聚合操作,还有很多更具普遍性的操作,例如排序等。在pandas中,此类高阶操作主要使用agg和apply两个个函数实现。在学习高阶操作前,我们先来看下这两个函数的区别。

3.2.1 agg 与 apply的区别

在说明两者区别前, 我们还是先来再认识下聚合操作。所谓的聚合,简单说就是将一组数据抽象成1个值,抽象的目的可以是求和、求平均、求方差等等,也就是将一个一维数组变为一个标量值。而聚合函数,也就是实现这一过程的函数。再回到本节的重点,agg与apply,故名思议,aggregate即为聚合之意,它所调用的函数必为聚合函数,而apply则更普世,适用于聚合与非聚合函数。

具体的例子将在下面一一列举。

3.2.2 多函数聚合

在进行groupby分析时,可能对数据既进行求和也进行求平均的运算,类似于dataframe对象中的describle方法实现。此时,我们可以使用agg方法加以实现。下面先举个例子:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped = df.groupby(['key1','key2'])
grouped_agg = grouped.agg(['sum','mean'])
print(grouped_agg)

上面代码运行结果如下:

这里可以看到,sum和mean方法分别被用到了每一个数据列里。还有一点,就是pandas在对聚合方法命名时,默认使用的就是函数的名字,如果你希望自定义函数名,可以采用元组来命名(name,fuction),  示例如下:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped = df.groupby(['key1','key2'])

grouped_agg = grouped.agg([('求和','sum'),('标准差',np.std)])
print(grouped_agg)

测试结果如下:

这里可以看到,原来默认为sum和np.std 命名的结果改成了元组内的name。不过这里要注意,对于np.std是没有双引号的,如果加了双引号,会报如下错误:

我想这和基本聚合操作里提到的优化的groupby方法有关,这些方法已经写到SeriesGroupBy类里,可以通过 '' 来直接调用,‘’而其他函数则无需''调用(包括你自己写的函数)。

再更进一步,上面的例子中使用的函数,会作用到所有的数据列中,如果想要制定某一数据列用某一个或几个函数,则可以通过字典的形式实现,格式为:{列明1:[函数名1,函数名2],列名:[函数名3,函数名4]...},具体示例如下:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped = df.groupby(['key1','key2'])

grouped_agg = grouped.agg({'data1':[('求和','sum'),'mean'],'data2':np.std})
print(grouped_agg)

执行结果如下:

可以看出,通过字典、list和元组的组合,可以实现对数据指定的操作及显示。

在这一小节最后的最后,我们再说一种普遍的情况,就是在大多数情况下,我们只是对数据列做一种操作,比如求平均,求和等等,此时不需要要再用复合列来进行标注,也就是将操作列名忽略,从而达到简化dataframe的目的。此时,可以通过设置as_index加以实现。这里要注意,仅对数据列1个函数的操作有效(操作函数不同列可以不同,但只能是一个),如果是像上面的例子中的多函数操作,即使设置为False,也是不会忽略的。

示例如下:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped = df.groupby(['key1','key2'],as_index = False)

grouped_agg = grouped.agg({'data1':'sum','data2':'mean'})
print(grouped_agg)
print(grouped_agg.columns)

grouped_mean = grouped.mean()
print(grouped_mean)

运行结果如下:

3.2.3 可广播函数

上面一小节洋洋洒洒这么多,主要介绍的是groupby的聚合函数,实际上,除了聚合函数外,还有很多其他的函数和应用。这里就要介绍transform了。首先,我们考虑这样一个应用需求:

根据groupby的分组求各个分组的平均值,然后再计算各个元素与所在分组均值的差值。对于这种需求,首先我们需要先有一个dataframe来记录每个元素所在组的均值,再用原dataframe减去这个dataframe即可。

上面的需求中,按照原数据格式显示每个元素所在组的均值这种措施,实际就可以使用transform实现。

实现代码如下:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped = df.groupby(df['key1'])

df_mean = grouped.transform(np.mean)
print(df_mean)
print(df[['data1','data2']]-df_mean)

输出结果为:

可以看出,transform的作用就是将一个函数应用到各个分组上,然后将生成的结果放到原dataframe中适当的位置。如果函数得到的结果是一个标量,那么就是将这个标量结果以广播的形式送到数组的各个位置。广播,也是transform最主要的功用,在下面我们即将介绍的apply而言,可以节省写广播方法的时间。

3.2.4 更为普适的函数

前面已经介绍了两种groupby的函数调用,分别为agg和transform,其中agg要求使用的函数是聚合函数,生成的结果是一个标量值;transform对于生成结果的格式也有限制,即要么产生一个可以广播的标量值(如np.mean),要么产生一个与原dataframe相同大小的结果数组。而对于更普遍的情况,例如生成的结果是一个与原dataframe大小不同的数组,就需要使用apply。

还是举个栗子:根据分组来获取第一行或者前几行的数据,实现代码如下:

import pandas as pd
import numpy as np

df = pd.DataFrame({'data1':np.random.randn(5),'data2':np.random.randn(5),'key1':['a','b','a','a','b'],'key2':['one','one','two','two','two']})
print(df)
print('\n')

grouped = df.groupby(df['key1'])

def first_five(df):
    return df.iloc[0:1,:]
grouped_five = grouped.apply(first_five)
print(grouped_five)

运行结果如下:

 

 

 

 

s://www.jb51.net/article/138587.htm

https://www.cnblogs.com/vczh/p/6746935.html

https://blog.csdn.net/u013317445/article/details/85268877?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值