Dev Study
← 評価のための統計
レッスン 9 / 10·約 9 分·第22期 評価のための統計

最悪だったものは、何もしなくても戻る

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

性能の数字を週ごとに見て、悪かったものに手を入れる。運用でよくある流れです。今回は、その「直したら良くなった」から対策の効果だけを取り出す方法を見ます。

まず結論

先週いちばん遅かったものは、何もしなくても今週は速く見えることが多い。測った値は「本当の遅さ」と「その週の揺れ」の和で、最悪を選ぶと上に揺れたものも一緒に選ぶからです。

だから「最悪を直したら良くなった」は、前後の比較だけでは効果の証拠になりません。同じ基準で選んだものを無作為に分け、手を入れない対照群と比べて初めて、対策の分が取り出せます。

? 読む前に予測

200本のエンドポイントの p95 を毎週測る模型で、1週目に最も遅かった10本の平均は約273ミリ秒でした(全体の平均は約200ミリ秒)。2週目、何も変えずにこの10本を測り直すと、平均はおおよそどうなるでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

測った値は、本当の遅さとその週の揺れの和

模型の設定はこうです。200本のエンドポイントそれぞれに本当の p95 があり、平均200・標準偏差20ミリ秒でばらつきます。週ごとの測定には、さらに標準偏差30ミリ秒の揺れが乗ります。2週目の揺れは1週目と無関係に引き直し、対策は何もしません。

1週目に最も遅かった10本は、2種類のものから選ばれます。本当に遅いものと、その週たまたま上に揺れたものです。2週目、本当の遅さは残り、揺れは引き直されます。だから途中まで戻ります。

どこまで戻るかは、揺れと本当の差の大きさの比で決まります。この模型では揺れ(30)のほうが本当の差(20)より大きいので、選んだときの隔たりの多くは揺れです。設定から計算すると、全体の平均からの隔たりのうち2週目に残るのは約3割(0.308、コードの最終行)。実際に回すと、73.3 ミリ秒の隔たりのうち 22.1 が残りました。

最遅10本を選ぶと、揺れも一緒に選ぶ(seed=1、2000回の中央値)
本当の遅さ平均200・ばらつき20。週をまたいで残る
その週の揺ればらつき30。毎週引き直す
足したものが1週目の測定値
最遅10本を選ぶ273.3。本当に遅い分+上に揺れた分
何もせず測り直す
2週目の同じ10本222.1。揺れの分が消える
  • ・全200本の2週目の平均は 199.9 で動かない。戻るのは値で選んだ部分だけ
  • ・200 まで戻りきらないのは、この10本が本当に平均より遅めだから
具体実物

模型を2000回回す

標準ライブラリだけの模型です。引数 EFFECT は後で使う「対策が本当に縮めるミリ秒」で、まずは 0(何もしない)で回します。

import random, statistics as st, sys

EFFECT = float(sys.argv[1]) if len(sys.argv) > 1 else 0.0  # ms the fix really removes
SEED = 1
TRUE_SD, NOISE_SD = 20, 30
rng = random.Random(SEED)
R = 2000
w1_worst, w2_worst, w1_best, w2_best, w2_all = [], [], [], [], []
drop, t_minus_c = [], []
for _ in range(R):
    true = [rng.gauss(200, TRUE_SD) for _ in range(200)]   # real p95 of 200 endpoints (ms)
    w1 = [t + rng.gauss(0, NOISE_SD) for t in true]         # week 1 measurement
    w2 = [t + rng.gauss(0, NOISE_SD) for t in true]         # week 2 measurement
    ranked = sorted(range(200), key=lambda i: -w1[i])
    worst10 = ranked[:10]
    w1_worst.append(st.mean(w1[i] for i in worst10))
    w2_worst.append(st.mean(w2[i] for i in worst10))
    w1_best.append(st.mean(w1[i] for i in ranked[-10:]))
    w2_best.append(st.mean(w2[i] for i in ranked[-10:]))
    w2_all.append(st.mean(w2))
    worst20 = ranked[:20]
    rng.shuffle(worst20)
    fixed, control = worst20[:10], worst20[10:]
    for i in fixed:
        w2[i] -= EFFECT
    drop.append(st.mean(w1[i] - w2[i] for i in fixed))
    t_minus_c.append(st.mean(w2[i] for i in fixed) - st.mean(w2[i] for i in control))

def show(name, v):
    s = sorted(v)
    print(f"{name:<22} median {st.median(s):6.1f}  95% of runs [{s[int(R*.025)]:6.1f}, {s[int(R*.975)]:6.1f}]")

print(f"EFFECT={EFFECT}  runs={R}  seed={SEED}")
show("worst10 week1", w1_worst)
show("worst10 week2", w2_worst)
show("best10 week1", w1_best)
show("best10 week2", w2_best)
show("all 200 week2", w2_all)
show("fixed: week1-week2", drop)
show("week2 fixed-control", t_minus_c)
share = TRUE_SD**2 / (TRUE_SD**2 + NOISE_SD**2)
print(f"share kept = {TRUE_SD}^2 / ({TRUE_SD}^2 + {NOISE_SD}^2) = {share:.3f}")
$ python3 lesson.py 0(Python 3.14.6。3.8.20 でも同一の出力)コマンド出力
EFFECT=0.0 runs=2000 seed=1
worst10 week1 median 273.3 95% of runs [ 261.6, 285.6]
worst10 week2 median 222.1 95% of runs [ 200.7, 243.6]
best10 week1 median 126.5 95% of runs [ 113.1, 137.8]
best10 week2 median 177.0 95% of runs [ 156.6, 199.1]
all 200 week2 median 199.9 95% of runs [ 194.9, 205.0]
fixed: week1-week2 median 43.5 95% of runs [ 21.2, 67.0]
week2 fixed-control median 0.1 95% of runs [ -30.0, 31.2]
share kept = 20^2 / (20^2 + 30^2) = 0.308

worst10 の2行が本題です。何もしていないのに、中央値で約51ミリ秒速くなりました。2000回のうち中央の95%で見ても、2週目は 243.6 を超えません。

all 200 week2 は 199.9。全体は戻りません。 戻るのは、1週目の値で選んだ部分だけです。向きは逆でも同じで、最速10本は 126.5 → 177.0 と遅いほうへ戻ります。

✓ ここまでの確認

模型で、1週目の最遅10本が2週目に速く見えた理由として、模型の設定から言えるのはどれですか。

抽象原理

戻るのは、値で選んだときだけ

この現象は「平均への回帰」と呼ばれます。Barnett らの論文の抄録はこう説明します(本文は確認できておらず、以下は抄録からです)。

Regression to the mean (RTM) is a statistical phenomenon that can make natural variation in repeated data look like real change.

目立つ条件も抄録にあります。with increasing measurement error and when follow-up measurements are only examined on a sub-sample selected using a baseline value。揺れが大きいことと、最初の値で選んだ一部だけを追うこと。模型の2つの観察(隔たりの約7割が消えた・全体は動かない)がそのまま対応します。

では、最悪の10本に本当に対策を入れたら、効果はどう取り出すのか。同じ基準で選んだものを無作為に2つに分け、片方にだけ手を入れます。戻りは両方に同じように起きるので、2週目に2群を比べれば戻りは打ち消されます。人が「直せそうなもの」を選んで分けると、群の違いに対策以外のものが混ざります。

具体実物

Galton が親子の身長で書いたこと

戻りを数字で書いた古い原典が Galton 1886 です。遺伝の研究で、種子の大きさと人の身長を調べました。親の側の値(原文の mid-parentage)からの隔たりと、子の隔たりの関係を、p.252 でこう書いています。

By the use of this word and that of “mid-parentage” we can define the law of regression very briefly. It is that the height-deviate of the offspring is, on the average, two-thirds of the height-deviate of its mid-parentage.

親の隔たりのうち、子に平均して残るのは3分の2。模型の「2週目に残るのは約3割」と同じ形の関係です(対応づけは本教材の整理です)。Galton はこれを遺伝の法則として書き、両側に同じく働くことも明記しています(p.253)。

The law is even-handed; it levies the same heavy succession-tax on the transmission of badness as well as of goodness.

模型の最速10本が遅いほうへ戻ったのと同じ向きの対称性です。なお Galton の語は regression towards mediocrity で、「平均への回帰」は後の用語です。Galton が統計の錯覚を見つけた、という言い方は原典に無い解釈を足すことになります。

✓ ここまでの確認

最遅20本を無作為に10本ずつ分け、片方にだけ対策を入れました。対策の効果として読むべき数字はどれですか。

⇄ 2つを見比べる
具体例 A

効かない対策(EFFECT=0)

最遅20本を無作為に分け、片方に何もしない対策を入れました。対策群の前後差は中央値43.5ミリ秒の改善に見えますが、2週目の対照群との差は0.1で、効果は無いと読めます。

$ python3 lesson.py 0 の最後の3行コマンド出力
fixed: week1-week2 median 43.5 95% of runs [ 21.2, 67.0]
week2 fixed-control median 0.1 95% of runs [ -30.0, 31.2]
share kept = 20^2 / (20^2 + 30^2) = 0.308
具体例 B

本当に30ミリ秒縮める対策(EFFECT=30)

同じ模型で、対策群の2週目を本当に30ミリ秒縮めました。前後差は73.5で、そのうち約44は回帰の分です。対照群との差は−29.9で、入れた効果とほぼ一致します。

$ python3 lesson.py 30 の最後の3行コマンド出力
fixed: week1-week2 median 73.5 95% of runs [ 51.2, 97.0]
week2 fixed-control median -29.9 95% of runs [ -60.0, 1.2]
share kept = 20^2 / (20^2 + 30^2) = 0.308

前後差は 43.5 と 73.5 で、どちらも改善に見えます。効かない対策と効く対策を見分けているのは、どの数字でしょうか。

何もしない対策でも前後差が出る理由を思い出し、その分を消す比べ方を探してください。

この章のまとめ

  • 測った値は本当の値+その回の揺れ。最悪で選ぶと、上に揺れたものも選ぶ。
  • 何もしなくても、選んだ部分は平均の側へ途中まで戻る。模型では 273.3 → 222.1(seed=1、2000回の中央値)。
  • 戻りは揺れが大きいほど、最初の値で選んだ一部を追うほど目立つ(Barnett らの抄録)。全体の平均は戻らない。
  • Galton 1886 は親子の身長で、隔たりが平均して3分の2に縮む法則を書き、高い側にも低い側にも働くと明記した。
  • 効果は前後差ではなく、無作為に分けた対照群との差で読む。何もしない対策でも前後差は 43.5 出た。
  • 「改善はすべて回帰」も誤り。効く対策の分は対照群との差として残る。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

Barnett らの抄録は、平均への回帰がより目立つ条件として何を挙げていますか。

用語集(3語)
平均への回帰
極端な値で選んだものを測り直すと、平均の側へ戻って見える現象。測定値に偶然の揺れが含まれるときに起きる。Galton 1886 の語は regression towards mediocrity。
対照群
対策を入れる群と同じ基準で選び、手を入れずに置いておく群。戻りは両群に同じように起きるので、差を取ると対策の分だけが残る。
無作為な割り付け
どれを対策群にするかを乱数で決めること。人が選ぶと「直せそうなもの」に偏り、群の差に対策以外の違いが混ざる。