Groupby と Resample
目次
Groupby と Resample¶
参照
Groupbyとは¶
1つのデータを複数のグループに分割する(Splitting)
分割した各データに関数を適用して値を得る (Applying)
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 |
ちなみに
by=
に渡す column 名は、複数指定可。その場合はリストで渡す。適用できるメソッド一覧
複数適用させたい場合は、
agg
もしくはaggregate
メソッドを使う自作の関数を使いたい場合は、
apply
メソッドを使う
分割されたデータを確認したい¶
groupby で得られた
groupby object
をリストやループに入れる。ただし巨大なデータは時間がかかる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つをまずは抑えてください。Datetimeindex や Periodindex といった、時間を表すindexを持つデータにしか使えない
どの時間の単位でデータを分割するかを指定する。その際に渡す文字列を "Frequency String" と呼ぶ。
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
ちなみに
曜日毎にグループ化したい場合は resample ではなく groupby
index.strftime("%w")
で曜日番号、もしくはindex.strftime("%a")
で曜日文字列を得て、カラムに追加。そのカラムで groupby する.resample("w")
は 週
各国の営業日や、マーケットのオープン時間などを知りたい場合
金融データのResampling¶
取引データからOHLCVを作成¶
取引データ取得
Datetimeindex を持つ DataFrame に変換
時間単位を指定してResampler Objectを作成
.ohlc()
メソッドを適用.sum()
メソッドを適用(出来高).count()
メソッドを適用(取引回数)表示
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()
メソッドを適用¶
resampler オブジェクトのメソッドとして .ohlc() が用意されている
price
データを 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