美文网首页我爱编程
【Chapter 8.3】重塑和轴向旋转

【Chapter 8.3】重塑和轴向旋转

作者: 蜘蛛的梦呓 | 来源:发表于2018-05-26 21:07 被阅读0次

    8.3 重塑和轴向旋转

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

    重塑层次化索引

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

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

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

    In [120]: data = pd.DataFrame(np.arange(6).reshape((2, 3)),
       .....:                     index=pd.Index(['Ohio','Colorado'], ame='state'),
       .....:                     columns=pd.Index(['one', 'two', 'three'],
       .....:                     name='number'))
    
    In [121]: data
    Out[121]: 
    number    one  two  three
    state                    
    Ohio        0    1      2
    Colorado    3    4      5
    

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

    In [122]: result = data.stack()
    
    In [123]: result
    Out[123]: 
    state     number
    Ohio      one       0
              two       1
              three     2
    Colorado  one       3
              two       4
              three     5
    dtype: int64
    

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

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

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

    In [125]: result.unstack(0)
    Out[125]: 
    state   Ohio  Colorado
    number                
    one        0         3
    two        1         4
    three      2         5
    
    In [126]: result.unstack('state')
    Out[126]: 
    state   Ohio  Colorado
    number                
    one        0         3
    two        1         4
    three      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]: data2
    Out[130]: 
    one  a    0
         b    1
         c    2
         d    3
    two  c    4
         d    5
         e    6
    dtype: int64
    
    In [131]: data2.unstack()
    Out[131]: 
           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默认会滤除缺失数据,因此该运算是可逆的:

    In [132]: data2.unstack()
    Out[132]: 
           a    b    c    d    e
    one  0.0  1.0  2.0  3.0  NaN
    two  NaN  NaN  4.0  5.0  6.0
    
    In [133]: data2.unstack().stack()
    Out[133]: 
    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
    
    In [134]: data2.unstack().stack(dropna=False)
    Out[134]: 
    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操作时,作为旋转轴的级别将会成为结果中的最低级别:

    In [135]: df = pd.DataFrame({'left': result, 'right': result + 5},
       .....:                   columns=pd.Index(['left', 'right'], name='side'))
    
    In [136]: df
    Out[136]: 
    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 [137]: df.unstack('state')
    Out[137]: 
    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,我们可以指明轴的名字:

    In [138]: df.unstack('state').stack('side')
    Out[138]: 
    state         Colorado  Ohio
    number side                 
    one    left          3     0
           right         8     5
    two    left          4     1
           right         9     6
    three  left          5     2
           right        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.98   
    1  1959.0      2.0  2778.801    1733.7  310.859   481.301   1919.7  29.15   
    2  1959.0      3.0  2775.488    1751.8  289.226   491.260   1916.4  29.35   
    3  1959.0      4.0  2785.204    1753.7  299.356   484.052   1931.3  29.37   
    4  1960.0      1.0  2847.699    1770.5  331.722   462.199   1955.5  29.54   
          m1  tbilrate  unemp      pop  infl  realint  
    0  139.7      2.82    5.8  177.146  0.00     0.00
    1  141.7      3.08    5.1  177.830  2.34     0.74  
    2  140.5      3.82    5.3  178.657  2.74     1.09  
    3  140.0      4.33    5.6  179.386  0.27     4.06  
    4  139.6      3.50    5.2  180.007  2.31     1.19  
    
    In [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]: pivoted
    Out[148]: 
    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
    ...          ...        ...    ...
    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 x 3 columns]
    

    前两个传递的值分别用作行和列索引,最后一个可选值则是用于填充DataFrame的数据列。假设有两个需要同时重塑的数据列:

    In [149]: ldata['value2'] = np.random.randn(len(ldata))
    
    In [150]: ldata[:10]
    Out[150]: 
            date     item     value    value2
    0 1959-03-31  realgdp  2710.349  0.523772
    1 1959-03-31     infl     0.000  0.000940
    2 1959-03-31    unemp     5.800  1.343810
    3 1959-06-30  realgdp  2778.801 -0.713544
    4 1959-06-30     infl     2.340 -0.831154
    5 1959-06-30    unemp     5.100 -2.370232
    6 1959-09-30  realgdp  2775.488 -1.860761
    7 1959-09-30     infl     2.740 -0.860757
    8 1959-09-30    unemp     5.300  0.560145
    9 1959-12-31  realgdp  2785.204 -1.265934
    

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

    In [151]: pivoted = ldata.pivot('date', 'item')
    
    In [152]: pivoted[:5]
    Out[152]: 
               value                    value2                    
    item        infl   realgdp unemp      infl   realgdp     unemp
    date                                                          
    1959-03-31  0.00  2710.349   5.8  0.000940  0.523772  1.343810
    1959-06-30  2.34  2778.801   5.1 -0.831154 -0.713544 -2.370232
    1959-09-30  2.74  2775.488   5.3 -0.860757 -1.860761  0.560145
    1959-12-31  0.27  2785.204   5.6  0.119827 -1.265934 -1.063512
    1960-03-31  2.31  2847.699   5.2 -2.359419  0.332883 -0.199543
    
    In [153]: pivoted['value'][:5]
    Out[153]: 
    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 [154]: unstacked = ldata.set_index(['date', 'item']).unstack('item')
    
    In [155]: unstacked[:7]
    Out[155]: 
               value                    value2                    
    item        infl   realgdp unemp      infl   realgdp     unemp
    date                                                          
    1959-03-31  0.00  2710.349   5.8  0.000940  0.523772  1.343810
    1959-06-30  2.34  2778.801   5.1 -0.831154 -0.713544 -2.370232
    1959-09-30  2.74  2775.488   5.3 -0.860757 -1.860761  0.560145
    1959-12-31  0.27  2785.204   5.6  0.119827 -1.265934 -1.063512
    1960-03-31  2.31  2847.699   5.2 -2.359419  0.332883 -0.199543
    1960-06-30  0.14  2834.390   5.2 -0.970736 -1.541996 -1.307030
    1960-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]: df
    Out[158]: 
       A  B  C  key
    0  1  4  7  foo
    1  2  5  8  bar
    2  3  6  9  baz
    

    key列可能是分组指标,其它的列是数据值。当使用pandas.melt,我们必须指明哪些列是分组指标。下面使用key作为唯一的分组指标:

    In [159]: melted = pd.melt(df, ['key'])
    
    In [160]: melted
    Out[160]: 
       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,可以重塑回原来的样子:

    In [161]: reshaped = melted.pivot('key', 'variable', 'value')
    
    In [162]: reshaped
    Out[162]: 
    variable  A  B  C
    key              
    bar       2  5  8
    baz       3  6  9
    foo       1  4  7
    

    因为pivot的结果从列创建了一个索引,用作行标签,我们可以使用reset_index将数据移回列:

    In [163]: reshaped.reset_index()
    Out[163]: 
    variable  key  A  B  C
    0         bar  2  5  8
    1         baz  3  6  9
    2         foo  1  4  7
    

    你还可以指定列的子集,作为值的列:

    In [164]: pd.melt(df, id_vars=['key'], value_vars=['A', 'B'])
    Out[164]: 
       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也可以不用分组指标:

    In [165]: pd.melt(df, value_vars=['A', 'B', 'C'])
    Out[165]: 
      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
    
    In [166]: pd.melt(df, value_vars=['key', 'A', 'B'])
    Out[166]: 
      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
    

    8.4 总结

    现在你已经掌握了pandas数据导入、清洗、重塑,我们可以进一步学习matplotlib数据可视化。我们在稍后会回到pandas,学习更高级的分析。

    相关文章

      网友评论

        本文标题:【Chapter 8.3】重塑和轴向旋转

        本文链接:https://www.haomeiwen.com/subject/wyhvjftx.html