【Python】配列生成（初期化）時間まとめ！！！

#まとめ！！！
最初に結果をまとめておこう．いわゆる「時間のない人のための」というやつである．かなり見やすくまとまっていると自負している．

##1d配列

| Code                       | Mean    | Stdev.  | Runs   | Loops   |
|----------------------------|---------|---------|--------|---------|
| `np.empty(N)   `             | 1.76 µs | 32 ns   | 7 runs | 1000000 |
| `np.zeros(N)    `            | 11.3 ms | 453 µs  | 7 runs | 100     |
| np.ones(N)                 | 14.9 ms | 182 µs  | 7 runs | 100     |
| np.zeros_like(np.empty(N)) | 19.9 ms | 320 µs  | 7 runs | 100     |
| np.ones_like(np.empty(N))  | 20.8 ms | 931 µs  | 7 runs | 10      |
| [None] * N                 | 35.9 ms | 418 µs  | 7 runs | 10      |
| [0] * N                    | 35.2 ms | 489 µs  | 7 runs | 10      |
| [None for i in range(N)]   | 417 ms  | 77.7 ms | 7 runs | 1       |
| [0 for i in range(N)]      | 375 ms  | 15.1 ms | 7 runs | 1       |


##2d配列

| Code                                         | Mean    | Stdev.  | Runs   | Loops  |
|----------------------------------------------|---------|---------|--------|--------|
| `np.empty([M,M])     `                         | 3.58 µs | 157 ns  | 7 runs | 100000 |
| `np.zeros([M,M])        `                      | 3.66 µs | 51.5 ns | 7 runs | 100000 |
| [[None] * M] * M   → [追記1](#追記1)               | 37.4 µs | 1.08 µs | 7 runs | 10000  |
| [[0] * M] * M      → [追記1](#追記1)               | 37.4 µs | 388 ns  | 7 runs | 10000  |
| np.ones([M,M])                               | 378 ms  | 5.46 ms | 7 runs | 1      |
| np.zeros_like(np.empty([M,M]))               | 375 ms  | 3.22 ms | 7 runs | 1      |
| np.ones_like(np.empty([M,M]))                | 384 ms  | 7.62 ms | 7 runs | 1      |
| [[None for j in range(M)] for i in range(M)] | 3.83 s  | 37.5 ms | 7 runs | 1      |
| [[0 for j in range(M)] for i in range(M)]    | 3.86 s  | 61.6 ms | 7 runs | 1      |


さて，これらの詳細を見ていこう．

#0 準備

バージョンは Python 3.8.6．

型の確認．

```python
type(None), type(0)
(NoneType, int)
```

適切なパラメータを設定する．

```python
N = int(1e7)
M = int(1e4)
```

インポートも一応計測する．

```python
%%timeit
import numpy as np
#112 ns ± 0.579 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
```

これはナノ秒なので考慮しなくて良い（インポートする際は`%%timeit`を外す）．

#1 実行

##1d配列
```python
%timeit np.empty(N)
%timeit np.zeros(N)
%timeit np.ones(N)
%timeit np.zeros_like(np.empty(N))
%timeit np.ones_like(np.empty(N))
%timeit [None] * N
%timeit [0] * N
%timeit [None for i in range(N)]
%timeit [0 for i in range(N)]
'''
1.76 µs ± 32 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
11.3 ms ± 453 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
14.9 ms ± 182 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
19.9 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
20.8 ms ± 931 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
35.9 ms ± 418 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
35.2 ms ± 489 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
417 ms ± 77.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
375 ms ± 15.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
'''
```

`np.empty(N)`が激烈に速い．ただし何が出力されるかその時々で異なるので速度は一定しないことに注意．100 μs ほどになることもあるが，それでも速い．
また，0 と None は実行する順序を入れ替えたり`%%timeit`で独立のセルで実行したりすると，速度が逆転することがある．ということはどちらを使ってもほぼ同じ速度と見て差し支えない．

##2d配列

```python
%timeit np.empty([M,M])
%timeit np.zeros([M,M])
%timeit np.ones([M,M])
%timeit np.zeros_like(np.empty([M,M]))
%timeit np.ones_like(np.empty([M,M]))
%timeit [[None] * M] * M
%timeit [[0] * M] * M
%timeit [[None for j in range(M)] for i in range(M)]
%timeit [[0 for j in range(M)] for i in range(M)]
'''
3.58 µs ± 157 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
3.66 µs ± 51.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
378 ms ± 5.46 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
375 ms ± 3.22 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
384 ms ± 7.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
37.4 µs ± 1.08 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
37.4 µs ± 388 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
3.83 s ± 37.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
3.86 s ± 61.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
'''
```

やはり`np.empty([M,M])`が速いが`np.zeros([M,M])`も負けていない．意外に掛け算も善戦している（→ [追記1](#追記1)）．`for` 文は言わずもがな遅く，書き方の工夫もまだできるかもしれないが，初期化には使えない．

#2 結果（表にまとめる）
##1d配列

| Code                       | Mean    | Stdev.  | Runs   | Loops   |
|----------------------------|---------|---------|--------|---------|
| `np.empty(N)   `             | 1.76 µs | 32 ns   | 7 runs | 1000000 |
| `np.zeros(N)    `            | 11.3 ms | 453 µs  | 7 runs | 100     |
| np.ones(N)                 | 14.9 ms | 182 µs  | 7 runs | 100     |
| np.zeros_like(np.empty(N)) | 19.9 ms | 320 µs  | 7 runs | 100     |
| np.ones_like(np.empty(N))  | 20.8 ms | 931 µs  | 7 runs | 10      |
| [None] * N                 | 35.9 ms | 418 µs  | 7 runs | 10      |
| [0] * N                    | 35.2 ms | 489 µs  | 7 runs | 10      |
| [None for i in range(N)]   | 417 ms  | 77.7 ms | 7 runs | 1       |
| [0 for i in range(N)]      | 375 ms  | 15.1 ms | 7 runs | 1       |


##2d配列

| Code                                         | Mean    | Stdev.  | Runs   | Loops  |
|----------------------------------------------|---------|---------|--------|--------|
| `np.empty([M,M])     `                         | 3.58 µs | 157 ns  | 7 runs | 100000 |
| `np.zeros([M,M])        `                      | 3.66 µs | 51.5 ns | 7 runs | 100000 |
| [[None] * M] * M → [追記1](#追記1)                | 37.4 µs | 1.08 µs | 7 runs | 10000  |
| [[0] * M] * M    → [追記1](#追記1)               | 37.4 µs | 388 ns  | 7 runs | 10000  |
| np.ones([M,M])                               | 378 ms  | 5.46 ms | 7 runs | 1      |
| np.zeros_like(np.empty([M,M]))               | 375 ms  | 3.22 ms | 7 runs | 1      |
| np.ones_like(np.empty([M,M]))                | 384 ms  | 7.62 ms | 7 runs | 1      |
| [[None for j in range(M)] for i in range(M)] | 3.83 s  | 37.5 ms | 7 runs | 1      |
| [[0 for j in range(M)] for i in range(M)]    | 3.86 s  | 61.6 ms | 7 runs | 1      |


#3 結論

配列生成だけなら`np.empty()`，初期化するなら`np.zeros()`が速い．

#4 おまけ

`np.empty_like` も速い．

```python
%timeit np.empty_like(np.empty(N))
%timeit np.empty_like(np.empty([M,M]))
'''
3.24 µs ± 59.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
7.1 µs ± 108 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
'''
```

#追記1

`[[None] * M] * M ` , `[[0] * M] * M ` について指摘があったので追記．2d配列としてこのままでは適当でないので，内包表記で書き直す．


```python
M = int(1e4)
%timeit [None] * M 
%timeit [0] * M
%timeit [[None] * M] * M
%timeit [[0] * M] * M
%timeit [None] * M * M   #比較のためおいた
%timeit [0] * M * M    #比較のためおいた
%timeit [[None] * M for i in range(M)]  #for 内包表記
%timeit [[0] * M for i in range(M)]  #for 内包表記

'''
18.4 µs ± 217 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
18.4 µs ± 171 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
37.6 µs ± 1.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
37.3 µs ± 661 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
725 ms ± 13.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
709 ms ± 1.47 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
672 ms ± 3.39 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
671 ms ± 7.46 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
'''
```

内包表記にすると問題なく2次元配列が生成されるが，速くないので数字の計算をするだけならおそらく使うことはないだろう．NumPy を用いたほうが良いと思われる（少なくともPython 3.8.6の場合）．
これまた絶対に使うことはないと思うが，乗算の方を np.array にいれて実験してみた．

```python
x = np.array([[0] * 3] * 3)
print(x)
x[0][0] = 1
x
'''
[[0 0 0]
 [0 0 0]
 [0 0 0]]
出力:
array([[1, 0, 0],
       [0, 0, 0],
       [0, 0, 0]])
'''
```

（2次元にできているっぽい．np.matrix では無理だった．また sympy.Matrix でも2次元行列にできたが時間がかかりすぎる）

```python
%timeit [[None] * M for i in range(M)]
%timeit np.array([[None] * M] * M)

%timeit [[0] * M for i in range(M)]
%timeit np.array([[0] * M] * M)

'''
716 ms ± 21 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4.06 s ± 65 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
682 ms ± 13 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
6.29 s ± 67.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
'''
```

更に遅くなってしまったのでこの記事の趣旨からは外れてしまうが，比較検討としては興味深い結果がえられた．

#追記2

上で想定していたのは，大きさ1000×1000の行列を作る，といったサイズの大きな場合だった．
長さ2の初期化しなくて良い配列を生成したときにおや？と思ったので追記．短い配列なら [None]の乗算のほうが速い．

```python
value = np.empty(2)
%timeit -r 3 -n 10000 [None]*len(value)
%timeit -r 3 -n 10000 np.empty(len(value))
'''
221 ns ± 18.3 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)
1.04 µs ± 132 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)
'''
```

どこで入れ替わるのか気になる．調べてみた．

```python
value = np.empty(300)
%timeit -r 3 -n 10000 [None]*len(value)
%timeit -r 3 -n 10000 np.empty(len(value))
'''
1.01 µs ± 66.3 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)
1.06 µs ± 118 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)
'''
```

```python
value = np.empty(400)
%timeit -r 3 -n 10000 [None]*len(value)
%timeit -r 3 -n 10000 np.empty(len(value))
'''
1.27 µs ± 94.3 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)
956 ns ± 26.6 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)
'''
```

約300を境にして入れ替わるようだ．他の場合も含めて，N 依存性をグラフにしても面白いだろう．

#参考記事
[numpy.empty — NumPy v1.19 Manual](https://numpy.org/doc/stable/reference/generated/numpy.empty.html)
Notes: empty, unlike zeros, does not set the array values to zero, and may therefore be marginally faster. On the other hand, it requires the user to manually set all the values in the array, and should be used with caution.
とある．
[【NumPy入門 np.empty】要素を初期化せずに新しい配列を作る | 侍エンジニア塾ブログ（Samurai Blog） - プログラミング入門者向けサイト](https://www.sejuku.net/blog/70972)
[【NumPy入門】配列の生成方法（1次元、2次元、高速化など） | 西住工房](https://algorithm.joho.info/programming/python/numpy-array-list/)
[NumPyで全要素を同じ値で初期化した配列ndarrayを生成 | note.nkmk.me](https://note.nkmk.me/python-numpy-zeros-ones-full/)
[NumPyで空の配列ndarrayを生成するemptyとempty_like | note.nkmk.me](https://note.nkmk.me/python-numpy-empty-empty-like/)
[未初期化の配列を生成するnumpy.empty関数の使い方 - DeepAge](https://deepage.net/features/numpy-empty.html)