pythonのnumpyについてまとめてみた

#はじめに
今回は自分がpythonのnumpyについてまとめた記事になっています。

numpyについては多くの人がまとめているため、特に目新しい点はないかもしれませんが、お付き合い頂ければ幸いです。

##arrayの作成
numpyのarrayを作成してみましょう。

arrayの簡単な作り方のひとつは、arrayメソッドにリストを渡すことです。

```.py
import numpy as np

lesson_list = [1, 2, 3, 4]
lesson_array = np.array(lesson_list)
print(lesson_array)
print(type(lesson_array))
```


>[1 2 3 4]
\<class 'numpy.ndarray'>


次は二次元配列を作成してみましょう。

また、arrayオブジェクトのshape変数には次元の情報が格納されています。

また、detype変数には格納されているデータの型が入っています。

```.py
lesson_list1 = [1, 2, 3, 4]
lesson_list2 = [5, 6, 7, 8]
lesson_array = np.array([lesson_list1, lesson_list2])
print(lesson_array)
print(lesson_array.shape)
print(lesson_array.dtype)
```

>\[[1 2 3 4]
 [5 6 7 8]]
(2, 4)
int32

numpyのarrayは全てのデータ型がそろっていなければならないことに注意してください。


####zeros, onesについて
次のようにすれば、決められた次元の、データが全て0または1のarrayを作成できます。

```.py
print(np.zeros([2, 2]))
print(np.ones([2, 2]))
```

>[[0. 0.]
 [0. 0.]]
[[1. 1.]
 [1. 1.]]

データ型を確認してみましょう。

```.py
print(np.zeros([2, 2]).dtype)
print(np.ones([2, 2]).dtype)
```

>float64
float64

このように、numpyのzerosやonesを用いて配列を生成すると、float64型のデータ型で配列が生成されます。

ちなみに、zerosやonesの引数はリストではなくタプルでも大丈夫です。

```.py
print(np.zeros((2, 2)))
print(np.ones((2, 2)))
```

>[[0. 0.]
 [0. 0.]]
[[1. 1.]
 [1. 1.]]

####zeros_like、ones_like
np.zeros_likeやnp.ones_likeを使えば、引数として渡した配列と同じ次元のデータが0または1のデータを作成できます。zerosやonesとはデータ型が異なることに注意してください。

```.py
test_array = np.array([[1, 2],
                       [3, 4]])
like_zeros_array = np.zeros_like(test_array)
like_ones_array = np.ones_like(test_array)
print(like_zeros_array)
print(like_ones_array)
print(like_zeros_array.dtype)
print(like_ones_array.dtype)
```

>[[0 0]
 [0 0]]
[[1 1]
 [1 1]]
int32
int32


###空っぽの配列の作成
numpyのemptyを使えば、空っぽの配列を作成できます。

```.py
print(np.empty((2, 2)))
print(np.empty((2, 2)).dtype)
```

>[[1.95821574e-306 1.60219035e-306]
 [1.37961506e-306 1.37961913e-306]]
float64

なぜemptyで空の配列を作成する必要があるのかは、[こちら](https://deepage.net/features/numpy-empty.html)の記事を参考にしてください。

簡単にまとめると、zerosやonesで初期化する場合と比べて若干高速になる点、また明示的に初期化する必要がないことをコードを読む人に伝えることができる点が挙げられます。

###単位行列の生成
以下のようにすると単位行列を生成できます。

```.py
print(np.eye(3))
```

>[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

###arangeを使ったarrayの生成
次の用にしてもarrayを生成できます。

```.py
print(np.arange(10))
```

>[0 1 2 3 4 5 6 7 8 9]


###linspaceを用いた配列生成
`linspace(最小値, 最大値, 配列の数)`で最小値から最大値までを配列の数で等間隔に分割した配列を生成できます。

```.py
array = np.linspace(0, 1, 10)
print(array)
```

>[0.         0.11111111 0.22222222 0.33333333 0.44444444 0.55555556
 0.66666667 0.77777778 0.88888889 1.        ]

##形状の確認
arrayのshapeに形状が格納されています。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array.shape)
```

>(3, 2)

機械学習では主に行方向の数がデータの数、列方向の数がデータの特徴量の数を表しています。

以下のようにすればデータの数にアクセスできます。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array.shape[0])
```

>3

以下のようにすればデータの特徴量の数にアクセスできます。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array.shape[1])
```

>2

##形状の変換
reshapeメソッドを用いれば、配列の形状を変換することができます。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array)
array = array.reshape(2, 3)
print(array)
```

>[[2 3]
 [4 5]
 [6 7]]
[[2 3 4]
 [5 6 7]]

####縦ベクトルへの変換
行列の縦ベクトルへの変換は非常によく用いるテクニックです。確認しましょう。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array)
array = array.reshape(-1, 1)
print(array)
```

>[[2 3]
 [4 5]
 [6 7]]
[[2]
 [3]
 [4]
 [5]
 [6]
 [7]]

rehapeに-1を指定すると、後から最適な値を代入してくれます。この例では、列を1にするということだけを決めれば、-1にはきちんと変換できるように6が代入されます。

####1次元配列への変換

`ravel`を使えば、多次元配列を1次元配列に変換できます。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array)
array = array.ravel()
print(array)
```

>[[2 3]
 [4 5]
 [6 7]]
[2 3 4 5 6 7]


##arrayの選択と抽出について
以下のようにすればスライスで抽出できます。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array)
print(array[0])  # 1行目を抽出
print(array[1:])  # 2行目以降を抽出
print(array[:2])  # 2行目までを抽出
```

>[[2 3]
 [4 5]
 [6 7]]
[2 3]
[[4 5]
 [6 7]]
[[2 3]
 [4 5]]


以下のようにすれば、n行目のm列の値にアクセスできます。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array)
print(array[0, 1])
print(array[0][1])
```

>[[2 3]
 [4 5]
 [6 7]]
3
3


列ごとの各要素を取り出すためには以下のようにします。機械学習の参考書にもよく書かれている書き方ですね。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array)
print(array[:, 1])
```

>[[2 3]
 [4 5]
 [6 7]]
[3 5 7]

###条件による選択
arrayから条件を指定して抽出することもできます。以下のコードで確認してください。

```.py
array = np.array([[2, 3], [4, 5], [6, 7]])
print(array)
print(array > 4)  # TrueとFalseが返ってくる
array = array[array > 4]  #抽出して代入
print(array)
```

>[[2 3]
 [4 5]
 [6 7]]
[[False False]
 [False  True]
 [ True  True]]
[5 6 7]

###whereメソッドを用いた抽出
まずはwhereメソッドがどのような挙動をするのかを確認しましょう。

whereメソッドは条件により値を置き換えるメソッドで、引数の内容は(条件, 条件を満たしたときに置き換える値, 条件を満たさないときに置き換える値) という形になっています。 

```.py
y = np.random.randn(10)
print(y)
y = np.where(y < 0.5, 0, 1)
print(y)
```

>[ 1.45238973  1.05699382  0.83257516  0.27009833  0.34137543 -0.9582972
  0.82159742 -0.76797059 -2.42945538 -0.64723916]
[1 1 1 0 0 0 1 0 0 0]

このwhereメソッドを用いて、以下のように抽出を行うことができます。yが1以上のときのxを抽出しましょう。


```.py
x = np.arange(10)
y = np.random.randn(10)
print(y)
y = np.where(y < 0.5, 0, 1)
print(y)
x = x[:][y == 1]
print(x)
```

>[-0.16263738 -0.10284738  0.85039664  0.03005703 -2.00022208  1.13589862
 -0.49716426 -0.34458117 -0.15785598  0.34361848]
[0 0 1 0 0 1 0 0 0 0]
[2 5]

##配列の結合について
配列を縦方向に結合したい場合は`vstack`を、配列を横方向に結合したい場合は`hstack`を使用します。

```.py
array_1 = np.array([[1, 2], [3, 4], [5, 6]])
array_2 = np.array([[7, 8], [9, 10], [11, 12]])

array_v = np.vstack([array_1, array_2])
print(array_v)
print('#####################')
array_h = np.hstack([array_1, array_2])
print(array_h)
```

>[[ 1  2]
 [ 3  4]
 [ 5  6]
 [ 7  8]
 [ 9 10]
 [11 12]]
\#####################
[[ 1  2  7  8]
 [ 3  4  9 10]
 [ 5  6 11 12]]

##arrayを使った計算について

以下のようにすればarray同士の掛け算ができます。

```.py
array1 = np.array([[1, 2, 3, 4],
                  [5, 6, 7, 8]])
print(array1 * array1)
```

>[[ 1  4  9 16]
 [25 36 49 64]]

結果をみればわかるように、同じ次元のarrayを掛け算すると、各々の要素を掛け算した値が返ってきます。

和や差も同じなので、ここでは割愛します。

####行列の積について
`np.matmul`を用いれば行列の積を計算できます。

`np.dot`でも二次元配列においては同様の挙動を示しますが、次元により細かい挙動が違うので、二次元以下の場合は[こちら](https://qiita.com/ao_log/items/64768b67153e8fb6820b)の記事を、三次元以上の場合は[こちら](https://www.uosansatox.biz/entry/2017/10/10/152915)の記事を参考にしてください。

しかし、ここで気をつけなければならないのが、一次元のarrayは縦ベクトルと横ベクトルを区別しないということです。

行列の掛け算を行うときには、通常線形代数でもそうであるように次元に注意する必要がありますが、一次元配列は縦ベクトルと横ベクトルを区別しないため、多少ガバガバでも計算できてしまうのです。

以下の例で確認しましょう。

```.py
array1 = np.array([[1, 1],
                   [1, 1]])
array2 = np.array([1, 1])
result = np.matmul(array1, array2)
print(array1.shape)
print(array2.shape)
print(result.shape)
```

>(2, 2)
(2,)
(2,)

2×2の行列であるarray1には2×1の縦ベクトルをかける必要があるのですが、細かいことを考えずとも計算できてしまいます。

array2はこの場合、2×1の縦ベクトルなのか1×2の横ベクトルなのかの区別はありません。以下のように行列の掛け算の順番を逆にしても計算できます。

```.py
array1 = np.array([[1, 1],
                   [1, 1]])
array2 = np.array([1, 1])
result = np.matmul(array2, array1) # ここが前の例とは逆になっています
print(array1.shape)
print(array2.shape)
print(result.shape)
```

>(2, 2)
(2,)
(2,)

このように、ベクトルの掛け算の順序を逆にしたにも関わらず計算が成功してしまいました。

以上のことから分かるように、numpyにおいては一次元配列は縦ベクトルでもあり、横ベクトルでもあるのです。

個人的にはこのガバガバさに慣れてしまうと怖いので、明示的にベクトルの形を揃えてあげた方が良い気がします。

ちなみに、tensorflowで用いるtensorオブジェクトはここの部分の区切りがしっかりとしているので、numpyの感覚で操作していたら痛い目を見ます(経験談)。

以下のように縦ベクトルと横ベクトルを意識して使い分けましょう。

```.py
array1 = np.array([[1], [1], [1]])  # これは縦ベクトル
array2 = np.array([[1, 1, 1]])  # これは横ベクトル

print(array1.shape)
print(array2.shape)
```

>(3, 1)
(1, 3)

この縦ベクトルと横ベクトルの使い方には慣れておきましょう。

また、numpyのarrayにはreshapeというメソッドが存在し、要素の数さえ合っていれば無理やり形を変えることができます。

```.py
array1 = np.array([1, 1, 1]).reshape((3, 1))  # これは縦ベクトル
array2 = np.array([1, 1, 1]).reshape((1, 3))  # これは横ベクトル

print(array1.shape)
print(array2.shape)
```

>(3, 1)
(1, 3)

###転置行列について
numpyのarrayのTメソッドを使うことで、転置行列にアクセスできます。

```.py
array1 = np.array([[1, 2],
                   [3, 4]])
print(array1.T)
```

>[[1 3]
 [2 4]]

###ベクトルの内積について
転置行列を利用することで、以下のように縦ベクトル同士の内積を計算することができます。

```.py
array1 = np.array([1, 2, 3]).reshape([3, 1])
array2 = np.array([4, 5, 6]).reshape([3, 1])

result = np.matmul(array1.T, array2)

print(result)
```

>[[32]]

また、`np.inner`を用いても内積を計算できます。

```.py
array1 = np.array([1, 2, 3])
array2 = np.array([4, 5, 6])

print(np.inner(array1, array2))
```

>32

###ユークリッド距離について
距離とは集合二つの近さを測る尺度を指すので、距離といっても様々なものが存在します。

距離については[こちら](https://sitest.jp/blog/?p=6784)の記事を参考にしてください。

ユークリッド距離とは、２つのベクトルの差のノルムを表します。人が定規で測るような、一般的な距離のことだと考えて差し支えないです。

また、ノルムとは大きさのことなのですが、$L^2$ノルムや$L^3$ノルム呼ばれるものが存在します。

ノルムについては[こちら](https://mathtrain.jp/lpnorm)の記事を参考にしてください。


$L^2$ノルムや$L^3$ノルムは、`numpy.linalg.norm`を用いて計算することができます。

以下のように第一引数にarrayを、第二引数に何乗ノルムなのかを指定してください。

```.py
array1 = np.array([1, 2, 3])
print('L^2ノルム:', np.linalg.norm(array1, 2))
print('L^3ノルム:', np.linalg.norm(array1, 3))
```

>L^2ノルム: 3.7416573867739413
L^3ノルム: 3.3019272488946263

$L^2$ノルムはユークリッドノルムと呼ばれます。

ユークリッド距離とは二つのベクトルの差の$L^2$ノルムであるので、以下のように計算できます。

```.py
array1 = np.array([1, 2, 3])
array2 = np.array([4, 5, 6])
dif_array = array2 - array1

print('ユークリッド距離は', np.linalg.norm(dif_array, 2))
```

>ユークリッド距離は 5.196152422706632

###軸(axis)を指定して合計、平均

次のようにして三次元のarrayを生成しましょう。

```.py
array1 = np.arange(9).reshape(3, 3)
print(array1)
```

>[[0 1 2]
 [3 4 5]
 [6 7 8]]

この三次元のarrayを操作していきます。

縦方向に対しての合計を計算しましょう。

```.py
array1 = np.arange(9).reshape(3, 3)
print(array1.sum(axis=0))
```

>[ 9 12 15]

横方向に対しての合計を計算しましょう。

```.py
array1 = np.arange(9).reshape(3, 3)
print(array1.sum(axis=1))
```

>[ 3 12 21]

このように、二次元配列においては`axis=0`を指定すると縦方向に対して計算し、`axis=1`を指定すると横方向に対して計算します。

二次元配列に対してこの操作を行うと、一次元配列になるので注意してください。

三次元以降の挙動については[こちら](https://qiita.com/shuetsu@github/items/2bf8bba233c5ecc7a0ad)の記事を参考にしてください。

以下のようにすると縦方向に対しての平均を計算できます。

```.py
array1 = np.arange(9).reshape(3, 3)
print(array1.mean(axis=0))
```

>[3. 4. 5.]

###arrayの計算のための関数について

####sqrtについて
`np.sqrt`関数を用いれば、平方根を計算できます。

```.py
array1 = np.arange(9).reshape(3, 3)
print(np.sqrt(array1))
```

>[[0.         1.         1.41421356]
 [1.73205081 2.         2.23606798]
 [2.44948974 2.64575131 2.82842712]]

####expについて
`np.exp`を用いれば、底がeで指数が`array`の値を計算できます。

```.py
array1 = np.arange(9).reshape(3, 3)
print(np.exp(array1))
```

>[[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]]

##統計量の計算について
`sum`、`mean`、`var`、`std`を用いて、合計・平均・分散・標準偏差を計算できます。

```.py
array = np.random.rand(10)
print(array)
print(np.sum(array))  # 合計
print(np.mean(array))  # 平均
print(np.var(array))  # 分散
print(np.std(array))  # 標準偏差
```

>[0.97178838 0.06742648 0.8142233  0.80975179 0.39847626 0.64877943
 0.33821537 0.54746943 0.41627389 0.50022593]
5.5126302499162465
0.5512630249916246
0.06459877168662138
0.2541628841641151

`man`で最大値、`argmax`で最大値のインデックス、`min`で最小値、`argmin`で最小値のインデックスを取り出すことができます。

```.py
array = np.random.rand(10)
print(array)
print(np.max(array))
print(np.argmax(array))
print(np.min(array))
print(np.argmin(array))
```

>[0.03157926 0.73164432 0.08068812 0.76555375 0.38739267 0.7224165
 0.69670821 0.27630965 0.11581652 0.08610501]
0.765553754084263
3
0.031579259068311494
0


##データ型の確認
以下のようにすればデータ型を確認することができます。

```.py
array = np.random.rand(10)
print(array.dtype)
```

>float64


##データ型を指定
次のようにすればデータ型を指定できます。

```.py
array = np.array([1, 2, 3], dtype=float)
print(array.dtype)
```

>float64

##桁数を丸める
次のようにすれば桁数を丸めることができます。

```.py
array = np.random.rand(5)
print(array)
array = np.round(array, 2)
print(array)
```

>[0.53934405 0.48650337 0.02114805 0.35863413 0.65589579]
[0.54 0.49 0.02 0.36 0.66]


##randomメソッドについて
numpyのrandomメソッドには、乱数を生成できるメソッドが大量にあるので、ざっくりまとめていきます。
[こちら](https://note.nkmk.me/python-numpy-random/)の記事にかなり細かくまとまっているので、参考にしてください。

####numpy.random.rand
`numpy.random.rand`は0~1の乱数を生成します。引数で次元を指定できます。

```.py
array1 = np.random.rand(3, 3, 3)
print(array1)
```

>[[[0.78207838 0.19169644 0.33656754]
  [0.17286481 0.71234607 0.46828202]
  [0.61559107 0.98703808 0.01870277]]

 >[[0.21355205 0.75614897 0.40712531]
  [0.33383552 0.93449879 0.85846818]
  [0.22658314 0.67313683 0.40054548]]

 >[[0.67963611 0.51894421 0.58826204]
  [0.18928953 0.26114807 0.19811072]
  [0.53317241 0.51059944 0.02840698]]]

####numpy.random.random_sample
`numpy.random.random_sample`は`numpy.random.rand`とほぼ同じですが、引数にタプルを渡して次元を指定します。

```.py
array1 = np.random.random_sample((3, 3, 3))
print(array1)
```

>[[[0.89149307 0.59828768 0.24341452]
  [0.28129118 0.8939036  0.97494691]
  [0.11672552 0.19523899 0.18622686]]

 >[[0.71346416 0.20468212 0.82842017]
  [0.45043354 0.42185774 0.91783287]
  [0.47859017 0.73222385 0.40167049]]

> [[0.19947387 0.73663499 0.55214293]
  [0.77191802 0.481025   0.8765084 ]
  [0.90954102 0.90351289 0.80627643]]]

#### numpy.random.randint
`numpy.random.randint`は先ほどの二つと異なり、最小値と最大値を指定することができ、また乱数は整数になっています。

引数に「乱数の最小値」「乱数の最大値」「乱数の次元」を渡しましょう。

次元はタプルで渡す必要があります。

```.py
array1 = np.random.randint(-1, 1, (2, 2))
print(array1)
```

>[[-1  0]
 [ 0  0]]

####numpy.random.randn
`numpy.random.randn`は、平均0、分散1の正規分布に従う乱数を返します。

次数をタプルで指定する必要がありません。

`numpy.random.rand`の正規分布に従うバージョンと考えてください。

```.py
array1 = np.random.randn(2, 2)
print(array1)
```

>[[0.32322902 0.66778836]
 [1.66995707 1.39959764]]

####numpy.random.normal
`numpy.random.normal`は前回の`numpy.random.rand`の平均と標準偏差を指定できるバージョンです。

最初の引数に「平均」を、二番目の引数に「標準偏差」を、三番目の引数に「次数」をタプルで渡してください。

```.py
array1 = np.random.normal(5, 3, (2, 2))
print(array1)
```

>[[ 6.76899252  2.45230625]
 [11.92108624  5.89567182]]




#終わりに
今回はここまでになります。お付き合い頂きありがとうございました。