pandas 数据聚合与分组运算

1. GroupBy技术
pandas对象(无论是Series、DataFrame还是其他的)中的数据会根据你所提供的一个或多个键被拆分(split)为多组。
拆分操作是在对象的特定轴上执行的。
例如:DataFrame可以在其行(axis=0)或列(axis=1)上进行分组,然后将一个函数应用(apply)到各个分组并产生一个新值。
最后,所有这些函数的执行结果会被合并(combine)到最终的结果对象中。
下图大致说明了分组聚合的演示:
pandas 数据聚合与分组运算
文章图片

分组键可以有多种形式,且类型不必相同:
(1)列表或数组,其长度与待分组的轴一样。
(2)表示DataFrame某个列名的值
(3)字典或Series,给出待分组轴上的值与分组名之间的对应关系
(4)函数,用于处理轴索引或索引中的各个标签
注意,后三种都只是快捷方式而己,其最终目的仍然是产生一组用于拆分对象的值。

import numpy as np from pandas import DataFrame,Series#(1)运行numpy的groupby方法分组 df=DataFrame({'key1':['a','a','b','b','a'], 'key2':['one','two','one','two','one'], 'data1':np.random.randn(5), 'data2':np.random.randn(5)}) print df #输出结果如下: #data1data2 key1 key2 # 01.6057581.477281aone # 10.7263550.724383atwo # 22.0075101.778955bone # 30.543327 -0.057515btwo # 4 -0.351209 -0.518018aone#假设想要按kye1进行分组,并计算data1列的平均值。访问data1,并根据key1调用groupby: grouped=df['data1'].groupby(df['key1']) #data1数据按key1进行分组 print grouped #输出结果如下:#grouped是一个GroupBy对象,它实际上还没有进行任何计算,只是含有一些有关分组键df['key1'] #的中间数据而己。换句话说,该对象已经有了接下来对各分组执行运算所需的一切信息。 #我们可以调用GroupBy的mean方法来计算分组平均值 print grouped.mean() #输出结果如下: # key1 # a-0.434085 # b-0.375503 # Name: data1, dtype: float64 #注意:数据(Series)根据分组键进行了聚合,产生了一个新的Series,其索引为key1列中的唯一值。 #之所以结果中索引的名称为key1,是因为原始DataFrame的列df['key1']就叫这个名字#若我们一次传入多个数组,就会得到不同的结果 means=df['data1'].groupby([df['key1'],df['key2']]).mean() print means #输出结果如下: # key1key2 # aone-0.365097 #two-0.381603 # bone-1.403614 #two-2.742555 # Name: data1, dtype: float64 #注意:通过两个键对数据进行了分组,得到的Series具有一个层次化索引(由唯一的键对组成)print means.unstack()#unstack和stack行列的转化 #输出结果如下: # key2onetwo # key1 # a-0.3501900.480907 # b-0.8577940.949170#在上面这些示例中,分组键均为Series.实际上,分组键可以是任何长度适当的数组: states=np.array(['Ohio','California','California','Ohio','Ohio']) years=np.array([2005,2005,2006,2005,2006]) print df['data1'].groupby([states,years]).mean() #输出结果如下: # California20050.449663 #20060.425458 # Ohio20050.537592 #2006-0.279219 # Name: data1, dtype: float64#还可以将列名(可以是字符串、数字或其他Python对象)用作分组键: print df.groupby('key1').mean() #输出结果如下: #data1data2 # key1 # a0.0463270.561451 # b-0.0859890.215877 print df.groupby(['key1','key2']).mean() #输出结果如下: # key1 key2 # aone-0.540068 -1.080045 #two-0.905713 -0.996828 # bone2.1111360.546535 #two0.648188 -0.498834#GroupBy的size方法,它可以返回一个含有分组大小的Series: print df.groupby(['key1','key2']).size() #输出结果如下: # key1key2 # aone2表示a one的有2个数 #two1 # bone1 #two1

对分组进行迭代:
import numpy as np from pandas import DataFrame,Series(1)运行numpy的groupby方法分组 df=DataFrame({'key1':['a','a','b','b','a'], 'key2':['one','two','one','two','one'], 'data1':np.random.randn(5), 'data2':np.random.randn(5)}) print df #输出结果如下: #data1data2 key1 key2 # 01.6057581.477281aone # 10.7263550.724383atwo # 22.0075101.778955bone # 30.543327 -0.057515btwo # 4 -0.351209 -0.518018aone(2)GroupBy对象支持迭代,可以产生一组二元元组(由分组名和数据块组成) for name,group in df.groupby('key1'): print name print group #输出结果如下: #name: # a #data1data2 key1 key2 # 01.891706 -0.492166aone # 11.809804 -0.292517atwo # 4 -1.1167791.870921aone #group: # b #data1data2 key1 key2 # 20.737782 -0.521142bone # 3 -0.8543841.356374btwo(3)对于多重键情况,元组的第一个元素将会是由键值组成的元组: for (k1,k2),group in df.groupby(['key1','key2']): print k1,k2 print group #输出结果如下: #k1,k2 # a one #data1data2 key1 key2 # 0 -0.767050 -2.596870aone # 40.6410770.480521aone # a two #data1data2 key1 key2 # 12.083827 -0.680062atwo #group # b one #data1data2 key1 key2 # 2 -0.070502 -0.213549bone # b two #data1data2 key1 key2 # 3 -0.614806 -0.016378btwo(4)当然,你可以对这些数据片段做任何操作。有一个有用的运算:将这些数据片段做成一个字典:dict(list(df.groupby(''))) pieces=dict(list(df.groupby('key1'))) print pieces #输出结果如下: # {'a':data1data2 key1 key2 # 0 -2.860537 -0.139119aone # 11.832103 -0.073315atwo # 4 -0.5241760.052688aone, #'b':data1data2 key1 key2 # 2 -0.2130961.076033bone # 30.654225 -0.786246btwo} # print pieces['b'] #输出结果如下: #data1data2 key1 key2 # 2 -0.2130961.076033bone # 30.654225 -0.786246btwo(5)groupby默认是在axis=0(行)上进行分组,通过设置也可以在其他任何轴上进行分组:axis=1(列) #拿上面的例子中的df来说,我们可以根据dtype对列进行分组: print df.dtypes #输出结果如下: # data1float64 # data2float64 # key1object # key2object # dtype: object grouped=df.groupby(df.dtypes,axis=1) print dict(list(grouped)) #将其转化为字典 #输出结果如下: # {dtype('O'):key1 key2 # 0aone # 1atwo # 2bone # 3btwo # 4aone, #dtype('float64'):data1data2 # 0 -1.586465 -0.380714 # 1 -1.786134 -0.663670 # 20.3203590.624725 # 3 -0.6204320.924842 # 4 -0.6634631.023058}

选取一个或一组列:
对于由DataFrame产生的GroupBy对象,如果一个(单个字符串)或一组(字符串数组)列名对其进行索引,
就能实选取部分列进行整合的目的。也就是说:
print df.groupby('key1')['data1'] 是下面代码的语法糖: print df['data1'].groupby(df['key1'])

尤其对于大数据集,很可能只需要对部分列进行聚合。
import numpy as np from pandas import DataFrame,Series# (1)运行numpy的groupby方法分组 df=DataFrame({'key1':['a','a','b','b','a'], 'key2':['one','two','one','two','one'], 'data1':np.random.randn(5), 'data2':np.random.randn(5)}) # print df #输出结果如下: #data1data2 key1 key2 # 01.6057581.477281aone # 10.7263550.724383atwo # 22.0075101.778955bone # 30.543327 -0.057515btwo # 4 -0.351209 -0.518018aone#例如,在前面那个数据集中,如果只需计算data2列的平均值并以DataFrame形式得到结果,我们可以编写: print df.groupby(['key1','key2'])[['data2']].mean() #输出结果如下: #data2 # key1 key2 # aone-1.329059 #two-0.486441 # bone0.400199 #two-1.215200 #这种索引操作所返回的对象是一个已分组的DataFrame s_grouped=df.groupby(['key1','key2'])['data2'] print s_grouped #输出结果如下: 它是一个groupby对象 print s_grouped.mean()

通过字典或Series进行分组:
import numpy as np from pandas import DataFrame,Series#(1)除数组以外,分组信息还可以其他形式存在。 # people=DataFrame(np.random.randn(5,5), #columns=['a','b','c','d','e'], #index=['Joe','Steve','Wes','Jim','Travis']) # # print people people=DataFrame([[1,0,1,2,1], [0,2,3,1,0], [1,2,3,0,1], [-1,0,-1,2,0], [2,3,1,-2,0]], columns=['a','b','c','d','e'], index=['Joe','Steve','Wes','Jim','Travis']) people.ix[2:3,['b','c']]=np.nan#ix[2:3]行索引为[2:3],索引切片不包括3,'b','c'列的值为Nan print people #输出结果如下: #abcde # Joe10.01.021 # Steve02.03.010 # Wes1NaNNaN01 # Jim-10.0 -1.020 # Travis23.01.0 -20#(2)假设己知列的分组关系,并希望根据分组计算列的总计: mapping={'a':'red','b':'red','c':'blue', 'd':'blue','e':'red','f':'orange'} #现在只需将这个字典传给groupby即可: by_column=people.groupby(mapping,axis=1) print by_column.sum() #输出结果如下:'c','d'相加存入blue中,其它的相加放入red中 #bluered # Joe3.02.0 # Steve4.02.0 # Wes0.02.0 # Jim1.0 -1.0 # Travis-1.05.0#(3) Series也有同样的功能,它可以被看做一个固定大小的映射。 #对于上面的那个例子,如果用Series作为分组键,则pandas会检查Series以确保其索引跟分组轴是对齐的: map_series=Series(mapping) print map_series #输出结果如下: # ared # bred # cblue # dblue # ered # forange print people.groupby(map_series,axis=1).count() #输出结果如下: #bluered # Joe23 # Steve23 # Wes12 # Jim23 # Travis23

通过函数进行分组:
import numpy as np from pandas import DataFrame,Series#前面一小节的示例DataFrame为例,其索引值为人的名字。假设你希望根据人名的长度进行分组, #虽然可以求取一个字符串长度数组,但其实仅仅传入len函数就可以了: people=DataFrame([[1,0,1,2,1], [0,2,3,1,0], [1,2,3,0,1], [-1,0,-1,2,0], [2,3,1,-2,0]], columns=['a','b','c','d','e'], index=['Joe','Steve','Wes','Jim','Travis']) print people #输出结果如下: #abcde # Joe10121 # Steve02310 # Wes12301 # Jim-10 -120 # Travis231 -20 print people.groupby(len).sum() #输出结果如下: #abcde # 312342#3是Joe,wes,Jim 在a,b,c,d,e列相加 # 502310#5只有steve一个人,故steve的数据抄下来即可 # 6231 -20#将函数跟数组、列表、字典、Series混合使用,因为任何东西最终都会被转换为数组: key_list=['one','one','one','two','two'] print people.groupby([len,key_list]).min() #输出结果如下: #abcde # 3 one10101 #two -10 -120 # 5 one02310 # 6 two231 -20

根据索引级别分组:
层次化索引数据集就在于它能够根据索引级别进行聚合。要实现该目的,通过Level关键字传入级别编号或名称即可:
import numpy as np from pandas import DataFrame,Series import pandas as pdcolumns=pd.MultiIndex.from_arrays([['US','US','US','JP','JP'], [1,3,5,1,3]],names=['cty','tenor']) hier_df=DataFrame(np.random.randn(4,5),columns=columns) print hier_df #输出结果如下: # ctyUSJP # tenor13513 # 01.1897052.3347320.6023380.6507341.933684 # 1-0.0115911.247894 -1.701154 -0.0522550.835968 # 2-0.936263 -0.3130181.5770310.1080070.116402 # 3-0.0327210.6885060.001428 -0.5500610.525248print hier_df.groupby(level='cty',axis=1).count()#axis=1列分组,count是取总计的个数 #输出结果如下: # ctyJPUS # 023#US的0的有3个 # 123 # 223 # 323

2.数据聚合
对于聚合,指的是任何能够从数组产生标量值的数据转换过程。例如:mean,count,min以及sum等。
许多常见的聚合运算都有就地计算数据集统计信息的优化实现,然而,并不是只能使用这些方法。
可以自己定义聚合运算,还可以调用分组对象上已经定义好的任何方法。例如,quantile可以计算
Series或DataFrame列的样本分位数。

import numpy as np from pandas import DataFrame,Series import pandas as pddf=DataFrame({'key1':['a','a','b','b','a'], 'key2':['one','two','one','two','one'], 'data1':np.random.randn(5), 'data2':np.random.randn(5)}) # print df #输出结果如下: #data1data2 key1 key2 # 0 -0.0656960.404059aone # 1 -1.202053 -0.303539atwo # 21.9630360.989266bone # 30.461733 -0.061501btwo # 41.1988642.111709aone grouped=df.groupby('key1') print grouped#返回groupby对象 print grouped['data1'].quantile(0.9) #算出按key1分组后的data1的分位数(0.9)的值 #输出结果如下: # key1 # a0.143167 # b1.407836 #注意:虽然quantile并没有明确地实现于GroupBy,但它是一个Series方法,所以这里是能用的。 #实际上,GroupBy会高效地对Series进行切片,然后对各片调用piece.quantile(0.9),最后将 #这些结果组装成最终结果。

(1)作用自定义的聚合函数:只需将其传入aggregate或agg方法即可
import numpy as np from pandas import DataFrame,Series import pandas as pddf=DataFrame({'key1':['a','a','b','b','a'], 'key2':['one','two','one','two','one'], 'data1':np.random.randn(5), 'data2':np.random.randn(5)}) # print df #输出结果如下: #data1data2 key1 key2 # 0 -0.0656960.404059aone # 1 -1.202053 -0.303539atwo # 21.9630360.989266bone # 30.461733 -0.061501btwo # 41.1988642.111709aone grouped=df.groupby('key1') print grouped#返回groupby对象def peak_to_peak(arr): return arr.max()-arr.min() print grouped.agg(peak_to_peak) #输出结果如下: #data1data2 # key1 # a1.6723001.298854 # b0.3154542.404649 #注意:自定义函数要比那些经过优化的函数慢得多,这是因为在构造中间分组数据块时存在非常大的开销(函数调用/数据重排等)#有些方法(如describe)也是可以用在这里的。即使严格来讲,它并非聚合运算: print grouped.describe()

经过优化的groupby的方法:
函数名说明
count分组中非NA值的数量
sum非NA值的和
mean非NA值的平均值
median非NA值的算术中位数
std、var无偏(分母为n-1)标准差和方差
min、max非NA值的最小值、最大值
prod非NA值的积
first、last第一个和最后一个非NA值
import numpy as np from pandas import DataFrame,Series import pandas as pd#以餐馆小费的数据集为例 #(1)通过read_csv加载之后,添加了一个表示小费比例的列tip_pct tips=pd.read_csv('ch08/tips.csv') tips['tip_pct']=tips['tip']/tips['total_bill'] print tips[:6]

面向列的多函数应用:
我们已经看到,对Series或DataFrame列的聚合运算其实就是使用aggregate(自定义函数)或调用mean,std之类的方法。
你可能希望对不同的列使用不同的聚合函数,或一次应用多个函数。

import numpy as np from pandas import DataFrame,Series import pandas as pd#根据sex和smoker对tips进行分组: grouped=tips.groupby(['sex','smoker']) #可以将函数名以字符串的形式传入: grouped_pct=grouped['tip_pct'] print grouped_pct.agg('mean') #如果传入一组函数或函数名,得到的DataFrame的列就会以相应的函数命名 print grouped_pct.agg(['mean','std',peak_to_peak])#mean,std是优化后的聚合函数,peak_to_peak是前面自定义的函数

pandas 数据聚合与分组运算
文章图片

import numpy as np from pandas import DataFrame,Series import pandas as pd#(1)如果传入的是一个由(name,function)元组组成的列表,则各元组的第一个元素就会被用作DataFrame的列名 print grouped_pct.agg([('foo','mean'),('bar',np.std)]) #输出结果如下: #foobar # sexsmoker # FemaleFalse0.150.03 #True0.180.07 # MaleFalse0.160.04 #True0.150.09#(2)对于DataFrame,还可以定义一组应用于全部列的函数,或不同的列应用不同的函数。 #我们想要对tip_pct和total_bill列计算三个统计信息 functions=['count','mean','max'] result=grouped['tip_pct','total_bill'].agg(functions) print result #输出结果如下: #tip_pcttotal_bill #countmeanmaxcountmeanmax # sexsmoker # FemaleFalse540.150.255418.135.8 #True330.180.413317.944.3 # MaleFalse970.160.299719.748.3 #True600.150.716022.250.8 #如上所见,结果DataFrame拥有层次化的列,这相当于分别对各列进行聚合,然后用concat将结果组装到一起(列名用作keys参数)。 print result['tip_pct'] #取出tip_pct的数据#(3)跟前面一样,这里也可以传入带有自定义名称的元组列表: ftuples=[('Durchschnitt','mean'),('Abweichung',np.var)] #Durchschnitt自定义名,mean该列要用到的方法 print grouped['tip_pct','total_bill'].agg(ftuples) #输出结果如下: #tip_pcttotal_bill # #DurchschnittAbweichungDurchschnittAbweichung # # sexsmoker # # FemaleFalse0.150.0318.153.0 # #True0.180.0717.984.4 # # MaleFalse0.160.0419.776.1 # #True0.150.092298#(4)假设你想要对不同的列应用不同的函数。具体的办法是向agg传入一个从列名映射到函数的字典: grouped.agg({'tip':np.max,'size':'sum'}) #tip是列名,np.max是方法 #输出结果如下: #sizetip # sexsmoker # FemaleFalse1405.2 #True740.07 # MaleFalse2630.04 #True1500.09 grouped.agg({'tip_pct':['min','max','mean','std'], 'size':'sum'}) #tip_pct有min,max,mean,std方法,size有sum方法 #输出结果如下: #tip_pctsize #minmeanmaxstdsum # sexsmoker # FemaleFalse0.050.150.250.03140 #True0.060.180.410.0774 # MaleFalse0.070.160.290.04263 #True0.030.150.710.08150 #只有将多个函数应用到至少一列时,DataFrame才会拥有层次化的列

以“无索引”的形式返回聚合数据:
到目前为止,所有示例中的聚合数据都有由唯一的分组键组成的索引(可能还是层次化的)。由于并不总是需要如此,
所以你可以向groupby传入as_index=False以禁用该功能:
print tips.groupby(['sex','smoker'],as_index=False).mean() #输出结果如下: # # sexsmokertotal_billtipsizetip_pct # FemaleFalse18.12.772.580.15 #True17.12.932.410.18 # MaleFalse19.073.162.290.16 #True22.033.152.510.15 #当然,对结果调用reset_index也能得到这种形式的结果。

3.分组级运算和转换
聚合只不过是分组运算的其中一种而己。它是数据转换的一个特例。下面将介绍transform和apply方法,它们能
够执行更多其他的分组运算。
(1)transform:会将一个函数应用到各个分组,然后将结果放置到各个分组的适当的位置上。
假设我们想要为一个DataFrame添加一个用于存放各索引分组平均值的例。一个办法是先聚合再合并:

df=DataFrame({'key1':['a','a','b','b','a'], 'key2':['one','two','one','two','one'], 'data1':np.random.randn(5), 'data2':np.random.randn(5)}) print df #输出结果如下: #data1data2 key1 key2 # 00.9993220.005281aone # 11.057139 -0.322877atwo # 21.130281 -0.272296bone # 32.328475 -0.497984btwo # 4 -0.364299 -0.701872aone#(1)按key1分组后取mean计算。因为k1可分为a,b两组,a,b两组中各有data1,data2 k1_means=df.groupby('key1').mean().add_prefix('mean_') #因为按key1分组,添加mean_ print k1_means #输出结果如下: #mean_data1mean_data2 # key1 # a1.6853050.720963 # b-0.671712-0.444973#(2)聚合 print pd.merge(df,k1_means,left_on='key1',right_index=True) #df,k1_means左连接 #输出结果如下: #data1data2 key1 key2mean_data1mean_data2 # 01.3008021.159461aone0.8519290.649493 # 11.5421130.605346atwo0.8519290.649493 # 4 -0.2871290.183673aone0.8519290.649493 # 2 -0.923089 -0.248955bone-0.507969-0.464783 # 3 -0.092848 -0.680611btwo-0.507969-0.464783 #注意:虽然这样也行,但是不太灵活,你可以将该过程看做利用np.mean函数对两个数据列进行转换。#(3)这次我们在GroupBy上使用transform方法。 # 不难看出,transform会将一个函数应用到各个分组,然后将结果放置到适当的位置上。 # 如果各分组产生的是一个标量值,则该值就会被广播出去。 key=['one','two','one','two','one'] print df.groupby(key).mean() #输出结果如下: #data1data2 # one -0.860574 -0.130721 # two -0.5639330.581333 print df.groupby(key).transform(np.mean) #输出结果如下: #data1data2 # 00.174402 -0.123673 # 10.7046850.599223 # 20.174402 -0.123673 # 30.7046850.599223 # 40.174402 -0.123673def demean(arr): return arr-arr.mean() demeaned=df.groupby(key).transform(demean) print demeaned #输出结果如下: #data1data2 # 0 -0.600930 -0.755259 # 1 -0.9601310.548909 # 20.0672990.460494 # 30.960131 -0.548909 # 40.5336310.294765 #检查一下demeaned现在的分组平均值是否是0: print demeaned.groupby(key).mean() #输出结果如下: #data1data2 # one -3.700743e-17 -1.850372e-17 # two0.000000e+00 -1.387779e-17

(2)apply:一般性的“拆分-应用-合并”
跟aggregate一样,transform也是一个有着严格条件的特殊函数:传入的函数只能产生两种结果,要么产生
一个可以广播的标量值(np.mean),要么产生一个相同大小的结果数组。apply会将待处理的对象拆分成多个片段,
然后对各片段调用传入的函数,最后将各片段组合到一起。
import numpy as np from pandas import DataFrame,Series import pandas as pd#原先那个小费数据集,假设你想要根据分组选出最高的5个tip_pct值。 #(1)编写一个选取指定列具有最大值的行的函数 def top(df,n=5,column='tip_pct'): #在批定列找出最大值,然后把这个值所在的行选取出来 return df.sort_index(by=column)[-n:] #按列排序,取[-n:]倒数第n个取出后面n个数 print top(tips,n=6) #输出结果如下: #total_billtipsexsmokerdaytimesizetip_pct # 109144FemaleTrueSatDinner20.27 # 183236.5MaleTrueSunDinner40.28 # 232113.3MaleFalseSatDinner20.29 # 6731FemaleTrueSatDinner10.32 # 1789.64FemaleTrueSunDinner20.41 # 1727.255.15MaleTrueSunDinner20.71(2)现在,如果对smoker分组并用该函数调用apply,就会得到: tips.groupby('smoker').apply(top) #返回结果如下: #total_billtipsexsmokerdaytimesizetip_pct # smoker # No8824.715.85MaleFalseThurLunch20.23 #18520.695MaleFalseSunDinner50.24 #18520.695MaleFalseSunDinner50.24 #18520.695MaleFalseSunDinner50.24 # Yes 10914.34FeMaleTrueSatDinner40.28 #10914.34FeMaleTrueSatDinner40.28 #10914.34FeMaleTrueSatDinner40.28 #10914.34FeMaleTrueSatDinner40.28#(3)top函数在DataFrame的各个片段上调用,然后结果由pandas.concat组装到一起,并以分组名称进行了标记。 #于是,最终结果就有了一个层次化索引,其内层索引值来自DataFrame. print tips.groupby(['smoker','day']).apply(top,n=1,column='total_bill') #输出结果如下: #total_billtipsexsmokerdaytimesizetip_pct # smoker day #NoFri24.715.85MaleFalseThurLunch20.23 #Sat20.695MaleFalseSunDinner50.24 #YesFri14.34FeMaleTrueSatDinner40.28 #Sat14.34FeMaleTrueSatDinner40.28 #Sun14.34FeMaleTrueSatDinner40.28 result=tips.groupby('smoker')['tip_pct'].describe() print result #输出结果如下: # smoker # Nocount151 #mean0.159 #std #min #25% #50% #75% #max #Yescount #mean #std #min #25% #50% #75% #max print result.unstack('smoker') #行,列互换 #输出结果如下: #smokerNoYes # count15193 # mean0.150.16 # std0.030.08 # min0.050.03 # 25%0.130.10 # 50%0.150.15 # 75%0.180.18 # max0.280.71#(4)GroupBy中,当你调用诸如describe之类的方法时,实际上只应用了下面两条代码的快捷方式而己: f=lambda x:x.describe() print grouped.apply(f)

禁止分组键:group_keys=False
分组键会跟原始对象的索引共同构成结果对象中的层次化索引。将group_keys=False传入groupby即可禁止该效果:

#分组键会跟原始对象的索引共同构成结果对象中的层次化索引。将group_keys=False传入groupby即可禁止该效果: def top(df,n=5,column='tip_pct'): return df.sort_index(by=column)[-n:] print tip.groupby('smoker',group_keys=False).apply(top) #输出结果如下: #total_billtipsexsmokerdaytimesizetip_pct # 109144FemaleTrueSatDinner20.27 # 183236.5MaleTrueSunDinner40.28 # 232113.3MaleFalseSatDinner20.29 # 6731FemaleTrueSatDinner10.32 # 1789.64FemaleTrueSunDinner20.41 # 1727.255.15MaleTrueSunDinner20.71

分位数和桶分析:
pandas有一些能根据指定面元或样本分位数将数据拆分成多块的工具(比如cut和qcut).将这些函数跟groupby结合起来,
就能非常轻松地实现对数据集的桶(bucket)或分位数(quantile)分析了。
下面的例子是以随机数据集为例,我们利用cut将其装入长度相等的桶中:每个等矩离区间就是一个桶
import numpy as np from pandas import DataFrame,Series import pandas as pd#(1)cut:bins为1表示整数--将X划分为多个个等间矩的区间 frame=DataFrame({'data1':np.random.randn(1000), 'data2':np.random.randn(1000)}) factor=pd.cut(frame.data1,4) #将frame.data1划分为等间矩的区间,4是precision=4精确度 print factor[:10] #输出结果如下: # 0(1.465, 3.234] # 1(-2.074, -0.304] # 2(-0.304, 1.465] # 3(-2.074, -0.304] # 4(-2.074, -0.304] # 5(-2.074, -0.304] # 6(-2.074, -0.304] # 7(-0.304, 1.465] # 8(-0.304, 1.465] # 9(-2.074, -0.304] # Name: data1, dtype: category # Categories (4, interval[float64]): [(-3.85, -2.074] < (-2.074, -0.304] < (-0.304, 1.465] < #(1.465, 3.234]]#(2)由于cut返回的Factor对象可直接用于groupby. #因此,我们可以像下面这样对data2做一些统计计算: def get_stats(group): return {'min':group.min(),'max':group.max(), 'count':group.count(),'mean':group.mean()} #data2按factor(等矩离区间)分组,factor的等矩离区间:但是每次运行一次就是随机数据,所以每次运行会不一样。 # [(-3.85, -2.074] < (-2.074, -0.304] < (-0.304, 1.465] <(1.465, 3.234]] grouped=frame.data2.groupby(factor) #分组后用apply调用方法,unstack再将行列转换 print grouped.apply(get_stats).unstack() #输出结果如下: #countmaxmeanmin # data1 # (-3.85, -2.074]24.01.771732 -0.037332 -1.769997 # (-2.074, -0.304]356.03.7810410.060009 -3.552757 # (-0.304, 1.465]549.03.146836 -0.057476 -3.123641 # (1.465, 3.234]71.02.765544 -0.056112 -1.968819#(3)上面的都是长度相等的桶。要根据样本分位数得到大小相等的桶,使用qcut.传入labels=False即可只获取分位数的编号。 #返回分位数编号 grouping=pd.qcut(frame.data1,10,labels=False) #qcut与cut区别是qcuat是根据这些值的频率来选择的,而cut是根据值来选择的 # print grouping grouped=frame.data2.groupby(grouping) print grouped.apply(get_stats).unstack() #输出结果如下: #countmaxmeanmin # data1 # 0100.02.8359620.031973 -1.992897 # 1100.03.0495670.071329 -2.239653 # 2100.03.0325950.123676 -2.090986 # 3100.01.662204 -0.103337 -3.066419 # 4100.02.6606740.032828 -2.237807 # 5100.02.8341810.201375 -1.970967 # 6100.02.444546 -0.129342 -2.436978 # 7100.02.2604550.066945 -1.728250 # 8100.02.3393730.035168 -2.050225 # 9100.02.1024980.012707 -3.043281

“长度相等的桶”指的是“区间大小相等”,“大小相等的桶”指的是“数据点数量相等”

示例:用特定于分组的值填充缺失值
对于缺失数据的清理工作,有时你会用dropna将其滤除,而有时则可能会希望用一个固定值或由数据集本身所衍生出来的
值去填充NA值。这时就得使用fillna这个工具了。在下面的例子中,用平均值去填充NA值。
import numpy as np from pandas import DataFrame,Series import pandas as pds=Series(np.random.randn(6)) s[::2]=np.nan#s[::2]从头到尾,步长为2 print s #输出结果如下: # 0NaN # 1-2.199721 # 2NaN # 3-0.286466 # 4NaN # 5-0.660635 # dtype: float64 print s.fillna(s.mean()) #输出结果如下: # 0-0.570229 # 1-0.423539 # 2-0.570229 # 30.055381 # 4-0.570229 # 5-1.342529 # dtype: float64#假设需要对不同的分组填充不同的值。只需将数据分组,并使用apply和一个能够对各数据块调用fillna的函数即可。 #下面是一些有关美国几个州的示例数据,这些州又被分为东部和西部: states=['Ohio','New York','Vermont','Florida', 'Oregon','Nevada','California','Idaho'] group_key=['East']*4+['West']*4 print group_key #输出结果: # ['East', 'East', 'East', 'East', 'West', 'West', 'West', 'West'] data=https://www.it610.com/article/Series(np.random.randn(8),index=states) data[['Vermont','Nevada','Idaho']]=np.nan print data #输出结果如下: # Ohio0.640267 # New York-0.313729 # VermontNaN # Florida2.214834 # Oregon1.363352 # NevadaNaN # California0.265563 # IdahoNaN # dtype: float64 print data.groupby(group_key).mean() #输出结果如下: # Ohio-0.657227 # New York0.502025 # VermontNaN # Florida0.045405 # Oregon0.960563 # NevadaNaN # California-0.366368 # IdahoNaN # dtype: float64 # East-0.036599 # West0.297098 # dtype: float64#可以用分组平均值去填充NA值,如下所示: fill_mean=lambda g:g.fillna(g.mean()) #每个空值用g.fillna(g.mean)来填充 print data.groupby(group_key).apply(fill_mean) #按group_key分组,分完组调用fill_mean的lambda方法 #即用mean填充Nan值 # Ohio0.980489 # New York1.151041 # Vermont0.786433 # Florida0.227769 # Oregon-1.550078 # Nevada-1.025264 # California-0.500450 # Idaho-1.025264 # dtype: float64#此外,也可以在代码中预定义各组的填充值。由于分组具有一个name属性,所以我们可以拿来用一下。 fill_values={'East':0.5,'West':-1} fill_func=lambda g:g.fillna(fill_values[g.name]) #每个空值用fill_values的name来填充 print data.groupby(group_key).apply(fill_func) #group_key分组,分完后调用函数fill_func来填充 #输出结果如下: # Ohio0.095284 # New York-0.232429 # Vermont0.500000 # Florida0.039151 # Oregon1.393373 # Nevada-1.000000 # California0.756425 # Idaho-1.000000 # dtype: float64

示例:随机取样和排列
假设你想要从一个大数据集中随机抽取样本以进行蒙特卡罗模拟或其他分析工作。“抽取”的方式有很多,
其中一些的效率会比其他的高很多。一个办法是:选取np.random.permutation(N)的前K个元素,
其中N为完整数据的大小,K为期望的样本大小。
下面是构造一副英语型扑克牌的一个方式:随机抽取take,排列np.random.permutation
import numpy as np from pandas import DataFrame,Series import pandas as pd#(1)红桃(Hearts)、黑桃(Spades)、梅花(Clubs)、方片(Diamonds) suits=['H','S','C','D'] card_val=(range(1,11)+[10]*3)*4 #1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 4组这个数 base_names=['A']+range(2,11)+['J','K','Q'] print base_names #输出结果如下:['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'K', 'Q'] cards=[] for suit in ['H','S','C','D']: cards.extend(str(num)+suit for num in base_names)#num为base_name的值,str(num)+suit值 print cards #输出结果如下:'AH', '2H', '3H', '4H', '5H', '6H', '7H', '8H', '9H', '10H', 'JH', 'KH', 'QH',H/S/C/D4组与base_name组合 deck=Series(card_val,index=cards) print deck #输出结果如下:index为H组合的如下,还有其它3组,AS..,AC..,AD... # AH1#AH中A是牌名,H是花色1是牌面的点数 # 2H2 # 3H3 # 4H4 # 5H5 # 6H6 # 7H7 # 8H8 # 9H9 # 10H10 # JH10 # KH10 # QH10#(2)现在我有了一个长度为52的Series,其索引为牌名,值则是21点或其它游戏中用于计分的点数,A的点数为1 print deck[:13] #取前13个数,即结果是上面展现的值#(3)take从列表或者是Series中抽取,permutation(int)返回一个一维从0-9的序列的随机排列 #现在从整副牌中抽出5张: def draw(deck,n=5): return deck.take(np.random.permutation(len(deck))[:n]) #permutation随机排列的抽取5个数据 print draw(deck) #输出结果如下: # 4H4 # JH10 # 7D7 # 10H10 # 9H9 # dtype: int64#(4)假设想要从每种花色中随机抽取两张牌。由于花色是牌名的最后一个字符。所以我们可以据此进行分组 get_suit=lambda card:card[-1]#card是牌名如:AH,card[-1]取牌名的最后一个字符是花色 print deck.groupby(get_suit).apply(draw,n=2) #按card[-1]花色分组,分好后抽取2张 #输出结果如下: # CQC10 #7C7 # DQD10 #2D2 # H3H3 #2H2 # S4S4 #10S10 # dtype: int64#(5)另一种办法: print deck.groupby(get_suit,group_keys=False).apply(draw,n=2) #输出结果如下: # 10C10 # 5C5 # 8D8 # 5D5 # 2H2 # 4H4 # 6S6 # 10S10 # dtype: int64

示例:分组加权平均数和相关系数
根据groupby的“拆分(groupBy)-应用(apply)-合并(merge,concate)”,DataFrame的列与列之间或
两个Series之间的运算(比如分组加权平均)成为一种标准作业。以下面这个数据集为例,它含有分组键、
值以及一些权重值:
import pandas as pd import numpy as np from pandas import DataFrame,Seriesdf=DataFrame({'category':['a','a','a','a','b','b','b','b'], 'data':np.random.randn(8), 'weight':np.random.rand(8)}) print df #输出结果如下: #categorydataweight # 0a0.6136570.723545 # 1a0.9737320.845376 # 2a0.2010820.634316 # 3a0.5979910.833267 # 4b -1.0178480.355760 # 5b -0.0179740.963866 # 6b -0.3524860.076372 # 7b -0.9752370.147921#然后可以利用category计算分组加权平均数: grouped=df.groupby('category') #按category分组 get_wavg=lambda g: np.average(g['data'],weights=g['weights']) #g['data']的平均值,weights加上权重的平均值 print grouped.apply(get_wavg) #下面举一个股票的收盘 close_px=pd.read_csv('cho9/stock_px.csv',parse_dates=True,index_col=0) print close_px[-4:] #计算收益率(通过百分比变化计算): rets=close_px.pct_change().dropna() spx_corr=lambda x:x.corrwith(x['SPX']) by_year=rets.groupby(lambda x:x.year) print by_year.apply(spx_corr)#还可以计算列与列之间的相关系数: by_year.apply(lambda g:g['AAPL'].corr(g['MSFT']))

示例:面向分组的线性回归
顺着上一例子继续,你可以用groupby执行更为复杂的分组统计分析,只要函数返回的是pandas对象或标量值即可。
例:我可以定义下面这个regress函数(利用statsmodels)库对各数据块执行普通最小二乘法回归(OLS)。
import statesmodels.api as sm 利用sm.OLS(Y,X).fit()最小二乘法

#利用OLS二乘法线性回归 import statesmodels.api as sm def regress(data,yvar,xvars): Y=data[yvar] X=data[xvars] X['intercept']=1. result=sm.OLS(Y,X).fit() return result.params#现在,为了按年计算AAPL对SPX收益率的线性回归: print by_year.apply(regress,'AAPL',['SPX'])

透视表和交叉表:透视表pivot_table用于分组求和(例求每个月买出的帽子总和)
透视表是各种电子表格程序和其它分析软件中一种常见的数据汇总工具。它根据一个或多个键对数据进行聚合,并根据
行和列上的分组键将数据分配到各个矩形区域中。
在python和pandas中,可以用groupby功能以及(能够利用层次化索引的)重塑运算制作透视表。
DataFrame有一个pivot_table方法,还有一个pand.pivot_table函数。pivot_table还可以添加分项小计(也叫margins)
import pandas as pd import numpy as np from pandas import DataFrame,Series#(1)回到小费数据集,假设我想要根据sex和smoker计算分组平均数(pivot_table的默认聚合类型),并将sex和smoker放到行上 print tips.pivot_table(rows=['sex','smoker']) #输出结果如下: #sizetiptip_pcttotal_bill # sexsmoker # FemaleNo2.592.770.1518.10 #Yes2.242.930.1817.97 # MaleNo2.713.110.1619.79 #Yes2.503.050.1522.97 #上面的对于groupby来说也是很简单就可以做到的事情。#(2)现在我们只想聚合tip_pct和size,而且想根据day进行分组。我将smoker放到列上,把day放到行上。 tips.pivot_table(['tip_pct','size'],rows=['sex','day']) #输出结果如下: # tip_pctsize # smokerNoYesNoYes # sexday # FemaleFri0.160.22.52.0 #SAT0.140.162.32.2 # MaleFri0.130.142.02.1 #SAT0.160.132.652.62#(3)还可以对这个表作进一步处理,传入margins=True添加分项小计。这将会添加标签为All的行和列。 #其值对应于单个等级中所有数据的分组统计。 # 在下面的这个例子中,All值为平均数:不单独考虑烟民与非烟民(All列),不单独考虑行分组两个级别 #中的任何单项(All)行。 tips.pivot_table(['tip_pct','size'],rows=['sex','day'], cols='smoker',margins=True) #输出结果如下: # tip_pctsize # smokerNoYesNoYesAll # sexday # FemaleFri2.162.22.52.00.199 #SAT2.142.162.32.20.156 # MaleFri2.132.142.02.10.180 #SAT2.162.132.652.620.165 # All2.662.402.562.220.160#(4)要使用其它的聚合函数,将其传给aggfunc即可 #例:使用count或len可以得到有关分组大小的交叉表: tips.pivot_table('tip_pct',rows=['sex','smoker'],cols='day', aggfunc=len,margins=True) #行:'sex','smoker'; 列:'day'; margins=True分项小计All; len方法 #输出结果如下: # dayFriSatSunThurAll # sexsmoker # FemaleNo213142554 #Yes7154733 # MaleNo232432097 #Yes827151060 # All19877662244#(5)如果存在空的组合(也就是NA),可能会希望设置一个fill_value: tips.pivot_table('size',rows=['time','sex','smoker'], cols='day',aggfunc='sum',fill_value=https://www.it610.com/article/0) #输出结果如下: # dayFriSatSunThur # timesexsmoker # Dinner FemaleNo230432 #Yes833100 #MaleNo2851430 #Yes8271510 # Lunch FemaleNo30060 #Yes60017 #MaleNo00050 #Yes50023

pivot_table的参数
参数名说明
values待聚合的列的名称。默认聚合所有数值列
rows用于分组的列名或其他分组键,出现在结果透视表的行
cols用于分组的列名或其他分组键,出现在结果透视表的列
aggfunc聚合函数或函数列表,默认为'mean'.可以是任何对groupby有效的函数
fill_value用于替换结果表中的缺失值
margins添加行,列小计和总计,默认为False
交叉表:crosstable(两个变量)
pandas 数据聚合与分组运算
文章图片

import pandas as pd import numpy as np from pandas import DataFrame,Series#交叉表:crosstab #交叉表是一种用于计算分组频率的特殊透透表。 #这个范例数据很典型,取自交驻表的Wikipedia页: data=https://www.it610.com/article/DataFrame({'Sample':[1,2,3,4,5,6,7,8,9,10], 'Gender':['Female','Male','Female','Male','Male','Male', 'Female','Female','Male','Female'], 'Handedness':['Right-handed','Left-handed','Right-handed','Right-handed', 'Left-handed','Right-handed','Right-handed','Left-handed', 'Right-handed','Right-handed']}) # print data #输出结果如下: #GenderHandednessSample # 0FemaleRight-handed1 # 1MaleLeft-handed2 # 2FemaleRight-handed3 # 3MaleRight-handed4 # 4MaleLeft-handed5 # 5MaleRight-handed6 # 6FemaleRight-handed7 # 7FemaleLeft-handed8 # 8MaleRight-handed9 # 9FemaleRight-handed10#(1)假设我们想要根据性别和用手习惯对这段数据进行统计汇总。 #虽然可以用pivot_table实现该功能,但是用pandas.crosstab函数更方便 print pd.crosstab(data.Gender,data.Handedness,margins=True) #index,columns,margins #输出结果如下: # HandednessLeft-handedRight-handedAll # Gender # Female145 # Male235 # All3710#(2)crosstab的前两个参数可以是数组、Series或数组列表。再比如对小费数据集: print pd.crosstab([tips.time,tips.day],tips.smoker,margins=True) #输出结果如下: # smokerNoYesAll # timeday # Dinner Fri3912 #Sat454287 #Sun571976 #Thur101 # LunchFri167 #Thur441761 # All15193244

示例:2012联邦选举委员会数据库
用到了:mapping,get,pivot_table

import pandas as pd import numpy as np from pandas import DataFrame,Series import os import matplotlib.pyplot as plt#获取文件的路径 path1=os.path.abspath('..') #获取当前脚本所在路径的上一级路径 print path1#(1)先将数据加载进来: fec=pd.read_csv(path1+'/pydata-book-2nd-edition/datasets/fec/P00000001-ALL.csv') print fec.ix[123456] #ix是取行索引ix[2]按下标取,下标从0开始算 #输出结果如下: # cmte_idC00431445 # cand_idP80003338 # cand_nmObama, Barack # contbr_nmELLMAN, IRA # contbr_cityTEMPE # contbr_stAZ # contbr_zip852816719 # contbr_employerARIZONA STATE UNIVERSITY # contbr_occupationPROFESSOR # contb_receipt_amt50 # contb_receipt_dt01-DEC-11 # receipt_descNaN # memo_cdNaN # memo_textNaN # form_tpSA17A # file_num772372 # Name: 123456, dtype: object#(2)上面的数据中没有党派信息,因此最好把它加进去,通过unique,可以获取全部的候选人名单。 unique_cands=fec.cand_nm.unique() #取fec(Series)中的cand_nm字段的值 print unique_cands #输出结果如下: # ['Bachmann, Michelle' 'Romney, Mitt' 'Obama, Barack' #"Roemer, Charles E. 'Buddy' III" 'Pawlenty, Timothy' 'Johnson, Gary Earl' #'Paul, Ron' 'Santorum, Rick' 'Cain, Herman' 'Gingrich, Newt' #'McCotter, Thaddeus G' 'Huntsman, Jon' 'Perry, Rick'] print unique_cands[2] #输出结果如下: # Obama, Barack#(3)最简单的办法是利用字典说明党派关系: parties={'Bachmann, Michelle':'Republican', 'Romney, Mitt':'Republican', 'Obama, Barack':'Democrat', "Roemer, Charles E. 'Buddy' III":'Republican', 'Pawlenty, Timothy':'Republican', 'Johnson, Gary Earl':'Republican', 'Paul, Ron':'Republican', 'Santorum, Rick':'Republican', 'Cain, Herman':'Republican', 'Gingrich, Newt':'Republican', 'McCotter, Thaddeus G':'Republican', 'Huntsman, Jon':'Republican', 'Perry, Rick':'Republican'} #现在通过这个映射以及Series对象的map方法,你可以根据候选人姓名得到一组党派信息 print fec.cand_nm[123456:123461] #输出结果如下: # Obama, Barack # 123456Obama, Barack # 123457Obama, Barack # 123458Obama, Barack # 123459Obama, Barack # 123460Obama, Barack # Name: cand_nm, dtype: object#(4)将其添加为一新列,使用map映射,有几个字段都映射为同一个,则values值相加 fec['party']=fec.cand_nm.map(parties) #fec.cand_nm有两个值Obama, Barack,map(parties)映射后就是找出Obama, Barack对应parties的党派 print fec['party'].value_counts() #输出结果如下: # Democrat593746 # Republican407985 # Name: party, dtype: int64#(5)这里有两个需要注意的地方。第一,该数据既包括赞助也包括退款(负的出资额) #value_counts对值进行统计 print (fec.contb_receipt_amt>0).value_counts() #输出结果如下: # True991475 # False10256 # Name: contb_receipt_amt, dtype: int64#(6)为了简化分析过程,限定该数据集只能有正的出资额: fec=fec[fec.contb_receipt_amt>0] #由于Barack Obama和Mitt Romney是最主要的两名候选人,所以专门准备了一个子集,只包含针对他们两人的竞选活动的赞助商用: fec_mrbo=fec[fec.cand_nm.isin(['Obama,Barack','Romney,Mitt'])] # print fec_mrbo[-2:]#(7)根据职业和雇主统计赞助信息: #例如律师们更倾向于民主党 #首先,根据职业计算出资总额 print fec.contbr_occupation.value_counts()[:10] #统计出不同职业的总计 #输出结果如下: # RETIRED233990 # INFORMATION REQUESTED35107 # ATTORNEY34286 # HOMEMAKER29931 # PHYSICIAN23432 # INFORMATION REQUESTED PER BEST EFFORTS21138 # ENGINEER14334 # TEACHER13990 # CONSULTANT13273 # PROFESSOR12555 # Name: contbr_occupation, dtype: int64#(8)不难看出,许多职业都涉及相同的基本工作类型,或者同一样东西有多种变体。 #下面代码可以清理一些这样的数据(将一个职业信息映射到另一个)。注意:这里巧妙地利用了dict.get,它允许没有映射 #关系的职业也能"通过": occ_mapping={'INFORMATION REQUESTED':'NOT PROVIDED', 'INFORMATION REQUESTED PER BEST EFFORTS':'NOT PROVIDED', 'INFORMATION REQUESTED(BEST EFFORTS)':'NOT PROVIDED', 'C.E.O':'CEO'} #如果没有提供相关映射,则返回x f=lambda x:occ_mapping.get(x,x) #occ_mapping,get如有则返回x的mapping的值,如果没有则返回原值 fec.contbr_occupation=fec.contbr_occupation.map(f) print fec.contbr_occupation.value_counts()[:10] #将前面的3个REQUESTED都映射为'NOT PROVIDED' #输出结果如下: # RETIRED233990 # NOT PROVIDED56245 # ATTORNEY34286 # HOMEMAKER29931 # PHYSICIAN23432 # ENGINEER14334 # TEACHER13990 # CONSULTANT13273 # PROFESSOR12555 # NOT EMPLOYED9828 # Name: contbr_occupation, dtype: int64#(9)下面对雇主也进行了同样的处理: print fec.contbr_employer.value_counts()[:10] #输出结果如下: # RETIRED206675 # SELF-EMPLOYED94505 # NOT EMPLOYED45877 # INFORMATION REQUESTED36135 # SELF24385 # INFORMATION REQUESTED PER BEST EFFORTS22260 # NONE19929 # HOMEMAKER18269 # SELF EMPLOYED6274 # REQUESTED4233 # Name: contbr_employer, dtype: int64 emp_mapping={'INFORMATION REQUESTED PER BEST EFFORTS':'NOT PROVIDED', 'INFORMATION REQUESTED':'NOT PROVIDED', 'SELF':'SELF-EMPLOYED', 'SELF EMPLOYED':'SELF-EMPLOYED',} #如果没有提供相关的映射,则返回x:用get f=lambda x:emp_mapping.get(x,x) fec.contbr_employer=fec.contbr_employer.map(f) print fec.contbr_employer.value_counts()[:10] #输出结果如下: # RETIRED206675 # SELF-EMPLOYED125164 # NOT PROVIDED58396 # NOT EMPLOYED45877 # NONE19929 # HOMEMAKER18269 # REQUESTED4233 # UNEMPLOYED2514 # US ARMY1817 # STUDENT1786 # Name: contbr_employer, dtype: int64#(10)可以通过pivot_table根据党派和职业对数据进行聚合,然后过滤掉总出资额不足200万美元的数据: by_occupation=fec.pivot_table('contb_receipt_amt',index='contbr_occupation', columns='party',aggfunc='sum') print by_occupation #输出结果如下: # partyDemocratRepublican # contbr_occupation #MIXED-MEDIA ARTIST / STORYTELLER100.0NaN #AREA VICE PRESIDENT250.0NaN #RESEARCH ASSOCIATE100.0NaN #TEACHER500.0NaN #THERAPIST3900.0NaN # 'MIS MANAGERNaN177.60 # (PART-TIME) SALES CONSULTANT & WRITERNaN285.00 # (RETIRED)NaN250.00 # -5000.02114.80 # --NaN75.00 over_2mm=by_occupation[by_occupation.sum(1)>2000000] print over_2mm #输出结果如下: # partyDemocratRepublican # contbr_occupation # ATTORNEY11141982.977.477194e+06 # C.E.O.1690.002.592983e+06 # CEO2074284.791.640758e+06 # CONSULTANT2459912.712.544725e+06 # ENGINEER951525.551.818374e+06 # EXECUTIVE1355161.054.138850e+06 # HOMEMAKER4248875.801.363428e+07 # INVESTOR884133.002.431769e+06 # LAWYER3160478.873.912243e+05 # MANAGER762883.221.444532e+06 # NOT PROVIDED4866973.962.023715e+07 # OWNER1001567.362.408287e+06 # PHYSICIAN3735124.943.594320e+06 # PRESIDENT1878509.954.720924e+06 # PROFESSOR2165071.082.967027e+05 # REAL ESTATE528902.091.625902e+06 # RETIRED25305116.382.356124e+07 # SELF-EMPLOYED672393.401.640253e+06#(11)把这些数据做成柱状图: over_2mm.plot(kind='barh') plt.show()

pandas 数据聚合与分组运算
文章图片

对各党派总出资额最高的职业:继续上面的例子
pandas中的order:是对值进行排序,或者用sort_values也是对值进行排序
pandas中的sort:是对行或列进行排序
#获取文件的路径 path1=os.path.abspath('..') #获取当前脚本所在路径的上一级路径 print path1#(1)先将数据加载进来: fec=pd.read_csv(path1+'/pydata-book-2nd-edition/datasets/fec/P00000001-ALL.csv') # print fec.ix[123456] #ix是取行索引ix[2]按下标取,下标从0开始算 #输出结果如下: # cmte_idC00431445 # cand_idP80003338 # cand_nmObama, Barack # contbr_nmELLMAN, IRA # contbr_cityTEMPE # contbr_stAZ # contbr_zip852816719 # contbr_employerARIZONA STATE UNIVERSITY # contbr_occupationPROFESSOR # contb_receipt_amt50 # contb_receipt_dt01-DEC-11 # receipt_descNaN # memo_cdNaN # memo_textNaN # form_tpSA17A # file_num772372 # Name: 123456, dtype: objectfec=fec[fec.contb_receipt_amt>0] print fec #由于Barack Obama和Mitt Romney是最主要的两名候选人,所以专门准备了一个子集,只包含针对他们两人的竞选活动的赞助商用: fec_mrbo=fec[fec.cand_nm.isin(['Bachmann, Michelle','Perry, Rick'])] #无Obama的数据则换其它两位 print fec_mrbo#(1)看一下对Obama和Romney总出资额最高的职业和企业。 #我们先对候选人进行分组,然后使用本章前面介绍的那种求取最大值的方法: def get_top_amounts(group,key,n=5): totals=group.groupby(key)['contb_receipt_amt'].sum() #按key分组,然后取出'contb_receipt_amt'值,再对它求和 #根据key对totals进行降序排列:order已经没了,改成sort_values也是对值进行排序 return totals.sort_values(ascending=False)[n:]#(2)然后根据职业和雇主进行聚合: grouped=fec_mrbo.groupby('cand_nm') print grouped.apply(get_top_amounts,'contbr_occupation',n=7) #输出结果如下 # cand_nmcontbr_occupation # Bachmann, MichelleATTORNEY47084.00 #EXECUTIVE38526.00 #CONSULTANT32768.50 #C.E.O.32123.00 #FARMER25614.00 #MANAGER24621.00 #SALES22298.00 #CEO21526.00 #INVESTOR21111.00 #NONE20253.00 #BUSINESS OWNER15518.00 #SELF EMPLOYED14686.00 #ACCOUNTANT13304.00 #DIRECTOR12131.00 #N9850.00 #DOCTOR9550.00 #CPA9280.00 #REAL ESTATE9275.00 #PASTOR8870.00 #NURSE8622.00 #LAWYER8418.00 #CONTRACTOR8319.00 #SELF-EMPLOYED8182.00 #SELF8038.00 #PILOT7935.00 #PROFESSOR7920.00 #MACHINIST7905.00 #SMALL BUSINESS OWNER7857.00 #VENTURE CAPITALIST7500.00 #DENTIST7335.00 #... # Perry, RickMANAGING ATTORNEY100.00 #SW TEST MGR100.00 #LAW ENFORCEMENT100.00 #CONSULTING ABSTRACTOR100.00 #SALES PRINTING100.00 #PROGRAMMER/RETIRED100.00 #DIRECTOR ACQUISITIONS100.00 #SALES MGR.100.00 #SELF-EMP100.00 #OPS SUPERVISOR100.00 #SENIOR AUDIO DSP ENGINEER100.00 #FEDERAL AGENT100.00 #SENIOR LOAN OFFICER - CORPORATE100.00 #AEROSPACE ENGINEER100.00 #NATIONAL ACCOUNTS MANAGER100.00 #ASST. D. A. RET'D100.00 #TEXAS TEACHER75.00 #RESEARCH ASST.60.36 #JUNIOR BUYER50.00 #LAB TECHNICIAN50.00 #CUSTODIAN50.00 #CHEM LAB SUPER50.00 #SCHOOL COUNSELOR50.00 #PHYSICIAN/MEDICAL DIRECTOR30.00 #SSGT / NCOIC EVALUATIONS25.00 #PRES. & CEO25.00 #TAX DIRECTOR25.00 #CEO/IT CONSULTANT25.00 #APP DEV25.00 #SPEC. ED. INSTR. AIDE25.00 # Name: contb_receipt_amt, Length: 3211, dtype: float64

对出资额分组:
import pandas as pd import numpy as np from pandas import DataFrame,Series import os import matplotlib.pyplot as plt#获取文件的路径 path1=os.path.abspath('..') #获取当前脚本所在路径的上一级路径 print path1#(1)先将数据加载进来: fec=pd.read_csv(path1+'/pydata-book-2nd-edition/datasets/fec/P00000001-ALL.csv') # print fec.ix[123456] #ix是取行索引ix[2]按下标取,下标从0开始算 #输出结果如下: # cmte_idC00431445 # cand_idP80003338 # cand_nmObama, Barack # contbr_nmELLMAN, IRA # contbr_cityTEMPE # contbr_stAZ # contbr_zip852816719 # contbr_employerARIZONA STATE UNIVERSITY # contbr_occupationPROFESSOR # contb_receipt_amt50 # contb_receipt_dt01-DEC-11 # receipt_descNaN # memo_cdNaN # memo_textNaN # form_tpSA17A # file_num772372 # Name: 123456, dtype: objectfec=fec[fec.contb_receipt_amt>0] print fec #由于Barack Obama和Mitt Romney是最主要的两名候选人,所以专门准备了一个子集,只包含针对他们两人的竞选活动的赞助商用: fec_mrbo=fec[fec.cand_nm.isin(['Bachmann, Michelle','Perry, Rick'])] #无Obama的数据则换其它两位 print fec_mrbo#对出资额分组 #(1)还可以对该数据做另一种非常实用的分析:利用cut函数根据出资额的大小将数据离散化到多个面元中。 bins=np.array([0,1,10,100,1000,10000,100000,1000000,10000000]) labels=pd.cut(fec_mrbo.contb_receipt_amt,bins) #bins是将cut成bin的等矩离的区间 print labels #输出结果如下: # ............ # 1001729(100, 1000] # 1001730(1000, 10000] # Name: contb_receipt_amt, Length: 25791, dtype: category # Categories (8, interval[int64]): [(0, 1] < (1, 10] < (10, 100] < (100, 1000] < (1000, 10000] < #(10000, 100000] < (100000, 1000000] < (1000000, 10000000]]#(2)根据候选人姓名以及面元标签对数据进行分组: grouped=fec_mrbo.groupby(['cand_nm',labels]) print grouped.size().unstack(0)#size多少个元素值。unstack行列转换 #输出结果如下: # cand_nmBachmann, MichellePerry, Rick # contb_receipt_amt # (0, 1]98.0NaN # (1, 10]118.014.0 # (10, 100]7480.01044.0 # (100, 1000]5121.05039.0 # (1000, 10000]265.06612.0 #从上面可以看出在小额赞助方面Michelle要更多。 # 你还可以对出资额求和并在面元内规格化,以便图形化显示两们候选 人各种赞助额度的比例: bucket_sums=grouped.contb_receipt_amt.sum().unstack(0) print bucket_sums #输出结果如下: # cand_nmBachmann, MichellePerry, Rick # contb_receipt_amt # (0, 1]98.00NaN # (1, 10]876.50108.00 # (10, 100]465668.6374645.82 # (100, 1000]1654273.162959292.45 # (1000, 10000]590523.0017271707.73 normed_sums=bucket_sums.div(bucket_sums.sum(axis=1),axis=0) print normed_sums #输出结果如下: # cand_nmBachmann, MichellePerry, Rick # contb_receipt_amt # (0, 1]1.000000NaN # (1, 10]0.8903000.109700 # (10, 100]0.8618470.138153 # (100, 1000]0.3585670.641433 # (1000, 10000]0.0330600.966940 normed_sums[:-2].plot(kind='barh',stacked=True) #排除了两个最大的面元,因为这些不是由个人捐赠的。 plt.show()

pandas 数据聚合与分组运算
文章图片

根据州统计赞助信息:

#获取文件的路径 path1=os.path.abspath('..') #获取当前脚本所在路径的上一级路径 print path1#(1)先将数据加载进来: fec=pd.read_csv(path1+'/pydata-book-2nd-edition/datasets/fec/P00000001-ALL.csv') # print fec.ix[123456] #ix是取行索引ix[2]按下标取,下标从0开始算 #输出结果如下: # cmte_idC00431445 # cand_idP80003338 # cand_nmObama, Barack # contbr_nmELLMAN, IRA # contbr_cityTEMPE # contbr_stAZ # contbr_zip852816719 # contbr_employerARIZONA STATE UNIVERSITY # contbr_occupationPROFESSOR # contb_receipt_amt50 # contb_receipt_dt01-DEC-11 # receipt_descNaN # memo_cdNaN # memo_textNaN # form_tpSA17A # file_num772372 # Name: 123456, dtype: objectfec=fec[fec.contb_receipt_amt>0] print fec #由于Barack Obama和Mitt Romney是最主要的两名候选人,所以专门准备了一个子集,只包含针对他们两人的竞选活动的赞助商用: fec_mrbo=fec[fec.cand_nm.isin(['Bachmann, Michelle','Perry, Rick'])] #无Obama的数据则换其它两位 # print fec_mrbo#(1)首先自然是根据候选人和州对数据进行聚合 grouped=fec_mrbo.groupby(['cand_nm','contbr_st']) totals=grouped.contb_receipt_amt.sum().unstack(0).fillna(0) #取grouped的contb_receipt_amt分组求和,然后行列互换,NA用0填充 totals=totals[totals.sum(1)>100000] #取出totals.sum>100000的州的数据(totals.sum>100000结果为True,False) print totals[:10] #取前10条信息 #输出结果如下: # cand_nmBachmann, MichellePerry, Rick # contbr_st # AZ64472.0084374.00 # CA360100.771720880.60 # CO59754.65233033.33 # FL193223.00882626.89 # GA42785.25200536.00 # IL99219.75410185.00 # LA47748.00631891.02 # MD64964.25144020.00 # MN260459.7511400.00 # MO52611.0074550.00#(2)如果对各行除以总赞助额,就会得到各候选人在各州的总赞助额比例: percent=totals.div(totals.sum(1),axis=0) #axis=0行, print percent[:10] #输出结果如下: # cand_nmBachmann, MichellePerry, Rick # contbr_st # AZ0.4331460.566854 # CA0.1730440.826956 # CO0.2040880.795912 # FL0.1796000.820400 # GA0.1758390.824161 # IL0.1947760.805224 # LA0.0702550.929745 # MD0.3108570.689143 # MN0.9580670.041933 # MO0.4137350.586265 #(2)可以用地图画出,但是basemap没有安装成功,没有完成下面的操作 from mpl_toolkits.basemap import Basemap,cm import numpy as np from matplotlib import rcParams from matplotlib.collections import LineCollection import matplotlib.pyplot as pltfrom shapelib import ShapeFile import dbflibobama=percent['Obama,Barack']fig=plt.figure(figsize=(12,12)) ax=fig.add_axes([0.1,0.1,0.8,0.8])lllat=21 urlat=53 lllon=-1118 urlon=-62m=Basemap(ax=ax,projection='stere', lon_O=(urlon+lllon)/2,lat_O=(urlat+lllat)/2, llcrnrlat=lllat,urcrnrlat=urlat,llcrnrlon=lllon, urcrnrlon=urlon,resolution='I') m.draswcoastlines() m.drawcountries()shp=ShapeFile('../states/statesp020') dbf=dbflib.open('../states/statesp020')for npoly in range(shp.info()[0]): #在地图上绘制彩色多边形 shpsegs=[] shp_object=shp.read_object(npoly) verts=shp_object.vertices() rings=len(verts) for ring in range(rings): lons,lats=zip(*verts[ring]) x,y=m(lons,lats) shpsegs.append(zip(x,y)) if ring==0: shapedict=dbf.read_record(npoly) name=shapedict['STATE'] lines=LineCollection(shpsegs.antialiaseds=(1,))try: per=obama[state_to_code[name.upper()]] except KeyError: continuelines.set_facecolors('k') lines.set_alpha(0.75*per) #把百分比变小一点 lines.set_edgecolors('k') lines.set_linewidth(0.3) plt.show()

【pandas 数据聚合与分组运算】

    推荐阅读