玩过Excel的都知道excel里面有个非常好用的分类汇总进行数据展示的功能——pivot table,即数据透视表。在Pandas里面也有个类似的功能,pivot和pivot_table。这个功能用好了,对数据进行分类汇总展示基本上都可以搞定了。
这一期我们就来拆一拆这些功能
1. Pivot and pivot_table
先从简单的pivot开始。首先确保pandas的版本在1.1之上,因为后面的很多功能时是pandas在1.1之后再加入的!
import pandas as pd
import numpy as np
print(pd.__version__)
>>>1.1.5
先创建一个简单的DF进行演示:
df.pivot(index=None, columns=None, values=None) -> 'DataFrame'
df = pd.DataFrame({'Class':[1,1,2,2],
'Name':['San Zhang','San Zhang','Si Li','Si Li'],
'Subject':['Chinese','Math','Chinese','Math'],
'Grade':[80,75,90,85]})
df
这个表不太符合我们的阅读习惯。我们希望将某个学生的所有成绩作为一行来展示,因而不同科目的成绩就需要单独一列。这个就是所谓的变形。pivot就是干这个的:
df.pivot(index = 'Name',
columns = 'Subject',
values = 'Grade')
从上面这个简单的例子,我们可以看到pivot的基本语法为:
df.pivot(index=None, columns=None, values=None),
返回值是一个经过变形之后的DataFrame。这个DataFrame有有三个要素,分别是:
- 变形后的行索引,例子中的 Name
- 需要转到列索引的列, 例子中的 Subject
- 以及这些列和行索引对应的数值,例子中的 Grade
它们分别对应了 pivot 方法中的 index, columns, values 参数。
新生成表的列索引是 columns 对应列的 unique 值,而新表的行索引是 index 对应列的 unique 值,而 values 对应了想要展示的数值列。
这里pivot 的使用依赖于唯一性条件,那如果不满足唯一性条件,那么必须通过聚合操作使得相同行列组合对应的多个值变为一个值。例如,张三和李四都参加了两次语文考试和数学考试,按照学院规定,最后的成绩是两次考试分数的平均值,此时就无法通过 pivot 函数来完成。
df.loc[1, 'Subject'] = 'Chinese'
df
此时,因为索引(San Zhang, Chinese)有两个值,再使用pivot就会报错
df.pivot(index = 'Name',
columns = 'Subject',
values = 'Grade')
>>>
ValueError
Traceback (most recent call last)
ValueError: Index contains duplicate entries, cannot reshape
另外, pivot 相关的三个参数允许被设置为列表(需要将pandas升级到1.1之上),这也意味着会返回多级索引。这里构造一个相应的例子来说明如何使用:下表中六列分别为班级、姓名、测试类型(期中考试和期末考试)、科目、成绩、排名。
df2 = pd.DataFrame({'Class':[1, 1, 2, 2, 1, 1, 2, 2],
'Name':['San Zhang', 'San Zhang', 'Si Li', 'Si Li',
'San Zhang', 'San Zhang', 'Si Li', 'Si Li'],
'Examination': ['Mid', 'Final', 'Mid', 'Final',
'Mid', 'Final', 'Mid', 'Final'],
'Subject':['Chinese', 'Chinese', 'Chinese', 'Chinese',
'Math', 'Math', 'Math', 'Math'],
'Grade':[80, 75, 85, 65, 90, 85, 92, 88],
'rank':[10, 15, 21, 15, 20, 7, 6, 2]})
df2
df2.pivot(index = ['Class', 'Name'],
columns = ['Subject', 'Examination'],
values = ['Grade', 'rank'])
这个多级索引的变形过程可以参考下面这张图进行理解:其中index/columns/values使用不同的颜色区分
从这两个例子我们可以看出使用pandas中pivot的前提是你需要确保你能够充分理解你的数据,并清楚地知道你想通过变形来如何展示,这个很多时候需要和业务挂钩。上面我们也提到了使用pivot的唯一性问题。本质上,使用pivot后的数据和原始数据是等价的,只不过通过变形,展示的方式不同。那么如果不满足唯一性怎么办呢?例如实际情况中,所有同学参加了两次考试,最终成绩以平均分即需要对分数进行聚合等操作,这时就需要pivot_table这个函数了。举例如下:
df3 = pd.DataFrame({'Name':['San Zhang', 'San Zhang',
'San Zhang', 'San Zhang',
'Si Li', 'Si Li', 'Si Li', 'Si Li'],
'Subject':['Chinese', 'Chinese', 'Math', 'Math',
'Chinese', 'Chinese', 'Math', 'Math'],
'Grade':[80, 90, 100, 90, 70, 80, 85, 95]})
df3
df3.pivot_table(index = "Name",
columns = 'Subject',
values = 'Grade',
aggfunc = 'max')
从上面可以看到pivot_table比pivot多了一个aggfunc参数,即聚合参数。也就是将原表中的多行数据按照什么函数聚合,从而解决pivot之前报错的问题
综上所述:
- pivot:无法聚合,只能简单重塑(reshape),如果存在重复数据将会报错;常用于处理非数字数据。
- pivot_table:可以聚合,正好弥补 pivot 的缺陷。
2. Melt
通过上面的pivot操作,我们可以把一个长表转为宽表,那么有没有将宽表变成长表的操作呢?当然有,就是melt。再pandas中,melt可以当成是pivot的逆操作,举例如下:
df = pd.DataFrame({'Class':[1,2],
'Name':['San Zhang', 'Si Li'],
'Chinese':[80, 90],
'Math':[80, 75]})
df
下面通过melt方法,将Chinese和math压缩到一列中,列名为subject
df_melt = df.melt(id_vars = ['Class', 'Name'],
value_vars = ['Chinese', 'Math'],
var_name = 'Subject',
value_name = 'Grade')
df_melt
melt的参数和压缩过程如下:
df.melt(
id_vars=None,
value_vars=None,
var_name=None,
value_name='value',
col_level=None,
ignore_index=True,
) -> 'DataFrame'
3. wide_to_long方法
melt 方法中,在列索引中被压缩的一组值对应的列元素只能代表同一层次的含义,即 values_name 。现在如果列中包含了交叉类别,比如期中期末的类别和语文数学的类别,那么想要把 values_name 对应的 Grade 扩充为两列分别对应语文分数和数学分数,只把期中期末的信息压缩,这种需求下就要使用 wide_to_long 函数来完成。
df = pd.DataFrame({'Class':[1,2],'Name':['San Zhang', 'Si Li'],
'Chinese_Mid':[80, 75], 'Math_Mid':[90, 85],
'Chinese_Final':[80, 75], 'Math_Final':[90, 85]})
df
pd.wide_to_long(df,
stubnames=['Chinese', 'Math'],
i = ['Class', 'Name'],
j='Examination',
sep='_',
suffix='.+')
变换的过程可以按照下图去理解:
4. 索引的变形
在第二章中提到了利用 swaplevel 或者 reorder_levels 进行索引内部的层交换,下面就要讨论 行列索引之间 的交换,由于这种交换带来了 DataFrame 维度上的变化,因此属于变形操作。在第一节中提到的4种变形函数与其不同之处在于,它们都属于某一列或几列 元素 和 列索引 之间的转换,而不是索引之间的转换。
unstack 函数的作用是把行索引转为列索引,例如下面这个简单的例子:
df = pd.DataFrame(np.ones((4,2)),
index = pd.Index([('A', 'cat', 'big'),
('A', 'dog', 'small'),
('B', 'cat', 'big'),
('B', 'dog', 'small')]),
columns=['col_1', 'col_2'])
df
使用unstack查看一下效果:
类似于 pivot 中的唯一性要求,在 unstack 中必须保证 被转为列索引的行索引层 和 被保留的行索引层 构成的组合是唯一的。
与 unstack 相反, stack 的作用就是把列索引的层压入行索引,其用法完全类似。这里不再举例,读者可以自行尝试。
在上面介绍的所有函数中,除了带有聚合效果的 pivot_table
以外,所有的函数在变形前后并不会带来 values
个数的改变,只是这些值在呈现的形式上发生了变化。在上一章讨论的分组聚合操作,由于生成了新的行列索引,因此必然也属于某种特殊的变形操作,但由于聚合之后把原来的多个值变为了一个值,因此 values
的个数产生了变化,这也是分组聚合与变形函数的最大区别。
练习:
Ex1:美国非法药物数据集
现有一份关于美国非法药物的数据集,其中 SubstanceName,DrugReports
分别指药物名称和报告数量:
问题1:将数据转为如下的形式:
思路: pivot的使用,将state,county,SubstanceName作为index, 之后需要reset_index重新设置索引
df_res = df.pivot(index = ['State', 'COUNTY','SubstanceName'],
columns = 'YYYY',
values = 'DrugReports').reset_index().rename_axis(columns = {'YYYY' : ''})
df_res
问题2. 将第1问中的结果恢复为原表。
思路-pivot的逆操作,melt
res_melted = df_res.melt(id_vars = ['State', 'COUNTY', 'SubstanceName'],
value_vars = df_res.columns[-8:],
var_name = 'YYYY',
value_name = 'DrugReports')
res_melted
将表中的NaN项去除,并将相关数据形式重新设定
res_melted = res_melted[df.columns].sort_values(['State','COUNTY','SubstanceName'],ignore_index=True).astype({'YYYY':'int64', 'DrugReports':'int64'})
问题3. 按 State 分别统计每年的报告数量总和,其中 State, YYYY 分别为列索引和行索引,要求分别使用 pivot_table 函数与 groupby+unstack 两种不同的策略实现,并体会它们之间的联系。
- pivot_table
df_3 = df.pivot_table(index ='YYYY',
columns = 'State',
values = 'DrugReports',
aggfunc = 'sum')
df_3
- groupby + unstack
df_4 = df.groupby(['State', 'YYYY'])['DrugReports'].sum().to_frame().unstack(0).droplevel(0,axis=1)
df_4
Ex2:特殊的wide_to_long方法
从功能上看,melt方法应当属于wide_to_long的一种特殊情况,即stubnames只有一类。请使用wide_to_long生成melt一节中的df_melted。(提示:对列名增加适当的前缀)
另外,更多精彩内容也可以微信搜索,并关注公众号:‘Python数据科学家之路“ ,期待您的到来和我交流!