分组模式及其对象

分组的一般模式

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

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

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

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

从上述的几个例子中不难看出,想要实现分组操作,必须明确三个要素:分组依据 、 数据来源 、 操作及其返回结果。同时从充分性的角度来说,如果明确了这三方面,就能确定一个分组操作,从而分组代码的一般模式即:

df.groupby(分组依据)[数据来源].使用操作
df = pd.read_csv('G:\代码\joyful-pandas-master\data\learn_pandas.csv')
df.head()
School Grade Name Gender Height Weight Transfer Test_Number Test_Date Time_Record
0 Shanghai Jiao Tong University Freshman Gaopeng Yang Female 158.9 46.0 N 1 2019/10/5 0:04:34
1 Peking University Freshman Changqiang You Male 166.5 70.0 N 1 2019/9/4 0:04:20
2 Shanghai Jiao Tong University Senior Mei Sun Male 188.9 89.0 N 2 2019/9/12 0:05:22
3 Fudan University Sophomore Xiaojuan Sun Female NaN 41.0 N 2 2020/1/3 0:04:08
4 Fudan University Sophomore Gaojuan You Male 174.0 74.0 N 2 2019/11/6 0:05:22

按照性别统计身高中位数:

df.groupby('Gender')['Height'].median()
Gender
Female    159.6
Male      173.4
Name: Height, dtype: float64

分组依据的本质

前面提到的若干例子都是以单一维度进行分组的,比如根据性别,如果现在需要根据多个维度进行分组,该如何做?事实上,只需在 groupby 中传入相应列名构成的列表即可。

例如,现希望根据学校和性别进行分组,统计身高的均值就可以如下写出:

df.groupby(['School', 'Gender'])['Height'].mean()
School                         Gender
Fudan University               Female    158.776923Male      174.212500
Peking University              Female    158.666667Male      172.030000
Shanghai Jiao Tong University  Female    159.122500Male      176.760000
Tsinghua University            Female    159.753333Male      171.638889
Name: Height, dtype: float64

目前为止, groupby 的分组依据都是直接可以从列中按照名字获取的,那如果希望通过一定的复杂逻辑来分组,例如根据学生体重是否超过总体均值来分组,同样还是计算身高的均值。

首先应该先写出分组条件:

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

然后将其传入 groupby 中:

df.groupby(condition)['Height'].mean()
Weight
False    159.034646
True     172.705357
Name: Height, dtype: float64

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

# 方法一
df_copy = df.copy()
df_copy['Weight'][df.Weight>df['Weight'].quantile(0.75)] = 'high'
df_copy['Weight'][(df.Weight>=df['Weight'].quantile(0.25)) & (df.Weight<=df['Weight'].quantile(0.75))] = 'normal'
df_copy['Weight'][df.Weight<df['Weight'].quantile(0.75)] = 'low'
df_copy.groupby('Weight')['Height'].mean()
Weight
high      174.935714
low       159.262308
normal    165.600000
Name: Height, dtype: float64
# 方法二
ind = df['Weight'].copy()
ind[df.Weight>df['Weight'].quantile(0.75)] = 'high'
ind[(df.Weight>=df['Weight'].quantile(0.25)) & (df.Weight<=df['Weight'].quantile(0.75))] = 'normal'
ind[df.Weight<df['Weight'].quantile(0.75)] = 'low'
df.groupby(ind)['Height'].mean()
Weight
high      174.935714
low       159.262308
normal    165.600000
Name: Height, dtype: float64

从索引可以看出,其实最后产生的结果就是按照条件列表中元素的值(此处是 True 和 False )来分组,下面用随机传入字母序列来验证这一想法:

item = np.random.choice(list('abc'), df.shape[0])
df.groupby(item)['Height'].mean()
a    163.589286
b    162.309677
c    163.764615
Name: Height, dtype: float64

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

df.groupby([condition, item])['Height'].mean()
Weight
False   a    159.110526b    158.723256c    159.263043
True    a    173.044444b    170.426316c    174.663158
Name: Height, dtype: float64

由此可以看出,之前传入列名只是一种简便的记号,事实上等价于传入的是一个或多个列,最后分组的依据来自于数据来源组合的unique值,通过 drop_duplicates 就能知道具体的组类别:

df[['School', 'Gender']].drop_duplicates()
School Gender
0 Shanghai Jiao Tong University Female
1 Peking University Male
2 Shanghai Jiao Tong University Male
3 Fudan University Female
4 Fudan University Male
5 Tsinghua University Female
9 Peking University Female
16 Tsinghua University Male
df.groupby([df['School'], df['Gender']])['Height'].mean()
School                         Gender
Fudan University               Female    158.776923Male      174.212500
Peking University              Female    158.666667Male      172.030000
Shanghai Jiao Tong University  Female    159.122500Male      176.760000
Tsinghua University            Female    159.753333Male      171.638889
Name: Height, dtype: float64

Groupby对象

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

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

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

gb.ngroups
16

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

res = gb.groups
res.keys() # 字典的值由于是索引,元素个数过多,此处只展示字典的键
dict_keys([('Fudan University', 'Freshman'), ('Fudan University', 'Junior'), ('Fudan University', 'Senior'), ('Fudan University', 'Sophomore'), ('Peking University', 'Freshman'), ('Peking University', 'Junior'), ('Peking University', 'Senior'), ('Peking University', 'Sophomore'), ('Shanghai Jiao Tong University', 'Freshman'), ('Shanghai Jiao Tong University', 'Junior'), ('Shanghai Jiao Tong University', 'Senior'), ('Shanghai Jiao Tong University', 'Sophomore'), ('Tsinghua University', 'Freshman'), ('Tsinghua University', 'Junior'), ('Tsinghua University', 'Senior'), ('Tsinghua University', 'Sophomore')])

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

df.groupby([df['School'], df['Gender']])['Height'].groups.keys()
dict_keys([('Fudan University', 'Female'), ('Fudan University', 'Male'), ('Peking University', 'Female'), ('Peking University', 'Male'), ('Shanghai Jiao Tong University', 'Female'), ('Shanghai Jiao Tong University', 'Male'), ('Tsinghua University', 'Female'), ('Tsinghua University', 'Male')])

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

gb.size()
School                         Grade
Fudan University               Freshman      9Junior       12Senior       11Sophomore     8
Peking University              Freshman     13Junior        8Senior        8Sophomore     5
Shanghai Jiao Tong University  Freshman     13Junior       17Senior       22Sophomore     5
Tsinghua University            Freshman     17Junior       22Senior       14Sophomore    16
dtype: int64

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

gb.get_group(('Fudan University', 'Freshman')).iloc[:3, :3] # 展示一部分
School Grade Name
15 Fudan University Freshman Changqiang Yang
28 Fudan University Freshman Gaoqiang Qin
63 Fudan University Freshman Gaofeng Zhao

分组的三大操作

熟悉了一些分组的基本知识后,重新回到开头举的(分别依据性别、季节、班级分组)三个例子,可能会发现一些端倪,即这三种类型分组返回的数据型态并不一样:

第一个例子中,每一个组返回一个标量值,可以是平均值、中位数、组容量 size 等

第二个例子中,做了原序列的标准化处理,也就是说每组返回的是一个 Series 类型

第三个例子中,既不是标量也不是序列,返回的整个组所在行的本身,即返回了 DataFrame 类型

由此,引申出分组的三大操作:聚合、变换和过滤,分别对应了三个例子的操作,下面就要分别介绍相应的 agg 、 transform 和 filter 函数及其操作。

聚合函数

内置聚合函数

在介绍agg之前,首先要了解一些直接定义在groupby对象的聚合函数,因为它的速度基本都会经过内部的优化,使用功能时应当优先考虑。根据返回标量值的原则,包括如下函数:

  • max, min
  • mean, median, quantile, std, var
  • count, sum
  • all, any
  • idxmax, idxmin
  • mad
  • nunique
  • skew
  • sem
  • size
  • prod
gb = df.groupby('Gender')['Height']
gb.idxmin()
Gender
Female    143
Male      199
Name: Height, dtype: int64
gb.quantile(0.95)
Gender
Female    166.8
Male      185.9
Name: Height, dtype: float64

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

gb = df.groupby('Gender')[['Height', 'Weight']]
gb.max()
Height Weight
Gender
Female 170.2 63.0
Male 193.9 89.0

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

  • all和any用于判断布尔值为True还是False(不为0的数字为True)

    • all:当序列元素全为 True 或非零元素时返回 True ,否则返回 False
    • any:当序列至少存在一个 True 或非零元素时返回 True ,否则返回 False
  • mad (mean absolute deviation) 平均绝对离差 , 用于分析分组后的每组数据做离散程度
    Mi=1n∑k=1n∣xk−xˉ∣M_i = \frac{1}{n} \sum_{k=1}^{n} |x_k-\bar{x}| Mi​=n1​k=1∑n​∣xk​−xˉ∣
  • skew (skewness) 偏度,用来反映分组后每组数据分布的偏态程度 , 正值为右偏 , 绝对值越大 , 偏度越高
    SKi=nn−1n−2∑k=1n(xk−xˉ)3(∑k=1n(xk−xˉ)2)32S K_{i}=\frac{n \sqrt{n-1}}{n-2} \frac{\sum_{k=1}^{n}\left(x_{k}-\bar{x}\right)^{3}}{\left(\sum_{k=1}^{n}\left(x_{k}-\bar{x}\right)^{2}\right)^{\frac{3}{2}}}SKi​=n−2nn−1​​(∑k=1n​(xk​−xˉ)2)23​∑k=1n​(xk​−xˉ)3​
  • kurt (kurtosis) 峰度,用来反映分组后每组数据分布的平尖程度,正值为尖峰分布,值越大越尖,负值为扁平程度,绝对值越大越扁平
    Ki=n(n+1)(n−1)(n−2)(n−3)∑k=1n(xk−xˉ)4(∑k=1n(xk−xˉ)2)2−3(n−1)2(n−2)(n−3)K_{i}=\frac{n(n+1)(n-1)}{(n-2)(n-3)} \frac{\sum_{k=1}^{n}\left(x_{k}-\bar{x}\right)^{4}}{\left(\sum_{k=1}^{n}\left(x_{k}-\bar{x}\right)^{2}\right)^{2}}-\frac{3(n-1)^{2}}{(n-2)(n-3)}Ki​=(n−2)(n−3)n(n+1)(n−1)​(∑k=1n​(xk​−xˉ)2)2∑k=1n​(xk​−xˉ)4​−(n−2)(n−3)3(n−1)2​
  • sem (standard error of mean) 均值标准误差,描述的是多个均值样本的标准差,体现均值抽样分布的离散程度,反映样本均值之间的差异
    SEMi=sNS E M_{i}=\frac{s}{\sqrt{N}}SEMi​=N​s​
    其中sss为样本的无偏估计标准差
  • prod (product) 连乘 , 每组prod表示为
    prodi=∏k=1nxkprod_{i} = \prod_{k=1}^n x_{k}prodi​=k=1∏n​xk​

agg方法

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

  • 无法同时使用多个函数

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

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

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

下面说明如何通过 agg 函数解决这四类问题:

使用多个函数

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

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

从结果看,此时的列索引为多级索引,第一层为数据源,第二层为使用的聚合方法,分别逐一对列使用聚合,因此结果为6列。

对特定的列使用特定的聚合函数

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

gb.agg({'Height':['mean','max'], 'Weight':'count'})
Height Weight
mean max count
Gender
Female 159.19697 170.2 135
Male 173.62549 193.9 54

Qusetion: 请使用传入字典的方法完成前一节中等价的聚合任务。

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

使用自定义函数

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

gb.agg(lambda x: x.mean()-x.min())
Height Weight
Gender
Female 13.79697 13.918519
Male 17.92549 21.759259

由于传入的是序列,因此序列上的方法和属性都是可以在函数中使用的,只需保证返回值是标量即可。下面的例子是指,如果组的指标均值,超过该指标的总体均值,返回High,否则返回Low。

def my_func(s):res = 'High'if s.mean() <= df[s.name].mean():res = 'Low'return res gb.agg(my_func)
Height Weight
Gender
Female Low Low
Male High High

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

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

聚合结果重命名

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

gb.agg([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')])
Height Weight
range my_sum range my_sum
Gender
Female 24.8 21014.0 29.0 6469.0
Male 38.2 8854.9 38.0 3929.0
gb.agg({'Height': [('rename_func', my_func), 'sum'],'Weight': lambda x:x.max()})
Height Weight
rename_func sum <lambda>
Gender
Female Low 21014.0 63.0
Male High 8854.9 89.0

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

gb.agg([('my_sum', 'sum')])
Height Weight
my_sum my_sum
Gender
Female 21014.0 6469.0
Male 8854.9 3929.0
gb.agg({'Height': [('my_func', my_func), 'sum'],'Weight': [('range', lambda x:x.max())]})
Height Weight
my_func sum range
Gender
Female Low 21014.0 63.0
Male High 8854.9 89.0

变换和过滤

变换函数与transform方法

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

gb.cummax().head()
Height Weight
0 158.9 46.0
1 166.5 70.0
2 188.9 89.0
3 NaN 46.0
4 188.9 89.0

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

a = pd.DataFrame(np.arange(12).reshape(3,4),columns = list("abdc"))
a =a.sort_index(axis=1,ascending=False)
a
d c b a
0 2 3 1 0
1 6 7 5 4
2 10 11 9 8
a.rank() # 显示了排名,默认按列进行排序
d c b a
0 1.0 1.0 1.0 1.0
1 2.0 2.0 2.0 2.0
2 3.0 3.0 3.0 3.0
a.rank(axis=1) # 按行进行排序
d c b a
0 3.0 4.0 2.0 1.0
1 3.0 4.0 2.0 1.0
2 3.0 4.0 2.0 1.0
a.iloc[[1,1],[1,2]] = 6
a
d c b a
0 2 3 1 0
1 6 6 6 4
2 10 11 9 8
a.rank(axis=1,method='average') # 在排名相等分组中,取其平均排名
# 数字6占了排名2,3,4的位置,取2,3,4平均值3
d c b a
0 3.0 4.0 2.0 1.0
1 3.0 3.0 3.0 1.0
2 3.0 4.0 2.0 1.0
a.rank(axis=1,method='max') # 在排名相等分组中,取其最大排名
d c b a
0 3.0 4.0 2.0 1.0
1 4.0 4.0 4.0 1.0
2 3.0 4.0 2.0 1.0
a.rank(axis=1,method='min') # 在排名相等分组中,取其最小排名
d c b a
0 3.0 4.0 2.0 1.0
1 2.0 2.0 2.0 1.0
2 3.0 4.0 2.0 1.0
a.rank(axis=1,method='first') # 在排名相等分组中,按原始数据中的出现顺序分配排名
d c b a
0 3.0 4.0 2.0 1.0
1 2.0 3.0 4.0 1.0
2 3.0 4.0 2.0 1.0

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

现对身高和体重进行分组标准化,即减去组均值后除以组的标准差:

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

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

# 方法一
gb.transform(lambda x:x.count() if x.name=='Height' else x.mean()).head()
Height Weight
0 132.0 47.918519
1 51.0 72.759259
2 51.0 72.759259
3 132.0 47.918519
4 51.0 72.759259
# 方法二
gb.transform(lambda x:{'Height':eval('x.count()'),'Weight':eval('x.mean()')}[x.name]).head()
Height Weight
0 132.0 47.918519
1 51.0 72.759259
2 51.0 72.759259
3 132.0 47.918519
4 51.0 72.759259

前面提到了 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

组索引与过滤

过滤在分组中是对于组的过滤,而索引是对于行的过滤,在第二章中的返回值,无论是布尔列表还是元素列表或者位置列表,本质上都是对于行的筛选,即如果符合筛选条件的则选入结果表,否则不选入。

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

在 groupby 对象中,定义了 filter 方法进行组的筛选,其中自定义函数的输入参数为数据源构成的 DataFrame 本身,在之前例子中定义的 groupby 对象中,传入的就是 df[[‘Height’, ‘Weight’]] ,因此所有表方法和属性都可以在自定义函数中相应地使用,同时只需保证自定义函数的返回为布尔值即可。

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

gb.filter(lambda x: x.shape[0] > 100).head()
Height Weight
0 158.9 46.0
3 NaN 41.0
5 158.0 51.0
6 162.5 52.0
7 161.9 50.0

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

df.groupby(df.index.isin([2,4,6])).filter(lambda x:x.name) # 分组完就True和False两类,filter返回为True的组
School Grade Name Gender Height Weight Transfer Test_Number Test_Date Time_Record
2 Shanghai Jiao Tong University Senior Mei Sun Male 188.9 89.0 N 2 2019/9/12 0:05:22
4 Fudan University Sophomore Gaojuan You Male 174.0 74.0 N 2 2019/11/6 0:05:22
6 Shanghai Jiao Tong University Freshman Qiang Chu Female 162.5 52.0 N 1 2019/12/12 0:03:53

跨列分组

apply的引入

之前几节介绍了三大分组操作,但事实上还有一种常见的分组场景,无法用前面介绍的任何一种方法处理,例如现在如下定义身体质量指数BMI:

BMI=WeightHeight2{\rm BMI} = {\rm\frac{Weight}{Height^2}}BMI=Height2Weight​
其中体重和身高的单位分别为千克和米,需要分组计算组BMI的均值。

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

apply的使用

def BMI(x):Height = x['Height']/100Weight = x['Weight']BMI_value = Weight/Height**2return BMI_value.mean()gb.apply(BMI)
Gender
Female    18.860930
Male      24.318654
dtype: float64

除了返回标量之外, apply 方法还可以返回一维 Series 和二维 DataFrame ,但它们产生的数据框维数和多级索引的层数应当如何变化?下面举三组例子就非常容易明白结果是如何生成的:

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

gb = df.groupby(['Gender','Test_Number'])[['Height','Weight']]
gb.apply(lambda x: 0)
Gender  Test_Number
Female  1              02              03              0
Male    1              02              03              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

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

gb.apply(lambda x: pd.Series([0,0],index=['a','b']))
a b
Gender Test_Number
Female 1 0 0
2 0 0
3 0 0
Male 1 0 0
2 0 0
3 0 0

Question: 请尝试在 apply 传入的自定义函数中,根据组的某些特征返回相同长度但索引不同的 Series,会报错吗?

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')])))
w y
x z
Gender Test_Number
Female 1 a 1.0 1.0
b 1.0 1.0
2 a 1.0 1.0
b 1.0 1.0
3 a 1.0 1.0
b 1.0 1.0
Male 1 a 1.0 1.0
b 1.0 1.0
2 a 1.0 1.0
b 1.0 1.0
3 a 1.0 1.0
b 1.0 1.0

Question: 请尝试在 apply 传入的自定义函数中,根据组的某些特征返回相同大小但列索引不同的 DataFrame ,会报错吗?如果只是行索引不同,会报错吗?

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

Question: 在 groupby 对象中还定义了 cov 和 corr 函数,从概念上说也属于跨列的分组处理。请利用之前定义的 gb 对象,使用apply函数实现与 gb.cov() 同样的功能并比较它们的性能。

gb.cov().head()
Height Weight
Gender Test_Number
Female 1 Height 20.963600 21.452034
Weight 21.452034 26.438244
2 Height 31.615680 30.386170
Weight 30.386170 34.568250
3 Height 23.582395 20.801307
gb.apply(lambda x:x.cov()).head()
Height Weight
Gender Test_Number
Female 1 Height 20.963600 21.452034
Weight 21.452034 26.438244
2 Height 31.615680 30.386170
Weight 30.386170 34.568250
3 Height 23.582395 20.801307

练习

汽车数据集

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

df = pd.read_csv('G:\代码\joyful-pandas-master\data\car.csv')
df.head(3)
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 。
df_copy = df.copy()
df_copy.groupby('Country').filter(lambda x:x.shape[0]>2).groupby('Country')['Price'].agg([('CoV', lambda x: x.std()/x.mean()), 'mean', 'count'])
CoV mean count
Country
Japan 0.387429 13938.052632 19
Japan/USA 0.240040 10067.571429 7
Korea 0.243435 7857.333333 3
USA 0.203344 12543.269231 26
  1. 按照表中位置的前三分之一、中间三分之一和后三分之一分组,统计 Price 的均值。
df_copy.shape[0]
60
condition = ['Head']*20+['Mid']*20+['Tail']*20
df_copy.groupby(condition)['Price'].mean()
Head     9069.95
Mid     13356.40
Tail    15420.65
Name: Price, dtype: float64
  1. 对类型 Type 分组,对 Price 和 HP 分别计算最大值和最小值,结果会产生多级索引,请用下划线把多级列索引合并为单层索引
res = df_copy.groupby('Type').agg({'Price': ['max'], 'HP': ['min']})
res.columns = res.columns.map(lambda x:'_'.join(x))
res
Price_max HP_min
Type
Compact 18900 95
Large 17257 150
Medium 24760 110
Small 9995 63
Sporty 13945 92
Van 15395 106
  1. 对类型 Type 分组,对 HP 进行组内的 min-max 归一化。
def normalize(s):s_min, s_max = s.min(), s.max()res = (s - s_min)/(s_max - s_min)return resdf_copy.groupby('Type')['HP'].transform(normalize).head()
0    1.00
1    0.54
2    0.00
3    0.58
4    0.80
Name: HP, dtype: float64
  1. 对类型 Type 分组,计算 Disp. 与 HP 的相关系数。
df_copy.groupby('Type')[['HP', 'Disp.']].apply(lambda x:np.corrcoef(x['HP'].values, x['Disp.'].values)[0,1])
Type
Compact    0.586087
Large     -0.242765
Medium     0.370491
Small      0.603916
Sporty     0.871426
Van        0.819881
dtype: float64

实现transform函数

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

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

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

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

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

Pandas学习——分组相关推荐

  1. pandas学习(数据分组与分组运算、离散化处理、数据合并)

    pandas学习(数据分组与分组运算.离散化处理.数据合并) 目录 数据分组与分组运算 离散化处理 数据合并 数据分组与分组运算 GroupBy技术:实现数据的分组,和分组运算,作用类似于数据透视表数 ...

  2. python中 s是什么意思_什么是Pandas?Pandas学习什么?

    pandas数据分析核心工具包,基于numpy构建,为数据分析而存在!具有以下特点: 1.一位数组Series+二维数组Dataframe 2.可直接读取数据做处理 3.兼容各种数据库 4.支持各种分 ...

  3. 记录一下pandas的分组统计功能,agg

    主要是记录一下pandas学习,最近要统计一个数据,我向以前stata里面有一个很好用的函数,就是tabstat,可以分组统计,并且输出很多指标. 最近处理数据我的数据是这样的. 我向按照 valid ...

  4. pandas python2_Python数据分析之pandas学习(二)

    有关pandas模块的学习与应用主要介绍以下8个部分: 1.数据结构简介:DataFrame和Series 2.数据索引index 3.利用pandas查询数据 4.利用pandas的DataFram ...

  5. pandas访问分组里面的数据_实战用pandas+PyQt5制作一款数据分组透视处理工具

    早起导读:pandas是Python数据处理的利器,如果每天都要使用pandas执行同样的操作,如何制作一个有界面的软件更高效的完成?本文提供了一种基于PyQt5的实现思路. 关键词:pandas P ...

  6. Pandas 学习手册中文第二版:1~5

    原文:Learning pandas 协议:CC BY-NC-SA 4.0 译者:飞龙 一.Pandas 与数据分析 欢迎来到<Pandas 学习手册>! 在本书中,我们将进行一次探索我们 ...

  7. 用pandas批量分组处理excel数据

    用pandas批量分组处理excel数据 需求比较简单,按照指定字段分组,计算指定字段的和或均值. 用SQL处理的话就一个groupby,可是没有数据库环境,只好用python(比起SQL真的是一点也 ...

  8. Pandas学习笔记(一)

    Pandas学习笔记一 Pandas数组读取 读取csv.tsv.txt文件 读取excel文件 读取mysql数据表 Pandas数据结构 创建Series的几种方法 根据标签查询Series数据 ...

  9. pandas学习笔记之DateFrame

    pandas学习笔记之DateFrame 文章目录 pandas学习笔记之DateFrame 1.DateFrame的创建 1)认识DataFrame对象 2)由二维列表创建(默认index和colu ...

  10. python数据分析-pandas学习

    文章目录 一.pandas环境的搭建 1.pandas 简介 2.pandas安装和调用 二.pandas学习 1.pandas简介 2.pandas学习资源 3.pandas核心数据结构 4.Ser ...

最新文章

  1. pandas dropna
  2. Eclipse可以执行jsp文件却无法访问Tomcat主页
  3. vs 中使用32 位mysql_vs2010连接mysql数据库(含win32和x64两种平台)
  4. mysql binlog c++_关于MySQL的日志管理(binlog)
  5. zabbix监控mysql 图_zabbix监控mysql
  6. 【小程序源码】多功能图片处理器一键多种处理照片
  7. 飞机飞行原理之空气流动基本规律
  8. 人工智能对数据分析师的影响
  9. angularjs 获取复选框的值_如何利用Python批量获取天眼查企业信息?
  10. java使用ZipOutputStream时出现的“不可预料的压缩文件末端”问题
  11. 饭前跑步还是饭后跑步 - 饭后多久跑步
  12. 【眼见为实】自己动手实践理解数据库REPEATABLE READ Next-Key Lock
  13. 晚上兼职送外卖靠谱吗?
  14. DeepFlow Large displacement optical flow with deep matching
  15. visual basic 6 下载 和 Vb6sp6 下载
  16. 怀旧服默认服务器文件,《魔兽世界》怀旧服:这些服务器名称的小知识你得知道...
  17. WIPS产品到底能不能堵上最强Wi-Fi 漏洞?这里有一场精彩的红蓝对抗
  18. 模糊查询下划线“_”时查询的是全部数据
  19. 检查两台主机是否同处于一个局域网
  20. Effective Java 第三版读书笔记(类和接口)

热门文章

  1. 淮安市第八届计算机比赛,淮安市科学技术协会
  2. 如何写出一篇好的软文?软文撰写的一些注意事项!
  3. 入手域名的渠道介绍!
  4. 电信光猫F420破解
  5. 一张图看明白云计算数据中心总体分层架构
  6. 关于克拉默法则的一些理解
  7. 02H5C3-11. Nginx中部署静态网站
  8. [unreal] 切换关卡
  9. JS编程建议——16:防止switch贯穿
  10. Koo叔说Shader-贴图切换