Pandas入门(中)

来源:互联网 发布:keilc51中文版软件 编辑:程序博客网 时间:2024/06/03 06:03

Pandas入门(中)

Martin

  • Pandas入门中
    • GroupBy技术
      • 对分组进行迭代
      • 选取一个或一组列
      • 通过字典或Series进行分组
      • 通过函数进行分组
      • 根据索引级别分组
    • 数据聚合
      • 面向列的多函数应用
      • 以无索引形式返回聚合数据
    • 分组级运算和转换
      • apply一般性的拆分-应用-合并
      • 禁止分组键
    • 透视表

GroupBy技术

“split-apply-combine”(拆分-应用-合并),这个几个词很好的描述了分组运算的整个过程。分组运算的第一个阶段,pandas对象(无论是Series是还DataFrame抑或其他)中的数据会根据你所提供的一个或者多个键被拆分(split)为多组。拆分操作是在对象的特定轴上执行的。例如,DataFrame可以在其行(axis=0)或列(axis=1)上进行分组。然后,将一个函数应用apply到各个分组并产生一个新值。最后所有这些函数的执行结果会被合并(combine)到最终的结果对象中。结果对象的形式一般取决于数据上所执行的操作。下图可以大致说明一个简单的分组聚合过程:

1.png-20.7kB
分组可以有多种形式,且类型不必相同:

  • 列表或数组,其长度与待分组的轴一样。
  • 表示DataFrame某个列名的值。
  • 字典或Series,给出待分组轴上的值与分组名之间的对应关系。
  • 函数,用于处理轴索引或索引中的各标签。

后三种都是快捷方式而已,其最终目的仍是产生一组用于拆分对象的值。
来个例子:

>>> df = DataFrame({'key1':['a','a','b','b','a'],'key2':['one','two','one','two','one'],'data1':np.random.randn(5),'data2':np.random.randn(5)})>>> df      data1     data2 key1 key20 -0.237560 -0.070793    a  one1  0.285330 -1.242159    a  two2 -0.447567 -0.850921    b  one3  2.644768 -1.463350    b  two4 -1.062439  0.454708    a  one

假设你想要按key1进行分组,并计算data1列的平均值。实现该功能的方式有很多,而我们这里要用的是:访问data1,并根据key1调用groupby:

>>> grouped = df['data1'].groupby(df['key1'])>>> grouped<pandas.core.groupby.SeriesGroupBy object at 0x7f35bb45fbd0>

变量grouped是一个GroupBy对象。它实际上还没有进行任何的计算,只是含有一些有关分组键df['key1']的中间数据而已。换句话说,该对象已经有了接下来对各分组执行运算所需的一切信息。例如,我们可以调用GroupBymean方法来计算分组平均值:

>>> grouped.mean()key1a   -0.338223b    1.098601Name: data1, dtype: float64

一会将详细讲解mean方法,这里最重要的是,数据(Series)根据分组键进行了聚合,产生了一个新的Series,其索引为key1列总的唯一值。之所以结果中索引的名称为key1,是因为原始的DataFramedf['key1']就叫这个名字。如果我们一次传入多个数组,就会得到不同的结果:

>>> means = df['data1'].groupby([df['key1'],df['key2']]).mean()>>> meanskey1  key2a     one    -0.650000      two     0.285330b     one    -0.447567      two     2.644768Name: data1, dtype: float64

这里,通过两个键对数据进行了分组,得到的Series具有一个层次化索引(由唯一的键对组成):

>>> means.unstack()                 # 之前讲的unstack方法key2       one       twokey1                    a    -0.650000  0.285330b    -0.447567  2.644768

在上面的例子中,分组键均为Series,实际上,分组键可以是任何长度适当的数组:

>>> states = np.array(['Ohio','California','California','Ohio','Ohio'])>>> years = np.array([2005,2005,2006,2005,2006])>>> df['data1'].groupby([states,years]).mean()California  2005    0.285330            2006   -0.447567Ohio        2005    1.203604            2006   -1.062439Name: data1, dtype: float64

此外,还可以将列名当成分组键:

>>> df.groupby('key1').mean()   # 未指明哪个数据列就是默认全部(data1和data2)         data1     data2key1                    a    -0.338223 -0.286081b     1.098601 -1.157135

可能已经注意到了,在执行df.groupby('key1').mean()时,结果中没有key2列。这是因为df['key2']不是数值数据(俗称麻烦列),所以被从结果中排除了。默认情况下,所有数值列都会被聚合

无论准备拿groupby做什么,都有可能用到GroupBysize方法,它可以返回一个含有分组大小的Series

>>> df      data1     data2 key1 key20  0.676933 -0.320961    a  one1 -0.695488  0.151366    a  two2 -1.268873 -1.337475    b  one3 -0.369639  1.246996    b  two4  1.193304 -0.607347    a  one>>> df.groupby(['key1','key2']).size()key1  key2a     one     2      two     1b     one     1      two     1dtype: int64

对分组进行迭代

GroupBy对象虽然保存了一些中间数据,但是不可见,不过它却支持迭代,可以产生一组二元元组(由分组名和数据块组成),还是上例子说话再:)

>>> df.groupby('key1')<pandas.core.groupby.DataFrameGroupBy object at 0x7f3cc35f5410>>>> for name,group in df.groupby('key1'):       # GroupBy对象迭代...     print name...     print group... a                                           # 分组名1      data1     data2 key1 key2             # 数据块10  0.676933 -0.320961    a  one1 -0.695488  0.151366    a  two4  1.193304 -0.607347    a  oneb                                           # 分组名2      data1     data2 key1 key2             # 数据块22 -1.268873 -1.337475    b  one3 -0.369639  1.246996    b  two

对于多重键的情况,元组的第一个元素将会是由键值组成的元组:

>>> for (k1,k2),group in df.groupby(['key1','key2']):...     print k1,k2...     print group... a one      data1     data2 key1 key20  0.676933 -0.320961    a  one4  1.193304 -0.607347    a  onea two      data1     data2 key1 key21 -0.695488  0.151366    a  twob one      data1     data2 key1 key22 -1.268873 -1.337475    b  oneb two      data1     data2 key1 key23 -0.369639  1.246996    b  two

当然,可以对这些数据片段做任何操作。有一个你可能会觉得有用的运算,将这些数据片段做成一个字典:

>>> pieces = dict(list(df.groupby('key1')))>>> pieces['a']      data1     data2 key1 key20  0.676933 -0.320961    a  one1 -0.695488  0.151366    a  two4  1.193304 -0.607347    a  one>>> pieces['b']      data1     data2 key1 key22 -1.268873 -1.337475    b  one3 -0.369639  1.246996    b  two

groupby默认是在axis=0上进行分组的,通过设置也可以在其他任何轴上进行分组。拿上df例子来说,我们可以根据dtype对列进行分组:

>>> df.dtypesdata1    float64data2    float64key1      objectkey2      objectdtype: object>>> dict(list(df.groupby(df.dtypes,axis=1))){dtype('O'):   key1 key20    a  one1    a  two2    b  one3    b  two4    a  one, dtype('float64'):       data1     data20  0.676933 -0.3209611 -0.695488  0.1513662 -1.268873 -1.3374753 -0.369639  1.2469964  1.193304 -0.607347}

选取一个或一组列

对于由DataFrame产生的GroupBy对象,如果用一个(单个字符)或一组(字符串数组)列名对其进行索引,就能实现选取部分列进行聚合的目的。也就是说:

>>>df.groupby('key1')['data1']>>>df.groupby('key1')[['data2']]

是以下代码的语法糖(一种代码的简洁形式):

>>>df['data1'].groupby(df['key1'])>>>df[['data2']].groupby(df['key1'])

尤其对于大数据集,很可能只需要对部分列进行聚合。例如,在前面那个数据集中,如果只需计算data2列的平均值并以DataFrame形式得到结果,我们可以编写:

>>> df.groupby(['key1','key2'])[['data2']].mean()              data2key1 key2          a    one  -0.464154     two   0.151366b    one  -1.337475     two   1.246996

这种索引操作所返回的对象是一个已分组的DataFrame(如果传入的是列表或数组)或已分组的Series(如果传入的是标量形式的单个列名)

>>> type(df.groupby(['key1','key2'])['data2'])   # 标量,返回Series<class 'pandas.core.groupby.SeriesGroupBy'>>>> type(df.groupby(['key1','key2'])[['data2']]) # 列表,返回DataFrame<class 'pandas.core.groupby.DataFrameGroupBy'>

通过字典或Series进行分组

除数组以外,分组信息还可以其他形式存在。来看另一个例子:

>>> people = DataFrame(np.random.randn(5,5),columns=['a','b','c','d','e'],index=['Joe','Steve','Wes','Jim','Travis'])>>> people               a         b         c         d         eJoe     1.194226  1.847886  2.063846  1.227742  1.087048Steve  -0.950449 -0.176224 -0.646874  0.381825 -1.584446Wes     1.195563 -3.315661 -0.393060  0.590954 -0.428106Jim     1.344869  1.364735  0.227360  0.195137  0.325883Travis -0.232074  1.602136 -0.018983 -0.887190  0.554802>>> people.ix[2:3,['b','c']] = np.nan>>> people               a         b         c         d         eJoe     1.194226  1.847886  2.063846  1.227742  1.087048Steve  -0.950449 -0.176224 -0.646874  0.381825 -1.584446Wes     1.195563       NaN       NaN  0.590954 -0.428106Jim     1.344869  1.364735  0.227360  0.195137  0.325883Travis -0.232074  1.602136 -0.018983 -0.887190  0.554802>>> mapping = {'a':'red','b':'red','c':'blue','d':'blue','e':'red','f':'orange'}

现在只需将这个字典传给groupby即可:

>>> by_column = people.groupby(mapping,axis=1)>>> by_column.sum()            blue       redJoe     3.291588  4.129159Steve  -0.265049 -2.711119Wes     0.590954  0.767457Jim     0.422497  3.035487Travis -0.906173  1.924865

还可以这样

>>> mapping2 = {'Joe':'one','Steve':'two','Wes':'one','Jim':'three','Travis':'two'}>>> by_row = people.groupby(mapping2,axis=0)>>> by_row.sum()              a         b         c         d         eone    2.389788  1.847886  2.063846  1.818696  0.658942three  1.344869  1.364735  0.227360  0.195137  0.325883two   -1.182522  1.425912 -0.665857 -0.505364 -1.029644

可能你也注意到了,我之前添加了几个NA值,进行sum后都自动过滤了。

Series也有同样地功能,它可以被看做是一个固定大小的映射。对于上面的例子,如果用Series作为分组键,则pandas会检查Series以确保其索引跟分组轴是对齐的:

>>> map_series = Series(mapping)    # 先将dict转换成Series>>> map_seriesa       redb       redc      blued      bluee       redf    orangedtype: object>>> people.groupby(map_series,axis=1).count()        blue  redJoe        2    3Steve      2    3Wes        1    2Jim        2    3Travis     2    3

通过函数进行分组

相较于字典或者Series,python函数在定义分组映射关系时可以更有创意且更为抽象。任何被当做分组键的函数都会在各个索引值上被调用一次,其返回至就会被用作分组名称。来个例子:

>>> people.groupby(len).sum()          a         b         c         d         e3  3.734657  3.212620  2.291206  2.013833  0.9848265 -0.950449 -0.176224 -0.646874  0.381825 -1.5844466 -0.232074  1.602136 -0.018983 -0.887190  0.554802

将函数跟数组、列表 、字典、Series混合使用也不是问题,因为任何东西最终都会被转换为数组:

>>> key_list = ['one','one','one','two','two']>>> people.groupby([len,key_list]).min()              a         b         c         d         e3 one  1.194226  1.847886  2.063846  0.590954 -0.428106  two  1.344869  1.364735  0.227360  0.195137  0.3258835 one -0.950449 -0.176224 -0.646874  0.381825 -1.5844466 two -0.232074  1.602136 -0.018983 -0.887190  0.554802

根据索引级别分组

层次化索引数据集最方便的地方就在于它能够根据索引级别进行聚合。要实现该目的通过关键字key传入级别编号或名称即可:

>>> columns = pd.MultiIndex.from_arrays([['US','US','US','JP','JP'],[1,3,5,1,3]],names=['city','tenor'])>>> hier_df = DataFrame(np.random.randn(4,5),columns=columns)>>> hier_dfcity         US                            JP          tenor         1         3         5         1         30      0.844416 -0.746479  0.260580 -0.529815  1.1275441     -0.286859 -0.305911 -0.488367 -1.634609  0.1452592      0.857976 -1.911634  0.732285  1.290351  0.8241453      0.274936 -0.527338 -0.586308  0.996981  0.387922>>> hier_df.groupby(level='city',axis=1).count()city  JP  US0      2   31      2   32      2   33      2   3

数据聚合

如果你要使用自己的聚合函数,只需要将其传入aggregateagg方法即可:

>>> def peak_to_peak(arr):...     return arr.max() -  arr.min()... >>> grouped.agg(peak_to_peak)key1a    2.621552b    1.243450Name: data1, dtype: float64

面向列的多函数应用

已经看到了,对SeriesDataFrame列的聚合运算其实就是使用aggregateagg或者调用诸如meanstd之类的方法。然而,我们更希望可以对不同的列使用不同的聚合函数,或一次应用多个函数。

在接下来,将使用一个有关餐馆小费的数据集:

>>> tips = pd.read_csv('下载/book_data/ch08/tips.csv')  # 文件存放的路径

添加消费占总额百分比tips_pct列

>>> tips['tip_pct'] = tips['tip'] / tips['total_bill']>>> tips[:6]   total_bill   tip     sex smoker  day    time  size   tip_pct0       16.99  1.01  Female     No  Sun  Dinner     2  0.0594471       10.34  1.66    Male     No  Sun  Dinner     3  0.1605422       21.01  3.50    Male     No  Sun  Dinner     3  0.1665873       23.68  3.31    Male     No  Sun  Dinner     2  0.1397804       24.59  3.61  Female     No  Sun  Dinner     4  0.1468085       25.29  4.71    Male     No  Sun  Dinner     4  0.186240

现在根据sexsmoker进行分组:

>>> grouped = tips.groupby(['sex','smoker'])>>> grouped_pct = grouped['tip_pct']>>> grouped_pct.agg('mean')sex     smokerFemale  No        0.156921        Yes       0.182150Male    No        0.160669        Yes       0.152771Name: tip_pct, dtype: float64

如果传入一组函数或函数名,得到的DataFrame的列就会以相应的函数名命名:

>>> grouped_pct.agg(['mean','std'])                   mean       stdsex    smoker                    Female No      0.156921  0.036421       Yes     0.182150  0.071595Male   No      0.160669  0.041849       Yes     0.152771  0.090588

并非一定要接受GroupBy自动给出的那些列名,特别是lambda函数,它们的名称是<lambda>,这样的辨识度就很低了,如果传入的是一个(name,function)元组组成的列表,则各元组的第一个元素就会被用作DataFrame的列名(可以将这种二元元组列表看做一个有序映射):

>>> grouped_pct.agg([('foo','mean'),('bar',np.std)])                    foo       barsex    smoker                    Female No      0.156921  0.036421       Yes     0.182150  0.071595Male   No      0.160669  0.041849       Yes     0.152771  0.090588

对于DataFrame,你还可以定义一组应用于全部列的函数,或不同的列应用不同的函数,假设我们想要对tip_pcttotal-bill列计算三个统计信息:

>>> functions = ['count','mean','max']>>> result = grouped['tip_pct','total_bill'].agg(functions)>>> result              tip_pct                     total_bill                                  count      mean       max      count       mean    maxsex    smoker                                                         Female No          54  0.156921  0.252672         54  18.105185  35.83       Yes         33  0.182150  0.416667         33  17.977879  44.30Male   No          97  0.160669  0.291990         97  19.791237  48.33       Yes         60  0.152771  0.710345         60  22.284500  50.81

如结果所示,DataFrame拥有层次化的列,这相当于分别对各列进行整合,然后用concat(连接函数)将结果组装到一起(列名用作keys参数)。

>>> result['tip_pct']               count      mean       maxsex    smoker                           Female No         54  0.156921  0.252672       Yes        33  0.182150  0.416667Male   No         97  0.160669  0.291990       Yes        60  0.152771  0.710345

跟前面一样,这里也可以传入带有自定义名称的元组列表。

>>> ftuples = [('Durchschnitt','mean'),('Abweichung',np.var)]>>> grouped['tip_pct','total_bill'].agg(ftuples)                   tip_pct              total_bill                         Durchschnitt Abweichung Durchschnitt Abweichungsex    smoker                                                Female No         0.156921   0.001327    18.105185  53.092422       Yes        0.182150   0.005126    17.977879  84.451517Male   No         0.160669   0.001751    19.791237  76.152961       Yes        0.152771   0.008206    22.284500  98.244673

现在,假设你想要对不同的列应用不同的函数。具体的办法及时向agg传入一个从列名映射到函数的字典:

>>> grouped.agg({'tip':np.max,'size':'sum'})                tip  sizesex    smoker            Female No       5.2   140       Yes      6.5    74Male   No       9.0   263       Yes     10.0   150

以“无索引”形式返回聚合数据

到目前为止,所有实例中的聚合数据都有唯一的分组键组成的索引(可能还是层次化的)。由于并不总是需要如此,所以你可以向groupby传入as_index=False以禁用该功能:

>>> tips.groupby(['sex','smoker'],as_index=False).mean()    # 禁用      sex smoker  total_bill       tip      size   tip_pct0  Female     No   18.105185  2.773519  2.592593  0.1569211  Female    Yes   17.977879  2.931515  2.242424  0.1821502    Male     No   19.791237  3.113402  2.711340  0.1606693    Male    Yes   22.284500  3.051167  2.500000  0.152771>>> tips.groupby(['sex','smoker']).mean()                   # 未禁用               total_bill       tip      size   tip_pctsex    smoker                                          Female No       18.105185  2.773519  2.592593  0.156921       Yes      17.977879  2.931515  2.242424  0.182150Male   No       19.791237  3.113402  2.711340  0.160669       Yes      22.284500  3.051167  2.500000  0.152771

分组级运算和转换

聚合只不过是分组运算的一种而已。它是数据转换的一个特例。也就是说它接受能够将一维数组简化为标量值的函数。在本节中,将介绍transformapply,它们能够执行更多其他分组运算。

假设我们想要为一个DataFrame添加一个用于存放各索引分组平均值的列。一个办法是先聚合再合并:

>>> df      data1     data2 key1 key20  1.289781  0.439950    a  one1 -0.337467 -0.560815    a  two2 -0.222298 -1.229548    b  one3  0.136132  0.293326    b  two4 -1.433943 -2.416655    a  one>>> k1_means = df.groupby('key1').mean().add_prefix('mean')>>> k1_means      meandata1  meandata2          # 列名加了前缀key1                      a     -0.160543  -0.845840b     -0.043083  -0.468111>>> pd.merge(df,k1_means,left_on='key1',right_index=True)      data1     data2 key1 key2  meandata1  meandata20  1.289781  0.439950    a  one  -0.160543  -0.8458401 -0.337467 -0.560815    a  two  -0.160543  -0.8458404 -1.433943 -2.416655    a  one  -0.160543  -0.8458402 -0.222298 -1.229548    b  one  -0.043083  -0.4681113  0.136132  0.293326    b  two  -0.043083  -0.468111

merge函数是进行表的连接操作,如果不明白其中参数的意思可以使用help(pd.merge)命令进行查询。

虽然这样也行,但是不太灵活。你可以将该过程看做利用np.mean函数对两个数据列进行行方向广播转换。再以之前用过的那个peopleDataFrame为例,这次在GroupBy上使用transform方法:

>>> key = ['one','two','one','two','one']>>> people.groupby(key).mean()           a         b         c         d         eone -0.16407 -0.006354 -0.404462  0.611442 -0.206593two -0.06096  0.516483 -0.036140  0.349761 -0.649544>>> people.groupby(key).transform(np.mean)              a         b         c         d         eJoe    -0.16407 -0.006354 -0.404462  0.611442 -0.206593Steve  -0.06096  0.516483 -0.036140  0.349761 -0.649544Wes    -0.16407 -0.006354 -0.404462  0.611442 -0.206593Jim    -0.06096  0.516483 -0.036140  0.349761 -0.649544Travis -0.16407 -0.006354 -0.404462  0.611442 -0.206593

不难看出,transform会将一个函数应用到各个分组,然后将结果放置到适当的位置上。如果各分组产生的是一个标量值,则该值就会被广播出去。

apply:一般性的“拆分-应用-合并”

回到之前的那个小费的数据集:

>>> tips[:7]   total_bill   tip     sex smoker  day    time  size   tip_pct0       16.99  1.01  Female     No  Sun  Dinner     2  0.0594471       10.34  1.66    Male     No  Sun  Dinner     3  0.1605422       21.01  3.50    Male     No  Sun  Dinner     3  0.1665873       23.68  3.31    Male     No  Sun  Dinner     2  0.1397804       24.59  3.61  Female     No  Sun  Dinner     4  0.1468085       25.29  4.71    Male     No  Sun  Dinner     4  0.1862406        8.77  2.00    Male     No  Sun  Dinner     2  0.228050

假设你想要根据分组选出最高的5个tip_pct值,首先,编写一个选取指定列具有最大值的行的函数:

>>> def top(df,n=5,column='tip_pct'):...     return df.sort_values(by=column)[-n:]... >>> top(tips,n=6)     total_bill   tip     sex smoker  day    time  size   tip_pct109       14.31  4.00  Female    Yes  Sat  Dinner     2  0.279525183       23.17  6.50    Male    Yes  Sun  Dinner     4  0.280535232       11.61  3.39    Male     No  Sat  Dinner     2  0.29199067         3.07  1.00  Female    Yes  Sat  Dinner     1  0.325733178        9.60  4.00  Female    Yes  Sun  Dinner     2  0.416667172        7.25  5.15    Male    Yes  Sun  Dinner     2  0.710345

现在,如果对smoker分组并用该函数调用apply,就会得到:

>>> tips.groupby('smoker').apply(top)            total_bill   tip     sex smoker   day    time  size   tip_pctsmoker                                                                   No     88        24.71  5.85    Male     No  Thur   Lunch     2  0.236746       185       20.69  5.00    Male     No   Sun  Dinner     5  0.241663       51        10.29  2.60  Female     No   Sun  Dinner     2  0.252672       149        7.51  2.00    Male     No  Thur   Lunch     2  0.266312       232       11.61  3.39    Male     No   Sat  Dinner     2  0.291990Yes    109       14.31  4.00  Female    Yes   Sat  Dinner     2  0.279525       183       23.17  6.50    Male    Yes   Sun  Dinner     4  0.280535       67         3.07  1.00  Female    Yes   Sat  Dinner     1  0.325733       178        9.60  4.00  Female    Yes   Sun  Dinner     2  0.416667       172        7.25  5.15    Male    Yes   Sun  Dinner     2  0.710345

top函数在DataFrame的各个片段上调用,然后结果由concat组装到一起,并以分组名称进行了标记。于是,最终结果就有了一个层次换索引,其内层索引值来自原DataFrame`。

如果传给apply的函数能够接受其他参数或关键字,则可以将这些内容放在函数名后面一并传入:

>>> tips.groupby('smoker').apply(top,n=1,column='total_bill')            total_bill   tip   sex smoker  day    time  size   tip_pctsmoker                                                                No     212       48.33   9.0  Male     No  Sat  Dinner     4  0.186220Yes    170       50.81  10.0  Male    Yes  Sat  Dinner     3  0.196812

禁止分组键

从上面的例子可以看出,分组键会跟原始对象的索引共同构成结果对象中的层次化苏音。将group_keys=False传入groupby即可禁止该效果:

>>> tips.groupby('smoker',group_keys=False).apply(top)     total_bill   tip     sex smoker   day    time  size   tip_pct88        24.71  5.85    Male     No  Thur   Lunch     2  0.236746185       20.69  5.00    Male     No   Sun  Dinner     5  0.24166351        10.29  2.60  Female     No   Sun  Dinner     2  0.252672149        7.51  2.00    Male     No  Thur   Lunch     2  0.266312232       11.61  3.39    Male     No   Sat  Dinner     2  0.291990109       14.31  4.00  Female    Yes   Sat  Dinner     2  0.279525183       23.17  6.50    Male    Yes   Sun  Dinner     4  0.28053567         3.07  1.00  Female    Yes   Sat  Dinner     1  0.325733178        9.60  4.00  Female    Yes   Sun  Dinner     2  0.416667172        7.25  5.15    Male    Yes   Sun  Dinner     2  0.710345

透视表

透视表(pivot table)是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具。它根据一个或多个键对数据进行聚合,并根据行和列上的分组键将数据分配到各个矩形区域。DataFrame有一个pivot_table方法,此外还有一个顶级的pandas.pivot_table函数。除能为groupby提供便利之外,pivot_table还可以添加分项小计(也叫做margins)。

回到消费数据集,假设我想要根据sexsmoker计算分组平均数(pivot_table的默认聚合类型是计算平均数,想要修改可以修改参数aggfunc=‘mean’),并将sexsmoker放到行上:

>>> tips.pivot_table(index=['sex','smoker'])    # 老版本为rows新版本改为index                   size       tip   tip_pct  total_billsex    smoker                                          Female No      2.592593  2.773519  0.156921   18.105185       Yes     2.242424  2.931515  0.182150   17.977879Male   No      2.711340  3.113402  0.160669   19.791237       Yes     2.500000  3.051167  0.152771   22.284500

这对groupby来说也是很简单的事。

>>> tips.groupby(['sex','smoker']).mean()   # 一样的效果               total_bill       tip      size   tip_pctsex    smoker                                          Female No       18.105185  2.773519  2.592593  0.156921       Yes      17.977879  2.931515  2.242424  0.182150Male   No       19.791237  3.113402  2.711340  0.160669       Yes      22.284500  3.051167  2.500000  0.152771

现在,假设我们只想聚合tips_pctsize,而且想根据day进行分组。将smoker放到列上,把day放到行上:

>>> tips.pivot_table(['tip_pct','size'],index=['sex','day'],columns=['smoker'])              tip_pct                size          smoker             No       Yes        No       Yessex    day                                         Female Fri   0.165296  0.209129  2.500000  2.000000       Sat   0.147993  0.163817  2.307692  2.200000       Sun   0.165710  0.237075  3.071429  2.500000       Thur  0.155971  0.163073  2.480000  2.428571Male   Fri   0.138005  0.144730  2.000000  2.125000       Sat   0.162132  0.139067  2.656250  2.629630       Sun   0.158291  0.173964  2.883721  2.600000       Thur  0.165706  0.164417  2.500000  2.300000

但是如果想要利用groupby来实现此效果就不能行得通了。

还可以对这个表作进一步处理,传入margins-True添加分项小计。这将会添加标签为All的行和列,其值对应于单个等级中所有数据的分组统计。在下面这个例子中,All值为平均数:不单独考虑烟民与非烟民(All列),不单独考虑行分组两个级别中的任何单项(All行):

>>> tips.pivot_table(['tip_pct','size'],index=['sex','day'],columns='smoker',margins=True)              tip_pct                          size                    smoker             No       Yes       All        No       Yes       Allsex    day                                                             Female Fri   0.165296  0.209129  0.199388  2.500000  2.000000  2.111111       Sat   0.147993  0.163817  0.156470  2.307692  2.200000  2.250000       Sun   0.165710  0.237075  0.181569  3.071429  2.500000  2.944444       Thur  0.155971  0.163073  0.157525  2.480000  2.428571  2.468750Male   Fri   0.138005  0.144730  0.143385  2.000000  2.125000  2.100000       Sat   0.162132  0.139067  0.151577  2.656250  2.629630  2.644068       Sun   0.158291  0.173964  0.162344  2.883721  2.600000  2.810345       Thur  0.165706  0.164417  0.165276  2.500000  2.300000  2.433333All          0.159328  0.163196  0.160803  2.668874  2.408602  2.569672

那么这个All列是怎么来的呢?什么叫不单独考虑烟民与非烟民(All列)?我们以第一行的第二列All来进行举例,其实就进行了如下操作:

>>> tips_Fir = tips[tips['day']=='Fri']>>> tips_result = tips_Fir[tips_Fir['sex']=='Female']>>> tips_result     total_bill   tip     sex smoker  day    time  size   tip_pct92         5.75  1.00  Female    Yes  Fri  Dinner     2  0.17391393        16.32  4.30  Female    Yes  Fri  Dinner     2  0.26348094        22.75  3.25  Female     No  Fri  Dinner     2  0.142857100       11.35  2.50  Female    Yes  Fri  Dinner     2  0.220264101       15.38  3.00  Female    Yes  Fri  Dinner     2  0.195059221       13.42  3.48  Female    Yes  Fri   Lunch     2  0.259314223       15.98  3.00  Female     No  Fri   Lunch     3  0.187735225       16.27  2.50  Female    Yes  Fri   Lunch     2  0.153657226       10.09  2.00  Female    Yes  Fri   Lunch     2  0.198216>>> tips_result['size'].mean()2.111111111111111

All行道理和列一样,不在进行阐述。

要使用其他的聚合函数,将其传给aggfunc即可。例如,使用countlen可以得到有关分组大小的交叉表:

>>> tips.pivot_table('tip_pct',index=['sex','smoker'],columns='day',aggfunc=len,margins=True)day             Fri   Sat   Sun  Thur    Allsex    smoker                               Female No       2.0  13.0  14.0  25.0   54.0       Yes      7.0  15.0   4.0   7.0   33.0Male   No       2.0  32.0  43.0  20.0   97.0       Yes      8.0  27.0  15.0  10.0   60.0All            19.0  87.0  76.0  62.0  244.0

如果存在空的组合(也就是NA),你可能会希望设置一个fill_value:

>>> tips.pivot_table('tip_pct',index=['time','sex','smoker'],columns='day',aggfunc='sum',fill_value=0)day                        Fri       Sat       Sun      Thurtime   sex    smoker                                        Dinner Female No      0.142857  1.923915  2.319939  0.159744              Yes     0.852716  2.457251  0.948299  0.000000       Male   No      0.276010  5.188230  6.806499  0.000000              Yes     0.635410  3.754804  2.609457  0.000000Lunch  Female No      0.187735  0.000000  0.000000  3.739542              Yes     0.611188  0.000000  0.000000  1.141508       Male   No      0.000000  0.000000  0.000000  3.314127              Yes     0.522432  0.000000  0.000000  1.644168

pivot_table参数说明。

参数名 说明 values 待聚合的列的名称。默认聚合所有数值列 indexs 用于分组的列明或其他分组键,出现在透视表结果的行 columns 用于分组的列明或其他分组键,出现在透视表结果的列 aggfunc 聚合函数或函数列表,默认为‘mean’。可以是任何对groupby有效的函数 fill_value 用于替换结果表中的缺失值 margins 添加行/列小计和总计,默认为False
0 0