- 8.3 重塑和轴向旋转
- 重塑层次化索引
- 将“长格式”旋转为“宽格式”
- 将“宽格式”旋转为“长格式”
8.3 重塑和轴向旋转
有许多用于重新排列表格型数据的基础运算。这些函数也称作重塑(reshape)或轴向旋转(pivot)运算。
重塑层次化索引
层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二:
- stack:将数据的列“旋转”为行。
- unstack:将数据的行“旋转”为列。
我将通过一系列的范例来讲解这些操作。接下来看一个简单的DataFrame,其中的行列索引均为字符串数组:
In [120]: data = pd.DataFrame(np.arange(6).reshape((2, 3)),.....: index=pd.Index(['Ohio','Colorado'], name='state'),.....: columns=pd.Index(['one', 'two', 'three'],.....: name='number'))In [121]: dataOut[121]:number one two threestateOhio 0 1 2Colorado 3 4 5
对该数据使用stack方法即可将列转换为行,得到一个Series:
In [122]: result = data.stack()In [123]: resultOut[123]:state numberOhio one 0two 1three 2Colorado one 3two 4three 5dtype: int64
对于一个层次化索引的Series,你可以用unstack将其重排为一个DataFrame:
In [124]: result.unstack()Out[124]:number one two threestateOhio 0 1 2Colorado 3 4 5
默认情况下,unstack操作的是最内层(stack也是如此)。传入分层级别的编号或名称即可对其它级别进行unstack操作:
In [125]: result.unstack(0)Out[125]:state Ohio Coloradonumberone 0 3two 1 4three 2 5In [126]: result.unstack('state')Out[126]:state Ohio Coloradonumberone 0 3two 1 4three 2 5
如果不是所有的级别值都能在各分组中找到的话,则unstack操作可能会引入缺失数据:
In [127]: s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])In [128]: s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])In [129]: data2 = pd.concat([s1, s2], keys=['one', 'two'])In [130]: data2Out[130]:one a 0b 1c 2d 3two c 4d 5e 6dtype: int64In [131]: data2.unstack()Out[131]:a b c d eone 0.0 1.0 2.0 3.0 NaNtwo NaN NaN 4.0 5.0 6.0
stack默认会滤除缺失数据,因此该运算是可逆的:
In [132]: data2.unstack()Out[132]:a b c d eone 0.0 1.0 2.0 3.0 NaNtwo NaN NaN 4.0 5.0 6.0In [133]: data2.unstack().stack()Out[133]:one a 0.0b 1.0c 2.0d 3.0two c 4.0d 5.0e 6.0dtype: float64In [134]: data2.unstack().stack(dropna=False)Out[134]:one a 0.0b 1.0c 2.0d 3.0e NaNtwo a NaNb NaNc 4.0d 5.0e 6.0dtype: float64
在对DataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别:
In [135]: df = pd.DataFrame({'left': result, 'right': result + 5},.....: columns=pd.Index(['left', 'right'], name='side'))In [136]: dfOut[136]:side left rightstate numberOhio one 0 5two 1 6three 2 7Colorado one 3 8two 4 9three 5 10In [137]: df.unstack('state')Out[137]:side left rightstate Ohio Colorado Ohio Coloradonumberone 0 3 5 8two 1 4 6 9three 2 5 7 10
当调用stack,我们可以指明轴的名字:
In [138]: df.unstack('state').stack('side')Out[138]:state Colorado Ohionumber sideone left 3 0right 8 5two left 4 1right 9 6three left 5 2right 10 7
将“长格式”旋转为“宽格式”
多个时间序列数据通常是以所谓的“长格式”(long)或“堆叠格式”(stacked)存储在数据库和CSV中的。我们先加载一些示例数据,做一些时间序列规整和数据清洗:
In [139]: data = pd.read_csv('examples/macrodata.csv')In [140]: data.head()Out[140]:year quarter realgdp realcons realinv realgovt realdpi cpi \0 1959.0 1.0 2710.349 1707.4 286.898 470.045 1886.9 28.981 1959.0 2.0 2778.801 1733.7 310.859 481.301 1919.7 29.152 1959.0 3.0 2775.488 1751.8 289.226 491.260 1916.4 29.353 1959.0 4.0 2785.204 1753.7 299.356 484.052 1931.3 29.374 1960.0 1.0 2847.699 1770.5 331.722 462.199 1955.5 29.54m1 tbilrate unemp pop infl realint0 139.7 2.82 5.8 177.146 0.00 0.001 141.7 3.08 5.1 177.830 2.34 0.742 140.5 3.82 5.3 178.657 2.74 1.093 140.0 4.33 5.6 179.386 0.27 4.064 139.6 3.50 5.2 180.007 2.31 1.19In [141]: periods = pd.PeriodIndex(year=data.year, quarter=data.quarter,.....: name='date')In [142]: columns = pd.Index(['realgdp', 'infl', 'unemp'], name='item')In [143]: data = data.reindex(columns=columns)In [144]: data.index = periods.to_timestamp('D', 'end')In [145]: ldata = data.stack().reset_index().rename(columns={0: 'value'})
这就是多个时间序列(或者其它带有两个或多个键的可观察数据,这里,我们的键是date和item)的长格式。表中的每行代表一次观察。
关系型数据库(如MySQL)中的数据经常都是这样存储的,因为固定架构(即列名和数据类型)有一个好处:随着表中数据的添加,item列中的值的种类能够增加。在前面的例子中,date和item通常就是主键(用关系型数据库的说法),不仅提供了关系完整性,而且提供了更为简单的查询支持。有的情况下,使用这样的数据会很麻烦,你可能会更喜欢DataFrame,不同的item值分别形成一列,date列中的时间戳则用作索引。DataFrame的pivot方法完全可以实现这个转换:
In [147]: pivoted = ldata.pivot('date', 'item', 'value')In [148]: pivotedOut[148]:item infl realgdp unempdate1959-03-31 0.00 2710.349 5.81959-06-30 2.34 2778.801 5.11959-09-30 2.74 2775.488 5.31959-12-31 0.27 2785.204 5.61960-03-31 2.31 2847.699 5.21960-06-30 0.14 2834.390 5.21960-09-30 2.70 2839.022 5.61960-12-31 1.21 2802.616 6.31961-03-31 -0.40 2819.264 6.81961-06-30 1.47 2872.005 7.0... ... ... ...2007-06-30 2.75 13203.977 4.52007-09-30 3.45 13321.109 4.72007-12-31 6.38 13391.249 4.82008-03-31 2.82 13366.865 4.92008-06-30 8.53 13415.266 5.42008-09-30 -3.16 13324.600 6.02008-12-31 -8.79 13141.920 6.92009-03-31 0.94 12925.410 8.12009-06-30 3.37 12901.504 9.22009-09-30 3.56 12990.341 9.6[203 rows x 3 columns]
前两个传递的值分别用作行和列索引,最后一个可选值则是用于填充DataFrame的数据列。假设有两个需要同时重塑的数据列:
In [149]: ldata['value2'] = np.random.randn(len(ldata))In [150]: ldata[:10]Out[150]:date item value value20 1959-03-31 realgdp 2710.349 0.5237721 1959-03-31 infl 0.000 0.0009402 1959-03-31 unemp 5.800 1.3438103 1959-06-30 realgdp 2778.801 -0.7135444 1959-06-30 infl 2.340 -0.8311545 1959-06-30 unemp 5.100 -2.3702326 1959-09-30 realgdp 2775.488 -1.8607617 1959-09-30 infl 2.740 -0.8607578 1959-09-30 unemp 5.300 0.5601459 1959-12-31 realgdp 2785.204 -1.265934
如果忽略最后一个参数,得到的DataFrame就会带有层次化的列:
In [151]: pivoted = ldata.pivot('date', 'item')In [152]: pivoted[:5]Out[152]:value value2item infl realgdp unemp infl realgdp unempdate1959-03-31 0.00 2710.349 5.8 0.000940 0.523772 1.3438101959-06-30 2.34 2778.801 5.1 -0.831154 -0.713544 -2.3702321959-09-30 2.74 2775.488 5.3 -0.860757 -1.860761 0.5601451959-12-31 0.27 2785.204 5.6 0.119827 -1.265934 -1.0635121960-03-31 2.31 2847.699 5.2 -2.359419 0.332883 -0.199543In [153]: pivoted['value'][:5]Out[153]:item infl realgdp unempdate1959-03-31 0.00 2710.349 5.81959-06-30 2.34 2778.801 5.11959-09-30 2.74 2775.488 5.31959-12-31 0.27 2785.204 5.61960-03-31 2.31 2847.699 5.2
注意,pivot其实就是用set_index创建层次化索引,再用unstack重塑:
In [154]: unstacked = ldata.set_index(['date', 'item']).unstack('item')In [155]: unstacked[:7]Out[155]:value value2item infl realgdp unemp infl realgdp unempdate1959-03-31 0.00 2710.349 5.8 0.000940 0.523772 1.3438101959-06-30 2.34 2778.801 5.1 -0.831154 -0.713544 -2.3702321959-09-30 2.74 2775.488 5.3 -0.860757 -1.860761 0.5601451959-12-31 0.27 2785.204 5.6 0.119827 -1.265934 -1.0635121960-03-31 2.31 2847.699 5.2 -2.359419 0.332883 -0.1995431960-06-30 0.14 2834.390 5.2 -0.970736 -1.541996 -1.3070301960-09-30 2.70 2839.022 5.6 0.377984 0.286350 -0.753887
将“宽格式”旋转为“长格式”
旋转DataFrame的逆运算是pandas.melt。它不是将一列转换到多个新的DataFrame,而是合并多个列成为一个,产生一个比输入长的DataFrame。看一个例子:
In [157]: df = pd.DataFrame({'key': ['foo', 'bar', 'baz'],.....: 'A': [1, 2, 3],.....: 'B': [4, 5, 6],.....: 'C': [7, 8, 9]})In [158]: dfOut[158]:A B C key0 1 4 7 foo1 2 5 8 bar2 3 6 9 baz
key列可能是分组指标,其它的列是数据值。当使用pandas.melt,我们必须指明哪些列是分组指标。下面使用key作为唯一的分组指标:
In [159]: melted = pd.melt(df, ['key'])In [160]: meltedOut[160]:key variable value0 foo A 11 bar A 22 baz A 33 foo B 44 bar B 55 baz B 66 foo C 77 bar C 88 baz C 9
使用pivot,可以重塑回原来的样子:
In [161]: reshaped = melted.pivot('key', 'variable', 'value')In [162]: reshapedOut[162]:variable A B Ckeybar 2 5 8baz 3 6 9foo 1 4 7
因为pivot的结果从列创建了一个索引,用作行标签,我们可以使用reset_index将数据移回列:
In [163]: reshaped.reset_index()Out[163]:variable key A B C0 bar 2 5 81 baz 3 6 92 foo 1 4 7
你还可以指定列的子集,作为值的列:
In [164]: pd.melt(df, id_vars=['key'], value_vars=['A', 'B'])Out[164]:key variable value0 foo A 11 bar A 22 baz A 33 foo B 44 bar B 55 baz B 6
pandas.melt也可以不用分组指标:
In [165]: pd.melt(df, value_vars=['A', 'B', 'C'])Out[165]:variable value0 A 11 A 22 A 33 B 44 B 55 B 66 C 77 C 88 C 9In [166]: pd.melt(df, value_vars=['key', 'A', 'B'])Out[166]:variable value0 key foo1 key bar2 key baz3 A 14 A 25 A 36 B 47 B 58 B 6
