Pandas从入门到精通(5)- 变形

玩过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
image.png

这个表不太符合我们的阅读习惯。我们希望将某个学生的所有成绩作为一行来展示,因而不同科目的成绩就需要单独一列。这个就是所谓的变形。pivot就是干这个的:

df.pivot(index = 'Name',
        columns = 'Subject',
        values = 'Grade')
image.png

从上面这个简单的例子,我们可以看到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
image.png

此时,因为索引(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
image.png
df2.pivot(index = ['Class', 'Name'],
         columns = ['Subject', 'Examination'],
         values = ['Grade', 'rank'])
image.png

这个多级索引的变形过程可以参考下面这张图进行理解:其中index/columns/values使用不同的颜色区分


image.png

从这两个例子我们可以看出使用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
image.png
df3.pivot_table(index = "Name",
               columns = 'Subject',
               values = 'Grade',
               aggfunc = 'max')
image.png

从上面可以看到pivot_table比pivot多了一个aggfunc参数,即聚合参数。也就是将原表中的多行数据按照什么函数聚合,从而解决pivot之前报错的问题
综上所述:

  1. pivot:无法聚合,只能简单重塑(reshape),如果存在重复数据将会报错;常用于处理非数字数据。
  2. 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
image.png

下面通过melt方法,将Chinese和math压缩到一列中,列名为subject

df_melt = df.melt(id_vars = ['Class', 'Name'],
                 value_vars = ['Chinese', 'Math'],
                 var_name = 'Subject',
                 value_name = 'Grade')
df_melt
image.png

melt的参数和压缩过程如下:
df.melt(
id_vars=None,
value_vars=None,
var_name=None,
value_name='value',
col_level=None,
ignore_index=True,
) -> 'DataFrame'


image.png

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
image.png
pd.wide_to_long(df,
                stubnames=['Chinese', 'Math'],
                i = ['Class', 'Name'],
                j='Examination',
                sep='_',
                suffix='.+')
image.png

变换的过程可以按照下图去理解:


image.png

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
image.png

使用unstack查看一下效果:

image.png

类似于 pivot 中的唯一性要求,在 unstack 中必须保证 被转为列索引的行索引层 和 被保留的行索引层 构成的组合是唯一的。
与 unstack 相反, stack 的作用就是把列索引的层压入行索引,其用法完全类似。这里不再举例,读者可以自行尝试。

在上面介绍的所有函数中,除了带有聚合效果的 pivot_table 以外,所有的函数在变形前后并不会带来 values 个数的改变,只是这些值在呈现的形式上发生了变化。在上一章讨论的分组聚合操作,由于生成了新的行列索引,因此必然也属于某种特殊的变形操作,但由于聚合之后把原来的多个值变为了一个值,因此 values 的个数产生了变化,这也是分组聚合与变形函数的最大区别。

练习:

Ex1:美国非法药物数据集

现有一份关于美国非法药物的数据集,其中 SubstanceName,DrugReports 分别指药物名称和报告数量:

image.png

问题1:将数据转为如下的形式:

image.png

思路: 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
image.png

问题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
image.png

将表中的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 两种不同的策略实现,并体会它们之间的联系。

  1. pivot_table
df_3 = df.pivot_table(index ='YYYY', 
                     columns = 'State',
                     values = 'DrugReports',
                     aggfunc = 'sum')
df_3
image.png
  1. groupby + unstack
df_4 = df.groupby(['State', 'YYYY'])['DrugReports'].sum().to_frame().unstack(0).droplevel(0,axis=1)
df_4
image.png

Ex2:特殊的wide_to_long方法

从功能上看,melt方法应当属于wide_to_long的一种特殊情况,即stubnames只有一类。请使用wide_to_long生成melt一节中的df_melted。(提示:对列名增加适当的前缀)


image.png

image.png

另外,更多精彩内容也可以微信搜索,并关注公众号:‘Python数据科学家之路“ ,期待您的到来和我交流!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容

  • 长宽表的变形 Pivot pivot 是一种典型的长表变宽表的函数对于一个基本的长变宽的操作而言,最重要的有三个要...
    罐罐儿111阅读 191评论 0 0
  • 参考:https://datawhalechina.github.io/joyful-pandas/build/h...
    叶小刀_b59f阅读 316评论 0 0
  • 1.长宽表的变形 什么是长表?什么是宽表?这个概念是对于某一个特征而言的。例如:一个表中把性别存储在某一个列中, ...
    58506fd3fbed阅读 252评论 0 0
  • 数据结构 在Pandas中有两类非常重要的数据结构,即序列Series和数据框DataFrame。Series类似...
    BubbleL阅读 698评论 0 2
  • 第1章 入门知识 简述:用于操作行列数据,方便地实现各种形式的数据分析; Pandas最初主要应用于金融数据分析工...
    惑也阅读 1,027评论 0 3