重塑和轴向旋转

有许多用于重新排列表格型数据的基础运算。这些函数也称作重塑(reshape)或轴向旋转(pivot)运算。

重塑层次化索引

层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二:

·stack:将数据的列“旋转”为行。

·unstack:将数据的行“旋转”为列。

我将通过一系列的范例来讲解这些操作。接下来看一个简单的DataFrame,其中的行列索引均为字符串:

  1. In [94]: data = DataFrame(np.arange(6).reshape((2, 3)),
  2. ...: index=pd.Index(['Ohio', 'Colorado'], name='state'),
  3. ...: columns=pd.Index(['one', 'two', 'three'], name='number'))
  4.  
  5. In [95]: data
  6. Out[95]:
  7. number one two three
  8. state
  9. Ohio 0 1 2
  10. Colorado 3 4 5

使用该数据的stack方法即可将列转换为行,得到一个Series:

  1. In [96]: result = data.stack()
  2.  
  3. In [97]: result
  4. Out[97]:
  5. state number
  6. Ohio one 0
  7. two 1
  8. three 2
  9. Colorado one 3
  10. two 4
  11. three 5

对于一个层次化索引的Series,你可以用unstack将其重排为一个DataFrame:

  1. In [98]: result.unstack()
  2. Out[98]:
  3. number one two three
  4. state
  5. Ohio 0 1 2
  6. Colorado 3 4 5

默认情况下,unstack操作的是最内层(stack也是如此)。传入分层级别的编号或名称即可对其他级别进行unstack操作:

  1. In [99]: result.unstack(0) In [100]: result.unstack('state')
  2. Out[99]: Out[100]:
  3. state Ohio Colorado state Ohio Colorado
  4. number number
  5. one 0 3 one 0 3
  6. two 1 4 two 1 4
  7. three 2 5 three 2 5

如果不是所有的级别值都能在各分组中找到的话,则unstack操作可能会引入缺失数据:

  1. In [101]: s1 = Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
  2.  
  3. In [102]: s2 = Series([4, 5, 6], index=['c', 'd', 'e'])
  4.  
  5. In [103]: data2 = pd.concat([s1, s2], keys=['one', 'two'])
  6.  
  7. In [104]: data2.unstack()
  8. Out[104]:
  9. a b c d e
  10. one 0 1 2 3 NaN
  11. two NaN NaN 4 5 6

stack默认会滤除缺失数据,因此该运算是可逆的:

  1. In [105]: data2.unstack().stack() In [106]: data2.unstack().stack(dropna=False) Out[105]: Out[106]:
  2. one a 0 one a 0
  3. b 1 b 1
  4. c 2 c 2
  5. d 3 d 3
  6. two c 4 e NaN
  7. d 5 two a NaN
  8. e 6 b NaN
  9. c 4
  10. d 5
  11. e 6

在对DataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别:

  1. In [107]: df = DataFrame({'left': result, 'right': result + 5},
  2. ...: columns=pd.Index(['left', 'right'], name='side'))
  3.  
  4. In [108]: df
  5. Out[108]:
  6. side left right
  7. state number
  8. Ohio one 0 5
  9. two 1 6
  10. three 2 7
  11. Colorado one 3 8
  12. two 4 9
  13. three 5 10
  14.  
  15. In [109]: df.unstack('state') In [110]: df.unstack('state').stack('side')
  16. Out[109]: Out[110]:
  17. side left right state Ohio Colorado
  18. state Ohio Colorado Ohio Colorado number side
  19. number one left 0 3
  20. one 0 3 5 8 right 5 8
  21. two 1 4 6 9 two left 1 4
  22. three 2 5 7 10 right 6 9
  23. three left 2 5
  24. right 7 10

将“长格式”旋转为“宽格式”

时间序列数据通常是以所谓的“长格式”(long)或“堆叠格式”(stacked)存储在数据库和CSV中的:译注4

  1. In [116]: ldata[:10]
  2. Out[116]:
  3. date item value
  4. 0 1959-03-31 00:00:00 realgdp 2710.349
  5. 1 1959-03-31 00:00:00 infl 0.000
  6. 2 1959-03-31 00:00:00 unemp 5.800
  7. 3 1959-06-30 00:00:00 realgdp 2778.801
  8. 4 1959-06-30 00:00:00 infl 2.340
  9. 5 1959-06-30 00:00:00 unemp 5.100
  10. 6 1959-09-30 00:00:00 realgdp 2775.488
  11. 7 1959-09-30 00:00:00 infl 2.740
  12. 8 1959-09-30 00:00:00 unemp 5.300
  13. 9 1959-12-31 00:00:00 realgdp 2785.204

关系型数据库(如MySQL)中的数据经常都是这样存储的,因为固定架构(即列名和数据类型)有一个好处:随着表中数据的添加或删除,item列中的值的种类能够增加或减少。在上面那个例子中,date和item通常就是主键(用关系型数据库的说法),不仅提供了关系完整性,而且提供了更为简单的查询支持。当然这也是有缺点的:长格式的数据操作起来可能不那么轻松。你可能会更喜欢DataFrame,不同的item值分别形成一列,date列中的时间值则用作索引。DataFrame的pivot方法完全可以实现这个转换:

  1. In [117]: pivoted = ldata.pivot('date', 'item', 'value')
  2.  
  3. In [118]: pivoted.head()
  4. Out[118]:
  5. item infl realgdp unemp
  6. date
  7. 1959-03-31 0.00 2710.349 5.8
  8. 1959-06-30 2.34 2778.801 5.1
  9. 1959-09-30 2.74 2775.488 5.3
  10. 1959-12-31 0.27 2785.204 5.6
  11. 1960-03-31 2.31 2847.699 5.2

前两个参数值分别用作行和列索引的列名,最后一个参数值则是用于填充DataFrame的数据列的列名。假设有两个需要参与重塑的数据列:

  1. In [119]: ldata['value2'] = np.random.randn(len(ldata))
  2.  
  3. In [120]: ldata[:10]
  4. Out[120]:
  5. date item value value2
  6. 0 1959-03-31 00:00:00 realgdp 2710.349 1.669025
  7. 1 1959-03-31 00:00:00 infl 0.000 -0.438570
  8. 2 1959-03-31 00:00:00 unemp 5.800 -0.539741
  9. 3 1959-06-30 00:00:00 realgdp 2778.801 0.476985
  10. 4 1959-06-30 00:00:00 infl 2.340 3.248944
  11. 5 1959-06-30 00:00:00 unemp 5.100 -1.021228
  12. 6 1959-09-30 00:00:00 realgdp 2775.488 -0.577087
  13. 7 1959-09-30 00:00:00 infl 2.740 0.124121
  14. 8 1959-09-30 00:00:00 unemp 5.300 0.302614
  15. 9 1959-12-31 00:00:00 realgdp 2785.204 0.523772

如果忽略最后一个参数,得到的DataFrame就会带有层次化的列:

  1. In [121]: pivoted = ldata.pivot('date', 'item')
  2.  
  3. In [122]: pivoted[:5]
  4. Out[122]:
  5. value value2
  6. item infl realgdp unemp infl realgdp unemp
  7. date
  8. 1959-03-31 0.00 2710.349 5.8 -0.438570 1.669025 -0.539741
  9. 1959-06-30 2.34 2778.801 5.1 3.248944 0.476985 -1.021228
  10. 1959-09-30 2.74 2775.488 5.3 0.124121 -0.577087 0.302614
  11. 1959-12-31 0.27 2785.204 5.6 0.000940 0.523772 1.343810
  12. 1960-03-31 2.31 2847.699 5.2 -0.831154 -0.713544 -2.370232
  13.  
  14. In [123]: pivoted['value'][:5]
  15. Out[123]:
  16. item infl realgdp unemp
  17. date
  18. 1959-03-31 0.00 2710.349 5.8
  19. 1959-06-30 2.34 2778.801 5.1
  20. 1959-09-30 2.74 2775.488 5.3
  21. 1959-12-31 0.27 2785.204 5.6
  22. 1960-03-31 2.31 2847.699 5.2

注意,pivot其实只是一个快捷方式而已:用set_index创建层次化索引,再用unstack重塑。

  1. In [124]: unstacked = ldata.set_index(['date', 'item']).unstack('item')
  2.  
  3. In [125]: unstacked[:7]
  4. Out[125]:
  5. value value2
  6. item infl realgdp unemp infl realgdp unemp
  7. date
  8. 1959-03-31 0.00 2710.349 5.8 -0.438570 1.669025 -0.539741
  9. 1959-06-30 2.34 2778.801 5.1 3.248944 0.476985 -1.021228
  10. 1959-09-30 2.74 2775.488 5.3 0.124121 -0.577087 0.302614
  11. 1959-12-31 0.27 2785.204 5.6 0.000940 0.523772 1.343810
  12. 1960-03-31 2.31 2847.699 5.2 -0.831154 -0.713544 -2.370232
  13. 1960-06-30 0.14 2834.390 5.2 -0.860757 -1.860761 0.560145
  14. 1960-09-30 2.70 2839.022 5.6 0.119827 -1.265934 -1.063512

译注4:由于作者在此处并未介绍ldata的生成代码,而后面又需要用到,所以不能独立看待这段代码。下载的资料采用的不是这个格式,需要处理一下才可用。如果不会处理或觉得太麻烦,就用Excel编辑一下吧。不过还是建议处理一下,就当做练手了。给个相对比较简单的小提示:先加载进来,然后stack,然后保存,然后再加载进来。