python之数据聚合及分组运算

对数据集进行分组并对各组应用一个函数(无论是聚合还是转换),这是数据分析工作中的重要环节。在将数据集准备好之后,通常的任务就是计算分组统计或生成透视表。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
"""

猜你喜欢

转载自blog.csdn.net/lzh_12345/article/details/79861901