# 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を速くしたいときに使うのが良さそうです。
おしまい。