有很多用于整理表格型数据的基本操作,指的就是reshape
和pivot
。
多层级索引提供一套统一的方法来整理DataFrame
中数据。主要有两个操作:
stack
unstack
下面我们会给出一些例子。这里有一个DataFrame
,我们用字符串数组来作为行和列的索引:
import numpy as np
import pandas as pd
data = pd.DataFrame(np.arange(6).reshape((2, 3)),
index=pd.Index(['Ohio', 'Colorado'], name='state'),
columns=pd.Index(['one', 'two', 'three'],
name='number'))
data
number | one | two | three |
---|---|---|---|
state | |||
Ohio | 0 | 1 | 2 |
Colorado | 3 | 4 | 5 |
使用stack
方法会把列数据变为行数据,产生一个Series
:
result = data.stack()
result
state number
Ohio one 0
two 1
three 2
Colorado one 3
two 4
three 5
dtype: int64
对于一个有多层级索引的Series
,可以用unstack
把它变回DataFrame
:
result.unstack()
number | one | two | three |
---|---|---|---|
state | |||
Ohio | 0 | 1 | 2 |
Colorado | 3 | 4 | 5 |
默认会把最内层的层级unstack
(取消堆叠),stack
默认也是这样。我们可以传入一个表示层级的数字或名字,来指定取消堆叠某个层级:
result.unstack(0)
state | Ohio | Colorado |
---|---|---|
number | ||
one | 0 | 3 |
two | 1 | 4 |
three | 2 | 5 |
result.unstack('state')
state | Ohio | Colorado |
---|---|---|
number | ||
one | 0 | 3 |
two | 1 | 4 |
three | 2 | 5 |
如果某个层级里的值不能在subgroup
(子组)里找到的话,unstack
可能会引入缺失值:
s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])
data2 = pd.concat([s1, s2], keys=['one', 'two'])
data2
one a 0
b 1
c 2
d 3
two c 4
d 5
e 6
dtype: int64
data2.unstack()
a | b | c | d | e | |
---|---|---|---|---|---|
one | 0.0 | 1.0 | 2.0 | 3.0 | NaN |
two | NaN | NaN | 4.0 | 5.0 | 6.0 |
stack
默认会把缺失值过滤掉,所以这两种操作是可逆的:
data2.unstack()
a | b | c | d | e | |
---|---|---|---|---|---|
one | 0.0 | 1.0 | 2.0 | 3.0 | NaN |
two | NaN | NaN | 4.0 | 5.0 | 6.0 |
data2.unstack().stack()
one a 0.0
b 1.0
c 2.0
d 3.0
two c 4.0
d 5.0
e 6.0
dtype: float64
data2.unstack().stack(dropna=False)
one a 0.0
b 1.0
c 2.0
d 3.0
e NaN
two a NaN
b NaN
c 4.0
d 5.0
e 6.0
dtype: float64
如果对一个DataFrame
使用unstack
,被取消堆叠(unstack
)的层级会变为结果中最低的层级:
df = pd.DataFrame({'left': result, 'right': result + 5},
columns=pd.Index(['left', 'right'], name='side'))
df # 行的话,有state和number两个层级,number是内层级。而列的话有side这一个层级
side | left | right | |
---|---|---|---|
state | number | ||
Ohio | one | 0 | 5 |
two | 1 | 6 | |
three | 2 | 7 | |
Colorado | one | 3 | 8 |
two | 4 | 9 | |
three | 5 | 10 |
df.unstack('state') # state被unstack后,变为比side更低的层级
side | left | right | ||
---|---|---|---|---|
state | Ohio | Colorado | Ohio | Colorado |
number | ||||
one | 0 | 3 | 5 | 8 |
two | 1 | 4 | 6 | 9 |
three | 2 | 5 | 7 | 10 |
调用stack
的时候,可以指明想要stack
(堆叠)哪一个轴:
df.unstack('state').stack('side')
state | Ohio | Colorado | |
---|---|---|---|
number | side | ||
one | left | 0 | 3 |
right | 5 | 8 | |
two | left | 1 | 4 |
right | 6 | 9 | |
three | left | 2 | 5 |
right | 7 | 10 |
一种用来把多重时间序列数据存储在数据库和CSV
中的格式叫long or stacked format
(长格式或堆叠格式)。下面我们加载一些数据,处理一下时间序列文件并做一些数据清理工作:
data = pd.read_csv('../examples/macrodata.csv')
data.head()
year | quarter | realgdp | realcons | realinv | realgovt | realdpi | cpi | m1 | tbilrate | unemp | pop | infl | realint | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1959.0 | 1.0 | 2710.349 | 1707.4 | 286.898 | 470.045 | 1886.9 | 28.98 | 139.7 | 2.82 | 5.8 | 177.146 | 0.00 | 0.00 |
1 | 1959.0 | 2.0 | 2778.801 | 1733.7 | 310.859 | 481.301 | 1919.7 | 29.15 | 141.7 | 3.08 | 5.1 | 177.830 | 2.34 | 0.74 |
2 | 1959.0 | 3.0 | 2775.488 | 1751.8 | 289.226 | 491.260 | 1916.4 | 29.35 | 140.5 | 3.82 | 5.3 | 178.657 | 2.74 | 1.09 |
3 | 1959.0 | 4.0 | 2785.204 | 1753.7 | 299.356 | 484.052 | 1931.3 | 29.37 | 140.0 | 4.33 | 5.6 | 179.386 | 0.27 | 4.06 |
4 | 1960.0 | 1.0 | 2847.699 | 1770.5 | 331.722 | 462.199 | 1955.5 | 29.54 | 139.6 | 3.50 | 5.2 | 180.007 | 2.31 | 1.19 |
periods = pd.PeriodIndex(year=data.year, quarter=data.quarter,
name='date')
columns = pd.Index(['realgdp', 'infl', 'unemp'], name='item')
data = data.reindex(columns=columns)
data.index = periods.to_timestamp('D', 'end')
ldata = data.stack().reset_index().rename(columns={0: 'value'})
对于PeriodIndex
,我们会在第十一章讲得更详细些。在这里,这个函数把year
和quarter
这两列整合起来作为一种时间间隔类型。
ldata
看起来是这样的:
ldata[:10]
date | item | value | |
---|---|---|---|
0 | 1959-03-31 | realgdp | 2710.349 |
1 | 1959-03-31 | infl | 0.000 |
2 | 1959-03-31 | unemp | 5.800 |
3 | 1959-06-30 | realgdp | 2778.801 |
4 | 1959-06-30 | infl | 2.340 |
5 | 1959-06-30 | unemp | 5.100 |
6 | 1959-09-30 | realgdp | 2775.488 |
7 | 1959-09-30 | infl | 2.740 |
8 | 1959-09-30 | unemp | 5.300 |
9 | 1959-12-31 | realgdp | 2785.204 |
这种格式叫做long format for multiple time series
(用于多重时间序列的长格式),或者有两个以上键(keys
)的观测数据(在这个例子里,keys
指的是date
和item
)。表格中的每一行表示一个观测数据。
这种数据经常被存储于关系型数据库中,比如MySQL
,这种固定的模式(列名和数据类型)能让作为item
列中不同的数据,添加到表格中。在前一个例子里,date
和item
通常被用来当做primary keys
(主键,这是关系型数据库里的术语),能实现relational integrity
(关系完整性)和更方便的join
(联结)。但是在一些例子里,这种格式的数据并不好处理;我们可能更喜欢有一个DataFrame
,其中一列能有不同的item
值,并用date
列作为索引。DataFrame
中的pivot
方法,就能做到这种转换:
pivoted = ldata.pivot('date', 'item', 'value')
pivoted
item | infl | realgdp | unemp |
---|---|---|---|
date | |||
1959-03-31 | 0.00 | 2710.349 | 5.8 |
1959-06-30 | 2.34 | 2778.801 | 5.1 |
1959-09-30 | 2.74 | 2775.488 | 5.3 |
1959-12-31 | 0.27 | 2785.204 | 5.6 |
1960-03-31 | 2.31 | 2847.699 | 5.2 |
1960-06-30 | 0.14 | 2834.390 | 5.2 |
1960-09-30 | 2.70 | 2839.022 | 5.6 |
1960-12-31 | 1.21 | 2802.616 | 6.3 |
1961-03-31 | -0.40 | 2819.264 | 6.8 |
1961-06-30 | 1.47 | 2872.005 | 7.0 |
1961-09-30 | 0.80 | 2918.419 | 6.8 |
1961-12-31 | 0.80 | 2977.830 | 6.2 |
1962-03-31 | 2.26 | 3031.241 | 5.6 |
1962-06-30 | 0.13 | 3064.709 | 5.5 |
1962-09-30 | 2.11 | 3093.047 | 5.6 |
1962-12-31 | 0.79 | 3100.563 | 5.5 |
1963-03-31 | 0.53 | 3141.087 | 5.8 |
1963-06-30 | 2.75 | 3180.447 | 5.7 |
1963-09-30 | 0.78 | 3240.332 | 5.5 |
1963-12-31 | 2.46 | 3264.967 | 5.6 |
1964-03-31 | 0.13 | 3338.246 | 5.5 |
1964-06-30 | 0.90 | 3376.587 | 5.2 |
1964-09-30 | 1.29 | 3422.469 | 5.0 |
1964-12-31 | 2.05 | 3431.957 | 5.0 |
1965-03-31 | 1.28 | 3516.251 | 4.9 |
1965-06-30 | 2.54 | 3563.960 | 4.7 |
1965-09-30 | 0.89 | 3636.285 | 4.4 |
1965-12-31 | 2.90 | 3724.014 | 4.1 |
1966-03-31 | 4.99 | 3815.423 | 3.9 |
1966-06-30 | 2.10 | 3828.124 | 3.8 |
... | ... | ... | ... |
2002-06-30 | 1.56 | 11538.770 | 5.8 |
2002-09-30 | 2.66 | 11596.430 | 5.7 |
2002-12-31 | 3.08 | 11598.824 | 5.8 |
2003-03-31 | 1.31 | 11645.819 | 5.9 |
2003-06-30 | 1.09 | 11738.706 | 6.2 |
2003-09-30 | 2.60 | 11935.461 | 6.1 |
2003-12-31 | 3.02 | 12042.817 | 5.8 |
2004-03-31 | 2.35 | 12127.623 | 5.7 |
2004-06-30 | 3.61 | 12213.818 | 5.6 |
2004-09-30 | 3.58 | 12303.533 | 5.4 |
2004-12-31 | 2.09 | 12410.282 | 5.4 |
2005-03-31 | 4.15 | 12534.113 | 5.3 |
2005-06-30 | 1.85 | 12587.535 | 5.1 |
2005-09-30 | 9.14 | 12683.153 | 5.0 |
2005-12-31 | 0.40 | 12748.699 | 4.9 |
2006-03-31 | 2.60 | 12915.938 | 4.7 |
2006-06-30 | 3.97 | 12962.462 | 4.7 |
2006-09-30 | -1.58 | 12965.916 | 4.7 |
2006-12-31 | 3.30 | 13060.679 | 4.4 |
2007-03-31 | 4.58 | 13099.901 | 4.5 |
2007-06-30 | 2.75 | 13203.977 | 4.5 |
2007-09-30 | 3.45 | 13321.109 | 4.7 |
2007-12-31 | 6.38 | 13391.249 | 4.8 |
2008-03-31 | 2.82 | 13366.865 | 4.9 |
2008-06-30 | 8.53 | 13415.266 | 5.4 |
2008-09-30 | -3.16 | 13324.600 | 6.0 |
2008-12-31 | -8.79 | 13141.920 | 6.9 |
2009-03-31 | 0.94 | 12925.410 | 8.1 |
2009-06-30 | 3.37 | 12901.504 | 9.2 |
2009-09-30 | 3.56 | 12990.341 | 9.6 |
203 rows × 3 columns
前两个传入的值是列,分别被用于作为行索引和列索引(date
是行索引,item
是列索引),最后是一个是可选的value column
(值列),用于填充DataFrame
。假设我们有两列值,我们想要同时整形:
ldata['value2'] = np.random.randn(len(ldata))
ldata[:10]
date | item | value | value2 | |
---|---|---|---|---|
0 | 1959-03-31 | realgdp | 2710.349 | 0.284176 |
1 | 1959-03-31 | infl | 0.000 | -1.428019 |
2 | 1959-03-31 | unemp | 5.800 | -1.238330 |
3 | 1959-06-30 | realgdp | 2778.801 | 0.144937 |
4 | 1959-06-30 | infl | 2.340 | -0.553085 |
5 | 1959-06-30 | unemp | 5.100 | -0.833374 |
6 | 1959-09-30 | realgdp | 2775.488 | 0.147826 |
7 | 1959-09-30 | infl | 2.740 | -0.059040 |
8 | 1959-09-30 | unemp | 5.300 | 0.531887 |
9 | 1959-12-31 | realgdp | 2785.204 | -0.169839 |
舍弃最后一个参数,我们能得到一个有多层级列的DataFrame
:
pivoted = ldata.pivot('date', 'item')
pivoted[:5]
value | value2 | |||||
---|---|---|---|---|---|---|
item | infl | realgdp | unemp | infl | realgdp | unemp |
date | ||||||
1959-03-31 | 0.00 | 2710.349 | 5.8 | -1.428019 | 0.284176 | -1.238330 |
1959-06-30 | 2.34 | 2778.801 | 5.1 | -0.553085 | 0.144937 | -0.833374 |
1959-09-30 | 2.74 | 2775.488 | 5.3 | -0.059040 | 0.147826 | 0.531887 |
1959-12-31 | 0.27 | 2785.204 | 5.6 | -0.436330 | -0.169839 | -0.203380 |
1960-03-31 | 2.31 | 2847.699 | 5.2 | 1.038559 | 0.644242 | -1.872500 |
pivoted['value'][:5]
item | infl | realgdp | unemp |
---|---|---|---|
date | |||
1959-03-31 | 0.00 | 2710.349 | 5.8 |
1959-06-30 | 2.34 | 2778.801 | 5.1 |
1959-09-30 | 2.74 | 2775.488 | 5.3 |
1959-12-31 | 0.27 | 2785.204 | 5.6 |
1960-03-31 | 2.31 | 2847.699 | 5.2 |
这里pivot
相当于用set_index
创建了一个多层级用里,并调用了unstack
:
unstacked = ldata.set_index(['date', 'item']).unstack('item')
unstacked[:7]
value | value2 | |||||
---|---|---|---|---|---|---|
item | infl | realgdp | unemp | infl | realgdp | unemp |
date | ||||||
1959-03-31 | 0.00 | 2710.349 | 5.8 | -1.428019 | 0.284176 | -1.238330 |
1959-06-30 | 2.34 | 2778.801 | 5.1 | -0.553085 | 0.144937 | -0.833374 |
1959-09-30 | 2.74 | 2775.488 | 5.3 | -0.059040 | 0.147826 | 0.531887 |
1959-12-31 | 0.27 | 2785.204 | 5.6 | -0.436330 | -0.169839 | -0.203380 |
1960-03-31 | 2.31 | 2847.699 | 5.2 | 1.038559 | 0.644242 | -1.872500 |
1960-06-30 | 0.14 | 2834.390 | 5.2 | 0.765028 | -0.835805 | 2.014334 |
1960-09-30 | 2.70 | 2839.022 | 5.6 | 0.168310 | 0.362230 | 1.603777 |
用于DataFrame
,与pivot
相反的操作是pandas.melt
。相对于把一列变为多列的pivot
,melt
会把多列变为一列,产生一个比输入的DataFrame
还要长的结果。看一下例子:
df = pd.DataFrame({'key': ['foo', 'bar', 'baz'],
'A': [1, 2, 3],
'B': [4, 5, 6],
'C': [7, 8, 9]})
df
A | B | C | key | |
---|---|---|---|---|
0 | 1 | 4 | 7 | foo |
1 | 2 | 5 | 8 | bar |
2 | 3 | 6 | 9 | baz |
'key
’列可以作为group indicator
(群指示器),其他列可以作为数据值。当使用pandas.melt
,我们必须指明哪些列是群指示器。这里我们令key
作为群指示器:
melted = pd.melt(df, ['key'])
melted
key | variable | value | |
---|---|---|---|
0 | foo | A | 1 |
1 | bar | A | 2 |
2 | baz | A | 3 |
3 | foo | B | 4 |
4 | bar | B | 5 |
5 | baz | B | 6 |
6 | foo | C | 7 |
7 | bar | C | 8 |
8 | baz | C | 9 |
使用pivot
,我们可以得到原来的布局:
reshaped = melted.pivot('key', 'variable', 'value')
reshaped
variable | A | B | C |
---|---|---|---|
key | |||
bar | 2 | 5 | 8 |
baz | 3 | 6 | 9 |
foo | 1 | 4 | 7 |
因为pivot
会给行标签创建一个索引(key
列),所以这里我们要用reset_index
来让数据变回去:
reshaped.reset_index()
variable | key | A | B | C |
---|---|---|---|---|
0 | bar | 2 | 5 | 8 |
1 | baz | 3 | 6 | 9 |
2 | foo | 1 | 4 | 7 |
当然,我们也可以在使用melt
的时候指定哪些列用于值:
pd.melt(df, id_vars=['key'], value_vars=['A', 'B'])
key | variable | value | |
---|---|---|---|
0 | foo | A | 1 |
1 | bar | A | 2 |
2 | baz | A | 3 |
3 | foo | B | 4 |
4 | bar | B | 5 |
5 | baz | B | 6 |
pandas.melt
也能在没有群指示器的情况下使用:
pd.melt(df, value_vars=['A', 'B', 'C'])
variable | value | |
---|---|---|
0 | A | 1 |
1 | A | 2 |
2 | A | 3 |
3 | B | 4 |
4 | B | 5 |
5 | B | 6 |
6 | C | 7 |
7 | C | 8 |
8 | C | 9 |
pd.melt(df, value_vars=['key','A', 'B'])
variable | value | |
---|---|---|
0 | key | foo |
1 | key | bar |
2 | key | baz |
3 | A | 1 |
4 | A | 2 |
5 | A | 3 |
6 | B | 4 |
7 | B | 5 |
8 | B | 6 |