馬鹿の一つ覚え: NumPy

## ここで利用するリスト変数
```python
import numpy
a = [None] * 8
a[0] = numpy.empty((1,))
```
ここではNumPy配列を複数格納するためのリストaを用意します。

## NumPy配列を作成

#### 基本的なNumPy配列の作成
```python
l = [float(i) for i in range(90)]
a[0] = numpy.array(l).reshape(6, 5, 3)   # 3階 (どれか1つは -1 にしても可)
print(a[0])
print(type(a[0]))    # <class 'numpy.ndarray'>
print(len(a[0]))    # 6 (a[0].shape[0]と同じ値)
print(a[0].dtype)    # float64 (これがdefault)
print(a[0].shape)    # (6, 5, 3)
print(a[0].ndim)    # 3 (階数)
print(a[0].size)    # 90 (全要素数, 6 * 5 * 3)
print(a[0].__class__)    # <class 'numpy.ndarray'>
a[0] = numpy.empty((8, 17, 5), dtype = int)    # 空の配列を作成 (高速)
a[0] = numpy.zeros((7, 11, 9), dtype = 'uint8')    # 0で初期化された配列を作成
a[0] = numpy.full((23, 19), -1.0, dtype = 'float16')    # -1.0で初期化
```
dtypeで指定するintやfloatはそれぞれint64とfloat64に相当するがPythonの型なのでクォートは不要。
データ型int8, int16, int32, int64, uint8, uint16, uint32, uint64, float16, float32, float64, float128を指定する場合は'float16'と言うようにクォートが必要。

#### 等差数列を作成
```python
a[0] = numpy.arange(3)    # [0 1 2]
a[1] = numpy.arange(-2, 1, 0.5)    # [-2.  -1.5 -1.  -0.5  0.   0.5]
a[2] = numpy.linspace(0, 6, 7)    # [0. 1. 2. 3. 4. 5. 6.]
a[3] = numpy.linspace(10, 13, 6)    # [10.  10.6 11.2 11.8 12.4 13. ]
a[4] = numpy.linspace(11, 10, 3)    # [11.  10.5 10. ]
```
linspaceでは最初の値から最後の値までで最後の値を含んだ要素数を指定するので、間隔の数は要素数から1を引いた値になる。

#### 同形状、同データ型で0や1で埋めたNumPy配列の作成
```python
a[0] = numpy.empty((5, 7, 4), dtype = 'uint8')
a[1] = numpy.zeros_like(a[0])
print(a[1].shape)
print(a[1].dtype)
print(a[1])
a[1] = numpy.ones_like(a[0])
print(a[1].shape)
print(a[1].dtype)
print(a[1])
```

#### タブ区切りテキストファイルを読み込む
```python
a[0] = numpy.loadtxt(file_name, delimiter = "\t",    # タブ区切り (デフォルトは空白)
           skiprows = 1,    # headerの1行目を読み飛ばす
           usecols = range(1, 4097),    # 最初の列を読み飛ばし、1から数えた4096列目まで
           dtype = 'float16')    # デフォルトはfloat
```

#### NumPy配列をそのままファイルに保存、そのファイルを読み込む
```python
numpy.save('data.npy', a[0])
a[1] = numpy.load('data.npy')
```

#### NumPy配列をタブ区切りテキストファイルに保存
```python
numpy.savetxt('data.tsv', a[0], delimiter = "\t", fmt = '%.4f')
```

## データ型と配列の種類

#### データ型の確認とデータ型の変更
```python
a[1] = a[0].astype('float16')
print(a[0].dtype)    # float64
print(a[1].dtype)    # float16
```

#### 配列とスカラの区別
```python
i = 19
print(type(i))    # <class 'int'>
i = numpy.uint16(19)    # 配列ではなくスカラも作成可能
print(type(i))    # <class 'numpy.uint16'>
print(numpy.isscalar(i))    # True
i = numpy.array(19)
print(type(i))    # <class 'numpy.ndarray'>
print(i.dtype)    # int64
```

#### 保存されるデータのサイズ
```python
a[0] = numpy.full((2000, 50000), 255, dtype = 'uint8')
numpy.save('data_a0.npy', a[0])    # 1バイト / 要素
a[1] = numpy.full((2000, 50000), 255, dtype = 'uint16')
numpy.save('data_a1.npy', a[1])    # 2バイト / 要素 (16ビットなので2バイト)
a[2] = numpy.full((2000, 50000), 256, dtype = 'uint16')
numpy.save('data_a2.npy', a[2])    # 2バイト / 要素
```

## テンソルの形状変更

#### reshapeで-1を使うと全体のサイズから残りのサイズを計算
```python
print(a[0].shape)    # (17500, 6252)
a[1] = a[0].reshape((-1, 6252, 10))    # 行列を3階のテンソルに
print(a[1].shape)    # (1750, 6252, 10)
a[2] = numpy.arange(6)
print(a[2].reshape((2, 3)))
    # [[0 1 2]    # order = 'C'
    #  [3 4 5]]
print(a[2].reshape((2, 3), order = 'F'))
    # [[0 2 4]    # order = 'F'
    #  [1 3 5]]
```

## 代入

#### 条件を指定して値を代入
```python
a[0] = numpy.arange(32, dtype = float).reshape(4, -1)
a[0][a[0] % 3 == 1] = numpy.nan
```

## 抽出、削除、結合、転置

#### ベクトルを結合
```python
print(a[0])    # [12 18 18 13 15]
print(a[1])    # [46 48 43 45]
print(numpy.append(a[0], a[1]))    # [12 18 18 13 15 46 48 43 45]
print(numpy.concatenate((a[0], a[1]), axis = 0))    # [12 18 18 13 15 46 48 43 45]
```

#### 行や列を抽出
```python
a[0] = numpy.arange(15).reshape(3, 5)    # 単純に3行5列の2次元配列で考えてみる
print(a[0])
print(a[0][[0, 2], :])    # 0から数えて0行目と2行目を取り出す; コロン:は省略可能
print(a[0][:, [2, 4]])    # 0から数えて2列目と4列目を取り出す; コロン:は省略不可
```

#### 行や列を削除
```python
print(a[0].shape)   # (3, 5)
print(a[0])    # 非破壊的なので以下の処理は元の3行5列のaに対して行われる
print(numpy.delete(a[0], 1, 0))    # 0から数えて1行目の行を削除, 第3引数の0で行を指定
print(numpy.delete(a[0], 3, 1))    # 0から数えて3列目を削除, 第3引数の1で列を指定
print(numpy.delete(a[0], [1, 4], 1))    # リスト指定で複数の行や列を削除することも可能
```

#### 転置
```python
print(a[0].shape)   # (82, 59)
print(a[0].T.shape)    # (59, 82)
```

#### axisではshapeのインデックスに相当する整数値を指定
```python
print(a)
# [[ 2  5 18  0]
#  [-1  3 -2  9]
#  [10 15 12  8]]
print(a.shape)    # (3, 4) # 縦方向に3行、横方向に4列
print(numpy.max(a, axis = 0))
# [10 15 18  9]
print(numpy.max(a, axis = 1))
# [18  9 15]
```
2次元配列ならば、0は縦方向、1は横方向。
3次元配列ならば、0は奥行き方向、1は縦方向、2は横方向。

### 配列を結合
```python
a[0] = numpy.array((1.3, 2.5, 0.8))
a[1] = numpy.array((0.9, 3.7, 2.8, 1.6))
a[2] = numpy.append(a[1], a[0])
print(a[2])    # [0.9 3.7 2.8 1.6 1.3 2.5 0.8]

training_data = numpy.concatenate((train_1, train_2, train_3), axis = 0)
```
結合する配列をタプルまたはリストで与え、結合させる方向の軸を0から始まる数値で与える。
例えば上の例で (40000, 28, 28, 1) の配列を3つ結合させると (120000, 28, 28, 1) の配列が出力される。

### 配列を縦方向に結合
```python
a0 = numpy.full((2, 6), 8, dtype = 'int8')    # 結合する配列の作成
a1 = numpy.full((2, 6), 5, dtype = 'int8')
a2 = numpy.full((2, 6), 9, dtype = 'int8')
print(numpy.vstack((a0, a1, a2)))    # 縦方向に結合 vstack()
# [[8 8 8 8 8 8]
#  [8 8 8 8 8 8]
#  [5 5 5 5 5 5]
#  [5 5 5 5 5 5]
#  [9 9 9 9 9 9]
#  [9 9 9 9 9 9]]
```

### 配列を深さ方向に結合
```python
print(a.shape, b.shape, c.shape)    # (2, 4) (2, 4) (2, 4)
d = numpy.dstack((a, b, c))
print(d.shape)    # (2, 4, 3)  行列が3階テンソルに展開されて深さ方向(axis = 2)に結合
```

### 行や列などの並び替え
```python
a[0] = numpy.random.randint(-8, 8, (3, 5))
print(a[0])
# [[-4  6 -8  3  6]
#  [-5 -8 -6 -1 -5]
#  [ 4 -8  2  1  1]]
a[0] = a[0][(0, 2, 1), :]
print(a[0])
# [[-4  6 -8  3  6]
#  [ 4 -8  2  1  1]
#  [-5 -8 -6 -1 -5]]
print(a[0][:, (4, 3, 2, 0, 1)])
# [[ 6  3 -8 -4  6]
#  [ 1  1  2  4 -8]
#  [-5 -1 -6 -5 -8]]
print(a[0])
# [[-4  6 -8  3  6]
#  [ 4 -8  2  1  1]
#  [-5 -8 -6 -1 -5]]
```
インデックスをタプルやリストを与える

## 表示設定

#### print()の表示設定
```python
numpy.set_printoptions(threshold = numpy.inf)    # 制限なく全て表示
numpy.set_printoptions(threshold = 2048)    # 要素数が2048を超えた場合は省略して表示
numpy.set_printoptions(linewidth = 128)    # 1行に表示する文字数
numpy.set_printoptions(suppress = True)    # 指数表記を抑制
numpy.set_printoptions(precision = 4)    # 小数点以下4桁まで e.g. 0.5822
```

## 数学

#### 円周率と自然対数の底
```python
pi = numpy.pi    # 3.141592653589793
e = numpy.e    # 2.718281828459045
```
#### 関数
```python
y = numpy.log(x)    # 自然対数
y = numpy.log10(x)    # 常用対数
y = numpy.log2(x)    # 底2
y = numpy.log1p(x)    # 1を足してからlog()
y = numpy.expm1(x)    # exp()してから1を引く
```

## 非数と無限大

#### 非数
```python
a[0] = numpy.nan
print(a[0])    # nan
print(type(a[0]))    # float
print(numpy.isnan(numpy.pi), numpy.isnan(a[0] + numpy.pi))    # False True
```
#### 無限大
```python
a[0], a[1] = numpy.inf, numpy.NINF
print(a[0], a[1])    # inf, -inf
print(type(a[0]), type(a[1]))    # float, float
```

## 統計

#### 乱数を出力
```python
a[0] = numpy.random.rand(2000)    # 0以上1未満の乱数を2000
a[0] = numpy.random.rand(3, 7, 2)       # (3, 7, 2)のテンソルで出力
a[0] = numpy.random.randn(1000)    # 標準正規分布の乱数を1000
a[0] = numpy.random.randn(3, 7, 2)
a[0] = numpy.random.randint(19)    # 0以上19未満の整数を1つ
a[0] = numpy.random.randint(14, 19)    # 14以上19未満の整数を1つ
a[0] = numpy.random.randint(14, 19, (3, 5)))    # (3, 5)の行列で出力
a[0] = numpy.random.choice(100, 8)    # 0から100未満の整数から8つを出力
a[0] = numpy.random.choice(numpy.arange(100, 200), 8)    # 100から200未満の整数から8つを出力
a[0] = numpy.random.choice(numpy.arange(100, 200), 8, replace = False)    # 重複出力なし
```
random_sample(), random(), ranf(), sample() もあるが特に利用する必要はない。

#### シャッフル
```python
print(numpy.random.permutation(10))    # [2 8 0 1 6 9 7 5 3 4]
a[0] = numpy.arange(10, 20)
print(numpy.random.permutation(a[0]))    # [19 10 18 11 15 16 14 17 12 13] シャッフル
print(a[0])    # [10 11 12 13 14 15 16 17 18 19]　非破壊的
print(numpy.random.shuffle(a[0]))    # 戻り値は None
print(a[0])    # [16 14 18 12 15 19 10 13 11 17] 破壊的シャッフル
```

#### ソート
```python
a[0] = numpy.random.randint(0, 10, (7,), dtype = int)
sorted = numpy.sort(a[0])
argsorted = numpy.argsort(a[0])
print(a[0])    # [3 4 5 8 9 3 0]
print(sorted)    # [0 3 3 4 5 8 9]
print(argsorted)    # [6 0 5 1 2 3 4]
```
argsort()は、並び換えられる元の配列のindexを要素とする配列を昇順でのみ返す。a[0].argsort()のようにメソッドとしても使える。

#### Ellipsisによる深いコピー
```python
a[0] = numpy.ones((4, 5, 8, 1), dtype = int)
a[0][...] = 3    # a[0][:, :, :, :] = 3 全ての要素に3を代入
```

#### 基本的な統計量
```python
a[0] = numpy.random.randint(11, 20, (4, 7))
print(a[0])
# [[11 14 18 12 16 17 18]
#  [18 15 17 17 19 18 15]
#  [19 14 19 14 14 17 15]
#  [11 18 11 15 18 12 14]]
print(a[0].mean())    # 全体の平均
print(a[0].var())    # 分散, var(ddof = 1)で普遍分散
print(a[0].std())    # 標準偏差
print(a[0].max())
print(a[0].min())
print(a[0].mean(axis = 0))    # 縦方向の総和を行数で割るので、列の数だけの平均値が出力
print(a[0].mean(axis = 1))    # 横方向の総和を列数で割るので、行の数だけの平均値が出力
print(numpy.median(a[0]))    # 全体の中央値
```

#### 最小値と最大値が格納されている最初の添字
```python
a[0] = numpy.array((1, 7, 6, 3, 0, 2, 5, 4, 7, 0), dtype = int)
print(numpy.argmin(a[0]))    # 4
print(numpy.argmax(a[0]))    # 1
```

#### 最小値と最大値を指定して外れている値を置換
```python
a[0] = numpy.array((-0.2, 0.0, 0.3, 0.5, 0.8, 0.9, 1.1, 1.2))
a[1] = a[0].clip(0.0, 1.0)    # [0.  0.  0.3 0.5 0.8 0.9 1.  1. ]
a[2] = numpy.clip(a[0], 0.0, 1.0)    # [0.  0.  0.3 0.5 0.8 0.9 1.  1. ]
a[3] = a[0].clip(0.0, None)    # [0.  0.  0.3 0.5 0.8 0.9 1.1 1.2]
```

## NumPy配列の軸

#### 配列の軸の移動
```python
a[0] = numpy.empty((3, 23, 85))
print(numpy.moveaxis(a[0], 0, 2).shape)    # (23, 85, 3)
print(a[0].transpose(1, 2, 0).shape)    # (23, 85, 3)
```
0次元目の軸を2次元目の軸へ移動。channel firstからchannel lastにする場合など。

#### 次元を追加
```python
a[0] = numpy.empty((3, 6, 2, 7))
print(a[0].shape)    # (3, 6, 2, 7)
a[0] = a[0][None, ...]
print(a[0].shape)    # (1, 3, 6, 2, 7)
a[0] = a[0][..., numpy.newaxis]
print(a[0].shape)    # (1, 3, 6, 2, 7, 1)
```
None または numpy.newaxis で新しい次元をサイズ1で追加、ミニバッチ処理に合わせるなど

#### チャネル間のデータの入れ替え
```python
a[0] = a[0][..., [2, 1, 0]]
```
BGRをRGBにする場合など

## NumPy配列の計算

#### 要素ごとの計算
```python
a[0] = numpy.array((2, -3, 0, 9, -4))
a[1] = numpy.array((5, 2, 8, 0, -2))
print(a[0] * a[1])    # [10 -6  0  0  8]
```

#### 合計値を計算
```python
print(a[0].sum(axis = 0))    # 縦方向なので列ごとの合計値, この書き方はpandasでも同様
print(a[0].sum(axis = 1))    # 横方向なので行ごとの合計値, この書き方はpandasでも同様
```

#### 累積和を取得
```python
list1 = [5, 4, 3]
list2 = [7, 8, 9, 10]
list3 = list1 + list2    # [5, 4, 3, 7, 8, 9, 10]
print(numpy.cumsum(list3))    # [ 5  9 12 19 27 36 46]
```

#### 行全体の加算
```python
a[0] = numpy.zeros((3, 5))    # 0で埋められた3行5列の行列
numpy.add.at(a[0], 1, numpy.full((5,), 4))    # 1行全てに4を足す
```

#### ベクトルの内積、行列の積
```python
a = numpy.random.randint(10, 99, (23, 15))
b = numpy.random.randint(10, 99, (15, 19))
print(numpy.dot(a, b).shape)    # (23, 19)
```

#### Pearson相関係数
```
x = numpy.array(( 26.7,  36.5,  47.5,  56.5,  62.5,  69.8))
y = numpy.array((501.3, 448.0, 350.0, 318.0, 276.0, 251.1))
cc = numpy.corrcoef(x, y)    # 引数2つの場合 (ccは2行2列)
a0 = numpy.empty((2, 6))
a0[0:] = x
a0[1:] = y
cc = numpy.corrcoef(a0)    # 引数が行列で1つの場合 (ccは2行2列)
```

#### 警告の例外処理
```python
# RuntimeWarning: invalid value encountered in true_divide
import warnings
a0 = numpy.full((1,), 8.0)
with warnings.catch_warnings():
  warnings.filterwarnings('error')
  for i in range(8, -8, -1):
    try:
      a1 = a0 / float(i)
      print(a0[0], '/', i, '=', a1[0])
    except RuntimeWarning:
      print(a0[0], '/', i, '=')
```

#### 要素の値の比較
```python
foo = numpy.array([[7, 9], [5, 1]])
bar = numpy.array([[7, 9], [5, 1]])
baz = numpy.array([[7, 6], [5, 1]])
print(foo == bar)
print(numpy.all(foo == bar))    # numpy.all() 全て一致していたら True
print(numpy.any(foo == bar))    # numpy.any() 1つでも一致していたら True
print(foo == baz)
print(numpy.all(foo == baz))
print(numpy.any(foo == baz))
```
```
[[ True  True]
 [ True  True]]
True
True
[[ True False]
 [ True  True]]
False
True
```

## 文字列

#### 文字列の扱い
```python
a = numpy.empty((3, 5), dtype = 'U7')    # 文字数は固定、この場合は7文字
a[2, 2] = 'qwertyuiop'    # 8文字目以降の'iop'は格納されない
print(type(a[2, 2]))    # <class 'numpy.str_'>
```

## BLAS

#### BLASの確認
```python
numpy.__config__.show()    # x86_64でcondaを利用していればIntel MKLのはず
```