numpy – 配列要素の演算

要素に対するスカラー演算

配列の要素に対するスカラー演算は、それぞれの要素に作用。

二次元配列も同じ。

なお、配列が後ろに来ても要素ごとに演算。

要素ごとの演算

同じ形状の配列同士の演算は、それぞれの要素ごとの演算。

要素ごとの比較

配列同士の比較は、要素ごとの比較結果の配列。

関数演算

numpyの関数の引数が配列の場合、要素ごとの演算結果となる。

 

numpy – ファイルの保存と読み込み

バイナリファイル

バイナリファイルとしての保存にはnp.save()、その読み込みにはnp.load()を使う。基本的なオプションは以下の通り。

numpy.dave("ファイル名", 配列)
配列 = numpy.load("ファイル名")

注意点として、ファイルの拡張子は.npy固定。

  • saveのファイル名に拡張子をつけない場合、自動的に拡張子.npyが付加される
  • saveのファイル名に別の拡張子を書いても、その後ろに.npyが付加される
  • loadのファイル名の拡張子は.npyでなければならない(違う場合はFileNotFoundError)

テキストファイル

概要

テキストファイルとしての保存にはnp.savetxt()、その読み込みにはnp.loadtxt()を使う。扱える配列の次元は1次元か2次元のみ。基本的なオプションは以下の通り。

numpy.savetxt(
    fname,         # ファイル名を任意の拡張子まで指定
    X,             # 書き込む配列
    fmt='%.18e',   # 書き込む書式
    delimiter=' ', # 行内の数値の区切り
    newline='n',   # 改行文字
    header='',     # ヘッダー文字列
    footer='',     # フッター文字列
    comments='# ', # コメントの開始文字
    encoding=None  # エンコーディング
)

numpy.loadtxt(
    fname,                 # ファイル名(拡張子まで)
    dtype=<class 'float'>, # データの型
    comments='#',          # コメント開始文字
    delimiter=None,        # 区切り文字(デフォルトはスペース)
    converters=None,
    skiprows=0,            # ファイル先頭から指定した行数だけ読み飛ばす
    usecols=None,          # タプルで指定した列のみ読み込み
    unpack=False,
    ndmin=0,
    encoding='bytes'max_rows=None)

なお、numpy.ndarrayはすべての要素の型が同じでなければならず、リストやタプルのように要素の型を混在させることはできない

数値データ

数値データのみを扱う場合、整数でも自動的に実数型に変換されて読み込まれる。

実行例

このときのファイルの内容は以下のようになっている。

3次元以上の配列を書き込もうとするとエラー。

文字列

文字列データについては、書き込む場合はfmt="%s"、読み込む場合にはdtype="unicode"を指定。

このとき、text.txtファイルの中は以下のようになっている。

CSVファイルの読み込み

以下のようなCSVファイルを配列に読み込む。

numpy.arrayはすべての要素の型が同じである必要があるため、上記のように文字列と数値が混在したファイルを一つの配列に読み込もうとするとエラーになる。

都道府県名と2次元のデータを別々に読み込むことは可能。

その他のメソッド

savez()/load()
複数のファイルを非圧縮で扱う。
savez_compressed()/load()
複数のファイルを圧縮して扱う
ndarray.tofile()
配列のストレージへの書き込み。

 

numpy – 配列の生成

リテラル

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

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

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

リスト内包表記も使える。

全要素が0、1の配列。

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

np.zeros(shape, dtype=None, order='C')

特定の行列の生成

np.identity()~単位行列

np.identity(n)

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

np.tri()~下三角行列

np.tri(n)

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

np.tril()、np.triu()

np.tril(matrix)
np.triu(matrix)

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

reshape()による配列化

ベクトルの行列化

np.reshape(source, shape)でsourceに指定したベクトルをshapeで指定したサイズの行列に変換。shapeはリストやタプルでサイズを指定。

行列のサイズ変更

行列を同じ要素数で異なるサイズに変更可能。

arange()との組み合わせ

よく見かける、arange()で生成したベクトルの行列化。

 

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のデータからなる複数の標本に対して、線形式で最も説明性の高いものを求める。

そのために、各標本の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によるバブルソートのコード例。

安定ソート

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

 

Python3 – random/乱数

概要

randomモジュールには、疑似乱数を発生させる関数や、コレクションからランダムな要素を選んだり、コレクションをシャッフルしてくれる関数が用意されている。

疑似乱数/random()

random()関数は、0≤r<1の範囲の一様乱数を浮動小数点で返す。

一様乱数/uniform()

uniform(a, b)は、a≤r<bの範囲の一様乱数を浮動小数点で返す。

整数乱数/randint()

randint(a, b)はa≤r<bの範囲の乱数を整数で返す。

ランダム選択/choise()

choise(c)はコレクションcからランダムな要素を一つ選んで返す。

引数に文字列を指定すると、その中から任意の位置の文字を一つ返す。

シャッフル/shuffle()

shuffle(c)はコレクションの内容をシャッフルする。イミュータブルな文字列を指定するとTypeErrorになる。

 

Python3 – 参照?

概要

Pythonで関数の受け渡しが参照渡しとされているが、改めて変数とオブジェクトの関係を含めて確認してみた。

オブジェクトの参照に関する基本的な考え方は

  • 論理値や数値も含めて全てオブジェクト
  • 変数にはインスタンスのアドレスが格納され、参照される
  • ミュータブルなオブジェクトでリテラルが同じものは共通の1つのオブジェクト
  • イミュータブルなオブジェクトはリテラルが同じでも異なるオブジェクト

関数の引数の受け渡しは

  • 仮引数は、関数が呼び出されたときに呼び出し元の実引数が指しているアドレスを受け取る
  • 関数内での参照も上記の通りで、ミュータブルなオブジェクトが対象の場合は、関数内で内容を変更すると・・・

変数からオブジェクトへの参照

数値の場合

下記のコードをまず確認する。

ある数値を変数に代入すると、その変数には数値(オブジェクト)のアドレスがセットされる。その変数の内容(アドレス)を別の変数に代入すると、新しい変数も同じアドレスをさすようになる。

次に、同じ数値を指している変数の一つに別の数値を代入すると、その変数は新しい数値オブジェクトのアドレスを指すようになる。

以下のように、同じ値の数値は1つのオブジェクトのアドレスが共有される。

数値計算の場合も、結果が同じ値なら1のオブジェクトが共有される。

変数が絡む演算でも、結果が同じ値なら同じアドレスを指す。

浮動小数点の場合、精度上少しでも異なる値は違うオブジェクトになる。

文字列の場合

数値の場合と同じで、変数は文字列オブジェクトのアドレスを指す。

異なる内容の文字列は、異なるオブジェクトとなる。

同じ内容の文字列リテラルは、異なる位置で用いられても1つのオブジェクトとして共有される。

面白いことに、文字列リテラル同士の演算結果が同じなら、これも同じオブジェクトとして共有される。

同じオブジェクトを指していても、一方に演算を施すと新たなオブジェクトが生成されるため、異なるオブジェクトを指すようになる。

なお、リテラル同士の演算で結果が同じ場合はオブジェクトが共有されたが、変数が絡む場合は、内容が同じであっても異なるオブジェクトとなる。

リストの場合

リストの場合もオブジェクトへの参照が変数に保存される。

リストの要素を変更した場合、そのリストを指している全ての変数に変更結果が反映される。

注意点。リストの場合、リテラルが同じでも異なるオブジェクトが生成される。

オブジェクトが異なるため、片方の変更は他方に反映されない。

関数の引数の参照

数値の場合

関数の仮引数に実数値を渡す場合、関数内で引数の値を変更しても(参照するアドレスが変更されても)呼び出し元の実引数のアドレスは変わらず、その値も変更されない。

Cと同じ仕様で、関数実行時に実引数の値がコピーされて渡されているのではないか。

文字列の場合

文字列の場合も、関数内での変更は呼び出し元に影響を与えない。

リストの場合

リストを引数に渡した場合、関数内での変更が呼び出し元にも影響を与える。リストのようなミュータブルオブジェクトの場合、それに対する変更は元のオブジェクトに対する変更であり、イミュータブルなオブジェクトのように新しいオブジェクトが生成されるわけではないため。