pandas学习-分组-task14

一、分组模式及其对象

  1. 分组的一般模式
    分组操作的三个要素:分组依据 、 数据来源 、 操作及其返回结果 。分组代码的一般模式即:
    df.groupby(分组依据)[数据来源].使用操作
    例如:现在返回到学生体测的数据集上,如果想要按照性别统计身高中位数,就可以如下写出:
    原数据
df.groupby('Gender')['Height'].median()
# Gender
# Female    159.6
# Male      173.4
# Name: Height, dtype: float64
  1. 分组依据的本质
    多个维度进行分组,只需在 groupby 中传入相应列名构成的列表即可。例如,现希望根据学校和性别进行分组,统计身高的均值就可以如下写出:
df.groupby(['School', 'Gender'])['Height'].mean()
# School                         Gender
# Fudan University               Female    158.776923
#                                Male      174.212500
# Peking University              Female    158.666667
#                                Male      172.030000
# Shanghai Jiao Tong University  Female    159.122500
#                                Male      176.760000
# Tsinghua University            Female    159.753333
#                                Male      171.638889
# Name: Height, dtype: float64
通过一定的复杂逻辑来分组,例如根据学生体重是否超过总体均值来分组,同样还是计算身高的均值。
condition = df.Weight > df.Weight.mean()
df.groupby(condition)['Height'].mean()
# Weight
# False    159.034646
# True     172.705357
# Name: Height, dtype: float64
从索引可以看出,其实最后产生的结果就是按照条件列表中元素的值(此处是 True 和 False )来分组,下面用随机传入字母序列来验证这一想法:(这里就像随机把每一个样本分成a,b,c三个组)
item = np.random.choice(list('abc'), df.shape[0])#随机生成200个值(在['a', 'b', 'c']中随机取)
# numpy.random.choice(a, size=None, replace=True, p=None)
# #从a(只要是ndarray都可以,但必须是一维的)中随机抽取数字,并组成指定大小(size)的数组
# #replace:True表示可以取相同数字,False表示不可以取相同数字
# #数组p:与数组a相对应,表示取数组a中每个元素的概率,默认为选取每个元素的概率相同。
df.groupby(item)['Height'].mean()
# a    164.430189
# b    161.986441
# c    163.336620
# Name: Height, dtype: float64
如果传入多个序列进入 groupby ,那么最后分组的依据就是这两个序列对应行的唯一组合:个人理解,先根据学生体重是否超过总体均值来分组True or False,在True里面随机分成3个组a,b,c。同理,False也一样。
df.groupby([condition, item])['Height'].mean()
# Weight   
# False   a    159.269444
#         b    158.326190
#         c    159.469388
# True    a    175.358824
#         b    171.029412
#         c    171.950000
# Name: Height, dtype: float64
由此可以看出,之前传入列名只是一种简便的记号,事实上等价于传入的是一个或多个列,最后分组的依据来自于**数据来源组合的unique值**,通过 drop_duplicates 就能知道具体的组类别:
#drop_duplicates()可以选择是否保留重复值,
#默认是保留重复值,想要不保留重复值的话直接设置参数keep为False即可。
df[['School', 'Gender']].drop_duplicates()

#                            School  Gender
# 0   Shanghai Jiao Tong University  Female
# 1               Peking University    Male
# 2   Shanghai Jiao Tong University    Male
# 3                Fudan University  Female
# 4                Fudan University    Male
# 5             Tsinghua University  Female
# 9               Peking University  Female
# 16            Tsinghua University    Male
df.groupby([df['School'], df['Gender']])['Height'].mean()
# School                         Gender
# Fudan University               Female    158.776923
#                                Male      174.212500
# Peking University              Female    158.666667
#                                Male      172.030000
# Shanghai Jiao Tong University  Female    159.122500
#                                Male      176.760000
# Tsinghua University            Female    159.753333
#                                Male      171.638889
# Name: Height, dtype: float64
  1. Groupby对象
    做分组操作时,所调用的方法都来自于 pandas 中的 groupby 对象,这个对象上定义了许多方法,也具有一些方便的属性。
gb = df.groupby(['School', 'Grade'])
通过 ngroups 属性,可以得到分组个数:
gb.ngroups
# 16
通过 groups 属性,可以返回从 组名 映射到 组索引列表 的字典:
res = gb.groups
res.keys() # 字典的键
# dict_keys([('Fudan University', 'Freshman'), 
#            ('Fudan University', 'Junior'), 
#            ('Fudan University', 'Senior'), 
#            ('Fudan University', 'Sophomore'), 
#            ('Peking University', 'Freshman'), 
#            ('Peking University', 'Junior'), 
#            ('Peking University', 'Senior'), 
#            ('Peking University', 'Sophomore'), 
#            ('Shanghai Jiao Tong University', 'Freshman'), 
#            ('Shanghai Jiao Tong University', 'Junior'), 
#            ('Shanghai Jiao Tong University', 'Senior'), 
#            ('Shanghai Jiao Tong University', 'Sophomore'), 
#            ('Tsinghua University', 'Freshman'), 
#            ('Tsinghua University', 'Junior'), 
#            ('Tsinghua University', 'Senior'), 
#            ('Tsinghua University', 'Sophomore')])
res.values()# 字典的值
# dict_values([Int64Index([15, 28, 63, 70, 73, 105, 108, 157, 186], dtype='int64'), Int64Index([26, 41, 82, 84, 90, 107, 145, 152, 173, 187, 189, 195], dtype='int64'), Int64Index([39, 46, 49, 52, 66, 77, 112, 129, 131, 138, 144], dtype='int64'), Int64Index([3, 4, 37, 48, 68, 98, 135, 170], dtype='int64'), Int64Index([1, 32, 35, 36, 38, 45, 54, 57, 88, 96, 99, 140, 185], dtype='int64'), Int64Index([9, 20, 59, 72, 75, 102, 159, 183], dtype='int64'), Int64Index([30, 86, 116, 127, 130, 132, 147, 194], dtype='int64'), Int64Index([29, 61, 83, 101, 120], dtype='int64'), Int64Index([0, 6, 10, 60, 114, 117, 119, 121, 141, 148, 149, 153, 184], dtype='int64'), Int64Index([31, 42, 50, 56, 58, 64, 85, 93, 115, 122, 143, 155, 164, 172, 174,
#             188, 190],
#            dtype='int64'), Int64Index([  2,  12,  19,  21,  22,  23,  79,  87,  89, 103, 104, 109, 123,
#             134, 156, 161, 165, 166, 171, 192, 197, 198],
#            dtype='int64'), Int64Index([13, 65, 71, 124, 167], dtype='int64'), Int64Index([5, 8, 33, 34, 43, 44, 47, 51, 62, 67, 81, 111, 125, 133, 136, 142,
#             146],
#            dtype='int64'), Int64Index([  7,  11,  16,  17,  27,  69,  94,  95, 113, 118, 128, 137, 150,
#             154, 158, 160, 162, 163, 169, 176, 177, 191],
#            dtype='int64'), Int64Index([14, 18, 24, 25, 78, 92, 100, 126, 168, 175, 179, 180, 193, 196], dtype='int64'), Int64Index([40, 53, 55, 74, 76, 80, 91, 97, 106, 110, 139, 151, 178, 181, 182,
#             199],
#            dtype='int64')])
当 size 作为 DataFrame 的属性时,返回的是表长乘以表宽的大小,但在 groupby 对象上表示统计每个组的元素个数:
gb.size()
# School                         Grade    
# Fudan University               Freshman      9
#                                Junior       12
#                                Senior       11
#                                Sophomore     8
# Peking University              Freshman     13
#                                Junior        8
#                                Senior        8
#                                Sophomore     5
# Shanghai Jiao Tong University  Freshman     13
#                                Junior       17
#                                Senior       22
#                                Sophomore     5
# Tsinghua University            Freshman     17
#                                Junior       22
#                                Senior       14
#                                Sophomore    16
# dtype: int64
通过 get_group 方法可以直接获取所在组对应的行,此时必须知道组的具体名字:
gb.get_group(('Fudan University', 'Freshman')).iloc[:3, :3] # 展示('Fudan University', 'Freshman')的前三行,前三列
#               School     Grade             Name
# 15  Fudan University  Freshman  Changqiang Yang
# 28  Fudan University  Freshman     Gaoqiang Qin
# 63  Fudan University  Freshman     Gaofeng Zhao
  1. 分组的三大操作
    三种类型分组返回的数据型态并不一样:
    第一个例子中,每一个组返回一个标量值,可以是平均值、中位数、组容量 size 等
    第二个例子中,做了原序列的标准化处理,也就是说每组返回的是一个 Series 类型
    第三个例子中,既不是标量也不是序列,返回的整个组所在行的本身,即返回了 DataFrame 类型

二、聚合函数

  1. 内置聚合函数
    直接定义在groupby对象的聚合函数,因为它的速度基本都会经过内部的优化,使用功能时应当优先考虑。根据返回标量值的原则,包括如下函数: max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod 。
gb = df.groupby('Gender')['Height']
gb.idxmin()#计算能够获取到最小值的索引位置(整数)
# Gender
# Female    143
# Male      199
# Name: Height, dtype: int64
gb.quantile(0.95)#分位数
# Gender
# Female    166.8
# Male      185.9
# Name: Height, dtype: float64
这些聚合函数当传入的数据来源包含多个列时,将按照列进行迭代计算:
gb = df.groupby('Gender')[['Height', 'Weight']]
print(gb.max())
#         Height  Weight
# Gender                
# Female   170.2    63.0
# Male     193.9    89.0
  1. agg方法
    虽然在 groupby 对象上定义了许多方便的函数,但仍然有以下不便之处:
    1)无法同时使用多个函数
    2)无法对特定的列使用特定的聚合函数
    3)无法使用自定义的聚合函数
    4)无法直接对结果的列名在聚合前进行自定义命名
    下面说明如何通过 agg 函数解决这四类问题:

【a】使用多个函数
当使用多个聚合函数时,需要用列表的形式把内置聚合函数对应的字符串传入,先前提到的所有字符串都是合法的。
skew样本值的偏度

从结果看,此时的列索引为多级索引,第一层为数据源,第二层为使用的聚合方法,分别逐一对列使用聚合,因此结果为6列。
print(gb.agg(['sum', 'idxmax', 'skew']))#skew样本值的偏度(三阶矩)
#          Height                   Weight                 
#             sum idxmax      skew     sum idxmax      skew
# Gender                                                   
# Female  21014.0     28 -0.219253  6469.0     28 -0.268482
# Male     8854.9    193  0.437535  3929.0      2 -0.332393

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

print(gb.agg({'Height':['mean','max'], 'Weight':'count'}))
#            Height        Weight
#              mean    max  count
# Gender                         
# Female  159.19697  170.2    135
# Male    173.62549  193.9     54

【c】使用自定义函数

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

print(gb.agg(lambda x: x.mean()-x.min()))
#           Height     Weight
# Gender                     
# Female  13.79697  13.918519
# Male    17.92549  21.759259
由于传入的是序列,因此序列上的方法和属性都是可以在函数中使用的,只需保证返回值是**标量**即可。下面的例子是指,如果组的指标均值,超过该指标的总体均值,返回High,否则返回Low。
def my_func(s):
    res = 'High'
    if s.mean() <= df[s.name].mean():
        print(s.name)
#         Height
#         Weight
        res = 'Low'
    return res
print(gb.agg(my_func))
#        Height Weight
# Gender              
# Female    Low    Low
# Male     High   High

【d】聚合结果重命名

如果想要对聚合结果的列名进行重命名,只需要将上述函数的位置改写成元组,元组的第一个元素为新的名字,第二个位置为原来的函数,包括聚合字符串和自定义函数,现举若干例子说明:

个人理解:第一个例子’range’为新的聚合结果的列名,lambda x: x.max()-x.min()为第一个聚合函数,'my_sum’为第二个聚合结果的列名,‘sum’为第二个聚合函数
第二个例子对’Height’这列数据第一个聚合操作my_func,聚合结果的列名为’my_func’,第二个聚合函数是sum(没有指定聚合结果的列名,所以也是sum),对’Weight’这列数据的操作是lambda x:x.max()。

print(gb.agg([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')]))
#        Height          Weight        
#         range   my_sum  range  my_sum
# Gender                               
# Female   24.8  21014.0   29.0  6469.0
# Male     38.2   8854.9   38.0  3929.0
print(gb.agg({'Height': [('my_func', my_func), 'sum'],'Weight': lambda x:x.max()}))
#         Height            Weight
#        my_func      sum <lambda>
# Gender                          
# Female     Low  21014.0     63.0
# Male      High   8854.9     89.0
另外需要注意,使用对一个或者多个列使用单个聚合的时候,重命名需要加方括号,否则就不知道是新的名字还是手误输错的内置函数字符串:
print(gb.agg([('my_sum', 'sum')]))
#          Height  Weight
#          my_sum  my_sum
# Gender                 
# Female  21014.0  6469.0
# Male     8854.9  3929.0
print(gb.agg({'Height': [('my_func', my_func), 'sum'],'Weight': [('range', lambda x:x.max())]}))
#         Height          Weight
#        my_func      sum  range
# Gender                        
# Female     Low  21014.0   63.0
# Male      High   8854.9   89.0

三、变换和过滤

  1. 变换函数与transform方法
    变换函数的返回值为同长度的序列,最常用的内置变换函数是累计函数: cumcount/cumsum/cumprod/cummax/cummin ,它们的使用方式和聚合函数类似,只不过完成的是组内累计操作。
    累计函数
print(gb.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(gb.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(gb.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
  1. 组索引与过滤

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

   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

四、跨列分组

  1. apply的引入
    apply的引入

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

def BMI(x):
    Height = x['Height']/100
    Weight = x['Weight']
    BMI_value = Weight/Height**2
    return BMI_value.mean()
gb.apply(BMI)
# Gender
# Female    18.860930
# Male      24.318654
# dtype: float64
除了返回标量之外, apply 方法还可以返回一维 Series 和二维 DataFrame
【a】标量情况:结果得到的是 Series ,索引与 agg 的结果一致 
gb = df.groupby(['Gender','Test_Number'])[['Height','Weight']]
gb.apply(lambda x: 0)
# Gender  Test_Number
# Female  1              0
#         2              0
#         3              0
# Male    1              0
#         2              0
#         3              0
# dtype: int64
gb.apply(lambda x: [0, 0]) # 虽然是列表,但是作为返回值仍然看作标量
# Gender  Test_Number
# Female  1              [0, 0]
#         2              [0, 0]
#         3              [0, 0]
# Male    1              [0, 0]
#         2              [0, 0]
#         3              [0, 0]
# dtype: object
【b】 Series 情况:得到的是 DataFrame ,行索引与标量情况一致,列索引为 Series 的索引
print(gb.apply(lambda x: pd.Series([0,0],index=['a','b'])))
#                     a  b
# Gender Test_Number      
# Female 1            0  0
#        2            0  0
#        3            0  0
# Male   1            0  0
#        2            0  0
#        3            0  0
【c】 DataFrame 情况:得到的是 DataFrame ,行索引最内层在每个组原先 agg 的结果索引上,再加一层返回的 DataFrame 行索引,同时分组结果 DataFrame 的列索引和返回的 DataFrame 列索引一致。
gb.apply(lambda x: pd.DataFrame(np.ones((2,2)),
                                index = ['a','b'],
                                columns=pd.Index([('w','x'),('y','z')])))

c

最后需要强调的是, apply 函数的灵活性是以牺牲一定性能为代价换得的,除非需要使用跨列处理的分组处理,否则应当使用其他专门设计的 groupby 对象方法,否则在性能上会存在较大的差距。同时,在使用聚合函数和变换函数时,也应当优先使用内置函数,它们经过了高度的性能优化,一般而言在速度上都会快于用自定义函数来实现。
五、练习
Ex1:汽车数据集
现有一份汽车数据集,其中 Brand, Disp., HP 分别代表汽车品牌、发动机蓄量、发动机输出。
ex1

# gb=df.groupby('Country')['Brand'].count()
# print(gb[gb.agg(lambda x: x>2)])
# # Country
# # Japan        19
# # Japan/USA     7
# # Korea         3
# # USA          26
# # Name: Brand, dtype: int64
# print(gb[gb.transform(lambda x: x>2)])
# # Country
# # Japan        19
# # Japan/USA     7
# # Korea         3
# # USA          26
# # Name: Brand, dtype: int64
df.groupby('Country').filter(
    lambda x:x.shape[0]>2).groupby('Country')['Price'].agg(
    [('CoV', lambda x: x.std()/x.mean()), 'mean', 'count'])

print(df.shape[0])
condition = ['Head']*20+['Mid']*20+['Tail']*20
df.groupby(condition)['Price'].mean()

res = df.groupby('Type').agg({'Price': ['max'], 'HP': ['min']})
res.columns = res.columns.map(lambda x:'_'.join(x))
res

def normalize(s):
    s_min, s_max = s.min(), s.max()
    res = (s - s_min)/(s_max - s_min)
    return res
df.groupby('Type')['HP'].transform(normalize).head()

df.groupby('Type')[['HP', 'Disp.']].apply(
    lambda x:np.corrcoef(x['HP'].values, x['Disp.'].values)[0,1])

Ex2:实现transform函数(参考)
ex2
my_groupby
单列分组
多列分组
标量广播
扩列广播

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值