python初心者のnumpy勉強記録

# １．目的

2018/5/7にnumpyに初めて触れた私の勉強記録。ちょこちょこ更新します～。強い人、優しく教えてください。

# ２．そもそもnumpyって？

pythonのライブラリの一つで、数値計算や機械学習によく用いられるらしい。Aidemyのコースで無料の範囲まで少し学習させてもらいましたが、通常のpythonでのリストの計算より圧倒的に高速。

# ３．使い方

Anacondaなり何かで環境構築していることを前提にします。なお、python3でコーディングしています。

まずは、とにもかくにもインポート：

```python
import numpy as np
```

## 配列を作ろう

`array`関数でnumpyの配列をつくることができるようです：

```python
arr1d = np.array([1, 2, 3]) #１次元配列
arr2d = np.array([[1, 2, 3], [4, 5, 6]]) #２次元配列
arr3d = np.array([
        [ [1, 2], 
          [3, 4] ], 
        [ [5, 6], 
          [7, 8] ]
]) #３次元配列
```

高次元配列もまあ素直に作ることはできますね。

他にも便利な関数として、`arange`関数があります：

```python
arr1 = np.arange(5);
>>>array([0, 1, 2, 3, 4])
```

このように、引数に一つだけ整数nを指定すると、0～(n-1)を要素に持つ１次元配列が生成されます。
より正確には、**np.arange([start,] stop [,skip])**らしいので、

```python
arr2 = np.arange(1, 9, 2)
>>> array([1, 3, 5, 7])
```

となる。先ほどのarray関数よりは楽に作れた。小数も指定できるが、配列の要素数を予想するのが困難になるので、要素数を固定したいときは`linspace`関数がいいそうだ：

```python
arr3 = np.linspace(0, 2, 9)
>>>array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  ])
```

たしかに便利そう。覚えておこう。

他にも配列を生成する関数としては、`zeros`や`ones`、`eye`関数などほんとにたくさんあるそうです。覚えられねえ。

```python:zeros.py
np.zeros(5)
>>>array([0., 0., 0., 0., 0.])
np.zeros((2, 2))
>>>array([
       [0., 0.],
       [0., 0.]
   ])
```

ones関数はzeros関数と使い方は同じ。0じゃなくて1になるだけ。上のように、タプルを渡すことで行列も作ることが出来ます。

そして`eye`関数は、単位行列を作ってくれる：

```python:eye.py
np.eye(4)
>>>array([[1., 0., 0., 0.],
          [0., 1., 0., 0.],
          [0., 0., 1., 0.],
          [0., 0., 0., 1.]])
```

ちなみに、先ほどから要素がすべて少数になっていますが、整数値にしたければ次のようにしたらいいようだ:

```python
np.ones((3, 3), dtype=int)
>>>array([[1, 1, 1],
          [1, 1, 1],
          [1, 1, 1]])
```

他にもおもしろいのは乱数を使って生成できる`numpy.random.rand`と`numpy.random.randn`ですね。

```python:numpy.random.rand.py
np.random.rand(3,2)
>>>array([[0.48894664, 0.16554932],
          [0.48366251, 0.23110038],
          [0.80066766, 0.39297847]])
```

np.random.randは、引数n, mが与えられるとn x m行列を生成し、各要素は0~1から一様分布にもとづいて生成されます。

同じようにnp.random.randnも使えるが、こちらは標準正規分布に従って各要素が生成されます（標準正規分布は、ざっくり言うと、0近辺の値をいい感じで出してくれる確率分布。ざっくりすぎた）。

```python:numpy.random.randn.py
np.random.randn(3, 2)
>>>array([[ 0.22404108,  1.24755193],
          [-1.22509214, -1.16674744],
          [-0.12295972,  0.7492247 ]])
```

## 演算

いくつか簡単な演算をすることができました：

```python:sum.py
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([10, 20, 30, 40, 50])
arr1 + arr2
>>>array([11, 22, 33, 44, 55])
```

このように、**各要素同士の**足し算になります。他にも引き算、掛け算、割り算でもやはり要素同士の演算になります。

他にも、次のような演算ができます：

```python
arr = np.array([[1,2,3,4], [5,6,7,8]])
#割り算
1 / arr
>>>array([[1.        , 0.5       , 0.33333333, 0.25      ],
          [0.2       , 0.16666667, 0.14285714, 0.125     ]])
#累乗
arr ** 3
>>>array([[  1,   8,  27,  64],
          [125, 216, 343, 512]], dtype=int32)
```

やはり各要素が独立した演算になっています。分かりやすいですね。

そして、これはほんと便利だなと思ったのが、`np.dot`関数です。これは、いわゆるベクトルの内積や２次元行列の積を計算してくれる優れものです：

```python
arr1 = np.eye(5) * 5
arr2 = np.eye(5) * 3
np.dot(arr1, arr2)
>>>array([[15.,  0.,  0.,  0.,  0.],
          [ 0., 15.,  0.,  0.,  0.],
          [ 0.,  0., 15.,  0.,  0.],
          [ 0.,  0.,  0., 15.,  0.],
          [ 0.,  0.,  0.,  0., 15.]])
```

単位行列が分かりやすいと思ってそうしました。確かに行列の積になっています。通常のpythonだとループで書かないといけないし、何より処理が遅いので、これはすごく優秀ですね。優勝です。

## 演算のための便利な関数

上と少し似ていますが、演算をしてくれる便利な関数もあったので少し紹介します。

まずは各要素の平方根を計算してくれる`numpy.sqrt`です：

```python:sqrt.py
arr = np.arange(11)
arr
>>>array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
#平方根を計算
np.sqrt(arr)
>>>array([0.        , 1.        , 1.41421356, 1.73205081, 2.        ,
          2.23606798, 2.44948974, 2.64575131, 2.82842712, 3.        ,
          3.16227766])
```

平方数である1,2,4,9のところがわかりやすいですね。
ほかにも、自然対数の底eのべき乗を計算してくれる関数`numpy.exp`もあります：

```python:exp.py
np.exp(arr) #ここのarrは上と同じとします
>>>array([1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 2.00855369e+01,
          5.45981500e+01, 1.48413159e+02, 4.03428793e+02, 1.09663316e+03,
          2.98095799e+03, 8.10308393e+03, 2.20264658e+04])
```

これも数値計算をするときは便利そうですね。物理やってると指数関数めちゃめちゃよく出てくるので。

２つの配列の各要素を比較して、大きいほうだけを取り出してきた配列を返す`numpy.maximum`という関数もあります：

```python:maximum.py
A = np.random.randn(10)
A
>>>array([ 0.67814484,  0.36353233, -0.50452911,  0.81036456,  0.62980394,
           1.13577201, -1.82685599, -0.33409179, -0.62550564, -0.061108  ])
B = np.random.randn(10)
B
>>>array([ 0.09633725,  0.10358867, -2.51940142,  0.13585893, -1.17688068,
           0.13461153, -1.01325164,  0.55530579, -1.41249275,  1.59625433])
#numpy.maximum
np.maximum(A,B)
>>>array([ 0.67814484,  0.36353233, -0.50452911,  0.81036456,  0.62980394,
           1.13577201, -1.01325164,  0.55530579, -0.62550564,  1.59625433])
```

確かに、大きいほうだけを取り出した配列が返っていることがわかります。これもいちいちループで処理しなくていいので楽チンですね。

## 添え字

通常のpythonのリストのように、[]を使って各要素を参照することができるそうです。また、スライスも使えます：

```python
arr1 = np.arange(10)
arr1[5]
>>>5

arr1[2:10:2]
>>>array([2, 4, 6, 8])
```

これは1次元配列の場合です。ただ、高次元配列の場合も、ほとんど同じように各要素に参照することができます。

```python
#２次元配列を生成
arr2 = np.array([1,2,3,4])
arr3 = arr2.reshape((2,2))
arr3
>>>array([[1, 2],
          [3, 4]])

#(1,1)の要素を参照する
arr3[1][1]
>>>4
arr3[1,1]
>>>4
```

まあ難しくないですね。とても扱いやすいです。
さらに、添え字としてリストを渡すこともできます。その場合、リストの要素で指定されている要素を複数抜き出すことができます：

```python
arr = np.arange(21)
arr
>>>array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20])
#添え字の指定にリストを用いる
arr[[1,3,5,7,18]]
>>>array([ 1,  3,  5,  7, 18])
```

また、高次元配列の場合、各添え字に対してスライスを用いることで、行列のある一部分だけを抽出することもできます：

```python
#3x3の行列を生成
arr = np.arange(5, 50, 5).reshape((3,3))
arr
>>>array([[ 5, 10, 15],
          [20, 25, 30],
          [35, 40, 45]])
#この行列の右上部分の2x2行列だけ取り出す
arr[:2, 1:]
>>>array([[10, 15],
          [25, 30]])
```

考え方としては、各添え字にスライスを適用して、その重なった部分を取り出していると理解したら良さそう。これも便利な気がする。

## ファイルへの読み書き

作成した配列をファイルに保存したり、保存されている配列を読み込んだりすることができるらしい。こちらの機能も確かに便利そう。

ファイルへの保存は`np.save`関数を使います：

```python:save.py
arr1 = np.arange(5)
arr1
>>>array([0, 1, 2, 3, 4])
#ファイルへ保存
np.save("test1", arr1) #ファイル名は"test1.npy"となる

#ファイルから読み込む
arr2 = np.load("test1.npy")
arr2
>>>array([0, 1, 2, 3, 4])
```

複数の配列をまとめて一つのファイルに保存する場合はnumpyのzipファイルに保存することができる。これは`numpy.savez`関数で実行できます：

```python:savez.py
arr1 = np.arange(10)
arr1
>>>array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr2 = np.random.randn(10)
arr2
>>>array([-0.6481415 , -0.1862373 , -0.38852157,  2.56197775, -0.65005117,
           1.15866373,  0.00373106,  0.64527106, -1.12312679,  0.08016327])
#まとめて保存
np.savez("test2", arr1, arr2) #"test2.npz"というファイルが作成される
```

作成したファイルの中身を見て、読み込んでみましょう：

```python:savez2.py
archive = np.load("test2.npz")
archive.files
>>>['arr_0', 'arr_1'] #中身にはそれぞれ'arr_0', 'arr_1'という名前がついているようだ
#それぞれ参照してみよう
archive['arr_0']
>>>array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
archive['arr_1']
>>>array([-0.6481415 , -0.1862373 , -0.38852157,  2.56197775, -0.65005117,
           1.15866373,  0.00373106,  0.64527106, -1.12312679,  0.08016327])

```

たしかに、保存した配列を取得することができていますね。ただ、参照するための名前が自動的に決められていますが、もちろん自分で決めることもできます：

```python:savez3.py
#arr1, arr2は上と同じものです
#それぞれに名前をつけて保存
np.savez("test3", tanaka=arr1, yamashita=arr2)

#読み込み
archive = np.load("test3.npz")
archive.files
>>>['tanaka', 'yamashita']
archive["tanaka"]
>>>array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
archive['yamashita']
>>>array([-0.6481415 , -0.1862373 , -0.38852157,  2.56197775, -0.65005117,
           1.15866373,  0.00373106,  0.64527106, -1.12312679,  0.08016327])
```

名前を自分でつけられると、読み込むときに便利そうですね。

# ４．まとめ

とりあえずここまで。また勉強してまとめようかと思います～。