• 【12月DW打卡】joyful-pandas


    第四章 分组Group

    小结

    • 实现transform函数,参考文章(如有所悟,实云里雾里)https://blog.csdn.net/qq_34903176/article/details/111595612
    • 本文主要学习内容源:joyful-pandas
    • 转换命令:jupyter nbconvert --to markdown E:PycharmProjectsTianChiProject0_山枫叶纷飞competitions08_joyful-pandas4_分组.ipynb

    一、分组模式及其对象

    1. 分组的一般模式

    分组的三个要素:分组依据、数据来源、操作、返回结果;分组代码的一般模式即:

    df.groupby(分组依据)[数据来源].使用操作
    
    #导包
    import numpy as np
    import pandas as pd
    

    举个梨子:

    df = pd.read_csv('E:\PycharmProjects\DatawhaleChina\joyful-pandas\data\learn_pandas.csv')
    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()
    condition
    
    0      False
    1       True
    2       True
    3      False
    4       True
           ...  
    195    False
    196    False
    197    False
    198     True
    199    False
    Name: Weight, Length: 200, dtype: bool
    
    df.groupby(condition)['Height'].mean()
    
    Weight
    False    159.034646
    True     172.705357
    Name: Height, dtype: float64
    

    【练一练】
    请根据上下四分位数分割,将体重分为high、normal、low三组,统计身高的均值。

    dfw = df['Weight']
    df['w_quantile'] = 'normal'
    df['w_quantile'] = dfw.mask(dfw >= dfw.quantile(0.75), 'high').mask(dfw<=dfw.quantile(0.25), 'low')
    df.groupby('w_quantile')['Height'].mean().head()
    
    w_quantile
    47.0    158.891667
    48.0    160.600000
    49.0    159.955556
    50.0    160.771429
    51.0    161.254545
    Name: Height, dtype: float64
    
    1. Groupby对象

    groupby对象 自带属性:

    # 打印对象属性 DataFrameGroupBy
    gb = df.groupby(['School', 'Grade'])
    gb
    
    <pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000013F55AE9850>
    
    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')])
    

    通过drop_duplicates得到具体的组类别,现在使用groups属性的keys也可以完成类似的功能。
    如上. 如下使用drop_duplicates得到具体的组类别:

    df[['School', 'Grade']].drop_duplicates()
    
    
    School Grade
    0 Shanghai Jiao Tong University Freshman
    1 Peking University Freshman
    2 Shanghai Jiao Tong University Senior
    3 Fudan University Sophomore
    5 Tsinghua University Freshman
    7 Tsinghua University Junior
    9 Peking University Junior
    13 Shanghai Jiao Tong University Sophomore
    14 Tsinghua University Senior
    15 Fudan University Freshman
    26 Fudan University Junior
    29 Peking University Sophomore
    30 Peking University Senior
    31 Shanghai Jiao Tong University Junior
    39 Fudan University Senior
    40 Tsinghua University Sophomore

    当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(('Tsinghua University', 'Freshman')).head()
    
    
    Name Gender Height Weight Transfer Test_Number Test_Date Time_Record w_quantile
    5 Xiaoli Qian Female 158.0 51.0 N 1 2019/10/31 0:03:47 51
    8 Changli Zhang Female 163.0 48.0 N 1 2020/1/5 0:05:13 48
    33 Yanjuan Han Female 163.7 49.0 N 3 2019/11/5 0:04:39 49
    34 Li Wu Female 164.3 51.0 N 1 2019/10/21 0:04:32 51
    43 Gaoli Feng Female 157.4 46.0 Y 1 2019/12/30 0:04:00 low
    1. 分组的三大操作

    这三种类型的分组返回数据的结果型态并不一样:

    第一个例子中,每一个组返回一个标量值,可以是平均值、中位数、组容量size等;(简单或者多列的mean等操作)

    第二个例子中,做了原序列的标准化处理,也就是说每组返回的是一个Series类型;(根据(布尔列的)分组的条件)

    第三个例子中,既不是标量也不是序列,返回的整个组所在行的本身,即返回了DataFrame类型; (getgroup)

    由此,引申出分组的三大操作:聚合、变换和过滤,分别对应了三个例子的操作,下面就要分别介绍相应的agg、transform和filter函数及其操作。

    ## 二、聚合函数
    
    1. 内置聚合函数

    !(原文pandas的说明地址)[https://pandas.pydata.org/pandas-docs/stable/reference/groupby.html]

    groupby内置的聚合函数,因为它的速度基本都会在内部进行优化,使用功能时会进行优先考虑。根据返回的标量值,包括如下的函数。

    • max
    • min
    • mean
    • median
    • count
    • all : 与门,当前分组中全为True则返回True
    • any : 或门,存在一个True则返回True
    • idxmax : idxmax()返回的是DataFrame中每一列最大值的索引
    • idxmin : idxmin()返回的是DataFrame中每一列最小值的索引
    • mad (mean absolute deviation)平均绝对离差, , 用于统计学中对分组后的每组数据做离散程度分析的指标之一
    • nunique : Return number of unique elements in the group.
    • skew :(skewness)偏度 , 用来反映分组后每组数据分布的偏态程度 , 正值为右偏 , 绝对值越大 , 偏度越高
    • quantile : 分位数
    • sum
    • std
    • var : 方差
    • sem :计算各组平均值的标准误差,不包括缺失值;对于多个分组,结果索引将是一个多重索引。
    • size
    • prod :计算每组的乘积

    【END】
    这些聚合函数当传入的数据来源包含多个列时,将按照列进行迭代计算:

    gb = df.groupby('Gender')[['Height', 'Weight']]
    gb.max()
    
    Height Weight
    Gender
    Female 170.2 63.0
    Male 193.9 89.0
    1. agg方法

    agg可以让groupby对象

    • 支持同一个分组支持使用多个函数
    • 特定列使用特定的聚合函数
    • 使用自定义的聚合函数
    • 对结果的列名在聚合前进行自定义命名

    【a】使用多个函数
    当使用多个聚合函数时,需要用列表的形式把内置聚合函数的对应的字符串传入,先前提到的所有字符串都是合法的:max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod。

    此时的列索引为多级索引,第一层为数据源,第二层为使用的聚合方法,分别逐一对列使用聚合,因此结果为2*3=6列。

    gb.agg(['sum', 'idxmax', 'mean'])
    
    
    Height Weight
    sum idxmax mean sum idxmax mean
    Gender
    Female 21014.0 28 159.19697 6469.0 28 47.918519
    Male 8854.9 193 173.62549 3929.0 2 72.759259

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

    gb.agg({'Height': ['sum', 'idxmax', 'mean'],
            'Weight': ['sum', 'idxmax', 'mean']})
    
    Height Weight
    sum idxmax mean sum idxmax mean
    Gender
    Female 21014.0 28 159.19697 6469.0 28 47.918519
    Male 8854.9 193 173.62549 3929.0 2 72.759259

    【c】使用自定义函数
    在agg中可以使用具体的自定义函数,如labmda函数求一次极差:

    gb.agg(lambda x: x.mean()-x.min())
    
    
    Height Weight
    Gender
    Female 13.79697 13.918519
    Male 17.92549 21.759259

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

    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

    另外需要注意,使用对一个或者多个列使用单个聚合的时候,重命名需要加方括号,否则就不知道是新的名字还是手误输错的内置函数字符串:

    def my_func(x):
        return x.max()
    gb.agg({'Height': [('my_func', my_func), 'sum'], 'Weight': [('range', lambda x:x.max())]})
    
    
    Height Weight
    my_func sum range
    Gender
    Female 170.2 21014.0 63.0
    Male 193.9 8854.9 89.0

    三、变换和过滤

    1. 变换函数与transform方法 (同group长度)

    1.1 变换函数cum...

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

    # 累计求最大值
    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
    # 求rank: Provide the rank of values within each group.
    #         提供每个组中值的等级。
    gb.rank().head()
    
    Height Weight
    0 58.0 47.5
    1 5.0 19.0
    2 50.0 54.0
    3 NaN 14.5
    4 27.0 31.5

    1.2. 使用transform方法
    当用自定义变换时需要使用transform方法,被调用的自定义函数,与agg的传入类型是一致的,其最后的返回结果是行列索引与数据源一致的DataFrame。

    gb.transform(lambda x: (x-x.mean())/x.std()).head()
    
    # 等价于
    gb.agg(lambda x: (x-x.mean())/x.std()).head()
    

    1.3. 【练一练】transform方法实现多列函数计算

    对于transform方法无法像agg一样,通过传入字典来对指定列使用特定的变换,如果需要在一次transform的调用中实现这种功能,请给出解决方案。

    # 使用内置函数
    gb.transform(lambda x:x.cummin() if x.name=='Height' else x.rank()).head()
    
    
    Height Weight
    0 158.9 47.5
    1 166.5 19.0
    2 166.5 54.0
    3 NaN 14.5
    4 166.5 31.5

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

    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. 组索引与过滤

    2.1 什么是组索引与过滤

    • 索引是行的过滤: 无论是布尔列表还是元素列表或者位置列表,本质上都是对于行的筛选,即如果筛选条件的则选入结果的表,否则不选入。

    • 过滤在分组中是对于组的过滤: 指的是如果对一个组的全体所在行进行统计的结果返回True则会被保留,False则该组会被过滤,最后把所有未被过滤的组其对应的所在行拼接起来作为DataFrame返回。

    • 在groupby对象中,定义了filter方法进行组的筛选,其中自定义函数的输入参数为数据源构成的DataFrame本身,在之前例子中定义的groupby对象中,传入的就是df[['Height', 'Weight']],因此所有表方法和属性都可以在自定义函数中相应地使用,同时只需保证自定义函数的返回为布尔值即可。

    如下示例,返回所有容量大于100的组:

    gb.filter(lambda x:x.shape[0]>100).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

    四、跨列分组

    1. apply的引入
    • apply函数可以实现跨列计算,输入数据为数据源构成的DataFrame本身——与filter一致。
    • 【a】标量情况:结果得到的是 Series ,索引与 agg 的结果一致
    • 【b】Series情况:得到的是DataFrame,行索引与标量情况一致,列索引为Series的索引
    • 【c】DataFrame情况:得到的是DataFrame,行索引最内层在每个组原先agg的结果索引上,再加一层返回的DataFrame行索引,同时分组结果DataFrame的列索引和返回的DataFrame列索引一致。
    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
    

    【练一练】
    在groupby对象中还定义了cov和corr函数,从概念上说也属于跨列的分组处理。请利用之前定义的gb对象,使用apply函数实现与gb.cov()同样的功能并比较它们的性能。

    gb.apply(lambda x:x.cov()).head()
    
    
    Height Weight
    Gender
    Female Height 25.542739 24.838146
    Weight 24.838146 29.224655
    Male Height 49.681137 47.803901
    # 用apply将每个组的每个列拆开分别计算各列之间的协方差矩阵
    gb.apply(lambda x:pd.DataFrame([[x[i].cov(x[j]) for j in x.columns] for i in x.columns],index=x.columns,columns=x.columns)).head()
    
    
    Height Weight
    Gender
    Female Height 25.542739 24.838146
    Weight 24.838146 29.224655
    Male Height 49.681137 47.803901
    %timeit -n 100 gb.cov()
    
    2.39 ms ± 182 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    %timeit -n 100 gb.apply(lambda x:x.cov()).head()
    
    2.36 ms ± 399 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    %timeit -n 100 gb.apply(lambda x:pd.DataFrame([[x[i].cov(x[j]) for j in x.columns] for i in x.columns],index=x.columns,columns=x.columns)).head()
    
    
    3.55 ms ± 223 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    五、练习

    Ex1:汽车数据集

    现有一份汽车数据集,其中Brand, Disp., HP分别代表汽车品牌、发动机蓄量、发动机输出。

    # 读取数据
    df = pd.read_csv('E:\PycharmProjects\DatawhaleChina\joyful-pandas\data\car.csv')
    df.head()
    
    
    Brand Price Country Reliability Mileage Type Weight Disp. HP
    0 Eagle Summit 4 8895 USA 4.0 33 Small 2560 97 113
    1 Ford Escort 4 7402 USA 2.0 33 Small 2345 114 90
    2 Ford Festiva 4 6319 Korea 4.0 37 Small 1845 81 63
    1. 先过滤出所属Country数超过2个的汽车,即若该汽车的Country在总体数据集中出现次数不超过2则剔除,再按Country分组计算价格均值、价格变异系数、该Country的汽车数量,其中变异系数的计算方法是标准差除以均值,并在结果中把变异系数重命名为CoV。
    # 过滤
    df01 = df.groupby('Country').filter(lambda x:x.shape[0]>2).sort_values(by='Country').head()
    df01
    
    Brand Price Country Reliability Mileage Type Weight Disp. HP
    46 Nissan Maxima V6 17899 Japan 5.0 22 Medium 3200 180 160
    37 Acura Legend V6 24760 Japan 5.0 20 Medium 3265 163 160
    45 Mazda 929 V6 23300 Japan 5.0 21 Medium 3480 180 158
    df01.groupby('Country')['Price'].agg(['mean',('Cov', lambda x:x.std()/x.min()), 'count'])
    
    
    mean Cov count
    Country
    Japan 21986.333333 0.201923 3
    1. 按照表中位置的前三分之一、中间三分之一和后三分之一分组,统计Price的均值。
    first_idx = df.shape[0]/3.0
    second_idx = df.shape[0]/3.0 * 2
    def my_fun_split(x):
        # 默认传入的df的就是index
        if x<=first_idx: return 0
        if x>first_idx and x<second_idx: return 1
        return 2
    df.groupby(my_fun_split)['Price'].mean()
    
    
    1. 对类型Type分组,对Price和HP分别计算最大值和最小值,结果会产生多级索引,请用下划线把多级列索引合并为单层索引。
    df003 = df.groupby('Type').agg({'Price': ['max'],'HP':['min']})
    print(df003.head())
    df003.columns.map(lambda x:'_'.join(x))
    
             Price   HP
               max  min
    Type               
    Compact  18900   95
    Large    17257  150
    Medium   24760  110
    Small     9995   63
    Sporty   13945   92
    
    
    
    
    
    Index(['Price_max', 'HP_min'], dtype='object')
    
    1. 对类型Type分组,对HP进行组内的min-max归一化。
    x = df.groupby('Type').HP.transform(lambda x:(x-x.min())/(x.max()-x.min()))
    x.head()
    
    0    1.00
    1    0.54
    2    0.00
    3    0.58
    4    0.80
    Name: HP, dtype: float64
    
    1. 对类型Type分组,计算Disp.与HP的相关系数。
    df.groupby('Type')[['Disp.','HP']].corr().head(6)
    
    
    Disp. HP
    Type
    Compact Disp. 1.000000 0.586087
    HP 0.586087 1.000000
    Large Disp. 1.000000 -0.242765
    HP -0.242765 1.000000
    Medium Disp. 1.000000 0.370491
    HP 0.370491 1.000000

    Ex2:实现transform函数

    1. groupby对象的构造方法是my_groupby(df, group_cols)
    2. 支持单列分组与多列分组
    3. 支持带有标量广播的my_groupby(df)[col].transform(my_func)功能
    4. pandas的transform不能跨列计算,请支持此功能,即仍返回Series但col参数为多列
    5. 无需考虑性能与异常处理,只需实现上述功能,在给出测试样例的同时与pandas中的transform对比结果是否一致
    class my_groupby:
        def __init__(self, my_df, group_cols):
            self.my_df = my_df.copy()
            self.groups = my_df[group_cols].drop_duplicates()
            if isinstance(self.groups, pd.Series):
                self.groups = self.groups.to_frame()
            self.group_cols = self.groups.columns.tolist()
            self.groups = {i: self.groups[i].values.tolist() for i in self.groups.columns}
            self.transform_col = None
        def __getitem__(self, col):
            self.pr_col = [col] if isinstance(col, str) else list(col)
            return self
        def transform(self, my_func):
            self.num = len(self.groups[self.group_cols[0]])
            L_order, L_value = np.array([]), np.array([])
            for i in range(self.num):
                group_df = self.my_df.reset_index().copy()
                for col in self.group_cols:
                    group_df = group_df[group_df[col]==self.groups[col][i]]
                group_df = group_df[self.pr_col]
                if group_df.shape[1] == 1:
                    group_df = group_df.iloc[:, 0]
                group_res = my_func(group_df)
                if not isinstance(group_res, pd.Series):
                    group_res = pd.Series(group_res,index=group_df.index,name=group_df.name)
                L_order = np.r_[L_order, group_res.index]
                L_value = np.r_[L_value, group_res.values]
            self.res = pd.Series(pd.Series(L_value, index=L_order).sort_index().values,index=self.my_df.reset_index().index, name=my_func.__name__)
            return self.res
    
    my_groupby(df, 'Type')
    

    单列分组:

    def f(s):
        res = (s-s.min())/(s.max()-s.min())
        return res
    my_groupby(df, 'Type')['Price'].transform(f).head()
    
    df.groupby('Type')['Price'].transform(f).head()
    

    多列分组:

    my_groupby(df, ['Type','Country'])['Price'].transform(f).head()
    
    df.groupby(['Type','Country'])['Price'].transform(f).head()
    

    标量广播:

    my_groupby(df, 'Type')['Price'].transform(lambda x:x.mean()).head()
    
    df.groupby('Type')['Price'].transform(lambda x:x.mean()).head()
    

    跨列计算:

    my_groupby(df, 'Type')['Disp.', 'HP'].transform(lambda x: x['Disp.']/x.HP).head()
    
    
    
    
    
  • 相关阅读:
    java中常量定义在interface中好还是定义在class中
    CharacterEncodingFilter-Spring字符编码过滤器
    Integer判断相等,到底该用==还是equals
    ThreadLocal实现session中用户信息 的线程间共享
    分布式部署引发的问题
    分布式部署
    LogBack通过MDC实现日志记录区分用户Session
    Fragment 简介 基础知识 总结 MD
    直播 相关技术文章 相关调研文章
    直播 背景 技术体系 乐视云直播Demo
  • 原文地址:https://www.cnblogs.com/zhazhaacmer/p/14191201.html
Copyright © 2020-2023  润新知