Python数据处理学习笔记 - pandas数据分组和聚合篇

这是我阅读《用Python进行数据分析》一书的笔记、实验和总结。本篇文章主要讲解pandas包中数据分组和聚合技术,主要涉及groupby、aggregate、apply、transform、(q)cut、透视表。数据分组和聚合是对DataFrame进行分析和处理的关键步骤,尤其是apply(),其提供了一个编写函数进行运算的强大接口,正因如此,pandas的agg技术比MongoDB等数据库的agg技术更加先进、灵活、高效。

约定俗成的,引入以下包:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pprint import pprint

在上一章介绍过map函数可以对行列进行运算,但是,大多数时候我们需要对某些行列进行运算,我们可以使用切片来选取行列之后计算,或者使用bool型函数来筛选符合条件的行列后进行运算。如果我们需要对于多个行列进行合并,比如合并二级level,这个时候使用索引操纵列进行map是通用的做法。但是由于这些需求过于常见但是这些操作过于复杂,所以就应运而生了聚合。这里的聚合和数据库的聚合技术是一种东西,不过却灵活、强大很多,它可以按照一定的规则对于行列进行拆分,然后进行某一种运算或者多种或者你自定义的运算,然后进行各种不同层级的数据合并(得益于Python和ndarray)。

数据分组和聚合本质上是按照索引将数据进行分组、计算,然后再合并(SPLIT.APPLY.COMBINE)的过程。对于分组,我们使用groupBy函数,对于分过的组进行计算,可以使用aggregate或者apply来进一步操作。aggregate本质上都是分组计算的一种,不过其更经常生成标量,比如sum、count等(类似MongoDB的aggregate技术)。而使用apply则可以组合各种自己编写的函数,返回各种矢量并进行concat最后生成表。

1. 数据分组:GroupBy技术

分组的依据选择

groupby可以对字符串(列名称)、numpy类型、等长对等索引的数组、Series、字典(列名称的map关系)、函数、索引层次进行分组。大体来说,只要是和index对应的等长列表(lsit),都可以进行分组。而其余的字段,比如column name、function map,本质上都是转换成为list再进行groupby操作。

分组状况查看

对于分过的组,可以使用grouped.groups进行查看,结果会返回一个字典。对于分过的组,也可以进行迭代,在后面会详细介绍分组对象的迭代。

1.1 通过list进行分组、多层分组

一个简单的例子

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)})
      data1     data2 key1 key2
0  0.089452 -1.467732    a  one
1 -0.296581  0.897673    a  two
2 -0.887990  1.029688    b  one
3 -0.535381 -1.179564    b  two
4 -0.651637  0.749137    a  one

只要一个等row index length的series、list即可进行分组

grouped = df['data1'].groupby(df['key1']) 
#使用Series对于Series进行分组,它们具有完全等同的row index。
print(grouped)
grouped.mean()
<pandas.core.groupby.SeriesGroupBy object at 0x000001B80461ABE0>

key1
a   -0.286255
b   -0.711685
Name: data1, dtype: float64

可以使用多key分组

类似于MongoDB的$group{a,b}分层分组。

means = df['data1'].groupby([df['key1'], df['key2']]).mean() 
#也可以使用一个[],进行多层的分组,类似于SQL的多Key聚合。
print(means)
print(means.unstack())
key1  key2
a     one    -0.281093
      two    -0.296581
b     one    -0.887990
      two    -0.535381
Name: data1, dtype: float64

key2       one       two
key1                    
a    -0.281093 -0.296581
b    -0.887990 -0.535381

引入外部的list而不是内部的column列也可以

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.296581
            2006   -0.887990
Ohio        2005   -0.222965
            2006   -0.651637
Name: data1, dtype: float64

可以指定column列name

其列自动会被删除而不会进行分组。这本质上是一种语法糖,填入一个Str的时候会自动对column name进行查找,类似于:

df.groupby([df["key1"],df["key2"]]).mean()

print(df.groupby('key1').mean())
print(df.groupby(['key1', 'key2']).mean())
print(df.groupby([df["key1"],df["key2"]]).mean())
         data1     data2
key1                    
a    -0.286255  0.059693
b    -0.711685 -0.074938
              data1     data2
key1 key2                    
a    one  -0.281093 -0.359298
     two  -0.296581  0.897673
b    one  -0.887990  1.029688
     two  -0.535381 -1.179564
              data1     data2
key1 key2                    
a    one  -0.281093 -0.359298
     two  -0.296581  0.897673
b    one  -0.887990  1.029688
     two  -0.535381 -1.179564

mean用于将被聚合的数据求平均值,而size则对于groupby对象进行count计数

print(df.groupby(['key1', 'key2']).mean())
df.groupby(['key1', 'key2']).size()
              data1     data2
key1 key2                    
a    one  -0.281093 -0.359298
     two  -0.296581  0.897673
b    one  -0.887990  1.029688
     two  -0.535381 -1.179564

key1  key2
a     one     2
      two     1
b     one     1
      two     1
dtype: int64

1.2 通过字典或者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
print(people)
               a         b         c         d         e
Joe     0.222491 -0.995911 -1.014604 -0.067544 -0.869915
Steve   0.777247 -1.353332  0.821747 -1.104266  0.156773
Wes    -0.080765       NaN       NaN  0.737536  0.132357
Jim    -0.733980 -0.728372 -1.101054 -0.518711 -0.099602
Travis -0.204770 -1.357502  1.421094  0.523682  1.917264
mapping = {'a': 'red', 'b': 'red', 'c': 'blue',
           'd': 'blue', 'f': 'red', 'e' : 'orange'} 
           #mapping中可以存在没有对应的列,程序不会对其进行匹配
by_column = people.groupby(mapping, axis=1)
print(by_column.sum())
            blue    orange       red
Joe    -1.082148 -0.869915 -0.773420
Steve  -0.282519  0.156773 -0.576085
Wes     0.737536  0.132357 -0.080765
Jim    -1.619765 -0.099602 -1.462352
Travis  1.944775  1.917264 -1.562272
map_series = pd.Series(mapping)
map_series
print(people.groupby(map_series, axis=1).count())
        blue  orange  red
Joe        2       1    2
Steve      2       1    2
Wes        1       1    1
Jim        2       1    2
Travis     2       1    2

区别于map和funcmap,这里的对各列进行的map分组本质上还是分组,其不保留各原始列的内容。

1.3 通过函数进行分组

传入一个函数作为groupby对象,函数会自动将index列各个index name值作为变量传入,然后返回一个新的值。并以这个新的值作为分组的名称。

print(people)
print(people.groupby(len).sum())
               a         b         c         d         e
Joe     0.222491 -0.995911 -1.014604 -0.067544 -0.869915
Steve   0.777247 -1.353332  0.821747 -1.104266  0.156773
Wes    -0.080765       NaN       NaN  0.737536  0.132357
Jim    -0.733980 -0.728372 -1.101054 -0.518711 -0.099602
Travis -0.204770 -1.357502  1.421094  0.523682  1.917264
          a         b         c         d         e
3 -0.592254 -1.724283 -2.115659  0.151281 -0.837160
5  0.777247 -1.353332  0.821747 -1.104266  0.156773
6 -0.204770 -1.357502  1.421094  0.523682  1.917264

函数还可以作为分组的多个key之一,因为其最后还是会被转换成为一个数组,非常灵活。

key_list = ['one', 'one', 'one', 'two', 'two']
print(people.groupby([len, key_list]).min())
              a         b         c         d         e
3 one -0.080765 -0.995911 -1.014604 -0.067544 -0.869915
  two -0.733980 -0.728372 -1.101054 -0.518711 -0.099602
5 one  0.777247 -1.353332  0.821747 -1.104266  0.156773
6 two -0.204770 -1.357502  1.421094  0.523682  1.917264

1.4 分组的迭代和内部结构

使用 grouped.groups 可以查看分组状况。如果有更多的需求,可以对分组对象进行迭代。groupby对象迭代,其返回一个元组,其第一个对象为分组的name,如果是单key group,则返回对象是一个str字符串,如果是多key group,则这个name则是一个包含多key name的tuple。

print(df.groupby('key1').mean())
for name, group in df.groupby('key1'):
    print(name)
    print(group)
         data1     data2
key1                    
a    -0.286255  0.059693
b    -0.711685 -0.074938
a
      data1     data2 key1 key2
0  0.089452 -1.467732    a  one
1 -0.296581  0.897673    a  two
4 -0.651637  0.749137    a  one
b
      data1     data2 key1 key2
2 -0.887990  1.029688    b  one
3 -0.535381 -1.179564    b  two
for (k1, k2), group in df.groupby(['key1', 'key2']):
    print((k1, k2))
    print(group)
for x in df.groupby(['key1', 'key2']):
    print(x)
    break
('a', 'one')
      data1     data2 key1 key2
0  0.089452 -1.467732    a  one
4 -0.651637  0.749137    a  one

('a', 'two')
      data1     data2 key1 key2
1 -0.296581  0.897673    a  two

('b', 'one')
     data1     data2 key1 key2
2 -0.88799  1.029688    b  one

('b', 'two')
      data1     data2 key1 key2
3 -0.535381 -1.179564    b  two

(('a', 'one'),       
data1     data2 key1 key2
0  0.089452 -1.467732    a  one
4 -0.651637  0.749137    a  one)

本质上,迭代返回的对象是:

[((name1,namea),ndarray),((name1,nameb),ndarray),((name2,namea),ndarray),((name2,nameb),ndarray)]

这很容易的利用dict和list函数来讲groupby对象变成一个以key name为key,以其聚合为value的dict。

pieces = dict(list(df.groupby('key1')))
pprint(pieces)
print(pieces['b'])
{'a':       
data1     data2 key1 key2
0  0.089452 -1.467732    a  one
1 -0.296581  0.897673    a  two
4 -0.651637  0.749137    a  one,

 'b':       
 data1     data2 key1 key2
2 -0.887990  1.029688    b  one
3 -0.535381 -1.179564    b  two}

      data1     data2 key1 key2
2 -0.887990  1.029688    b  one
3 -0.535381 -1.179564    b  two

1.5 组和列的切片选取

df.groupby(["key1"])["data1"].mean() 本质上是 df["data1"].groupby(["key1"]).mean() 的语法糖

df.groupby(["key1"])[["data1"]].mean() 本质上是 df[["data1"]].groupby(["key1"]).mean() 的语法糖

对于前者,其返回子类Groupby对象,不保留切片索引。对于后者,其仅仅是对整体的结果进行切片,保留了索引。

print(df.groupby(['key1', 'key2'])[['data2']].mean())
              data2
key1 key2          
a    one  -0.359298
     two   0.897673
b    one   1.029688
     two  -1.179564
print(df.groupby(['key1', 'key2'])['data2'].mean())
print(df.groupby(['key1', 'key2'])[['data1',"data2"]].mean())
print(df.groupby(['key1', 'key2'])['data1','data2'].mean())
key1  key2
a     one    -0.359298
      two     0.897673
b     one     1.029688
      two    -1.179564
Name: data2, dtype: float64
              data1     data2
key1 key2                    
a    one  -0.281093 -0.359298
     two  -0.296581  0.897673
b    one  -0.887990  1.029688
     two  -0.535381 -1.179564
              data1     data2
key1 key2                    
a    one  -0.281093 -0.359298
     two  -0.296581  0.897673
b    one  -0.887990  1.029688
     two  -0.535381 -1.179564

区别是,采用【】这种选择方法而不是【【】】生成的对象不是groupby而是serise/dataframegroupby对象(如果选择某一列进行计算的话)。

对于多个列进行选择的话,不论是【】还是【【】】,其生成的对象都是一样的。

1.6 axis=1的groupby

最后,也可以传递df.dtypes来作为分组依据。这里的第二个知识点是,可以指定axis为1来对column进行group,之前所有的实例都是对axis=0的row index进行的分组。

print(df)
print(df.dtypes)
grouped = df.groupby(df.dtypes, axis=1)
      data1     data2 key1 key2
0  0.089452 -1.467732    a  one
1 -0.296581  0.897673    a  two
2 -0.887990  1.029688    b  one
3 -0.535381 -1.179564    b  two
4 -0.651637  0.749137    a  one

data1    float64
data2    float64
key1      object
key2      object
dtype: object
for dtype, group in grouped:
    print(dtype)
    print(group)
float64
      data1     data2
0  0.089452 -1.467732
1 -0.296581  0.897673
2 -0.887990  1.029688
3 -0.535381 -1.179564
4 -0.651637  0.749137

object
  key1 key2
0    a  one
1    a  two
2    b  one
3    b  two
4    a  one

1.7 level=2的groupby

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)
print(hier_df)
cty          US                            JP          
tenor         1         3         5         1         3
0     -1.047668 -0.056759  1.000576  0.213877  0.277200
1      2.460159  0.134850 -0.852783  1.358721  0.783409
2     -0.555137 -0.075603  1.629682 -0.716262 -0.981589
3     -0.135948  0.311016  0.310988 -0.741287 -0.024857
#按照level进行groupby,除了此level的所有子层会进行合并。
print(hier_df.groupby(level='cty', axis=1).count())
cty  JP  US
0     2   3
1     2   3
2     2   3
3     2   3
print(hier_df.groupby(level="tenor",axis=1).count())
tenor  1  3  5
0      2  2  1
1      2  2  1
2      2  2  1
3      2  2  1

2. 数据聚合:agg、transform、apply

groupBy的作用主要在于其为我们提供了一个包含分组信息的可迭代的groupBy对象。这就完成了数据分组的任务,然而,真正关键的部分才刚刚开始,我们需要对这些分组进行运算,不论是内置的运算还是我们自己编写的运算,在这里需要注意,运算传入的对象是group的ndarray,至于传出什么结果,一般而言,像是agg应用count、sum这类内置函数会生成标量,然后被整个成一张表,也就是说,每个group会返回一行数据。对于transform来说,每个group中的每行元素都会返回一个数据。对于apply来说,我们可以返回任意的矢量值,也就是说每个group不一定返回相同的长度数据,反正最后会被concat进行数据合并。

2.1 数据聚合过程

groupby.mean(),这个mean是怎么工作的?这涉及到分组后的数据聚合,pandas定义了很多优化过的方法,比如sum、count、first等,但是我们也可以写自己的方法,使用agg/aggregate()进行调用。

quantile可以计算分位数。其本身是一个对于series作用的函数,在这里发生了什么?

groupby对象将结果进行了切片(piece),每一个piece对应一个series,对各片调用了piece.quantile(),然后将分别生成的结果进行了组装。

print(df)
grouped = df.groupby('key1')
for name,value in grouped["data1"]:
    print("\n",name,"\n",value)
grouped['data1'].quantile(0.9)
      data1     data2 key1 key2
0  0.089452 -1.467732    a  one
1 -0.296581  0.897673    a  two
2 -0.887990  1.029688    b  one
3 -0.535381 -1.179564    b  two
4 -0.651637  0.749137    a  one

 a 
 0    0.089452
1   -0.296581
4   -0.651637
Name: data1, dtype: float64

 b 
 2   -0.887990
3   -0.535381
Name: data1, dtype: float64

key1
a    0.012245
b   -0.570642
Name: data1, dtype: float64

也可以使用自己的agg方法,传入的是经过groupby分片的piece(Series),对series进行max和min的计算后返回结果。

在这里需要注意的是,data1和data2都有根据key1分组的两个不同piece,因此就有了四个piece,所以合并之后会生成2×2的表格

def peak_to_peak(arr):
    #print("\nI am in peak, \nthe att is \n%s"%arr)
    return arr.max() - arr.min()
print(grouped.agg(peak_to_peak))
         data1     data2
key1                    
a     0.741089  2.365405
b     0.352608  2.209252
print(grouped.describe().T[:3])
key1                a         b
data1 count  3.000000  2.000000
      mean  -0.286255 -0.711685
      std    0.370652  0.249332

内置的一些函数比如sum、count、mean、median、std、var、min、max、prod、first、last,这些都是经过优化的方法,因此使用起来非常快速。而自己定义的函数速度则受限,因此尽量采用内置的函数进行聚合计算。

2.2 agg技术: 面向列的函数应用

开始编写自己的过程函数

tips = pd.read_csv('tips.csv')
# Add tip percentage of total bill
tips['tip_pct'] = tips['tip'] / tips['total_bill']
print(tips[:6])
   total_bill   tip smoker  day    time  size   tip_pct
0       16.99  1.01     No  Sun  Dinner     2  0.059447
1       10.34  1.66     No  Sun  Dinner     3  0.160542
2       21.01  3.50     No  Sun  Dinner     3  0.166587
3       23.68  3.31     No  Sun  Dinner     2  0.139780
4       24.59  3.61     No  Sun  Dinner     4  0.146808
5       25.29  4.71     No  Sun  Dinner     4  0.186240
grouped = tips.groupby(['day', 'smoker'])
grouped_pct = grouped['tip_pct']
grouped_pct.agg('mean')
day   smoker
Fri   No        0.151650
      Yes       0.174783
Sat   No        0.158048
      Yes       0.147906
Sun   No        0.160113
      Yes       0.187250
Thur  No        0.160298
      Yes       0.163863
Name: tip_pct, dtype: float64

agg可以调用str表示的内置函数,或者直接一个函数。可以传递多个函数生成多个结果,放在一个列表中即可。

需要注意,这里和groupby技术中直接调用函数并call不同,采用agg可以使用我们自己的函数,并且能够一次进行多个函数的计算。

多函数计算

print(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

函数别名设置

agg不仅可以调用多个函数生成结果表,还可以对每个函数进行名称的自定义。

print(grouped_pct.agg([('MEAN', 'mean'), ('STD', np.std)]))
                 MEAN       STD
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

多层分组的多函数计算

再看一个更加复杂的例子,使用多个index进行group,对结果进行多个函数的计算,最后汇聚成表。

functions = ['count', 'mean', 'max']
result = grouped['tip_pct', 'total_bill'].agg(functions)
print(type(result))
print(result)
<class 'pandas.core.frame.DataFrame'>
            tip_pct                     total_bill                  
              count      mean       max      count       mean    max
day  smoker                                                         
Fri  No           4  0.151650  0.187735          4  18.420000  22.75
     Yes         15  0.174783  0.263480         15  16.813333  40.17
Sat  No          45  0.158048  0.291990         45  19.661778  48.33
     Yes         42  0.147906  0.325733         42  21.276667  50.81
Sun  No          57  0.160113  0.252672         57  20.506667  48.17
     Yes         19  0.187250  0.710345         19  24.120000  45.35
Thur No          45  0.160298  0.266312         45  17.113111  41.19
     Yes         17  0.163863  0.241255         17  19.190588  43.11
print(result['tip_pct'])
             count      mean       max
day  smoker                           
Fri  No          4  0.151650  0.187735
     Yes        15  0.174783  0.263480
Sat  No         45  0.158048  0.291990
     Yes        42  0.147906  0.325733
Sun  No         57  0.160113  0.252672
     Yes        19  0.187250  0.710345
Thur No         45  0.160298  0.266312
     Yes        17  0.163863  0.241255

带有别名的多层分组多函数计算

ftuples = [('Durchschnitt', 'mean'), ('Abweichung', np.var)]
print(grouped['tip_pct', 'total_bill'].agg(ftuples))
                 tip_pct              total_bill            
            Durchschnitt Abweichung Durchschnitt  Abweichung
day  smoker                                                 
Fri  No         0.151650   0.000791    18.420000   25.596333
     Yes        0.174783   0.002631    16.813333   82.562438
Sat  No         0.158048   0.001581    19.661778   79.908965
     Yes        0.147906   0.003767    21.276667  101.387535
Sun  No         0.160113   0.001793    20.506667   66.099980
     Yes        0.187250   0.023757    24.120000  109.046044
Thur No         0.160298   0.001503    17.113111   59.625081
     Yes        0.163863   0.001551    19.190588   69.808518

多层分组非对称列索引多函数计算

agg技术不仅能够接受一个列表,还可以像group一样接受一个dict,dict的name为需要聚合的column name,dict的value为需要应用的函数。甚至你可以传递多个函数而不仅仅是一个函数,函数们放在列表中,作为二级index显示。

print(grouped.sum())
print(grouped.agg({'tip' : np.max, 'size' : 'sum'}))
print(grouped.agg({'tip_pct' : ['min', 'max', 'mean', 'std'],
             'size' : 'sum'}))
             total_bill     tip  size   tip_pct
day  smoker                                    
Fri  No           73.68   11.25     9  0.606602
     Yes         252.20   40.71    31  2.621746
Sat  No          884.78  139.63   115  7.112145
     Yes         893.62  120.77   104  6.212055
Sun  No         1168.88  180.57   167  9.126438
     Yes         458.28   66.82    49  3.557756
Thur No          770.09  120.32   112  7.213414
     Yes         326.24   51.51    40  2.785676

               tip  size
day  smoker             
Fri  No       3.50     9
     Yes      4.73    31
Sat  No       9.00   115
     Yes     10.00   104
Sun  No       6.00   167
     Yes      6.50    49
Thur No       6.70   112
     Yes      5.00    40

              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

没有索引的聚合形式

print(tips.groupby(['day', 'smoker'], as_index=True).mean())
print(tips.groupby(['day', 'smoker'], as_index=False).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
    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

使用内置的函数进行agg

聚合是一种特殊的分组运算,其特点是将多维数据进行运算并且生成一个标量值。

print(people)
print(people.groupby(["one","two","one","one","two"]).mean())
print(people.groupby(["one","two","one","one","two"]).agg("mean"))
               a         b         c         d         e
Joe     0.222491 -0.995911 -1.014604 -0.067544 -0.869915
Steve   0.777247 -1.353332  0.821747 -1.104266  0.156773
Wes    -0.080765       NaN       NaN  0.737536  0.132357
Jim    -0.733980 -0.728372 -1.101054 -0.518711 -0.099602
Travis -0.204770 -1.357502  1.421094  0.523682  1.917264
            a         b         c         d         e
one -0.197418 -0.862142 -1.057829  0.050427 -0.279053
two  0.286238 -1.355417  1.121420 -0.290292  1.037019
            a         b         c         d         e
one -0.197418 -0.862142 -1.057829  0.050427 -0.279053
two  0.286238 -1.355417  1.121420 -0.290292  1.037019

2.3 transform技术:不合并的agg

transform和agg不同的是,其保留了所有分组组内的值,不对聚合整体进行运算。但是其结果填充了对于组内运算的值,这看起来有些奇怪。造成这种情况的原因是,transform运行的函数生成了一个标量值,而这个标量值在组内被广播了。

print(people.groupby(["one","two","one","one","two"]).transform("count"))
print(people.groupby(["one","two","one","one","two"]).agg("count")) #显然下面这个看起来更清晰一些。逻辑上来说。
          a    b    c    d    e
Joe     3.0  2.0  2.0  3.0  3.0
Steve   2.0  2.0  2.0  2.0  2.0
Wes     3.0  2.0  2.0  3.0  3.0
Jim     3.0  2.0  2.0  3.0  3.0
Travis  2.0  2.0  2.0  2.0  2.0
     a  b  c  d  e
one  3  2  2  3  3
two  2  2  2  2  2
def defmean(arr):
    #print("\n","The arr is\n",arr)
    return arr - arr.mean()
res = people.groupby(["one","two","one","one","two"]).transform(defmean) 
#这个函数返回的是标量值,因此比上一个看起来有意义些。
print(res)
               a         b         c         d         e
Joe     0.419909 -0.133770  0.043225 -0.117971 -0.590861
Steve   0.491009  0.002085 -0.299673 -0.813974 -0.880246
Wes     0.116653       NaN       NaN  0.687109  0.411410
Jim    -0.536562  0.133770 -0.043225 -0.569138  0.179451
Travis -0.491009 -0.002085  0.299673  0.813974  0.880246
print(res.groupby(["one","two","one","one","two"]).mean())
                a             b             c             d             e
one -3.700743e-17  5.551115e-17  1.110223e-16 -3.700743e-17 -1.850372e-17
two -2.775558e-17  0.000000e+00  5.551115e-17  0.000000e+00 -1.110223e-16

transform和agg一样,其工作必须产生一个标量或者对应大小的数组,对于前者的标量,其会进行广播。但是,对于apply,就没有这种限制。

2.4 apply技术:动态自定义

apply技术可以传递pieces,然后进行函数运算后返回一个任意东西,经过concat方法被合并成结果,其使用限制取决于你的想象。

print(tips[:5])
   total_bill   tip smoker  day    time  size   tip_pct
0       16.99  1.01     No  Sun  Dinner     2  0.059447
1       10.34  1.66     No  Sun  Dinner     3  0.160542
2       21.01  3.50     No  Sun  Dinner     3  0.166587
3       23.68  3.31     No  Sun  Dinner     2  0.139780
4       24.59  3.61     No  Sun  Dinner     4  0.146808
def top(df, n=5, column='tip_pct'):
    return df.sort_values(by=column)[-n:]
print(top(tips, n=6))
     total_bill   tip smoker  day    time  size   tip_pct
109       14.31  4.00    Yes  Sat  Dinner     2  0.279525
183       23.17  6.50    Yes  Sun  Dinner     4  0.280535
232       11.61  3.39     No  Sat  Dinner     2  0.291990
67         3.07  1.00    Yes  Sat  Dinner     1  0.325733
178        9.60  4.00    Yes  Sun  Dinner     2  0.416667
172        7.25  5.15    Yes  Sun  Dinner     2  0.710345
print(tips.groupby('smoker').apply(top))
            total_bill   tip smoker   day    time  size   tip_pct
smoker                                                           
No     88        24.71  5.85     No  Thur   Lunch     2  0.236746
       18