对数据集进行分组并对各组应用一个函数(无论是聚合还是转换),这是数据分析工作中的重要环节。在将数据集准备好之后,通常的任务就是计算分组统计或生成透视表。pandas提供了一个灵活高效的groupby功能,它使你能以以一种自然的方式对数据集进行切片、切块、摘要等操作。
一、GroupBy技术
“split-apply-combine”(拆分-应用-合并),很好的描述了分组运算的整个过程,如下图所示。
- 第一阶段:pandas对象中的数据会根据你所提供的一个或多个键被拆分(split)为多组。拆分操作是在对象的特定轴上执行的。
- 第二阶段:将一个函数应用(apply)到各个分组并产生一个新值。
- 第三阶段:所有这些函数的执行结果会被合并(combine)到最终的结果对象中。
1.1 group by基本用法
代码示例:
import numpy as np import pandas as pd df = pd.DataFrame({'key1' : ['a', 'a', 'b', 'b', 'a'], 'key2' : ['one', 'two', 'one', 'two', 'one'], 'data1' : np.random.randn(5), 'data2' : np.random.randn(5)}) #分组键是Series对象 grouped = df['data1'].groupby(df['key1']) #返回GroupBy对象,并未计算,但包含计算的所有信息 grouped.mean() means = df['data1'].groupby([df['key1'], df['key2']]).mean() """ df: grouped.mean(): means: data1 data2 key1 key2 key1 key1 key2 0 -0.092273 -0.658181 a one a 0.370973 a one -0.188932 1 1.490783 -0.601443 a two b 0.422070 two 1.490783 2 0.657373 -1.449184 b one b one 0.657373 3 0.186767 1.233480 b two two 0.186767 4 -0.285591 -0.234298 a one """ #分组键可以为任何适当长度的数组 states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio']) years = np.array([2005, 2005, 2006, 2005, 2006]) df['data1'].groupby([states, years]).mean() """result: California 2005 -0.038644 2006 0.169926 Ohio 2005 0.702394 2006 -1.049059 """ #分组还可以是列名(字符串、数字或其他python对象) df.groupby('key1').mean() """ data1 data2 key1 a -0.010345 0.105864 b 0.259023 -0.230571 """ df.groupby(['key1', 'key2']).mean() """ data1 data2 key1 key2 a one 0.003805 -0.103989 two -0.038644 0.525572 b one 0.169926 -0.459576 two 0.348119 -0.001566 """ #groupby的size方法,返回的是一个含有分组大小的Series df.groupby(['key1', 'key2']).size() """ df: df.groupby(['key1', 'key2']).size(): data1 data2 key1 key2 key1 key2 0 1.056668 -0.174590 a one a one 2 1 -0.038644 0.525572 a two two 1 2 0.169926 -0.459576 b one b one 1 3 0.348119 -0.001566 b two two 1 4 -1.049059 -0.033389 a one """
1.2 对分组进行迭代
GroupBy对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)。
代码示例:
#Groupby对象支持迭代 import numpy as np import pandas as pd df = pd.DataFrame({'key1' : ['a', 'a', 'b', 'b', 'a'], 'key2' : ['one', 'two', 'one', 'two', 'one'], 'data1' : np.random.randn(5), 'data2' : np.random.randn(5)}) for name, group in df.groupby('key1'): print(name) print(group) """result: a data1 data2 key1 key2 0 0.006542 -1.554254 a one 1 0.924896 -0.807694 a two 4 0.753633 0.365298 a one b data1 data2 key1 key2 2 -0.348402 2.171439 b one 3 2.013282 -1.030958 b two """ #对于多重键元组的第一个元素将是由键值组成的元组 for (k1, k2), group in df.groupby(['key1', 'key2']): print((k1, k2)) print(group) """result: ('a', 'one') data1 data2 key1 key2 0 0.006542 -1.554254 a one 4 0.753633 0.365298 a one ('a', 'two') data1 data2 key1 key2 1 0.924896 -0.807694 a two ('b', 'one') data1 data2 key1 key2 2 -0.348402 2.171439 b one ('b', 'two') data1 data2 key1 key2 3 2.013282 -1.030958 b two """ #将数据片段做成字典 pieces = dict(list(df.groupby('key1'))) """pieces= {'a': data1 data2 key1 key2 0 0.006542 -1.554254 a one 1 0.924896 -0.807694 a two 4 0.753633 0.365298 a one, 'b': data1 data2 key1 key2 2 -0.348402 2.171439 b one 3 2.013282 -1.030958 b two} """ #group by默认在axis=0上分组,可以通过设置在其他轴上进行分组 grouped = df.groupby(df.dtypes, axis=1) """df.dtypes= data1 float64 data2 float64 key1 object key2 object """ for dtype, group in grouped: print(dtype) print(group) """result: float64 data1 data2 0 0.006542 -1.554254 1 0.924896 -0.807694 2 -0.348402 2.171439 3 2.013282 -1.030958 4 0.753633 0.365298 object key1 key2 0 a one 1 a two 2 b one 3 b two 4 a one """
1.3 选取一个或一组列
对于由DataFrame产生的GroupBy对象,如果用一个或一组列名对其进行索引,就能实现选取部分列进行聚合的目的。
代码示例:
#以下两句话的结果相同 df.groupby('key1')['data1'] df['data1'].groupby(df['key1']) #对部分列进行聚合时,则可以这样写: df.groupby(['key1', 'key2'])[['data2']].mean() """result: data2 key1 key2 a one -0.594478 two -0.807694 b one 2.171439 two -1.030958 """
1.4 通过字典或Series进行分组
除数组以外,分组信息还可以其他形式存在。
代码示例:
#通过字典或Series进行分组 people = pd.DataFrame(np.random.randn(5, 5), columns=['a', 'b', 'c', 'd', 'e'], index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis']) people.iloc[2:3, [1, 2]] = np.nan # Add a few NA values mapping = {'a': 'red', 'b': 'red', 'c': 'blue', 'd': 'blue', 'e': 'red', 'f' : 'orange'} by_column = people.groupby(mapping, axis=1) by_column.sum() """ people: by_column.sum(): a b c d e blue red Joe 0.775302 -0.160469 0.025246 -1.062986 0.339286 Joe -1.037739 0.954119 Steve -3.076949 -0.905321 1.279802 -0.113725 1.227737 Steve 1.166078 -2.754533 Wes -0.633888 NaN NaN 0.603396 0.212681 Wes 0.603396 -0.421207 Jim -2.551847 0.228456 0.223684 0.364597 -0.249074 Jim 0.588281 -2.572465 Travis 2.081185 1.618638 2.052801 0.385477 0.576724 Travis 2.438278 4.276546 """
1.5用函数进行分组
python函数在定义分组映射关系时可以更有创意且更为抽象。任何被当做分组键的函数都会在各个索引上被调用一次,其返回值就会被作为分组的名称。
扫描二维码关注公众号,回复:
997490 查看本文章
代码示例:
#用函数进行分组 people = pd.DataFrame(np.random.randn(5, 5), columns=['a', 'b', 'c', 'd', 'e'], index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis']) """people= a b c d e Joe 0.775302 -0.160469 0.025246 -1.062986 0.339286 Steve -3.076949 -0.905321 1.279802 -0.113725 1.227737 Wes -0.633888 NaN NaN 0.603396 0.212681 Jim -2.551847 0.228456 0.223684 0.364597 -0.249074 Travis 2.081185 1.618638 2.052801 0.385477 0.576724 """ #传入len函数,计算人名的字符串长度 people.groupby(len).sum() """result: a b c d e 3 -2.410433 0.067987 0.248930 -0.094993 0.302893 5 -3.076949 -0.905321 1.279802 -0.113725 1.227737 6 2.081185 1.618638 2.052801 0.385477 0.576724 """ #将函数与数组、列表、字典、Series混合使用 key_list = ['one', 'one', 'one', 'two', 'two'] #当一个数与NaN做运算时,默认NaN不存在,例:1和NaN比较大小后,最小值为1 people.groupby([len, key_list]).min() """result: a b c d e 3 one -0.633888 -0.160469 0.025246 -1.062986 0.212681 two -2.551847 0.228456 0.223684 0.364597 -0.249074 5 one -3.076949 -0.905321 1.279802 -0.113725 1.227737 6 two 2.081185 1.618638 2.052801 0.385477 0.576724 """
1.6 根据索引级别分组
层次化索引数据集最方便的地方就在于它能够根据索引级别进行聚合。可使用level关键字传入级别编号或名称,来实现该目的:
#根据索引级别分组 columns = pd.MultiIndex.from_arrays([['US', 'US', 'US', 'JP', 'JP'], [1, 3, 5, 1, 3]], names=['cty', 'tenor']) hier_df = pd.DataFrame(np.random.randn(4, 5), columns=columns) result=hier_df.groupby(level='cty', axis=1).count() """ hier_df: result: cty US JP tenor 1 3 5 1 3 cty JP US 0 -0.399634 -1.075307 0.034410 -0.038911 -0.450597 0 2 3 1 0.772425 0.118035 0.232763 1.997144 0.782356 1 2 3 2 -1.092196 -0.863868 -0.312989 0.325849 0.777053 2 2 3 3 1.014243 0.125624 -0.320793 -0.365383 -0.721173 3 2 3 """
二、数据聚合
对于聚合,指的是任何能够从数组产生标量值的数据转换过程,例如mean、count、min以及sum等。许多常见的聚合运算都有就地计算数据集统计信息的优化实现(describe函数)。
常见聚合方法:
代码示例:
#聚合运算 #计算峰峰值 def peak_to_peak(arr): return arr.max() - arr.min() tips = pd.read_csv('tips.csv') # Add tip percentage of total bill tips['tip_pct'] = tips['tip'] / tips['total_bill'] grouped = tips.groupby(['day', 'smoker']) grouped_pct = grouped['tip_pct'] #利用agg方法,使用聚合函数 grouped_pct.agg('mean') #聚合函数还可以自定义,自定义函数名不需加‘’ grouped_pct.agg(peak_to_peak) """ grouped_pct.agg('mean'): grouped_pct.agg(peak_to_peak) day smoker day smoker Fri No 0.151650 Fri No 0.067349 Yes 0.174783 Yes 0.159925 Sat No 0.158048 Sat No 0.235193 Yes 0.147906 Yes 0.290095 Sun No 0.160113 Sun No 0.193226 Yes 0.187250 Yes 0.644685 Thur No 0.160298 Thur No 0.193350 Yes 0.163863 Yes 0.151240 """ #一次性使用多个聚合函数,得到的DataFrame的列就会以相应的函数命名 grouped_pct.agg(['mean', 'std', peak_to_peak]) """ mean std peak_to_peak day smoker Fri No 0.151650 0.028123 0.067349 Yes 0.174783 0.051293 0.159925 Sat No 0.158048 0.039767 0.235193 Yes 0.147906 0.061375 0.290095 Sun No 0.160113 0.042347 0.193226 Yes 0.187250 0.154134 0.644685 Thur No 0.160298 0.038774 0.193350 Yes 0.163863 0.039389 0.151240 """ #传入(name,function)的元组,则第一个元素为列名 grouped_pct.agg([('foo', 'mean'), ('bar', np.std)]) """ foo bar day smoker Fri No 0.151650 0.028123 Yes 0.174783 0.051293 Sat No 0.158048 0.039767 Yes 0.147906 0.061375 Sun No 0.160113 0.042347 Yes 0.187250 0.154134 Thur No 0.160298 0.038774 Yes 0.163863 0.039389 """ #不同列应用不同函数,使用字典: grouped.agg({'tip_pct' : ['min', 'max', 'mean', 'std'], 'size' : 'sum'}) """ tip_pct size min max mean std sum day smoker Fri No 0.120385 0.187735 0.151650 0.028123 9 Yes 0.103555 0.263480 0.174783 0.051293 31 Sat No 0.056797 0.291990 0.158048 0.039767 115 Yes 0.035638 0.325733 0.147906 0.061375 104 Sun No 0.059447 0.252672 0.160113 0.042347 167 Yes 0.065660 0.710345 0.187250 0.154134 49 Thur No 0.072961 0.266312 0.160298 0.038774 112 Yes 0.090014 0.241255 0.163863 0.039389 40 """ #当聚合数据不是由唯一的分组键组成的索引,如层次化索引,需as_index=False禁用索引功能 tips.groupby(['day', 'smoker'], as_index=False).mean() """ day smoker total_bill tip size tip_pct 0 Fri No 18.420000 2.812500 2.250000 0.151650 1 Fri Yes 16.813333 2.714000 2.066667 0.174783 2 Sat No 19.661778 3.102889 2.555556 0.158048 3 Sat Yes 21.276667 2.875476 2.476190 0.147906 4 Sun No 20.506667 3.167895 2.929825 0.160113 5 Sun Yes 24.120000 3.516842 2.578947 0.187250 6 Thur No 17.113111 2.673778 2.488889 0.160298 7 Thur Yes 19.190588 3.030000 2.352941 0.163863 #与tips.groupby(['day', 'smoker']).mean()区别一下: total_bill tip size tip_pct day smoker Fri No 18.420000 2.812500 2.250000 0.151650 Yes 16.813333 2.714000 2.066667 0.174783 Sat No 19.661778 3.102889 2.555556 0.158048 Yes 21.276667 2.875476 2.476190 0.147906 Sun No 20.506667 3.167895 2.929825 0.160113 Yes 24.120000 3.516842 2.578947 0.187250 Thur No 17.113111 2.673778 2.488889 0.160298 Yes 19.190588 3.030000 2.352941 0.163863 """
三、分组级运算和转换
聚合是分组运算中的一种,是数据转换的一个特例。
- transform会将一个函数应用到各个分组,然后将结果放置到适当的位置上。如果个分组产生的是一个标量值,则该值就会被广播出去。
- apply是GroupBy对象中最一般化的方法,apply会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试将各片段组合到一起。
#transform方法 people = pd.DataFrame(np.random.randn(5, 5), columns=['a', 'b', 'c', 'd', 'e'], index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis']) key=['one','two','one','two','one'] people.groupby(key).mean() """ a b c d e one 0.246559 0.656440 0.443460 -0.034783 0.705865 two 0.180082 -0.377286 -0.507926 0.938013 0.382583 """ people.groupby(key).transform(np.mean) """ a b c d e Joe 0.246559 0.656440 0.443460 -0.034783 0.705865 Steve 0.180082 -0.377286 -0.507926 0.938013 0.382583 Wes 0.246559 0.656440 0.443460 -0.034783 0.705865 Jim 0.180082 -0.377286 -0.507926 0.938013 0.382583 Travis 0.246559 0.656440 0.443460 -0.034783 0.705865 """ #apply方法 tips = pd.read_csv('tips.csv') def top(df, n=5, column='tip_pct'): return df.sort_values(by=column)[-n:] top(tips, n=6) #top函数在DataFrame的各个片段上调用,然后结果由pandas.concat组装在一起,并以分组名称进行了标记 tips.groupby('smoker').apply(top) """ def top(df, n=5, column='tip_pct'): return df.sort_values(by=column)[-n:] top(tips, n=6) tips.groupby('smoker').apply(top) """
四、透视图和交叉表
- 透视表(pivot table)是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具。它根据一个或多个键对数据进行聚合,并根据行和列上的分组键将数据分配到各个矩形区域中。DataFrame有一个pivot_table方法,此外还有一个顶级的pandas.pivot_table函数。除能为groupby提供便利之外,pivot_table还可以添加分项小计(也叫margins)。
- 交叉表(cross-tabulation,简称crosstab)是一种用于计算分组频率的特殊透视表。
#透视表 tips.pivot_table(index=['day', 'smoker']) #数据是tips['tip_pct', 'size'],索引是['time', 'day'], #列名是'smoker',通过margins=True打开分项小计All tips.pivot_table(['tip_pct', 'size'], index=['time', 'day'], columns='smoker', margins=True) """result: size tip_pct smoker No Yes All No Yes All time day Dinner Fri 2.000000 2.222222 2.166667 0.139622 0.165347 0.158916 Sat 2.555556 2.476190 2.517241 0.158048 0.147906 0.153152 Sun 2.929825 2.578947 2.842105 0.160113 0.187250 0.166897 Thur 2.000000 NaN 2.000000 0.159744 NaN 0.159744 Lunch Fri 3.000000 1.833333 2.000000 0.187735 0.188937 0.188765 Thur 2.500000 2.352941 2.459016 0.160311 0.163863 0.161301 All 2.668874 2.408602 2.569672 0.159328 0.163196 0.160803 """ #使用count或len聚合函数,并传入aggfunc,可以获得有关分组大小的交叉表 tips.pivot_table('tip_pct', index=['time', 'smoker'], columns='day', aggfunc=len, margins=True) """ day Fri Sat Sun Thur All time smoker Dinner No 3.0 45.0 57.0 1.0 106.0 Yes 9.0 42.0 19.0 NaN 70.0 Lunch No 1.0 NaN NaN 44.0 45.0 Yes 6.0 NaN NaN 17.0 23.0 All 19.0 87.0 76.0 62.0 244.0 """ #存在空的组合(即:NA),可以设置fill_value tips.pivot_table('size', index=['time', 'smoker'], columns='day', aggfunc='sum', fill_value=0) """ day Fri Sat Sun Thur time smoker Dinner No 6 115 167 2 Yes 20 104 49 0 Lunch No 3 0 0 110 Yes 11 0 0 40 """ #pd.crosstab(index,columns,value=None,rownames=None,colnames=None, #aggfunc=None,margins=False,dropna=True,normalize=False) #[tips.time, tips.day]是行索引,tips.smoker是列索引,并打开分项小计功能 pd.crosstab([tips.time, tips.day], tips.smoker, margins=True) """ smoker No Yes All time day Dinner Fri 3 9 12 Sat 45 42 87 Sun 57 19 76 Thur 1 0 1 Lunch Fri 1 6 7 Thur 44 17 61 All 151 93 244 """