Pivot Tables
(数据透视表)是一种常见的数据汇总工具,常见与各种spreadsheet programs
(电子表格程序,比如Excel
)和一些数据分析软件。它能按一个或多个keys
来把数据聚合为表格,能沿着行或列,根据组键来整理数据。
数据透视表可以用pandas
的groupby
来制作,这个本节会进行介绍,除此之外还会有介绍如何利用多层级索引来进行reshape
(更改形状)操作。DataFrame
有一个pivot_table
方法,另外还有一个pandas.pivot_table
函数。为了有一个更方便的groupby
接口,pivot_table
能添加partial totals
(部分合计),也被称作margins
(边界)。
回到之前提到的tipping
数据集,假设我们想要计算一个含有组平均值的表格(a table of group means
,这个平均值也是pivot_table
默认的聚合类型),按day
和smoker
来分组:
import numpy as np
import pandas as pd
tips = pd.read_csv('../examples/tips.csv')
# Add tip percentage of total bill
tips['tip_pct'] = tips['tip'] / tips['total_bill']
tips.head()
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 |
tips.pivot_table(index=['day', 'smoker'])
size | tip | tip_pct | total_bill | ||
---|---|---|---|---|---|
day | smoker | ||||
Fri | No | 2.250000 | 2.812500 | 0.151650 | 18.420000 |
Yes | 2.066667 | 2.714000 | 0.174783 | 16.813333 | |
Sat | No | 2.555556 | 3.102889 | 0.158048 | 19.661778 |
Yes | 2.476190 | 2.875476 | 0.147906 | 21.276667 | |
Sun | No | 2.929825 | 3.167895 | 0.160113 | 20.506667 |
Yes | 2.578947 | 3.516842 | 0.187250 | 24.120000 | |
Thur | No | 2.488889 | 2.673778 | 0.160298 | 17.113111 |
Yes | 2.352941 | 3.030000 | 0.163863 | 19.190588 |
这个结果也可以通过groupby
直接得到。
现在假设我们想要按time
分组,然后对tip_pct
和size
进行聚合。我们会把smoker
放在列上,而day
用于行:
tips.pivot_table(['tip_pct', 'size'], index=['time', 'day'],
columns='smoker')
tip_pct | size | ||||
---|---|---|---|---|---|
smoker | No | Yes | No | Yes | |
time | day | ||||
Dinner | Fri | 0.139622 | 0.165347 | 2.000000 | 2.222222 |
Sat | 0.158048 | 0.147906 | 2.555556 | 2.476190 | |
Sun | 0.160113 | 0.187250 | 2.929825 | 2.578947 | |
Thur | 0.159744 | NaN | 2.000000 | NaN | |
Lunch | Fri | 0.187735 | 0.188937 | 3.000000 | 1.833333 |
Thur | 0.160311 | 0.163863 | 2.500000 | 2.352941 |
我们也快成把这个表格加强一下,通过设置margins=True
来添加部分合计(partial total
)。这么做的话有一个效果,会给行和列各添加All
标签,这个All
表示的是当前组对于整个数据的统计值:
tips.pivot_table(['tip_pct', 'size'], index=['time', 'day'],
columns='smoker', margins=True)
tip_pct | size | ||||||
---|---|---|---|---|---|---|---|
smoker | No | Yes | All | No | Yes | All | |
time | day | ||||||
Dinner | Fri | 0.139622 | 0.165347 | 0.158916 | 2.000000 | 2.222222 | 2.166667 |
Sat | 0.158048 | 0.147906 | 0.153152 | 2.555556 | 2.476190 | 2.517241 | |
Sun | 0.160113 | 0.187250 | 0.166897 | 2.929825 | 2.578947 | 2.842105 | |
Thur | 0.159744 | NaN | 0.159744 | 2.000000 | NaN | 2.000000 | |
Lunch | Fri | 0.187735 | 0.188937 | 0.188765 | 3.000000 | 1.833333 | 2.000000 |
Thur | 0.160311 | 0.163863 | 0.161301 | 2.500000 | 2.352941 | 2.459016 | |
All | 0.159328 | 0.163196 | 0.160803 | 2.668874 | 2.408602 | 2.569672 |
这里,对于All
列,这一列的值是不考虑吸烟周和非吸烟者的平均值(smoker versus nonsmoker
)。对于All
行,这一行的值是不考虑任何组中任意两个组的平均值(any of the two levels of grouping
)。
想要使用不同的聚合函数,传递给aggfunc
即可。例如,count
或len
可以给我们一个关于组大小(group size
)的交叉表格:
tips.pivot_table('tip_pct', index=['time', 'smoker'], columns='day',
aggfunc=len, margins=True)
day | Fri | Sat | Sun | Thur | All | |
---|---|---|---|---|---|---|
time | smoker | |||||
Dinner | No | 3.0 | 45.0 | 57.0 | 1.0 | 106.0 |
Yes | 9.0 | 42.0 | 19.0 | NaN | 70.0 | |
Lunch | No | 1.0 | NaN | NaN | 44.0 | 45.0 |
Yes | 6.0 | NaN | NaN | 17.0 | 23.0 | |
All | 19.0 | 87.0 | 76.0 | 62.0 | 244.0 |
如果一些组合是空的(或NA
),我们希望直接用fill_value
来填充:
tips.pivot_table('tip_pct', index=['time', 'size', 'smoker'],
columns='day', aggfunc='mean', fill_value=0)
day | Fri | Sat | Sun | Thur | ||
---|---|---|---|---|---|---|
time | size | smoker | ||||
Dinner | 1 | No | 0.000000 | 0.137931 | 0.000000 | 0.000000 |
Yes | 0.000000 | 0.325733 | 0.000000 | 0.000000 | ||
2 | No | 0.139622 | 0.162705 | 0.168859 | 0.159744 | |
Yes | 0.171297 | 0.148668 | 0.207893 | 0.000000 | ||
3 | No | 0.000000 | 0.154661 | 0.152663 | 0.000000 | |
Yes | 0.000000 | 0.144995 | 0.152660 | 0.000000 | ||
4 | No | 0.000000 | 0.150096 | 0.148143 | 0.000000 | |
Yes | 0.117750 | 0.124515 | 0.193370 | 0.000000 | ||
5 | No | 0.000000 | 0.000000 | 0.206928 | 0.000000 | |
Yes | 0.000000 | 0.106572 | 0.065660 | 0.000000 | ||
6 | No | 0.000000 | 0.000000 | 0.103799 | 0.000000 | |
Lunch | 1 | No | 0.000000 | 0.000000 | 0.000000 | 0.181728 |
Yes | 0.223776 | 0.000000 | 0.000000 | 0.000000 | ||
2 | No | 0.000000 | 0.000000 | 0.000000 | 0.166005 | |
Yes | 0.181969 | 0.000000 | 0.000000 | 0.158843 | ||
3 | No | 0.187735 | 0.000000 | 0.000000 | 0.084246 | |
Yes | 0.000000 | 0.000000 | 0.000000 | 0.204952 | ||
4 | No | 0.000000 | 0.000000 | 0.000000 | 0.138919 | |
Yes | 0.000000 | 0.000000 | 0.000000 | 0.155410 | ||
5 | No | 0.000000 | 0.000000 | 0.000000 | 0.121389 | |
6 | No | 0.000000 | 0.000000 | 0.000000 | 0.173706 |
cross-tabulation
(交叉表,简写为crosstab
),是数据透视表的一个特殊形式,只计算组频率(group frequencies
)。这里有个例子:
data = pd.DataFrame({'Sample': np.arange(1, 11),
'Nationality': ['USA', 'Japan', 'USA', 'Japan', 'Japan', 'Japan', 'USA', 'USA', 'Japan', 'USA'],
'Handedness': ['Right-handed', 'Left-handed', 'Right-handed', 'Right-handed', 'Left-handed', 'Right-handed', 'Right-handed', 'Left-handed', 'Right-handed', 'Right-handed']})
data
Handedness | Nationality | Sample | |
---|---|---|---|
0 | Right-handed | USA | 1 |
1 | Left-handed | Japan | 2 |
2 | Right-handed | USA | 3 |
3 | Right-handed | Japan | 4 |
4 | Left-handed | Japan | 5 |
5 | Right-handed | Japan | 6 |
6 | Right-handed | USA | 7 |
7 | Left-handed | USA | 8 |
8 | Right-handed | Japan | 9 |
9 | Right-handed | USA | 10 |
作为调查分析(survey analysis
)的一部分,我们想要按国家和惯用手来进行汇总。我们可以使用pivot_table
来做到这点,不过pandas.crosstab
函数会更方便一些:
pd.crosstab(data.Nationality, data.Handedness, margins=True)
Handedness | Left-handed | Right-handed | All |
---|---|---|---|
Nationality | |||
Japan | 2 | 3 | 5 |
USA | 1 | 4 | 5 |
All | 3 | 7 | 10 |
crosstab
的前两个参数可以是数组或Series
或由数组组成的列表(a list of array
)。对于tips
数据,可以这么写:
pd.crosstab([tips.time, tips.day], tips.smoker, margins=True)
smoker | No | Yes | All | |
---|---|---|---|---|
time | day | |||
Dinner | Fri | 3 | 9 | 12 |
Sat | 45 | 42 | 87 | |
Sun | 57 | 19 | 76 | |
Thur | 1 | 0 | 1 | |
Lunch | Fri | 1 | 6 | 7 |
Thur | 44 | 17 | 61 | |
All | 151 | 93 | 244 |