PythonのnetCDF4読み込み速度とfor文のネスト

気象データを長期間に渡って異なる気圧面でプロットしようとすると、for文によるネストやnetCDFファイルの読み込みが自然と多くなる。

そこでfor文のネストの構造を変えることで、データの配列を得るのにかかる時間がどのように変わるのかを調べてみた。

# 検証コード1
　検証コードは[こちら](https://note.nkmk.me/python-timeit-measure/)を参考にした。
　データは[NCAR's RDA](https://rda.ucar.edu/)から得た。データにはジオポテンシャルで、[時間, 気圧面, 緯度, 経度]の4次元配列である。なお、時間は0~23の24時間、気圧面は37となっている。

　`func_1`はデータを1回読み込み、3次元配列をaに格納している。その後、各気圧面におけるジオポテンシャルをbに代入している。
　`func_2`は各気圧面ごとにデータを読み込み、2次元配列をその都度得ている。

```check1.py
import timeit
from netCDF4 import Dataset

def func_1():
    nc = Dataset('../data/era5/Z_2020070100_2020070123.nc', 'r')
    a = nc.variables['Z'][0, :]
    for i in range(len(a)):
        b = a[i, :]
    return 0

def func_2():
    nc = Dataset('../data/era5/Z_2020070100_2020070123.nc', 'r')
    lev = nc.variables['level'][:]
    for i in range(len(lev)):
        b = nc.variables['Z'][0, i, :]
    return 0

loop = 20

result_1 = timeit.timeit(lambda: func_1(), number=loop)
result_2 = timeit.timeit(lambda: func_2(), number=loop)

print('1: ', result_1 / loop)
print('2: ', result_2 / loop)
```
　結果は

```
1:  0.009774951753206551
2:  0.018051519710570573
```
のようになった。なんと2倍もの速度差がある。
　これより、3次元配列を1度に読み込む速度と2次元配列をその都度読み込む速度では、3次元配列を読み込む速度の方が優位であることが分かった。


# 検証コード2
　扱いたいのデータは4次元配列なので4次元配列の場合も調べる。`func_1`と`func_2`は検証コード1同様、`func_1`が読み込み1回、`func_2`がその都度読み込みとする。

```check1.py
import timeit
from netCDF4 import Dataset

def func_1():
    nc = Dataset('../data/era5/Z_2020070100_2020070123.nc', 'r')
    a = nc.variables['Z'][0, :]
    for i in range(len(a)):
        b = a[i, :]
    return 0

def func_2():
    nc = Dataset('../data/era5/Z_2020070100_2020070123.nc', 'r')
    lev = nc.variables['level'][:]
    for i in range(len(lev)):
        b = nc.variables['Z'][0, i, :]
    return 0

loop = 10

result_1 = timeit.timeit(lambda: func_1(), number=loop)
result_2 = timeit.timeit(lambda: func_2(), number=loop)

print('1: ', result_1 / loop)
print('2: ', result_2 / loop)
```

　すると、結果は以下のようになった。

```
1:  1.4546271565370261
2:  1.3412013622000813
```
なんと、3次元の時は速かった一気に配列を読み込む方法が、4次元の時はその都度読み込む方法よりも遅いことが分かった。配列も多次元になるとfor文以上に処理速度が遅くなってしまうということであろうか。興味深い結果である。
　またこれから、その二つのいいとこ取りをすればもっとも速くデータを読み込むことのできるプログラムになるのではと考え、次のコードの速度比較をした。

# 検証コード3
　新たに`func_3`を作成し速度を比較した。`func_3`は時間はfor文で回し、その時間ごとの3次元配列[気圧面, 緯度, 経度]を読み込む関数である。

```check3.py
from netCDF4 import Dataset
import timeit

def func_1():
    nc = Dataset('../data/era5/Z_2020070100_2020070123.nc', 'r')
    a = nc.variables['Z'][:]
    for i in range(len(a)):
        b = a[i, :]
        for j in range(len(b)):
            c = b[j, :]
    return 0

def func_2():
    nc = Dataset('../data/era5/Z_2020070100_2020070123.nc', 'r')
    time = nc.variables['time'][:]
    lev = nc.variables['level'][:]
    for j in range(len(time)):
        for i in range(len(lev)):
            b = nc.variables['Z'][j, i, :]
    return 0

def func_3():
    nc = Dataset('../data/era5/Z_2020070100_2020070123.nc', 'r')
    time = nc.variables['time'][:]
    for j in range(len(time)):
        a = nc.variables['Z'][j, :]
        for i in range(len(a)):
            b = a[i, :]
    return 0


loop = 10

result_1 = timeit.timeit(lambda: func_1(), number=loop)
result_2 = timeit.timeit(lambda: func_2(), number=loop)
result_3 = timeit.timeit(lambda: func_3(), number=loop)

print('1: ', result_1 / loop)
print('2: ', result_2 / loop)
print('3: ', result_3 / loop)
```
　結果は以下の通り。

```
1:  1.4101094176992774
2:  1.344068780587986
3:  1.0753227178938687
```
見立てどおり、`func_3`が最速となった。
# 結論
3つの検証により分かったことは、

- 3次元配列[気圧面, 緯度, 経度]まではまとめて読み込んだ方が速く、
- 4次元配列[時間, 気圧面, 緯度, 経度]はfor文で回して3次元配列として扱った方が速い

ということである。
　気象データの可視化は時間のかかる作業である。この検証が時間の節約に役立てば幸いである。