一、数据聚合与分组运算

对数据集进行分组并对各组应用一个函数,通常是数据分析工作中的重要环节。在将数据集加载、融合、准备好之后,通常就是计算分组统计或生成透视表。pandas提供了一个灵活高效的gruopby功能,它使你能以一种自然的方式对数据集进行切片、切块、摘要等操作。

关系型数据库和SQL能够如此流行的原因之一就是其能够方便地对数据进行连接、过滤、转换和聚合。但是,像SQL这样的查询语言所能执行的分组运算的种类很有限。在本章中你将会看到,由于Python和pandas强大的表达能力,我们可以执行复杂得多的分组运算。在本章中,你将会学到:

·使用一个或多个键分割pandas对象。

·计算分组的概述统计,比如数量、平均值或标准差,或是用户定义的函数。

·应用组内转换或其他运算,如规格化、线性回归、排名或选取子集等。

·计算透视表或交叉表。

·执行分位数分析以及其它统计分组分析。

二、实例

2.7 数据聚合

聚合指的是任何能够从数组产生标量值的数据转换过程。之前的例子已经用过一些,比如mean、count、min以及sum等。你可能想知道在GroupBy对象上调用mean()时究竟发生了什么。许多常见的聚合运算都有进行优化。

你可以使用自己发明的聚合运算,还可以调用分组对象上已经定义好的任何方法。例如,quantile可以计算Series或DataFrame列的样本分位数。

虽然quantile并没有明确地实现于GroupBy,但它是一个Series方法,所以这里是能用的。实际上,GroupBy会高效地对Series进行切片,然后对各片调用piece.quantile(0.9),最后将这些结果组装成最终结果:

In [10]: df = pd.DataFrame({'key1' : ['a', 'a', 'b', 'b', 'a'],

....: 'key2' : ['one', 'two', 'one', 'two', 'one'],

....: 'data1' : np.random.randn(5),

....: 'data2' : np.random.randn(5)})

In [52]: grouped = df.groupby('key1')

In [53]: grouped['data1'].quantile(0.9)

Out[53]:

key1

a -0.488541

b 1.098984

Name: data1, dtype: float64

如果要使用你自己的聚合函数,只需将其传入aggregate或agg方法即可:

In [54]: def peak_to_peak(arr):

....: return arr.max() - arr.min()

In [55]: grouped.agg(peak_to_peak)

Out[55]:

data1 data2

key1

a 0.818696 0.725013

b 1.140007 1.135537

有些方法(如describe)并非聚合运算也是可以用在这里:

In [56]: grouped.describe()

Out[56]:

data1 ... data2

count mean std ... 50% 75% max

key1 ...

a 3.0 -0.803859 0.412721 ... 0.125915 0.228816 0.331716

b 2.0 0.642981 0.806107 ... -0.800166 -0.516282 -0.232398

[2 rows x 16 columns]

2.8 面向列的多函数应用

回到前面小费的例子。使用read_csv导入数据之后,我们添加了一个小费百分比的列tip_pct:

In [57]: tips = pd.read_csv('examples/tips.csv')

In [58]: tips['tip_pct'] = tips['tip'] / tips['total_bill']

In [59]: tips[:6]

Out[59]:

total_bill tip smoker day time size tip_pct

0 16.99 1.01 No Sun Dinner 2 0.059447

1 10.34 1.66 No Sun Dinner 3 0.160542

2 21.01 3.50 No Sun Dinner 3 0.166587

3 23.68 3.31 No Sun Dinner 2 0.139780

4 24.59 3.61 No Sun Dinner 4 0.146808

5 25.29 4.71 No Sun Dinner 4 0.186240

对Series或DataFrame列的聚合运算其实就是使用aggregate(使用自定义函数)或调用诸如mean、std之类的方法。然而,你可能希望对不同的列使用不同的聚合函数,或一次应用多个函数。其实这也好办,我将通过一些示例来进行讲解。首先,我根据天和smoker对tips进行分组:

In [60]: grouped = tips.groupby(['day', 'smoker'])

可以将函数名以字符串的形式传入:

In [61]: grouped_pct = grouped['tip_pct']

In [62]: grouped_pct.agg('mean')

Out[62]:

day smoker

Fri No 0.151650

Yes 0.174783

Sat No 0.158048

Yes 0.147906

Sun No 0.160113

Yes 0.187250

Thur No 0.160298

Yes 0.163863

Name: tip_pct, dtype: float64

如果传入一组函数或函数名,得到的DataFrame的列就会以相应的函数命名:

In [63]: grouped_pct.agg(['mean', 'std', peak_to_peak])

Out[63]:

mean std peak_to_peak

day smoker

Fri No 0.151650 0.028123 0.067349

Yes 0.174783 0.051293 0.159925

Sat No 0.158048 0.039767 0.235193

Yes 0.147906 0.061375 0.290095

Sun No 0.160113 0.042347 0.193226

Yes 0.187250 0.154134 0.644685

Thur No 0.160298 0.038774 0.193350

Yes 0.163863 0.039389 0.151240

这里,我们传递了一组聚合函数进行聚合,独立对数据分组进行评估。

你并非一定要接受GroupBy自动给出的那些列名,特别是lambda函数,它们的名称是'',这样的辨识度就很低了。因此,如果传入的是一个由(name,function)元组组成的列表,则各元组的第一个元素就会被用作DataFrame的列名:

In [64]: grouped_pct.agg([('foo', 'mean'), ('bar', np.std)])

Out[64]:

foo bar

day smoker

Fri No 0.151650 0.028123

Yes 0.174783 0.051293

Sat No 0.158048 0.039767

Yes 0.147906 0.061375

Sun No 0.160113 0.042347

Yes 0.187250 0.154134

Thur No 0.160298 0.038774

Yes 0.163863 0.039389

对于DataFrame,你还有更多选择,你可以定义一组应用于全部列的一组函数,或不同的列应用不同的函数。假设我们想要对tip_pct和total_bill列计算三个统计信息:

In [65]: functions = ['count', 'mean', 'max']

In [66]: result = grouped['tip_pct', 'total_bill'].agg(functions)

In [67]: result

Out[67]:

tip_pct total_bill

count mean max count mean max

day smoker

Fri No 4 0.151650 0.187735 4 18.420000 22.75

Yes 15 0.174783 0.263480 15 16.813333 40.17

Sat No 45 0.158048 0.291990 45 19.661778 48.33

Yes 42 0.147906 0.325733 42 21.276667 50.81

Sun No 57 0.160113 0.252672 57 20.506667 48.17

Yes 19 0.187250 0.710345 19 24.120000 45.35

Thur No 45 0.160298 0.266312 45 17.113111 41.19

Yes 17 0.163863 0.241255 17 19.190588 43.11

结果DataFrame拥有层次化的列,这相当于分别对各列进行聚合,然后用concat将结果组装到一起,使用列名用作keys参数:

In [68]: result['tip_pct']

Out[68]:

count mean max

day smoker

Fri No 4 0.151650 0.187735

Yes 15 0.174783 0.263480

Sat No 45 0.158048 0.291990

Yes 42 0.147906 0.325733

Sun No 57 0.160113 0.252672

Yes 19 0.187250 0.710345

Thur No 45 0.160298 0.266312

Yes 17 0.163863 0.241255

跟前面一样,这里也可以传入带有自定义名称的一组元组:

In [69]: ftuples = [('Durchschnitt', 'mean'),('Abweichung', np.var)]

In [70]: grouped['tip_pct', 'total_bill'].agg(ftuples)

Out[70]:

tip_pct total_bill

Durchschnitt Abweichung Durchschnitt Abweichung

day smoker

Fri No 0.151650 0.000791 18.420000 25.596333

Yes 0.174783 0.002631 16.813333 82.562438

Sat No 0.158048 0.001581 19.661778 79.908965

Yes 0.147906 0.003767 21.276667 101.387535

Sun No 0.160113 0.001793 20.506667 66.099980

Yes 0.187250 0.023757 24.120000 109.046044

Thur No 0.160298 0.001503 17.113111 59.625081

Yes 0.163863 0.001551 19.190588 69.808518

现在,假设你想要对一个列或不同的列应用不同的函数。具体的办法是向agg传入一个从列名映射到函数的字典:

In [71]: grouped.agg({'tip' : np.max, 'size' : 'sum'})

Out[71]:

tip size

day smoker

Fri No 3.50 9

Yes 4.73 31

Sat No 9.00 115

Yes 10.00 104

Sun No 6.00 167

Yes 6.50 49

Thur No 6.70 112

Yes 5.00 40

In [72]: grouped.agg({'tip_pct' : ['min', 'max', 'mean', 'std'],

....: 'size' : 'sum'})

Out[72]:

tip_pct size

min max mean std sum

day smoker

Fri No 0.120385 0.187735 0.151650 0.028123 9

Yes 0.103555 0.263480 0.174783 0.051293 31

Sat No 0.056797 0.291990 0.158048 0.039767 115

Yes 0.035638 0.325733 0.147906 0.061375 104

Sun No 0.059447 0.252672 0.160113 0.042347 167

Yes 0.065660 0.710345 0.187250 0.154134 49

Thur No 0.072961 0.266312 0.160298 0.038774 112

Yes 0.090014 0.241255 0.163863 0.039389 40

只有将多个函数应用到至少一列时,DataFrame才会拥有层次化的列。

2.9 以“没有行索引”的形式返回聚合数据

到目前为止,所有示例中的聚合数据都有由唯一的分组键组成的索引。由于并不总是需要如此,所以你可以向groupby传入as_index=False以禁用该功能:

In [73]: tips.groupby(['day', 'smoker'], as_index=False).mean()

Out[73]:

day smoker total_bill tip size tip_pct

0 Fri No 18.420000 2.812500 2.250000 0.151650

1 Fri Yes 16.813333 2.714000 2.066667 0.174783

2 Sat No 19.661778 3.102889 2.555556 0.158048

3 Sat Yes 21.276667 2.875476 2.476190 0.147906

4 Sun No 20.506667 3.167895 2.929825 0.160113

5 Sun Yes 24.120000 3.516842 2.578947 0.187250

6 Thur No 17.113111 2.673778 2.488889 0.160298

7 Thur Yes 19.190588 3.030000 2.352941 0.163863

当然,对结果调用reset_index也能得到这种形式的结果。使用as_index=False方法可以避免一些不必要的计算。

2.10 apply:一般性的“拆分-应用-合并”

最通用的GroupBy方法是apply,本节剩余部分将重点讲解它。apply会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试将各片段组合到一起。

回到之前那个小费数据集,假设你想要根据分组选出最高的5个tip_pct值。首先,编写一个选取指定列具有最大值的行的函数:

In [74]: def top(df, n=5, column='tip_pct'):

....: return df.sort_values(by=column)[-n:]

In [75]: top(tips, n=6)

Out[75]:

total_bill tip smoker day time size tip_pct

109 14.31 4.00 Yes Sat Dinner 2 0.279525

183 23.17 6.50 Yes Sun Dinner 4 0.280535

232 11.61 3.39 No Sat Dinner 2 0.291990

67 3.07 1.00 Yes Sat Dinner 1 0.325733

178 9.60 4.00 Yes Sun Dinner 2 0.416667

172 7.25 5.15 Yes Sun Dinner 2 0.710345

现在,如果对smoker分组并用该函数调用apply,就会得到:

In [76]: tips.groupby('smoker').apply(top)

Out[76]:

total_bill tip smoker day time size tip_pct

smoker

No 88 24.71 5.85 No Thur Lunch 2 0.236746

185 20.69 5.00 No Sun Dinner 5 0.241663

51 10.29 2.60 No Sun Dinner 2 0.252672

149 7.51 2.00 No Thur Lunch 2 0.266312

232 11.61 3.39 No Sat Dinner 2 0.291990

Yes 109 14.31 4.00 Yes Sat Dinner 2 0.279525

183 23.17 6.50 Yes Sun Dinner 4 0.280535

67 3.07 1.00 Yes Sat Dinner 1 0.325733

178 9.60 4.00 Yes Sun Dinner 2 0.416667

172 7.25 5.15 Yes Sun Dinner 2 0.710345

top函数在DataFrame的各个片段上调用,然后结果由pandas.concat组装到一起,并以分组名称进行了标记。于是,最终结果就有了一个层次化索引,其内层索引值来自原DataFrame。

如果传给apply的函数能够接受其他参数或关键字,则可以将这些内容放在函数名后面一并传入:

In [77]: tips.groupby(['smoker', 'day']).apply(top, n=1, column='total_bill')

Out[77]:

total_bill tip smoker day time size tip_pct

smoker day

No Fri 94 22.75 3.25 No Fri Dinner 2 0.142857

Sat 212 48.33 9.00 No Sat Dinner 4 0.186220

Sun 156 48.17 5.00 No Sun Dinner 6 0.103799

Thur 142 41.19 5.00 No Thur Lunch 5 0.121389

Yes Fri 95 40.17 4.73 Yes Fri Dinner 4 0.117750

Sat 170 50.81 10.00 Yes Sat Dinner 3 0.196812

Sun 182 45.35 3.50 Yes Sun Dinner 3 0.077178

Thur 197 43.11 5.00 Yes Thur Lunch 4 0.115982

可能你已经想起来了,之前我在GroupBy对象上调用过describe:

In [78]: result = tips.groupby('smoker')['tip_pct'].describe()

In [79]: result

Out[79]:

count mean std ... 50% 75% max

smoker ...

No 151.0 0.159328 0.039910 ... 0.155625 0.185014 0.291990

Yes 93.0 0.163196 0.085119 ... 0.153846 0.195059 0.710345

[2 rows x 8 columns]

In [80]: result.unstack('smoker')

Out[80]:

smoker

count No 151.000000

Yes 93.000000

mean No 0.159328

Yes 0.163196

std No 0.039910

Yes 0.085119

min No 0.056797

Yes 0.035638

25% No 0.136906

Yes 0.106771

50% No 0.155625

Yes 0.153846

75% No 0.185014

Yes 0.195059

max No 0.291990

Yes 0.710345

dtype: float64

在GroupBy中,当你调用诸如describe之类的方法时,实际上只是应用了下面两条代码的快捷方式而已:

f = lambda x: x.describe()

grouped.apply(f)

2.11 禁止分组键

从上面的例子中可以看出,分组键会跟原始对象的索引共同构成结果对象中的层次化索引。将group_keys=False传入groupby即可禁止该效果:

In [81]: tips.groupby('smoker', group_keys=False).apply(top)

Out[81]:

total_bill tip smoker day time size tip_pct

88 24.71 5.85 No Thur Lunch 2 0.236746

185 20.69 5.00 No Sun Dinner 5 0.241663

51 10.29 2.60 No Sun Dinner 2 0.252672

149 7.51 2.00 No Thur Lunch 2 0.266312

232 11.61 3.39 No Sat Dinner 2 0.291990

109 14.31 4.00 Yes Sat Dinner 2 0.279525

183 23.17 6.50 Yes Sun Dinner 4 0.280535

67 3.07 1.00 Yes Sat Dinner 1 0.325733

178 9.60 4.00 Yes Sun Dinner 2 0.416667

172 7.25 5.15 Yes Sun Dinner 2 0.710345

三、小结

grouped = df.groupby('key1')

grouped['data1'].quantile(0.9)

grouped.agg(peak_to_peak)

grouped.describe()

grouped = tips.groupby(['day', 'smoker'])

grouped_pct = grouped['tip_pct']

grouped_pct.agg('mean')

grouped_pct.agg(['mean', 'std', peak_to_peak])

grouped_pct.agg([('foo', 'mean'), ('bar', np.std)])

result = grouped['tip_pct', 'total_bill'].agg(functions)

grouped['tip_pct', 'total_bill'].agg(ftuples)

grouped.agg({'tip' : np.max, 'size' : 'sum'})

grouped.agg({'tip_pct' : ['min', 'max', 'mean', 'std'],

....: 'size' : 'sum'})

tips.groupby(['day', 'smoker'], as_index=False).mean()

tips.groupby('smoker').apply(top)

tips.groupby(['smoker', 'day']).apply(top, n=1, column='total_bill')

result = tips.groupby('smoker')['tip_pct'].describe()

tips.groupby('smoker', group_keys=False).apply(top)

·后记

这一章相对会抽象一些,我们先快速过一遍,后续再作巩固。

欢迎点赞·评论·分享·收藏·浇筑树苗

(o・ェ・o)ノ r (苗·lv.0)

python数据分组聚合案例_《利用Python进行数据分析》十章·数据聚合与分组运算·学习笔记(二)...相关推荐

  1. python怎么读取sav格式_利用Python读取外部数据文件

    利用Python读取外部数据文件 [color=rgb(0, 0, 0) !important]刘顺祥 [color=rgb(0, 0, 0) !important]摘要: 不论是数据分析,数据可视化 ...

  2. python爬去百度文库_利用Python语言轻松爬取数据[精品文档]

    利用 Python 语言轻松爬取数据 对于小白来说,爬虫可能是一件非常复杂. 技术门槛很高的事情. 比如有人认为学爬虫必须精通 Python ,然后哼哧哼哧系统学习 Python 的每个知识点,很久之 ...

  3. python爬取百度文库_利用Python语言轻松爬取数据

    利用 Python 语言轻松爬取数据 对于小白来说,爬虫可能是一件非常复杂. 技术门槛很高的事情. 比如有人认为学爬虫必须精通 Python ,然后哼哧哼哧系统学习 Python 的每个知识点,很久之 ...

  4. python实时数据存储与显示_利用python进行数据加载和存储

    1.文本文件 (1)pd.read_csv加载分隔符为逗号的数据:pd.read_table从文件.URL.文件型对象中加载带分隔符的数据.默认为制表符.(加载为DataFrame结构) 参数name ...

  5. python数据对比找不同_利用Python读取文件的四种不同方法比对

    利用Python读取文件的四种不同方法比对 大家都知道Python 读文件的方式多种多样,但是当需要读取一个大文件的时候,不同的读取方式会有不一样的效果.下面就来看看详细的介绍吧. 场景 逐行读取一个 ...

  6. python爬取网易云_利用python爬取网易云音乐,并把数据存入mysql

    作者:sergiojune Python爱好者社区--专栏作者 个人公众号:日常学python 专注python爬虫,数据可视化,数据分析,python前端技术 公众号:Python爱好者社区 获取本 ...

  7. python为啥爬取数据会有重复_利用Python来爬取“吃鸡”数据,为什么别人能吃鸡?...

    原标题:利用Python来爬取"吃鸡"数据,为什么别人能吃鸡? 首先,神装镇楼 背景 最近老板爱上了吃鸡(手游:全军出击),经常拉着我们开黑,只能放弃午休的时间,陪老板在沙漠里奔波 ...

  8. 如何用python完成评分功能呢_利用python基于电影评分数据进行

    本文以Movielens 1M数据集为例,利用Python,对电影的各项数据进行分析,分析对于不同的性别的电影评分,以及性别差异对评分的差异 加载python库以及数据: import pandas ...

  9. access数据放到list中_利用Python提取视频中的字幕(文字识别)

    我的CSDN博客id:qq_39783601,昵称是糖潮丽子~辣丽 从今天开始我会陆续将数据分析师相关的知识点分享在这里,包括Python.机器学习.数据库等等. 今天来分享一个Python小项目! ...

  10. 钉钉python 自动发消息软件_利用Python自动发送钉钉数据消息,一个简单的上手小项目...

    现在大部分公司都使用钉钉作为内部的主要沟通工具,钉钉消息基本都上都能快速有效的被阅读,打开率会比邮件高上不少.所以准备使用钉钉来播报平台每日的成交额,并附上一些鼓励的话和图片.起到一个激励团队的作用 ...

最新文章

  1. K8S Deployment脚本部署Tomcat集群
  2. 不同局域网内经Internet的P2P通信技术总结
  3. Seq2Seq中Exposure Bias现象的浅析与对策
  4. E. 存储过程(procedure)
  5. SAP工程师对Spark的尝试
  6. 智能会议系统(15)--- linphone-android 业务流程
  7. 想找一些设计素材不知道去哪里找?
  8. 文件上传—DiskFileItemFactory核心类
  9. 毕业论文的6中降重方法
  10. 计算机网络和电气之间的联系,2020年电气工程师《基础知识》历年真题精选0830...
  11. python tkinter 图片_如何用python tkinter插入显示图片?
  12. 计算机网络:广域网的基本概念
  13. HDU5510 Bazinga(KMP)
  14. Pascal 英语句子语法解析器
  15. 大地坐标系(Geocentric Geodetic Coordinate System)与笛卡尔积坐标系(Geocentric Space Rectangular Coordinate System)
  16. 用css实现鼠标移入按钮,按钮出现动态的渐变色边框效果
  17. Python3下基于bs4和sqlalchemy的爬虫实现
  18. 【PE806】Nim on Towers of Hanoi(汉诺塔游戏,生成函数)
  19. Centos各个版本下载地址
  20. signature=d0108467db7c8f306f2bd7cd45de4c83,Programmable data-routing multiplexer

热门文章

  1. source的作用详细讲解
  2. 爬虫实战---爬取猫眼电影
  3. 第欧根尼:我崇尚简朴朴素克己的生活
  4. 【SeedCoder2015年 热身题5 搜索】上上下下ABAB (题目+答案)
  5. 绕过tp路由器管理密码_TP-Link路由器管理员密码多少 TP-Link路由器管理员密码介绍【详解】...
  6. 我转行程序员的那一年(八)
  7. Tier1 OEM ODM 区别
  8. 在Vue3项目中使用 Echarts 绘制股票图表的分享(二):绘制现价图+均价图
  9. cad2006激活未找到html文件,[转载]AutoCAD2006启动时提示“许可证系统出现问题”解决方法...
  10. 微软 MSCRM 教育成功案例 界面展示