今回は、分散を求める関数であるnp.var()関数について解説します。まずは簡単な分散の復習から入りましょう。

分散

分散というのは一般的にデータのばらつき度合いを示すために使われる指標です。平均との偏差の2乗を平均したものが分散になります。式で表すと以下のようになります。

求める分散の値をV とすると、

V = \frac{1}{N}\sum_{i=0}^N (x - \overline{x})^2

ここで\overline{x} x の平均値を表しています。このV の平方根をとったものが標準偏差です。 標準偏差が使われるのは、もととなるデータにおける単位を揃えるためです。分散のままだと単位はもとのデータの単位をそれぞれ2乗したものになってしまいます。

式を見てもらえばわかるように、平均からデータがどれくらい離れているのか、その平均をとることによって分散という指標はできています。 あくまで散らばり具合の基準点は平均となります。

np.var関数

では実際に関数の使い方を見ていきましょう。

APIドキュメント

まずはAPIドキュメントをみます。この関数のAPIドキュメントは以下の通りです。

numpy.var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False)

params:

パラメータ名 概要
a 配列に相当するもの 分散を求めたい配列を指定します。ndarrayでなかった場合変換が試みられます。
axis Noneもしくはint
もしくはintのタプル
(省略可能)初期値None
どの軸(axis)に沿って分散を計算するかを指定します。Noneの場合、すべてのデータを対象に分散を求めます。
dtype データ型 (省略可能)初期値None
分散を計算する際に用いるデータ型を指定します。デフォルトでは入力された配列が整数型の場合float32が指定され、それ以外の場合、aで指定された配列のデータ型に従います。
out ndarray (省略可能)初期値None
結果を格納する配列をここで指定します。
ddof int (省略可能)初期値0
分散を計算する際、平均との偏差の2乗の和をN-ddofで割ります。初期値ではddof=0なのでデータ数であるNで割ることになります。
keepdims bool値 (省略可能)初期値False
結果を出力するとき、要素数が1となった次元を残したままにするかどうかを指定します。Trueの場合残します。こうすることでブロードキャスティングが正しく行われるようにできます。

returns:

outに何も指定されていない場合、求めた分散を格納した配列が返されます。
指定されている場合は出力された配列への参照が返されます。

指定できる引数が数多くあります。最初に分散を求めたい配列を指定します。axisで分散を計算する方向を指定します。axisについては以下の記事で詳しく解説しているので気になる方は見てみてください。

dtypeで計算する際に用いるデータ型を指定し、outで結果を格納する配列を指定します。

ddofで分散を求める際にddof=0であれば、与えられたデータを集合全体と見てその分散を求める標本分散をddof=1ならば一部のデータから全体のデータの分散を計算する不偏分散を求めます。

NumPyの軸(axis)と次元数(ndim)は何を意味するのか /features/numpy-axis.html

標本分散と不偏分散については以下のサイトで詳しく解説されているので参考にしてみてください。

18-4. 標本分散と不偏分散

分散を求める

では、実際に使っていきましょう。特に引数を指定せずにデータ全体の分散を求めてみます。

In [1]: import numpy as np

In [2]: a = np.array([10, 20, 12, 0, 3, 5])

In [3]: np.var(a) # 特に引数を指定しない場合は6つのデータから分散を求める。
Out[3]: 43.55555555555555

次にaxisを指定してみます。

In [4]: b = np.random.randint(20, size=(3,4))

In [5]: b # bの中身を確認。
Out[5]:
array([[ 3, 13, 12,  1],
       [10, 19,  1,  6],
       [ 8, 13, 12, 18]])

In [6]: np.var(b) # 特にaxisを指定しないと全体の分散を求める。
Out[6]: 33.388888888888886

In [7]: np.var(b, axis=0) # 行ごとの分散を求める
Out[7]: array([  8.66666667,   8.        ,  26.88888889,  50.88888889])

In [8]: np.var(b, axis=1) # 列ごとの分散を求める
Out[8]: array([ 28.1875,  43.5   ,  12.6875])

In [10]: np.var(b, axis=(0,1)) # こうすると0,1番目の軸方向でやるとすべての範囲の分散を求める
Out[10]: 33.388888888888886

次にdtypeを指定します。

In [16]: c = np.random.randn(100).reshape(5,20) # 標準正規分布からランダムな数列を生成。

In [18]: c.dtype # データ型を確認
Out[18]: dtype('float64')

In [19]: c
Out[19]:
array([[ 0.35225642,  0.42735088,  0.22483062, -1.29718125,  0.19805096,
        -0.52872563, -0.37953642,  1.35085875,  1.06166236, -1.14408896,
         1.54089466,  0.5729667 , -1.55339662, -1.11796015, -0.30161906,
        -0.04310339, -0.90791445,  1.33607073, -1.1710254 ,  1.49489929],
       [ 2.01375678, -0.64110448,  0.18106096, -0.03658098, -0.62123187,
        -0.61729062,  0.19154253,  0.93459067, -1.67334457, -1.77243433,
         1.17715007, -0.58395848,  0.64823962, -0.19429409,  0.40297725,
         0.38401512, -0.55167875, -0.30052436, -0.86550869, -1.29361117],
       [ 0.00993361,  0.48064153,  0.12597228, -0.13348795,  0.13881167,
        -1.40062426,  0.33302593, -1.07486468,  0.22216967, -0.79206793,
        -0.64137661, -1.80691328, -1.18824   , -0.23372683, -0.35116358,
         0.95200835,  0.3781709 , -1.23003955, -1.35842974, -1.05603139],
       [-1.48968822, -1.26374257,  0.80641034,  0.05188685, -0.42511282,
         0.12333293,  3.26526552, -2.05103589,  0.8892263 , -1.83975271,
         1.09835528,  0.86211006, -1.15216875, -0.57875082, -0.63924306,
        -0.40284799, -0.16096745, -0.59009197, -0.61755593, -0.86973981],
       [-0.47688695,  0.62437855, -0.32973941,  0.0707855 ,  1.18317729,
         0.37800033, -1.30444915,  2.28224717,  0.10517787,  0.1081565 ,
         0.73538965,  1.28810643, -1.70036478,  1.60113382, -1.18890108,
        -1.61474179,  0.71776759, -1.95725267, -0.15671136, -0.91014567]])

In [20]: np.var(c, dtype='float32') # dtypeを指定する。
Out[20]: 1.043533

In [22]: np.var(c, dtype='float64')
Out[22]: 1.0435329101767985

ddofを指定する

次にddofを変更してみます。先程の標準正規分布から生成した配列のサンプルデータ数を少なくして分散がddof=0,ddof=1のどちらがより1に近づくか見てみましょう。

In [23]: d = np.random.randn(10) # 10個のサンプルデータから計算してみる。

In [24]: d
Out[24]:
array([-1.87744275,  1.02445975,  0.02985718, -0.96668578,  1.45083393,
       -0.19564106, -0.72043885, -0.45597266, -1.49547549, -1.33429341])

In [25]: np.var(d, ddof=0) # まずはデフォルトの値であるddof=0から。(標本分散)
Out[25]: 1.0334719835739459

In [26]: np.var(d, ddof=1) # 次は不偏分散を求める。
Out[26]: 1.1483022039710509

In [27]: e = np.random.randn(5) # もっとサンプル数を減らしてみる

In [28]: e
Out[28]: array([-1.76749733, -2.19574813, -0.54184825, -0.80253071, -0.65802786])

In [29]: np.var(e)
Out[29]: 0.43964219645143265

In [30]: np.var(e, ddof=1) # 1により近づく。
Out[30]: 0.54955274556429079

サンプル数を少なくするほど母集団の分散を計算できていることがわかります。

配列の次元数を保存する

最後はkeepdimsです。keepdimをTrueにすることでブロードキャストを適用します。例えば計算した分散の値でもとのデータを割りたいときにはkeepdims=Trueにすると安全です。

In [31]: f = np.random.randint(20, size=(2,5,10)) # 3次元のランダム配列

In [32]: f
Out[32]:
array([[[13,  7,  7, 13, 19, 17,  1, 17,  8, 12],
        [19,  5,  5,  5, 14, 11,  3,  5,  0, 12],
        [ 2, 17, 14,  4,  6, 19, 14, 15, 12, 14],
        [16,  6, 12,  2, 12, 11,  9, 18,  0, 13],
        [ 0, 13, 10, 10,  6,  2,  4, 11, 18,  6]],

       [[ 9,  5,  7,  8, 18,  4, 14,  7,  3, 11],
        [ 5, 10, 11,  3, 10, 19, 12,  5, 18,  0],
        [17,  3, 18,  0, 14, 12,  1, 16,  4,  9],
        [ 6,  0, 12, 11,  9,  2,  1, 19, 14,  7],
        [ 1,  5, 12,  9, 11, 19, 14, 12,  0,  7]]])

In [33]: f_var = np.var(f, axis=1) # それぞれの行ごとの分散を求める

In [34]: f/f_var # これだとブロードキャストがうまく適用されない
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-34-0ad664488579> in <module>()
----> 1 f/f_var # これだとブロードキャストがうまく適用されない

ValueError: operands could not be broadcast together with shapes (2,5,10) (2,10)

In [35]: f_var.shape # 形状を確認してみる。
Out[35]: (2, 10)

In [36]: f_var = np.var(f, axis=1, keepdims=True)

In [37]: f/f_var # keepdimsをTrueにするとうまく計算できる。
Out[37]:
array([[[ 0.22413793,  0.32649254,  0.65789474,  0.78502415,  0.7711039 ,
          0.48295455,  0.04512635,  0.7535461 ,  0.16447368,  1.53061224],
        [ 0.32758621,  0.23320896,  0.46992481,  0.30193237,  0.56818182,
          0.3125    ,  0.13537906,  0.22163121,  0.        ,  1.53061224],
        [ 0.03448276,  0.79291045,  1.31578947,  0.24154589,  0.24350649,
          0.53977273,  0.63176895,  0.66489362,  0.24671053,  1.78571429],
        [ 0.27586207,  0.27985075,  1.12781955,  0.12077295,  0.48701299,
          0.3125    ,  0.40613718,  0.79787234,  0.        ,  1.65816327],
        [ 0.        ,  0.60634328,  0.93984962,  0.60386473,  0.24350649,
          0.05681818,  0.18050542,  0.48758865,  0.37006579,  0.76530612]],

       [[ 0.31424581,  0.46992481,  0.56451613,  0.48309179,  1.69172932,
          0.07727975,  0.37796976,  0.25216138,  0.06229236,  0.7994186 ],
        [ 0.17458101,  0.93984962,  0.88709677,  0.18115942,  0.93984962,
          0.36707883,  0.32397408,  0.18011527,  0.37375415,  0.        ],
        [ 0.59357542,  0.28195489,  1.4516129 ,  0.        ,  1.31578947,
          0.23183926,  0.02699784,  0.57636888,  0.08305648,  0.65406977],
        [ 0.20949721,  0.        ,  0.96774194,  0.66425121,  0.84586466,
          0.03863988,  0.02699784,  0.68443804,  0.29069767,  0.50872093],
        [ 0.0349162 ,  0.46992481,  0.96774194,  0.54347826,  1.03383459,
          0.36707883,  0.37796976,  0.43227666,  0.        ,  0.50872093]]])

参考

numpy.var — NumPy v1.13 Manual