• 利用Python进行数据分析-Pandas(第六部分-数据聚合与分组运算)


      对数据集进行分组并对各组应用一个函数(无论是聚合还是转换),通常是数据分析工作中的重要环节。在将数据集加载、融合、准备好之后,通常是计算分组统计或生成透视表。pandas提供了一个灵活高效的groupby功能,它使你能以一种自然的方式对数据集进行切片、切块、摘要等操作。

      关系型数据库和SQL能够如此流行的原因之一就是能够方便地对数据进行连接、过滤、转换和聚合。但是,像SQL这样的查询语言所能执行的分组运算的种类很有限。在本部分你将会看到,由Python和pandas强大的表达能力,我们可以执行复杂得多的分组运算(利用任何可以接受pandas对象或NumPy数组的函数)。在本部分,你将会学到:

    • 计算分组摘要统计,如计数、平均值、标准差,或用户自定义函数。
    • 计算分组的概述统计,比如数量、平均值或标准差,或是用户自定义的函数。
    • 应用组内转换或其他运算,如规格化、线性回归、排名或选取子集等。
    • 计算透视表或交叉表。
    • 执行分位数分宜以及其它统计分组分析。

    1、GroupBy机制

      Hadlley Wickham(许多热门R语言包的作者)创造了一个用于表示分组运算的术语"split-apply-combine"(拆分-应用-合并)。(在这里说一句题外话;对于Hadlley Wickham我在知乎上有做过详细的关于他对R语言的可视化包做了详尽的介绍,感兴趣的可以在这里查看:https://zhuanlan.zhihu.com/p/31929985)。第一个阶段,pandas对象(无论是Series、DataFrame还是其他的)中的数据会根据你所提供的一个或多个键被拆分(split)为多组。拆分操作是在对象的特定轴上执行的。例如,DataFrame可以在其行(axis=0)或列(axis=1)上进行分组。然后,将一个函数应用(apply)到各个分组并产生一个新值。最后,所有这些函数的执行结果会被合并(combine)到最终结果对象中。结果对象的形式一般取决于数据上所执行的操作。如下图大致说明了一个简单的分组聚合过程。

    分组键可以由多种形式,且类型不必相同:

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

    注意:后三种都只是快捷方式而已,其最终的目的仍然是产生一组用于拆分对象的值。如果觉得这些东西看起来很抽象,不用担心,将在本部分给出大量的栗子。首先来看看下面这个非常简单的表格型数据集:

    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)})
    print(df)
      key1 key2     data1     data2
    0    a  one -1.749765  0.514219
    1    a  two  0.342680  0.221180
    2    b  one  1.153036 -1.070043
    3    b  two -0.252436 -0.189496
    4    a  one  0.981321  0.255001
    

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

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

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

    print(grouped.mean())
    key1
    a   -0.141921
    b    0.450300
    Name: data1, dtype: float64
    

    稍后将详细讲解.mean()的调用过程。这里最重要的是,数据(Series)根据分组键进行了聚合,产生了一个新的Series,其索引为key1列中的唯一值。之所以结果中索引的名称为key1,是因为原始DataFrame的列df['key1']就叫这个名字。

    如果我们一次传入多个数组的列表,就会得到不同的结果:

    means = df['data1'].groupby([df['key1'], df['key2']]).mean()
    print(means)
    key1  key2
    a     one    -0.384222
          two     0.342680
    b     one     1.153036
          two    -0.252436
    Name: data1, dtype: float64
    

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

    print(means.unstack())
    key2       one       two
    key1                    
    a    -0.384222  0.342680
    b     1.153036 -0.252436
    

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

    states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'])
    years = np.array([2005, 2005, 2006, 2005, 2006])
    print(df['data1'].groupby([states, years]).mean())
    California  2005    0.342680
                2006    1.153036
    Ohio        2005   -1.001101
                2006    0.981321
    Name: data1, dtype: float64
    

    通常,分组信息就位于相同的要处理DataFrame中。这里,你还可以将列名(可以是字符串、数字或其他Python对象)用作分组键:

    print(df.groupby('key1').mean())
             data1     data2
    key1                    
    a    -0.141921  0.330133
    b     0.450300 -0.629770
    print(df.groupby(['key1', 'key2']).mean())
                  data1     data2
    key1 key2                    
    a    one  -0.384222  0.384610
         two   0.342680  0.221180
    b    one   1.153036 -1.070043
         two  -0.252436 -0.189496
    

    你可能已经注意到了,第一个例子在执行df.groupby('key1').mean()时,结果中没有key2列。这是因为df['key2']不是数值数据(俗称“麻烦列”),所以被从结果中排除了。默认情况下,所有数值列都会被聚合,虽然有时可能会被过滤一个子集,稍后就会碰到。

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

    print(df.groupby(['key1', 'key2']).size())
    key1  key2
    a     one     2
          two     1
    b     one     1
          two     1
    dtype: int64
    

    注意,任何分组关键词中的缺失值,都会被从结果中除去。

    2、对分组进行迭代

      GroupBy对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)。看下面的栗子:

    for name, group in df.groupby('key1'):
        print(name)
        print(group)
    a
      key1 key2     data1     data2
    0    a  one -1.749765  0.514219
    1    a  two  0.342680  0.221180
    4    a  one  0.981321  0.255001
    b
      key1 key2     data1     data2
    2    b  one  1.153036 -1.070043
    3    b  two -0.252436 -0.189496
    

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

    for (k1, k2), group in df.groupby(['key1', 'key2']):
        print((k1, k2))
        print(group)
    ('a', 'one')
      key1 key2     data1     data2
    0    a  one -1.749765  0.514219
    4    a  one  0.981321  0.255001
    ('a', 'two')
      key1 key2    data1    data2
    1    a  two  0.34268  0.22118
    ('b', 'one')
      key1 key2     data1     data2
    2    b  one  1.153036 -1.070043
    ('b', 'two')
      key1 key2     data1     data2
    3    b  two -0.252436 -0.189496
    

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

    pieces =dict(list(df.groupby('key1')))
    print(pieces['b'])
      key1 key2     data1     data2
    2    b  one  1.153036 -1.070043
    3    b  two -0.252436 -0.189496
    

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

    print(df.dtypes)
    grouped = df.groupby(df.dtypes, axis=1)
    key1      object
    key2      object
    data1    float64
    data2    float64
    dtype: object
    

    可以如下打印分组:

    for dtype, group in grouped:
        print(dtype)
        print(group)
    float64
          data1     data2
    0 -1.749765  0.514219
    1  0.342680  0.221180
    2  1.153036 -1.070043
    3 -0.252436 -0.189496
    4  0.981321  0.255001
    object
      key1 key2
    0    a  one
    1    a  two
    2    b  one
    3    b  two
    4    a  one
    

    3、选取一列或列的子集

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

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

    是以下代码的语法糖:

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

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

    print(df.groupby(['key1', 'key2'])[['data2']].mean())
                  data2
    key1 key2          
    a    one   0.384610
         two   0.221180
    b    one  -1.070043
         two  -0.189496
    

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

    s_grouped = df.groupby(['key1', 'key2'])['data2']
    print(s_grouped)
    <pandas.core.groupby.generic.SeriesGroupBy object at 0x000001B28E96FBE0>
    print(s_grouped.mean())
    key1  key2
    a     one     0.384610
          two     0.221180
    b     one    -1.070043
          two    -0.189496
    Name: data2, dtype: float64
    

    4、通过字典或Series进行分组

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

    np.random.seed(10)
    people = pd.DataFrame(np.random.rand(5, 5), columns=['a', 'b', 'c', 'd', 'e'], index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'] )
    people.iloc[2:3, [1, 2]] = np.nan
    print(people)
                   a         b         c         d         e
    Joe     0.771321  0.020752  0.633648  0.748804  0.498507
    Steve   0.224797  0.198063  0.760531  0.169111  0.088340
    Wes     0.685360       NaN       NaN  0.512192  0.812621
    Jim     0.612526  0.721755  0.291876  0.917774  0.714576
    Travis  0.542544  0.142170  0.373341  0.674134  0.441833

    现在,假设已知列的分组关系,并希望根据分组计算列的和:

    mapping = {'a': 'red', 'b': 'red', 'c': 'blue', 'd': 'blue', 'e': 'red', 'f': 'orange'}

    现在,你可以将这个字典传给groupby,来构造数组,但我们可以直接传递字典(我包含了键“f”来强调,存在未使用的分组键是可以的):

    by_column = people.groupby(mapping, axis=1)
    print(by_column.sum())
                blue       red
    Joe     1.382452  1.290580
    Steve   0.929642  0.511199
    Wes     0.512192  1.497981
    Jim     1.209650  2.048857
    Travis  1.047474  1.126548

    Series也有同样功能,它可以被看做一个固定大小的映射:

    map_series = pd.Series(mapping)
    print(map_series)
    a       red
    b       red
    c      blue
    d      blue
    e       red
    f    orange
    dtype: object
    print(people.groupby(map_series, axis=1).count())
            blue  red
    Joe        2    3
    Steve      2    3
    Wes        1    2
    Jim        2    3
    Travis     2    3

    5、通过函数进行分组

      比起使用字典或Series,使用Python函数是一种更原生的方法定义分组映射。任何被当做分组键的函数都会在各个索引值上被调用一次,其返回值就会被用作分组名称。具体点说,以上一节的示例DataFrame为例,其索引值为人的名字。你可以计算一个字符串长度的数组,更简单的方法是传入len函数:

    print(people.groupby(len).sum())
              a         b         c         d         e
    3  2.069207  0.742507  0.925524  2.178770  2.025704
    5  0.224797  0.198063  0.760531  0.169111  0.088340
    6  0.542544  0.142170  0.373341  0.674134  0.441833

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

    key_list = ['one', 'one', 'one', 'two', 'two']
    print(people.groupby([len, key_list]).min())
                  a         b         c         d         e
    3 one  0.685360  0.020752  0.633648  0.512192  0.498507
      two  0.612526  0.721755  0.291876  0.917774  0.714576
    5 one  0.224797  0.198063  0.760531  0.169111  0.088340
    6 two  0.542544  0.142170  0.373341  0.674134  0.441833
    

    6、根据索引级别分组

    层次化索引数据集最方便的地方在于它能够根据轴索引的一个级别进行聚合:

    columns = pd.MultiIndex.from_arrays([['US', 'US', 'US', 'JP', 'JP'], [1, 3, 5, 1, 3]], names=['cty', 'tenor'])
    np.random.seed(10)
    hier_df = pd.DataFrame(np.random.rand(4, 5), columns=columns)
    print(hier_df)
    cty          US                            JP          
    tenor         1         3         5         1         3
    0      0.771321  0.020752  0.633648  0.748804  0.498507
    1      0.224797  0.198063  0.760531  0.169111  0.088340
    2      0.685360  0.953393  0.003948  0.512192  0.812621
    3      0.612526  0.721755  0.291876  0.917774  0.714576
    

    要根据级别分组,使用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
    

    7、数据聚合

      聚合指的是任何能够从数组产生标量值的数据转换过程。之前的例子已经用过一些,比如mean、count、min以及sum等。你可能想知道在GroupBy对象上调用mean()时究竟发生了什么。许多常见的聚合函数都有进行优化。然而,除了这些方法,你还可以使用其他的:

    函数名 说明
    count 分组中非NA值的数量
    sum 非NA值的和
    mean 非NA值的平均值
    median 非NA值的算术中位数
    std、var 无偏(分母为n-1)标准差和方法
    min、max 非NA值的最小值和最大值
    prod 非NA值的积
    first、last 第一个和最后一个非NA值

    你可以使用自己发明的聚合函数,还可以调用分组对象上已经定义好的任何方法。例如,quantile可以计算Series或DataFrame列的样本分位数。

     虽然quantile并没有明确地实现于groupBy,但它是一个Series方法,所以这里是能用的。实际上,GroupBy会高效地对Series进行切片,然后对各片调用piece.quantile(0.9),最后将这些结果组装成最终结果:

    grouped = df.groupby('key1')
    print(grouped['data1'].quantile(0.9))
    key1
    a    0.853593
    b    1.012489
    Name: data1, dtype: float64
    

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

    def peak_to_peak(arr):
        return arr.max() - arr.min()
    
    
    print(grouped.agg(peak_to_peak))
             data1     data2
    key1                    
    a     2.731086  0.293039
    b     1.405472  0.880547
    

    你可能注意到,有些方法(如describe)也是可以用在这里的,即使严格来讲,它们并非聚合运算:

    print(grouped.describe())
         data1                      ...     data2                    
         count      mean       std  ...       50%       75%       max
    key1                            ...                              
    a      3.0 -0.141921  1.428579  ...  0.255001  0.384610  0.514219
    b      2.0  0.450300  0.993819  ... -0.629770 -0.409633 -0.189496
    
    [2 rows x 16 columns]
    

    8、面向列的多函数应用

      回到前面小费的例子。使用read_csv导入数据之后,我们添加了一个小费百分比的列tip_pct:

    tips = pd.read_csv('tips.csv', encoding='utf-8')
    tips['tip_pct'] = tips['tip']/tips['total_bill']
    print(tips[:6])
       total_bill   tip     sex smoker  day    time  size   tip_pct
    0       16.99  1.01  Female     No  Sun  Dinner     2  0.059447
    1       10.34  1.66    Male     No  Sun  Dinner     3  0.160542
    2       21.01  3.50    Male     No  Sun  Dinner     3  0.166587
    3       23.68  3.31    Male     No  Sun  Dinner     2  0.139780
    4       24.59  3.61  Female     No  Sun  Dinner     4  0.146808
    5       25.29  4.71    Male     No  Sun  Dinner     4  0.186240
    

    你已经看到,对Series或DataFrame列的聚合运算其实就是使用aggregate(使用自定义函数)或调用诸如mean、std之类的方法。然而,你可能希望对不同的列使用不同的聚合函数,或一次应用多个函数。其实这也好办,将通过一些示例来进行讲解。首先,根据天和smoker对tips进行分组:

    grouped = tips.groupby(['day', 'smoker'])
    grouped_pct = grouped['tip_pct']
    print(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
    

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

    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
    

    这里,我们传递了一组聚合函数进行聚合,独立对数据分组进行评估。

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

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

    对于DataFrame,你还有更多选择,你可以定义一组应用于全部列的一组函数,或不同的列应用不同的函数。假设我们想要对tip_pct和total_bill列计算三个统计信息:

    functions = ['count', 'mean', 'max']
    result = grouped['tip_pct', 'total_bill'].agg(functions)
    print(result)
                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
    

    如你所见,结果DataFrame拥有层次化的列,这相当于分别对各列进行聚合,然后用concat将结果组装到一起,使用列名用做keys参数:

    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
    

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

    print(grouped['tip_pct', 'total_bill'].agg(ftuples))
                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传入一个从列名映射到函数的字典:

    print(grouped.agg({'tip': np.max, 'size': 'sum'}))
                   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
    print(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
    

    只有将多个函数应用到至少一列时,DataFrame才会拥有层次化的列。

    9、以“没有行索引”的形式返回聚合数据

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

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

    当然,对结果调用reset_index也能得到这种形式的结果。使用as_index=False方法可以避免一些不必要的计算。

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

      最通用的GroupBy方法是apply,本节剩余部分将重点讲解它。如图所示,apply会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试将各片段组合到一起:

       回到之前那个小费数据集,假设你想要根据分组选出最高的5个tip_pct值。首先,编写一个选取指定列具有最大值的行的函数:

    def top(df, n=5, column='tip_pct'):
        return df.sort_values(by=column)[-n:]
    print(top(tips, n=6))
         total_bill   tip     sex smoker  day    time  size   tip_pct
    109       14.31  4.00  Female    Yes  Sat  Dinner     2  0.279525
    183       23.17  6.50    Male    Yes  Sun  Dinner     4  0.280535
    232       11.61  3.39    Male     No  Sat  Dinner     2  0.291990
    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
    

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

    print(tips.groupby('smoker').apply(top))
                total_bill   tip     sex smoker   day    time  size   tip_pct
    smoker                                                                   
    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.291990
    Yes    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的各个片段上调用,然后结果由pandas.concat组装到一起,并以分组名称进行了标记。于是,最终结果就有了一个层次化索引,其内层索引值来自原DataFrame。

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

    print(tips.groupby(['smoker', 'day']).apply(top, n=1, column='total_bill'))
                     total_bill    tip     sex smoker   day    time  size   tip_pct
    smoker day                                                                     
    No     Fri  94        22.75   3.25  Female     No   Fri  Dinner     2  0.142857
           Sat  212       48.33   9.00    Male     No   Sat  Dinner     4  0.186220
           Sun  156       48.17   5.00    Male     No   Sun  Dinner     6  0.103799
           Thur 142       41.19   5.00    Male     No  Thur   Lunch     5  0.121389
    Yes    Fri  95        40.17   4.73    Male    Yes   Fri  Dinner     4  0.117750
           Sat  170       50.81  10.00    Male    Yes   Sat  Dinner     3  0.196812
           Sun  182       45.35   3.50    Male    Yes   Sun  Dinner     3  0.077178
           Thur 197       43.11   5.00  Female    Yes  Thur   Lunch     4  0.115982
    

    除了这些基本用法之外,能否充分发挥apply的威力很大程度取决于你的创造力。传入的那个函数能做什么全由你说了算,它只需返回一个pandas对象或标量值即可。本章后续部分的示例主要用于讲解如何利用groupby解决各种各样的问题。

    11、禁止分组键

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

    print(tips.groupby('smoker', group_keys= False).apply(top))
         total_bill   tip     sex smoker   day    time  size   tip_pct
    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.291990
    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
    

    12、分位数和桶分析

      曾在第8章中讲过,pandas有一些能够根据指定面元或样本分位数将数据拆分成多块的工具(比如cut和qcut)。将这些函数跟groupby结合起来,就能非常轻松的实现对数据集的桶(bucket)或分位数(quantile)分析了。以下面这个简单的随机数据集为例,我们利用cut将其装入长度相等的桶中:

    持续更新中......

                  
    申明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    线程的资源释放(一)
    iOS开发完整项目
    iOS开发多线程技术方案
    Windows 7 Beta泄漏版存在安全问题 狼人:
    工信部:黑客入侵等是网络安全防护工作的重点 狼人:
    微软下周2将发布13个补丁 修复26个安全漏洞 狼人:
    调查显示互联网14%SSL认证不安全 狼人:
    09年恶意软件放缓 2010年共享最危险 狼人:
    IE曝新安全漏洞 千万网民隐私遭受威胁 狼人:
    安全关注:2009年信息安全八大预测 狼人:
  • 原文地址:https://www.cnblogs.com/lsyb-python/p/12020483.html
Copyright © 2020-2023  润新知