04.4.2 高校生向け
Zスコアで異常値を検出(高校生向け)
こんな場面を想像してみよう #
スマートウォッチで毎日の歩数を半年分記録しました。ふだんは 8,000〜12,000 歩ですが、遠足の日は 25,000 歩、熱で寝込んだ日は 1,500 歩でした。こうした「ふだんとちがう日」を自動で見つけるには、どんなルールを書けばよいでしょう。
「5,000 歩以上はなれたら異常」と決めても、毎日 20,000 歩歩く部活生には通用しません。そこで**「はなれ方」を、そのデータ自身の散らばり(標準偏差)を単位にして測る**。これが Z スコアの発想です。
上の図は 180 日分の値(青い線)です。線は 100 を中心に 90〜110 の間で波打っていますが、60 日目あたりで 125 近くまで跳ね、140 日目あたりで 80 近くまで落ちています。Z スコアを計算して \(|z| > 2.5\) の点だけに赤い印をつけると、ちょうどその 2 日だけが赤くなり、ふだんの波の山や谷は赤くなりません。
仕組み:平均の帯からの飛び出しを測る #
まず、全部の値の平均 \(\mu\) を求めます。グラフでは、青い線のまん中を通る水平な中心線で、図では 100 のあたりです。次に標準偏差 \(\sigma\) を求めます。青い線が中心線からふだんどれくらいはなれて上下しているか、その「ふつうの幅」です。続いて各日の Z スコア \(z = (x - \mu)/\sigma\) を計算します。中心線の上なら正、下なら負で、中心線から標準偏差 1 個分はなれると \(z = \pm 1\) です。最後に閾値を決めて判定します。\(|z| > 2.5\) なら異常で、グラフでは中心線の上下に \(2.5\sigma\) の帯をかぶせて、帯の外に出た点だけを赤く塗るのと同じです。
$$ z_t = \frac{x_t - \mu}{\sigma}, \qquad |z_t| > 2.5 \ \Rightarrow\ \text{異常} $$10 個の数で同じ計算を手で追えるのが、次の短いコードです。8 番目の 125 だけが「異常」になるはずです。
| |
8 番目の値 125 だけが \(z \approx 2.9\) で閾値を超え、異常と判定されます。
動かして見てみよう #
下の図で「再生」を押すと、説明した手順が 1 つずつ進みます。手順ごとの説明は図の下に出ます。
気をつけたいこと #
最初の落とし穴は、異常値が平均 \(\mu\) と標準偏差 \(\sigma\) を引っぱることです。125 のような極端な値が混ざるほど、平均も標準偏差も大きくなり、その異常値自身の \(z\) は小さく見積もられます。異常がたくさん混ざったデータでは、中央値と四分位範囲を使う方法(RobustScaler と同じ発想)のほうが安定します。二つ目は、周期的な波は異常ではないのに標準偏差 \(\sigma\) を膨らませることです。季節や曜日の周期による波は「ふだんの動き」ですが、標準偏差はこの波の分まで含んで計算されるので、小さめの異常が帯の中に隠れます。先に波の成分を取り除いてから Z スコアを計算すると見つけやすくなります。三つ目は、閾値に正解がないことです。2.5 を 2 に下げれば見逃しは減りますが、ふだんの揺れを誤って異常と呼ぶ回数(誤検知)が増えるので、「見逃しと誤検知のどちらが困るか」で決めます。
やってみよう #
最初の課題は、自分の 2 週間分の歩数(または睡眠時間)を記録して平均と標準偏差を計算し、各日の \(z\) を求めることです。\(|z| > 2\) の日はあったか、その日に何があったかを振り返ってみてください。二つ目は、上のコードの x に「異常値を 1 個ではなく 3 個」混ぜて実行し、平均と標準偏差がどう変わるか、3 個すべてが検出されるかを確かめることです。三つ目は思考実験で、「毎週日曜だけ歩数が倍になる人」のデータを想像します。日曜の値は異常と呼ぶべきか、呼ぶべきでないか、Z スコアだけで判定するとどんな問題が起きるかを説明してみてください。
つぎに読む #
pandas と matplotlib で時系列を描き、異常点に印をつける実装は 標準版の Z スコアで異常値を検出 で扱っています。Z スコアそのものを「前処理」として使う話は StandardScaler(高校生向け)、外れ値に引っぱられない中央値版は RobustScaler(高校生向け) へ進んでください。欠損や平均の切り替わりなど、ほかのデータ品質チェックは 時系列のデータ品質 のセクションにまとめています。