ある一覧データから別の一覧データのキーに存在するものを抽出するという要件があります。
Pandasで言うと、上図のように、2つのDataFrameをmerge(つまり表データのJOINの論法ですね)することで、このようなデータを取得するというのが私の手グセなのですが、結合したDataFrameで後続の処理を行うことも多いので、この方法に特に疑問を持っていませんでした。
今更なのですが、この処理方式って自分が思うよりコスト高な処理なのではとふと頭をよぎったので、今回、同じようなことができる複数のロジック表記例について、雑に時間計測してみました。
前準備
import pandas as pd import numpy as np import random np.random.seed(seed=64) random.seed(64) lista = list(map(str, list(np.random.rand(1000000)))) df_orig = pd.DataFrame([[i, j] for i, j in zip(lista, lista)], columns=['a', 'b']) foo = df_orig.set_index('a').to_dict(orient='index') df = df_orig.copy() listb = random.sample(lista, int(len(lista)/3)) df2 = pd.DataFrame([[i, j] for i, j in zip(listb, listb)], columns=['a', 'b']) bar = df2.set_index('a').to_dict(orient='index')
比較例一覧
def a(df2): # mergeでフィルターです。 # 生成したDataFrameの二次利用もふまえるとそんなに悪くない(個人的にはスキな)イディオムだと思うのですが.... df3 = pd.merge(df, df2, how='inner', on='a') df3['a'] def b(listb): # isinです。 cond = df['a'].isin(listb) df[cond] def c(listb): # これは比較用の例で、Pandasは使われていません。 for i in listb: foo.get(i) def c2(bar): # これは比較用の例で、Pandasは使われていません。 for i in foo.keys(): bar.get(i) def d(): # mergeではなく、applyを使う案のひとつです。 # DataFrame全体をターゲットにしているため、これは遅いです。 df.apply(lambda s: foo.get(s['a']), axis=1) def d2(): # d()の要件であれば、最初にSeriesを取り出した方が良いですね。 df['a'].apply(lambda x: foo.get(x)) def e(): # mergeではなく、縦に結合して、重複(=キーが重なる。つまり、対象のデータである)を抜き出すことで、該当の要件を実現します。 # この例では、全ての項目が同じものがあぶり出されます(ので、オーバースペックというか、ここまでの要件をぴったり実現したものではありません) df3 = pd.concat([df, df2]) df3[df3.duplicated()] def e2(): # e()の実際の例 # 個人の感想ですが、「関数型プログラミング」ぽいアプローチかも。 df3 = pd.concat([df, df2]) df3[df3.duplicated('a')]
計測結果
In [6]: %time a(df2) ...: ...: %time b(listb) ...: ...: %time c(listb) ...: ...: %time c2(bar) ...: ...: %time d() ...: ...: %time d2() ...: ...: %time e() ...: ...: %time e2() CPU times: user 693 ms, sys: 84.7 ms, total: 777 ms Wall time: 777 ms CPU times: user 422 ms, sys: 4.78 ms, total: 427 ms Wall time: 428 ms CPU times: user 174 ms, sys: 289 µs, total: 174 ms Wall time: 174 ms CPU times: user 251 ms, sys: 363 µs, total: 252 ms Wall time: 252 ms CPU times: user 10.4 s, sys: 16.4 ms, total: 10.4 s Wall time: 10.5 s CPU times: user 431 ms, sys: 2.29 ms, total: 433 ms Wall time: 435 ms CPU times: user 732 ms, sys: 48.4 ms, total: 780 ms Wall time: 784 ms CPU times: user 420 ms, sys: 11.8 ms, total: 432 ms Wall time: 433 ms In [7]:
所感(mergeでフィルターを代用するのはそれほど悪くないかも)
データバリエーションや複数回計測していない状況なので、あくまで所感ですが、個人的なフェイバリットのmerge方式(関数aの例)は、生成したDataFrameの二次利用に続くならそれほど悪くないと思いました。
抽出自体のみで良い場合は、isin(リスト)方式(関数bの例)がコードが簡潔であることも含めて、優勝かな。