GIthubにアップロードしてくれていた(crypto-data-analysis-course)の vol01_data_acquisition_and_cleansing.ipynb を読みました。
そのうち、
# 出来高がゼロの足が連続している場合、取引停止やデータ欠損の兆候
zero_volume = df_1h[df_1h['volume'] == 0]
print(f"=== 出来高ゼロのチェック === ")
print(f" 出来高ゼロの足: {len(zero_volume)} 件 / {len(df_1h):,} 件")
if len(zero_volume) > 0:
# 連続ゼロの検出
is_zero = (df_1h['volume'] == 0).astype(int)
consecutive = is_zero.groupby((is_zero != is_zero.shift()).cumsum()).cumsum()
max_consecutive = consecutive.max()
print(f" 最大連続ゼロ: {max_consecutive} 本")
if max_consecutive >= 3:
print(" ⚠️ 3本以上の連続ゼロ出来高があります。取引停止の可能性があるため要調査")
print(f"\n出来高ゼロの行:")
print(zero_volume[['close', 'volume']].head(10).to_string())
else:
print("→ 出来高ゼロの足はなし(正常)")
の
consecutive = is_zero.groupby((is_zero != is_zero.shift()).cumsum()).cumsum()
部分が何度やって読み解いてもよく分からなかったので、考えていこうと思います。
consecutive = is_zero.groupby((is_zero != is_zero.shift()).cumsum()).cumsum() とは
まず、このまま眺めていてもよく分からないので、分解します。
# 1つ前の行と値が変わった場所を True にする
is_changed = is_zero != is_zero.shift()
# 値が変わるたびにグループ番号を増やす
group_id = is_changed.cumsum()
# グループごとに累積和を取る
# これにより、ゼロ出来高が 1, 2, 3... と数えられる
consecutive = is_zero.groupby(group_id).cumsum()
順番に見ていきます。
is_changed = is_zero != is_zero.shift()
これは、右側の
is_zero != is_zero.shift()
は条件ですね。この結果を
is_changed
に代入するというコードのようです。
では、
is_zero != is_zero.shift()
は何を言っているのでしょうか?
例をもちいて考えていきます。
| 時点 | is_zero | is_zero.shift() |
|---|---|---|
| 1 | 0 | Nan |
| 2 | 0 | 0 |
| 3 | 1 | 0 |
| 4 | 1 | 1 |
| 5 | 1 | 1 |
| 6 | 0 | 1 |
| 7 | 1 | 0 |
| 8 | 1 | 1 |
| 9 | 0 | 1 |
この
is_zero
と
is_zero.shift()
が
!=
の時、つまり、
is_zero
と
is_zero.shift()
が
違う時、True
同じ時、False
なので、
| 時点 | is_zero | is_zero.shift() | is_changed (違う?) |
|---|---|---|---|
| 1 | 0 | Nan | True |
| 2 | 0 | 0 | False |
| 3 | 1 | 0 | True |
| 4 | 1 | 1 | False |
| 5 | 1 | 1 | False |
| 6 | 0 | 1 | True |
| 7 | 1 | 0 | True |
| 8 | 1 | 1 | False |
| 9 | 0 | 1 | True |
| 10 | 1 | 0 | True |
となります。
group_id = is_changed.cumsum()
次はこれです。
これをはじめて見た時、私は
cumsum()
って、何と思いました😅
これは 累積和 というものらしいです。
どういう事をするものかというと
np.cumsum()
| np | np.cumsum() |
|---|---|
| 0 | 0 |
| 2 | 2 |
| 1 | 3 |
| 5 | 8 |
| 0 | 8 |
| 1 | 9 |
| 3 | 12 |
| 4 | 16 |
つまり、
np.cumsum()
のはじめの行は
np
をそのまま移し、その次の行からは
前の行の
np.cumsum()
とその行の
np
を足すを繰り返す関数のようです。
では、話を
group_id = is_changed.cumsum()
に戻しまして、
つまり、これは
is_changed.cumsum()
は
is_changed
を累積和したものだと分かります。
つまり、下の表のようになります。
| 時点 | is_zero | is_changed | is_zeroを数値に | group_id |
|---|---|---|---|---|
| 1 | 0 | True | 1 | 1 |
| 2 | 0 | False | 0 | 1 |
| 3 | 1 | True | 1 | 2 |
| 4 | 1 | False | 0 | 2 |
| 5 | 1 | False | 0 | 2 |
| 6 | 0 | True | 1 | 3 |
| 7 | 1 | True | 1 | 4 |
| 8 | 1 | False | 0 | 4 |
| 9 | 0 | True | 1 | 5 |
| 10 | 1 | True | 1 | 6 |
最後に、
consecutive = is_zero.groupby(group_id).cumsum()
です。
ここで、これは何?
となるのは、
groupby()
です。
例えば、
import pandas as pd
df = pd.DataFrame({
"symbol": ["BTC", "BTC", "ETH", "ETH", "SOL"],
"volume": [100, 200, 50, 70, 30]
})
こんなDataFrameがあったとして、このDataframeの中身は
| symbol | volume | |
|---|---|---|
| 0 | BTC | 100 |
| 1 | BTC | 200 |
| 2 | ETH | 50 |
| 3 | ETH | 70 |
| 4 | SOL | 30 |
です。ここで、
df.groupby("symbol")["volume"].sum()
を考えます。
この結果は
symbol
BTC 300
ETH 120
SOL 30
Name: volume, dtype: int64
になるそうです。
つまり、
df の symbol のうち、BTC の行だけ集めて volume を合計
df の symbol のうち、ETH の行だけ集めて volume を合計
df の symbol のうち、SOL の行だけ集めて volume を合計
しています。
では、例えから本題に戻ります。
consecutive = is_zero.groupby(group_id).cumsum()
これは、is_zero を group_id の値でグループ分けして、各グループ内で cumsum() するという意味です。
| 時点 | is_zero | group_id |
|---|---|---|
| 1 | 0 | 1 |
| 2 | 0 | 1 |
| 3 | 1 | 2 |
| 4 | 1 | 2 |
| 5 | 1 | 2 |
| 6 | 0 | 3 |
| 7 | 1 | 4 |
| 8 | 1 | 4 |
| 9 | 0 | 5 |
| 10 | 1 | 6 |
| is_zero | → | group_id |
|---|---|---|
| 0, 0 | → | グループ1 |
| 1, 1, 1 | → | グループ2 |
| 0 | → | グループ3 |
| 1, 1 | → | グループ4 |
| 0 | → | グループ5 |
| 1 | → | グループ6 |
つまり、
グループ1、グループ2、グループ3、グループ4、グループ5、グループ6に分けて 、グループごとに is_zeroの累積和をとっている。
| 時点 | is_zero | group_id | グループ | consecutive |
|---|---|---|---|---|
| 1 | 0 | 1 | グループ1 | 0 |
| 2 | 0 | 1 | グループ1 | 0 |
| 3 | 1 | 2 | グループ2 | 1 |
| 4 | 1 | 2 | グループ2 | 2 |
| 5 | 1 | 2 | グループ2 | 3 |
| 6 | 0 | 3 | グループ3 | 0 |
| 7 | 1 | 4 | グループ4 | 1 |
| 8 | 1 | 4 | グループ4 | 2 |
| 9 | 0 | 5 | グループ5 | 0 |
| 10 | 1 | 6 | グループ6 | 1 |
結局、この1行は何をしていたのか?
is_zero = (df_1h['volume'] == 0).astype(int)
consecutive = is_zero.groupby((is_zero != is_zero.shift()).cumsum()).cumsum()
↓分解
is_changed = is_zero != is_zero.shift()
group_id = is_changed.cumsum()
consecutive = is_zero.groupby(group_id).cumsum()
これは、
- volume == 0 の行を 1、それ以外を 0 にする
- 0 と 1 が切り替わる場所を見つける
- 切り替わるたびに group_id を増やす
- group_id ごとに cumsum() する
- その結果、出来高ゼロが何本連続しているかが分かる
という処理だった。
つまり、
is_zero = (df_1h['volume'] == 0).astype(int)
consecutive = is_zero.groupby((is_zero != is_zero.shift()).cumsum()).cumsum()
max_consecutive = consecutive.max()
と合わせると、この1行は「最大で何本連続して出来高ゼロだったか」を調べるための前処理でした。
ああ、少し分かった気がしました😅
出典
この記事は、Hoheto さんの GitHub リポジトリ
「crypto-data-analysis-course」の vol01_data_acquisition_and_cleansing.ipynb を学習した記録です。
元リポジトリ: https://github.com/i-love-profit/crypto-data-analysis-course
作者: Hoheto さん
ライセンス: MIT License
元コードの一部を引用し、自分の理解のために分解・解説しています。
コメント