頑張らないために頑張る

ゆるく頑張ります

DataFrameの行と列を入れ替える

Posted at — Aug 27, 2022

概要

pandasのDataFrameにおいて、行と列は交互に入れ替えることが可能です。ここでは入れ替える方法についていくつか紹介します。

行と列を単純に入れ替える

DataFrameの行と列を単純に入れ替える場合、T属性かtranspose()メソッドを使います。

>>> import pandas
>>> import io
>>> s = '''a,b,c
... hoge,1,2
... fuga,3,4
... hoge,5,6
... hoge,7,8
... piyo,9,10
... fuga,11,12
... piyo,3,6
... hoge,7,12
... fuga,1,8
... piyo,1,2
... '''
>>> df = pandas.read_csv(io.StringIO(s))
>>> df
      a   b   c
0  hoge   1   2
1  fuga   3   4
2  hoge   5   6
3  hoge   7   8
4  piyo   9  10
5  fuga  11  12
6  piyo   3   6
7  hoge   7  12
8  fuga   1   8
9  piyo   1   2
>>> df.T
      0     1     2     3     4     5     6     7     8     9
a  hoge  fuga  hoge  hoge  piyo  fuga  piyo  hoge  fuga  piyo
b     1     3     5     7     9    11     3     7     1     1
c     2     4     6     8    10    12     6    12     8     2
>>> df.transpose()
      0     1     2     3     4     5     6     7     8     9
a  hoge  fuga  hoge  hoge  piyo  fuga  piyo  hoge  fuga  piyo
b     1     3     5     7     9    11     3     7     1     1
c     2     4     6     8    10    12     6    12     8     2

T属性による処理もtranspose()メソッドによる処理も結果は一緒です。また、どちらも新しいオブジェクトを返し、元のDataFrameは変更しません。行と列を単純に入れ替えるだけの処理なので、集計など入れ替え以外の処理はできません。必要であれば、入れ替え後のオブジェクトを用いて集計などを行うことになります。

横に長いテーブルを縦に長いテーブルに変換する

「横に長いテーブル(ワイドフォーマット)」についてある列に注目し「縦に長いテーブル(ロングフォーマット)」に変換する場合は、単純に行と列を入れ替えるだけのT属性などでは対応できません。こういう場合は、melt()メソッドかstack()メソッドを利用します。

>>> import pandas as pd
>>> import io
>>> s = '''id,name,category,2022-01,2022-02,2022-03,2022-04,2022-05,2022-06,2022-07,2022-08,2022-09,2022-10,2022-11,2022-12,2023-01
... 1,hoge,foo,10,11,12,13,14,15,16,17,18,19,120,121,122
... 2,fuga,foo,20,21,22,23,24,25,26,27,28,29,220,221,222
... 3,hoge,baz,30,31,32,33,34,35,36,37,38,39,320,321,322
... 4,piyo,bar,40,41,42,43,44,45,46,47,48,49,420,421,422
... 5,hoge,bar,50,51,52,53,55,55,56,57,58,59,520,521,522
... 6,piyo,baz,60,61,62,63,66,66,66,67,68,69,620,621,622
... '''
>>> df = pd.read_csv(io.StringIO(s))
>>> df
   id  name category  2022-01  2022-02  2022-03  2022-04  2022-05  2022-06  2022-07  2022-08  2022-09  2022-10  2022-11  2022-12  2023-01
0   1  hoge      foo       10       11       12       13       14       15       16       17       18       19      120      121      122
1   2  fuga      foo       20       21       22       23       24       25       26       27       28       29      220      221      222
2   3  hoge      baz       30       31       32       33       34       35       36       37       38       39      320      321      322
3   4  piyo      bar       40       41       42       43       44       45       46       47       48       49      420      421      422
4   5  hoge      bar       50       51       52       53       55       55       56       57       58       59      520      521      522
5   6  piyo      baz       60       61       62       63       66       66       66       67       68       69      620      621      622
>>> df.melt()
   variable value
0        id     1
1        id     2
2        id     3
3        id     4
4        id     5
..      ...   ...
91  2023-01   222
92  2023-01   322
93  2023-01   422
94  2023-01   522
95  2023-01   622

[96 rows x 2 columns]
>>> df.drop('id', axis=1).melt(id_vars='name')
    name  variable value
0   hoge  category   foo
1   fuga  category   foo
2   hoge  category   baz
3   piyo  category   bar
4   hoge  category   bar
..   ...       ...   ...
79  fuga   2023-01   222
80  hoge   2023-01   322
81  piyo   2023-01   422
82  hoge   2023-01   522
83  piyo   2023-01   622

[84 rows x 3 columns]
>>> df.drop('id', axis=1).melt(id_vars=['name', 'category'])
    name category variable  value
0   hoge      foo  2022-01     10
1   fuga      foo  2022-01     20
2   hoge      baz  2022-01     30
3   piyo      bar  2022-01     40
4   hoge      bar  2022-01     50
..   ...      ...      ...    ...
73  fuga      foo  2023-01    222
74  hoge      baz  2023-01    322
75  piyo      bar  2023-01    422
76  hoge      bar  2023-01    522
77  piyo      baz  2023-01    622

[78 rows x 4 columns]

もともと横に長かった変換元のテーブルについて、melt()を実行すると縦に長いテーブルへ変換できます。

T属性などと異なる点は、列名がそのまま行名にならずある項目の1データに変換されることです。ここではvariableという項目列がそれにあたり、この行にもともとの列名が格納されます。そして、valueという項目列がもともとの実データを持つ構造になっています。つまり、変換後のテーブルが持つ列名および行名は変換元のDataFrameには存在しないものです。

stack()も基本的にな動作は同じで、ワイドなテーブルをロングなテーブルに変換します。

>>> df.stack()
0  id             1
   name        hoge
   category     foo
   2022-01       10
   2022-02       11
               ... 
5  2022-09       68
   2022-10       69
   2022-11      620
   2022-12      621
   2023-01      622
Length: 96, dtype: object

ただ、stack()の変換結果は縦に長いテーブルへ変換されているものの、melt()とは実行結果が異なっています。

>>> type(df)
<class 'pandas.core.frame.DataFrame'>
>>> type(df.melt())
<class 'pandas.core.frame.DataFrame'>
>>> type(df.stack())
<class 'pandas.core.series.Series'>

stack()melt()と異なる最大の点は、返ってくる値の型が異なることです。

melt()はDataFrameが返ってきますが、stack()Seriesが返ってきます。stack()の結果は、見た目にはなんとなくDataFrameとして複数の列が存在しているように見えますが、実際は1列のSeriesでマルチインデックスになっています。注意!

縦に長いテーブルを横に長いテーブルに変換する

先ほどとは逆で、「縦に長いテーブル(ロングフォーマット)」についてある行に注目し「横に長いテーブル(ワイドフォーマット)」に変換する場合は、pivot()メソッドかunstack()メソッドを利用します。

なお、melt()pivot()は相互関係にあるため、melt()で変換したオブジェクトに対しpivot()を実行することで、変換前の状態に戻すことが可能です。ただ、厳密にはpivot()の処理直後はオプションの指定によりマルチインデックスになるなど、脳死でmelt()実行前の状態に戻せるわけではないので注意。マルチインデックスを解消するなどの対処を実行してやれば、変換前のDataFrameの状態に戻せはしますが。

>>> df
   id  name category  2022-01  2022-02  2022-03  2022-04  2022-05  2022-06  2022-07  2022-08  2022-09  2022-10  2022-11  2022-12  2023-01
0   1  hoge      foo       10       11       12       13       14       15       16       17       18       19      120      121      122
1   2  fuga      foo       20       21       22       23       24       25       26       27       28       29      220      221      222
2   3  hoge      baz       30       31       32       33       34       35       36       37       38       39      320      321      322
3   4  piyo      bar       40       41       42       43       44       45       46       47       48       49      420      421      422
4   5  hoge      bar       50       51       52       53       55       55       56       57       58       59      520      521      522
5   6  piyo      baz       60       61       62       63       66       66       66       67       68       69      620      621      622
>>> df_melted = df.melt(id_vars=['id', 'name', 'category'])
>>> df_melted
    id  name category variable  value
0    1  hoge      foo  2022-01     10
1    2  fuga      foo  2022-01     20
2    3  hoge      baz  2022-01     30
3    4  piyo      bar  2022-01     40
4    5  hoge      bar  2022-01     50
..  ..   ...      ...      ...    ...
73   2  fuga      foo  2023-01    222
74   3  hoge      baz  2023-01    322
75   4  piyo      bar  2023-01    422
76   5  hoge      bar  2023-01    522
77   6  piyo      baz  2023-01    622

[78 rows x 5 columns]
>>> df_melted.pivot(index=['id', 'name', 'category'], columns='variable', values='value')
variable          2022-01  2022-02  2022-03  2022-04  2022-05  2022-06  2022-07  2022-08  2022-09  2022-10  2022-11  2022-12  2023-01
id name category                                                                                                                     
1  hoge foo            10       11       12       13       14       15       16       17       18       19      120      121      122
2  fuga foo            20       21       22       23       24       25       26       27       28       29      220      221      222
3  hoge baz            30       31       32       33       34       35       36       37       38       39      320      321      322
4  piyo bar            40       41       42       43       44       45       46       47       48       49      420      421      422
5  hoge bar            50       51       52       53       55       55       56       57       58       59      520      521      522
6  piyo baz            60       61       62       63       66       66       66       67       68       69      620      621      622

もともとのDataFrameから1回melt()した結果に対し、pivot()を実行してみます。その結果は上記のとおりで、indexオプションで指定した列についてはマルチインデックスとなるため、厳密にmelt()による変換前の状態に戻ったわけではない点に注意です。

>>> df
   id  name category  2022-01  2022-02  2022-03  2022-04  2022-05  2022-06  2022-07  2022-08  2022-09  2022-10  2022-11  2022-12  2023-01
0   1  hoge      foo       10       11       12       13       14       15       16       17       18       19      120      121      122
1   2  fuga      foo       20       21       22       23       24       25       26       27       28       29      220      221      222
2   3  hoge      baz       30       31       32       33       34       35       36       37       38       39      320      321      322
3   4  piyo      bar       40       41       42       43       44       45       46       47       48       49      420      421      422
4   5  hoge      bar       50       51       52       53       55       55       56       57       58       59      520      521      522
5   6  piyo      baz       60       61       62       63       66       66       66       67       68       69      620      621      622
>>> df.melt()
   variable value
0        id     1
1        id     2
2        id     3
3        id     4
4        id     5
..      ...   ...
91  2023-01   222
92  2023-01   322
93  2023-01   422
94  2023-01   522
95  2023-01   622

[96 rows x 2 columns]
>>> df.melt().pivot(columns='variable', values='value')
variable 2022-01 2022-02 2022-03 2022-04 2022-05 2022-06 2022-07 2022-08 2022-09 2022-10 2022-11 2022-12 2023-01 category   id name
0            NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN      NaN    1  NaN
1            NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN      NaN    2  NaN
2            NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN      NaN    3  NaN
3            NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN      NaN    4  NaN
4            NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN      NaN    5  NaN
..           ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...      ...  ...  ...
91           NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     222      NaN  NaN  NaN
92           NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     322      NaN  NaN  NaN
93           NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     422      NaN  NaN  NaN
94           NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     522      NaN  NaN  NaN
95           NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     NaN     622      NaN  NaN  NaN

[96 rows x 16 columns]

ただし、melt()を実行する際にまったくオプションを指定していなかった場合、pivot()での再現ができませんでした。実際にpivot()を実行すると、上記のように欠損値の多いオブジェクトに変換されてしまいます。melt()を実行した時点で列がvariablevalueの2つだけになっており、変換元の構造をほぼ失っているせいだと思われるため、こうなるとindexオプションによる指定も不可でmelt()による変換前の状態再現が難しそうです。自分は今の所再現方法がわかりません・・・。

>>> df
   id  name category  2022-01  2022-02  2022-03  2022-04  2022-05  2022-06  2022-07  2022-08  2022-09  2022-10  2022-11  2022-12  2023-01
0   1  hoge      foo       10       11       12       13       14       15       16       17       18       19      120      121      122
1   2  fuga      foo       20       21       22       23       24       25       26       27       28       29      220      221      222
2   3  hoge      baz       30       31       32       33       34       35       36       37       38       39      320      321      322
3   4  piyo      bar       40       41       42       43       44       45       46       47       48       49      420      421      422
4   5  hoge      bar       50       51       52       53       55       55       56       57       58       59      520      521      522
5   6  piyo      baz       60       61       62       63       66       66       66       67       68       69      620      621      622
>>> df.stack().unstack()
  id  name category 2022-01 2022-02 2022-03 2022-04 2022-05 2022-06 2022-07 2022-08 2022-09 2022-10 2022-11 2022-12 2023-01
0  1  hoge      foo      10      11      12      13      14      15      16      17      18      19     120     121     122
1  2  fuga      foo      20      21      22      23      24      25      26      27      28      29     220     221     222
2  3  hoge      baz      30      31      32      33      34      35      36      37      38      39     320     321     322
3  4  piyo      bar      40      41      42      43      44      45      46      47      48      49     420     421     422
4  5  hoge      bar      50      51      52      53      55      55      56      57      58      59     520     521     522
5  6  piyo      baz      60      61      62      63      66      66      66      67      68      69     620     621     622

unstack()は、stack()処理後のようなSeriesについて実行可能で、横に長いテーブルへ変換します。pivot()による変換とは異なり、unstack()stack()を処理する前の状態に戻すには、上記のように脳死で実行可能です。

>>> type(df.melt().pivot(columns='variable', values='value'))
<class 'pandas.core.frame.DataFrame'>
>>> type(df.stack().unstack())
<class 'pandas.core.frame.DataFrame'>

なおmelt()stack()の場合とは異なり、pivot()unstack()も返すオブジェクトはどちらもDataFrameです。

まとめ

「行と列を入れ替える」という作業はあまり頻繁に行うものでもないと思います。いざ必要になったときには思い出せるよう、頭の片隅にでも入れておきたいと思います。

それはそうと、melt()のオプションなしはどうやってpivot()で戻すのよ・・・?_(┐「ε:)_

reference

  1. pandas.DataFrameの行と列を入れ替える(転置)
  2. 便利だけど分かりにくいデータフレームを再構築するPandasのMelt()関数のお話し
  3. pandasでstack, unstack, pivotを使ってデータを整形
comments powered by Disqus