一覧CS-13-1 · 統計と測定

データの分布と代表値

応答時間を集計し、遅い要求の追加が平均・中央値・百分位に与える影響を確かめます。

説明

平均応答時間だけでは、一部の利用者に発生する長い待ち時間が分かりません。中央値は整列後の中央、百分位は指定した割合の位置にある値です。度数分布は、値の範囲ごとに何件あるかを示します。

まず10件の応答時間を集計します。そこへ500ミリ秒の要求を追加し、代表値と分布がどう変わるかを調べます。百分位の計算法と階級の境界も指定して、同じデータから異なる集計が生じる理由を確認します。

1測定値と単位

各値は一つの要求の応答時間で、単位はミリ秒です。まず整列して全件を確認します。

import statistics as st; import math集計と切上げ; latency = [18, 21, 19, 25, 22, 20, 24, 23, 19, 29]10件の測定値; sorted(latency), len(latency), sum(latency)整列・件数・合計

10件の合計は220ミリ秒です。これは10要求の時間を足した値であり、並行して処理した場合の経過時間ではありません。

2平均と中央値

平均は合計を件数で割ります。偶数件の中央値は、整列後の中央二つの平均です。

st.mean(latency), st.median(latency)平均と中央値, sorted(latency)[4:6]中央二つの値

平均22に対し中央値は21.5です。中央の21と22の間にあり、実際に21.5の測定値がなくても中央値として使います。

3長い待ち時間を追加

500ミリ秒の要求が一つ発生した場合を加えます。元の10件は保持します。

with_slow = latency + [500]遅い要求を追加; st.mean(with_slow), st.median(with_slow), max(with_slow)平均・中央値・最大値

平均は約65.45、中央値は22になります。平均は長い待ち時間の影響を大きく受けます。500が測定誤りか、実際に起きた遅延かは値だけで判断できないため、都合よく削除してはいけません。

4順位で百分位を求める

ここではnearest-rank法を使います。昇順のN件から、切上げ(N×p)番目を採ります。配列の添字は順位より1小さくなります。

percentile = lambda values, p: sorted(values)[math.ceil(len(values)*p)-1]順位を切り上げて取得; [(p, percentile(latency,p), percentile(with_slow,p)) for p in [0.5,0.9,0.95]]追加前後の百分位

遅い要求を加えるとp95は500になります。11件では上位側の位置が粗いため、p95という名称だけから大量の測定に相当する精度は得られません。この定義のp50と、偶数件の中央値は一致しない場合があります。

5補間する方法との違い

Pythonのinclusive方式は端点を含む位置を補間します。同じ10件から90百分位を求め、順位方式と比較します。

st.quantiles(latency,n=10,method='inclusive')[-1]補間した90百分位, percentile(latency,0.9)順位による90百分位

補間では25.4、順位方式では25です。どちらかが測定ミスなのではなく、定義が違います。監視ツール間で値を比較する際は、集計期間・対象件数とともに百分位の方式もそろえます。

6幅10ミリ秒で度数を集計

左端以上、右端未満の区間に数えます。20は10〜20ではなく20〜30へ入ります。

bins = [(10,20),(20,30),(30,100),(100,501)]区間と境界; [(lo,hi,sum(lo <= x < hi for x in with_slow)) for lo,hi in bins]区間別の件数

度数は3、7、0、1です。幅の異なる区間を使っているため、この度数をそのまま同じ幅の棒の高さにすると密度を表すヒストグラムにはなりません。ここでは区間別の件数表として読みます。

7細かい区間で中心部分を調べる

通常の10件を幅2ミリ秒で集計します。星一つが1件です。

print('\n'.join(f'{lo:2d}-{lo+2:2d}: '+ '*'*sum(lo <= x < lo+2 for x in latency) for lo in range(18,30,2)))同じ幅の区間を文字の棒で表示

中心部分にも18〜20の3件、20〜22の2件などの違いがあります。階級幅を変えると見える形も変わるため、少数データでは元の値を併記すると解釈しやすくなります。

8単位を変えても対象は同じ

ミリ秒から秒へ変換します。換算後の平均と、元の平均を1000で割った値を照合します。

seconds = [x/1000 for x in with_slow]ミリ秒を秒へ; st.mean(seconds), st.mean(with_slow)/1000, percentile(seconds,0.95)平均の一致とp95

平均約0.06545秒、p95は0.5秒です。単位の換算は値の尺度を変えるだけで、遅い要求の存在や集計対象は変えません。

まとめ

平均と中央値は分布の異なる性質を表し、少数の大きな値への感度も違います。

百分位には複数の定義があるため、方式と標本数を併記します。

区間の境界・幅・単位を明示し、集計から消える情報を元の測定値で補います。

操作履歴と解説を振り返れます。

Python 3

1
import statistics as st; import math; latency = [18, 21, 19, 25, 22, 20, 24, 23, 19, 29]; sorted(latency), len(latency), sum(latency)
([18, 19, 19, 20, 21, 22, 23, 24, 25, 29], 10, 220)
2
st.mean(latency), st.median(latency), sorted(latency)[4:6]
(22, 21.5, [21, 22])
3
with_slow = latency + [500]; st.mean(with_slow), st.median(with_slow), max(with_slow)
(65.45454545454545, 22, 500)
4
percentile = lambda values, p: sorted(values)[math.ceil(len(values)*p)-1]; [(p, percentile(latency,p), percentile(with_slow,p)) for p in [0.5,0.9,0.95]]
[(0.5, 21, 22), (0.9, 25, 29), (0.95, 29, 500)]
5
st.quantiles(latency,n=10,method='inclusive')[-1], percentile(latency,0.9)
(25.4, 25)
6
bins = [(10,20),(20,30),(30,100),(100,501)]; [(lo,hi,sum(lo <= x < hi for x in with_slow)) for lo,hi in bins]
[(10, 20, 3), (20, 30, 7), (30, 100, 0), (100, 501, 1)]
7
print('\n'.join(f'{lo:2d}-{lo+2:2d}: '+ '*'*sum(lo <= x < lo+2 for x in latency) for lo in range(18,30,2)))
18-20: ***
20-22: **
22-24: **
24-26: **
26-28: 
28-30: *
8
seconds = [x/1000 for x in with_slow]; st.mean(seconds), st.mean(with_slow)/1000, percentile(seconds,0.95)
(0.06545454545454546, 0.06545454545454546, 0.5)

演習完了