数据透视表是电子表格程序和其他数据分析软件中常见的数据汇总工具。它按一个或多个键聚合数据表,一些组键沿行,一些组键沿列将数据排列在一个矩形中。我们使用 pandas 的 groupby 结合分层索引在Python 中实现数据透视表。DataFrame 有一个 pivot_table 方法,同时pandas也有一个 pivot_table 函数。除了为 groupby 提供方便的接口外,pivot_table还可以添加部分总计(边距)。
针对之前的小费数据集tip.csv,如果我们要计算一个按行中的日期和 smoker 排列的组均值表(默认pivot_table聚合类型),可以用如下代码实现。先看下tips.csv文件中的数据内容:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
print(tips.head())
tips["tip_pct"] = tips["tip"] / tips["total_bill"]result = tips.pivot_table(index=["day", "smoker"], values=["size", "tip", "tip_pct", "total_bill"])
print(result)
使用pandas对象的pivot_table方法,按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 |
以上结果也可以使用 tips.groupby([“day”, “smoker”]).mean() 直接生成。现在,如果我们按时间分组(time和day),并将time和day放在行中,smoker放在表列中,同时只取 tip_pct 和 size 的平均值:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
tips["tip_pct"] = tips["tip"] / tips["total_bill"]result = tips.pivot_table(index=["time", "day"], columns="smoker", values=["tip_pct", "size"])
print(result)
输出结果:
size | tip_pct | ||||
---|---|---|---|---|---|
smoker | No | Yes | No | Yes | |
time | day | ||||
Dinner | Fri | 2.000000 | 2.222222 | 0.139622 | 0.165347 |
Sat | 2.555556 | 2.476190 | 0.158048 | 0.147906 | |
Sun | 2.929825 | 2.578947 | 0.160113 | 0.187250 | |
Thur | 2.000000 | NaN | 0.159744 | NaN | |
Lunch | Fri | 3.000000 | 1.833333 | 0.187735 | 0.188937 |
Thur | 2.500000 | 2.352941 | 0.160311 | 0.163863 |
下面我们通过传递 margins=True 来扩充此表,让表增加按行的总计和按列的总计,总计行和列会具备All标签(索引):
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
tips["tip_pct"] = tips["tip"] / tips["total_bill"]result = tips.pivot_table(index=["time", "day"], columns="smoker", values=["tip_pct", "size"], margins=True)
print(result)
输出结果:
size | tip_pct | ||||||
---|---|---|---|---|---|---|---|
smoker | No | Yes | All | No | Yes | All | |
time | day | ||||||
Dinner | Fri | 2.000000 | 2.222222 | 2.166667 | 0.139622 | 0.165347 | 0.158916 |
Sat | 2.555556 | 2.476190 | 2.517241 | 0.158048 | 0.147906 | 0.153152 | |
Sun | 2.929825 | 2.578947 | 2.842105 | 0.160113 | 0.187250 | 0.166897 | |
Thur | 2.000000 | NaN | 2.000000 | 0.159744 | NaN | 0.159744 | |
Lunch | Fri | 3.000000 | 1.833333 | 2.000000 | 0.187735 | 0.188937 | 0.188765 |
Thur | 2.500000 | 2.352941 | 2.459016 | 0.160311 | 0.163863 | 0.161301 | |
All | 2.668874 | 2.408602 | 2.569672 | 0.159328 | 0.163196 | 0.160803 |
从上面输出可以看出,列方向,All 值是列的平均值;行方向,All值是No smoker和Yes somker的平均值。
上面使用的使用的是pivot_table默认使用的聚合函数 mean ,要使用其他函数可以将聚合函数或函数列表传递给pivot_table的 aggfunc 关键字参数。例如,下面我们使用 “count” 或 len 来计算组大小(计数或频率)(“count”计数排除数据组内的 null 值,而 len 则不会):
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
tips["tip_pct"] = tips["tip"] / tips["total_bill"]result = tips.pivot_table(index=["time", "smoker"], columns="day", values="tip_pct", aggfunc=len, margins=True)print(result)
输出结果:
day | Fri | Sat | Sun | Thur | All | |
---|---|---|---|---|---|---|
time | smoker | |||||
Dinner | No | 3.0 | 45.0 | 57.0 | 1.0 | 106 |
Yes | 9.0 | 42.0 | 19.0 | NaN | 70 | |
Lunch | No | 1.0 | NaN | NaN | 44.0 | 45 |
Yes | 6.0 | NaN | NaN | 17.0 | 23 | |
All | 19.0 | 87.0 | 76.0 | 62.0 | 244 |
如果要填充组中数据的空值(或NA),我们还可以使用pivot_table的参数fill_value:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
tips["tip_pct"] = tips["tip"] / tips["total_bill"]result = tips.pivot_table(index=["time", "size", "smoker"], columns="day", values="tip_pct", fill_value=0)print(result)
输出结果:
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 | |
... | ... | ... | ... | ... | ... | ... |
Lunch | 3 | 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 |
21 rows × 4 columns
下图是pivot_table 函数参数的列表:
交叉制表:交叉表:是计算组频率的数据透视表的一种特殊情况。
我们用代码示例来学习。下面代码,我们按国籍来统计各个国家的人是习惯使用左手还是右手,可以使用上面学习的 pivot_table 来实现,但 pandas.crosstab 函数会更方便:
import numpy as np
import pandas as pd
from io import StringIO# 用表格样式的字符串创建一个pandas对象
data = """Sample Nationality Handedness
1 USA Right-handed
2 Japan Left-handed
3 USA Right-handed
4 Japan Right-handed
5 Japan Left-handed
6 Japan Right-handed
7 USA Right-handed
8 USA Left-handed
9 Japan Right-handed
10 USA Right-handed"""
data = pd.read_table(StringIO(data), sep="\s+")
print(data)# 按国籍Nationality,统计惯用手Handedness数据
result = pd.crosstab(data["Nationality"], data["Handedness"], margins=True)
print(result)
data输出:
Sample | Nationality | Handedness | |
---|---|---|---|
0 | 1 | USA | Right-handed |
1 | 2 | Japan | Left-handed |
2 | 3 | USA | Right-handed |
3 | 4 | Japan | Right-handed |
4 | 5 | Japan | Left-handed |
5 | 6 | Japan | Right-handed |
6 | 7 | USA | Right-handed |
7 | 8 | USA | Left-handed |
8 | 9 | Japan | Right-handed |
9 | 10 | USA | Right-handed |
result输出:
Handedness | Left-handed | Right-handed | All |
---|---|---|---|
Nationality | |||
Japan | 2 | 3 | 5 |
USA | 1 | 4 | 5 |
All | 3 | 7 | 10 |
crosstab 的前两个参数都可以是一个数组、 Series 或数组列表。再看tips.csv数据示例:
import numpy as np
import pandas as pdtips = pd.read_csv("examples/tips.csv")
tips["tip_pct"] = tips["tip"] / tips["total_bill"]result = pd.crosstab([tips["time"], tips["day"]], tips["smoker"], margins=True)
print(result)
输出结果:
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 |
总结:掌握 pandas 的数据分组工具的使用,对数据清理和建模以及统计分析非常有用。在学完这些基础之后,我们将用真实数据对 groupby 的使用进行示例学习。不过,在这之前,我们掌握时间序列数据的处理。下一次学习时间序列数据处理。