pandas groupby_Pandas笔记深入Groupby,它的功能没有你想的这么简单

点击上方“潜心的Python小屋”关注我们,第一时间推送优质文章。

前言

大家好,我是潜心。上篇文章提到了Groupby,但其中举例的代码有点问题,在提取序列时用到了for循环,效率很慢,后来查找了官方文档,才明白apply的重要性,再次对Groupby进行深入并总结。

本文约2.1k字,预计阅读15分钟。

Groupby: split-apply-combine

Pandas中Groupby定义如下:

def groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False, observed=False)

Groupby具体来说指的是涉及以下一个或多个步骤的过程:

  • 分割(Splitting):根据一些标准将数据划分为多个组。
  • 应用(Applying):独立地对每个组应用一个函数。
  • 组合(Combing):将结果组合成数据结构。

911ce877923580c544ea4e1ff3e3caed.png

其中,分割是最直接的,也是最常用的(如上图)。但是事实上,在许多情况下,我们可能希望将数据集分成组,并对这些组做一些统计值计算。在Applying中,可能希望做以下其中一项:

  • 聚集(Aggregation):计算每个组的汇总统计信息(或统计信息),例如计算分组的和、均值、个数等。
  • 转换(Transformation):执行一些特定组的计算并返回一个like-indexed对象。
  • 过滤(Filteration):根据判断为真或假的组计算,丢弃一些组。

此次我们不讨论Group的分割问题,主要聚焦它的Applying,并结合实际的大数据量的实际表来比较它们的效率问题。采用的数据表为用户与他点击的广告,共100w条:

user_idcreative_id
030920567330
1309203072255
2309203072255
3309203879497
4309203751623
.........

Aggregation

当创建了GroupBy对象,根据需求我们可以对分组的数据执行计算。最简单的是我们通过agg()方法来调用一些Python内置函数进行聚合计算,常用的内置函数为:meanmediansumsizecountstddescribeminmax等。

这里我们基于user_id对数据进行划分,简单应用部分内置函数,统计对每个用户他点击过的最大和最小的广告id:

%%time
In[1]: df.groupby('user_id').agg(['count', 'min', 'max'])

CPU times: user 72 ms, sys: 0 ns, total: 72 ms
Wall time: 70.9 ms
Out[1]:

user_id count min max   
31 26 109090 4440651
34 63 3983 4266235
36 19 67988 3999372
310 12 10039 3042631
312 28 24918 3904071
... ... ... ...
363305 16 42555 4187970
363311 19 187530 4151703
363317 15 63052 4307786
363318 41 7400 4079814
363319 167 10257 4389117
30025 rows × 3 columns

以上我们发现索引变为了user_id若不想改变DataFrame的索引,则需要在groupby方法中的参数as_index设置为False

当然如果你需要进一步进行统计运算,则panda允许提供多个lambdas自定义计算。在这种情况下,panda将混淆lambda函数的名称,将_附加到每个后续的lambda。可以通过rename()更改列名。

%%time
In[2]: df.groupby('user_id').agg([lambda x: x.max() - x.min(), lambda x: x.mean() - x.median()])

CPU times: user 20.4 s, sys: 0 ns, total: 20.4 s
Wall time: 20.4 s
Out[2]:
        creative_id     
user_id  31 4331561 -107949.84615434 4262252 430452.82539736 3931384 -16653.473684310 3032592 -121110.916667312 3879153 208385.392857
... ... ...363305 4145415 364584.500000363311 3964173 524387.842105363317 4244734 787231.666667363318 4072414 -110147.829268363319 4378860 250463.10778430025 rows × 2 columns

对不同的列使用不同的聚合可以通过字典的方式实现(这里采用文档的Code,CD为列名):

In [3]: grouped.agg({'C': np.sum,
   ....:              'D': lambda x: np.std(x, ddof=1)})
   ....: 
Out[3]: 
            C         D
A                      
bar  0.392940  1.366330
foo -1.796421  0.884785

Transformation

transform方法返回一个与正在分组的对象索引相同(大小相同)的对象。

我们通过对表进行与agg()相同的内置函数进行比较:

%%time
In[4]: df.groupby('user_id').transform('count')

CPU times: user 28 ms, sys: 0 ns, total: 28 ms
Wall time: 29 ms
Out[4]:
creative_id
0 42
1 42
2 42
3 42
4 42
... ...
999995 167
999996 167
999997 167
999998 167
999999 167
1000000 rows × 1 columns

我们发现返回了一个与原数据表大小相同的对象,并且把groupby中的by参数给省略了。(并且它不能一次使用多个内置函数)。

实际用途: 如果我们需要为原数据表添加一列count或其他内容,则需要使用该方法。

In[5]: df['ad_count'] = df.groupby('user_id').transform('count')
Out[5]: 
   user_id creative_id ad_count
0    30920  567330 42
1    30920  3072255 42
2    30920  2361327 42
3    30920  3879497 42
4    30920  3751623 42
... ... ... ...
999995 363319 121860 167
999996 363319 413801 167
999997 363319 415805 167
999998 363319 487803 167
999999 363319 655613 167
1000000 rows × 3 columns

Filteration

filter方法是通过一些布尔判断对分组后的内容进行筛选,返回一个原始对象的子集。

假设我们需要过滤得到用户点击广告数小于100的样本:

%%time
In[6]: df.groupby('user_id').filter(lambda x: len(x) 100)

CPU times: user 7.48 s, sys: 0 ns, total: 7.48 s
Wall time: 7.49 s
Out[6]:
   user_id creative_id ad_count
0    30920  567330 42
1    30920  3072255 42
2    30920  2361327 42
3    30920  3879497 42
4    30920  3751623 42
... ... ... ...
999828 363318 1779084 41
999829 363318 1985010 41
999830 363318 66606  41
999831 363318 1056195 41
999832 363318 1435072 41
838425 rows × 3 columns

可以发现,样本数目减少,每个用户点击广告数大于100的已被去除。

Apply

apply方法相比更加灵活,它可以完成上述的agg、transform以及filter,具体取决于传递给它的是什么。并且它可以使用自定义函数。

例如返回每个用户最大的广告id(即agg方法)。

%%time
def change_ad_count(df):
    return df['creative_id'].max()
In[7]: df.groupby('user_id').apply(change_ad_count)
  

CPU times: user 4.93 s, sys: 0 ns, total: 4.93 s
Wall time: 4.93 s
Out[7]:
user_id
31        4440651
34        4266235
36        3999372
310       3042631
312       3904071
           ...   
363305    4187970
363311    4151703
363317    4307786
363318    4079814
363319    4389117
Length: 30025, dtype: int64

比较

因为上篇文章提到了Word2vec,通过每个用户所点击的广告来构造句子,因此需要将按照user_id进行分组,并将每个用户点击的广告构造一个列表。之前用了for循环,效率极慢,现在改用aggapply方法进行比较。

agg:

%%time
sentences = df.groupby(['user_id'])['creative_id'].agg(lambda x: x.tolist()).tolist()

CPU times: user 4.13 s, sys: 64 ms, total: 4.2 s
Wall time: 4.2 s

apply:

%%time
sentences = df.groupby(['user_id'])['creative_id'].apply(lambda x: x.tolist()).tolist()
CPU times: user 4.18 s, sys: 52 ms, total: 4.23 s
Wall time: 4.23 s
%%time
sentences = df.groupby(['user_id']).apply(lambda x: x['creative_id'].tolist()).tolist()
CPU times: user 2.32 s, sys: 44 ms, total: 2.37 s
Wall time: 2.37 s

我们发现apply方法与agg相差无几,但我们若将creative_id放入lambda中,则效率更高。个人认为应该是和处理的对象不同。

df.groupby(['user_id'])['creative_id']

0x7f1495e8d9e8>
df.groupby(['user_id'])0x7f1495e80a20>## apply中再处理Series对象

总结

groupby是pandas最有效的方法之一,经常与aggtransformfilterapply相结合使用。

                                             【阅读的朋友帮忙点个在看可以嘛

90e13166f3b1101007b2b23f4c944061.png

往期 精彩回顾Pandas笔记---通过比赛整理出的10条Pandas实用技巧Pandas笔记---概述与数据结构机器学习笔记---你真的懂决策树么?机器学习笔记---信息熵爬虫实战(三)----使用百度API获取经纬度/地址今日头条爬虫实战----爬取图片扫码关注更多精彩 88fbb1b6fca2301455f5f152705a2dc7.png 7515a022930664b0198fd9c7545fbebe.png abac935deea55b4a553a6d17469406fe.png我就知道你“在看” 181420a92d30c77fdec1790bd6043ec3.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值