matplotlib.pyplot – グラフエリアの表示要素

概要

pyplotで直接グラフを描画する際の基本的なオプションの例示。

各オプションの説明

グラフタイトル

pyplot.tytle(label[, loc])

文字列labelをグラフ上部に表示する。表示位置はloc="left"/"center"/"right"で指定(デフォルトは"center")

軸ラベル

pyplot.xlabel(label)
pyplot.ylabel(label)

文字列labelをx軸/y軸のラベルとして表示する。

軸の範囲

pyplot.xlim(left, right)
pyplot.ylim(bottom, top)

x軸・y軸の上限・下限を設定する。設定は2つの変数で与えるか、以下のようにタプルで与えてもよい。

pyplot.xlim((left, right))

また、上下限のいずれか1つの値を指定し、他方の値を保持したまま値の変更が可能。

pyplot.xlim(right=1)
pyplot.ylim(bottom=-1)

引数なしで実行すると、上限・下限の値がタプルで返される。

left, right = pyplot.xlim()

軸目盛の設定

pyplot.xticks(ticks[, labels])
pyplot.yticks(ticks[, labels])

リストなどで与えた値を軸目盛とする。labelsticksと同じ要素数で与えると、その内容が軸目盛のラベルに置き換えられる。

凡例の表示

pyplot.legend(loc=location)

データに設定されたラベルで、locationで指定した位置に凡例を表示する。

locationは文字列か整数のコードで以下のように指定。

location string location code
‘best’ 0
‘upper right’ 1
‘upper left’ 2
‘lower left’ 3
‘lower right’ 4
‘right’ 5
‘center left’ 6
‘center right’ 7
‘lower center’ 8
‘upper center’ 9
‘center’ 10

格子の表示

pyplot.grid(True/False)

Trueを指定すると、軸目盛に対応した格子が描かれる。

水平線・垂直線の表示

pyplot.hlines([y, xmin, xmax, colors='k', linestyles='solid', label='']
pyplot.vlines([x, ymin, ymax, colors='k', linestyles='solid', label='']

たとえばhlinesの場合、水平線を引くyの値をリスト等で与え、xminxmaxの間に線を描く。colorsは線の色、linestylesは線のスタイルで'solid', 'dashed', 'dashdot', 'dotted'のいずれか。

colorslinestylesは、1つ指定した場合は全ての水平線/垂直線に適用されるほか、yの要素数にあわせて個別に指定することもできる。

colorscolorlinestyleslinestyleと単数形で指定しても実行される。

 

 

numpy – arange, linspace~数列の生成

numpy.arange()~間隔を指定した数列の生成

引数・戻り値

numpy.arange([start, ]stop, [step, ]dtype = None)

引数 概要
start int/float 数列の開始値。省略可(デフォルトは0)
stop int/float 数列の終了値。省略不可。
step int/float 数列の間隔。省略可(デフォルトは1)。
dtype dtype 生成される数列のデータ型。指定しない場合は引数の型が適用される。

戻り値:開始値startからstepずつ増加し、stop未満の数列のndarray

引数の指定例

必ず以下のように指定する。numpy.arange(stop=5, step=2)のような指定はできない。

numpy.arange(stop)
0以上stop未満で増分1の数列を返す。数列の型は引数の型による。
numpy.arange(start, stop)
start以上stop未満で増分1の数列を返す。数列の型は引数の型による。
numpy.arange(start, stop, step)
startから始まり、stepずつ増加/減少し、stepの手前までの数列を返す。数列の型は引数の型による。

その他

降順の数列

stepを負の値にして降順の指定も可能。この場合はstart≥n>endの範囲となる。

実数列

stepを指定して実数の列も作れる。

ただしデフォルトでstart=0(0.0)step=1(1.0)なので以下のような挙動になる。

dtypeによる型指定

dtypeで強制的に型を指定可能。

numpy.linspace()~個数を指定した数列の生成

引数・戻り値

numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)

引数 概要
start int/float 数列の開始値。省略不可。
stop int/float 数列の終了値。省略不可。
num int 数列の要素数。省略可(デフォルトは50)。
endpoint bool stopを要素に含むかどうかを指定。Trueで含み、Falseなら含まない。省略可(デフォルトはTruestopを含む)。
retstep bool 戻り値として配列に加えて公差を返すかどうかを指定。Trueで配列を第1要素、公差を第2要素とするタプルを返し、Falseなら配列のみを返す。
dtype dtype 生成される数列のデータ型。指定しない場合はfloatが適用される。

戻り値:startからstopまでをnum等分した数列のndarray

引数の指定例

numpy.linspace(start, stop)
startから始まりstopで終わる50個の数列を返す。
numpy.linspace(start, stop, num)
startから始まりstopで終わるnum個の数列を返す。
numpy.linspace(start, stop, num, endpoint=False)
startから始まりstopの手前で終わるnum個の数列を返す。

その他

dtypeによる型指定

dtype=intと指定して、整数列を生成できる。

ただし分割個数を適切に指定しないと、変な結果になる。

retstepを指定した公差の取り出し

retstep=Trueと指定すると、第1要素に数列のndarray、第2要素に公差を持つタプルが返される。

 

 

Python3 – forループの最初と最後だけ処理を変えたい

やりたいこと

たとえば多数の計算結果を表示するとき、リストなら最初だけ"["がついて、各要素の後ろに", "が付加されて、最後の要素だけそのカンマがなくて"]"が表示される。このような処理をいろいろな形で実装したいというような場合。

単純に考えると次のように格好が悪い。

これを、<0, 1, 2, 3, 4>のように末尾だけ表示方法を変えたい、さらには、何個かごとに改行させて、2行目以降の行頭を1行目と変更したいときの処理を考える。

インデックスを利用する方法

1行で表示する場合

以下の例では、enumerateでコレクションのインデックスを取り出して、その値によって処理を分けている。行頭"<"の処理はforループの前でやってもよいが、次の複数行表示の準備としてif文で判定している。

複数行に分けて表示する場合

長いリストや多次元の配列などを複数行に分けて表示したい場合、行ごとの行末文字も変わってくる。そのような処理をforループで書いてみた。

ジェネレーターを使う方法

ジェネレーターを使った方法。このジェネレーターは、各要素のprefix、本体要素、suffixの3つを返す。

1文字目だけ処理を変えるため、コレクションをイテレーターとし、ループに入る前に最初の要素を取り出している。next()関数でイテレーターの内容を取り出すと、その後のforループでは以降の要素が順次取り出されることを利用している。

prefixは最初だけ"<"を使い、その後は""としてprefixなしとして扱っている。

1行で表示する場合

複数行に分けて表示する場合

複数行に分ける場合、1行当たりの要素数を設定し、カウンターで要素位置を検出している。カウンターの値から先頭要素と判定した場合にprefixを" "とし、その他の場合は""としている。

 

Python3 – yield文によるジェネレーターの実装

return文の確認

yield文はreturn文と同様に関数の中で使われ、戻り値を指定するが、その挙動は全く異なる。

まず準備として、通常のreturn文を持つ関数の動作を確認。呼び出されるたびに常に関数の先頭からreturn文まで実行される。

yield文にするとジェネレーターが生成される

このreturn文をyield文に変更してみると、関数の戻り値を返すのではなく、この関数がジェネレーターオブジェクトのコンストラクターとなっている。

ジェネレーターオブジェクトには、(Python3では)__next__()メソッドがあって、ジェネレーターで生成された値を順次取り出してくれる。そこで上のfunc1()でジェネレーターのインスタンスを生成し、直接値を取り出してみる。

このジェネレーターは値を1つしか生成しないので、2つ目を取り出そうとするとStopIteration例外を投げる。

yield文によるジェネレーターの挙動

以下の例は、3つの値を返すジェネレーターの例で、確認のためにyield文の前の処理をprint文で表示させるようにしている。なお、ここでは__next__()メソッドの代わりに組み込み関数next()を用いている。

関数で生成されるのはジェネレーターでStopIterationを投げるので、次のようにfor文で使える。

yiled文とreturn文を混ぜた場合

関数の中でyield文を書くと、return文があってもジェネレーターが生成される。

ただしreturn文があるとそこでStopIterationが投げられる。このとき、return文で指定した戻り値が得られるようだが、ジェネレーターとしては無視されるらしい。

このジェネレーターをfor文で使うと、以下のようにreturn文の手前まで実行される。

実装の例

たとえば引数を与えて、その数以下であるフィボナッチ数列を返すジェネレーターを考える。

 

二項分布の平均と分散

概要

二項分布B(n, p)の平均と分散は以下のようになる。

(1)    \begin{alignat*}{1} E(X) &= np \\ V(X) &= np(1-p) \end{alignat*}

これらを導くのに、有用なテクニックを使っているのでまとめておく。

直接定義式から導く方法

この方法は、平均、分散の定義式から直接導いていく過程で、意図的に二項展開の形に持ち込んでいく方法。

平均

平均の定義から、以下のように変形していく。

(2)    \begin{alignat*}{1} E(X) &= \sum_{k=0}^n k {}_n\mathrm{C}_k p^k (1-p)^{n-k} \\ &= \sum_{k=1}^n k \frac{n!}{(n-k)! k!} p^k (1-p)^{n-k} \\ &= \sum_{k=1}^n n \frac{(n-1)!}{\left( (n-1) - (k-1) \right)! (k-1)!} pp^{k-1} (1-p)^{(n-1)-(k-1)} \end{alignat*}

上式では、各項にkが乗じられていることから、以下の流れで変形している。

  • k=0のとき1番目の項はゼロとなるので、和の開始値をk=1とする
  • 分子にあるkを使って、{}_n C_kの分母においてk! \rightarrow (k-1)!とする
  • これに整合させるため、分母において(n-k)!((n-1) - (k-1))!と変形
  • さらに組み合わせの式に整合させるため分子をn (n-1)!と変形し、最終的に{}_{n-1} C_{k-1}を引き出している。
  • 後に二項定理を使うため、p, \; 1-pの指数も調整している

ここでk-1 = k'とおくと、カウンターの範囲はk=1 \sim nからk'=0 \sim n-1となることから、

(3)    \begin{alignat*}{1} E(X) &= np \sum_{k'=0}^{n-1} \frac{(n-1)!}{\left( (n-1) - k' \right)! k'!} p^{k'} (1-p)^{(n-1)-k'} \\ &= np (p + 1 - p)^{n-1} \\ &= np \end{alignat*}

上式では、変形した和の部分が二項展開の形になっていることを利用している。

分散

分散については、k^2が各項に乗じられるが、これをk(k-1) + kと変形して、階乗のランクを下げるところがミソ。

(4)    \begin{alignat*}{1} V(X) &= E(X^2) - [E(X)]^2 \\ &= \sum_{k=0}^n k^2 {}_n C_k p^k (1-p)^{n-k} - (np)^2 \\ &= \sum_{k=0}^n \left( k(k-1) + k \right) {}_n C_k p^k (1-p)^{n-k} - (np)^2 \\ &= \sum_{k=2}^n k(k-1) {}_n C_k p^k (1-p)^{n-k} + \sum_{k=1}^n k {}_n C_k p^k (1-p)^{n-k} - (np)^2 \\ \end{alignat*}

上式で、1項目はk(k-1)が乗じられているのでカウンターをk=2から、2項目は同じくk=1からとしている。

ここで1項目についてk'' = k - 2と置いて、平均の時と同じ考え方で以下のように変形。

(5)    \begin{alignat*}{1} &\sum_{k=2}^n k(k-1) {}_n C_k p^k (1-p)^{n-k} \\ &= \sum_{k=2}^n k(k-1) \frac{n!}{(n-k)! k!} p^k (1-p)^{n-k} \\ &= \sum_{k=2}^n n(n-1) \frac{(n-2)!}{((n-2) -(k-2))! (k-2)!} p^2 p^{k-2} (1-p)^{(n-2)-(k-2)} \\ &= n(n-1) p^2 \sum_{k'=0}^{n-2} \frac{(n-2)!}{((n-2) -k'')! k''!} p^{k''} (1-p)^{(n-2)-k''} \\ &= n(n-1) p^2 ( p + (1-p))^{n-2} \\ &= n(n-1) p^2 \end{alignat*}

2項目については、k' = k-1とおいて、

(6)    \begin{alignat*}{1} &\sum_{k=1}^n k {}_n C_k p^k (1-p)^{n-k} \\ &= \sum_{k=1}^n n \frac{(n-1)!}{\left( (n-1) - (k-1) \right) ! (k-1)!} p p^{k-1} (1-p)^{(n-1)-(k-1)} \\ &= np \sum_{k'=0}^{n-1} \frac{(n-1)!}{\left( (n-1) - k'\right) ! k'!} p p^{k'} (1-p)^{(n-1)-k'} \\ &= np \left( (p + (1-p) \right) ^{n-1} \\ &= np \end{alignat*}

以上を併せて、

(7)    \begin{alignat*}{1} V(X) &= n(n-1) p^2 + np - (np)^2 \\ &= np(1-p) \end{alignat*}

微分による方法

この方法は、kp^k, \; k^2 p^kの形に着目して、全事象の式を微分する方法。式展開が素直であり、平均の式を微分した結果がそのまま分散の式になってしまうところが美しい。

平均

二項分布の全確率の和は1となる。

(8)    \begin{equation*} \sum_{k=0}^n {}_n \mathrm{C}_k p^k (1-p)^{n-k} = (p + 1 - p)^n = 1 \end{equation*}

この式の両辺をpで微分する。

(9)    \begin{gather*} \sum_{k=0}^n {}_n \mathrm{C}_k \left( k p^{k-1} (1-p)^{n-k} - (n-k)p^k (1-p)^{n-k-1} \right) = 0 \\ \sum_{k=0}^n {}_n \mathrm{C}_k p^{k-1} (1-p)^{n-k-1} \left( k(1-p) - (n-k)p \right) = 0 \\ \sum_{k=0}^n {}_n \mathrm{C}_k p^{k-1} (1-p)^{n-k-1} (k - np) = 0 \\ \end{gather*}

両辺にp(1-p)をかける。

(10)    \begin{gather*} \sum_{k=0}^n {}_n \mathrm{C}_k p^k (1-p)^{n-k} (k - np) = 0 \\ \sum_{k=0}^n k {}_n \mathrm{C}_k p^k (1-p)^{n-k} = np \sum_{k=0}^n {}_n \mathrm{C}_k p^k (1-p)^{n-k}\\ \therefore E(X) = np \end{gather*}

分散

式(10)をもう一度pで微分する。

(11)    \begin{gather*} \sum_{k=0}^n k {}_n \mathrm{C}_k p^k (1-p)^{n-k} = np \\ \sum_{k=0}^n k {}_n \mathrm{C}_k \left( kp^{k-1} (1-p)^{n-k} -(n-k)p^k (1-p)^{n-k-1} \right) = n\\ \sum_{k=0}^n k {}_n \mathrm{C}_k p^{k-1} (1-p)^{n-k-1} \left( k (1-p) -(n-k)p \right) = n\\ \sum_{k=0}^n k {}_n \mathrm{C}_k p^{k-1} (1-p)^{n-k-1} (k  - np) = n\\ \end{gather*}

両辺にp(1-p)をかける。

(12)    \begin{gather*} \sum_{k=0}^n k {}_n \mathrm{C}_k p^k (1-p)^{n-k} (k  - np) = np(1-p) \\ \sum_{k=0}^n k^2 {}_n \mathrm{C}_k p^k (1-p)^{n-k} - np \sum_{k=0}^n k {}_n \mathrm{C}_k p^k (1-p)^{n-k}= np(1-p) \\ E(X^2) - (np)^2 = np(1-p) \\ \therefore V(X) = np(1-p) \end{gather*}

 

回帰分析~重回帰

概要

単回帰(X, Y)への線形関係を扱うのに対して、重回帰は複数の説明変数X_k \; (k=1 \ldots m)Yの線形関係を扱う。

データ(X_1, \ldots , X_m, Y) = (x_{1i}, \ldots, x_{mi}, y_i), (i = 1 \ldots n)に対して以下の線形式で最も説明性の高いものを求める。

(1)    \begin{equation*} \hat{y} = w_0 + w_1 x_1 + \cdots + w_m x_m \end{equation*}

そのために、データとその推測値の残差\hat{y_i} - y_iの平方和が最小となるような係数w_0, w_1, \ldots, w_mを最小二乗法により求める。

定式化

説明変数とターゲットのデータセットn組が次のように得られているとする。

(2)    \begin{equation*} x_{1i}, \ldots , x_{mi}, y_i \quad {\rm where} \quad i = 1, \ldots , n \end{equation*}

残差の平方和については

(3)    \begin{equation*} \min \sum_{i=1}^n (\hat{y} -y_i)^2 \quad {\rm where} \quad \hat{y}_i = w_0 + w_1 x_{1i} + \cdots w_m x_{mi} \end{equation*}

ここで残差を最小化するw_0, w_1, \ldots, w_mを求めるために、それぞれで偏微分する。

(4)    \begin{equation*} \begin{array}{c} \left\{ \begin{array}{l} \displaystyle \frac{\partial}{\partial w_0} \sum_{i=1}^n (w_0 + w_1 x_{1i} + \cdots + w_m x_{mi} - y_i)^2 = 0 \\ \displaystyle \frac{\partial}{\partial w_1} \sum_{i=1}^n (w_0 + w_1 x_{1i} + \cdots + w_m x_{mi} - y_i)^2 = 0 \\ \vdots \\ \displaystyle \frac{\partial}{\partial w_m} \sum_{i=1}^n (w_0 + w_1 x_{1i} + \cdots + w_m x_{mi} - y_i)^2 = 0 \\ \end{array} \right. \end{equation*}

変形すると、

(5)    \begin{equation*} \left\{ \begin{array}{l} \displaystyle \sum_{i=1}^n 2(w_0 + w_1 x_{1i} + \cdots + w_m x_{mi} - y_i) = 0 \\ \displaystyle \sum_{i=1}^n 2(w_0 + w_1 x_{1i} + \cdots + w_m x_{mi} - y_i) x_{1i} = 0 \\ \vdots \\ \displaystyle \sum_{i=1}^n 2(w_0 + w_1 x_{1i} + \cdots + w_m x_{mi} - y_i) x_{mi} = 0 \\\end{array} \right. \end{array} \end{equation*}

更にこれを展開するのに、S_k = \sum_{i=1}^n x_{ki}, S_{kl} = \sum_{i=1}^n x_{ki} x_{li}, S_y = \sum_{i=1}^n y_i, S_{ky} = \sum_{i=1}^n x_{ki} y_iとおいて

(6)    \begin{equation*} \left\{ \begin{array}{l} n w_0 + w_1 S_1+ \cdots + w_m S_m - S_y = 0 \\ w_0S_1 + w_1 S_{11} + \cdots + w_m S_{1m} - S_{1y} = 0 \\ \vdots \\ w_0 S_m + w_1 S_{m1} + \cdots + w_m S_{mm} - S_{my} = 0 \\ \end{array} \right. \end{array} \end{equation*}

これを行列形式で表示すると

(7)    \begin{equation*} \left( \begin{array}{cccc} n & S_1 & \cdots & S_m \\ S_1 & S_{11} & \cdots & S_{1m} \\ \vdots & \vdots & & \vdots \\ S_m & S_{m1} & \cdots & S_{mm} \end{array} \right) \left( \begin{array}{c} w_0 \\ w_1 \\ \vdots \\ w_m \end{array} \right) = \left( \begin{array}{c} S_y \\ S_{1y} \\ \vdots \\ S_{my} \end{array} \right) \end{equation*}

解の導出

式(7)の連立方程式を解けば各係数を得ることができる。ここで左辺の係数行列は以下のようにも表せる。

(8)    \begin{equation*} \left( \begin{array}{cccc} 1 & 1 & \cdots & 1 \\ x_{11} & x_{12} & \cdots & x_{1n} \\ \vdots & \vdots & & \vdots \\ x_{m1} & x_{m2} & \cdots & x_{mn} \end{array} \right) \left( \begin{array}{cccc} 1 & x_{11} & \cdots & x_{m1} \\ 1 & x_{12} & \cdots & x_{m2} \\ \vdots & \vdots & & \vdots \\ 1 & x_{1n} & \cdots & x_{mn} \end{array} \right) = \boldsymbol{X}^T \boldsymbol{X} \end{equation*}

また右辺は次のように表せる。

(9)    \begin{equation*} \left( \begin{array}{cccc} 1 & 1 & \cdots & 1 \\ x_{11} & x_{12} & \cdots & x_{1n} \\ \vdots & \vdots & & \vdots \\ x_{m1} & x_{m2} & \cdots & x_{mn} \end{array} \right) \left( \begin{array}{c} y_1 \\ y_2 \\ \vdots \\ y_n \end{array} \right) = \boldsymbol{X}^T \boldsymbol{y} \end{equation*}

したがって式(7)は以下のように表せる。

(10)    \begin{equation*} \boldsymbol{X}^T \boldsymbol{X} \boldsymbol{w} = \boldsymbol{X}^T \boldsymbol{y} \end{equation*}

これを解くと

(11)    \begin{equation*} \boldsymbol{w} = ( \boldsymbol{X}^T \boldsymbol{X} )^{-1} \boldsymbol{X}^T \boldsymbol{y} \end{equation*}

ベクトル・行列による表現と解

定式化の段階からベクトル・行列を用いてみる。式(1)の各変数を全てベクトル形式で表すと以下のようになる。

(12)    \begin{equation*} \hat{\boldsymbol{y}} = \boldsymbol{x}^T \boldsymbol{w} \end{equation*}

ここで、

(13)    \begin{equation*} \boldsymbol{x} = \left( \begin{array}{c} 1 & x_1 & \vdots & x_n \end{array} \right) \quad , \quad \boldsymbol{w} = \left( \begin{array}{c} w_0 & w_1 & \vdots & w_n \end{array} \right) \end{equation*}

複数データからなるデータセットに対して、式(8)の行列表現を用いると、以下のように表現できる。

(14)    \begin{equation*} \hat{\boldsymbol{y}} = \boldsymbol{X} \boldsymbol{w} \end{equation*}

各データの残差については以下のようになる。

(15)    \begin{equation*} \hat{\boldsymbol{y}} - \boldsymbol{y} = \boldsymbol{X} \boldsymbol{w} - \boldsymbol{y} \end{equation*}

ここで残差の二乗和を損失関数Lとして、これを行列で表現すると以下のようになる。

(16)    \begin{align*} L &= \left( \hat{\boldsymbol{y}} - \boldsymbol{y} \right)^T \left( \hat{\boldsymbol{y}} - \boldsymbol{y} \right) \\ &= \left( \boldsymbol{Xw} - \boldsymbol{y} \right)^T \left( \boldsymbol{Xw} - \boldsymbol{y} \right) \\ &= \left( \boldsymbol{w}^T \boldsymbol{X}^T - \boldsymbol{y}^T \right) \left( \boldsymbol{Xw} - \boldsymbol{y} \right) \\ &= \boldsymbol{w}^T \boldsymbol{X}^T \boldsymbol{Xw} - \boldsymbol{w}^T \boldsymbol{X}^T \boldsymbol{y} - \boldsymbol{y}^T \boldsymbol{Xw} + \boldsymbol{y}^T \boldsymbol{y} \end{align*}

上式の第2項、第3項については以下のように整理できる。

(17)    \begin{align*} - \boldsymbol{w}^T \boldsymbol{X}^T \boldsymbol{y} - \boldsymbol{y}^T \boldsymbol{Xw} = - \left( \boldsymbol{Xw} \right)^T \boldsymbol{y} - \boldsymbol{y}^T \boldsymbol{Xw} = -2 \boldsymbol{y}^T \boldsymbol{Xw} \end{align*}

損失関数Lはスカラーであり、これを最小にするためにベクトルwで微分し、それらの値がゼロとなるような方程式とする。

(18)    \begin{align*} \frac{dL}{d\boldsymbol{w}} = \frac{d}{d\boldsymbol{w}} \left( \boldsymbol{w}^T \boldsymbol{X}^T \boldsymbol{Xw} - 2 \boldsymbol{y}^T \boldsymbol{Xw} + \boldsymbol{y}^T \boldsymbol{y} \right) = 2 \boldsymbol{X}^T \boldsymbol{Xw} -2 \boldsymbol{X}^T \boldsymbol{y} = \boldsymbol{0} \end{align*}

なお第1項については、式(7)と式(8)より\boldsymbol{X}^T\boldsymbol{X}は対象行列なので転置しても同じ行列となり、行列の微分の公式から以下のようになることを利用している。

(19)    \begin{align*} \frac{d}{d\boldsymbol{x}} \boldsymbol{w}^T \boldsymbol{X}^T \boldsymbol{Xw} = \left( \boldsymbol{X}^T \boldsymbol{X} + \left(\boldsymbol{X}^T \boldsymbol{X} \right)^T \right) \boldsymbol{w} = 2 \boldsymbol{X}^T \boldsymbol{X} \boldsymbol{w} \end{align*}

式(18)をwについて解いて以下を得る。

(20)    \begin{equation*} \boldsymbol{w} = \left( \boldsymbol{X}^T \boldsymbol{X} \right)^{-1} \boldsymbol{X}^T \boldsymbol{y} \end{equation*}

多重共線性

式(7)からw_0を消去する。

(21)    \begin{equation*} \left( \begin{array}{ccc} S_{11} - \dfrac{{S_1}^2}{n} & \cdots & S_{1m} - \dfrac{S_1 S_m}{n}\\ \vdots & & \vdots \\ S_{m1} - \dfrac{S_m S_1}{n} & \cdots & S_{mm} - \dfrac{{S_m_}^2}{n} \end{array} \right) \left( \begin{array}{c} w_1 \\ \vdots \\ w_m \end{array} \right) = \left( \begin{array}{c} S_{1y} - \dfrac{S_1 S_y}{n} \\ \vdots \\ S_{my} - \dfrac{S_m S_y}{n} \end{array} \right) \end{equation*}

これを分散・共分散で表すと以下の通り。ただし以下でV_{ii}=\rm{Var}(X_i)V_{ij} = \rm{Cov}(X_i, X_j)と表している。

(22)    \begin{equation*} \left( \begin{array}{ccc} V_{11} & \cdots & V_{1m}\\ \vdots & & \vdots \\ V_{m1} & \cdots & V_{mm} \end{array} \right) \left( \begin{array}{c} w_1 \\ \vdots \\ w_m \end{array} \right) = \left( \begin{array}{c} V_{1y} \\ \vdots \\ V_{my} \end{array} \right) \end{equation*}

ここでx_j = a x_i + bと2つの間に完全な線形関係がある場合、分散・共分散の性質から以下の関係が成り立つ。

(23)    \begin{equation*} V_{jj} = a^2V_{ii}, \; V_{ji} = V_{ij} = aV_{ii}, \; V_{jk} = V_{kj} = aV_{ji} = aV_{ij} \end{equation*}

これらを式(22)の係数行列に適用すると、

(24)    \begin{align*} &\left[ \begin{array}{ccccccc} V_{11} & \cdots & V_{1i} & \cdots & V_{1j} & \cdots & V_{1m}\\ \vdots && \vdots && \vdots && \vdots\\ V_{i1} & \cdots & V_{ii} & \cdots & V_{ij} & \cdots & V_{im}\\ \vdots && \vdots && \vdots && \vdots\\ V_{j1} & \cdots & V_{ji} & \cdots & V_{jj} & \cdots & V_{jm}\\ \vdots && \vdots && \vdots && \vdots\\ V_{m1} & \cdots & V_{mi} & \cdots & V_{mj} & \cdots & V_{mm} \end{array} \right]\\ &= \left[ \begin{array}{ccccccc} V_{11} & \cdots & V_{1i} & \cdots & aV_{1i} & \cdots & V_{1m}\\ \vdots && \vdots && \vdots && \vdots\\ V_{i1} & \cdots & V_{ii} & \cdots & aV_{ii} & \cdots & V_{im}\\ \vdots && \vdots && \vdots && \vdots\\ aV_{i1} & \cdots & aV_{ii} & \cdots & a^2V_{ii} & \cdots & aV_{im}\\ \vdots && \vdots && \vdots && \vdots\\ V_{m1} & \cdots & V_{mi} & \cdots & aV_{mi} & \cdots & V_{mm} \end{array} \right] \end{align*}

係数行列の行(列)が一次従属となっていて、その行列式がゼロとなることから、連立方程式は解を持たない。変数間の関係が線形でなくても相関がかなり高い場合は行列式がゼロに近づき、解が不安定になる。

k平均法

概要

k平均法(k-means clustering)はクラスタリングの手法の1つで、与えられたデータ群の特徴と初期値に基づいて、データを並列(非階層)のクラスターに分類する。

ここではk平均法の簡単な例を実装したKMeansClusteringクラスによって、その挙動を確認する。

テストケース

基本形

2つのクラスターがある程度明確なケースで試してみる。一定の円内にランダムに点を発生させ、そのグループを2つ近づけた例。

以下のように、重なった部分は仕方がないが、かなり元のグループに近い分類となっている。

初期値を変えた場合

代表点の初期値を変えて実行してみる。

上記とはかなり離れた初期値を設定しても、解は同じになる。

収束解も上記と全く同じ値になる。

クラスターが不明確な場合

先の結果だけを見ると、かなり初期値がずれてもクラス分類は安定なように見える。

そこで次に、元々の分布に明確なクラス分けが見えない場合に3つのクラスターに分ける例を考える。

初期値1

初期値2

上記に対して初期値を変更。

データは同じだが、クラスター分けは違ってきている。

極端な例

次に、元の分布でクラスターが見いだせないような極端な場合を考える。

初期値1

代表点の初期値は縦に並んでおり、クラスターも縦方向に分割されている。

初期値2

全く同じデータで代表点の初期値を横に並べた場合、クラスター分けは大きく異なっている。

3クラスター

最後に、元のデータでクラスターがかなり明確な場合を試してみる。

初期値1

初期値が隅の方から始まっていても、3つのクラスターによく分かれている。

初期値2

初期値の場所や並びがかなり異なっていても、クラスター分けは安定している。

まとめ

k平均法は初期値によって解が変動するとされているが、明らかにクラスターが明確な場合には解は安定している。

ただしそのようなケースは、特徴量の数が少なく分布が一目瞭然の場合に相当するので、特徴量が多く一目ではそのクラスターがわかりにくいような場合には、やはり初期値の取り方に大きく影響されるものと考えられる。

 

Python3 – KMeansClustering

概要

k平均法(k-means clustering)はクラスタリングの手法の1つで、与えられたデータ群の特徴と初期値に基づいて、データを並列(非階層)のクラスターに分類する。

アルゴリズムはシンプルで、以下の手順。

  1. クラスターの数だけクラスターの代表点の初期値を設定する
  2. 代表点の位置が収束するまで以下を繰り返す
    1. データの各点から最も近い代表点を選ぶ
    2. 同じ代表点の点群から重心を算出し、新しい代表点の位置とする

このクラスは、特徴量が2つのデータ群と代表点の初期値を与えて、k平均法でクラスタリングを行うテストクラス。

2つの特徴量x_datay_dataを与えてオブジェクトを生成し、代表点の初期値x_meansy_meansを与えてメソッドを実行して結果を得る。

全コード

KMeansClusteringクラスの全コードは以下の通り。

利用方法

クラスタリングを行うインスタンスの生成

初期データを与え、KMeansClusteringクラスのインスタンスを生成する。

コンストラクターに与える引数は以下の通り。

x_data, y_data
クラスタリングを行うデータの特徴量x、yの配列(1次元のndarray)。

クラスタリングの実行

生成したインスタンスに対して、クラスタリングを行うメソッドを実行して結果を得る。

メソッドに与える引数は以下の通り。

x_means, y_means
k個のクラスターの代表点の初期値(1次元のndarray)。

結果は以下のタプルで与えられる。

x_means, y_means
クラスターの代表点のx、yの配列。収束までの各計算段階の値を記録しており、2次元のndarrayの各行が各計算ステップに相当。
groups
各データが属するクラスター(代表点)番号のndarray

実行例

以下のコードでKMeansClusteringクラスをテスト。内容は以下の通り。

  1. 2つの円状に散らばるランダムな点群を発生させ、1つのデータとしてまとめる
    • random_scatter_dataは指定した中心・半径の円内に指定した数のランダムな点を発生させるモジュールで、別途作成(最後の方にコードを掲載)
  2. クラスターの代表点の数と位置、散布図を描画するパラメーターを設定する
    • プロットする図の数は2行3列で固定し、初期状態を除いた5つの図を表示させる
    • 予め実行させてコンソールで収束回数を確認し、33行目で表示させる計算ステップを指定している
  3. 初期状態の散布図をプロット
  4. クラスタリングを行うKMeansClustringオブジェクトを生成し、結果を得るためのメソッドを実行(53-54行目)
  5. 結果をプロット

このコードの実行結果はコンソールで以下のように表示される。

この結果から1、2、3、5、7回目の計算結果を図示するよう上のコードでセットした表示結果は以下の通り。

グループが比較的明確なので、早い段階で代表点の位置が定まっている。

クラス説明

__init__()~インスタンス生成

クラスタリングを行うデータの特徴量x_datay_datandarrayで与えてインスタンスを生成。

プライベート・メンバーは以下の通り。

_x, _y
クラスタリングを行うデータの2つの特徴量の配列。計算過程で変更されない。
_x_means, _y_means
代表点の計算結果を保存していく配列。分析実行時に初期値が与えられるため、初期値はNone。
_num_data
データの個数。
_num_means
代表点(クラスターの個数)。代表点が分析実行時に与えられるため、初期値は0。
_groups
各データの属するクラスターを保存していく配列。

get_result()~分析の実行

k個の代表点のx、yを引数として渡し、結果を得る。

引数

_x_means, _y_means
k個の代表点の初期値x、yを、それぞれ1次元のndarrayで与える。引数で与えた配列は変更されない。

戻り値

x_means, y_means
代表点の計算結果が保存された配列。各行は計算ステップに相当。
groups
各計算ステップにおける、各点のクラスターが保存された配列。各行は計算ステップに相当。

処理内容

  1. 代表点の初期値をプライベート・メンバーにコピーし、代表点の個数をセット
  2. 全ての代表点の位置が収束するまで、以下を繰り返す
    1. 各データについて、最も近い代表点をセット
    2. 共通の代表点を持つデータから、新しい代表点の位置を計算
    3. 代表点の前回最後の計算値と今回の計算値が収束したならループ終了、でなければ計算結果を追加してループ継続
  3. 計算結果を戻り値として終了

プライベートメソッド

_distance()~2点間の距離

2つの点の距離を与える。ここではユークリッド距離の2乗。

_point_converged()~収束判定

2つの点の座標から、点の位置が収束したかどうかを判定。

本来、各座標値はfloatなので'=='による判定は危険だが、ここでは収束の速さと確実性を信じて簡易に設定。

_all_points_converged()~全ての点の収束判定

配列で与えた2組の点がすべて収束条件を満たしているか判定。

_set_nearest_mean_point()~各点に最も近い代表点

処理内容

  1. 各データについて、それぞれから最も近い距離にある代表点を探し、その番号を1次元の配列groupsに記録
  2. _groups配列に、今回の分類結果を行として追加

_revise_mean_points()~代表点の更新

同じ代表点に属するデータからそれらの重心を計算し、新しい代表点として返す。

random_scatter_data.py

今回整理のためにつくったモジュールで、内容は以下の通り。

 

Python3 – 2次元配列への行・列の追加

概要

2次元配列をデータテーブルのように使っていて、行や列を追加する場合の方法を整理。

リストの場合とndarrayの場合それぞれについて、行の追加、列の追加のためのメソッドや関数と、その使い方の注意を記しておく。

結論として、ndarrayを使う場合はnumpy.vstack()関数、numpy.hstack()関数を用いるのが、配列の2次元化や追加方向のaxis指定がなく紛れがない。

リストの場合

リストの場合はappend()メソッド、insert()メソッドで行や列を追加する。ただし、より現実的な方法をこちらに整理した。

注意点

  • リストのappend()メソッド、insert()メソッドは元のリストを書き換える
  • 各メソッドは戻り値を持たない(None)
  • 初期データが1行/1列の場合に2次元配列であることを明示する必要がある

初期リストが空の場合は単に1次元の空のリストを準備すればいいので、初期リストがある場合でも、空のリストを準備してからそこに追加するように決めておけばミスは減りそう。

行の追加

append()メソッド

append()メソッドは素直に引数のリストをもとのリストに追加する。ただし、下のコードの1行目のように元のリストが2次元配列であることを明示しなければならない。

以下は失敗。元のリストを単なる要素リストで定義してしまうと、追加するリストが要素の一つとして扱われてしまう。

空のリストへの追加。

この場合は1次元の空のリストを用意すれば、追加されるリストが要素として順次追加されていく。

insert()メソッド

insert()メソッドも素直にリストを追加してくれるが、追加位置を指定するのに一手間かかる。行の最後に追加するときは、追加位置をlen(元のリスト)で指定する

その他の仕様はappend()メソッドと同じ。

空のリストへの追加もappend()メソッドと同様。

列の追加

列の追加の場合、各要素が縦に並んだ列ベクトルとしての定義になる。

append()メソッド

行の追加と同様。最初の1列から定義する場合、明示的に列ベクトルの2次元配列であることを明示する必要がある。

空のリストへの追加の場合は、行の追加と同様シンプル。

insert()メソッド

insert()メソッドの場合も行の追加と同様。

空のリストへの追加も行の追加と同様。

ndarrayの場合

ndarrayの場合は、numpyのモジュール関数append()insert()のほか、hstack()関数、vstack()関数も使える。

注意点

  • リストのメソッドと異なり、numpyの各モジュール関数は元の配列を変更せず、変更後の新たな配列を戻り値として返す
  • append()関数、insert()関数では、行の追加/列の追加に応じて引数axisを指定する
  • append()関数は、行を追加する場合は追加する配列も2次元で明示する必要があるが、列を追加する場合にはそのまま指定する
  • 空の配列はempty()関数で準備し、型を指定しておく
  • hstack()関数、vstack()関数は、元の配列と追加する配列をタプルで指定する

行の追加

ndarrayで行を追加する場合、numpyモジュールのappend()関数、insert()関数でaxis=0を指定するか、vstack()関数を利用する。

numpy.append()関数

初期配列が2次元であることを明示しなければならない点はリストのappend()メソッドと同じだが、追加配列でもこれが必要になる。

numpy.append(元の配列, 追加する配列, axis=0)

  • 追加する配列は、元の配列と次元・列数があっていなければならない
    →行ベクトルでもndarrayの2次元配列としなければならない
    →下記コード例では4行目のnp.array([b])
    →こうしないと”次元が合わない”としてエラーになる
  • 追加する配列を行として追加する場合は、axis=0の指定が必須(これを指定しないと、単に元の要素に続いて追加配列の要素が追加されるだけ)

空の配列への追加の場合、numpy.empty(0, n)で空の配列を準備する。

numpy.empty((行数, 列数), dtype=型)

  • 確保する配列の各次元のサイズはタプルで指定
  • 行が空なので行数は0で指定
  • dtype引数で型を明示しないとデフォルトのfloat型になる

numpy.insert()関数

リストのinsert()メソッドと同様だが、いくつか注意が必要。

numpy.insert(元の配列, 追加する行位置, 追加する配列, axis=0)

  • 追加する位置は最後の行の次の行番号で、これは元の配列のshapeプロパティーで得られる(shape[0]は配列各次元のサイズで、2次元配列の場合shape[0]は行数、shape[1]は列数を表す)
  • 追加する配列はそのまま指定する(2次元で定義しない)
    →追加する配列は2次元化しても同じ結果になる
  • axis=0の指定が必要

空の配列への追加する場合は、元の配列をnumpy.empty(0, n)で生成。

numpy.vstack()関数

numpy.vstack()関数は引数に2以上の配列を指定し、それらを縦に連結していく。特に2次元配列化や軸の指定の必要はない。

空の配列への追加の場合は、numpy.empty(0, n)で生成した空の配列を含めて、順次タプルの中に追加したい配列を指定するだけでよい。

列の追加

ndarrayを列として追加していく場合、追加する配列も列形式である必要がある。列方向の追加をaxis引数で指定する必要がある。

  • 通常の行形式の1次元配列を列として追加する場合、reshape(列数, 1)で列ベクトル化する
  • 追加するベクトルを[]で囲って2次元化する必要はない
  • axisi=1で列方向の追加として指定

numpy.append()関数

行の追加の時と違って、元の配列も追加する配列も2次元化しない。

空の配列への追加は、列数をゼロとしてnumpy.empty(n, 0)で指定する。

numpy.insert()関数

numpy.insert()関数も行の追加と同じだが、以下の点に注意。

  • 列の追加位置(最後の列の次)は元の変数のshapeプロパティーの2つ目shape[1]で指定する
  • 追加する配列は2次元化せずそのまま指定
  • 列方向の追加なのでaxis=1を指定する

空の配列への追加はこれまでと同様。

numpy.hstack()関数

numpy.hstack()関数はvstack()と同様、引数に2以上の配列を指定し、それらを横に連結していく。1次元配列はreshape(-1, 1)などで列ベクトル化する必要がある。

空の配列への追加の場合は、numpy.empty(n, 0)で生成した空の配列から始めて、順次タプルの中に追加したい配列を指定するだけでよい。