LoginSignup
3
3

More than 5 years have passed since last update.

大きなDataFrameで次に条件を満たす行を抽出する

Last updated at Posted at 2019-01-04

本来の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

問題設定

DataFrameの作成
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を自作する

方法1
%%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

方法2,3
%%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万行くらいまでならこれで良いような気がします。
  

方法4,5
%%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にしてしまったほうがまだマシっぽい。
  

方法6
%%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条件式)]が良い。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3