编程实践(Pandas)Task04

一、分组模式及其对象

1. 分组的一般模式

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

df = pd.read_csv('data/learn_pandas.csv')
# 按照性别统计身高中位数
df.groupby('Gender')['Height'].median()

Gender
Female    159.6
Male      173.4
Name: Height, dtype: float64

2. 分组依据的本质

多个维度进行分组,只需在 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

如果希望通过一定的复杂逻辑来分组,首先应该先写出分组条件,然后将其传入 groupby中。

# 根据学生体重是否超过总体均值来分组,同样还是计算身高的均值
condition = df.Weight > df.Weight.mean()
df.groupby(condition)['Height'].mean()

Weight
False    159.034646
True     172.705357
Name: Height, dtype: float64

从索引可以看出,其实最后产生的结果就是按照条件列表中元素的值(此处是 True 和 False )来分组

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

a    162.415000
b    163.400000
c    163.773913
Name: Height, dtype: float64

此处的索引就是原先item中的元素,如果传入多个序列进入 groupby ,那么最后分组的依据就是这两个序列对应行的唯一组合

df.groupby([condition, item])['Height'].mean()

Weight   
False   a    157.170000
        b    160.186842
        c    159.663265
True    a    172.905000
        b    171.031250
        c    173.845000
Name: Height, dtype: float64

事实上等价于传入的是一个或多个列,最后分组的依据来自于数据来源组合的unique值

df[['Grade', 'Gender']].drop_duplicates()

	Grade		Gender
0	Freshman	Female
1	Freshman	Male
2	Senior		Male
3	Sophomore	Female
4	Sophomore	Male
7	Junior		Female
12	Senior		Female
16	Junior		Male
df.groupby([df['Grade'], df['Gender']])['Height'].mean()

Grade      Gender
Freshman   Female    159.689189
           Male      175.260000
Junior     Female    159.782500
           Male      171.207143
Senior     Female    158.480556
           Male      175.594118
Sophomore  Female    158.363158
           Male      172.030000
Name: Height, dtype: float64

3. Groupby对象

定义用法
属性ngroups分组个数
属性groups返回从 组名 映射到 组索引列表 的字典
方法size统计每个组的元素个数
方法get_group获取所在组对应的行

分组操作时,所调用的方法都来自于 pandas 中的 groupby 对象,这个对象上定义了许多方法

gb = df.groupby(['Grade', 'Gender'])
gb
>>>  <pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000000008103A60>

通过 ngroups 属性,可以得到分组个数

gb.ngroups
>>> 8

通过 groups 属性,可以返回从 组名 映射到 组索引列表 的字典:

res = gb.groups
res
>>> {('Freshman', 'Female'): [0, 5, 6, 8, 15, 28, 32, 33, 34, 43, 44, 45, 47, 51, 57, 62, 63, 67, 70, 81, 88, 96, 105, 108, 111, 114, 119, 121, 125, 133, 136, 140, 141, 142, 146, 148, 149, 157, 185, 186], ('Freshman', 'Male'): [1, 10, 35, 36, 38, 54, 60, 73, 99, 117, 153, 184], ('Junior', 'Female'): [7, 9, 11, 20, 26, 27, 31, 42, 56, 58, 59, 64, 69, 75, 84, 85, 90, 93, 107, 113, 115, 118, 122, 128, 137, 143, 145, 154, 155, 159, 163, 164, 169, 172, 173, 174, 176, 183, 187, 188, 189, 191, 195], ('Junior', 'Male'): [16, 17, 41, 50, 72, 82, 94, 95, 102, 150, 152, 158, 160, 162, 177, 190], ('Senior', 'Female'): [12, 14, 19, 22, 25, 30, 39, 46, 49, 52, 77, 78, 79, 86, 87, 89, 92, 100, 103, 104, 109, 112, 123, 126, 129, 130, 132, 138, 144, 156, 161, 166, 168, 175, 180, 194, 196, 197], ('Senior', 'Male'): [2, 18, 21, 23, 24, 66, 116, 127, 131, 134, 147, 165, 171, 179, 192, 193, 198], ('Sophomore', 'Female'): [3, 13, 29, 37, 53, 55, 65, 68, 80, 83, 97, 101, 106, 110, 120, 124, 139, 151, 167, 170], ('Sophomore', 'Male'): [4, 40, 48, 61, 71, 74, 76, 91, 98, 135, 178, 181, 182, 199]}
res.keys()
>>> dict_keys([('Freshman', 'Female'), ('Freshman', 'Male'), ('Junior', 'Female'), ('Junior', 'Male'), ('Senior', 'Female'), ('Senior', 'Male'), ('Sophomore', 'Female'), ('Sophomore', 'Male')])

当 size 作为 DataFrame 的属性时,返回的是表长乘以表宽的大小,但在 groupby 对象上表示统计每个组的元素个数

gb.size()

Grade      Gender
Freshman   Female    40
           Male      12
Junior     Female    43
           Male      16
Senior     Female    38
           Male      17
Sophomore  Female    20
           Male      14
dtype: int64

通过 get_group 方法可以直接获取所在组对应的行(此时必须知道组的具体名字)

gb.get_group(('Freshman','Male')).iloc[:3,:4]

	School							Grade		Name			Gender
1	Peking University				Freshman	Changqiang You	Male
10	Shanghai Jiao Tong University	Freshman	Xiaopeng Zhou	Male
35	Peking University				Freshman	Gaoli Zhao		Male

4. 分组的三大操作

· 每一个组返回一个标量值,可以是平均值、中位数、组容量 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.skew() # 求偏度

Gender
Female   -0.219253
Male      0.437535
Name: Height, dtype: float64

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

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

		Weight		Height
Gender		
Female	47.918519	159.19697
Male	72.759259	173.62549

2. agg方法

groupby对象上内置的聚合函数有以下几个缺点:

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

		Weight			Height
		max		idxmax	max		idxmax
Gender				
Female	63.0	28		170.2	28
Male	89.0	2		193.9	193
2.2 对特定的列使用特定的聚合函数
gb.agg({'Weight':['min','idxmin'], 'Height':['max','idxmax']})

		Weight			Height
		min		idxmin	max		idxmax
Gender				
Female	34.0	49		170.2	28
Male	51.0	199		193.9	193
2.3 使用自定义函数

在 agg 中可以使用具体的自定义函数, 需要注意传入函数的参数是之前数据源中的列,逐列进行计算 。

# 分组计算身高和体重的极差
gb.agg(lambda x: x.mean() - x.min())

		Weight		Height
Gender		
Female	13.918519	13.79697
Male	21.759259	17.92549

由于传入的是序列,因此序列上的方法和属性都是可以在函数中使用的,只需保证返回值是标量即可。

def my_func(x):
    res = 'High'
    if x.mean() <= df[x.name].mean():
        res = 'Low'
    return res
gb.agg(my_func)

		Weight	Height
Gender		
Female	Low		Low
Male	High	High
2.4 聚合结果重命名

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

gb.agg([('range', lambda x: x.max()-x.mean()), ('my_sum','sum')])

		Weight				Height
		range		my_sum	range	my_sum
Gender				
Female	15.081481	6469.0	11.00303	21014.0
Male	16.240741	3929.0	20.27451	8854.9
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

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

# 修改上一个例子
gb.agg({'Height': [('my_func', my_func), 'sum'], 'Weight': [('max',lambda x: x.max())]})

		Height			Weight
		my_func	sum		max
Gender			
Female	Low		21014.0	63.0
Male	High	8854.9	89.0

三、变换和过滤

1. 变换函数与transform方法

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

gb.cumsum().head() # 样本的累计最大值

	Weight	Height
0	46.0	158.9
1	70.0	166.5
2	159.0	355.4
3	87.0	NaN
4	233.0	529.4

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

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

	Weight		Height
0	-0.354888	-0.058760
1	-0.355000	-1.010925
2	2.089498	2.167063
3	-1.279789	NaN
4	0.159631	0.053133

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

gb.transform('mean').head() 

	Weight		Height
0	47.918519	159.19697
1	72.759259	173.62549
2	72.759259	173.62549
3	47.918519	159.19697
4	72.759259	173.62549

2. 组索引与过滤

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

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

	Weight	Height
0	46.0	158.9
3	41.0	NaN
5	51.0	158.0
6	52.0	162.5
7	50.0	161.9

四、跨列分组

1. apply的引入

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的均值。

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

2. apply的使用

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 ,但它们产生的数据框维数和多级索引的层数应当如何变化?

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

五、练一练

练习1

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

# 先根据上四分位数和下四分位数分组(只会用最笨的办法):
def cond(x):
    if x < df.Weight.quantile(0.25):
        return 'low'
    elif x < df.Weight.quantile(0.75):
        return 'normal'
    else:
        return 'high'
df.groupby(df.Weight.apply(cond))['Height'].mean()

Weight
high      172.920755
low       153.753659
normal    161.800000
Name: Height, dtype: float64

练习2

上一小节介绍了可以通过 drop_duplicates 得到具体的组类别,现请用 groups 属性完成类似的功能。

df[['Grade', 'Gender']].drop_duplicates()

	Grade		Gender
0	Freshman	Female
1	Freshman	Male
2	Senior		Male
3	Sophomore	Female
4	Sophomore	Male
7	Junior		Female
12	Senior		Female
16	Junior		Male
gb = df.groupby(['Grade', 'Gender'])
pd.DataFrame(data = gb.groups.keys(), columns=['Grade','Gender'])

	Grade		Gender
0	Freshman	Female
1	Freshman	Male
2	Junior		Female
3	Junior		Male
4	Senior		Female
5	Senior		Male
6	Sophomore	Female
7	Sophomore	Male

练习3

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

用法
allall() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为 TRUE,如果是返回 True,否则返回 False。元素除了是 0、空、None、False 外都算 True。
anyany() 函数用于判断给定的可迭代参数 iterable 是否全部为 False,则返回 False,如果有一个为 True,则返回 True。元素除了是 0、空、FALSE 外都算 TRUE。
mad根据平均值计算平均绝对距离差。DataFrame.mad(axis=None, skipna=None, level=None)[source]
skew偏度(skewness),是统计数据分布偏斜方向和程度的度量,是统计数据分布非对称程度的数字特征。偏度(Skewness)亦称偏态、偏态系数。DataFrame.skew(axis=None, skipna=None, level=None, numeric_only=None, **kwargs)
sem返回平均值的无偏标准误差。DataFrame.sem(axis=None, skipna=None, level=None, ddof=1, numeric_only=None, **kwargs)
prod返回不同维度上的乘积。

练习4

请使用【b】中的传入字典的方法完成【a】中等价的聚合任务。

gb = df.groupby('Gender')[['Weight','Height']]
gb.agg(['sum', 'idxmax', 'skew'])

		Weight						Height
		sum		idxmax	skew		sum		idxmax	skew
Gender						
Female	6469.0	28		-0.268482	21014.0	28		-0.219253
Male	3929.0	2		-0.332393	8854.9	193		0.437535
gb.agg({'Weight':['sum','idxmax','skew'], 'Height':['sum','idxmax','skew']})

		Weight						Height
		sum		idxmax	skew		sum		idxmax	skew
Gender						
Female	6469.0	28		-0.268482	21014.0	28		-0.219253
Male	3929.0	2		-0.332393	8854.9	193		0.437535

练习5

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

gb.describe()

		Weight																	Height
		count	mean		std			min		25%		50%		75%		max		count	mean		std			min		25%		50%		75%		max
Gender																
Female	135.0	47.918519	5.405983	34.0	44.0	48.0	52.00	63.0	132.0	159.19697	5.053982	145.4	155.675	159.6	162.825	170.2
Male	54.0	72.759259	7.772557	51.0	69.0	73.0	78.75	89.0	51.0	173.62549	7.048485	155.7	168.900	173.4	177.150	193.9
gb.agg(['count','mean','std','min',('25%',lambda x: x.quantile(0.25)),('50%','quantile'),('75%',lambda x: x.quantile(0.75)),'max'])

		Weight																	Height
		count	mean		std			min		25%		50%		75%		max		count	mean		std			min		25%		50%		75%		max
Gender																
Female	135.0	47.918519	5.405983	34.0	44.0	48.0	52.00	63.0	132.0	159.19697	5.053982	145.4	155.675	159.6	162.825	170.2
Male	54.0	72.759259	7.772557	51.0	69.0	73.0	78.75	89.0	51.0	173.62549	7.048485	155.7	168.900	173.4	177.150	193.9

练习6

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

# 根据不同的性别对身高和体重进行排序
gb.rank().head()

	Weight	Height
0	47.5	58.0
1	19.0	5.0
2	54.0	50.0
3	14.5	NaN
4	31.5	27.0

练习7

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

gb.agg({'Weight':'max','Height':'mean'})

		Weight	Height
Gender		
Female	63.0	159.19697
Male	89.0	173.62549

六、练习

Ex1:汽车数据集

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

  1. 先过滤出所属 Country 数超过2个的汽车,即若该汽车的 Country在总体数据集中出现次数不超过2则剔除,再按 Country 分组计算价格均值、价格变异系数、该 Country 的汽车数量,其中变异系数的计算方法是标准差除以均值,并在结果中把变异系数重命名为 CoV

参考文献

  1. https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch4.html#id10
  2. https://www.runoob.com/python/python-built-in-functions.html
  3. https://blog.csdn.net/u010665216/article/details/78591288?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160890518016780276319374%252522%25252C%252522scm%252522%25253A%25252220140713.130102334…%252522%25257D&request_id=160890518016780276319374&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-1-78591288.nonecase&utm_term=pandas%20skew
  4. https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.mad.html
  5. https://blog.csdn.net/ge_nious/article/details/78959004?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160890534716780273310094%252522%25252C%252522scm%252522%25253A%25252220140713.130102334…%252522%25257D&request_id=160890534716780273310094&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-4-78959004.nonecase&utm_term=pandas%20prod
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值