note.nkmk.me

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

Date: 2017-12-08 / Modified: 2019-08-21 / tags: Python, pandas

pandas.DataFrameをfor文でループ処理(イテレーション)する場合、単純にそのままfor文で回すと列名が返ってくるだけなので、繰り返し処理のためのメソッドを使って列ごと・行ごと(一列ずつ・一行ずつ)の値を取得する。

以下のpandas.DataFrameを例とする。

import pandas as pd

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

以下の内容について説明する。

  • pandas.DataFrameをそのままforループに適用
  • 1列ずつ取り出す
    • DataFrame.iteritems()メソッド
  • 1行ずつ取り出す
    • DataFrame.iterrows()メソッド
    • DataFrame.itertuples()メソッド
  • 特定の列の値を順に取り出す
  • ループ処理で値を更新する
  • 処理速度

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

スポンサーリンク

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

pandas.DataFrameをそのままforループにつっこんだ場合は以下のようにコラム名(列名)が順に取り出される。

for column_name in df:
    print(type(column_name))
    print(column_name)
    print('======\n')
# <class 'str'>
# age
# ======
# 
# <class 'str'>
# state
# ======
# 
# <class 'str'>
# point
# ======
# 

特殊メソッド__iter__()が呼ばれている。

for column_name in df.__iter__():
    print(type(column_name))
    print(column_name)
    print('======\n')
# <class 'str'>
# age
# ======
# 
# <class 'str'>
# state
# ======
# 
# <class 'str'>
# point
# ======
# 

1列ずつ取り出す

DataFrame.iteritems()メソッド

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

pandas.Seriesはインデックス名などを指定してその行の値を取り出せる。

for column_name, item in df.iteritems():
    print(type(column_name))
    print(column_name)
    print('~~~~~~')

    print(type(item))
    print(item)
    print('------')

    print(item['Alice'])
    print(item[0])
    print(item.Alice)
    print('======\n')
# <class 'str'>
# age
# ~~~~~~
# <class 'pandas.core.series.Series'>
# Alice    24
# Bob      42
# Name: age, dtype: int64
# ------
# 24
# 24
# 24
# ======
# 
# <class 'str'>
# state
# ~~~~~~
# <class 'pandas.core.series.Series'>
# Alice    NY
# Bob      CA
# Name: state, dtype: object
# ------
# NY
# NY
# NY
# ======
# 
# <class 'str'>
# point
# ~~~~~~
# <class 'pandas.core.series.Series'>
# Alice    64
# Bob      92
# Name: point, dtype: int64
# ------
# 64
# 64
# 64
# ======
# 

1行ずつ取り出す

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

特定の列の値のみが必要なのであれば、次に説明するように列を指定して個別にforループで回したほうがさらに速い。

処理速度についての実験結果は最後に示す。

DataFrame.iterrows()メソッド

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

pandas.Seriesはコラム名などを指定してその列の値を取り出せる。

for index, row in df.iterrows():
    print(type(index))
    print(index)
    print('~~~~~~')

    print(type(row))
    print(row)
    print('------')

    print(row['point'])
    print(row[2])
    print(row.point)
    print('======\n')
# <class 'str'>
# Alice
# ~~~~~~
# <class 'pandas.core.series.Series'>
# age      24
# state    NY
# point    64
# Name: Alice, dtype: object
# ------
# 64
# 64
# 64
# ======
# 
# <class 'str'>
# Bob
# ~~~~~~
# <class 'pandas.core.series.Series'>
# age      42
# state    CA
# point    92
# Name: Bob, dtype: object
# ------
# 92
# 92
# 92
# ======
# 

DataFrame.itertuples()メソッド

itertuples()メソッドを使うと、1行ずつ、インデックス名(行名)とその行のデータのタプルを取得できる。タプルの最初の要素がインデックス名となる。

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

for row in df.itertuples():
    print(type(row))
    print(row)
    print('------')

    print(row[3])
    print(row.point)
    print('======\n')
# <class 'pandas.core.frame.Pandas'>
# Pandas(Index='Alice', age=24, state='NY', point=64)
# ------
# 64
# 64
# ======
# 
# <class 'pandas.core.frame.Pandas'>
# Pandas(Index='Bob', age=42, state='CA', point=92)
# ------
# 92
# 92
# ======
# 

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

for row in df.itertuples(name=None):
    print(type(row))
    print(row)
    print('------')

    print(row[3])
    print('======\n')
# <class 'tuple'>
# ('Alice', 24, 'NY', 64)
# ------
# 64
# ======
# 
# <class 'tuple'>
# ('Bob', 42, 'CA', 92)
# ------
# 92
# ======
# 

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

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

pandas.DataFrameの列はpandas.Seriesとなる。

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

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

pandas.Seriesをforループに適用するとその値が順に取得できるので、pandas.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()メソッドはビューではなくコピーを返すので、pandas.Seriesを変更しても元データは更新されない。

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[]については以下の記事も参照。

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

上の例と同じ処理。上で更新したオブジェクトをさらに更新している。

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

新たな列の追加も可能。

df['new'] = df['point'] + df['age'] * 2
print(df)
#        age state  point  new
# Alice   24    NY    112  160
# Bob     42    CA    176  260

単純な四則演算のほか、列の各要素にNumPyの関数を適用することもできる。以下は平方根の例。なお、ここではpd.npでNumPyの関数にアクセスしているが、NumPyを別途インポートしてももちろん構わない。

df['age_sqrt'] = pd.np.sqrt(df['age'])
print(df)
#        age state  point  new  age_sqrt
# Alice   24    NY    112  160  4.898979
# Bob     42    CA    176  260  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    112  160  4.898979       n
# Bob     42    CA    176  260  6.480741       c

処理速度

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

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

import pandas as pd

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

print(df.head())
#     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
# 3  30  31  32  33  34  35  36  37  38  39
# 4  40  41  42  43  44  45  46  47  48  49

print(df.tail())
#       0    1    2    3    4    5    6    7    8    9
# 95  950  951  952  953  954  955  956  957  958  959
# 96  960  961  962  963  964  965  966  967  968  969
# 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
# 4.53 ms ± 325 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit
for t in df.itertuples():
    pass
# 981 µs ± 43.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%%timeit
for t in df.itertuples(name=None):
    pass
# 718 µs ± 10.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

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

%%timeit
for i, j, k in zip(df[0], df[4], df[9]):
    pass
# 46.1 µs ± 588 ns per loop (mean ± std. dev. of 7 runs, 10000 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
# 147 µs ± 3.78 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

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

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

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

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事