pandasのdf.index = List[...]
的な代入文が嫌い
pandasにおいて、データフレームの行名や列名を設定・更新するとき、df.index = List[...]
的な代入操作は普遍的に行われている。つまり、
df = pd.DataFrame(np.random.randn(5, 3))
print(df)
# 0 1 2
# 0 0.221652 -0.243116 -0.551044
# 1 0.285863 -0.596317 0.070156
# 2 1.404026 0.362331 -0.237264
# 3 0.320011 1.144342 1.560148
# 4 -1.386724 -0.234210 -0.488311
のようなデータフレームがあったときに、
df.columns = ['A', 'B', 'C']
print(df)
# A B C
# 0 0.221652 -0.243116 -0.551044
# 1 0.285863 -0.596317 0.070156
# 2 1.404026 0.362331 -0.237264
# 3 0.320011 1.144342 1.560148
# 4 -1.386724 -0.234210 -0.488311
のような操作をすることが、一般的に行われている。
公式ドキュメントにもこっそり
If you create an index yourself, you can just assign it to the
index
field:data.index = index
などと書いてある。
僕も、わけもわからずネットのコードを写経していた時代にはよくやっていた。
しかし、このような操作(表記というべきか?)は避けるべきではなかろうか。
df.columns = ['A', 'B', 'C']
なにやら奇妙な一行という感じがする。この違和感がどこからくるのかよくわからないが。
オブジェクトの属性に直接リストを突っ込むというところに、なんとも言えぬ気持ち悪さを感じるのかもしれない。
じゃあどうすればいいのか
pd.DataFrame.set_axis()
あるいはpd.DataFrame.rename()
を使おう。
set_axis()
の使い方
df = pd.DataFrame(np.random.randn(5, 3))
print(df)
# 0 1 2
# 0 0.221652 -0.243116 -0.551044
# 1 0.285863 -0.596317 0.070156
# 2 1.404026 0.362331 -0.237264
# 3 0.320011 1.144342 1.560148
# 4 -1.386724 -0.234210 -0.488311
のようなデータフレームがあったとき、以下のようにして列名を変更する。
# df.columns = ['A', 'B', 'C'] ではなく
df.set_axis(['A', 'B', 'C'],
axis='columns', inplace=True)
print(df)
# A B C
# 0 0.221652 -0.243116 -0.551044
# 1 0.285863 -0.596317 0.070156
# 2 1.404026 0.362331 -0.237264
# 3 0.320011 1.144342 1.560148
# 4 -1.386724 -0.234210 -0.488311
set_axis()
の第一引数に名前のリストを渡し、続いてindex
(行名)を変更するときはaxis=0
あるいはaxis='index'
、columns
(列名)を変更するときはaxis=1
あるいはaxis='columns'
とする。inplace
オプションは他のメソッドと同じ。
set_axis()
の第一引数には名前のリストだけでなく、Index
オブジェクトを渡すこともできる。したがって、pd.MultiIndex
を渡すことで、マルチインデックスにすることができる。
例えば、公式のクックブック『Creating a MultiIndex from a labeled frame』では
df = pd.DataFrame({'One_X': [1.1, 1.1, 1.1],
'One_Y': [1.2, 1.2, 1.2],
'Two_X': [1.11, 1.11, 1.11],
'Two_Y': [1.22, 1.22, 1.22]})
print(df)
# One_X One_Y Two_X Two_Y
# 0 1.1 1.2 1.11 1.22
# 1 1.1 1.2 1.11 1.22
# 2 1.1 1.2 1.11 1.22
このcolumns
をマルチインデックスにするのに、以下の操作を行っている。
df.columns = pd.MultiIndex.from_tuples([tuple(c.split('_'))
for c in df.columns])
print(df)
# One Two
# X Y X Y
# 0 1.1 1.2 1.11 1.22
# 1 1.1 1.2 1.11 1.22
# 2 1.1 1.2 1.11 1.22
これは以下のようにする。
df.set_axis(pd.MultiIndex.from_tuples([tuple(c.split('_'))
for c in df.columns]),
axis='columns', inplace=True)
print(df)
# One Two
# X Y X Y
# 0 1.1 1.2 1.11 1.22
# 1 1.1 1.2 1.11 1.22
# 2 1.1 1.2 1.11 1.22
結局、要は
df.index = new_idx
df.columns = new_col
# ↑同じ処理(表記が違うだけ)↓
df.set_axis(new_idx, axis='index', inplace=True)
df.set_axis(new_col, axis='columns', inplace=True)
ということである。
rename()
の使い方
pd.DataFrame.rename()
は、既存の行明・列名を変換するときに用いる。
まず、辞書で1:1の置換ができる。辞書のキーに存在しない列名があっても、エラーが出るということはない。
df = pd.DataFrame(np.random.randn(5, 3))
print(df)
# 0 1 2
# 0 0.221652 -0.243116 -0.551044
# 1 0.285863 -0.596317 0.070156
# 2 1.404026 0.362331 -0.237264
# 3 0.320011 1.144342 1.560148
# 4 -1.386724 -0.234210 -0.488311
df.rename(columns={0: 'A', 1: 'B'}, inplace=True)
print(df)
# A B 2
# 0 0.221652 -0.243116 -0.551044
# 1 0.285863 -0.596317 0.070156
# 2 1.404026 0.362331 -0.237264
# 3 0.320011 1.144342 1.560148
# 4 -1.386724 -0.234210 -0.488311
df.rename(columns={2: 'C', 3: 'D'}, inplace=True)
print(df)
# A B C
# 0 0.221652 -0.243116 -0.551044
# 1 0.285863 -0.596317 0.070156
# 2 1.404026 0.362331 -0.237264
# 3 0.320011 1.144342 1.560148
# 4 -1.386724 -0.234210 -0.488311
index
とcolumns
を両方同時に変更できる。
df = pd.DataFrame(np.random.randn(5, 3))
print(df)
# 0 1 2
# 0 0.221652 -0.243116 -0.551044
# 1 0.285863 -0.596317 0.070156
# 2 1.404026 0.362331 -0.237264
# 3 0.320011 1.144342 1.560148
# 4 -1.386724 -0.234210 -0.488311
df.rename(index={k: v for k, v in enumerate('abcde')},
columns={k: v for k, v in enumerate('ABC')},
inplace=True)
print(df)
# A B C
# a 0.221652 -0.243116 -0.551044
# b 0.285863 -0.596317 0.070156
# c 1.404026 0.362331 -0.237264
# d 0.320011 1.144342 1.560148
# e -1.386724 -0.234210 -0.488311
また、行名・列名に関数を適用して変換することができる。
df = pd.DataFrame(np.random.randn(5, 3),
index=list('abcde'), columns=list('ABC'))
print(df)
# A B C
# a 0.221652 -0.243116 -0.551044
# b 0.285863 -0.596317 0.070156
# c 1.404026 0.362331 -0.237264
# d 0.320011 1.144342 1.560148
# e -1.386724 -0.234210 -0.488311
df.rename(index=str.upper,
columns=lambda x: x*2,
inplace=True)
print(df)
# AA BB CC
# A 0.221652 -0.243116 -0.551044
# B 0.285863 -0.596317 0.070156
# C 1.404026 0.362331 -0.237264
# D 0.320011 1.144342 1.560148
# E -1.386724 -0.234210 -0.488311
以上のように、rename()
は、index
あるいはcolumns
引数に置換辞書ないし関数を渡して用いる。set_axis()
のように、以下のような書き方もできるが、おすすめはしない。
df.rename({0: 'A', 1: 'B'}, axis='columns', inplace=True)
何が良いか
df.set_axis()
はdf.index = List[...]
的なやつと何が違うのか?
まず、気持ち悪さがない。
次に、inplace
オプションがあるため、元のデータフレームの行明・列名を変更するのか、行明・列名を変更したデータフレームのコピーを作成するのか選択できる。後者を実行する場合、df.index = List[...]
的なやつは2行を要する。
df2 = df.copy()
df2.index = new_idx
set_axis()
はより短く書ける。
df2 = df.set_axis(new_idx)
また、set_axis()
は文ではないので、メソッドチェーンに加えたり、リスト内包表記の中で処理することができる。例えば、
df = pd.concat([df_sub.set_axis(['A', 'B', 'C'], axis='columns').drop(columns='B')
for df_sub in list_of_dfs])
みたいなことができる。
また、rename()
はマルチインデックスの行名・列名を変更するときに便利である。
print(df)
# One Two
# X Y X Y
# 0 1.1 1.2 1.11 1.22
# 1 1.1 1.2 1.11 1.22
# 2 1.1 1.2 1.11 1.22
# 複数のレベルを一度に変更
df.rename(columns={'One': 'one', 'X': 'x', 'Y': 'y'}, inplace=True)
print(df)
# one Two
# x y x y
# 0 1.1 1.2 1.11 1.22
# 1 1.1 1.2 1.11 1.22
# 2 1.1 1.2 1.11 1.22
# 特定レベルのみに関数適用
df.rename(columns=str.upper, level=0, inplace=True)
print(df)
# ONE TWO
# x y x y
# 0 1.1 1.2 1.11 1.22
# 1 1.1 1.2 1.11 1.22
# 2 1.1 1.2 1.11 1.22
みんなもdf.index = List[...]
からは卒業しよう!