Pandasでの行名・列名を再定義する操作を紹介します。PandasのDataFrameを自由に扱えるようになると、データ分析のしやすさが格段に変わるので、覚えておくと便利です。

そこで本記事では、

  • reindex関数の使い方

について解説します。

reindex関数はインデックスラベル、カラムラベルとデータの値との対応関係が崩れないようにしてそれぞれのラベルを更新する関数です。基本的には離散的な値に対してさらに細かい周期でのサンプリングデータとの整合性をとるために使われることが多いと思います。

Indexオブジェクトそのものの解説については以下の記事を参考にしてください。

PandasのIndexの理解と使い方まとめ /features/pandas-index.html

上記の記事でもreindex関数の使い方について触れていますが、この記事では一からその使い方について解説していきます。

reindex関数

APIドキュメント

reindex関数のAPIドキュメントは以下の通りです。

pandas.DataFrame.reindex(labels=None, index=None, columns=None, axis=None, method=None, copy=True, level=None, fill_value=nan, limit=None, tolerance=None)

params:

パラメータ名 概要
labels 配列に相当するオブジェクト (省略可能)初期値None
axis引数によって指定された軸(インデックスかカラム)の新しいラベルを指定します。
index,columns 配列に相当するオブジェクト (省略可能)初期値None
これらの引数は引数名を省略せず、明示した形で使う必要があります。それぞれの軸方向に対する新しいラベルを指定します。
axis (0,1)もしくは(‘index’,’columns’)
のいずれか
(省略可能)初期値None
どの軸ラベルを更新するかを指定します。
method None, ‘backfill’/’bfill’,’pad’/’ffill’,’nearest’のいずれか (省略可能)初期値None
値を補間するときに、どのような手法で埋め合わせをするかを指定します。Noneのときは値の補間を行いません。
copy bool値 (省略可能)初期値True
デフォルトでは、たとえ同じインデックスやカラムラベルを渡されても新しいオブジェクトを生成します。Falseにするとこれを止めることができます。
level int型もしくはIndexオブジェクトのname (省略可能)初期値None
MultiIndexに再ラベリングする際、どの階層のIndexオブジェクトに対してブロードキャストを行うかを指定します。
fill_value 値, np.NaN (省略可能)初期値np.NaN
欠損値に対してどの値で埋め合わせをするかを指定します。データ内で不整合が起きなければどんな値でも可能です。
limit int,None (省略可能)初期値None
欠損値補間を行うとき、連続して補間する値の上限を指定します。
tolerance ラベルと同じ型, None (省略可能)初期値None
limitの拡張版。各ラベルごとにlimitの値を指定できます。

returns:

軸ラベルが更新されたDataFrame

ここで1つ注目して欲しいのは、大まかにインデックスやカラムラベルを更新するやり方は2通り存在しています。

  • labelsで新しいラベルを、axisでカラムかインデックスラベルかを選択
  • index,columnsに個別に指定する

公式ドキュメントでは後者のやり方を推奨しています。こちらの方が直感的で可読性が高いと感じます。

サンプルコード

実際に手を動かして実践して見ましょう。先ほど取り上げた2つの手法を試して見ます。

まずはlabelsaxisを使ったやり方です。

In [1]: import pandas as pd

In [2]: import numpy as np

In [4]: a = pd.DataFrame(np.arange(20).reshape(4,5), columns=["a","b","d","e", "f"],index=[0, 2, 3, 5])

In [5]: a.reindex([0,1,2,3,4,5], axis='index') # axis=0 or 'index'でインデックスラベル更新
Out[5]:
      a     b     d     e     f
0   0.0   1.0   2.0   3.0   4.0
1   NaN   NaN   NaN   NaN   NaN
2   5.0   6.0   7.0   8.0   9.0
3  10.0  11.0  12.0  13.0  14.0
4   NaN   NaN   NaN   NaN   NaN
5  15.0  16.0  17.0  18.0  19.0

In [7]: a.reindex(["a","b","c","d","e","f"], axis=1) # axis=1 or 'columns'でカラムラベル更新
Out[7]:
    a   b   c   d   e   f
0   0   1 NaN   2   3   4
2   5   6 NaN   7   8   9
3  10  11 NaN  12  13  14
5  15  16 NaN  17  18  19

インデックス、カラムラベルとその値は1対1対応しているのでreindex関数を適用したあともそれは変わりません。むしろ、この対応関係を崩さないためにこの関数を使うと言っても過言ではないです。

新たなラベルに対してはデフォルトでNaN値が埋め合わされます。(Not a Numberの略です)

では、indexcolumns引数を使って同じ操作をして見ましょう。indexcolumns引数では、同時に両方のラベルを更新することも可能です。

In [8]: a.reindex(index=[0,1,2,3,4,5]) # インデックスラベルを変更
Out[8]:
      a     b     d     e     f
0   0.0   1.0   2.0   3.0   4.0
1   NaN   NaN   NaN   NaN   NaN
2   5.0   6.0   7.0   8.0   9.0
3  10.0  11.0  12.0  13.0  14.0
4   NaN   NaN   NaN   NaN   NaN
5  15.0  16.0  17.0  18.0  19.0

In [9]: a.reindex(columns=["a","b","c","d","e","f"])
Out[9]:
    a   b   c   d   e   f
0   0   1 NaN   2   3   4
2   5   6 NaN   7   8   9
3  10  11 NaN  12  13  14
5  15  16 NaN  17  18  19

In [10]: a.reindex(index=[1,7]) # 元のインデックスラベルに存在しない値を指定すると全てがNaNになる
Out[10]:
    a   b   d   e   f
1 NaN NaN NaN NaN NaN
7 NaN NaN NaN NaN NaN

In [11]: a.reindex(index=[0,1,2,3,4,5],columns=["a","b","c","d","e","f"]) # 両方同時に指定することも可能
Out[11]:
      a     b   c     d     e     f
0   0.0   1.0 NaN   2.0   3.0   4.0
1   NaN   NaN NaN   NaN   NaN   NaN
2   5.0   6.0 NaN   7.0   8.0   9.0
3  10.0  11.0 NaN  12.0  13.0  14.0
4   NaN   NaN NaN   NaN   NaN   NaN
5  15.0  16.0 NaN  17.0  18.0  19.0


次はmethodについて解説します。欠損値を補間するのに、以下のルールを適用します。

  • 直前の値を使いたかったらpadもしくはffill(forward filling)を使う
  • 直後の値を使いたかったらbackfillもしくはbfill(back filling)を使う
  • 一番近くにある値を使いたかったらnearestを使う

理解しやすいように具体的に新たなDataFrameオブジェクトを作って試して見ます。

In [14]: b = pd.DataFrame(np.arange(20).reshape(4,5),index=[0,4,5,8],columns=["a","e","g","j","k"])

In [15]: b
Out[15]:
    a   e   g   j   k
0   0   1   2   3   4
4   5   6   7   8   9
5  10  11  12  13  14
8  15  16  17  18  19

まずはffillpadを使った補間です。直前の値がデータの補間に使われます。

In [16]: b.reindex(range(10),method="ffill") # method="pad"
Out[16]:
    a   e   g   j   k
0   0   1   2   3   4
1   0   1   2   3   4
2   0   1   2   3   4
3   0   1   2   3   4
4   5   6   7   8   9
5  10  11  12  13  14
6  10  11  12  13  14
7  10  11  12  13  14
8  15  16  17  18  19
9  15  16  17  18  19

indexに連番を指定したいときには、Pythonのビルトイン関数range関数を使うと簡潔にコードを書くことができます。

欠損値の補間をみてみると、1,2,3番目のところが全て0番目の値になっており、6,75番目の値が使われていることが分かります。

ではbfillまたはbackfillですと逆方向に補間を行います。

In [17]: b.reindex(range(10),method="bfill") # method="backfill"
Out[17]:
      a     e     g     j     k
0   0.0   1.0   2.0   3.0   4.0
1   5.0   6.0   7.0   8.0   9.0
2   5.0   6.0   7.0   8.0   9.0
3   5.0   6.0   7.0   8.0   9.0
4   5.0   6.0   7.0   8.0   9.0
5  10.0  11.0  12.0  13.0  14.0
6  15.0  16.0  17.0  18.0  19.0
7  15.0  16.0  17.0  18.0  19.0
8  15.0  16.0  17.0  18.0  19.0
9   NaN   NaN   NaN   NaN   NaN

最後の9番目の値に対しては補間できる値がなかったためNaNのままになってしまいました。

次にnearestにしてみます。これは一番近くにある値で補間を行います。

In [18]: b.reindex(range(10),method="nearest")
Out[18]:
    a   e   g   j   k
0   0   1   2   3   4
1   0   1   2   3   4
2   5   6   7   8   9
3   5   6   7   8   9
4   5   6   7   8   9
5  10  11  12  13  14
6  10  11  12  13  14
7  15  16  17  18  19
8  15  16  17  18  19
9  15  16  17  18  19

次はcopyを指定してみます。デフォルト値ではTrueとなっており、デフォルトではコピーが作成されるようになっています。

これにより、デフォルトでは元の値が変更されてもインデックスを変更する前の値が変更されることはありません。

In [19]: c = b.reindex() # 特に何も指定しないとbと同じものが生成される

In [20]: c
Out[20]:
    a   e   g   j   k
0   0   1   2   3   4
4   5   6   7   8   9
5  10  11  12  13  14
8  15  16  17  18  19

In [21]: c.iloc[0,0] = 22 # 値の一部を変更する

In [22]: c # cの中身は変更されているが
Out[22]:
    a   e   g   j   k
0  22   1   2   3   4
4   5   6   7   8   9
5  10  11  12  13  14
8  15  16  17  18  19

In [23]: b # bの中身は変更されていない
Out[23]:
    a   e   g   j   k
0   0   1   2   3   4
4   5   6   7   8   9
5  10  11  12  13  14
8  15  16  17  18  19

copy=Falseにすると値の変更が反映されます。

In [24]: d = b.reindex(copy=False) # copy=Falseにしてみる

In [25]: d.iloc[0,0] = 22

In [26]: d
Out[26]:
    a   e   g   j   k
0  22   1   2   3   4
4   5   6   7   8   9
5  10  11  12  13  14
8  15  16  17  18  19

In [27]: b
Out[27]:
    a   e   g   j   k
0  22   1   2   3   4
4   5   6   7   8   9
5  10  11  12  13  14
8  15  16  17  18  19

しかしながら、ラベルの値を変更すると自動的にコピーが生成されてしまうのであまりこの引数を変更することに意味はありません。

In [28]: b.iloc[0,0] = 0 # bの中身を元に戻す

In [29]: e = b.reindex(index=range(10),copy=False)

In [30]: e
Out[30]:
      a     e     g     j     k
0   0.0   1.0   2.0   3.0   4.0
1   NaN   NaN   NaN   NaN   NaN
2   NaN   NaN   NaN   NaN   NaN
3   NaN   NaN   NaN   NaN   NaN
4   5.0   6.0   7.0   8.0   9.0
5  10.0  11.0  12.0  13.0  14.0
6   NaN   NaN   NaN   NaN   NaN
7   NaN   NaN   NaN   NaN   NaN
8  15.0  16.0  17.0  18.0  19.0
9   NaN   NaN   NaN   NaN   NaN


In [35]: e.iloc[5, 0] = 100

In [36]: e
Out[36]:
       a     e     g     j     k
0    0.0   1.0   2.0   3.0   4.0
1    NaN   NaN   NaN   NaN   NaN
2    NaN   NaN   NaN   NaN   NaN
3    NaN   NaN   NaN   NaN   NaN
4    5.0   6.0   7.0   8.0   9.0
5  100.0  11.0  12.0  13.0  14.0
6    NaN   NaN   NaN   NaN   NaN
7    NaN   NaN   NaN   NaN   NaN
8   15.0  16.0  17.0  18.0  19.0
9    NaN   NaN   NaN   NaN   NaN

In [37]: b # 値が変化しない
Out[37]:
    a   e   g   j   k
0   0   1   2   3   4
4   5   6   7   8   9
5  10  11  12  13  14
8  15  16  17  18  19

次はlevelの指定です。これはMultiIndexとなっているときに使われるものです。

sample_reindex.csv

上記のデータを使います。簡単に中身を整理します。

In [88]: df = pd.read_csv("sample_reindex.csv")

In [89]: df
Out[89]:
        time class  score
0    9:00:00     A     91
1    9:10:00     B     56
2    9:20:00     C     74
3    9:30:00     C     28
4    9:40:00     B     14
5   10:30:00     A     49
6   10:30:00     B     56
7   10:40:00     A     84
8   10:50:00     C     86
9   11:10:00     B     65
10  11:10:00     A     39

In [90]: df = df.set_index(["class","time"])

平均を計算します。

In [91]: df_new = df.mean(level="class")

In [92]: df_new
Out[92]:
           score
class           
A      65.750000
B      47.750000
C      62.666667

そして、これをclassラベルをブロードキャストを適用して拡張した形でreindex関数を使います。

In [93]: df_new.reindex(df.index, level=0) # level="class"でもよい
Out[93]:
                    score
class time               
A     9:00:00   65.750000
B     9:10:00   47.750000
C     9:20:00   62.666667
      9:30:00   62.666667
B     9:40:00   47.750000
A     10:30:00  65.750000
B     10:30:00  47.750000
A     10:40:00  65.750000
C     10:50:00  62.666667
B     11:10:00  47.750000
A     11:10:00  65.750000

In [94]: df_new.reindex(df.index, level=1) # 元々ない階層だと新たにIndexオブジェクトが作られるので全てNaNになってしまう。
Out[94]:
                score
class time           
A     9:00:00     NaN
B     9:10:00     NaN
C     9:20:00     NaN
      9:30:00     NaN
B     9:40:00     NaN
A     10:30:00    NaN
B     10:30:00    NaN
A     10:40:00    NaN
C     10:50:00    NaN
B     11:10:00    NaN
A     11:10:00    NaN

次にfill_valueです。これは今まで欠損値を埋めていた値は全てNaNでしたが、それを適宜変更できます。

In [96]: a
Out[96]:
    a   b   d   e   f
0   0   1   2   3   4
2   5   6   7   8   9
3  10  11  12  13  14
5  15  16  17  18  19

In [97]: a.reindex(index=range(6),fill_value=-999)
Out[97]:
     a    b    d    e    f
0    0    1    2    3    4
1 -999 -999 -999 -999 -999
2    5    6    7    8    9
3   10   11   12   13   14
4 -999 -999 -999 -999 -999
5   15   16   17   18   19

In [98]: a.reindex(index=range(6),fill_value="missing")
Out[98]:
         a        b        d        e        f
0        0        1        2        3        4
1  missing  missing  missing  missing  missing
2        5        6        7        8        9
3       10       11       12       13       14
4  missing  missing  missing  missing  missing
5       15       16       17       18       19

次はlimitです。データ補間できる距離の最大値を示します。

limit=2でやってみます。

In [100]: b
Out[100]:
    a   e   g   j   k
0   0   1   2   3   4
4   5   6   7   8   9
5  10  11  12  13  14
8  15  16  17  18  19

In [102]: b.reindex(index=range(10), limit=2, method="ffill")
Out[102]:
      a     e     g     j     k
0   0.0   1.0   2.0   3.0   4.0
1   0.0   1.0   2.0   3.0   4.0
2   0.0   1.0   2.0   3.0   4.0
3   NaN   NaN   NaN   NaN   NaN
4   5.0   6.0   7.0   8.0   9.0
5  10.0  11.0  12.0  13.0  14.0
6  10.0  11.0  12.0  13.0  14.0
7  10.0  11.0  12.0  13.0  14.0
8  15.0  16.0  17.0  18.0  19.0
9  15.0  16.0  17.0  18.0  19.0

limit=2にしてあるので補間できる値の個数が上限2に限定されます。そのため3のところの値は0の値がすでに2回補間されているため補間の対象とならずNaNとなります。

最後にtoleranceです。これはlimitの拡張版で、limitと同じ使い方もできますし、limitの値を各ラベルごとに割り当てることも可能です。

In [105]: b.reindex(index=range(10), tolerance=2, method="ffill")
Out[105]:
      a     e     g     j     k
0   0.0   1.0   2.0   3.0   4.0
1   0.0   1.0   2.0   3.0   4.0
2   0.0   1.0   2.0   3.0   4.0
3   NaN   NaN   NaN   NaN   NaN
4   5.0   6.0   7.0   8.0   9.0
5  10.0  11.0  12.0  13.0  14.0
6  10.0  11.0  12.0  13.0  14.0
7  10.0  11.0  12.0  13.0  14.0
8  15.0  16.0  17.0  18.0  19.0
9  15.0  16.0  17.0  18.0  19.0



In [109]: b.reindex(index=range(10), tolerance=[1,1,1,2,2,2,2,0,0,0], method="ffill")
Out[109]:
      a     e     g     j     k
0   0.0   1.0   2.0   3.0   4.0
1   0.0   1.0   2.0   3.0   4.0
2   NaN   NaN   NaN   NaN   NaN
3   NaN   NaN   NaN   NaN   NaN
4   5.0   6.0   7.0   8.0   9.0
5  10.0  11.0  12.0  13.0  14.0
6  10.0  11.0  12.0  13.0  14.0
7   NaN   NaN   NaN   NaN   NaN
8  15.0  16.0  17.0  18.0  19.0
9   NaN   NaN   NaN   NaN   NaN

まとめ

今回はreindex関数の使い方について解説しました。名前の通り、インデックスラベルやカラムラベルを再定義するのに使われる関数です。

新しいラベルを振り分ける時に発生する欠損値に対してどのように補間するかを指定するために引数が増えていますが、そのような操作はfillna関数に任せてとりあえず新しいラベルを振ることができるということに焦点を置くという使い方をすることもできます。そうすることで、コードも書きやすくなるかと思います。

もちろん、他の引数も使いこなせるのであれば是非使ってコードをよりシンプルにするよう試みてください。

参考