Numpyのappend, insert, hstack, vstackを使ったデータ加工

データセット構築の際にNumpyの多次元配列(主に2次元)に識別子や別の配列の情報を付加するしたいときにどのような書き方をすればいいか忘れないための自分用メモ。

```python
import numpy as np
A=np.array([np.arange(0,6),np.arange(10,16),np.arange(20,26)])
B=np.array([100,200,300])
C=np.array([np.arange(1000,1004),np.arange(2000,2004),np.arange(3000,3004)])

print(A)
"""
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24]])
"""

print(B)
"""
array([100, 200, 300])
"""

print(C)
"""
array([[1000, 1001, 1002, 1003],
       [2000, 2001, 2002, 2003],
       [3000, 3001, 3002, 3003]])
"""
```

のような配列を元にする。BはAの行数と同じ要素数。AとCは同じ行数。

## 2次元配列の各行の配列に情報を付加

### 1変数を付加

```python
# 配列Aの各行の配列の先頭(index 0番目)に100を追加。
# axisがないと多次元配列が平坦化された結果が返る。
# axis=0にすると新たな行に追加になるが今回は使わない。
# axis=1にすると新たな列に追加。
np.insert(A, 0, 100, axis=1)
"""
array([[100,   0,   1,   2,   3,   4,   5],
       [100,  10,  11,  12,  13,  14,  15],
       [100,  20,  21,  22,  23,  24,  25]])
"""

# 末尾に-100を追加。インデックスはlen(A)ではなく列の長さのlen(A[0])。
np.insert(A, len(A[0]), -100, axis=1)
"""
array([[   0,    1,    2,    3,    4,    5, -100],
       [  10,   11,   12,   13,   14,   15, -100],
       [  20,   21,   22,   23,   24,   25, -100]])
"""

# インデックスに-1を指定しても期待した場所に挿入されないので注意。
np.insert(A, -1, -100, axis=1)
"""
array([[   0,    1,    2,    3,    4, -100,    5],
       [  10,   11,   12,   13,   14, -100,   15],
       [  20,   21,   22,   23,   24, -100,   25]])
"""
```

2番目のインデックスを変えると任意の場所に値を挿入できる。

appendやvstack, hstackはスカラー値の付加はできないので事前に行と同じ長さの配列に変換して渡す必要があるのと、先頭末尾のみで任意のインデックスには挿入できない。

```py
np.append(A, np.tile(np.array(50), (len(A),1)), axis=1)
np.hstack([A, np.tile(np.array(50), (len(A),1))])
"""
array([[ 0,  1,  2,  3,  4,  5, 50],
       [10, 11, 12, 13, 14, 15, 50],
       [20, 21, 22, 23, 24, 25, 50]])
"""

# このとき行方向の長さを揃えた配列を作る
np.tile(np.array(50), (len(A),1))
array([[50],
       [50],
       [50]])
```

### 1次元配列の各要素を付加

2次元配列の行数と付加したい1次元配列の長さは同じ必要がある。

```python
len(A)==len(B)
"""
True
"""
```

```python
# 先頭に
np.insert(A, 0, B, axis=1)
"""
array([[100,   0,   1,   2,   3,   4,   5],
       [200,  10,  11,  12,  13,  14,  15],
       [300,  20,  21,  22,  23,  24,  25]])
"""

# 末尾に
np.insert(A, len(A[0]), B, axis=1)
"""
array([[  0,   1,   2,   3,   4,   5, 100],
       [ 10,  11,  12,  13,  14,  15, 200],
       [ 20,  21,  22,  23,  24,  25, 300]])
"""
```

2番目のインデックスを変えると任意の場所に1次元配列を挿入できる。
1次元配列を先頭や末尾に追加するだけならappendやhstackでも同じことができるがshapeを合わせる必要がある。

```python
# appendやhstackを使う場合はこのままでは次元数が違うのでエラーになる
B=np.array([100,200,300])

# 行方向に長さを揃えたこの形を渡さないといけない。
# 1次元配列には転置を使ったnp.tanspose(B)で行列入れ替えができないのでreshapeを使う。
B.reshape(-1,1)
"""
array([[100],
       [200],
       [300]])
"""

# 先頭に追加
np.append(B.reshape(-1,1), A, axis=1)
np.hstack([B.reshape(-1,1), A])

# 末尾に追加
np.append(A, B.reshape(-1,1), axis=1)
np.hstack([A, B.reshape(-1,1)])
```

### 1次元配列を付加

```python
# insertを使う場合は付加する1次元配列の行列を入れ替えて渡す。
np.insert(A, 2, B.reshape(-1,1), axis=1)
"""
array([[  0,   1, 100, 200, 300,   2,   3,   4,   5],
       [ 10,  11, 100, 200, 300,  12,  13,  14,  15],
       [ 20,  21, 100, 200, 300,  22,  23,  24,  25]])
"""

# 上記の方法で事足りるが一行ずつ一次元配列を付加することもできる。
np.vstack([np.insert(A[ix], 2, B) for ix in range(len(A))])
"""
array([[  0,   1, 100, 200, 300,   2,   3,   4,   5],
       [ 10,  11, 100, 200, 300,  12,  13,  14,  15],
       [ 20,  21, 100, 200, 300,  22,  23,  24,  25]])
"""

# np.vstackがないと次のようにリスト内で独立したNumpy配列ができるため行方向の結合が最後に必要。
np.insert(A[ix], 2, B) for ix in range(len(A))]
"""
[array([  0,   1, 100, 200, 300,   2,   3,   4,   5]),
 array([ 10,  11, 100, 200, 300,  12,  13,  14,  15]),
 array([ 20,  21, 100, 200, 300,  22,  23,  24,  25])]
"""
```

先頭,末尾に追加する際はappendやhstackも使おうと思えば使えるが1次元配列BをAの行数分複製したものを渡す必要がある。

```python
np.tile(B, (len(A),1)) # このように複製して渡す
"""
array([[100, 200, 300],
       [100, 200, 300],
       [100, 200, 300]])
"""

np.append(np.tile(B,(len(A),1)), A, axis=1)
"""
array([[100, 200, 300,   0,   1,   2,   3,   4,   5],
       [100, 200, 300,  10,  11,  12,  13,  14,  15],
       [100, 200, 300,  20,  21,  22,  23,  24,  25]])
"""

np.hstack([A, np.tile(B,(len(A),1))])
"""
array([[  0,   1,   2,   3,   4,   5, 100, 200, 300],
       [ 10,  11,  12,  13,  14,  15, 100, 200, 300],
       [ 20,  21,  22,  23,  24,  25, 100, 200, 300]])
"""
```

または、一行ずつ渡す。

```
np.vstack([np.append(B,A[ix]) for ix in range(len(A))])
np.vstack([np.hstack([A[ix], B]) for ix in range(len(A))])
```

余談だが`np.tile`を使って複製しているところは`np.repeat`を使った書き方もできる。1次元配列を指定した場合は配列内の各要素が繰り返されるので1行の2次元配列に変換して中の1次元配列を1要素とみなして繰り返させる。

```python
B[None,:].repeat(len(A),axis=0)
np.array([B]).repeat(len(A),axis=0)
"""
array([[100, 200, 300],
       [100, 200, 300],
       [100, 200, 300]])
"""
```

## 2次元配列に2次元配列を付加

付加したい2次元配列の長さは同じ必要がある。

```python
len(A)==len(C)
"""
True
"""
```

今までは挿入するindexにスカラー値入れていたが、`[]`で囲った値を指定する。

```python
np.insert(A, [2], C, axis=1)
"""
array([[   0,    1, 1000, 1001, 1002, 1003,    2,    3,    4,    5],
       [  10,   11, 1004, 1005, 1006, 1007,   12,   13,   14,   15],
       [  20,   21, 1008, 1009, 1010, 1011,   22,   23,   24,   25]])
"""

np.insert(A, [len(A[0])], C, axis=1)
"""
array([[   0,    1,    2,    3,    4,    5, 1000, 1001, 1002, 1003],
       [  10,   11,   12,   13,   14,   15, 1004, 1005, 1006, 1007],
       [  20,   21,   22,   23,   24,   25, 1008, 1009, 1010, 1011]])
"""

# 一行ずつ入れて最後に縦方向に結合することもできる。
np.vstack([np.insert(A[ix], 5, C[ix]) for ix in range(len(A))])
"""
array([[   0,    1,    2,    3,    4, 1000, 1001, 1002, 1003,    5],
       [  10,   11,   12,   13,   14, 1004, 1005, 1006, 1007,   15],
       [  20,   21,   22,   23,   24, 1008, 1009, 1010, 1011,   25]])
"""
```

先頭、末尾でいい場合は次のものが使える。

```
np.append(A,C,axis=1)
np.hstack([A,C])
np.block([A,C])
```

## 2次元配列を複製

### 全体を繰り返すようにN個のブロックを複製

```python
N=3
np.vstack([A]*N)
"""
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25]])
"""

# データ加工と組み合わせながら複製するにはこの形を元にしたほうが便利
np.vstack([A for ix in range(N)])
```

### 1次元配列が連続するようにN個のブロックを複製

```python
N=3
A.repeat(N,axis=0)
"""
array([[ 0,  1,  2,  3,  4,  5],
       [ 0,  1,  2,  3,  4,  5],
       [ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [10, 11, 12, 13, 14, 15],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [20, 21, 22, 23, 24, 25],
       [20, 21, 22, 23, 24, 25]])
"""

# データ加工と組み合わせながら複製するにはこの形を元にしたほうが便利。
# repeatに渡す1次元配列は`[]`で囲って1行の2次元配列に変換する必要があるので注意。
np.vstack([np.array([A[ix]]).repeat(N,axis=0) for ix in range(N)])
```


## 2次元配列を複製しつつ各ブロックを識別する情報を付加

### N個のブロックを複製しつつ各ブロックの配列の先頭にブロック番号をつける

先頭にブロック番号をつける。

```python
N=3 # ブロック数
np.vstack([np.insert(A, 0, ix, axis=1) for ix in range(N)])
"""
array([[ 0,  0,  1,  2,  3,  4,  5],
       [ 0, 10, 11, 12, 13, 14, 15],
       [ 0, 20, 21, 22, 23, 24, 25],
       [ 1,  0,  1,  2,  3,  4,  5],
       [ 1, 10, 11, 12, 13, 14, 15],
       [ 1, 20, 21, 22, 23, 24, 25],
       [ 2,  0,  1,  2,  3,  4,  5],
       [ 2, 10, 11, 12, 13, 14, 15],
       [ 2, 20, 21, 22, 23, 24, 25]])
"""
```

先頭にブロックに対応した一次元配列Bの要素を入れる。

```python
# ブロック数は配列Bの長さ
np.vstack([np.insert(A, 0, ix, axis=1) for ix in B])
"""
array([[100,   0,   1,   2,   3,   4,   5],
       [100,  10,  11,  12,  13,  14,  15],
       [100,  20,  21,  22,  23,  24,  25],
       [200,   0,   1,   2,   3,   4,   5],
       [200,  10,  11,  12,  13,  14,  15],
       [200,  20,  21,  22,  23,  24,  25],
       [300,   0,   1,   2,   3,   4,   5],
       [300,  10,  11,  12,  13,  14,  15],
       [300,  20,  21,  22,  23,  24,  25]])
"""
```

## N個のブロックに複製された2次元配列に要素を付加

すでにいろいろ加工してブロックができた状態の2次元配列を想定。

### ブロック番号に対応する配列Bの要素を先頭に付加

```python
N=3 # ブロック数
D=np.vstack([np.insert(A, 0, ix, axis=1) for ix in range(N)])
"""
array([[ 0,  0,  1,  2,  3,  4,  5],
       [ 0, 10, 11, 12, 13, 14, 15],
       [ 0, 20, 21, 22, 23, 24, 25],
       [ 1,  0,  1,  2,  3,  4,  5],
       [ 1, 10, 11, 12, 13, 14, 15],
       [ 1, 20, 21, 22, 23, 24, 25],
       [ 2,  0,  1,  2,  3,  4,  5],
       [ 2, 10, 11, 12, 13, 14, 15],
       [ 2, 20, 21, 22, 23, 24, 25]])
"""

Nb = len(D)//N # 各ブロックの長さ(Aの長さ)

# ix番目のブロックに対してB[ix]の値を先頭に入れる。BはNbより長ければエラーにならない。
np.vstack([np.insert(D[ix*Nb:(ix+1)*Nb], 0, B[ix], axis=1) for ix in range(Nb)])
"""
array([[100,   0,   0,   1,   2,   3,   4,   5],
       [100,   0,  10,  11,  12,  13,  14,  15],
       [100,   0,  20,  21,  22,  23,  24,  25],
       [200,   1,   0,   1,   2,   3,   4,   5],
       [200,   1,  10,  11,  12,  13,  14,  15],
       [200,   1,  20,  21,  22,  23,  24,  25],
       [300,   2,   0,   1,   2,   3,   4,   5],
       [300,   2,  10,  11,  12,  13,  14,  15],
       [300,   2,  20,  21,  22,  23,  24,  25]])
"""
```

### ブロックの各要素に対して配列Bの要素を先頭に付加

上の場合は `B[ix]` を指定したが、この場合は `B` を指定する。

```python
N=3 # ブロック数
D=np.vstack([np.insert(A, 0, ix, axis=1) for ix in range(N)])
Nb = len(D)//N # 各ブロックの長さ(Aの長さ)

# ix番目のブロックに対して配列Bを行方向に展開して先頭に入れる。NbとBの長さは同じでないとエラーになる。
np.vstack([np.insert(D[ix*Nb:(ix+1)*Nb], 0, B, axis=1) for ix in range(Nb)])
"""
array([[100,   0,   0,   1,   2,   3,   4,   5],
       [200,   0,  10,  11,  12,  13,  14,  15],
       [300,   0,  20,  21,  22,  23,  24,  25],
       [100,   1,   0,   1,   2,   3,   4,   5],
       [200,   1,  10,  11,  12,  13,  14,  15],
       [300,   1,  20,  21,  22,  23,  24,  25],
       [100,   2,   0,   1,   2,   3,   4,   5],
       [200,   2,  10,  11,  12,  13,  14,  15],
       [300,   2,  20,  21,  22,  23,  24,  25]])
"""
```

## N個のブロックに複製された2次元配列に配列を付加

すでにいろいろ加工してブロックができた状態の2次元配列を想定。
次の2次元配列の中の1次元配列を付加することを考える。

### ブロック番号に対応する配列Cの1次元配列要素を末尾に付加

```python
N=3 # ブロック数
D=np.vstack([np.insert(A, 0, ix, axis=1) for ix in range(N)])
Nb = len(D)//N # 各ブロックの長さ(Aの長さ)

# これだと1次元配列内の1要素ずつを付加しようとして、この場合はエラーになる。
np.vstack([np.insert(D[ix*Nb:(ix+1)*Nb], len(D[ix]), C[ix], axis=1) for ix in range(Nb)])

# C[ix]のshapeを指定して`1次元配列を付加`と同じことをしてあげる必要がある。
np.vstack([np.insert(D[ix*Nb:(ix+1)*Nb], len(D[ix]), C[ix].reshape(-1,1), axis=1) for ix in range(Nb)])
array([[   0,    0,    1,    2,    3,    4,    5, 1000, 1001, 1002, 1003],
       [   0,   10,   11,   12,   13,   14,   15, 1000, 1001, 1002, 1003],
       [   0,   20,   21,   22,   23,   24,   25, 1000, 1001, 1002, 1003],
       [   1,    0,    1,    2,    3,    4,    5, 1004, 1005, 1006, 1007],
       [   1,   10,   11,   12,   13,   14,   15, 1004, 1005, 1006, 1007],
       [   1,   20,   21,   22,   23,   24,   25, 1004, 1005, 1006, 1007],
       [   2,    0,    1,    2,    3,    4,    5, 1008, 1009, 1010, 1011],
       [   2,   10,   11,   12,   13,   14,   15, 1008, 1009, 1010, 1011],
       [   2,   20,   21,   22,   23,   24,   25, 1008, 1009, 1010, 1011]])
```

先頭か末尾でいいならこれでもいいがあまりわかりやすくはない。

```
np.vstack([np.hstack([D[ix*Nb:(ix+1)*Nb],np.tile(C[ix],(Nb,1))]) for ix in range(Nb)])
```

### ブロックの各要素に対して配列Cの1次元配列を末尾に付加

```python
N=3 # ブロック数
D=np.vstack([np.insert(A, 0, ix, axis=1) for ix in range(N)])
Nb = len(D)//N # 各ブロックの長さ(Aの長さ)

# ix番目のブロックに対して配列Cを末尾に入れる。
# 普通にCを指定してしたら想定の配列を転置したCを入れようとしてサイズ周りのエラーになる。
np.vstack([np.insert(D[ix*Nb:(ix+1)*Nb], len(D[ix]), C, axis=1) for ix in range(Nb)])

# なのでCは転置させて入れる。NbとCの長さは同じでないとエラーになる。
np.vstack([np.insert(D[ix*Nb:(ix+1)*Nb], len(D[ix]), C.T, axis=1) for ix in range(Nb)])
"""
array([[   0,    0,    1,    2,    3,    4,    5, 1000, 1001, 1002, 1003],
       [   0,   10,   11,   12,   13,   14,   15, 1004, 1005, 1006, 1007],
       [   0,   20,   21,   22,   23,   24,   25, 1008, 1009, 1010, 1011],
       [   1,    0,    1,    2,    3,    4,    5, 1000, 1001, 1002, 1003],
       [   1,   10,   11,   12,   13,   14,   15, 1004, 1005, 1006, 1007],
       [   1,   20,   21,   22,   23,   24,   25, 1008, 1009, 1010, 1011],
       [   2,    0,    1,    2,    3,    4,    5, 1000, 1001, 1002, 1003],
       [   2,   10,   11,   12,   13,   14,   15, 1004, 1005, 1006, 1007],
       [   2,   20,   21,   22,   23,   24,   25, 1008, 1009, 1010, 1011]])
"""
```

先頭か末尾でいいならこれでも可。こちらのほうがわかりやすい。

```
np.vstack([np.block( [D[ix*Nb:(ix+1)*Nb], C] ) for ix in range(Nb)])
```