DataWhale & Pandas(四、分组)

DataWhale & Pandas(四、分组)


Pandas学习手册


学习大纲: 

DataWhale & Pandas(四、分组)


目录

DataWhale & Pandas(四、分组)

学习大纲: 

补充:

分组统计方法

 通过字典或者Series分组

分组 - 可迭代对象

聚合 Aggregations

筛选 Filtration

时序重采样 resample

例子:按组填充缺失值

思路

方法一:

方法二:

一、分组模式及其对象

1.1. 分组的一般模式

1.2. 分组依据的本质

1.3. Groupby对象

1.4. 分组的三大操作

二、聚合函数

2.1 内置聚合函数

2.2 agg方法

注意

三、变换和过滤

3.1. 变换函数与transform方法

3.2. 组索引与过滤

四、跨列分组

4.1 apply的引入

4.2. apply的使用

注意:

五、练习

Ex1:汽车数据集

Ex2:实现transform函数

心得: 


补充:

分组统计方法

分组对象支持几乎所有的 df 的统计方法,见数学统计方法

grouped = s.groupby(level=0)  # 唯一索引用.groupby(level=0),将同一个index的分为一组
print(grouped)
print(grouped.first(),'→ first:非NaN的第一个值\n')
print(grouped.last(),'→ last:非NaN的最后一个值\n')
print(grouped.sum(),'→ sum:非NaN的和\n')
print(grouped.mean(),'→ mean:非NaN的平均值\n')
print(grouped.median(),'→ median:非NaN的算术中位数\n')
print(grouped.count(),'→ count:非NaN的值\n')
print(grouped.min(),'→ min、max:非NaN的最小值、最大值\n')
print(grouped.std(),'→ std,var:非NaN的标准差和方差\n')
print(grouped.prod(),'→ prod:非NaN的积\n')

grouped.corr()
grouped.sem()
grouped.prod()
grouped.cummax() # 每组的累计最大值
grouped.cumsum() # 累加
grouped.mad() # 平均绝对偏差

# 特别的有
df.groupby('team').ngroups # 5 分组数
df.groupby('team').ngroup() # 分组序号
df.groupby('team').first() # 组内第一个
df.groupby('team').last() # 组内最后一个

# 库姆计数,按组对成员标记, 支持正排倒排
# 返回每个元素在所在组的序号的序列
grouped.cumcount(ascending=False)

 通过字典或者Series分组

df = pd.DataFrame(np.arange(16).reshape(4,4),columns = ['a','b','c','d'])
print(df)
mapping = {'a':'one','b':'one','c':'two','d':'two','e':'three'}
by_column = df.groupby(mapping, axis = 1)
print(by_column.sum())
# mapping中,a、b列对应的为one,c、d列对应的为two,以字典来分组
s = pd.Series(mapping)
print(s,'\n')
print(s.groupby(s).count())
# s中,index中a、b对应的为one,c、d对应的为two,以Series来分组

分组 - 可迭代对象

df = pd.DataFrame({'X' : ['A', 'B', 'A', 'B'], 'Y' : [1, 4, 3, 2]})
print(df)
print(df.groupby('X'), type(df.groupby('X')))
print('-----')

print(list(df.groupby('X')), '→ 可迭代对象,直接生成list\n')
print(list(df.groupby('X'))[0], '→ 以元祖形式显示\n')
for n,g in df.groupby('X'):
    print(n)
    print(g)
    print('###')
print('-----')
# n是组名,g是分组后的Dataframe

print(df.groupby(['X']).get_group('A'),'\n')
print(df.groupby(['X']).get_group('B'),'\n')
print('-----')
# .get_group()提取分组后的组

grouped = df.groupby(['X'])
print(grouped.groups)
print(grouped.groups['A'])  # 也可写:df.groupby('X').groups['A']
print('-----')
# .groups:将分组后的groups转为dict
# 可以字典索引方法来查看groups里的元素

sz = grouped.size()
print(sz,type(sz))
print('-----')
# .size():查看分组后的长度

df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar','foo', 'bar', 'foo', 'foo'],
                   'B' : ['one', 'one', 'two', 'three', 'two', 'two', 'one', 'three'],
                   'C' : np.random.randn(8),
                   'D' : np.random.randn(8)})
print(df)
print()
print(df.groupby(['A','B']))
print()
grouped = df.groupby(['A','B']).groups

print(grouped)
print()
print(grouped[('foo', 'three')])
# 按照两个列进行分组

聚合 Aggregations

.aggregate() 简写为 .agg()。它的作用是将分组后的对象给定统计方法,也支持按字段分别给定不同的统计方法。

# 所有列使用一个计算计算方法
df.groupby('team').aggregate(sum)
df.groupby('team').agg(sum)
grouped.agg(np.size)
grouped['Q1'].agg(np.mean)

## 多个计算方法
# 所有列指定多个计算方法
grouped.agg([np.sum, np.mean, np.std])
# 指定列使用多个计算方法
grouped['Q1'].agg([sum, np.mean, np.std])
# 一列使用多个计算方法
df.groupby('team').agg({'Q1': ['min', 'max'], 'Q2': 'sum'})

# 指定列名,列表是为原列和方法
df.groupby('team').Q1.agg(Mean='mean', Sum='sum')
df.groupby('team').agg(Mean=('Q1', 'mean'), Sum=('Q2', 'sum'))
df.groupby('team').agg(
    Q1_max=pd.NamedAgg(column='Q1', aggfunc='max'),
    Q2_min=pd.NamedAgg(column='Q2', aggfunc='min')
)
# 如果列名不是有效的 python 变量,则可以用以下方法
df.groupby('team').agg(**{
    '1_max':pd.NamedAgg(column='Q1', aggfunc='max')})

# 聚合结果使用函数
# lambda/函数 所有方法都可以用
def max_min(x):
    return x.max() - x.min()
# 定义函数
df.groupby('team').Q1.agg(Mean='mean',
                          Sum='sum',
                          Diff=lambda x: x.max() - x.min(),
                          Max_min=max_min
                         )

# 不同列不同的计算方法
df.groupby('team').agg({'Q1': sum,  # 总和
                        'Q2': 'count', # 总数
                        'Q3':'mean', # 平均
                        'Q4': max}) # 最大值

# 分组对象使用函数
# 定义函数
def max_min(var):
    return var.max() - var.min()
# 调用函数
df.groupby('team').agg(max_min)

 筛选 Filtration

filter() 筛选后后显示筛选后的原 dataframe,内容是满足组条件的所有明细:

# 值的长度都大于等于 3 的
df.groupby('team').filter(lambda x: len(x) >= 3)
# Q1成绩只要有一个大于97的组
df.groupby(['team']).filter(lambda x: (x['Q1'] > 97).any())
# 所有成员平均成绩大于 60 的组
df.groupby(['team']).filter(lambda x: (x.mean() >= 60).all())
# Q1 所有成员成绩之和超过 1060 的组
df.groupby('team').filter(lambda g: g.Q1.sum() > 1060)

时序重采样 resample

使用 TimeGrouper 对时间进行分组:

idx = pd.date_range('1/1/2000', periods=100, freq='T')
df = pd.DataFrame(data=1 * [range(2)],
                  index=idx,
                  columns=['a', 'b'])

# 三个周期一聚合(一分钟一个周期)
df.groupby('a').resample('3T').sum()
# 30 秒一分组
df.groupby('a').resample('30S').sum()
# 每月
df.groupby('a').resample('M').sum()
# 以右边时间点为标识
df.groupby('a').resample('3T', closed='right').sum()

 例子:按组填充缺失值

需要在源数据中填充缺失值,在类别列标示的AB两组数据中,每列每组只有一个有值的数据,需要按组将缺失值填充为这个值。

源数据:

类别 col1 col2 col3
A  NaN  NaN  NaN
A    d  NaN  NaN
A  NaN  NaN  NaN
A  NaN    c    e
B  NaN    Y    Z
B  NaN  NaN  NaN
B    X  NaN  NaN
将以上源数据中的缺失值填充完成如下样子:

  类别 col1 col2 col3
0  A    d    c    e
1  A    d    c    e
2  A    d    c    e
3  A    d    c    e
4  B    X    Y    Z
5  B    X    Y    Z
6  B    X    Y    Z

思路

由于要按组来处理数据,就需要用 Pandas 的 groupby 进行分组,分组后填充数据用 fillna,用 apply 调用函数来应用 fillna。

由于 fillna 的填充方法没有取唯一非空值填充的方法,我们需要用 method='bfill' 先向后填充,再用 method='ffill' 向前填充,来完成所有缺失值位置的填充。

方法一:

# 结果为需求中的目标数据
    df.groupby('类别')
    .apply(lambda x: x.fillna(method='bfill'))
    .groupby('类别')
    .apply(lambda x: x.fillna(method='ffill'))

方法二:

# 一
    df.groupby('类别')
    .apply(lambda x: x.fillna(method='bfill').fillna(method='ffill'))
# 二
    df.groupby('类别')
    .apply(lambda x: x.fillna(method='bfill'))
    .fillna(method='ffill')
import numpy as np
import pandas as pd

一、分组模式及其对象

1.1. 分组的一般模式

分组操作在日常生活中使用极其广泛,例如:

  • 依据 性别 分组,统计全国人口 寿命 的 平均值

  • 依据 季节 分组,对每一个季节的 温度 进行 组内标准化

  • 依据 班级 分组,筛选出组内 数学分数 的 平均值超过80分的班级

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

例如:

df.groupby('Gender')['Longevity'].mean()

如果想要按照性别统计身高中位数:

df.groupby('Gender')['Height'].median()

1.2. 分组依据的本质

如果现在需要根据多个维度进行分组,该如何做?事实上,只需在 groupby 中传入相应列名构成的列表即可。

groupby 的分组依据都是直接可以从列中按照名字获取的

# 例如根据学生体重是否超过总体均值来分组,同样还是计算身高的均值。

# 首先应该先写出分组条件:
condition = df.Weight > df.Weight.mean()
#传入groupby
df.groupby(condition)['Height'].mean()

通过 drop_duplicates 就能知道具体的组类别:

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

1.3. Groupby对象

最终具体做分组操作时,所调用的方法都来自于 pandas 中的 groupby 对象

gb = df.groupby(['School', 'Grade'])
# 通过 ngroups 属性,可以得到分组个数
gb.ngroups 
# 通过 groups 属性,可以返回从 组名 映射到 组索引列表 的字典
res = gb.groups
res.keys() # 字典的值由于是索引,元素个数过多,此处只展示字典的键

统计每个组的元素个数:

gb.size()

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

gb.get_group(('Fudan University', 'Freshman')).iloc[:3, :3]

1.4. 分组的三大操作

三大操作:聚合、变换和过滤,分别对应了:agg 、 transform 和 filter函数及其操作

二、聚合函数

2.1 内置聚合函数

根据返回标量值的原则,包括如下函数:

 max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod 

2.2 agg方法

虽然在 groupby 对象上定义了许多方便的函数,但仍然有以下不便之处:

  • 无法同时使用多个函数

  • 无法对特定的列使用特定的聚合函数

  • 无法使用自定义的聚合函数

  • 无法直接对结果的列名在聚合前进行自定义命名

【a】使用多个函数

当使用多个聚合函数时,需要用列表的形式把内置聚合函数对应的字符串传入,先前提到的所有字符串都是合法的。

gb.agg(['sum', 'idxmax', 'skew'])

【b】对特定的列使用特定的聚合函数

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

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

【c】使用自定义函数

需要注意传入函数的参数是之前数据源中的列,逐列进行计算 

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

【d】聚合结果重命名
将上述函数的位置改写成元组,元组的第一个元素为新的名字,第二个位置为原来的函数,包括聚合字符串和自定义函数

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

注意

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

三、变换和过滤

3.1. 变换函数与transform方法

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

gb.cummax().head()
gb.transform(lambda x: (x-x.mean())/x.std()).head()
  • 当用自定义变换时需要使用 transform 方法,被调用的自定义函数, 其传入值为数据源的序列 ,与 agg 的传入类型是一致的,其最后的返回结果是行列索引与数据源一致的 DataFrame 。
  •  transform 只能返回同长度的序列,但事实上还可以返回一个标量,这会使得结果被广播到其所在的整个组

3.2. 组索引与过滤

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

例子:在原表中通过过滤得到所有容量大于100的组:

gb.filter(lambda x: x.shape[0] > 100).head()

四、跨列分组

4.1 apply的引入

  •  使用apply 函数来进行多列数据同时处理

4.2. apply的使用

  •  apply 的自定义函数传入参数与 filter 完全一致,只不过后者只允许返回布尔值。
  • 除了返回标量之外, apply 方法还可以返回一维 Series 和二维 DataFrame 

【a】标量情况:结果得到的是 Series ,索引与 agg 的结果一致

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

gb.apply(lambda x: 0)

# 虽然是列表,但是作为返回值仍然看作标量

gb.apply(lambda x: [0, 0])

【b】 Series 情况:得到的是 DataFrame ,行索引与标量情况一致,列索引为 Series 的索引

gb.apply(lambda x: pd.Series([0,0],index=['a','b']))

【c】 DataFrame 情况:得到的是 DataFrame ,行索引最内层在每个组原先 agg 的结果索引上,再加一层返回的 DataFrame 行索引,同时分组结果 DataFrame 的列索引和返回的 DataFrame 列索引一致

gb.apply(lambda x: pd.DataFrame(np.ones((2,2)),
                                index = ['a','b'],
                               columns=pd.Index([('w','x'),('y','z')])))

注意:

        apply 函数的灵活性是以牺牲一定性能为代价换得的,除非需要使用跨列处理的分组处理,否则应当使用其他专门设计的 groupby 对象方法,否则在性能上会存在较大的差距。同时,在使用聚合函数和变换函数时,也应当优先使用内置函数,它们经过了高度的性能优化,一般而言在速度上都会快于用自定义函数来实现。

五、练习

Ex1:汽车数据集

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

df = pd.read_csv('data/car.csv')
df.head(3)
  • 先过滤出所属 Country 数超过2个的汽车,即若该汽车的 Country 在总体数据集中出现次数不超过2则剔除,再按 Country 分组计算价格均值、价格变异系数、该 Country 的汽车数量,其中变异系数的计算方法是标准差除以均值,并在结果中把变异系数重命名为 CoV 。

  • 按照表中位置的前三分之一、中间三分之一和后三分之一分组,统计 Price 的均值。

  • 对类型 Type 分组,对 Price 和 HP 分别计算最大值和最小值,结果会产生多级索引,请用下划线把多级列索引合并为单层索引。

  • 对类型 Type 分组,对 HP 进行组内的 min-max 归一化。

  • 对类型 Type 分组,计算 Disp. 与 HP 的相关系数。

Ex2:实现transform函数

  • groupby 对象的构造方法是 my_groupby(df, group_cols)

  • 支持单列分组与多列分组

  • 支持带有标量广播的 my_groupby(df)[col].transform(my_func) 功能

  • pandas 的 transform 不能跨列计算,请支持此功能,即仍返回 Series 但 col 参数为多列

  • 无需考虑性能与异常处理,只需实现上述功能,在给出测试样例的同时与 pandas 中的 transform 对比结果是否一致

心得: 

国奖的纪念品里面有个小纸条,上面是这么写的:

  • 伴过自习室不眠的分与秒
  • 流过赛场上无尽的汗与泪
  • 一天   一月  一年
  • 你从书窗望去——
  • 凌云之志  奋发之姿  是如今的你
  • 国之脊梁  世界之光  是你的未来
上一篇:第四章 分组


下一篇:Latex中的表格