【Chapter 8.3】重塑和軸向旋轉(zhuǎn)

8.3 重塑和軸向旋轉(zhuǎn)

有許多用于重新排列表格型數(shù)據(jù)的基礎(chǔ)運(yùn)算捕儒。這些函數(shù)也稱(chēng)作重塑(reshape)或軸向旋轉(zhuǎn)(pivot)運(yùn)算隘庄。

重塑層次化索引

層次化索引為DataFrame數(shù)據(jù)的重排任務(wù)提供了一種具有良好一致性的方式。主要功能有二:

  • stack:將數(shù)據(jù)的列“旋轉(zhuǎn)”為行数冬。
  • unstack:將數(shù)據(jù)的行“旋轉(zhuǎn)”為列。

我將通過(guò)一系列的范例來(lái)講解這些操作。接下來(lái)看一個(gè)簡(jiǎn)單的DataFrame晶框,其中的行列索引均為字符串?dāng)?shù)組:

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

對(duì)該數(shù)據(jù)使用stack方法即可將列轉(zhuǎn)換為行,得到一個(gè)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

對(duì)于一個(gè)層次化索引的Series懂从,你可以用unstack將其重排為一個(gè)DataFrame:

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

默認(rèn)情況下授段,unstack操作的是最內(nèi)層(stack也是如此)。傳入分層級(jí)別的編號(hào)或名稱(chēng)即可對(duì)其它級(jí)別進(jìn)行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

如果不是所有的級(jí)別值都能在各分組中找到的話番甩,則unstack操作可能會(huì)引入缺失數(shù)據(jù):

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默認(rèn)會(huì)濾除缺失數(shù)據(jù)侵贵,因此該運(yùn)算是可逆的:

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

在對(duì)DataFrame進(jìn)行unstack操作時(shí),作為旋轉(zhuǎn)軸的級(jí)別將會(huì)成為結(jié)果中的最低級(jí)別:

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

當(dāng)調(diào)用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

將“長(zhǎng)格式”旋轉(zhuǎn)為“寬格式”

多個(gè)時(shí)間序列數(shù)據(jù)通常是以所謂的“長(zhǎng)格式”(long)或“堆疊格式”(stacked)存儲(chǔ)在數(shù)據(jù)庫(kù)和CSV中的模燥。我們先加載一些示例數(shù)據(jù),做一些時(shí)間序列規(guī)整和數(shù)據(jù)清洗:

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'})

這就是多個(gè)時(shí)間序列(或者其它帶有兩個(gè)或多個(gè)鍵的可觀察數(shù)據(jù)掩宜,這里蔫骂,我們的鍵是date和item)的長(zhǎng)格式。表中的每行代表一次觀察牺汤。

關(guān)系型數(shù)據(jù)庫(kù)(如MySQL)中的數(shù)據(jù)經(jīng)常都是這樣存儲(chǔ)的辽旋,因?yàn)楣潭軜?gòu)(即列名和數(shù)據(jù)類(lèi)型)有一個(gè)好處:隨著表中數(shù)據(jù)的添加,item列中的值的種類(lèi)能夠增加檐迟。在前面的例子中补胚,date和item通常就是主鍵(用關(guān)系型數(shù)據(jù)庫(kù)的說(shuō)法),不僅提供了關(guān)系完整性追迟,而且提供了更為簡(jiǎn)單的查詢(xún)支持溶其。有的情況下,使用這樣的數(shù)據(jù)會(huì)很麻煩敦间,你可能會(huì)更喜歡DataFrame瓶逃,不同的item值分別形成一列,date列中的時(shí)間戳則用作索引廓块。DataFrame的pivot方法完全可以實(shí)現(xiàn)這個(gè)轉(zhuǎn)換:

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]

前兩個(gè)傳遞的值分別用作行和列索引厢绝,最后一個(gè)可選值則是用于填充DataFrame的數(shù)據(jù)列。假設(shè)有兩個(gè)需要同時(shí)重塑的數(shù)據(jù)列:

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

如果忽略最后一個(gè)參數(shù)带猴,得到的DataFrame就會(huì)帶有層次化的列:

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其實(shí)就是用set_index創(chuàng)建層次化索引,再用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

將“寬格式”旋轉(zhuǎn)為“長(zhǎng)格式”

旋轉(zhuǎn)DataFrame的逆運(yùn)算是pandas.melt拴清。它不是將一列轉(zhuǎn)換到多個(gè)新的DataFrame靶病,而是合并多個(gè)列成為一個(gè)会通,產(chǎn)生一個(gè)比輸入長(zhǎng)的DataFrame÷χ埽看一個(gè)例子:

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列可能是分組指標(biāo)渴语,其它的列是數(shù)據(jù)值。當(dāng)使用pandas.melt昆咽,我們必須指明哪些列是分組指標(biāo)驾凶。下面使用key作為唯一的分組指標(biāo):

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,可以重塑回原來(lái)的樣子:

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

因?yàn)閜ivot的結(jié)果從列創(chuàng)建了一個(gè)索引掷酗,用作行標(biāo)簽调违,我們可以使用reset_index將數(shù)據(jù)移回列:

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也可以不用分組指標(biāo):

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 總結(jié)

現(xiàn)在你已經(jīng)掌握了pandas數(shù)據(jù)導(dǎo)入泻轰、清洗技肩、重塑,我們可以進(jìn)一步學(xué)習(xí)matplotlib數(shù)據(jù)可視化浮声。我們?cè)谏院髸?huì)回到pandas虚婿,學(xué)習(xí)更高級(jí)的分析。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泳挥,一起剝皮案震驚了整個(gè)濱河市然痊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屉符,老刑警劉巖剧浸,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異矗钟,居然都是意外死亡唆香,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)吨艇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)躬它,“玉大人,你說(shuō)我怎么就攤上這事东涡》胂牛” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵软啼,是天一觀的道長(zhǎng)桑谍。 經(jīng)常有香客問(wèn)我延柠,道長(zhǎng)祸挪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任贞间,我火速辦了婚禮贿条,結(jié)果婚禮上雹仿,老公的妹妹穿的比我還像新娘。我一直安慰自己整以,他們只是感情好胧辽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著公黑,像睡著了一般邑商。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凡蚜,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天人断,我揣著相機(jī)與錄音,去河邊找鬼朝蜘。 笑死恶迈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谱醇。 我是一名探鬼主播暇仲,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼副渴!你這毒婦竟也來(lái)了奈附?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤煮剧,失蹤者是張志新(化名)和其女友劉穎桅狠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體轿秧,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡中跌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了菇篡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漩符。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驱还,靈堂內(nèi)的尸體忽然破棺而出嗜暴,到底是詐尸還是另有隱情,我是刑警寧澤议蟆,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布闷沥,位于F島的核電站,受9級(jí)特大地震影響咐容,放射性物質(zhì)發(fā)生泄漏舆逃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望路狮。 院中可真熱鬧虫啥,春花似錦、人聲如沸奄妨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)砸抛。三九已至评雌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間直焙,已是汗流浹背柳骄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留箕般,地道東北人耐薯。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像丝里,于是被迫代替她去往敵國(guó)和親曲初。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容