pandasの高速化

pandasの高速化

import numpy as np
import pandas as pd
from pandarallel import pandarallel
import swifter

ここではorderbookのデータをDataFrameに読み込みます。

raw_df = pd.read_pickle("btcusd_2020-07-08.pickle")
raw_df.head()
price size timestamp side
2020-07-08 22:00:05 9433.24 2.305583 2020-07-08 22:00:05 bid
2020-07-08 22:00:05 9434.32 1.000000 2020-07-08 22:00:05 ask
2020-07-08 22:00:05 9434.35 0.530100 2020-07-08 22:00:05 ask
2020-07-08 22:00:05 9434.90 0.015848 2020-07-08 22:00:05 ask
2020-07-08 22:00:05 9435.00 0.050000 2020-07-08 22:00:05 ask

depth=10の板情報からbid/askが最も近い価格(price)と枚数(size)を抽出します。

bid = (
    raw_df.groupby("side")
    .get_group("bid")
    .groupby("timestamp")[["price", "size"]]
    .max()
)
ask = (
    raw_df.groupby("side")
    .get_group("ask")
    .groupby("timestamp")[["price", "size"]]
    .min()
)
df = pd.concat([bid, ask], axis=1)
df.index.name = None
df.columns = "bid_price", "bid_size", "ask_price", "ask_size"
df.head()
bid_price bid_size ask_price ask_size
2020-07-08 22:00:05 9433.24 2.305583 9433.25 0.015848
2020-07-08 22:00:06 9433.24 2.305583 9433.25 0.015848
2020-07-08 22:00:07 9433.24 2.323611 9433.25 0.015848
2020-07-08 22:00:08 9433.61 1.700000 9433.62 0.015848
2020-07-08 22:00:09 9433.51 1.650000 9433.52 0.015848

サンプルとして、priceとsizeから仲値を算出する関数を作成します。

def get_mid_price(bid, bid_sz, ask, ask_sz):
    try:
        mid_price = ask + (ask_sz / (ask_sz + bid_sz) * (bid - ask))
    except ZeroDivisionError:
        mid_price = None
    return mid_price


def get_mid_price_from_series(ser):
    return get_mid_price(*ser)


vfunc = np.vectorize(get_mid_price)

作成した関数を各行に対して apply メソッドで適用します。

%timeit df.apply(get_mid_price_from_series, axis=1)
59.8 ms ± 108 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

numpy.vectorize はベクトル化した関数を定義します。引数には array-like なオブジェクトを渡します。

%timeit vfunc(*df.T.values)
2.46 ms ± 6.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

ちなみに

get_mid_price 関数の処理は関数化をしなくとも、演算子を使った式で算出できます

bid_, bid_sz_, ask_, ask_sz_ = df.T.values
%timeit ask_ + (ask_sz_ / (ask_sz_ + bid_sz_) * (bid_ - ask_))
17.1 µs ± 43.8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

ちなみに

get_mid_price 関数内の処理は配列(numpy.ndarray)に対応しているため、この関数の引数にSeriesなどを渡せます

%timeit get_mid_price(*df.T.values)
132 µs ± 611 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
pandarallel.initialize()
INFO: Pandarallel will run on 1 workers.
INFO: Pandarallel will use Memory file system to transfer data between the main process and workers.

DataFrameから apply メソッドの代わりに parallel_apply メソッドを実行すると、関数を並列に適用します。

%timeit df.parallel_apply(get_mid_price_from_series, axis=1)
109 ms ± 4.56 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit df.swifter.apply(get_mid_price_from_series, axis=1)
115 ms ± 434 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)