NumPy の配列操作 (3)

[前回](http://qiita.com/ynakayama/items/3250452949102840e624)の続きで性懲りもなく [NumPy](http://www.numpy.org/) の話が続きます。

# そもそもなぜ NumPy を使うのか

データ分析と言いますがそもそもデータとは何でしょうか。データというのは数の集まりです。我々の周りを見回してみると、机の上にある本も、食卓の上の皿も、八百屋さんに並んでいるりんごも、その総数はすべて有限です。たとえば本は 14 冊あり、皿は 22 枚あり、りんごは全部で 56 個あるといった具合です。

このように有限で個数の少ないものはその全体の集まりも、必要ならいつでもひとつにまとめてみることができます。我々が生きて経験する世界の中にあるものの集まりはこうして有限個のものから成り立っています。

ものの集まりというのは集合です。考えている対象全体の個数が小さい時には、ひとつひとつの物の認識とその集合全体が作る認識とはそれほど異なっていません。しかし、ひとつの概念に包括される対象の個数が多くなってくると、全体をひとつとして認識する仕方は異なってきます。たとえば自然数で言うとひとつひとつの自然数を認識することと、自然数全体をひとつの記号として考える認識は違いがあります。この全体をひとつのものとして考える認識の仕方が集合論の基盤にあります。

さてここで集合論と線形代数について考えてみましょう。線形代数におけるベクトル空間とはベクトルと呼ばれる元からなる集まりの成す数学的構造を指します。大まかに言えば線形代数はベクトルや行列の数学なのですが、これらベクトルや行列、それらからなる多次元配列を扱うためには専用のライブラリのサポートが肝要です。したがって NumPy を使いこなすということは、現実の分析対象のデータについての構造や操作を使いこなすということでもあるのです。

## ndarray オブジェクトの保存

np.save および np.load 関数は ndarray オブジェクトをファイルとして入出力することができます。さらに np.savetxt と np.loadtxt ではファイルがテキスト形式になります。

また pandas を利用できる環境ならより高次の read_csv, read_table, write_csv, write_table 関数が使えます。

```py3
arr = np.arange(10)
#=> array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

np.save('hoge', arr) # hoge で保存する、拡張子は .npy
arr2 = np.load('hoge.npy') # 保存したオブジェクトを読み込む
arr2
#=> array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) # 復元成功

np.savetxt('fuga.txt', arr) # テキストで保存する
arr3 = np.loadtxt('fuga.txt') # テキストから読み込む
arr3
#=> array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])
```

## 行列計算

前述したように NumPy などの配列計算において線形代数の計算というのは重要な位置付けです。

reshape 関数は多次元配列を生成するときに非常に便利な関数です。

```py3
x = np.array([1,2,3,4,5,6]).reshape([2,3])
#=> array([[1, 2, 3],
#          [4, 5, 6]])

y = np.array([6,23,-1,7,8,9]).reshape([3,2])
#=> array([[ 6, 23],
#          [-1,  7],
#          [ 8,  9]])

x.dot(y) # 内積を求める
#=> array([[ 28,  64],
#          [ 67, 181]])
```

```py3
from numpy.linalg import inv, qr, pinv, eig

x = np.random.randn(5,5)
x.T # 転置する
#=> array([[ 0.1797343 , -1.48685211,  1.89995885, -1.48818535,  0.22707072],
#          [ 0.16362348,  0.73820851,  0.6830228 , -0.28744869,  1.60110706],
#          [-0.25212006, -0.75832623,  1.08510935,  0.36069392, -0.25172285],
#          [-1.23742215, -0.27616976,  1.09778477, -0.79290683,  1.88819678],
#          [ 1.25424329, -0.44571606, -0.37970879,  0.25329534, -0.0571783 ]])

mat = x.T.dot(x)
mat # 転置して内積を求める
#=> array([[ 8.119134  ,  1.02085855,  2.54992915,  3.88270879, -0.22322061],
#          [ 1.02085855,  3.68441518, -0.36661744,  3.59459509, -0.54751549],
#          [ 2.54992915, -0.36661744,  2.00954999,  0.95132328, -0.28449209],
#          [ 3.88270879,  3.59459509,  0.95132328,  7.00660306, -2.15457716],
#          [-0.22322061, -0.54751549, -0.28449209, -2.15457716,  1.98339569]])

inv(mat) # 正方行列に対して逆行列を返す
#=> array([[ 0.34294894,  0.13024165, -0.30841121, -0.30883099, -0.30517266],
#          [ 0.13024165,  0.87379103,  0.25904943, -0.69902729, -0.46633355],
#          [-0.30841121,  0.25904943,  0.98083558, -0.06094767,  0.11128055],
#          [-0.30883099, -0.69902729, -0.06094767,  0.91304207,  0.75537864],
#          [-0.30517266, -0.46633355,  0.11128055,  0.75537864,  1.17764414]])

q, r = qr(mat) # QR 分解をする
q
#=> array([[-0.86261627,  0.28238894,  0.35807769,  0.08420784, -0.20208678],
#          [-0.10846098, -0.74407004,  0.01429817,  0.58226198, -0.30880827],
#          [-0.27091687,  0.2393467 , -0.87410947,  0.31594231,  0.0736905 ],
#          [-0.41251786, -0.54595751, -0.16208726, -0.50524429,  0.50021529],
#          [ 0.02371604,  0.10611234,  0.28501978,  0.54661567,  0.7798415 ]])

r
#=> array([[-9.41221986, -2.67672118, -3.10345255, -6.93833754,  1.26485136],
#          [ 0.        , -4.56152677,  0.92426978, -5.40443517,  1.66303293],
#          [ 0.        , -0.        , -1.08401917, -1.13963141,  1.07545497],
#          [ 0.        ,  0.        ,  0.        , -1.997258  ,  1.7452655 ],
#          [-0.        , -0.        , -0.        , -0.        ,  0.66220471]])

pinv(mat) # ムーアペンローズの擬似逆行列を返す
#=> array([[ 0.34294894,  0.13024165, -0.30841121, -0.30883099, -0.30517266],
#          [ 0.13024165,  0.87379103,  0.25904943, -0.69902729, -0.46633355],
#          [-0.30841121,  0.25904943,  0.98083558, -0.06094767,  0.11128055],
#          [-0.30883099, -0.69902729, -0.06094767,  0.91304207,  0.75537864],
#          [-0.30517266, -0.46633355,  0.11128055,  0.75537864,  1.17764414]])

np.trace(mat) # 対角成分の総和を返す
#=> 22.80309791710043

eig(mat) # 正方行列に対して固有値及び固有ベクトルを返す
#=> (array([ 13.3600683 ,   5.95602662,   2.24791381,   0.81881059,   0.4202786 ]),
#    array([[-0.64467006, -0.63086541,  0.20659265,  0.31642236, -0.20881982],
#          [-0.31183983,  0.5014887 ,  0.55973159, -0.32086736, -0.48477798],
#          [-0.19333292, -0.33433927, -0.32170008, -0.86424035, -0.02091191],
#          [-0.65225524,  0.42536152, -0.20774674,  0.04440243,  0.59033922],
#          [ 0.15601897, -0.24042203,  0.70524491, -0.21917587,  0.61028427]]))
```


## まとめ

今回はとくに重要な線形代数関数の頻出する関数について説明しました。科学計算の基礎となる部分ですのでしっかり学んでおきましょう。