LoginSignup
7
5

More than 3 years have passed since last update.

pandasの`df.index = List[...]`的な代入文が嫌い

Posted at

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

――Indexing and selecting data — pandas 1.0.1 documentation

などと書いてある。

僕も、わけもわからずネットのコードを写経していた時代にはよくやっていた。

しかし、このような操作(表記というべきか?)は避けるべきではなかろうか。

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

indexcolumnsを両方同時に変更できる。

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[...]からは卒業しよう!

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5