Groupby と Resample

Groupbyとは

  1. 1つのデータを複数のグループに分割する(Splitting)

  2. 分割した各データに関数を適用して値を得る (Applying)

  3. 2で得た値をデータに一つにまとめる (Combining)

import pandas as pd
import numpy as np

df = pd.DataFrame(
    {
        "date": pd.date_range(start="2000-1-1 0:0:0", periods=9, freq="H"),
        "class": np.array(["A", "B", "C"]).repeat(3),
        "value A": np.arange(1,10),
        "value B": np.arange(1,10) * 100,
    }
)
# データ確認
df
date class value A value B
0 2000-01-01 00:00:00 A 1 100
1 2000-01-01 01:00:00 A 2 200
2 2000-01-01 02:00:00 A 3 300
3 2000-01-01 03:00:00 B 4 400
4 2000-01-01 04:00:00 B 5 500
5 2000-01-01 05:00:00 B 6 600
6 2000-01-01 06:00:00 C 7 700
7 2000-01-01 07:00:00 C 8 800
8 2000-01-01 08:00:00 C 9 900
# class 毎にデータを分割
df_grouped = df.groupby(by="class")
df_grouped
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fb0847e8790>
# グループ化した各データの "value A" カラムに max 関数を適用し、一つのデータにまとめる
mx = df_grouped[["value A", "value B"]].max()
# まとめたデータを確認
mx
value A value B
class
A 3 300
B 6 600
C 9 900

ちなみに

分割されたデータを確認したい

  1. groupby で得られた groupby object をリストやループに入れる。ただし巨大なデータは時間がかかる

  2. groupby object のメソッドである、 .get_group() を使う

list(df_grouped)
[('A',
                   date class  value A  value B
  0 2000-01-01 00:00:00     A        1      100
  1 2000-01-01 01:00:00     A        2      200
  2 2000-01-01 02:00:00     A        3      300),
 ('B',
                   date class  value A  value B
  3 2000-01-01 03:00:00     B        4      400
  4 2000-01-01 04:00:00     B        5      500
  5 2000-01-01 05:00:00     B        6      600),
 ('C',
                   date class  value A  value B
  6 2000-01-01 06:00:00     C        7      700
  7 2000-01-01 07:00:00     C        8      800
  8 2000-01-01 08:00:00     C        9      900)]
for k, df in df_grouped:
    print(df)
                 date class  value A  value B
0 2000-01-01 00:00:00     A        1      100
1 2000-01-01 01:00:00     A        2      200
2 2000-01-01 02:00:00     A        3      300
                 date class  value A  value B
3 2000-01-01 03:00:00     B        4      400
4 2000-01-01 04:00:00     B        5      500
5 2000-01-01 05:00:00     B        6      600
                 date class  value A  value B
6 2000-01-01 06:00:00     C        7      700
7 2000-01-01 07:00:00     C        8      800
8 2000-01-01 08:00:00     C        9      900
df_grouped.get_group("A")
date class value A value B
0 2000-01-01 00:00:00 A 1 100
1 2000-01-01 01:00:00 A 2 200
2 2000-01-01 02:00:00 A 3 300

Resample とは

  • "時間"で Groupby すること

  • .groupby() メソッドではなく .resample() メソッドを使う

  • groupby() との違いとして、以下3つをまずは抑えてください。

    1. Datetimeindex や Periodindex といった、時間を表すindexを持つデータにしか使えない

    2. どの時間の単位でデータを分割するかを指定する。その際に渡す文字列を "Frequency String" と呼ぶ。

    3. label オプション

      • どちらのエッジでラベルをつけるかを指定

# 1. 時間を表す index を持つデータに対してのみ使用可

df = pd.DataFrame(
    {
        "date": pd.date_range(start="2000-1-1 0:0:0", periods=9, freq="H"),
        "class": np.array(["A", "B", "C"]).repeat(3),
        "value A": np.arange(1,10),
        "value B": np.arange(1,10) * 100,
    }
)

# date コラムを、このデータのインデックスに設定して上書き
df.set_index("date", inplace=True)
df
class value A value B
date
2000-01-01 00:00:00 A 1 100
2000-01-01 01:00:00 A 2 200
2000-01-01 02:00:00 A 3 300
2000-01-01 03:00:00 B 4 400
2000-01-01 04:00:00 B 5 500
2000-01-01 05:00:00 B 6 600
2000-01-01 06:00:00 C 7 700
2000-01-01 07:00:00 C 8 800
2000-01-01 08:00:00 C 9 900
df.index
DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 01:00:00',
               '2000-01-01 02:00:00', '2000-01-01 03:00:00',
               '2000-01-01 04:00:00', '2000-01-01 05:00:00',
               '2000-01-01 06:00:00', '2000-01-01 07:00:00',
               '2000-01-01 08:00:00'],
              dtype='datetime64[ns]', name='date', freq=None)
# 2. Frequency String
# resample() メソッドは、Resampler object を返す
df_resampled = df.resample("2H")
df_resampled
<pandas.core.resample.DatetimeIndexResampler object at 0x7fb0847e8040>
# あとはgroupby と同様
# 2時間毎に、Value Aの max を取得
df_resampled["value A"].max()
date
2000-01-01 00:00:00    2
2000-01-01 02:00:00    4
2000-01-01 04:00:00    6
2000-01-01 06:00:00    8
2000-01-01 08:00:00    9
Freq: 2H, Name: value A, dtype: int64
# 3. label オプション
# label = 'right' を指定すると、bin の最後のエッジがラベルになる
df.resample("2H", label = "right")["value A"].max()
date
2000-01-01 02:00:00    2
2000-01-01 04:00:00    4
2000-01-01 06:00:00    6
2000-01-01 08:00:00    8
2000-01-01 10:00:00    9
Freq: 2H, Name: value A, dtype: int64

ちなみに

金融データのResampling

取引データからOHLCVを作成

  1. 取引データ取得

  2. Datetimeindex を持つ DataFrame に変換

  3. 時間単位を指定してResampler Objectを作成

  4. .ohlc() メソッドを適用

  5. .sum() メソッドを適用(出来高)

  6. .count() メソッドを適用(取引回数)

  7. 表示

import asyncio

import nest_asyncio
import pandas as pd
import plotly.graph_objects as go
import pybotters
from IPython.display import HTML

nest_asyncio.apply()

1. pybotters 経由で FTX の取引データを取得

async def get_trades(market_name, start_time, end_time):
    async with pybotters.Client(
        apis={"ftx": ["", ""]}, base_url="https://ftx.com/api"
    ) as client:
        res = await client.get(
            f"/markets/{market_name}/trades",
            params={
                "start_time": start_time,
                "end_time": end_time,
            },
        )
        return await res.json()

# 取得したデータを確認
data = asyncio.run(get_trades("BTC-PERP", 1643641200, 1643727600))
data["result"][:3]
[{'id': 3254047916,
  'price': 38526.0,
  'size': 0.1464,
  'side': 'buy',
  'liquidation': False,
  'time': '2022-02-01T14:59:59.690649+00:00'},
 {'id': 3254047915,
  'price': 38526.0,
  'size': 0.0536,
  'side': 'buy',
  'liquidation': False,
  'time': '2022-02-01T14:59:59.690649+00:00'},
 {'id': 3254047888,
  'price': 38524.0,
  'size': 0.0047,
  'side': 'sell',
  'liquidation': False,
  'time': '2022-02-01T14:59:59.288778+00:00'}]

2. DataFrame の作成

  • time コラムを datetimeindex に持つDataFrameを作成

df = pd.DataFrame(data["result"])
df
id price size side liquidation time
0 3254047916 38526.0 0.1464 buy False 2022-02-01T14:59:59.690649+00:00
1 3254047915 38526.0 0.0536 buy False 2022-02-01T14:59:59.690649+00:00
2 3254047888 38524.0 0.0047 sell False 2022-02-01T14:59:59.288778+00:00
3 3254047870 38524.0 0.1210 sell False 2022-02-01T14:59:59.078000+00:00
4 3254047855 38524.0 0.0127 sell False 2022-02-01T14:59:58.777561+00:00
... ... ... ... ... ... ...
4995 3253998379 38475.0 0.1500 sell False 2022-02-01T14:53:19.423013+00:00
4996 3253998377 38475.0 0.0080 sell False 2022-02-01T14:53:19.370232+00:00
4997 3253998376 38475.0 0.2960 sell False 2022-02-01T14:53:19.370232+00:00
4998 3253998375 38475.0 0.6960 sell False 2022-02-01T14:53:19.370232+00:00
4999 3253998374 38476.0 0.0014 buy False 2022-02-01T14:53:19.339001+00:00

5000 rows × 6 columns

df.dtypes
id               int64
price          float64
size           float64
side            object
liquidation       bool
time            object
dtype: object
# time を datetime 型に変更し、この dataframe の index として設定
df = pd.DataFrame(data["result"])
df["time"] = pd.to_datetime(df["time"])
df.set_index("time", inplace=True)
df.sort_index(inplace=True)
df
id price size side liquidation
time
2022-02-01 14:53:19.339001+00:00 3253998374 38476.0 0.0014 buy False
2022-02-01 14:53:19.370232+00:00 3253998377 38475.0 0.0080 sell False
2022-02-01 14:53:19.370232+00:00 3253998375 38475.0 0.6960 sell False
2022-02-01 14:53:19.370232+00:00 3253998376 38475.0 0.2960 sell False
2022-02-01 14:53:19.423013+00:00 3253998379 38475.0 0.1500 sell False
... ... ... ... ... ...
2022-02-01 14:59:58.777561+00:00 3254047855 38524.0 0.0127 sell False
2022-02-01 14:59:59.078000+00:00 3254047870 38524.0 0.1210 sell False
2022-02-01 14:59:59.288778+00:00 3254047888 38524.0 0.0047 sell False
2022-02-01 14:59:59.690649+00:00 3254047915 38526.0 0.0536 buy False
2022-02-01 14:59:59.690649+00:00 3254047916 38526.0 0.1464 buy False

5000 rows × 5 columns

3. resampler object を作成

例:1分足で作成

rule = "1min"
df_resampled = df.resample(rule, label="right")
df_resampled
<pandas.core.resample.DatetimeIndexResampler object at 0x7fb05aced730>

4. .ohlc() メソッドを適用

df_ohlc = df_resampled["price"].ohlc()
df_ohlc
open high low close
time
2022-02-01 14:54:00+00:00 38476.0 38538.0 38452.0 38516.0
2022-02-01 14:55:00+00:00 38517.0 38520.0 38383.0 38395.0
2022-02-01 14:56:00+00:00 38395.0 38461.0 38374.0 38417.0
2022-02-01 14:57:00+00:00 38416.0 38417.0 38336.0 38395.0
2022-02-01 14:58:00+00:00 38395.0 38478.0 38394.0 38476.0
2022-02-01 14:59:00+00:00 38472.0 38473.0 38407.0 38473.0
2022-02-01 15:00:00+00:00 38473.0 38550.0 38473.0 38526.0
# ohlc メソッドを使わずに、OHLCを作るには

df_ohlc_2 = pd.DataFrame(
    {
        "open": df_resampled["price"].first(),
        "high": df_resampled["price"].max(),
        "low": df_resampled["price"].min(),
        "close": df_resampled["price"].last(),
    }
)

df_ohlc_2
open high low close
time
2022-02-01 14:54:00+00:00 38476.0 38538.0 38452.0 38516.0
2022-02-01 14:55:00+00:00 38517.0 38520.0 38383.0 38395.0
2022-02-01 14:56:00+00:00 38395.0 38461.0 38374.0 38417.0
2022-02-01 14:57:00+00:00 38416.0 38417.0 38336.0 38395.0
2022-02-01 14:58:00+00:00 38395.0 38478.0 38394.0 38476.0
2022-02-01 14:59:00+00:00 38472.0 38473.0 38407.0 38473.0
2022-02-01 15:00:00+00:00 38473.0 38550.0 38473.0 38526.0

5. .sum() メソッドを適用(出来高)

  • size を合計して出来高を出す

df_ohlc["volume"] = df_resampled["size"].sum()
df_ohlc
open high low close volume
time
2022-02-01 14:54:00+00:00 38476.0 38538.0 38452.0 38516.0 89.7768
2022-02-01 14:55:00+00:00 38517.0 38520.0 38383.0 38395.0 201.3913
2022-02-01 14:56:00+00:00 38395.0 38461.0 38374.0 38417.0 257.0133
2022-02-01 14:57:00+00:00 38416.0 38417.0 38336.0 38395.0 213.7127
2022-02-01 14:58:00+00:00 38395.0 38478.0 38394.0 38476.0 177.0822
2022-02-01 14:59:00+00:00 38472.0 38473.0 38407.0 38473.0 94.0939
2022-02-01 15:00:00+00:00 38473.0 38550.0 38473.0 38526.0 123.6707

6. .count() メソッドを適用(取引回数)

  • id を数えて取引回数を出す。

  • 行数のカウントなので id 以外でもよい

df_ohlc["count"] = df_resampled["id"].count()
df_ohlc
open high low close volume count
time
2022-02-01 14:54:00+00:00 38476.0 38538.0 38452.0 38516.0 89.7768 410
2022-02-01 14:55:00+00:00 38517.0 38520.0 38383.0 38395.0 201.3913 739
2022-02-01 14:56:00+00:00 38395.0 38461.0 38374.0 38417.0 257.0133 1097
2022-02-01 14:57:00+00:00 38416.0 38417.0 38336.0 38395.0 213.7127 939
2022-02-01 14:58:00+00:00 38395.0 38478.0 38394.0 38476.0 177.0822 763
2022-02-01 14:59:00+00:00 38472.0 38473.0 38407.0 38473.0 94.0939 451
2022-02-01 15:00:00+00:00 38473.0 38550.0 38473.0 38526.0 123.6707 601

関数化

def generate_ohlcv(df_resampled):
    df_ohlc = df_resampled["price"].ohlc()
    df_ohlc["volume"] = df_resampled["size"].sum()
    df_ohlc["count"] = df_resampled["id"].count()
    return df_ohlc

OHLCV を sell と buy で分けて作成

  • FTX から取得したデータに side があるので、side で groupby して、resample する

rule = "1min"
df_buy_resampled = df.groupby("side").get_group("buy").resample(rule, label="right")
df_sell_resampled = df.groupby("side").get_group("sell").resample(rule, label="right")
df_buy = generate_ohlcv(df_buy_resampled)
df_sell = generate_ohlcv(df_sell_resampled)
# コラム名をリネーム
df_buy.rename(columns={c:f"{c}_buy" for c in df_buy.columns}, inplace=True)
df_sell.rename(columns={c:f"{c}_sell" for c in df_sell.columns}, inplace=True)
# DataFrame のConcat
pd.concat([df_buy,df_sell], axis=1)
open_buy high_buy low_buy close_buy volume_buy count_buy open_sell high_sell low_sell close_sell volume_sell count_sell
time
2022-02-01 14:54:00+00:00 38476.0 38536.0 38453.0 38517.0 29.2431 159 38475.0 38538.0 38452.0 38516.0 60.5337 251
2022-02-01 14:55:00+00:00 38517.0 38517.0 38392.0 38395.0 62.0482 224 38520.0 38520.0 38383.0 38391.0 139.3431 515
2022-02-01 14:56:00+00:00 38395.0 38461.0 38375.0 38417.0 130.6459 505 38394.0 38460.0 38374.0 38409.0 126.3674 592
2022-02-01 14:57:00+00:00 38407.0 38417.0 38340.0 38395.0 89.1943 424 38416.0 38416.0 38336.0 38394.0 124.5184 515
2022-02-01 14:58:00+00:00 38395.0 38478.0 38395.0 38478.0 123.8794 477 38394.0 38477.0 38394.0 38476.0 53.2028 286
2022-02-01 14:59:00+00:00 38472.0 38473.0 38411.0 38473.0 51.7610 238 38471.0 38471.0 38407.0 38467.0 42.3329 213
2022-02-01 15:00:00+00:00 38473.0 38550.0 38473.0 38526.0 69.7287 391 38479.0 38549.0 38479.0 38524.0 53.9420 210

アップサンプリングとダウンサンプリング

  • ダウンサンプリング :高頻度から低頻度へ(毎日→毎月)

  • アップサンプリング :低頻度から高頻度へ(毎週→毎日)

  • 今日話した内容は全てダウンサンプリング。

  • アップサンプリングしたい場合も同様に可。データがない場合は NaNが返る。

# 例:1分足で作った df_ohlc の close データを使って 30秒の max を得る
df_ohlc.resample("30s")["close"].max()
time
2022-02-01 14:54:00+00:00    38516.0
2022-02-01 14:54:30+00:00        NaN
2022-02-01 14:55:00+00:00    38395.0
2022-02-01 14:55:30+00:00        NaN
2022-02-01 14:56:00+00:00    38417.0
2022-02-01 14:56:30+00:00        NaN
2022-02-01 14:57:00+00:00    38395.0
2022-02-01 14:57:30+00:00        NaN
2022-02-01 14:58:00+00:00    38476.0
2022-02-01 14:58:30+00:00        NaN
2022-02-01 14:59:00+00:00    38473.0
2022-02-01 14:59:30+00:00        NaN
2022-02-01 15:00:00+00:00    38526.0
Freq: 30S, Name: close, dtype: float64