本来のDataFrameの使い方では行の順番に依存しない方がよいと思いますが、pandasで時系列に関連したデータをあつかっていると、条件を満たす次の行を選び出したいことがたまにあります。
pandas公式ではそれに該当するようなmethodがなく、大きなDataFrameではすぐ近くに条件を満たす行があることは分かっていてもかなり時間がかかってしまいます。
先に結論
windowのgeneratorを自作すると速い。
手軽に試したいならdf[(df条件式)&(df.index条件式)]
が良い。
なお、numbaやCythonなどを使った高速化はおこなっていません。
実験環境
windows10, i7-4600U, python3.6.7, pandas 0.23.4, jupyter-notebook
問題設定
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame(np.random.rand(100000000), columns=['A'])
df.iat[10000,0]
>> 0.7482679812896987
この1億行のDataFrameから、10000行目以降で0.9999を上回る行を見つけたいとします。実際には、15000行目くらいにあります。
なお、各試行前にdel dfして作りなおしています。
方法1:windowのgeneratorを自作する
%%time
def window_generator(_series, window):
l=_series.shape[0]
i=0
while i * window < l:
yield _series.iloc[slice(i*window, min((i+1)*window, l))]
i+=1
gen = window_generator(df.iloc[10000:,0], 10000)
for _se in gen:
temp = _se[_se > 0.9999]
if not temp.empty:
print(temp.iat[0])
break
else:
pass
>> 0.9999620173413053
>> Wall time: 13.9 ms
ということで本命。そこそこ速いです。seriesをwindow幅ごとに切って、1個ずつyieldしてます。見つかれば吉、見つからなければもう1個yield。これはwindow幅を10000と見つかる幅に設定しているのがポイントで、この幅の設定を誤るとかえって遅くなります。for文だけで書いてあげても良いですが、他でも使い回せるので。
ただ、generatorをつくるオーバーヘッドがあるので、100万行くらいまでだと下記のワンライナーに対する優位性が失われます。
方法2~6
%%time
df[(df['A']>0.9999)&(df.index>10000)].iat[0,0]
>> Wall time: 685 ms
>> 0.9999620173413053
%%time
df.loc[(df['A']>0.9999)&(df.index>10000), 'A'].iat[0]
>> Wall time: 654 ms
>> 0.9999620173413053
どちらも大差ないですね。ワンライナーで書けますし、100万~1000万行くらいまでならこれで良いような気がします。
%%time
df.loc[10000:, 'A'].pipe(lambda x:x[x>0.9999]).iat[0]
>> Wall time: 936 ms
>> 0.9999620173413053
%%time
df.loc[10000:, :].query('A > 0.9999').iat[0,0]
>> Wall time: 1.78 s
>> 0.9999620173413053
10000行以降、という条件を先に抽出してみますが、かえって遅くなりました。Seriesにはqueryメソッドが使えないのでpipeにしています。DataFrameのままquery文で処理するよりは、先にseriesにしてしまったほうがまだマシっぽい。
%%time
for i, v in df.iloc[10000:,0].iteritems():
if v > 0.9999:
print(i,v)
break
>> 14988 0.9999620173413053
>> Wall time: 6.34 s
お試しに。10000行目から1行ずつ回してみるという方法。5000行目くらいで見つかるがかなり遅い。ただこれ、何行目で見つかるときもあまり変わらなかったので、おそらく最初のiteritemsを準備するときにかなりの時間がかかっていそうです。
もう一度結論
windowのgeneratorを自作すると速い。
手軽に試したいならdf[(df条件式)&(df.index条件式)]
が良い。