pandas.DataFrameのforループ処理(イテレーション)

Modified: | Tags: Python, pandas

pandasでDataFrameをfor文でループ処理(イテレーション)する場合、単純にそのままfor文で回すと列名が返ってくる。繰り返し処理のためのメソッドitems()(旧称iteritems())やiterrows()などを使うと、1列ずつ・1行ずつ取り出せる。

なお、後半で述べるように、DataFrameに対する多くの処理はfor文なしで実現できる。

Pythonにおけるfor文についての詳細は以下の記事を参照。

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

import pandas as pd

print(pd.__version__)
# 2.1.4

df = pd.DataFrame({'age': [24, 42], 'state': ['NY', 'CA'], 'point': [64, 92]},
                  index=['Alice', 'Bob'])
print(df)
#        age state  point
# Alice   24    NY     64
# Bob     42    CA     92

DataFrameをそのままforループに適用

DataFrameをそのままforループで回すと、列名が順に取り出される。

for column_name in df:
    print(column_name)
# age
# state
# point

1列ずつ取り出す: items()(旧称iteritems())

items()メソッドを使うと、列名とその列のデータ(Series)のタプル(column name, Series)を1列ずつ取得できる。

各要素の値はSeriesのラベル名を指定すると取り出せる。

for column_name, item in df.items():
    print(column_name)
    print(type(item))
    print(item['Alice'], item['Bob'])
    print('======')
# age
# <class 'pandas.core.series.Series'>
# 24 42
# ======
# state
# <class 'pandas.core.series.Series'>
# NY CA
# ======
# point
# <class 'pandas.core.series.Series'>
# 64 92
# ======

なお、以前はiteritems()という名前だったがitems()に変更された。iteritems()はpandas 2.0で削除された。

1行ずつ取り出す: iterrows(), itertuples()

DataFrameから1行ずつ取り出すメソッドにはiterrows()itertuples()がある。itertuples()のほうが高速。

特定の列の値のみが必要なのであれば、次に説明するように列を指定して個別にforループで回すほうがさらに速い。処理速度についての実験結果は最後に示す。

iterrows()メソッド

iterrows()メソッドを使うと、行名とその行のデータ(Series)のタプル(index, Series)を1行ずつ取得できる。

for index, row in df.iterrows():
    print(index)
    print(type(row))
    print(row['age'], row['state'], row['point'])
    print('======')
# Alice
# <class 'pandas.core.series.Series'>
# 24 NY 64
# ======
# Bob
# <class 'pandas.core.series.Series'>
# 42 CA 92
# ======

itertuples()メソッド

itertuples()メソッドを使うと、行のデータをnamedtuple(名前付きタプル)として1行ずつ取得できる。

デフォルトではPandasという名前のnamedtupleを返す。最初の要素が行名となる。namedtupleなので、[]のほか.でも各要素の値にアクセスできる。

for row in df.itertuples():
    print(type(row))
    print(row)
    print(row[0], row[1], row[2], row[3])
    print(row.Index, row.age, row.state, row.point)
    print('======')
# <class 'pandas.core.frame.Pandas'>
# Pandas(Index='Alice', age=24, state='NY', point=64)
# Alice 24 NY 64
# Alice 24 NY 64
# ======
# <class 'pandas.core.frame.Pandas'>
# Pandas(Index='Bob', age=42, state='CA', point=92)
# Bob 42 CA 92
# Bob 42 CA 92
# ======

引数indexFalseにすると行名はnamedtupleに含まれない。また、引数namenamedtupleの名前を指定できる。

for row in df.itertuples(index=False, name='Person'):
    print(type(row))
    print(row)
    print(row[0], row[1], row[2])
    print(row.age, row.state, row.point)
    print('======')
# <class 'pandas.core.frame.Person'>
# Person(age=24, state='NY', point=64)
# 24 NY 64
# 24 NY 64
# ======
# <class 'pandas.core.frame.Person'>
# Person(age=42, state='CA', point=92)
# 42 CA 92
# 42 CA 92
# ======

引数nameNoneにするとノーマルのタプルを返す。

for row in df.itertuples(name=None):
    print(type(row))
    print(row)
    print(row[0], row[1], row[2], row[3])
    print('======')
# <class 'tuple'>
# ('Alice', 24, 'NY', 64)
# Alice 24 NY 64
# ======
# <class 'tuple'>
# ('Bob', 42, 'CA', 92)
# Bob 42 CA 92
# ======

特定の列の値を順に取り出す

上述のiterrows()itertuples()メソッドは各行のすべての要素を返すが、特定の列の要素のみが必要な場合は以下のようにも書ける。

DataFrameの列はSeriesとなる。

print(df['age'])
# Alice    24
# Bob      42
# Name: age, dtype: int64

print(type(df['age']))
# <class 'pandas.core.series.Series'>

Seriesをforループに適用するとその値が順に取得できるので、DataFrameの列を指定してforループに適用するとその列の値が順に取得できる。

for age in df['age']:
    print(age)
# 24
# 42

組み込み関数zip()を使うと、複数列の値をまとめて取得することも可能。

for age, point in zip(df['age'], df['point']):
    print(age, point)
# 24 64
# 42 92

行名(インデックス)を取得したい場合はindex属性を使う。上の例と同様に、他の列とzip()でまとめて取得できる。

print(df.index)
# Index(['Alice', 'Bob'], dtype='object')

print(type(df.index))
# <class 'pandas.core.indexes.base.Index'>

for index in df.index:
    print(index)
# Alice
# Bob

for index, state in zip(df.index, df['state']):
    print(index, state)
# Alice NY
# Bob CA

ループ処理で値を更新する

1行ずつ取り出すiterrows()メソッドが返すSeriesはビューではなくコピーである場合があるので、それを変更しても元データは更新されない可能性がある。

print(df)
#        age state  point
# Alice   24    NY     64
# Bob     42    CA     92

for index, row in df.iterrows():
    row['point'] += row['age']

print(df)
#        age state  point
# Alice   24    NY     64
# Bob     42    CA     92

at[]で元のDataFrameの要素を選択して処理すると更新される。

for index, row in df.iterrows():
    df.at[index, 'point'] += row['age']

print(df)
#        age state  point
# Alice   24    NY     88
# Bob     42    CA    134

なお、at[]を使った例はあくまでもこういうこともできるという例であり、多くの場合、要素を更新したり既存の列を処理して新たな列として追加するのにforループを使う必要はない。forループを使わないほうがシンプルに書けるし、処理速度も速い。具体例は次に紹介する。

forループを使わずにDataFrameを処理

前節の例と同じ処理は以下のように書ける。

df = pd.DataFrame({'age': [24, 42], 'state': ['NY', 'CA'], 'point': [64, 92]},
                  index=['Alice', 'Bob'])
print(df)
#        age state  point
# Alice   24    NY     64
# Bob     42    CA     92

df['point'] += df['age']
print(df)
#        age state  point
# Alice   24    NY     88
# Bob     42    CA    134

既存の列を処理して新たな列として追加することも可能。

df['new'] = df['point'] + df['age'] * 2 + 1000
print(df)
#        age state  point   new
# Alice   24    NY     88  1136
# Bob     42    CA    134  1218

+*などの算術演算子による演算のほか、列の各要素にNumPyの関数を適用することもできる。以下は平方根の例。

import numpy as np

df['age_sqrt'] = np.sqrt(df['age'])
print(df)
#        age state  point   new  age_sqrt
# Alice   24    NY     88  1136  4.898979
# Bob     42    CA    134  1218  6.480741

文字列については、列(Series)を直接処理するための文字列メソッドが用意されている。以下は小文字に変換して1文字目を取り出す例。

df['state_0'] = df['state'].str.lower().str[0]
print(df)
#        age state  point   new  age_sqrt state_0
# Alice   24    NY     88  1136  4.898979       n
# Bob     42    CA    134  1218  6.480741       c

map()apply()で任意の関数を要素ごとあるいは行・列ごとに適用することも可能。以下は組み込み関数hex()を適用して16進数表現の文字列に変換する例。

df['point_hex'] = df['point'].map(hex)
print(df)
#        age state  point   new  age_sqrt state_0 point_hex
# Alice   24    NY     88  1136  4.898979       n      0x58
# Bob     42    CA    134  1218  6.480741       c      0x86

処理速度

iterrows(), itertuples()、および、列指定によるforループで1行ずつ値を取得する方法について、処理速度を比較する。

以下のような100行10列のDataFrameを例とする。要素は数値のみ、行名index、列名columnsもデフォルトの連番、というシンプルな例。

import pandas as pd
import numpy as np

df = pd.DataFrame(np.arange(1000).reshape(100, 10))
print(df.shape)
# (100, 10)

print(df.head(3))
#     0   1   2   3   4   5   6   7   8   9
# 0   0   1   2   3   4   5   6   7   8   9
# 1  10  11  12  13  14  15  16  17  18  19
# 2  20  21  22  23  24  25  26  27  28  29

print(df.tail(3))
#       0    1    2    3    4    5    6    7    8    9
# 97  970  971  972  973  974  975  976  977  978  979
# 98  980  981  982  983  984  985  986  987  988  989
# 99  990  991  992  993  994  995  996  997  998  999

以下のコードはJupyter Notebook上でマジックコマンド%%timeitを使って計測したもの。Pythonスクリプトとして実行しても計測されないので注意。

%%timeit
for i, row in df.iterrows():
    pass
# 735 µs ± 20.5 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

%%timeit
for t in df.itertuples():
    pass
# 202 µs ± 1.74 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

%%timeit
for t in df.itertuples(name=None):
    pass
# 148 µs ± 780 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

%%timeit
for i in df[0]:
    pass
# 4.27 µs ± 30.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

%%timeit
for i, j, k in zip(df[0], df[4], df[9]):
    pass
# 13.5 µs ± 53.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

%%timeit
for t in zip(df[0], df[1], df[2], df[3], df[4], df[5], df[6], df[7], df[8], df[9]):
    pass
# 41.3 µs ± 281 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

iterrows()は各行をSeriesに変換するのでかなり遅い。itertuples()iterrows()よりも速いが、列を指定する方法が最も高速。例の環境では全列取り出してもitertuples()より速かった。

100行くらいであれば特に気にならないが、さらに行数が増えるとiterrows()は体感できるほど遅くなる。そのような場合はitertuples()や列指定を使ってみるといいだろう。

もちろん、上述のように、forループなしで実現できる処理はforループを使わないのが一番良い。

関連カテゴリー

関連記事