ndarray – 配列の生成

リテラル

要素をリテラルで指定する。

2次元配列は行列形式できれいに表示されるが、3次元以上は1行表示になる。

リストでもタプルでも結果は同じ。

自動生成

配列形状を指定して全要素を特定の値で埋める

全要素が0、1の配列

numpy.zeros(shape, [dtype=None], [order='C'])
numpy.ones(shape, [dtype=None], [order='C'])

引数はどちらも同じで、配列形状、データタイプ、行列優先の指定。

多次元配列を生成する場合、配列のサイズをタプルとして引数に渡す。

全要素の数値を指定

numpy.full(shape, fill_value, [dtype=None], [order='C'])]

他の配列と同じ形状で特定の値を埋める

全要素が0、1の配列

numpy.zeros_like(a, [dtype=None], [order='C'])
numpy.ones_like(a, [dtype=None], [order='C'])

全要素の値を指定

numpy.full_like(a, fill_value, [dtype=None], [order='C'])]

 初期化せず配列枠だけ確保する

zeros()ones()と同じで、配列の形だけを確保するが各要素は初期化しない。

この場合も多次元配列ではサイズのタプルを引数で渡す。

配列形状を(1, 1)で指定すると、1要素の2次元配列が確保される。

配列形状を数値0で指定すると、空の配列が確保される。

配列形状で0の次元が含まれると空の配列になる。

内包表記

リストの内包表記が使える。

range()関数

普通にrange()関数も使える。

numpy.arange()~間隔を指定

numpy.arrange([start=0], stop, [step=1], [dtype=None])

numpy.arange()関数はrange()関数よりも高速な生成が可能。

range()関数と同じようにパラメータを設定。dtypeはデータ型を指定。

range()関数は整数しか引数に指定できないが、numpy.arange()関数はfloatの指定が可能。

numpy.linspace()~分割数を指定

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

初期値と終了値を指定して、その間を等分割したリストを返す。分割数を指定しないとデフォルトで50分割。基本の型はfloat。

この分割数は区間数ではなく、生成される数列の個数である点に注意。以下の例では0~1の間で10個の数値を生成させるため、区間数は9となる(植木算の考え方)。

デフォルトでは終端値を含めるが、含めないようにもできる。

生成された数列と分割幅のタプルを返すことができる。

特定の行列の生成

numpy.identity()~単位行列

numpy.identity(n)

正方行列の1辺の要素数を指定して、単位行列を生成する。

numpy.tri()~下三角行列の生成

numpy.tri(n)

左下の要素が1の下三角行列を生成する。

numpy.tril()numpy.triu()~行列の三角化

numpy.tril(matrix)
nunmpy.triu(matrix)

与えられた行列を下三角/上三角化。

numpy – 配列操作 – 抽出

概要

配列の要素や行・列の抽出などに関する操作。

準備として、以下の1次元、2次元配列を考える。

要素の参照

1次元配列の要素の参照は、リストと同じ。

2次元配列の要素は、[行, 列]で指定。行・列の値の考え方は1次元配列の要素と同じ。

行・列の参照

単一の行・列の参照

2次元配列の行の参照は、行番号を指定。

2次元配列の列の参照はややこしくて、[:,列番号]で指定。1つ目の:は行番号のプレースホルダーみたいなものか。

ただし、列を取り出した結果でも、1次元の配列になる。

直接列ベクトルで取り出したい場合は、[:,列番号:列番号+1]で可能。

なお、1次元の配列にndarray.Tを作用させても、1次元配列のままで列ベクトルにはならない。

範囲を指定した行・列の参照

行の範囲を指定して、複数行の行列を返す。

列の範囲を指定する場合。

複数の行・列を指定した参照

連続しない複数の行を取り出した行列をつくるには、[行番号, 行番号, …]とする。

複数列を取り出す場合。

参照であることの注意

以上の操作で取り出された配列は、元の配列への参照を保っているため、その要素を変更すると元の配列の要素も変更される。

元の配列に影響させたくない場合は、copy.copy()、copy.deepcopy()、np.copy()でオブジェクトをコピーする必要がある。

対角要素の取り出し

np.diag()で、2次元配列の対角要素を取り出した1次元配列が得られる。

ただし、その結果は書き込みできない。

条件を指定した取り出し

配列に条件式を適用して、各要素が要件に合致していればTrue、合致していなければFalseを要素とする配列を返す。

上で得られた配列を要素とすることで、条件に合致した要素のみを取り出した1次元配列を得る。

 

Python3 – numpyのインストール

numpyのインストールは、コマンドラインからpipで。numpyのインストール後にpipのアップグレードを推奨されたので、これも実行。

 

回帰分析~単回帰

概要

(X,\: Y) = (x_i,\: y_i) \; (i=1 \ldots n)のデータからなる複数の標本に対して、線形式で最も説明性の高いものを求める。

そのために、各データのx_iに線形式を適用して得られた値\hat{y_i} = a x_i + by_iの差(残差)の平方和が最小となるような係数a,\: bを求める(最小二乗法)。

定式化

(1)    \begin{equation*} \sum_{i=1}^n (\hat{y_i} - y_i)^2 \; \rightarrow \; \min \quad {\rm where} \; \hat{y_i} = a x_i + b \end{equation*}

ここで残差を最小化するa,\: bを求めるために、それぞれで偏微分する。

(2)    \begin{equation*} \left\{ \begin{array}{l} \displaystyle \frac{\partial}{\partial a} \sum_{i=1}^n (a x_i + b - y_i)^2 = 0 \\ \displaystyle \frac{\partial}{\partial b} \sum_{i=1}^n (a x_i + b - y_i)^2 = 0 \end{array} \right. \quad \Rightarrow \quad \left\{ \begin{array}{l} \displaystyle \sum_{i=1}^n 2(a x_i + b - y_i)x_i = 0 \\ \displaystyle \sum_{i=1}^n 2(a x_i + b - y_i) = 0 \end{array} \right. \end{equation*}

展開して

(3)    \begin{equation*} \left\{ \begin{array}{l} \displaystyle a \sum_{i=1}^n x_i^2 + b \sum_{i=1}^n x_i - \sum_{i=1}^n x_i y_i = 0 \\ \displaystyle a \sum_{i=1}^n x_i + bn - \sum_{i=1}^n y_i = 0 \end{array} \right. \end{equation*}

各和の記号を新たに定義し、行列表示で表すと、

(4)    \begin{equation*} \left[ \begin{array}{cc} S_{xx} & S_x \\ S_x & n \end{array} \right] \left[ \begin{array}{c} a \\ b \end{array} \right] = \left[ \begin{array}{c} S_{xy} \\ S_y \end{array} \right] \end{equation*}

回帰式の係数

式(4)を解くと以下を得る。

(5)    \begin{equation*} \left[ \begin{array}{c} a \\ b \end{array} \right] = \left[ \begin{array}{c} \dfrac{n S_{xy} - S_x S_y}{n S_{xx} - {S_x}^2} \\ \dfrac{S_{xx} S_y - S_x S_{xy}}{n S_{xx} - {S_x}^2} \end{array} \right] \end{equation*}

あるいは式(5)については以下のようにも表せる。ただし以下の式で、\sigma_{XY} = {\rm Cov}(X, Y){\sigma_X}^2 = {\rm Var}(X)

(6)    \begin{equation*} a = \frac{\displaystyle \sum_{i=1}^n (x_i - \overline{X})(y_i - \overline{Y})}{\displaystyle \sum_{i=1}^n (x_i - \overline{X})^2} =\frac{\sigma_{XY}}{{\sigma _X}^2} \end{equation*}

(7)    \begin{equation*} b = \overline{Y} - a \overline{X} \end{equation*}

決定係数

全変動・回帰変動・残差変動

x_iの回帰式による推定値を\hat{y}_i = a x_i + bで表す。

ここで、y_i,\: \hat{y},\: \overline{Y}によって、以下の変動が定義される。

y_i - \overline{Y}
全変動。平均からのデータのばらつき。
\hat{y}_i - \overline{Y}
回帰変動。平均からのばらつきのうち、回帰式によって説明される部分。
y_i - \hat{y}_i
残差変動。平均からのばらつきのうち、回帰式によって説明されない部分。

当然、[全変動]=[回帰変動]+[残差変動]となることはすぐに確認できる。

また、これら3つの変動の2乗和をTSS(Total Sum of Squares)、ESS(Explained Sum of Squares、RSS(Residual Sum of Squares)として、以下のように定義する。

(8)    \begin{eqnarray*} TSS &=& \sum_{i=1}^n (y_i - \overline{Y})^2 \\ ESS &=& \sum_{i=1}^n (\hat{y}_i - \overline{Y})^2 \\ RSS &=& \sum_{i=1}^n (y_i - \hat{y}_i)^2 \end{eqnarray*}

このとき、それぞれについて以下のように表せる。

(9)    \begin{eqnarray*} TSS &=& n {\sigma _Y}^2 \\ ESS &=& n \frac{{\sigma_{XY}}^2}{{\sigma_x}^2} \\ RSS &=& n {\sigma_Y}^2 - n \frac{{\sigma_{XY}}^2}{{\sigma_X}^2} \end{eqnarray*}

これらから、全変動・回帰変動・残差変動の2乗和についても、以下の関係が成り立つことがわかる。

(10)    \begin{equation*} TSS = ESS + RSS \end{equation*}

補足~ESS

式(9) のESSの確認。以下のように、線形変換された分散から導ける。

(11)    \begin{eqnarray*} ESS &=& \sum_{i=1}^n \left( a x_i +b - (a \overline{X} + b) \right)^2 \\ &=& n {\rm Var}(a X + b) \\ &=& na^2{\rm Var}(X) \\ &=& n a^2 {\sigma_x}^2 \\ &=& n \left( \frac{\sigma_{XY}}{{\sigma_x}^2} \right)^2 {\sigma_x}^2 \\ &=& n \frac{{\sigma_{XY}}^2}{{\sigma_x}^2} \end{eqnarray*}

あるいは、以下のように地道に計算しても確認できる。

(12)    \begin{eqnarray*} \sum_{i=1}^n (\hat{y}_i - \overline{Y}) &=& \sum_{i=1}^n ({\hat{y}_i}^2 - 2 \hat{y}_i \overlne{Y} + \overline{Y}^2) \\ &=& \sum_{i=1}^n (\hat{y}_i^2 - 2(a x_i + b) \overline{Y} +\overline{Y}^2) \\ &=& \sum_{i=1}^n (\hat{y}_i^2 - 2a x_i \overline{Y} - 2b\overline{Y} + \overline{Y}^2) \\ &=& \sum_{i=1}^n \hat{y}_i^2 - 2an \overline{X} \; \overline{Y} -2nb \overline{Y} + n \overline{Y}\end{eqnarray*} \\

ここで

(13)    \begin{eqnarray*} \sum_{i=1}^n \hat{y}_i^2 &=& \sum_{i=1}^n (ax_i + b)^2 \\ &=&\sum_{i=1}^n (a^2 x_i^2 + 2ab x_i + b^2) \\ &=& a^2 \sum_{i=1}^n x_i^2 + 2abn \overline{X} + nb^2 \end{eqnarray*}

これを代入して、

(14)    \begin{eqnarray*} ESS &=& a^2 \sum_{i=1}^n x_i^2 + 2nab \overline{X} + nb^2 - 2na \overline{X} \; \overline{Y} - 2nb \overline{Y} + n \onerline{Y} \end{eqnarray*}

さらに\sum_{i=1}^n x_i^2 = \n \sigma_X^2 + n \overline{X}^2を考慮して、

(15)    \begin{eqnarray*} ESS &=& n a^2 {\sigma_X}^2 + n a^2 \overline{X}^2 + 2nab \overline{X} + nb^2 -2na\overline{X}\;\overline{Y} - 2nb\overline{Y} + n\overline{Y}^2 \\ &=& n a^2 {\sigma_X}^2 + n(a \overline{X} - \overline{Y})^2 + 2nab\overline{X} + nb^2 - 2nb\overline{Y} \\ &=& n a^2 {\sigma_X}^2 + nb^2 + 2nab\overline{X} + nb^2 - 2nb\overline{Y} \\ &=& n a^2 {\sigma_X}^2 + 2nb(b + a\overline{X} - \overline{Y}) \\ &=& n a^2 {\sigma_X}^2 \\ &=& n \left( \frac{\sigma_{XY}}{{\sigma_X}^2} \right)^2 {\sigma_X}^2 \\ &=& n \frac{{\sigma_{XY}}^2}{{\sigma_X}^2} \end{eqnarray*}

補足~RSS

式(9) のRSSの確認。

(16)    \begin{eqnarray*} RSS &=& \sum_{i=1}^n (y_i - \hat{y}_i)^2 = \sum_{i=1}^n (y_i - a x_i - b)^2 \\ &=& \sum_{i=1}^n (y_i - a x_i - \overline{Y} + a \overline{X})^2 \\ &=& \sum_{i=1}^n \left(y _i - \overline{Y} - a (x_i - \overline{X}) \right) ^2 \\ &=& \sum_{i=1}^n \left(y _i - \overline{Y} - \frac{\sigma_{XY}}{{\sigma_X}^2} (x_i - \overline{X}) \right) ^2 \\ &=& n {\sigma_Y}^2 - 2n \frac{\sigma_{XY}}{{\sigma_X}^2} \sigma_{XY} + n \frac{{\sigma_{XY}}^2}{{\sigma_X}^4} {\sigma_X}^2 \\ &=& n {\sigma_Y}^2 - 2n \frac{{\sigma_{XY}}^2}{{\sigma_X}^2} + n \frac{{\sigma_{XY}}^2}{{\sigma_X}^2} \\ &=& n {\sigma_Y}^2 - n \frac{{\sigma_{XY}}^2}{{\sigma_X}^2} \end{eqnarray*}

決定係数

決定係数は、全変動のうち回帰変動でどれだけ説明できるか(残差変動が少ないか)を表したもので、通常R^2で表される。

(17)    \begin{align*} R^2 = \frac{ESS}{TSS} = \frac{TSS - RSS}{TSS} = 1 - \frac{RSS}{TSS} \end{align*}

この値は、残差変動が小さいほど1に近づき、RSS = TSSすなわち全変動が回帰変動で全く説明できないときに0となる。

 

クイックソート

概要

クイックソートは、特定の値(ピボット)を基準にして、データの先頭と最後尾から1つずつ中央に近づけていきながら、ピボットより小さなデータが前に、大きなデータが後ろに配置されるように交換し、その処理を再帰的に行っていく。

考え方

以下2段階に分けて、与えられたデータ列をピボット以上・未満に振り分ける操作と、それらを再帰的に実行していく操作を示す。

ピボットの取り方にはいろいろあるようだが、ここでは与えられたデータの先頭と最後尾の値の平均値とする。

部分列に対する操作

以下の図で、4,6,2,…5のデータの部分列が与えられたとする。その際の操作は以下の通り。

  • ⓪ 部分列の先頭と最後尾のデータの平均を計算し、それをピボットとして設定(4.5)
  • ① 左からピボット以上のデータを探索(6≥4.5)、右からピボット未満のデータを探索(1<4.5)
  • ② 探索した左右のデータを交換
  • ③ さらに左からピボット以上のデータを探索(7≥4.5)、右からピボット未満のデータを探索(0<4.5)
  • ④ 探索した左右のデータを交換
  • ⑤ さらに探索を進めた結果、左右のポインターが交差
    • このとき、境界より左にピボット未満、境界より右にピボット以上のデータが格納されている
    • 3を境界とし、境界を含む左側とそれより右側の部分列に分けて、再帰的に探索を行う

再帰処理

部分列の操作を踏まえたうえで、それらの再帰的な処理の様子を下図に示す。図中、それぞれの線種や背景色の意味は以下の通り

  • 二重枠囲みはピボットの値
  • 赤はピボット以上のデータ
  • 青はピボット未満のデータ
  • 黄色は境界値
  • 緑色は位置が確定したデータ

各段階の処理は以下の通り。

  • ① 元の列からピボット以上/未満のデータを振り分け
  • ② 左側の部分列に対する再帰1段目、ピボットによる振り分け
  • ③ 左側の部分列に対する再帰2段目、ピボットによる振り分け
  • ④ 左側の部分列に対する再帰3段目、ピボットによる振り分け
  • ⑤⑥ 左側・右側の再帰終了、0と1の位置確定
  • ⑦ 右側の部分列に対する再帰3段目
  • ⑧⑨ 左側・右側の再帰終了、2と3の位置確定
  • ⑩ 右側の再帰終了、4の位置確定(ここで再帰1段目の左側終了)
  • ⑪ 右側の部分列に対する再帰1段目、ピボットによる振り分け
  • ⑫ 左側の再帰終了、5の位置確定
  • ⑬ 右側の再帰2段目、ピボットによる振り分け
  • ⑭⑮ 左側・右側の再帰終了、6と7の位置確定

コード

以下はPythonによる実装。全体のデータ列に対して位置を指定して並べ替えを実行する再帰関数quick_sort_core()を定義し、全体列に対してこれを呼び出す入口の関数quick_sort()を定義。

例示したデータに対して実行。

要素がすべて同じで、要素数が偶数・奇数の場合も正常に動作。

同じ要素が複数含まれる場合も正常に動作。

動作状況の確認

並べ替えの動作中の状況を以下のコードで確認。各所にprint()関数をちりばめてみる。

動作結果は以下の通りで、再帰的に実行されているのがわかる。

 

バブルソート

概要

バブルソートは、すべてのデータを一つずつチェックしながら、(昇順の場合)最も大きいデータを後ろに押し出していく。

たとえば以下のような初期データ列があるとする。

3, 4, 1, 2, 0

最初のループでは、全データの中で最大のものが一番最後に来るようにする。

3, 4, 1, 2, 0
3, 4, 1, 2, 0 → 3, 1, 4, 2, 0
3, 1, 4, 2, 0 → 3, 1, 2, 4, 0
3, 1, 2, 4, 0 → 3, 1, 2, 0, 4

3, 1, 2, 0, 4 → 1, 3, 2, 0, 4
1, 3, 2, 0, 4 → 1, 2, 3, 0, 4
1, 2, 3, 0, 4 → 1, 2, 0, 3, 4

1, 2, 0, 3, 4
1, 2, 0, 3, 4 → 1, 0, 2, 3, 4

1, 0, 2, 3, 4 → 0, 1, 2, 3, 4

ループ回数は(n-1)+(n-2)+\cdots+1=\frac{n(n-1)}{2}

コード

Pythonによるバブルソートのコード例。

安定ソート

バブルソートは安定ソートである。すなわち、同じソートキーのデータについて、ソート後も元の順序が保持される。