pandas.DataFrameのforループ処理(イテレーション)
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で削除された。
- What’s new in 2.0.0 (April 3, 2023) — pandas 2.1.4 documentation
- DEPR: Series/DataFrame/HDFStore.iteritems() by mroeschke · Pull Request #45321 · pandas-dev/pandas
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
# ======
引数index
をFalse
にすると行名はnamedtuple
に含まれない。また、引数name
でnamedtuple
の名前を指定できる。
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
# ======
引数name
をNone
にするとノーマルのタプルを返す。
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ループを使わないのが一番良い。