Tasl04_分组

一、分组模式及其对象

分组需要明确三个要素:分组依据数据来源操作及其返回结果,通俗上来说就是在哪些数据上对什么进行分组,做了哪些操作,一般模式为

df.groupby(分组依据)[数据来源].使用操作

## 具体点
df.groupby('School')['Height'].mean() # 根据学校分组,对分组数据中Height这一列求平均值,得到的每个学校身高的平均值

根据多列进行分组只需要将标量改成列表即可:df.groupby(['School','Gender'])['Height'].mean()

另外,pandas还支持根据传入条件分组:

condition = df.Weight > df.Weight.mean()

df.groupby(condition)['Height'].mean()
练一练

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

weight = df['Weight']
low = weight.quantile(0.25)
high = weight.quantile(0.75)
weight = weight.mask(weight<low,'low').mask(weight > high,'high').mask(((weight >= weight.quantile(0.25))&(weight <= weight.quantile(0.75))),'mid')
df_test = df.copy()
df_test['Weight'] = weight
df_test.groupby('Weight')['Height'].mean()

分别上面流式编程的代码可以看出,操作的都是groupby对象,groupby对象不可直接输出,其中定义了一些属性及其方法,搭配使用即可得到一些想要的输出,下面这些例子可以解释这句话:

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

## 得到分组个数
gb.ngroups
## 输出16

类似的属性还有很多:

  • gb.ngroups:返回分组个数
  • gb.size():统计每组元素的个数
  • gb.get_group((想得到的分组)):比如gb.get_group(('Fudan University', 'Freshman')),这里的返回值是一个DataFrame对象,因此可以使用iloc等方法部分展示
  • gb.mean():返回每列的平均数,可以返回指定列的平均数,比如gb.mean['Height'],当不指定时返回的是所有数字列的平均数
  • gb.median():返回中位数,用法同上

二、聚合函数

1.内置聚合函数

max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod是一些直接定义在groupby上的聚合函数

练一练

请查阅文档,明确 all/any/mad/skew/sem/prod 函数的含义。

当聚合函数传入的数据来源包含多个列时,逐列进行迭代,如下所示:

gb = df.groupby('Gender')[['Height', 'Weight']]
gb.max()

## 输出
Height  Weight
Gender                
Female   170.2    63.0
Male     193.9    89.0
2.agg函数

agg函数解决了以下几个问题:

  • 无法同时使用多个函数
  • 无法对特定的列使用特定的聚合函数
  • 无法使用自定义的聚合函数
  • 无法直接对结果的列名在聚合前进行自定义命名
使用多个函数聚合
gb.agg(['sum', 'idxmax', 'skew'])
对特定的列聚合

可以通过传入字典的方式

gb.agg({'Height':['mean','max'], 'Weight':'count'}).head()
练一练

请使用传入字典的方法完成等价的聚合任务。

gb.agg({'Height':['sum','idxmax','skew'], 'Weight':['sum','idxmax','skew']})
使用自定义函数

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

gb.agg(lambda x: x.mean()-x.min())
练一练

groupby 对象中可以使用 describe 方法进行统计信息汇总,请同时使用多个聚合函数,完成与该方法相同的功能。

值得注意的是agg函数传入的是序列,因此在自定义函数中也能使用Series所包含的属性和方法

聚合结果重命名
gb.agg([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')])

传入的是一个由元组构成的列表,每个列表第一个元素是新起的名字,第二个是聚合函数

重命名还有以下几种语法:

## 对my_func聚合结果重命名为my_func,sum函数不重命名
gb.agg({'Height': [('my_func', my_func), 'sum'],'Weight': lambda x:x.max()})

## 别忘了括号,不然不知道是否是使用多个函数聚合
gb.agg([('my_sum', 'sum')])
三、变换和过滤
1.变换
练一练

groupby 对象中, rank 方法也是一个实用的变换函数,请查阅它的功能并给出一个使用的例子。

## 得到倒数第二高的行
df[df.Height.rank(method='dense') == 2]

transform 方法和agg方法的传入值是一样的,都是数据源的序列Series,结合下面的例子应该能很好的明白:

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

需要注意的是返回的结果是行列索引和数据源一致的DataFrame,从上面的例子也可以看出。

上面的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
2.组过滤

和前面一章学习的索引不同,索引是对每行元素的筛选,组过滤是对于每个组别的筛选,符合条件的组别将保留,不符合条件的组别将被过滤。

pandas中使用filter函数完成组过滤功能,接收一个参数,传入的是DataFrame对象本身,返回值需要为bool值,最终会根据布尔值来选取未被过滤的组其对应的所在行拼接成一个DataFrame返回,例子如下:

## 挑选出数量大于100的组别
gb.filter(lambda x: x.shape[0] > 100).head()
练一练

从概念上说,索引功能是组过滤功能的子集,请使用 filter 函数完成 loc[.] 的功能,这里假设 ” . “是元素列表。

## 挑选出性别为女的组别
def my_func(x):
    return x.name=='Female'
gb.filter(my_func)

四、跨列分组

当我们需要对多列进行操作,并且返回标量时,上述的过滤、转换、agg函数都不可用了,这时候,就需要引入apply函数帮助我们解决问题,下面举个分组计算组BMI均值的例子:

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函数传入的参数和filter完全一致,返回值两者有所不同,后者只能返回布尔值

apply传入的函数较为灵活,可返回标量SeriesDataFrame

  • 当函数返回为标量时,如上面的例子所示
  • 当函数返回值为Series,会得到一个DataFrame对象,行索引与返回标量的情况一致,列索引为Series的列索引
  • 当函数返回值为DataFrame时,行索引为分组情况加上一层该DataFrame的索引,列索引为DataFrame的列索引,例子如下
gb.apply(lambda x: pd.DataFrame(np.ones((2,2)),index = ['a','b'],columns=pd.Index([('w','x'),('y','z')])))
## 结果
		w	y
		x	z
Gender			
Female	a	1.0	1.0
b	1.0	1.0
Male	a	1.0	1.0
b	1.0	1.0

需要注意的是,虽然apply很灵活,但是和内置函数相比性能上可能存在差距,如果可以的话建议优先使用内置函数

五、练习

Ex1:汽车数据集

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

In [45]: df = pd.read_csv('data/car.csv')

In [46]: df.head(3)
Out[46]: 
             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
  2. 按照表中位置的前三分之一、中间三分之一和后三分之一分组,统计 Price 的均值。
  3. 对类型 Type 分组,对 PriceHP 分别计算最大值和最小值,结果会产生多级索引,请用下划线把多级列索引合并为单层索引。
  4. 对类型 Type 分组,对 HP 进行组内的 min-max 归一化。
  5. 对类型 Type 分组,计算 Disp.HP 的相关系数。

第一问:

gb_demo = df.groupby('Country')
gb_demo = gb_demo.filter(lambda x : x.shape[0] > 2)
gb_demo.groupby('Country')['Price'].agg(['mean', ('CoV',lambda x : x.std()/x.mean()), 'count'])

第二问:

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

上面这个是参考答案,利用传入一个列表做标识进行分组

第三问:

def func(x):
    return x[0]+'_'+x[1]
col_idx = df_demo.columns.map(func)
df_copy = df_demo.copy()
df_copy.columns = col_idx
df_copy

第四问:

df_demo = df.groupby('Type')['HP'].transform(lambda x: (x-x.min())/(x.max()-x.min()))
df_demo.head()

第五问:

df.groupby('Type')['Disp.','HP'].corr()

Ex2:实现transform函数

  • groupby 对象的构造方法是 my_groupby(df, group_cols)
  • 支持单列分组与多列分组
  • 支持带有标量广播的 my_groupby(df)[col].transform(my_func) 功能
  • pandastransform 不能跨列计算,请支持此功能,即仍返回 Seriescol 参数为多列
  • 无需考虑性能与异常处理,只需实现上述功能,在给出测试样例的同时与 pandas 中的 transform 对比结果是否一致

这几天比较忙,这题参考答案代码暂时还没看完,周六周日会补上

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值