# Swifterとかで移動平均を計算 fanannanです。この記事はfintalk Advent Calendar 2019の18日目です。 ここのボスになんか書けと言われたので書きました。基本的には@drillerせんせいの記事のぱくりです(申請済)。 swifterとは、pandasのようなテーブルデータ(DataFrame)に関数を適用する(apply)速度を速くできるライブラリです。ノーマルのpandasをちょっと変更するだけで扱えるのが特徴です。 pandasの代替ライブラリのなかには、高速化を謳っているものても、使いみちによってはpandasよりもむしろ遅くなってしまうものも多いようですが、swifterは(swifterが使える場面では)ほぼどんなケースでもちゃんと速くなるらしいです。気になったのでちょろっと試してみました。金融ネタということで、今回は移動平均を算出してみます。 ## インストール swifterは、pandasの代替ではなく、pandasに追加して使うライブラリなので、pandasが入っていない場合には、pandasもインストールする必要があります。 ```bash $ pip install -U pandas $ pip install -U swifter ``` condaをお使いのかたはこちらでいけます。 ```bash conda install -c conda-forge swifter ``` ## サンプルデータ 時系列データを用意するのが大変なので、今回は乱数で代用することにします。 ```python import numpy as np import pandas as pd import swifter t = np.arange("1900-01-01", "2019-12-25", dtype=np.datetime64) np.random.seed(seed=0) y = np.random.rand(len(t)) ``` これで日付時系列tと乱数列y が用意できたので、これらから pandasのDataFrameを作成します。 ```python df_pd = pd.DataFrame({"t": t, "y": y}) df_pd.head() ``` :::info ||t|y |-|-|- |0| 1900-01-01| 0.548814 |1| 1900-01-02| 0.715189 |2| 1900-01-03| 0.602763 |3| 1900-01-04| 0.544883 |4| 1900-01-05| 0.423655 ::: ## 速度比較 まずは、pandasで5日間の移動平均を算出してみます。 ```python %timeit df_pd.rolling(5).mean() ``` :::info 997 µs ± 5.87 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) ::: ここで念のため、計算結果も確認しておきます。 ```python df_pd.rolling(5).mean().tail() ``` :::info ||y |-|- |43817| 0.581484 |43818| 0.483147 |43819| 0.460279 |43820| 0.409074 |43821| 0.329882 ::: つぎは、swifterを使う場合です。 ```Python %timeit df_pd.swifter.rolling(5).apply(np.mean) ``` :::info 321 ms ± 3.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) ::: 一瞬眼を疑いました。桁が違いますね。µs ではなくmsです。全然ダメですね。期待はずれです。 ちなみに、こんな風に書くと余計な表示を消したり、スレッド数を変えたりできます。 ``` df_pd.swifter.set_npartitions(npartitions=8).progress_bar(False).rolling(5).apply(np.mean, raw=False) ``` いろいろ条件を変えてやってみましたが、徒労に終わりました。データフレームのサイズを大きくしたら良くなるかと思いましたが、却ってひどくなりました。 ということで、ほかの実験は省略します。 そういえば、移動平均ではrollingを使っていますが、そうではないものでもやってみましょう。 ```python %timeit df_pd.apply(lambda x:x) ``` :::info 329 ms ± 10.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) ::: ```python %timeit df_pd.swifter.apply(lambda x:x) ``` :::info 1.44 ms ± 38.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) ::: んむむ? では、データフレームのサイズを100倍にしてみます。 ``` %timeit df_pd100.apply(lambda x:x) ``` :::info 31.4 s ± 441 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) ::: ```python %timeit df_pd100.swifter.apply(lambda x:x) ``` :::info 1.62 ms ± 50 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) ::: なんだ、圧倒的じゃないか、我が軍は。 Vaexではpandasの置き換えになるので、pandasとの機能の違いで困ることもあるようですが、swifterの場合は、必要に応じてpandasに付加するだけなので、pandasの機能はそのままに使えます。お手軽にapplyを速くしたいときに使うのが良さそうです。 おしまい。