K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

平均の変化点を可視化

中級

04.4.3

平均の変化点を可視化

最終更新 2020-04-18 読了時間 2 分
まとめ
変化点の可視化は、系列の平均レベルが切り替わった日付に縦線を引き、その前後で水準が違うことを一枚の図で共有する手法です。このページでは、平均 80・95・110 の 3 区間(切り替わりは 70 日目と 140 日目)を持つ 240 日の系列を作り、series.rolling(window=14, min_periods=1).mean() の 14 日移動平均を青い太線で重ね、ax.axvline で 2 本の赤い破線を境界に引きます。読み終えると、移動平均が段差を 14 日かけてなぞる形から変化点を目で確かめ、境界の前後で平均や分散がどれだけ変わったかを言葉にして、モデルを分割するかダミー変数を足すかの判断につなげられるようになります。

施策の投入日や仕様変更の日に赤い縦線を引き、14 日移動平均を重ねると、「その日を境に平均が 15 上がった」という変化を数字と図の両方で共有できます。

直感 #

値下げキャンペーンを 3 月 11 日に始め、新しい配送業者に 5 月 20 日に切り替えたとします。日次の売上の折れ線は毎日の揺れが大きく、「本当に水準が変わったか」は見た目では判断できません。そこで 14 日間の平均を取った滑らかな線を重ねると、揺れが \(1/\sqrt{14} \approx 0.27\) 倍に縮み、段差だけが残ります。その段差の位置に施策日の縦線を引けば、「線の左側は平均 80、右側は平均 95」と言い切れる図になります。ここでやっているのは ruptures のような自動検出ではなく、知っている日付に線を引いて移動平均が同意しているかを確かめる作業です。

何が起きているか(ステップで見る) #

コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。

見るポイント:どこで区切ると左右のばらつきの合計が最小になるかを総当たりで探す。

Python で描く #

次のコードは、平均 80(0〜69 日目)、95(70〜139 日目)、110(140 日目以降)の 3 区間を持つ 240 日の系列を作り、観測値を灰色、14 日移動平均を青、2 つの変化点を赤い破線で描きます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

rng = np.random.default_rng(19)
dates = pd.date_range("2020-01-01", periods=240, freq="D")
segments = [
    (0, 80, 1.5),
    (70, 95, 1.8),
    (140, 110, 2.2),
]

values = np.empty(len(dates))
segment_bounds = [seg[0] for seg in segments[1:]] + [len(dates)]
for (start, mean, scale), end in zip(segments, segment_bounds):
    values[start:end] = mean + rng.normal(0, scale, end - start)

series = pd.Series(values, index=dates)
rolling = series.rolling(window=14, min_periods=1).mean()

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(series.index, series, color="#64748b", alpha=0.7, label="観測値")
ax.plot(rolling.index, rolling, color="#2563eb", linewidth=2, label="14日移動平均")

for start, _, _ in segments[1:]:
    ax.axvline(dates[start], color="#ef4444", linestyle="--", linewidth=1.5)

ax.set_title("平均の変化点を確認")
ax.set_xlabel("日付")
ax.set_ylabel("値")
ax.legend()
ax.grid(alpha=0.3)

fig.tight_layout()
fig.savefig("static/images/timeseries/change_point.svg")

平均の変化点を可視化

実行結果の読み方 #

灰色の観測値は 80 のまわりで揺れたあと 70 日目で 95 へ、140 日目で 110 へ階段状に跳ね上がります。青い 14 日移動平均は赤い破線の位置から約 14 日かけて次の水準に登ります。その間は斜めの坂です。これは移動平均が変化点の前後の値を混ぜて平均するためで、坂の長さが window と同じになることを確認してください。赤い破線のすぐ左と右で青い線の高さを読むと、1 本目で 80 → 95、2 本目で 95 → 110 と、どちらも 15 の差です。後半ほど灰色の線の揺れが大きいのは標準偏差を 1.5 → 2.2 に増やしたためで、変化点では平均だけでなく分散も変わりうることを示しています。

使いどころと落とし穴 #

この図は、施策や外部環境の変化を知っている人が「その日から水準が変わった」と主張する根拠として使います。境界の前後で series[:70].mean() と series[70:140].mean() のように平均を計算して差を添えると、図の印象を数字で裏づけられます。前後で平均や分散が大きく変わるなら、学習データを変化点以降に絞る、区間ダミー変数を回帰に加える、といった対処に進みます。落とし穴は、変化点を手で置くぶん「見たいところに線を引いてしまう」ことで、再現性のある分析には ruptures のような自動検出(コスト関数 "l2" で平均の変化を探す)や BOCPD を併用し、手で引いた線と検出結果が一致するかを確かめます。移動平均の window を長くしすぎると変化点が右へずれて見えるので、段差が始まる日は青い線ではなく灰色の観測値で読むようにします。