ループで回してもメモリが32GB程度あるPCなら数千万行くらいまでは大丈夫なのですが、いくつか注意点があります!
- Pandasのデータフレームのatやiloc、locなどでの特定の行へのアクセスはとても遅いです!
- そのため、applyなどのメソッドを使うか、一旦リストに直してループを回すと速度的に問題なく処理ができます!
参考 :
※2番目の記事で丁寧に比較してくださった方の情報によると、リストに直す方が速めです!
試しにJupyterで特定位置の行にアクセスする方法でatとリストのインデックスにアクセスする方法を比較してみると、リストの方がはるかに速いとが確認できます(%%timeit
という箇所は、マジックコマンドと呼ばれるJupyterの速度計測用の機能を使っています)。
from datetime import date
import pandas as pd
# 仮データです。
df = pd.DataFrame(
data=[{
'date': date(2020, 1, 1),
'animal': 'ネズミ',
}, {
'date': date(2020, 1, 3),
'animal': 'ウサギ',
}, {
'date': date(2020, 1, 4),
'animal': 'コアラ',
}, {
'date': '餅が食べたい',
'animal': '私も食べたい',
}, {
'date': date(2020, 1, 1),
'animal': 'ゴリラ',
}])
データフレームのatで特定行にアクセスする場合の速度
%%timeit
df.at[2, 'date']
3.07 µs ± 147 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
※1回辺り約3マイクロ秒(1 / 1000 / 1000秒)
# 特定のカラムをリストに一旦変換。
date_list = df['date'].tolist()
25.7 ns ± 3.76 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
※1回辺り約25ナノ秒(1 / 1000 / 1000 / 1000秒)
また、isinstance関数も1回辺りナノ秒の世界なので特にパフォーマンスでネックになったりはしないと思われます!
%%timeit
isinstance(date_val, date)
56.4 ns ± 1.58 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
そのため、リスト変換を挟んで対応すると以下のようになります!
from datetime import date
import pandas as pd
# 仮データです。
df = pd.DataFrame(
data=[{
'date': date(2020, 1, 1),
'animal': 'ネズミ',
}, {
'date': date(2020, 1, 3),
'animal': 'ウサギ',
}, {
'date': date(2020, 1, 4),
'animal': 'コアラ',
}, {
'date': '餅が食べたい',
'animal': '私も食べたい',
}, {
'date': date(2020, 1, 1),
'animal': 'ゴリラ',
}])
# 日付の行であればTrueを格納するリスト(is_date_bools)を
# 作成・判定しています。
date_list = df['date'].tolist()
is_date_bools = []
for date_val in date_list:
is_date_bools.append(isinstance(date_val, date))
print('-' * 20)
print('各行の判定結果:', is_date_bools)
print('-' * 20)
# 日付判定がTrueの行のみ残したデータフレームを取得します。
sliced_df = df[is_date_bools]
print('算出結果:\n', sliced_df)
--------------------
各行の判定結果: [True, True, True, False, True]
--------------------
算出結果:
animal date
0 ネズミ 2020-01-01
1 ウサギ 2020-01-03
2 コアラ 2020-01-04
4 ゴリラ 2020-01-01
些細な点ですが、真偽値との比較は〇〇 == True
といったようにせずに、真偽値単体で比較するように、とPEP8で定められているので、df[is_date_bools == True]
とせずにdf[is_date_bools]
としています!(確か前者だとflake8やらautopep8などのLintに引っかかってしまったかもしれません)
参考 : [Pythonコーディング規約]PEP8を読み解く
試しに100万行くらいのデータで速度を測ってみます!(NumPyを使ってランダムな値を各行で選択してデータフレームを作っています)
import numpy as np
date_choices = (
date(2020, 1, 1),
date(2020, 1, 2),
date(2020, 1, 3),
'餅が食べたい',
)
animal_choices = (
'ネズミ',
'ウサギ',
'コアラ',
'ゴリラ',
)
df = pd.DataFrame(
columns=['date', 'animal'],
index=np.arange(0, 1000000))
df['date'] = np.random.choice(date_choices, size=len(df))
df['animal'] = np.random.choice(animal_choices, size=len(df))
print('用意された100万行のデータフレーム:\n', df)
用意された100万行のデータフレーム:
date animal
0 2020-01-03 コアラ
1 2020-01-01 ネズミ
2 2020-01-02 コアラ
3 2020-01-03 ウサギ
4 餅が食べたい ネズミ
... ... ...
999995 餅が食べたい コアラ
999996 2020-01-03 ネズミ
999997 2020-01-02 ゴリラ
999998 2020-01-01 ネズミ
999999 2020-01-02 ネズミ
[1000000 rows x 2 columns]
100万行で処理時間を測ってみるサンプル
%%timeit
date_list = df['date'].tolist()
is_date_bools = []
for date_val in date_list:
is_date_bools.append(isinstance(date_val, date))
sliced_df = df[is_date_bools]
192 ms ± 791 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
100万行で私の環境で192ミリ秒(約0.2秒)なので、(びったり線形に計算時間が伸びないところもあるとは思いますが)数千万行程度であれば数秒で終わると思われます!
さらに大きいデータ、例えば億行の世界でメモリや計算時間が厳しい・・・みたいなケースでは、Pandasのように扱えて大規模データで計算ができるDaskやVaexなどのライブラリの利用をご検討ください
参考 : PythonのDaskをしっかり調べてみた(大きなデータセットを快適に扱う)
※参考になりましたらLGTMぽちっと押していただけますと喜びます・・・!