2次元の計算でよくある計算を numpy でスマートに書く

前回の続き
[numpy で2次元の行列計算と for とのパフォーマンス比較](https://qiita.com/kurab/items/fde10ef6ed90f765066c)

2次元の配列の計算をスライスの形で書くことには、割とすぐ慣れた。読むのも書くのも。
進めるうちに、その知識だけでは対応できない複雑な計算などもあり、うんうん悩んだが、2次元の計算であれば、だいたいこれくらいあれば大丈夫だろうというものをまとめた。
おそらく、これらの組み合わせでだいたいなんとかなるだろう。足らなかったら後で足す。

以下、for のネスト形式と numpy の slice 形式の対比で書くが、前回確認した通り、python で for をネストさせてはいけない。

## 全要素で計算
for のネスト形式:

```python
for i in range(x.shape[0]):
    for j in range(x.shape[1]):
        z[i, j] = x[i, j] + y[i, j]
```

numpy の slice 形式:

```python
z = x + y
```
slice する必要はないが、あえて書くならば、

```python
z[0:, 0:] = x[0:, 0:] + y[0:, 0:]
```


## 一部の要素で計算
for のネスト形式:

```python
for i in range(x.shape[0]-1):
    for j in range(1, x.shape[1]-1):
        z[i, j] = x[i, j] + y[i, j-1]
```

numpy の slice 形式:

```python
z[0:-1, 1:-1] = x[0:-1, 1:-1] + y[0:-1, 0:-2]
```

## 条件式が含まれる
for のネスト形式:

```python
for i in range(x.shape[0]-1):
    for j in range(1, x.shape[1]-1):
        z[i, j] = x[i, j] + y[i, j-1]
        if z[i, j] < 10:
            z[i, j] = 0
```

numpy の slice 形式:

```python
z[0:-1, 1:-1] = x[0:-1, 1:-1] + y[0:-1, 0:-2]
z = np.where(z < 10, 0, z)
```

この他に、```if i == 0:``` などの境界値での振る舞いを計算する条件式なども、度々登場するが、この場合、```np.where``` などを使う必要はなく、```z[0, 1:-1]```, ```z[x.shape[0]-1, 1:-1]``` などと書けば良い。

## 次元の異なる配列が計算式に含まれる
for のネスト形式:

```python
for i in range(x.shape[0]-1):
    for j in range(1, x.shape[1]-1):
        z[i, j] = x[i, j] + y[i, j-1] + h[i]
```

numpy の slice 形式:

```python
h2 = h.reshape(h.shape[0], 1)
z[0:-1, 1:-1] = x[0:-1, 1:-1] + y[0:-1, 0:-2] + h2[0:-1]
```
```h``` をそのまま足すことができないので、reshape が必要になる。
ここで登場する ```h``` は、このような使われ方しかしないのであれば、最初から ```(nnn,)``` ではなく ```(nnn, 1)```で初期化すべきだが、この場合値にアクセスするには、```h[i]``` ではなく ```h[i, 0]``` とする必要があるので、どちらが良いかは Case by case。```h``` が配列ではなく定数の場合は、そのまま ```+h``` とすれば良い。


## 確認用サンプルコード

```python
import numpy as np


def judge(z, z_sl):
    if (z == z_sl).all():
        print('yes')
    else:
        print('no')


def get_init_array():
    x = np.random.randint(10, size=(10, 5))
    y = np.random.randint(10, size=(10, 5))
    z = np.zeros((10, 5))
    z_sl = np.zeros_like(z)
    return [x, y, z, z_sl]


# ===== 全要素で計算 =====
x, y, z, z_sl = get_init_array()
# for nest
for i in range(x.shape[0]):
    for j in range(x.shape[1]):
        z[i, j] = x[i, j] + y[i, j]

# numpy
z_sl = x + y
# z_sl[0:, 0:] = x[0:, 0:] + y[0:, 0:]

judge(z, z_sl)

# ===== 一部の要素で計算 =====
x, y, z, z_sl = get_init_array()
# for nest
for i in range(0, x.shape[0] - 1):
    for j in range(1, x.shape[1] - 1):
        z[i, j] = x[i, j] + y[i, j-1]

# numpy
z_sl[0:-1, 1:-1] = x[0:-1, 1:-1] + y[0:-1, 0:-2]

judge(z, z_sl)

# ===== 条件式が含まれる =====
x, y, z, z_sl = get_init_array()
# for nest
for i in range(0, x.shape[0] - 1):
    for j in range(1, x.shape[1] - 1):
        z[i, j] = x[i, j] + y[i, j-1]
        if z[i, j] < 10:
            z[i, j] = 0

# numpy
z_sl[0:-1, 1:-1] = x[0:-1, 1:-1] + y[0:-1, 0:-2]
z_sl = np.where(z_sl < 10, 0, z_sl)

judge(z, z_sl)

# ===== 次元の異なる配列が計算式に含まれる =====
x, y, z, z_sl = get_init_array()
# for nest
q = np.random.randint(10, size=10)

for i in range(0, x.shape[0] - 1):
    for j in range(1, x.shape[1] - 1):
        z[i, j] = x[i, j] + y[i, j-1] + q[i]

# numpy
q_sl = q.reshape(q.shape[0], 1)
z_sl[0:-1, 1:-1] = x[0:-1, 1:-1] + y[0:-1, 0:-2] + q_sl[0:-1]

judge(z, z_sl)

```