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 |
配列に相当するオブジェクト | (省略可能)初期値Noneaxis 引数によって指定された軸(インデックスかカラム)の新しいラベルを指定します。 |
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 | (省略可能)初期値Nonelimit の拡張版。各ラベルごとにlimit の値を指定できます。 |
returns:
軸ラベルが更新されたDataFrame
ここで1つ注目して欲しいのは、大まかにインデックスやカラムラベルを更新するやり方は2通り存在しています。
-
labels
で新しいラベルを、axis
でカラムかインデックスラベルかを選択 -
index
,columns
に個別に指定する
公式ドキュメントでは後者のやり方を推奨しています。こちらの方が直感的で可読性が高いと感じます。
サンプルコード
実際に手を動かして実践して見ましょう。先ほど取り上げた2つの手法を試して見ます。
まずはlabels
とaxis
を使ったやり方です。
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の略です)
では、index
とcolumns
引数を使って同じ操作をして見ましょう。index
とcolumns
引数では、同時に両方のラベルを更新することも可能です。
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
まずはffill
とpad
を使った補間です。直前の値がデータの補間に使われます。
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,7
は5
番目の値が使われていることが分かります。
では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
となっているときに使われるものです。
上記のデータを使います。簡単に中身を整理します。
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
関数に任せてとりあえず新しいラベルを振ることができるということに焦点を置くという使い方をすることもできます。そうすることで、コードも書きやすくなるかと思います。
もちろん、他の引数も使いこなせるのであれば是非使ってコードをよりシンプルにするよう試みてください。
参考
- pandas.DataFrame.reindex pandas 0.23.2 documentation
- Python for Data Analysis 2nd edition –Wes McKinney(書籍)
- 公式ドキュメント サンプルコード