rcmdnk's blog

マンガでわかる統計学 素朴な疑問からゆる~く解説 (サイエンス・アイ新書)

標準偏差(Standard Deviation, std)を計算する際、 いろいろなライブラリでstdと呼ばれる関数がありますが それぞれ自由度のデフォルト値が違うものがあっていつも調べ直してしまうという話。

主にPythonのnumpyとpandasのもの。

std

標準偏差

とあるn個のデータ、 \(x_1, x_2, ..., x_n\) の標準偏差は

平均値 \(\hat{x}\) を使って

\[\begin{aligned} s = \sqrt{\frac{1}{n}\sum^{n}_{i=1}\left(x_i - \hat{x}\right)^2} \end{aligned}\]

となります。

このデータがある母集団から抜き出したものだとすると、もとの母集団の標準偏差は

\[\begin{aligned} v = \sqrt{\frac{1}{n-1}\sum^{n}_{i=1}\left(x_i - \hat{x}\right)^2} \end{aligned}\]

と推定され、これを不偏標準偏差といいます。

違いは最初の\(n\)で割っているか\(n-1\)で割ってるかで、 この部分が自由度(Degree of Freedom)を表しています。

詳しい説明はたくさんあるので省くとして、 \(n\)が十分大きいときには\(n-1\)で割ろうが\(n\)で割ろうがほとんど値に変わりはありませんが いずれにしろ値は変わってしまうのできちんと欲しい物に対応した値を使わないといけません。

いろいろなライブラリの標準偏差関数

Pythonでこのあたりの統計量をよく扱うライブラリとしてNumpyとPandasがあります。

これらは共にstdという関数を持っていますが、それらの自由度にあたる部分のデフォルト値が違っています。

共に、ddof(Delta Degree of Freedome)という引数を持っていて、 自由度部分がデータ数\(n\)と合わせて\(n-ddof\)になるものですが、これが違ってきます。

テーブルデータを扱っていればPandasで読み込んで直接DataFrameのstdを使う事も多いですが、 ちょっと一部だけ配列で取り出してnumpy.stdで計算してみて、みたいなこともあったりします。

そういったときに毎回混乱してhelpで見てみるという。。。

これら以外に、

  • scipy.stats.tstd: ddof=1 (the Trimmed Standard Deviation, 端の値を外れ値として落として計算する機能を含む)

ddof=1がデフォルトです。

また、以下のものはddofに対応するパラメーターはなく、決まった値だけを返します。

標準ライブラリのstatisticsが持つpstdevの方はthe Population Standard Deviation (母標準偏差)のPがついたもので、 入力の集団が母集団そのものとしてddof=0でその集団の標準偏差を返すようになっています。

statisticsに関してはpなしの方がデフォルトだと思うと、

  • Numpy, scikit-learn: ddof=0
  • Pandas, SciPy, statistics: ddof=1

がそれぞれのデフォルト値となっています。

この辺何度やってもどちらか不安になるので、やっぱり使う時はちゃんと引き数指定でやった方が安全です。

scikit-learnのStandardScalerだと変更できないのでこれはわからなくなったら確認。

Python以外でもたまに使うExcelの関数なんかだと、 STDEV.S 関数STDEV.P 関数 と言った感じで別々の関数があったりします。 (ここのSはthe Sample Standard Deviation、標本標準偏差のS)

テスト

テストコード:

ltitleang:python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env python3

import numpy as np
import pandas as pd
import statistics
import scipy
from sklearn.preprocessing import StandardScaler


x = [1, 2, 3, 4, 5]
print('Data:', x)
print(f"{'numpy.std':40s}: {np.std(x)}")
print(f"{'numpy.std, ddof=0':40s}: {np.std(x, ddof=0)}")
print(f"{'numpy.std, ddof=1':40s}: {np.std(x, ddof=1)}")
df = pd.DataFrame(x)
print(f"{'pandas.DataFrame.std':40s}: {df.std()[0]}")
print(f"{'pandas.DataFrame.std, ddof=0':40s}: {df.std(ddof=0)[0]}")
print(f"{'pandas.DataFrame.std, ddof=1':40s}: {df.std(ddof=1)[0]}")
print(f"{'scipy.stats.tstd':40s}: {scipy.stats.tstd(x)}")
print(f"{'scipy.stats.tstd, ddof=0':40s}: {scipy.stats.tstd(x, ddof=0)}")
print(f"{'scipy.stats.tstd, ddof=1':40s}: {scipy.stats.tstd(x, ddof=1)}")
s = StandardScaler()
s.fit(np.array(x).reshape(-1, 1))
print(f"{'sklearn.preprocessing.StandardScaler':40s}: {s.scale_[0]}")
print(f"{'statistics.stdev':40s}: {statistics.stdev(x)}")
print(f"{'statistics.pstdev':40s}: {statistics.pstdev(x)}")

結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
Data: [1, 2, 3, 4, 5]
numpy.std                               : 1.4142135623730951
numpy.std, ddof=0                       : 1.4142135623730951
numpy.std, ddof=1                       : 1.5811388300841898
pandas.DataFrame.std                    : 1.5811388300841898
pandas.DataFrame.std, ddof=0            : 1.4142135623730951
pandas.DataFrame.std, ddof=1            : 1.5811388300841898
scipy.stats.tstd                        : 1.5811388300841898
scipy.stats.tstd, ddof=0                : 1.4142135623730951
scipy.stats.tstd, ddof=1                : 1.5811388300841898
sklearn.preprocessing.StandardScaler    : 1.4142135623730951
statistics.stdev                        : 1.5811388300841898
statistics.pstdev                       : 1.4142135623730951
Sponsored Links
Sponsored Links

« LEDテープライトによる間接照明 MathJaxを使って数式をWebページに表示する »

}