在进行数据分析时,无论是使用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