pandas groupby_数据分析实践爬坑——Pandas进阶操作记录(一)

开篇记:

前几天有个数据分析需求,写了好久都没写出来。结果领导过来,到我电脑上,劈里啪啦一通操作,一个多小时就搞定了。当时看得我真是目瞪狗呆,感觉曾经在简历上写的会Python, 会pandas,会numpy 简直就是赤裸裸的欺骗(狗头)。所以打算开个专栏,记录一些工作中遇到的数据分析相关的技术,计划是和数分相关的都会放进来,所以内容可能会比较杂。同时,另一个专栏《统计学习与数据分析学习心得》也要逐步恢复更新。之前的专栏可能会更偏向理论,这个专栏将会更偏向于实践应用。当然了,既然是实践应用,为了不牵扯一些Legal issues, 数据的话我会选择用公开数据集或者自己造一些数据,不会使用工作中真实环境的数据。

以下为正文:

关于pandas的一些基本操作网上已经很多很多资料了。今天主要是记录一些工作之中遇到的,不太常用OR网上资料比较少OR自认为比较重要的一些操作。

  1. 取df某一列的排名

在SQL中取排名的操作我们已经很熟悉,开窗然后用row_number()或者rank()之类的函数。在pandas里面其实也有类似的操作,就是用rank函数。rank函数可以帮我们返回某一列(数值型变量)的排名。比如我们造个小型数据:

v2-99ee5bba10ac3ce478dd69f53e3530ab_b.png

对这个df调用rank,就会得到:

v2-069ab8ce9a6a3e9d02a52e33b075ecd6_b.jpg

我们可以看到,其中每一列都变成了排名(甚至包括B列)。这里指的注意的是,在不指定排名方式的时候,rank函数的排名方式是’average',即当有重复值的时候,用这些数的排名的均值作为它们共同的‘排名’。当然了,这个参数是可以指定的,可选的有‘min','max','first' 和’dense'。 除了排名方法,rank函数还为我们提供了‘缺失值处理’,‘排名的顺序’(默认ascending),方向(axis = 0), 以及"是否返回排名百分比“等参数。可谓功能强大,拿不准时参阅pd document即可。

当然了,这个rank 还可以在group by后使用,这样使用就有点类似SQL中的partition by 后面跟rank函数。具体效果如下:

v2-071ae67709a1288c3889ef61d632d087_b.jpg

这里我们看到,A和C列变成了排名,可是B列消失了!这个和我们通常的直觉不太符合,因为如果通常我们使用groupby 后面跟一个聚合函数的话,那么返回的df的索引应该是groupby()所基于的键,比如,对我们最开始生成的df进行groupby然后取mean,返回的df应该是:

v2-3c362006ec601e0efdf5eae0f0d6f67a_b.jpg

这样的返回是符合我们基本常识的,index列应该是‘B’列所对应的categorical的值的Unique才对。但为什么groupby 后调用rank,返回的df的索引并不是‘B’呢?这里rank虽然用法和聚合函数差不多,但是它似乎并不是一个聚合函数。通常groupby后再使用聚合函数以后,数据的行数会减少。比如上例,原数据有五行,df.groupby.mean()以后,返回的df只有三行,原因是,当'B'相同时,对应的其他两列的数据被‘聚合’(求平均)了。它有点类似于”聚合以后再展开“。所以,原数据有多少行,原数据就有多少行。

上面也提到了,pandas里groupby 后rank,有点类似于SQL中partition by 后rank。SQL里gorupby 和 partition by 的区别就是,groupby后面必须要跟‘聚合函数’(aggregate function), 而partition by 后面跟的是”窗口函数“(window function, 或者叫”分析函数“,analytical function)。partition by 后面跟的东西,理论上是不影响数据条数的。而pandas并没有区分group by 和 partition by,但从返回的东西来看,rank前面的groupby其实相当于partition by。

为了验证这个猜想,我试着在原df中插入两列:

v2-b8f1c826dd83ddaacae48f1fc9128148_b.jpg

发现果然如此。groupby 后 rank 并没有影响到原数据的值和顺序。

2. groupby的用法

刚才既然提到了groupby, 那就再多聊聊groupby。groupby我们平时最常用的用法就是,groupby后面跟一个聚合函数,比如df.groupby('B').mean()。但经过我的探索,发现groupby的用法远不止这么简单。它的功能其实非常强大。比如:

1)groupby基于的键可以不止一个

我们groupby的时候,参数by后面的东西其实可以不止一个字段。为了说明这个问题,我在上面的df中加入另一个categorical的字段‘D’:

v2-12f62987b52e9f67a5d421336295284d_b.png

然后我们基于‘B’和‘D’两个字段进行group by。会得到:

v2-d43135fab14fd3724f4a2d9c7d7e80dc_b.jpg

这里就得到了groupby两个字段的df。这里可以看到,B和D同时作为了索引列。这个在pandas里叫做‘多重索引’(multi-indexes)。

这里多说一两句。对于这种多重索引的df,当我们想对里面数据进行操作(索引,切片等)的时候,应该怎么做呢?一种方法是,把索引列给搞成普通列。这个可以用reset_index()来实现,比如:

v2-c28c97ce62339fe6b71f83a55cf61f6d_b.jpg

可以看到,reset_index后,多重索引列就变成了普通列,这个时候就可以用传统的方法进行切片或者索引。

另一种方法是,multiindex的df,其索引multiindex其实是一个tuple。我们可以用tuple的形式来进行切片。比如我想得到B为‘b',D为'e'的行,我们可以:

v2-c85d4857d790a4eabd54d984c915079f_b.jpg

同理,如果想得到B为b, D为e,同时列名为”A“这个格子里的数据,则可以:

v2-b558fe4f48ba726daeea5e397ebb156f_b.jpg

2) groupby 的聚合函数也可以不止一个

比如,当我们groupby之后,想对不同的列使用不同的聚合函数,比如我们最开始的df,groupby后想对A列取平均,B列取最大值,这样是可以的吗?

是可以的。我们只需要用agg方法,然后把列名和对应的聚合函数的字典传入就可以了。用法如下:

v2-75052ce21793b842b81571f9b20d50d7_b.jpg

3) groupby后面不一定非要跟聚合函数

事实上,groupby后面跟的函数,除了通常的聚合函数(count, sum, mean, max, min...)以外,还可以跟apply + 其他(自定义)函数。在举例说明之前,我们可以先搞明白为什么可以这样操作。这要从pandas库groupby的原理说起。事实上,pandas的groupby 是一个“拆分再合并”的过程。所谓拆分,就是按照组别把df分成很多个df(s),然后合并就是基于一定的“规则”把这些df操作了以后再合起来。这些‘规则’可以是我们理解的一般意义下的“聚合函数”,比如sum, mean, count等,也可以是其他的函数。

v2-3b3ee9619b684a5bdf29727ec36d7d62_b.jpg
group by 的过程

因为刚才说到,groupby 实际上是先按照组别(group by 所基于的列的Uniq的值就是组别)把df拆分成很多小的df,用函数操作了以后再合起来,那就意味着,我这个函数只要是对df的操作,理论上都可以跟到groupby后面。我们可以以之前的df为例:

v2-9b82d0e4fe95485c8f3510243a7389c1_b.png
原df

假设我想对这个df执行这样的一个操作:我先基于B列groupby, 然后如果组内的数据条数大于1,那么其对应的‘C’列的元素就+1,否则就在‘C’列+3。这个用group by + 函数的方法很好实现:

v2-86ea183ac8cdc159917164edc7214201_b.jpg

我们可以看到,我们自定义了一个叫做c_plus_gb的函数,在把df group by 之后,用apply的方法调用这个函数。在这个函数内部,对于传递进去的参数(也是一个df),我们同样用apply方法对其某一列进行操作。从结果看,运行上述代码后,由于基于B列groupby了以后,‘a’组内数据条数只有一条,所以其对应的C列的值为3(原为0);'b'组内因为数据条数是3条,所以其C列对应的值为1,这个结果是符合我们预期的。从功能上看,我们可以看到这段小代码里,其实就是对df直接操作的,这个叫做c_plus_gb的函数,传参时,并不是把之前的大df传了进去,而传的是groupby后,基于组划分的小df。换言之,groupby之后,有多少组,我们就要调用多少次这个函数,同时就要把groupby之后组对应的小df当作参数传进去。可想而知,这个效率会比较低。但不得不说,这个操作真的很powerful。

3. 再探apply操作

刚才的函数里我们用到了apply函数,这是一个很简单且优雅的用法。对于dataframe的apply方法我之前有写过一篇文章专门探讨,当时只说了apply的一些比较简单的用法,比如对df的某一列进行批量操作。它的写法是这样的:

v2-cd248b1cfbf33493f90b61b2f5d3c769_b.jpg

比如这个小代码定义了一个叫做plus的函数,它有一个参数x,它的功能是给这个x加1以后返回。我们知道,在python中,如果要调用一个函数,应该写成plus()的形式,括号中是这个函数的参数,即便这个函数没有参数,或者参数可以省略,那括号也是需要保留的。(当然了,上面这段代码可以用匿名函数一行完成,foo['C'] = foo['C'].apply(lambda x: x + 1),这里写成显式的函数是为了举例方便)

但我们看到,在用apply方法去apply这个plus函数的时候,我们写成了apply(plus),这这里的plus是没有括号的,它被省略了。甚至x这个参数,也被省略了!也就是说,这个函数中,对应的df 或者series中的元素的参数,是可以直接被省略的。那么问题来了,如果我们定义的函数比较复杂,需要传进去多个参数的时候,该怎么办呢?

我们很自然地想到写成apply(plus(x,y))这样的形式。不过很遗憾,这样是不行的。但是apply函数提供了另一个参数,叫做args,这个参数专门接收apply(funcs)内部函数的参数。但只接收非series 对应元素的参数。比如假设plus函数有两个参数,第一个是df或者series中的元素,第二个是函数需要用到的其他参数,那么这时候我们不能写成apply(plus, args = (x, y))这样的形式,而是只需要写成apply(plus, args = (y))就好了。需要注意的是,args这个参数,如果func对应多个参数的时候,必须是以元组的形式传进去。

(此文完)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值