Pandas表格变形

长宽表的变形

例如:一个表中把性别存储在某一个列中,那么它就是关于性别的长表;如果把性别作为列名,列中的元素是某一其他的相关特征数值,那么这个表是关于性别的宽表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd

df_long = pd.DataFrame({'Gender':['F','F','M','M'], 'Height':[163, 160, 175, 180]})
'''
Gender Height
0 F 163
1 F 160
2 M 175
3 M 180
'''

df_wide = pd.DataFrame({'Height: F':[163, 160], 'Height: M':[175, 180]})
'''
Height: F Height: M
0 163 175
1 160 180
'''

显然这两张表从信息上是完全等价的,它们包含相同的身高统计数值,只是这些数值的呈现方式不同,而其呈现方式主要又与性别一列选择的布局模式有关,即到底是以 long 的状态存储还是以 wide 的状态存储。因此,Pandas 针对此类长宽表的变形操作设计了一些有关的变形函数。

df.pivot()

对于一个基本的长变宽的操作而言,最重要的有三个要素,分别是变形后的行索引、需要转到列索引的列,以及这些列和行索引对应的数值,它们分别对应了 pivot 方法中的 index, columns, values 参数。新生成表的列索引是 columns 对应列的 unique 值,而新表的行索引是 index 对应列的 unique 值,而 values 对应了想要展示的数值列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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]})
'''
Class Name Subject Grade
0 1 San Zhang Chinese 80
1 1 San Zhang Math 75
2 2 Si Li Chinese 90
3 2 Si Li Math 85
'''

df_wide = df.pivot(index='Name', columns='Subject', values='Grade'))
'''
Subject Chinese Math
Name
San Zhang 80 75
Si Li 90 85
'''

利用 pivot 进行变形操作需要满足唯一性的要求,即由于在新表中的行列索引对应了唯一的 value ,因此原表中的 index 和 columns 对应两个列的行组合必须唯一。

df.pivot_table()

如果表格不满足唯一性条件,那么必须通过聚合操作使得相同行列组合对应的多个值变为一个值。例如在上面的例子中,张三和李四都参加了两次语文考试和数学考试,按照学院规定,最后的成绩是两次考试分数的平均值,此时需要使用 pivot_table 来实现,其中的 aggfunc 参数就是使用的聚合函数,可以传入合法的聚合操作字符串,还可以传入以序列为输入标量为输出的聚合函数来实现自定义操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
df = 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]})
'''
Name Subject Grade
0 San Zhang Chinese 80
1 San Zhang Chinese 90
2 San Zhang Math 100
3 San Zhang Math 90
4 Si Li Chinese 70
5 Si Li Chinese 80
6 Si Li Math 85
7 Si Li Math 95
'''

df_wide = df.pivot_table(index='Name', columns='Subject', values='Grade', aggfunc='mean')
'''
Subject Chinese Math
Name
San Zhang 85 95
Si Li 75 90
'''

pivot_table 还具有边际汇总的功能,可以通过设置 margins=True 来实现,其中边际的聚合方式与
aggfunc 中给出的聚合方法一致。下面的操作就分别统计了语文均分和数学均分、张三均分和李四均分,以及总体所有分数的均分。

1
2
3
4
5
6
7
8
print(df.pivot_table(index = 'Name', columns = 'Subject', values = 'Grade', aggfunc='mean', margins=True))
'''
Subject Chinese Math All
Name
San Zhang 85 95.0 90.00
Si Li 75 90.0 82.50
All 80 92.5 86.25
'''

df.melt()

前面我们利用 pivot 把长表转为宽表,那么就可以通过相应的逆操作 melt 把宽表转为长表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
df_wide = pd.DataFrame({'Class':[1,2],
'Name':['San Zhang', 'Si Li'],
'Chinese':[80, 90],
'Math':[80, 75]})
'''
Class Name Chinese Math
0 1 San Zhang 80 80
1 2 Si Li 90 75
'''

df_long = df_wide.melt(id_vars = ['Class', 'Name'],
value_vars = ['Chinese', 'Math'],
var_name = 'Subject',
value_name = 'Grade')
'''
Class Name Subject Grade
0 1 San Zhang Chinese 80
1 2 Si Li Chinese 90
2 1 San Zhang Math 80
3 2 Si Li Math 75
'''

其中的参数 id_vars 表示在长表中的索引,value_vars 指明在宽表变换长表过程中需要从列转换到行的变量,var_name 指明在长表中的列变量名,value_name 指明在长表中的列变量取值。

pd.wide_to_long()

melt 方法中,在列索引中被压缩的一组值对应的列元素只能代表同一层次的含义,即 values_name 。现在如果列中包含了交叉类别,比如期中期末的类别和语文数学的类别,那么想要把 values_name 对应的 Grade 扩充为两列分别对应语文分数和数学分数,只把期中期末的信息压缩,这种需求下就要使用 wide_to_long 函数来完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
df_wide = 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]})
'''
Class Name Chinese_Mid Math_Mid Chinese_Final Math_Final
0 1 San Zhang 80 90 80 90
1 2 Si Li 75 85 75 85
'''

df_long = pd.wide_to_long(df_wide, stubnames=['Chinese', 'Math'],
i = ['Class', 'Name'],
j='Examination', sep='_', suffix='.+')
'''
Chinese Math
Class Name Examination
1 San Zhang Mid 80 90
Final 80 90
2 Si Li Mid 75 85
Final 75 85
'''

其中,参数 stubnames 指明在长表中留存的列名, i 指明索引,j 指明宽表拆分后新增的列名,sep 是用来拆分宽表列名的分隔符。

索引的变形

unstack 函数的作用是把行索引转为列索引,主要参数是移动的层号,默认转化最内层,移动到列索引的最内层,同时支持同时转化多个层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import numpy as np

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'])
'''
col_1 col_2
A cat big 1.0 1.0
dog small 1.0 1.0
B cat big 1.0 1.0
dog small 1.0 1.0
'''

print(df.unstack()) # 等价于 df.unstack(2)
'''
col_1 col_2
big small big small
A cat 1.0 NaN 1.0 NaN
dog NaN 1.0 NaN 1.0
B cat 1.0 NaN 1.0 NaN
dog NaN 1.0 NaN 1.0
'''

print(df.unstack([0, 2]))
'''
col_1 col_2
A B A B
big small big small big small big small
cat 1.0 NaN 1.0 NaN 1.0 NaN 1.0 NaN
dog NaN 1.0 NaN 1.0 NaN 1.0 NaN 1.0
'''

类似于 pivot 中的唯一性要求,在 unstack 中必须保证被转为列索引的行索引层和被保留的行索引层构成的组合是唯一的。与 unstack 相反,stack 的作用就是把列索引的层压入行索引,其用法完全类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
df_T = 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']).T
'''
A B
cat dog cat dog
big small big small
col_1 1.0 1.0 1.0 1.0
col_2 1.0 1.0 1.0 1.0
'''

print(df_T.stack())
'''
A B
cat dog cat dog
col_1 big 1.0 NaN 1.0 NaN
small NaN 1.0 NaN 1.0
col_2 big 1.0 NaN 1.0 NaN
small NaN 1.0 NaN 1.0
'''

其他变形函数

explode 函数能够对某一列的元素进行纵向的展开,被展开的单元格必须存储 list, tuple, Series, np.ndarray 中的一种类型。

1
2
3
4
5
6
7
8
9
10
11
df_ex = pd.DataFrame({'A': [[1, 2], 'my_str', {1, 2}, pd.Series([3, 4])], 'B': 1})
print(df_ex.explode('A'))
'''
A B
0 1 1
0 2 1
1 my_str 1
2 {1, 2} 1
3 3 1
3 4 1
'''

get_dummies 是用于特征构建的重要函数之一,其作用是把类别特征转为指示变量。例如,对年级一列转为指示变量,属于某一个年级的对应列标记为 1,否则为 0:

1
2
3
4
5
6
7
8
9
10
df = pd.read_csv('data/learn_pandas.csv')
print(pd.get_dummies(df.Grade).head())
'''
Freshman Junior Senior Sophomore
0 1 0 0 0
1 1 0 0 0
2 0 0 1 0
3 0 0 0 1
4 0 0 0 1
'''