pandasで任意の位置の値を取得・変更するat, iat, loc, iloc

Modified: | Tags: Python, pandas

pandas.DataFrameの任意の位置のデータを取り出したり変更(代入)したりするには、at, iat, loc, ilocを使う。at()ではなくat[]のように記述する。

位置の指定方法および選択できる範囲に違いがある。

  • 位置の指定方法
    • at, loc: 行名(行ラベル)、列名(列ラベル)
    • iat, iloc: 行番号、列番号
  • 選択し取得・変更できるデータ
    • at, iat: 単独の要素の値
    • loc, iloc: 単独および複数の要素の値
      • リストやスライスで範囲を指定可能
      • 単独の要素にアクセスする場合はatiatのほうが高速

pandas.DataFrameの行・列、pandas.Seriesの要素を選択する場合はインデックス参照df[]も使用できる。

なお、以前あったget_value()およびix[]は、バージョン1.0で削除された。

本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。以下のpandas.DataFrameを例とする。

import pandas as pd

print(pd.__version__)
# 2.0.3

df = pd.DataFrame({'col_0': ['00', '10', '20', '30', '40'],
                   'col_1': ['01', '11', '21', '31', '41'],
                   'col_2': ['02', '12', '22', '32', '42'],
                   'col_3': ['03', '13', '23', '33', '43']},
                  index=['row_0', 'row_1', 'row_2', 'row_3', 'row_4'])
print(df)
#       col_0 col_1 col_2 col_3
# row_0    00    01    02    03
# row_1    10    11    12    13
# row_2    20    21    22    23
# row_3    30    31    32    33
# row_4    40    41    42    43

at, iat: 単独の要素の値を選択、取得・変更

atは行名と列名で位置を指定する。

データを取得するだけでなく、その位置に新たな値を設定(代入)することもできる。

print(df.at['row_1', 'col_2'])
# 12

df.at['row_1', 'col_2'] = '0'
print(df.at['row_1', 'col_2'])
# 0

iatは行番号と列番号で位置を指定する。行番号・列番号は0始まり。

atと同じく、データを取得するだけでなく、その位置に新たな値を設定(代入)できる。

print(df.iat[1, 2])
# 0

df.iat[1, 2] = '12'
print(df.iat[1, 2])
# 12

loc, iloc: 単独および複数の要素の値を選択、取得・変更

locilocは、単独の値だけでなく範囲を指定して複数のデータを選択できる。locは行名と列名、ilocは行番号と列番号で位置を指定する。

単独の要素の値を選択

単独の値にアクセスする場合はat, iatと同じ。処理速度はat, iatのほうが速い。

print(df.loc['row_1', 'col_2'])
# 12

print(df.iloc[1, 2])
# 12

データを取得するだけでなく、その位置に新たな値を設定(代入)することもできる。

df.loc['row_1', 'col_2'] = '0'
print(df.loc['row_1', 'col_2'])
# 0

df.iloc[1, 2] = '12'
print(df.iloc[1, 2])
# 12

複数の要素の値を選択(リスト・スライスで範囲指定)

loc, ilocでは、行名や列番号などのスカラー値のほか、リスト[a, b, c, ...]やスライスstart:stop:stepでデータの範囲・位置を指定して複数の値にアクセスできる。

スライスの書き方は通常のスライスと同じ。stepは省略可。

スライスstart:stop:stepで指定するとき、loc(行名・列名)ではstopの要素も含まれるので注意。iloc(行番号・列番号)では通常のスライスと同様にstopの一つ前まで。

また、リストで指定すると、行や列の順番は指定したリストの順番になる。

print(df.loc['row_1':'row_3', ['col_2', 'col_0']])
#       col_2 col_0
# row_1    12    10
# row_2    22    20
# row_3    32    30

print(df.iloc[1:3, [2, 0]])
#       col_2 col_0
# row_1    12    10
# row_2    22    20

スライスでstepを指定すると、奇数行・偶数行を抽出したりできる。

print(df.iloc[::2, [0, 3]])
#       col_0 col_3
# row_0    00    03
# row_2    20    23
# row_4    40    43

print(df.iloc[1::2, [0, 3]])
#       col_0 col_3
# row_1    10    13
# row_3    30    33

複数の値を一括で変更することが可能。スカラー値を代入するとすべての要素がその値になる。範囲に対して代入する場合は、二次元リスト(リストのリスト)や二次元のNumPy配列ndarrayなどを使う。

df.iloc[1:3, [2, 0]] = '0'
print(df)
#       col_0 col_1 col_2 col_3
# row_0    00    01    02    03
# row_1     0    11     0    13
# row_2     0    21     0    23
# row_3    30    31    32    33
# row_4    40    41    42    43

df.iloc[1:3, [2, 0]] = [['12', '10'], ['22', '20']]
print(df)
#       col_0 col_1 col_2 col_3
# row_0    00    01    02    03
# row_1    10    11    12    13
# row_2    20    21    22    23
# row_3    30    31    32    33
# row_4    40    41    42    43

なお、行・列をスカラー値で指定して一行・一列を選択するとpandas.Seriesが返されるが、同じ一行・一列でも、スライスやリストで指定するとpandas.DataFrameとなる。

特に、行をpandas.Seriesで取得すると暗黙の型変換が行われる可能性があるので要注意。詳細は後述。

print(df.loc['row_1', ['col_0', 'col_2']])
print(type(df.loc['row_1', ['col_0', 'col_2']]))
# col_0    10
# col_2    12
# Name: row_1, dtype: object
# <class 'pandas.core.series.Series'>

print(df.loc['row_1':'row_1', ['col_0', 'col_2']])
print(type(df.loc['row_1':'row_1', ['col_0', 'col_2']]))
#       col_0 col_2
# row_1    10    12
# <class 'pandas.core.frame.DataFrame'>

print(df.loc[['row_1'], ['col_0', 'col_2']])
print(type(df.loc[['row_1'], ['col_0', 'col_2']]))
#       col_0 col_2
# row_1    10    12
# <class 'pandas.core.frame.DataFrame'>

行・列を選択

インデックス参照df[]で行・列を選択できるが、以下の指定方法に限られる。

  • 行の選択: 行名・行番号のスライス
  • 列の選択: 列名、または、列名のリスト

詳細は以下の記事を参照。

print(df['row_1':'row_3'])
#       col_0 col_1 col_2 col_3
# row_1    10    11    12    13
# row_2    20    21    22    23
# row_3    30    31    32    33

print(df[1:3])
#       col_0 col_1 col_2 col_3
# row_1    10    11    12    13
# row_2    20    21    22    23

print(df['col_1'])
# row_0    01
# row_1    11
# row_2    21
# row_3    31
# row_4    41
# Name: col_1, dtype: object

print(df[['col_1', 'col_3']])
#       col_1 col_3
# row_0    01    03
# row_1    11    13
# row_2    21    23
# row_3    31    33
# row_4    41    43

loc, ilocを使うと、より柔軟に行・列を指定できる。

loc, ilocで列の指定を省略すると行が選択できる。行名・行番号単独での指定やリストによる指定も可能。

print(df.loc['row_2'])
# col_0    20
# col_1    21
# col_2    22
# col_3    23
# Name: row_2, dtype: object

print(df.iloc[[1, 3]])
#       col_0 col_1 col_2 col_3
# row_1    10    11    12    13
# row_3    30    31    32    33

loc, ilocで行の指定を:(全体のスライス)にすると列を選択できる。スライスによる指定やilocでの列番号による指定も可能。

print(df.loc[:, 'col_1':])
#       col_1 col_2 col_3
# row_0    01    02    03
# row_1    11    12    13
# row_2    21    22    23
# row_3    31    32    33
# row_4    41    42    43

print(df.iloc[:, 2])
# row_0    02
# row_1    12
# row_2    22
# row_3    32
# row_4    42
# Name: col_2, dtype: object

上述のように、行・列をスカラー値で指定して一行・一列を選択するとpandas.Seriesが返されるが、同じ一行・一列でも、スライスやリストで指定するとpandas.DataFrameとなる。

特に、行をpandas.Seriesで選択すると暗黙の型変換が行われる可能性があるので要注意。詳細は後述。

print(df.loc['row_2'])
print(type(df.loc['row_2']))
# col_0    20
# col_1    21
# col_2    22
# col_3    23
# Name: row_2, dtype: object
# <class 'pandas.core.series.Series'>

print(df.loc['row_2':'row_2'])
print(type(df.loc['row_2':'row_2']))
#       col_0 col_1 col_2 col_3
# row_2    20    21    22    23
# <class 'pandas.core.frame.DataFrame'>

print(df.loc[['row_2']])
print(type(df.loc[['row_2']]))
#       col_0 col_1 col_2 col_3
# row_2    20    21    22    23
# <class 'pandas.core.frame.DataFrame'>

boolのリストによるマスク(ブーリアンインデックス)

loc, ilocでは、bool値(True, False)のリストやnumpy.ndarrayを指定してデータをマスクすることができる。例では行に指定しているが、列に指定することも可能。

l_bool = [True, False, False, True, False]

print(df.loc[l_bool, ['col_0', 'col_2']])
#       col_0 col_2
# row_0    00    02
# row_3    30    32

print(df.iloc[l_bool, [0, 2]])
#       col_0 col_2
# row_0    00    02
# row_3    30    32

要素数が一致していないとエラー。

l_bool_wrong = [True, False, False]

# print(df.loc[l_bool_wrong, ['col_0', 'col_2']])
# IndexError: Boolean index has wrong length: 3 instead of 5

locではbool値を要素とするpandas.Seriesも指定可能。順番ではなくラベルを元にマスクされる。

s_bool = pd.Series([True, False, False, True, False], index=reversed(df.index))
print(s_bool)
# row_4     True
# row_3    False
# row_2    False
# row_1     True
# row_0    False
# dtype: bool

print(df.loc[s_bool, ['col_0', 'col_2']])
#       col_0 col_2
# row_1    10    12
# row_4    40    42

ilocではpandas.Seriesは指定不可。

# print(df.iloc[s_bool, [0, 2]])
# ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types

locでも要素数やラベルが一致していないとエラー。

s_bool_wrong = pd.Series([True, False, False], index=['row_0', 'row_1', 'row_2'])

# print(df.loc[s_bool_wrong, ['col_0', 'col_2']])
# IndexingError: Unalignable boolean Series provided as indexer (index of the boolean Series and of the indexed object do not match).

s_bool_wrong = pd.Series([True, False, False, True, False],
                         index=['row_0', 'row_1', 'row_2', 'row_3', 'XXX'])

# print(df.loc[s_bool_wrong, ['col_0', 'col_2']])
# IndexingError: Unalignable boolean Series provided as indexer (index of the boolean Series and of the indexed object do not match).

行名・列名が重複している場合

行名index、列名columnsに重複する値が含まれていてもエラーにならない。

重複する行名・列名を持つpandas.DataFrameを例とする。

df_duplicated = df.rename(columns={'col_2': 'col_1'}, index={'row_3': 'row_2'})
print(df_duplicated)
#       col_0 col_1 col_1 col_3
# row_0    00    01    02    03
# row_1    10    11    12    13
# row_2    20    21    22    23
# row_2    30    31    32    33
# row_4    40    41    42    43

at, locでは、重複したラベルを指定すると該当の複数要素が選択される。

print(df_duplicated.at['row_2', 'col_1'])
print(type(df_duplicated.at['row_2', 'col_1']))
#       col_1 col_1
# row_2    21    22
# row_2    31    32
# <class 'pandas.core.frame.DataFrame'>

print(df_duplicated.loc[:'row_2', ['col_1', 'col_3']])
print(type(df_duplicated.loc[:'row_2', ['col_1', 'col_3']]))
#       col_1 col_1 col_3
# row_0    01    02    03
# row_1    11    12    13
# row_2    21    22    23
# row_2    31    32    33
# <class 'pandas.core.frame.DataFrame'>

iat, ilocで行番号・列番号で指定する場合は、位置を基準とするのでラベルが重複していても特に問題ない。

print(df_duplicated.iat[2, 1])
# 21

print(df_duplicated.iloc[:2, [1, 3]])
#       col_1 col_3
# row_0    01    03
# row_1    11    13

混乱のもとになるので、強い理由がないのであれば行名・列名は一意の値にしたほうがよい。

行名・列名が一意の値になっている(重複していない)かどうかはindex.is_unique, columns.is_uniqueで確認できる。

print(df_duplicated.index.is_unique)
# False

print(df_duplicated.columns.is_unique)
# False

行名・列名の変更については以下の記事を参照。

番号と名前(ラベル)で位置を指定

行番号と列名(列ラベル)のように番号と名前(ラベル)の組み合わせで位置を指定したい場合、atまたはlocindex, column属性を使う方法がある。

index, column属性で行番号または列番号からその行名・列名を取得できる。

print(df.index[2])
# row_2

print(df.columns[2])
# col_2

スライスやリストを指定して複数の行名・列名を取得することも可能。

print(df.index[1:4])
# Index(['row_1', 'row_2', 'row_3'], dtype='object')

print(df.columns[[1, 3]])
# Index(['col_1', 'col_3'], dtype='object')

これを利用すると、atまたはlocで番号と名前の組み合わせで位置を指定できる。

print(df.at[df.index[2], 'col_2'])
# 22

print(df.loc[['row_0', 'row_3'], df.columns[[1, 3]]])
#       col_1 col_3
# row_0    01    03
# row_3    31    33

以下のように[]loc,ilocを繰り返す書き方もできるが、これはchained indexingと呼ばれ、SettingWithCopyWarningという警告の要因となる。

データを取得・確認するだけであれば問題ないが、新たな値を代入すると想定外の結果となる場合があるので注意。

print(df['col_2'][2])
# 22

print(df.loc[['row_0', 'row_3']].iloc[:, [1, 3]])
#       col_1 col_3
# row_0    01    03
# row_3    31    33

行をpandas.Seriesで選択する際の暗黙の型変換

locilocで一行を選択してpandas.Seriesで取得する場合、元のpandas.DataFrameの各列のデータ型dtypeが異なっていると暗黙の型変換が行われる。

整数intの列と浮動小数点数floatの列を持つpandas.DataFrameを例とする。

df_mix = pd.DataFrame({'col_int': [0, 1, 2], 'col_float': [0.1, 0.2, 0.3]}, index=['A', 'B', 'C'])
print(df_mix)
#    col_int  col_float
# A        0        0.1
# B        1        0.2
# C        2        0.3

print(df_mix.dtypes)
# col_int        int64
# col_float    float64
# dtype: object

locilocで一行をpandas.Seriesとして取得すると、そのデータ型はfloatとなる。intの列にあった要素はfloatに変換される。

print(df_mix.loc['B'])
# col_int      1.0
# col_float    0.2
# Name: B, dtype: float64

print(type(df_mix.loc['B']))
# <class 'pandas.core.series.Series'>

以下のように[]を続けて書くと、floatに変換されたpandas.Seriesの要素が返される。元の型と異なる型として要素の値が返されてしまうので要注意。

print(df_mix.loc['B']['col_int'])
# 1.0

print(type(df_mix.loc['B']['col_int']))
# <class 'numpy.float64'>

[]loc,ilocを繰り返す書き方は避けてatiatを使えば、元の型の要素が取得できる。

print(df_mix.at['B', 'col_int'])
# 1

print(type(df_mix.at['B', 'col_int']))
# <class 'numpy.int64'>

なお、locilocにおいてリストやスライスで一行を選択した場合は、pandas.Seriesではなくpandas.DataFrameが返される。この場合は元のデータ型dtypeが保持される。

print(df_mix.loc[['B']])
#    col_int  col_float
# B        1        0.2

print(type(df_mix.loc[['B']]))
# <class 'pandas.core.frame.DataFrame'>

print(df_mix.loc[['B']].dtypes)
# col_int        int64
# col_float    float64
# dtype: object

関連カテゴリー

関連記事