重塑和轴向旋转
有许多用于重新排列表格型数据的基础运算。这些函数也称作重塑(reshape)或轴向旋转(pivot)运算。
重塑层次化索引
层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二:
·stack:将数据的列“旋转”为行。
·unstack:将数据的行“旋转”为列。
我将通过一系列的范例来讲解这些操作。接下来看一个简单的DataFrame,其中的行列索引均为字符串:
- In [94]: data = DataFrame(np.arange(6).reshape((2, 3)),
- ...: index=pd.Index(['Ohio', 'Colorado'], name='state'),
- ...: columns=pd.Index(['one', 'two', 'three'], name='number'))
- In [95]: data
- Out[95]:
- number one two three
- state
- Ohio 0 1 2
- Colorado 3 4 5
使用该数据的stack方法即可将列转换为行,得到一个Series:
- In [96]: result = data.stack()
- In [97]: result
- Out[97]:
- state number
- Ohio one 0
- two 1
- three 2
- Colorado one 3
- two 4
- three 5
对于一个层次化索引的Series,你可以用unstack将其重排为一个DataFrame:
- In [98]: result.unstack()
- Out[98]:
- number one two three
- state
- Ohio 0 1 2
- Colorado 3 4 5
默认情况下,unstack操作的是最内层(stack也是如此)。传入分层级别的编号或名称即可对其他级别进行unstack操作:
- In [99]: result.unstack(0) In [100]: result.unstack('state')
- Out[99]: Out[100]:
- state Ohio Colorado state Ohio Colorado
- number number
- one 0 3 one 0 3
- two 1 4 two 1 4
- three 2 5 three 2 5
如果不是所有的级别值都能在各分组中找到的话,则unstack操作可能会引入缺失数据:
- In [101]: s1 = Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
- In [102]: s2 = Series([4, 5, 6], index=['c', 'd', 'e'])
- In [103]: data2 = pd.concat([s1, s2], keys=['one', 'two'])
- In [104]: data2.unstack()
- Out[104]:
- a b c d e
- one 0 1 2 3 NaN
- two NaN NaN 4 5 6
stack默认会滤除缺失数据,因此该运算是可逆的:
- In [105]: data2.unstack().stack() In [106]: data2.unstack().stack(dropna=False) Out[105]: Out[106]:
- one a 0 one a 0
- b 1 b 1
- c 2 c 2
- d 3 d 3
- two c 4 e NaN
- d 5 two a NaN
- e 6 b NaN
- c 4
- d 5
- e 6
在对DataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别:
- In [107]: df = DataFrame({'left': result, 'right': result + 5},
- ...: columns=pd.Index(['left', 'right'], name='side'))
- In [108]: df
- Out[108]:
- 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
- In [109]: df.unstack('state') In [110]: df.unstack('state').stack('side')
- Out[109]: Out[110]:
- side left right state Ohio Colorado
- state Ohio Colorado Ohio Colorado number side
- number one left 0 3
- one 0 3 5 8 right 5 8
- two 1 4 6 9 two left 1 4
- three 2 5 7 10 right 6 9
- three left 2 5
- right 7 10
将“长格式”旋转为“宽格式”
时间序列数据通常是以所谓的“长格式”(long)或“堆叠格式”(stacked)存储在数据库和CSV中的:译注4
- In [116]: ldata[:10]
- Out[116]:
- date item value
- 0 1959-03-31 00:00:00 realgdp 2710.349
- 1 1959-03-31 00:00:00 infl 0.000
- 2 1959-03-31 00:00:00 unemp 5.800
- 3 1959-06-30 00:00:00 realgdp 2778.801
- 4 1959-06-30 00:00:00 infl 2.340
- 5 1959-06-30 00:00:00 unemp 5.100
- 6 1959-09-30 00:00:00 realgdp 2775.488
- 7 1959-09-30 00:00:00 infl 2.740
- 8 1959-09-30 00:00:00 unemp 5.300
- 9 1959-12-31 00:00:00 realgdp 2785.204
关系型数据库(如MySQL)中的数据经常都是这样存储的,因为固定架构(即列名和数据类型)有一个好处:随着表中数据的添加或删除,item列中的值的种类能够增加或减少。在上面那个例子中,date和item通常就是主键(用关系型数据库的说法),不仅提供了关系完整性,而且提供了更为简单的查询支持。当然这也是有缺点的:长格式的数据操作起来可能不那么轻松。你可能会更喜欢DataFrame,不同的item值分别形成一列,date列中的时间值则用作索引。DataFrame的pivot方法完全可以实现这个转换:
- In [117]: pivoted = ldata.pivot('date', 'item', 'value')
- In [118]: pivoted.head()
- Out[118]:
- 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
前两个参数值分别用作行和列索引的列名,最后一个参数值则是用于填充DataFrame的数据列的列名。假设有两个需要参与重塑的数据列:
- In [119]: ldata['value2'] = np.random.randn(len(ldata))
- In [120]: ldata[:10]
- Out[120]:
- date item value value2
- 0 1959-03-31 00:00:00 realgdp 2710.349 1.669025
- 1 1959-03-31 00:00:00 infl 0.000 -0.438570
- 2 1959-03-31 00:00:00 unemp 5.800 -0.539741
- 3 1959-06-30 00:00:00 realgdp 2778.801 0.476985
- 4 1959-06-30 00:00:00 infl 2.340 3.248944
- 5 1959-06-30 00:00:00 unemp 5.100 -1.021228
- 6 1959-09-30 00:00:00 realgdp 2775.488 -0.577087
- 7 1959-09-30 00:00:00 infl 2.740 0.124121
- 8 1959-09-30 00:00:00 unemp 5.300 0.302614
- 9 1959-12-31 00:00:00 realgdp 2785.204 0.523772
如果忽略最后一个参数,得到的DataFrame就会带有层次化的列:
- In [121]: pivoted = ldata.pivot('date', 'item')
- In [122]: pivoted[:5]
- Out[122]:
- value value2
- item infl realgdp unemp infl realgdp unemp
- date
- 1959-03-31 0.00 2710.349 5.8 -0.438570 1.669025 -0.539741
- 1959-06-30 2.34 2778.801 5.1 3.248944 0.476985 -1.021228
- 1959-09-30 2.74 2775.488 5.3 0.124121 -0.577087 0.302614
- 1959-12-31 0.27 2785.204 5.6 0.000940 0.523772 1.343810
- 1960-03-31 2.31 2847.699 5.2 -0.831154 -0.713544 -2.370232
- In [123]: pivoted['value'][:5]
- Out[123]:
- 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重塑。
- In [124]: unstacked = ldata.set_index(['date', 'item']).unstack('item')
- In [125]: unstacked[:7]
- Out[125]:
- value value2
- item infl realgdp unemp infl realgdp unemp
- date
- 1959-03-31 0.00 2710.349 5.8 -0.438570 1.669025 -0.539741
- 1959-06-30 2.34 2778.801 5.1 3.248944 0.476985 -1.021228
- 1959-09-30 2.74 2775.488 5.3 0.124121 -0.577087 0.302614
- 1959-12-31 0.27 2785.204 5.6 0.000940 0.523772 1.343810
- 1960-03-31 2.31 2847.699 5.2 -0.831154 -0.713544 -2.370232
- 1960-06-30 0.14 2834.390 5.2 -0.860757 -1.860761 0.560145
- 1960-09-30 2.70 2839.022 5.6 0.119827 -1.265934 -1.063512
译注4:由于作者在此处并未介绍ldata的生成代码,而后面又需要用到,所以不能独立看待这段代码。下载的资料采用的不是这个格式,需要处理一下才可用。如果不会处理或觉得太麻烦,就用Excel编辑一下吧。不过还是建议处理一下,就当做练手了。给个相对比较简单的小提示:先加载进来,然后stack,然后保存,然后再加载进来。