分组模式及其对象
分组操作应用非常广泛:
- 依据 性别分组,统计 _学生身高_的中位数
- 依据 季节分组,对每个季节的 _温度_进行组内标准化
- 依据 班级分组,筛选组内 _数学分数_的平均值超过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()
gb = df.groupby(['School', 'Grade'])
print(gb)
<pandas.core.groupby.generic.dataframegroupby object at 0x000001cbc8be6220>
</pandas.core.groupby.generic.dataframegroupby>
具体做分组操作时,所调用的方法都来自于 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官方文档。且这些聚合函数当传入的数据来源包含多个列时,将按照列进行迭代计算
在 groupby
对象上定义的内置聚合函数有以下的缺点:无法同时使用多个函数、无法对特定的列使用特定的聚合函数、无法使用自定义的聚合函数、无法直接对结果的列名在聚合前进行自定义命名
变换函数的返回值为同长度的序列。
常用的内置变换函数有累计函数 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 =Heigh t 2 Weight
其中体重和身高的单位分别为千克和米,需要分组计算组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
待补充
待补充
Original: https://blog.csdn.net/qq_48633796/article/details/126607844
Author: 少喝奶茶qaq
Title: Datawhale-Pandas中文教程[4]
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/742593/
转载文章受原作者版权保护。转载请注明原作者出处!