NumPy と pandas で分散の答えが違う!pandas の要約統計量
同じ 10 個の数から、NumPy の np.var は 3.6 を返し、pandas の .var() は 4.0 を返します。
計算がちがうわけではありません。既定の ddof が、片方は 、もう片方は だからです[1]。
pandas で要約統計量を出すときは、この既定を頭に置いておく必要があります。
Series と DataFrame
1 列ぶんのデータが Series、表になったものが DataFrame です。
import numpy as np
import pandas as pd
data = pd.Series([4, 8, 5, 9, 6, 7, 3, 8, 6, 4])Series は値の並びに見出しがついたものと考えると分かりやすい。見出しを指定しなければ から順に振られます。
DataFrame は、その Series を列として横に並べたものです。列ごとに型が違ってよいので、数値と文字列を同じ表に置けます。
基本の統計量
メソッドとして呼びます。出力を右にコメントで添えました。
print(data.mean()) # 6.0
print(data.var()) # 4.0
print(data.var(ddof=0)) # 3.6
print(data.std()) # 2.0
print(np.var(data.to_numpy())) # 3.6pandas の .var() は既定で で割ります[1]。不偏分散のほうです。
最後の行で同じデータを NumPy に渡すと 3.6 が返りました。既定が で割るほうだからです。
ddof=1。 で割る不偏分散
ddof=0。 で割る標本分散
どちらも引数で切りかえられます。混ぜて使うときは、どちらの既定に合わせるかを先に決めておく。
同じ集計を 2 つの道具で書き直したら値が変わった、という事故はここから起きます。
describe で一度に出す
DataFrame には、主な統計量をまとめて返すメソッドがあります。
frame = pd.DataFrame({
'国語': [62, 72, 66, 70, 58],
'数学': [68, 75, 64, 80, 55],
'組': ['A', 'B', 'A', 'B', 'A'],
})
print(frame.describe())出力はこうなります。
国語 数学
count 5.000000 5.000000
mean 65.600000 68.400000
std 5.727128 9.710819
min 58.000000 55.000000
25% 62.000000 64.000000
50% 66.000000 68.000000
75% 70.000000 75.000000
max 72.000000 80.000000数値の列だけが選ばれ、文字列の「組」は落ちています。数値の列がまったくない場合だけ、文字列の列が対象になります[2]。
count は欠測を除いた個数です。std はここでも で割った値になります。
50% の行が中央値です。国語の中央値 66.0 は平均 65.6 とほぼ同じで、大きな偏りはありません。
数学のほうは std が 9.71 と大きく、min 55 から max 80 まで開いています。同じ 5 人でも、科目によって散らばりが違う。
型を確かめる
集計の前に、各列がどう読みこまれたかを見ておきます。
print(frame.dtypes)国語 int64
数学 int64
組 str
dtype: object数値の列が int64、文字列の列が str になっています。ここが意図と違っていると、集計の結果もおかしくなる。
外部のファイルから読みこんだ場合、数値のはずの列が文字列になっていることがあります。空白や単位の記号が 1 つ混じっているだけで、列全体が文字列と判定される。
平均が出ないとき、まず疑うのはこの型です。エラーにならずに文字列の連結が起きる場合もあり、そのほうがたちが悪い。
行と列のどちらへ集計するか
DataFrame のメソッドは、既定で列ごとに集計します。
print(frame[['国語', '数学']].median())
print(frame[['国語', '数学']].mean(axis=1))国語 66.0
数学 68.0
dtype: float64
0 65.0
1 73.5
2 65.0
3 75.0
4 56.5
dtype: float64上は科目ごとの中央値、下は 1 人ごとの 2 科目の平均です。axis=1 を渡すと横方向に集計されます。
科目ごとの平均を出したいのか、生徒ごとの平均を出したいのか。同じデータでも、集計の向きで答える問いが変わります。
群ごとに集計する
列の値でグループに分け、それぞれの統計量を出せます。
print(frame.groupby('組')['数学'].mean())組
A 62.333333
B 77.500000
Name: 数学, dtype: float64複数の統計量をまとめて出すこともできます。
print(frame.groupby('組')['数学'].agg(['count', 'mean', 'std']))count mean std
組
A 3 62.333333 6.658328
B 2 77.500000 3.535534A 組が 3 人、B 組が 2 人です。この人数を見ずに平均だけを比べると、判断を誤ります。
B 組の std が 3.54 と小さく見えますが、2 人しかいないので自由度は 1 しかありません。この値から母集団のばらつきを語る根拠は薄い。
groupby の出力には count を必ず添えておくと、あとで読み返したときに人数が分かります。

列どうしの関係
数値の列だけをとり出して、相関行列や共分散行列を出せます。
print(frame[['国語', '数学']].corr())
print(frame[['国語', '数学']].cov())国語 数学
国語 1.000000 0.875661
数学 0.875661 1.000000
国語 数学
国語 32.8 48.7
数学 48.7 94.3対角線が になるのは、自分自身との相関だからです。共分散行列のほうは、対角線に各列の分散が並びます。
国語の分散 32.8 と数学の分散 94.3。数学のほうが 3 倍近く散らばっています。
相関 0.876 は強い正の関係ですが、5 人ぶんしかありません。この人数では、相関係数の値そのものが大きく振れます。
欠測値は既定で落ちる
pandas の集計は、既定で欠測を無視します[1]。
data = pd.Series([4.0, 8.0, np.nan, 9.0, 6.0])
print(data.mean()) # 6.75
print(data.count()) # 4
print(len(data)) # 5
print(data.isna().sum()) # 1.mean() が 6.75 を返しました。欠測を除いた 4 個で計算しています。
NumPy が nan を返したのとは反対の設計です。pandas のほうが便利ですが、欠測があることに気づかないまま進む危険もある。
.count() は欠測を除いた個数、len() はもとの長さです。この 2 つがずれていたら、欠測があるということ。
落とし穴は合計を自分で個数で割ったときです。.sum() は欠測を飛ばすのに、len() は飛ばしません。
この例では になり、正しい 6.75 とずれます。割る相手は .count() のほう。
欠測をどう扱うか
集計が通ってしまうぶん、欠測の扱いは自分で決める必要があります。
まず isna().sum() で何個あるかを数える
無作為に抜けているかどうかを考える
落とすか埋めるかを決めて、決めたことを記録する
大きい値だけが抜けている場合、残りで計算した平均は母平均の推定になりません。関数は落としてくれますが、落としてよいかは判断できない。
報告するときは、何個のうち何個で計算したかを一緒に書きます。describe の count がその役目を果たします。
pandas の Series に .var() を当てたら 4.0、np.var で同じデータを計算したら 3.6 でした。原因はどれですか。
- 既定の ddof が pandas は 1、NumPy は 0 だから
- pandas が欠測を落としているから
- 計算の精度が違うから
describe を 1 度呼べば、count と mean と std がまとめて出ます。あとは ddof の既定と、count と len のずれだけ確かめる。
型を見て、向きを決めて、欠測を数える。集計そのものより、その前後のほうに手間をかける価値があります。














pandas は n−1 で割る不偏分散、NumPy は n で割る標本分散を既定にしています。この例に欠測はなく、精度の問題でもありません。data.var(ddof=0) とすれば 3.6 で一致します。