Data Whale第20期组队学习 Pandas学习—分组


任何分组(groupby)操作都涉及原始对象的以下操作:分割对象、应用一个函数和结合的结果。
在许多情况下,任何分组(groupby)可以执行以下操作 :聚合 (计算汇总统计)、转换 (执行一些特定于组的操作)和过滤 (在某些情况下丢弃数据)。实现分组操作,必须明确分组依据 、 数据来源 、 操作及其返回结果等三个要素。

一、groupby的基础操作

import pandas as pd
import numpy as np
df=pd.DataFrame({'A': ['w', 'e', 'l', 'c', 'o', 'm', 'e', 'c'],
                 'B': [3, 8, 7, 4, 6, 2, 5, 1],
                 'C': [112, 198, 147, 154, 125, 187, 92, 123]})
print("df=",df)
# df=    A  B    C
# 0  w  3  112
# 1  e  8  198
# 2  l  7  147
# 3  c  4  154
# 4  o  6  125
# 5  m  2  187
# 6  e  5   92
# 7  c  1  123
print("df.groupby('A').mean()=",df.groupby('A').mean()) # 按A列分组(groupby),获取其他列的均值
# df.groupby('A').mean()=      B      C
# A
# c  2.5  138.5
# e  6.5  145.0
# l  7.0  147.0
# m  2.0  187.0
# o  6.0  125.0
# w  3.0  112.0

print("df.groupby(['A','B']).mean()=",df.groupby(['A','B']).mean()) # 按多列分组(groupby),获取其他列的均值
# df.groupby(['A','B']).mean()=        C
# A B
# c 1  123
#   4  154
# e 5   92
#   8  198
# l 7  147
# m 2  187
# o 6  125
# w 3  112
# 分组后,可以选取单列数据,或者多个列组成的列表(list)进行运算
df1=pd.DataFrame([[1,9,5,6,1],[2,7,8,7,3],[1,5,2,4,6],[3,6,9,4,1],[5,6,4,8,2]]
                 ,columns=['C','H','I','N','A'])
print("df1=",df1)
# df1=    C  H  I  N  A
# 0  1  9  5  6  1
# 1  2  7  8  7  3
# 2  1  5  2  4  6
# 3  3  6  9  4  1
# 4  5  6  4  8  2
x=df1.groupby("I")
print("x['H'].mean()=",x['H'].mean())
# x['H'].mean()= I
# 2    5
# 4    6
# 5    9
# 8    7
# 9    6
# Name: H, dtype: int64
print("x[['C','N']].mean()=",x[['C','N']].mean())
# x[['C','N']].mean()=    C  N
# I
# 2  1  4
# 4  5  8
# 5  1  6
# 8  2  7
# 9  3  4
print("x.agg({'C':'mean','A':'sum'})=",x.agg({'C':'mean','A':'sum'}))
# x.agg({'C':'mean','A':'sum'})=    C  A
# I
# 2  1  6
# 4  5  2
# 5  1  1
# 8  2  3
# 9  3  1

1.1 聚合方法size()和count()

size与count的区别: size计数包含Nan值,而count计数不包含Nan值。

df2 = pd.DataFrame({"Name":["Alice", "Bob", "Mallory", "Mallory", "Bob" , "Mallory"],
                    "City":["Seattle", "Seattle", "Portland", "Seattle", "Seattle", "Portland"],
                    "Val": [5, 7, 2, np.nan, np.nan, 9]})
print("df2=",df2)
# df2=       Name      City  Val
# 0    Alice   Seattle  5.0
# 1      Bob   Seattle  7.0
# 2  Mallory  Portland  2.0
# 3  Mallory   Seattle  NaN
# 4      Bob   Seattle  NaN
# 5  Mallory  Portland  9.0
print("df2.groupby(['Name', 'City'],as_index=False)['Val'].count()=",df2.groupby(['Name', 'City'], as_index=False)['Val'].count())
# df2.groupby(['Name', 'City'],as_index=False)['Val'].count()=       Name      City  Val
# 0    Alice   Seattle    1
# 1      Bob   Seattle    1
# 2  Mallory  Portland    2
# 3  Mallory   Seattle    0
print('df2.groupby(["Name", "City"])["Val"].size().reset_index(name="Size")=',
      df2.groupby(["Name", "City"])['Val'].size().reset_index(name='Size'))
# df2.groupby(["Name", "City"])["Val"].size().reset_index(name="Size")=       Name      City  Size
# 0    Alice   Seattle     1
# 1      Bob   Seattle     2
# 2  Mallory  Portland     2
# 3  Mallory   Seattle     1

二、聚合函数

2.1 内置聚合函数

直接定义在groupby对象的聚合函数有如下函数:
max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod。

df3 = pd.read_csv('D:/binchen/txzq/data/learn_pandas.csv')
print("df3.groupby('Gender')['Height'].median()=",
      df3.groupby('Gender')['Height'].median())
# df3.groupby('Gender')['Height'].median()= Gender
# Female    159.6
# Male      173.4
# Name: Height, dtype: float64
condition = df3.Weight > df3.Weight.mean()
print("df3.groupby(condition)['Height'].mean()=",df3.groupby(condition)['Height'].mean())
# df3.groupby(condition)['Height'].mean()= Weight
# False    159.034646
# True     172.705357
# Name: Height, dtype: float64

item = np.random.choice(list('abc'), df3.shape[0])
print("df3.groupby(item)['Height'].mean()=",
      df3.groupby(item)['Height'].mean())
# df3.groupby(item)['Height'].mean()= a    163.128571
# b    162.652000
# c    163.702857
# Name: Height, dtype: float64
gb = df3.groupby('Gender')['Height']
print("gb.idxmin()=",gb.idxmin())
# gb.idxmin()= Gender
# Female    143
# Male      199
# Name: Height, dtype: int64
print("gb.quantile(0.85)=",gb.quantile(0.85))
# gb.quantile(0.85)= Gender
# Female    164.57
# Male      179.55
# Name: Height, dtype: float64
print("gb.sum()=",gb.sum())
# gb.sum()= Gender
# Female    21014.0
# Male       8854.9
# Name: Height, dtype: float64
print("gb.prod()=",gb.prod())
# gb.prod()= Gender
# Female    4.232080e+290
# Male      1.594210e+114
# Name: Height, dtype: float64
print("gb.count()=",gb.count())
# gb.count()= Gender
# Female    132
# Male       51
# Name: Height, dtype: int64

# 这些聚合函数当传入的数据来源包含多个列时,将按照列进行迭代计算
gb1 = df3.groupby('Gender')[['Height', 'Weight']]
print("gb1.max()=",gb1.max())
# gb1.max()=         Height  Weight
# Gender
# Female   170.2    63.0
# Male     193.9    89.0
print("gb1.min()=",gb1.min())
# gb1.min()=         Height  Weight
# Gender                
# Female   145.4    34.0
# Male     155.7    51.0

2.2 agg方法

即使在 groupby 对象上定义了许多方便的函数,但仍然有以下不便之处:

1)无法同时使用多个函数
2)无法对特定的列使用特定的聚合函数
3) 无法使用自定义的聚合函数
4) 无法直接对结果的列名在聚合前进行自定义命名

使用 agg 函数解决以上四类问题。
1、使用多个聚合函数
当使用多个聚合函数时,需要用列表的形式把内置聚合函数的对应的字符串传入,先前提到的所有字符串都是合法的。

print("gb1.agg(['sum','idxmax','skew','min','max','prod'])=",
      gb1.agg(['sum','idxmax','skew','min','max','prod']))
# gb1.agg(['sum','idxmax','skew','min','max','prod'])=          Height                          ...    Weight                           
#             sum idxmax      skew    min  ...      skew   min   max           prod
# Gender                                   ...                                     
# Female  21014.0     28 -0.219253  145.4  ... -0.268482  34.0  63.0  3.039202e+226
# Male     8854.9    193  0.437535  155.7  ... -0.332393  51.0  89.0  2.540227e+100
# 
# [2 rows x 12 columns]
# 从运行结果上看,列索引为多级索引,第一层为数据源,第二层为使用的聚合方法,分别逐一对列使用聚合,因此结果为12列。

2、对特定的列使用特定的聚合函数
对于方法和列的特殊对应,可以通过构造字典传入 agg 中实现,其中字典以列名为键,以聚合字符串或字符串列表为值。

print("gb1.agg({'Height':['mean','max'],'Weight':'count'})=",
      gb1.agg({'Height':['mean','max'],'Weight':'count'}))
# gb1.agg({'Height':['mean','max'],'Weight':'count'})=            Height        Weight
#              mean    max  count
# Gender
# Female  159.19697  170.2    135
# Male    173.62549  193.9     54
print("gb1.agg({'Height':['mean','max','sum','idxmax','skew','count'],'Weight':['mean','max','sum','idxmax','skew','count']})=",
      gb1.agg({'Height':['mean','max','sum','idxmax','skew','count'],'Weight':['mean','max','sum','idxmax','skew','count']}))
# gb1.agg({'Height':['mean','max','sum','idxmax','skew','count'],'Weight':['mean','max','sum','idxmax','skew','count']})=            Height                         ...  Weight
#              mean    max      sum idxmax  ...     sum idxmax      skew count
# Gender                                    ...
# Female  159.19697  170.2  21014.0     28  ...  6469.0     28 -0.268482   135
# Male    173.62549  193.9   8854.9    193  ...  3929.0      2 -0.332393    54
#
# [2 rows x 12 columns]

3、使用自定义函数
在 agg 中可以使用具体的自定义函数, 但是需要注意传入函数的参数是之前数据源中的列,逐列进行计算 。下面分组计算身高和体重的极差的代码如下:

print("gb1.agg(lambda x: x.mean()-x.min())=",gb1.agg(lambda x: x.mean()-x.min()))
# gb1.agg(lambda x: x.mean()-x.min())=           Height     Weight
# Gender                     
# Female  13.79697  13.918519
# Male    17.92549  21.759259
def my_function(x):
    res = 'High'
    if x.mean()<= df3[x.name].mean():
        res='Low'
    return  res
print("gb1.agg(my_function)=",gb1.agg(my_function))
# gb1.agg(my_function())=        Height Weight
# Gender              
# Female    Low    Low
# Male     High   High

4、聚合结果重命名
如果想要对结果进行重命名,只需要将上述函数的位置改写成元组,元组的第一个元素为新的名字,第二个位置为原来的函数,包括聚合字符串和自定义函数。

print("gb1.agg([('range', lambda x: x.max()-x.min()), ('my_count', 'count')])=",
      gb1.agg([('range', lambda x: x.max()-x.min()), ('my_count', 'count')]))
# gb1.agg([('range', lambda x: x.max()-x.min()), ('my_count', 'count')])=        Height          Weight
#         range my_count  range my_count
# Gender
# Female   24.8      132   29.0      135
# Male     38.2       51   38.0       54
print("gb1.agg({'Height': [('my_function', my_function), 'sum'],'Weight': lambda x: x.min()})=",
      gb1.agg({'Height': [('my_function', my_function), 'sum'],'Weight': lambda x: x.min()}))
# gb1.agg({'Height': [('my_function', my_function), 'sum'],'Weight': lambda x: x.min()})=             Height            Weight
#        my_function      sum <lambda>
# Gender
# Female         Low  21014.0     34.0
# Male          High   8854.9     51.0

# 需要注意,使用对一个或者多个列使用单个聚合的时候,重命名需要加方括号,
# 否则就不知道是新的名字还是手误输错的内置函数字符串
print("gb1.agg([('my_idxmax', 'idxmax')])=",
      gb1.agg([('my_idxmax', 'idxmax')]))
# gb1.agg([('my_idxmax', 'idxmax')])=           Height    Weight
#        my_idxmax my_idxmax
# Gender
# Female        28        28
# Male         193         2
print("gb1.agg({'Height': [('my_function', my_function), 'prod'],'Weight': [('range', lambda x:x.min())]})=",
      gb1.agg({'Height': [('my_function', my_function), 'prod'],'Weight': [('range', lambda x:x.min())]}))
# gb1.agg({'Height': [('my_function', my_function), 'prod'],'Weight': [('range', lambda x:x.min())]})=             Height                Weight
#        my_function           prod  range
# Gender                                  
# Female         Low  4.232080e+290   34.0
# Male          High  1.594210e+114   51.0

三、变换和过滤

3.1 变换函数与transform方法

变换函数的返回值为同长度的序列,最常用的内置变换函数是累计函数: cumcount/cumsum/cumprod/cummax/cummin ,它们的使用方式和聚合函数类似,只不过完成的是组内累计操作。

print("gb1.cummax().head()=",gb1.cummax().head())
# gb1.cummax().head()=    Height  Weight
# 0   158.9    46.0
# 1   166.5    70.0
# 2   188.9    89.0
# 3     NaN    46.0
# 4   188.9    89.0

当用自定义变换时需要使用 transform 方法,被调用的自定义函数, 其传入值为数据源的序列 ,与 agg 的传入类型是一致的,其最后的返回结果是行列索引与数据源一致的 DataFrame .现对身高和体重进行分组标准化,即减去组均值后除以组的标准差,代码如下:

print("gb1.transform(lambda x: (x-x.mean())/x.std()).head()=",
      gb1.transform(lambda x: (x-x.mean())/x.std()).head())
# gb1.transform(lambda x: (x-x.mean())/x.std()).head()=      Height    Weight
# 0 -0.058760 -0.354888
# 1 -1.010925 -0.355000
# 2  2.167063  2.089498
# 3       NaN -1.279789
# 4  0.053133  0.159631

transform 只能返回同长度的序列,但事实上还可以返回一个标量,这会使得结果被广播到其所在的整个组,这种标量广播的技巧在特征工程中是非常常见的。例如,构造两列新特征来分别表示样本所在性别组的身高均值和体重均值。代码如下:

print("gb1.transform('mean').head()=", gb1.transform('mean').head())
# gb1.transform('mean').head()=       Height     Weight
# 0  159.19697  47.918519
# 1  173.62549  72.759259
# 2  173.62549  72.759259
# 3  159.19697  47.918519
# 4  173.62549  72.759259

3.2 组索引与过滤

过滤在分组中是对于组的过滤,而索引是对于行的过滤,在第二章中的返回值,无论是布尔列表还是元素列表或者位置列表,本质上都是对于行的筛选,即如果筛选条件的则选入结果的表,否则不选入。
组过滤作为行过滤的推广,指的是如果对一个组的全体所在行进行统计的结果返回 True 则会被保留, False 则该组会被过滤,最后把所有未被过滤的组其对应的所在行拼接起来作为 DataFrame 返回。
在 groupby 对象中,定义了 filter 方法进行组的筛选,其中自定义函数的输入参数为数据源构成的 DataFrame 本身,在之前例子中定义的 groupby 对象中,传入的就是 df[[‘Height’, ‘Weight’]] ,因此所有表方法和属性都可以在自定义函数中相应地使用,同时只需保证自定义函数的返回为布尔值即可。
例如,在原表中通过过滤得到所有容量大于100的组,代码如下:

print("gb1.filter(lambda x: x.shape[0] > 120).head()=",
      gb1.filter(lambda x: x.shape[0] > 120).head())
# gb1.filter(lambda x: x.shape[0] > 120).head()=    Height  Weight
# 0   158.9    46.0
# 3     NaN    41.0
# 5   158.0    51.0
# 6   162.5    52.0
# 7   161.9    50.0

四、 跨列分组

4.1 apply的引入

定义身体质量指数BMI: B M I = W e i g h t / H e i g h t 2 BMI=Weight/Height2 BMI=Weight/Height2 其中体重和身高的单位分别为千克和米,需要分组计算组BMI的均值。
首先,这不是过滤操作,因此 filter 不符合要求;其次,返回的均值是标量而不是序列,因此 transform 不符合要求;最后,似乎使用 agg 函数能够处理,但是之前强调过聚合函数是逐列处理的,而不能够 多列数据同时处理 。由此,引出了 apply 函数来解决这一问题。

4.2 apply的使用

在设计上, apply 的自定义函数传入参数与 filter 完全一致,但是后者只允许返回布尔值。现如下解决上述计算问题:

def BMI(b):
    Height = b['Height'] / 100
    Weight = b['Weight']
    BMI_value = Weight / Height ** 2
    return BMI_value.mean()
print("gb1.apply(BMI)=",gb1.apply(BMI))
# gb1.apply(BMI)= Gender
# Female    18.860930
# Male      24.318654
# dtype: float64
"""除了返回标量之外, apply 方法还可以返回一维 Series 和二维 DataFrame ,"""

1、标量情况:结果得到的是 Series ,索引与 agg 的结果一致

gb2 = df3.groupby(['Gender','Test_Number'])[['Height','Weight']]
print("gb2.apply(lambda x: 0)=",gb2.apply(lambda x: 0))
# gb2.apply(lambda x: 0)= Gender  Test_Number
# Female  1              0
#         2              0
#         3              0
# Male    1              0
#         2              0
#         3              0
# dtype: int64
print("gb2.apply(lambda x: [0, 0,1])=",gb2.apply(lambda x: [0, 0,1]))
# gb2.apply(lambda x: [0, 0,1])= Gender  Test_Number
# Female  1              [0, 0, 1]
#         2              [0, 0, 1]
#         3              [0, 0, 1]
# Male    1              [0, 0, 1]
#         2              [0, 0, 1]
#         3              [0, 0, 1]
# dtype: object

2、Series 情况:得到的是 DataFrame ,行索引与标量情况一致,列索引为 Series 的索引

print("gb2.apply(lambda x: pd.Series([0,0,1,6,2],index=['w','o','r','l','d']))=",
      gb2.apply(lambda x: pd.Series([0,0,1,6,2],index=['w','o','r','l','d'])))
# gb2.apply(lambda x: pd.Series([0,0,1,6,2],index=['w','o','r','l','d']))=                     w  o  r  l  d
# Gender Test_Number               
# Female 1            0  0  1  6  2
#        2            0  0  1  6  2
#        3            0  0  1  6  2
# Male   1            0  0  1  6  2
#        2            0  0  1  6  2
#        3            0  0  1  6  2

3、DataFrame 情况:得到的是 DataFrame ,行索引最内层在每个组原先 agg 的结果索引上,再加一层返回的 DataFrame 行索引,同时分组结果 DataFrame 的列索引和返回的 DataFrame 列索引一致。

print("gb.apply(lambda x: pd.DataFrame(np.ones((3,3)),index = ['w','q','x'],columns=pd.Index([('d','x','s'),('o','y','z'),('e','r','u')])))=",
      gb.apply(lambda x: pd.DataFrame(np.ones((3,3)),index = ['w','q','x'],columns=pd.Index([('d','x','s'),('o','y','z'),('e','r','u')]))))
# gb.apply(lambda x: pd.DataFrame(np.ones((3,3)),index = ['w','q','x'],columns=pd.Index([('d','x','s'),('o','y','z'),('e','r','u')])))=             d    o    e
#             x    y    r
#             s    z    u
# Gender                 
# Female w  1.0  1.0  1.0
#        q  1.0  1.0  1.0
#        x  1.0  1.0  1.0
# Male   w  1.0  1.0  1.0
#        q  1.0  1.0  1.0
#        x  1.0  1.0  1.0

参考文献

1、https://www.yiibai.com/pandas/python_pandas_groupby.html
2、https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch4.html#id2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值