numpyの使い方

数値計算とか機械学習とかにはpythonがいいよって聞いて、pythonはじめたはいいけど、ちょっと難しいけどC++のほうが全然早いじゃんって思っている人。numpyの名前は聞いたことがあるけど使ったことはない人。numpyって使ってみたけど、要はmathパッケージの発展版？って思ってる人。そんな人たちのために、numpyの正しい使い方を教えます！

## numpyのインストール

pythonをanacondaを使ってインストールした人はすでにnumpyは入っていると思います。ほかにもインストールの方法によってはnumpyが入ってる可能性はあります。そこでまずnumpyが入っているかを確認しましょう。
ターミナル(windowsの場合はコマンドプロンプト)に`python`と打ち込んでpythonのコンソールを起動します。pythonのコンソールで

```
> import numpy
```

と打ってみてエラーが出なければインストールされています。
`No Module Named numpy`みたいなエラーが出る場合はインストールされていないのでインストールする必要があります。
ターミナルにおいて(pythonのコンソールではありません)

```
$ pip install numpy
```

でインストールできます。


## 配列の作り方

ここから具体的にnumpyを使ったプログラミングを見ていきますが、numpyは

```
> import numpy as np
```

とインポートされているとします。これはモジュールnumpyをnpという名前でインポートするという意味です。

### 一次元配列の作り方

numpyの基本は配列を作るとこから始まります。中身が`1,2,3`の配列は

```
> arr = np.asarray([1,2,3])
> arr
array([1, 2, 3])
```

とつくれます。
さらに、`dtype`を指定することで配列の型を指定することができます。よく使う型は`np.int32`、`np.float32`、`np.float64`などがあります。これを使って`np.int32`型の配列を作るには

```
> arr = np.asarray([1,2,3], dtype=np.int32)
> arr
array([1, 2, 3], dtype=int32)
```

とします。すでに存在している配列の型を変更するには

```
> i_arr = np.asarray([1,2,3], dtype=np.int32)
> f_arr = i_arr.astype(np.float32)
> f_arr
array([ 1.,  2.,  3.], dtype=float32)
```

とします。この時元の配列`i_arr`は変化しません。

```
> i_arr
array([1, 2, 3], dtype=int32)
```

### 多次元配列の作り方

多次元配列を作るには

```
> arr = np.asarray([[1,2,3], [4,5,6]])
> arr
array([[1, 2, 3],
       [4, 5, 6]])
```

とします。一次元の場合と同様に型の指定、変更が行えます。`shape`要素に配列の形が入っています。

```
> arr.shape
(2, 3)
```

これはtuple型です。ちなみに一次元の配列の形は

```
> arr = np.asarray([1,2,3])
> arr.shape
(3,)
```

になります。これは要素が一つしかないtuple型です。

### 特別の配列の作り方

numpyでは簡単に特別な配列を作ることができます。

```
> # 要素が全部0の配列
> np.zeros((2, 3))
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])
> # 要素がすべて1の配列
> np.ones((2, 3))
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])
> # 要素を[0-1)の範囲でランダムに初期化する
> np.random.rand(2, 3)
array([[ 0.24025569,  0.48947483,  0.61541917],
       [ 0.01197138,  0.6885749 ,  0.48316059]])
> # 要素を正規分布にのっとって生成する
> np.random.randn(2, 3)
array([[ 0.23397941, -1.58230063, -0.46831152],
       [ 1.01000451, -0.21079169,  0.80247674]])
```

ほかにもたくさんの配列を生成する関数が用意されています。こういう配列ほしいなって思ったときはググってみると見つかるかもしれません。

## 配列の計算

### 基本的な計算

numpyの力は配列同士の計算がとても簡単にできることにあります。

```
> a = np.asarray([[1,2,3],[4,5,6]])
```

に対して

```
> 3 * a
array([[ 3,  6,  9],
       [12, 15, 18]])
```

となります。配列をスカラー倍するとそれぞれの要素が定数倍されます。スカラーを足せば

```
> 3 + a
array([[4, 5, 6],
       [7, 8, 9]])
```

とそれぞれの要素に足されます。配列同士の計算は

```
> b = np.asarray([[2,3,4],[5,6,7]])
> a + b
array([[ 3,  5,  7],
       [ 9, 11, 13]])
> a * b
array([[ 2,  6, 12],
       [20, 30, 42]])
```

とおんなじ形の配列同士の計算はおんなじ位置の要素同士が計算されてその形の配列が返ります。
異なる形の配列同士が計算できることもあります。

```
> v = np.asarray([2,1,3])
> a * v
array([[ 2,  2,  9],
       [ 8,  5, 18]])
> a + v
array([[3, 3, 6],
       [6, 6, 9]])
```

二次元配列と一次元配列の計算では、二次元配列の列(column)の数が一次元配列の長さと同じである時に次元配列の行一つ一つを一次元配列と計算させた結果が返ります。よって二次元配列と同じ形の配列となります。

二次元配列を一つの行列として演算を行うことも可能です。

```
> M = np.asarray([[1,2,3], [2,3,4]])
> N = np.asarray([[1,2],[3,4], [5,6]])
```

の二つの配列の行列としての積を求めるには

```
> M.dot(N)
array([[22, 28],
       [31, 40]])
```

とします。ここでは$2\times 3$行列と$3\times 2$行列を掛けているので$2\times 2$行列が返ります。

### 関数呼び出し

numpyでは配列を様々な関数にいれることができます。この時、関数はそれぞれの要素に対して作用します。たとえば

```
> a = np.asarray([[1,2], [3,1])
> np.log(a)
array([[ 0.        ,  0.69314718],
       [ 1.09861229,  0.        ]])
```

となります。この時元の配列は変化しません。
ほかにも三角関数、`exp`、`sqrt`等々考えうるほとんどの関数が用意されています。

## 統計を取る

numpyは配列の統計を取るのも得意です。まず100この乱数を生成します。

```
> arr = np.random.rand(100)
```

配列の平均を取るには

```
> np.mean(arr)
0.52133315138159586
```

とします。最大値、最小値は

```
> np.max(arr)
0.98159897843423383
> np.min(arr)
0.031486992721019846
```

でえられます。標準偏差は

```
> np.std(arr)
0.2918171894076691
```

和を得るには

```
> np.sum(arr)
52.133315138159588
```

とします。また、二次元配列に関してはどの向きに統計を取るのかを指定することもできます。たとえば

```
> arr = np.asarray([[1,2,3], [2,3,4]])
> np.sum(arr, axis=0)
array([3, 5, 7])
> np.sum(arr, axis=1)
array([6, 9])
```

となります。

## 実際に使ってみる

ここまでを含めて、三次元空間内にある100個のベクトルの原点からのユークリッド距離の平均を求めるコードをnumpyを使って計算してみます。

まず`data`配列が、形が`(100, 3)`の配列で一列目が$x$座標、二列目が$y$座標、三列目が$z$座標だとします。ここでは

```
> data = np.random.randn(100, 3)
```

として生成しました。ユークリッド距離は

```math
d(x,y,z) = \sqrt{x^2+y^2+z^3}
```

なので、まずそれぞれの要素を自乗します。

```
> squared = data**2
```

次にそれぞれの行に対して和を取ります。

```
> squared_sum = np.sum(squared, axis=1)
```

このとき`squared_sum`は一次元配列になります。これに対して平方根を取ればそれぞれの点のユークリッド距離がわかります。

```
> dist = np.sqrt(squared_sum)
```

この距離の平均を取れば

```
> np.mean(dist)
1.5423905808984208
```

となりました。（データをランダムに生成しているので結果は少しずつ違います）

numpyを使わずにこのコードをかけば、100個の点一つ一つを計算するのに`for`ループを使って、次元が大きくなればそれぞれの点に対しても`for`ループを使ってとコードが複雑になるだけでなく実行速度も遅くなります。このように、大きな配列を一度に計算してしまおうというのがnumpyの基本的なアイデアになります。その結果、numpyはpythonとは思えないほど高速に複雑な演算を行うことができます。

ちなみに、今回は練習のために一つ一つ計算しましたが、numpyには`np.linalg.norm`という関数が用意されていて、ユークリッド距離を簡単に計算することができます。

## まとめ

numpyの基本的な使い方は以上ですが、numpyにはもっと様々な機能があります。たとえば条件を満たす要素のインデックスを探す`np.where`関数などがあります。

numpyに限らず、pythonはググりながら書いてみて経験していくことが上達の最短の道だと思うので、最初は混乱しても頑張って書いてみてください！