Datawhale-Pandas中文教程[4]

分组

分组模式及其对象

分组的基本用法

分组操作应用非常广泛:

  • 依据性别分组,统计学生身高中位数
  • 依据季节分组,对每个季节的温度进行组内标准化
  • 依据班级分组,筛选组内数学分数平均值超过80分的班级

想要实现分组操作,必须明确三个要素:分组依据数据来源操作及其返回结果
df.groupby(分组依据)[数据来源].使用操作

df = pd.read_csv('data/learn_pandas.csv')
df.groupby('Gender')['Height'].median()
# 根据学校和性别进行分组,统计身高的均值
df.groupby(['School', 'Gender'])['Height'].mean()

如果需要根据多个维度进行分组,只需要在groupby中的分组依据中传入相应列名构成的列表即可。
分组依据可以直接从列中按照名字获取。如果希望通过一定的复杂逻辑来分组,可以先写出分组条件,再将其传入groupby

# 根据学生体重是否超过总体均值来分组
# condition = df.weight > df.weight.mean()
df.groupby(condition)['Height'].mean()
# 根据上下四分位数分割,将体重分为high, normal, low三组,统计身高的均值
def my_group(x):
     if x <= df.Weight.quantile(0.25):
         return('low')
     if x >= df.Weight.quantile(0.75):
         return('high')
     else:
         return('normal')
condition = df.Weight.apply(my_groupby)
df.groupby(condition)['Height].mean()

groupby方法最后产生的结果就是按照分组依据中元素的值(可以是布尔,可以是[‘low’,‘high’,‘normal’],'abc’等)来分组,例如上面和下面这个例子。(感觉也像根据自己给定的索引来分组)

my_series = np.random.choice(list('abc'), df.shape[0])
df.groupby(my_series)['Height'].mean()

如果传入多个序列进入groupby, 最后分组的依据就是对这两个序列对应行的唯一组合。通过drop_duplicates()方法能够知道来自于数据来源组合的unique值,也就是具体的组类别,后面的groupby中的groups属性也能完成类似的功能

# 传入多个序列进入groupby
df.groupby([condition, my_series])['Height'].mean()
# 来自不同学校的男生和女生的身高平均值
df.groupby([df['School'], df['Gender']])['Height'].mean()

Groupby对象

gb = df.groupby(['School', 'Grade'])
print(gb)
# <pandas.core.groupby.generic.DataFrameGroupBy object at 0x000001CBC8BE6220>

具体做分组操作时,所调用的方法都来自于 pandas 中的groupby对象,这个对象上定义了许多方法,也具有一些方便的属性。

  • ngroups属性:得到分组个数
  • groups属性:返回从组名映射到组索引列表的字典(字典的键是组合形成的元组列表,值是相应的元素在原表中的索引)
res = gb.groups
res.keys()
res[('Fudan University', 'Freshman')]
# -> Int64Index([15, 28, 63, 70, 73, 105, 108, 157, 186], dtype='int64')
# 表示原表中第15,28,...个的学生,School是复旦,Grade是Freshman
  • size: 返回每个组的元素个数,作为DataFrame的属性时返回的是表长乘以表宽的大小
  • get_group方法:直接获取所在组对应的行,此时必须知道组的具体名字,返回的是DataFrame
gb.get_group(('Fudan University', 'Freshman')).iloc[:3, :3]
# 展示前三行前三列
  • mean, median等(待补充

分组的三大操作

熟悉了一些分组的基本知识后,重新回到开头举的三个例子,可能会发现一些端倪,即这三种类型分组返回的数据型态并不一样:
第一个例子中,每一个组返回一个标量值,可以是平均值、中位数、组容量 size 等
第二个例子中,做了原序列的标准化处理,也就是说每组返回的是一个 Series 类型
第三个例子中,既不是标量也不是序列,返回的整个组所在行的本身,即返回了 DataFrame 类型
由此引申出了分组的三大操作

  • agg: 操作
  • transform: 变换
  • filter: 过滤
聚合
内置聚合函数

使用聚合功能时应当优先考虑直接定义在groupby对象的聚合函数,因为它的速度基本都会经过内部的优化。
聚合函数返回标量值,包括:
max/min/mean/median/count/all/any /idxmax/idxmin/mad/nunique/skew /quantile/sum/std/var/sem/size/prod
这些函数顾名思义已经比较清楚了,不清楚的可以去查阅pandas官方文档。且这些聚合函数当传入的数据来源包含多个列时,将按照列进行迭代计算

agg方法

groupby对象上定义的内置聚合函数有以下的缺点:无法同时使用多个函数、无法对特定的列使用特定的聚合函数、无法使用自定义的聚合函数、无法直接对结果的列名在聚合前进行自定义命名

  1. 使用多个函数:
    用列表的形式把内置聚合函数对应的字符串传入。先前提到的所有字符串都可以,例如gb.agg(['sum', 'idxmax', 'skew'])
    此时的列索引为多级索引,第一层为数据源,第二层为使用的聚合方法,分别逐一对列使用聚合。
  2. 对特定的列使用特定的聚合函数:
    通过构造字典传入agg实现,其中字典以列名为键,以聚合字符串或字符串列表为值,例如gb.agg({'Height': ['mean','max'], 'Weight':'count'})
  3. 使用自定义函数:
    自己定义函数,也可以是匿名函数,传入函数的参数是之前数据源中的列,agg方法中的函数会逐列进行计算,例如分组计算身高和体重的极差gb.add(lambda x: x.max()-x.min())
    由于传入的是序列,因此序列上的方法和属性都可以在函数中使用,只需要保证返回值是标量即可。
    练一练:在groupby对象中可以使用describe方法进行统计信息汇总,请同时使用多个聚合函数,完成与该方法相同的功能。
    gb = df.groupby('Gender')[['Height', 'Weight']]
    gb.agg({'Height':['count','mean','std','min',('25%', lambda x: x.quantile(0.25)),('50%', lambda x: x.quantile(0.5)),('75%', lambda x: x.quantile(0.75)),'max'],'Weight':['count','mean','std','min',('25%', lambda x: x.quantile(0.25)),('50%', lambda x: x.quantile(0.5)),('75%', lambda x: x.quantile(0.75)),'max']})
    
    # 分组的指标均值超过总体均值则返回High,否则返回Low
    def my_func(s):
    	res = 'High'
    	if s.mean() <= df[s.name].mean():
        	res = 'Low'
    	return res
    	gb.add(my_func)
    
  4. 聚合结果重命名:
    将上述函数的位置改写成元组,元组的第一个元素为新的名字,第二个位置为原来的函数,包括聚合字符串和自定义函数,上面是一个很好的例子。
    需要注意,使用对一个或者多个列使用单个聚合的时候,重命名需要加方括号,否则就不知道是新的名字还是手误输错的内置函数字符串。
    gb.agg([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')])
    gb.agg({'Height': [('my_func', my_func), 'sum'],
        'Weight': lambda x:x.max()})
    gb.agg([('my_sum', 'sum')])
    gb.agg({'Height': [('my_func', my_func), 'sum'],
        'Weight': [('range', lambda x:x.max())]})
    
变换

变换函数的返回值为同长度的序列。
常用的内置变换函数有累计函数cumcount, cumsum, cumprod, cummax, cummin,其使用方式和聚合函数类似,只不过完成的是组内累计操作。
groupby对象上填充类和滑窗类的变换函数将在第七章和第十章中讨论。
cumcount:

GroupBy.cumcount(ascending: bool = True) 
# 等价于 self.apply(lambda x: pd.Series(np.arange(len(x)), x.index))
# 作用是给分组的组内各个项目进行编号
df = pd.DataFrame([['a'], ['a'], ['a'], ['b'], ['b'], ['a']],
                  columns=['A'])
df.groupby('A').cumcount()
df.groupby('A').cumcount(ascending=False)
# 参数为False没太搞明白

cumsum:

# https://blog.csdn.net/qq_22238533/article/details/72900634?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-72900634-blog-79472875.t5_download_all&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-72900634-blog-79472875.t5_download_all&utm_relevant_index=1

cummax: gb.cummax().head()
rank:

# DataFrame等数据结构也有rank方法
# 
自定义变换

transform方法:被调用的自定义函数传入值为数据源的序列,与agg的传入类型是一致的,其最后的返回结果是行列索引与数据源一致的DataFrame

# 对身高和体重进行分组标准化
# 即减去组均值后除以组的标准差
gb.transform(lambda x: (x-x.mean())/x.std()).head()

transform只能返回同长度的序列,但事实上还可以返回一个标量,这会使得结果被广播到其所在的整个组,这种 标量广播 的技巧在特征工程中是非常常见的。

# 构造两列新特征来分别表示样本所在性别组的身高均值和体重均值
gb.transform('mean').head() # 传入返回标量的函数也是可以的
过滤

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

# 在原表中通过过滤得到所有容量大于100的组
gb.filter(lambda x: x.shape[0] > 100).head()

跨列分组

之前几节介绍了三大分组操作,但事实上还有一种常见的分组场景,无法用前面介绍的任何一种方法处理:
定义身体质量指数BMI:
B M I = W e i g h t H e i g h t 2 {\rm BMI} = {\rm\frac{Weight}{Height^2}} BMI=Height2Weight
其中体重和身高的单位分别为千克和米,需要分组计算组BMI的均值

# 
def BMI(x):
    Height = x['Height']/100
    Weight = x['Weight']
    BMI_value = Weight/Height**2
    return BMI_value.mean()
gb.apply(BMI)

首先,这不是过滤操作,因此filter不符合要求;其次,返回的均值是标量而不是序列,因此transform不符合要求;最后,似乎使用agg函数能够处理,但是之前强调过聚合函数是逐列处理的,而不能够多列数据同时处理 。由此,引出了 apply函数来解决这一问题。
除了返回标量之外, apply 方法还可以返回一维 Series 和二维 DataFrame

待补充

练习

待补充

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值