テストの点数や売上の数字を前にして、とりあえず平均を出してみる。Pythonを覚えたての頃、多くの人が通る道です。
そのとき、sumで合計してlenで割るコードを毎回書いていませんか。もちろんそれでも動きますが、実はもっと楽で、しかも間違えにくい方法があります。
Pythonには、基本的な統計の計算をまとめたstatisticsというモジュールが標準で入っています。pip installも要りません。
今回は、平均値だけを見ていると何を見落とすのかという話から始めます。そのうえで、statisticsの主な関数と実務での使いどころを、動くコードと一緒に見ていきましょう。
statisticsは、インストール不要の統計の道具箱¶
statisticsは、平均や中央値、ばらつきといった基本の統計量を計算するためのモジュールです。Python 3.4で標準ライブラリに加わりました。
追加を提案したPEP 450には、NumPyと競合するものではないとはっきり書かれています。本格的な数値計算ライブラリと、自分で書く間に合わせのコードの中間を埋める存在、という位置づけです。
ですから、数万件の配列を高速に処理する用途には向きません。そのかわり、import一行ですぐ使えて、計算の正確さにも気を配って作られています。
まずは、もっとも基本的な平均から試してみましょう。
import statistics
scores = [72, 85, 90, 64, 78]
print(statistics.mean(scores)) # 77.8
sum(scores) / len(scores)と同じ答えです。わざわざモジュールを使う意味はあるのか、と感じたかもしれませんね。
その疑問への答えは、このあと少しずつ見えてきます。
sumとlenで平均を出すと、何が困るのか¶
自分で書いた平均の計算には、いくつか見落としやすい落とし穴があります。代表的なものを比べてみましょう。
| 場面 | sum ÷ len で書いた場合 | statistics.mean |
|---|---|---|
| データが空 | ZeroDivisionError | StatisticsError(意味がわかるエラー) |
| 0.1を10個並べた平均 | 0.09999999999999999 | 0.1 |
| ジェネレータを渡す | lenが使えずTypeError | そのまま計算できる |
| 中央値や標準偏差も欲しい | 毎回自分で実装する | 関数を呼ぶだけ |
2行目は少し驚くかもしれません。sum([0.1] * 10) / 10は、なぜかぴったり0.1になりません。
これはfloatという小数の型が、0.1を2進数で正確に表せないことから来る誤差です。statistics.meanは内部で分数を使って計算してから最後に変換するので、こうした誤差が出にくくなっています。
小数の誤差がなぜ起こるのかは、こちらで詳しく解説しています。【関連記事】Pythonの丸め誤差とは?初心者にもわかる原因と対策をエンジニア歴10年の視点で解説
平均値だけでは、データの姿は見えない¶
ここからが今回いちばん伝えたい話です。平均値はとても便利ですが、たった1つの極端な値で大きく動いてしまうという弱点があります。
たとえば、あるAPIのレスポンス時間を5回測ったとします。4回は300ミリ秒前後だったのに、1回だけ5000ミリ秒かかったとしましょう。
import statistics
response_ms = [300, 320, 310, 5000, 305]
print(statistics.mean(response_ms)) # 1247
print(statistics.median(response_ms)) # 310
平均は1247ミリ秒です。この数字だけを見ると、いつも1秒以上かかる遅いAPIに見えてしまいますよね。
一方で、中央値は310ミリ秒です。ふだんは300ミリ秒ちょっとで返ってきて、たまに大きく遅れることがある、という実態に近いのはこちらでしょう。
私は10年ほどエンジニアとして開発に関わってきましたが、平均値だけを報告して現場を混乱させた経験があります。障害の調査で平均の応答時間が悪化したと伝えたところ、実際は一部のリクエストだけが極端に遅く、原因の切り分けに半日を無駄にしてしまいました。
それ以来、数字を報告するときは平均と中央値をセットで出すようにしています。この2つが大きくずれていたら、どこかに極端な値が混じっているサインです。
代表値を求める関数を使い分ける¶
データの真ん中あたりを表す値を、統計では代表値と呼びます。statisticsには代表値のための関数がいくつか用意されています。
| 関数 | 求めるもの | 向いている場面 |
|---|---|---|
| mean | 算術平均 | 極端な値が少ないデータ |
| fmean | floatで計算する平均 | 速さを優先したいとき(3.8以降) |
| median | 中央値 | 外れ値が混じりやすいデータ |
| mode | 最頻値 | 一番多い回答やカテゴリを知りたいとき |
| multimode | 最頻値をすべて | 同じ回数で並ぶ値がありそうなとき |
meanとfmeanの違いは、計算の正確さと速さのバランスです。fmeanはデータをすべてfloatに変えてから計算するので、meanより高速に動きます。
medianは偶数個のとき真ん中2つの平均になる¶
中央値は、データを小さい順に並べたときの真ん中の値です。では、データが偶数個のときはどうなるのでしょうか。
import statistics
data = [1, 2, 3, 4]
print(statistics.median(data)) # 2.5
print(statistics.median_low(data)) # 2
print(statistics.median_high(data)) # 3
medianは、真ん中の2つである2と3の平均をとって2.5を返します。これがいちばん一般的な中央値の定義です。
ただ、人数や個数のように、実際にデータとして存在する値を答えにしたい場合もあります。そんなときは、小さいほうを返すmedian_lowか、大きいほうを返すmedian_highを使います。
modeは文字列にも使える¶
最頻値は、一番多く出てくる値のことです。meanやmedianと違って、数値以外のデータにも使えるのが特徴です。
import statistics
answers = ["Python", "Java", "Python", "Go", "Python", "Java"]
print(statistics.mode(answers)) # Python
tie = [1, 1, 2, 2, 3]
print(statistics.mode(tie)) # 1
print(statistics.multimode(tie)) # [1, 2]
アンケートの回答で一番多かった選択肢を知りたい、といった場面で役立ちます。
注意したいのは、同じ回数で並ぶ値があるときです。Python 3.8以降のmodeは、最初に見つかった値を1つだけ返します。
並んでいる値をすべて知りたいなら、multimodeを使いましょう。回数も一緒に数えたい場合は、collectionsのCounterも便利です。【関連記事】Pythonのcollectionsとは?Counterやdefaultdictで毎日のコードが短くなる使い方を解説
ばらつきを数字にする、分散と標準偏差¶
平均が同じでも、中身がまったく違うデータはよくあります。たとえば、全員が70点のクラスと、30点と100点が入り混じったクラスでは、指導の仕方も変わってきますよね。
このばらつきを数字で表すのが、分散と標準偏差です。標準偏差は、データが平均からどれくらい離れているかの目安だと考えてください。
import statistics
data = [2, 4, 4, 4, 5, 5, 7, 9]
print(statistics.pstdev(data)) # 2.0
print(statistics.stdev(data)) # 2.138089935299395
print(statistics.pvariance(data)) # 4
print(statistics.variance(data)) # 4.571428571428571
同じデータなのに、stdevとpstdevで答えが違います。ここは初心者が一番混乱しやすいところです。
stdevとpstdevはどう使い分けるのか¶
違いは、手元のデータを全体と見るか、全体の一部と見るかです。表にすると次のようになります。
| 関数 | 前提 | 使う場面の例 |
|---|---|---|
| pstdev / pvariance | 手元のデータが全体(母集団) | クラス全員の点数、今月の全注文 |
| stdev / variance | 手元のデータは全体の一部(標本) | アンケートの回答者、抜き取り検査 |
頭のpはpopulation、つまり母集団の略です。一部のデータから全体のばらつきを推測するときは、少し大きめに見積もるほうが正しい推定になるため、stdevの値はpstdevより大きくなります。
迷ったときは、このデータの外側にまだデータがあるかを考えてみてください。あるならstdev、これで全部ならpstdevです。
なお、stdevとvarianceはデータが2つ以上ないと計算できません。1件だけ渡すと、StatisticsErrorが発生します。
quantilesで、データを4つに区切って見る¶
平均と中央値に慣れてきたら、次はデータの分布を見てみましょう。quantilesは、データを同じ割合ずつに区切る境目の値を返します。
import statistics
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(statistics.quantiles(data)) # [2.75, 5.5, 8.25]
print(statistics.quantiles(data, n=4, method="inclusive")) # [3.25, 5.5, 7.75]
何も指定しなければn=4で、データを4つに区切る3つの境目を返します。これを四分位数と呼びます。
真ん中の5.5は中央値と同じです。最初と最後の値を見れば、データの半分がどの範囲に収まっているかがわかります。
methodは境目の計算方法の指定です。既定のexclusiveは手元のデータを一部と見る場合、inclusiveは全体と見る場合に向いていて、stdevとpstdevの関係に似ています。
レスポンス時間の監視では、n=100にして上位1パーセントの境目を見ることもあります。遅いリクエストの傾向をつかむのに便利です。
実務でstatisticsを使うときの注意点¶
ここまでで、statisticsのおもな使い方は一通りそろいました。最後に、実際のコードで使うときに気をつけたい点をまとめておきます。
空のデータは必ず想定しておく¶
statisticsの関数は、データが空だとStatisticsErrorを発生させます。ログの集計などで、たまたま対象が0件になる日は意外とあるものです。
import statistics
def summarize(values):
"""数値のリストから、平均・中央値・標準偏差をまとめて返す。"""
if not values:
return None
result = {
"mean": statistics.mean(values),
"median": statistics.median(values),
}
# 標準偏差は2件以上ないと計算できない
if len(values) >= 2:
result["stdev"] = statistics.stdev(values)
return result
print(summarize([300, 320, 310, 5000, 305]))
print(summarize([])) # None
このように、件数を先に確認しておくと安心です。夜間バッチが空データで止まって朝に呼び出される、という事態を防げます。
大量のデータならNumPyやpandasを選ぶ¶
statisticsは、Pythonのリストをそのまま扱える手軽さが魅力です。その反面、何十万件もの数値を処理する用途には向いていません。
大量のデータや、表形式のデータをまとめて集計したいときは、NumPyやpandasを使うほうが速くて便利です。【関連記事】PythonのNumPyとは?配列計算がリストより速くなる仕組みを初心者向けに解説
目安としては、次のように使い分けるとよいでしょう。
| 状況 | おすすめ |
|---|---|
| 数十〜数千件のリストをさっと集計したい | statistics |
| 外部ライブラリを入れられない環境 | statistics |
| 大量の数値をまとめて計算したい | NumPy |
| CSVなど表形式のデータを列ごとに集計したい | pandas |
私自身、小さな運用スクリプトではstatisticsをよく使います。サーバーに追加のライブラリを入れる許可を取るのが面倒な現場でも、標準ライブラリなら気兼ねなく使えるからです。
表形式のデータを本格的に扱いたくなったら、pandasに進んでみてください。【関連記事】pandas入門 データ処理をやってみよう
ほかにもある便利な関数¶
statisticsには、今回紹介しきれなかった関数もあります。興味が出てきたら、公式ドキュメントで少しずつ試してみてください。
Python 3.10以降では、2つのデータの関係を調べるcorrelationやlinear_regressionも使えます。たとえば、勉強時間とテストの点数にどれくらい関係があるかを、ライブラリなしでざっくり確かめられます。
ほかにも、正規分布を扱うNormalDistというクラスがあります。平均と標準偏差を渡すだけで、ある値以下になる確率などを計算できる、なかなか面白い道具です。
まとめ¶
今回は、Pythonの標準ライブラリstatisticsの使い方を見てきました。ポイントを振り返っておきましょう。
statisticsを使えば、平均・中央値・最頻値・標準偏差をインストールなしで計算できます。自分でsumとlenを書くより、誤差や空データの扱いで安心感があります。
そして何より大切なのは、平均値だけでデータを判断しないことです。平均と中央値を並べて見るだけで、外れ値の存在に気づけるようになります。
統計と聞くと身構えてしまうかもしれませんが、まずは手元の数字で中央値を出してみるところからで十分です。数字の見え方が、きっと少し変わってくるはずです。
ここまでお読みいただきありがとうございました。