Daskでmatplotlibを並列化

#はじめに
netcdfのデータを[xarray](http://xarray.pydata.org/en/stable/ "xarray")で開いて、matplotlibで図にするのを、[dask](https://dask.org/ "dask")で並列化(multiprocess)してみた。

データは、NCEP/NCAR reanalysisの地表面付近の毎月の温位("pottmp.sig995.mon.mean.nc")で、NOAAの[ESRL](https://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanalysis.derived.surface.html "ESRL")で入手した。

# データ
```python
import xarray as xr
ds=xr.open_dataset("pottmp.sig995.mon.mean.nc")
print(ds)
```

    <xarray.Dataset>
    Dimensions:  (lat: 73, lon: 144, time: 852)
    Coordinates:
      * lat      (lat) float32 90.0 87.5 85.0 82.5 80.0 ... -82.5 -85.0 -87.5 -90.0
      * lon      (lon) float32 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
      * time     (time) datetime64[ns] 1948-01-01 1948-02-01 ... 2018-12-01
    Data variables:
        pottmp   (time, lat, lon) float32 ...
    Attributes:
        Conventions:    COARDS
        description:    Data from NCEP initialized reanalysis (4x/day).  These ar...
        platform:       Model
        NCO:            20121012
        history:        Created 2011/06/27 by ESRL/PSD Web & Data Team\nConverted...
        title:          monthly mean pottmp.sig995 from the NCEP Reanalysis
        References:     http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reana...
        dataset_title:  NCEP-NCAR Reanalysis 1



```python
print(ds["pottmp"].max(),ds["pottmp"].min())
```

    <xarray.DataArray 'pottmp' ()>
    array(334.699951) <xarray.DataArray 'pottmp' ()>
    array(214.599854)



```python
ds.close()
```
1度データxarrayでデータを開いて見てみると、データは852か月分ある。最初のデータは最大約335K、最低は約215Kであり、コンターのレベルの参考にする。
# plot 


```python
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
```
図化にはmatplotlibを使い、緯度経度座標で図にするために[cartopy](https://scitools.org.uk/cartopy/docs/latest/ "cartopy")を使用する。

## test 1 (parallel)


```python
import dask
```


```python
@dask.delayed
def plot_month(n):
    ds=xr.open_dataset("pottmp.sig995.mon.mean.nc")
    pottmp=ds["pottmp"].isel(time=n)
    
    fig=plt.figure()
    proj=ccrs.PlateCarree()
    proj180=ccrs.PlateCarree(central_longitude=180)
    ax = fig.add_subplot(111, projection=proj180)
    ax.coastlines()
    cl=ax.contourf(pottmp.lon,pottmp.lat,pottmp,np.arange(200,350,20),transform=proj)
    ax.set_title(pottmp.time.data)
    fig.colorbar(cl,shrink=0.5)
    plt.tight_layout()
    fig.savefig("pottmp_{n}.png".format(n=n+1))
    plt.close()
    
    ds.close()
```


```python
%%time
_ = dask.compute(*[plot_month(n) for n in range(852)],scheduler="processes")
```

    CPU times: user 5.83 s, sys: 281 ms, total: 6.11 s
    Wall time: 3min 6s

Daskで852か月分の作図をmultiprocessにしたコード。実行時間は3分6秒。実行環境は手元の4コアのノートPC (Panasonic CF-NX2)。

例として最初の図はこのようになる。
![pottmp_1.png](https://qiita-image-store.s3.amazonaws.com/0/77672/f7feee8b-36ac-cd2c-86d6-fe82ff1dff36.png)
## test 2 (serial)

```python
def plot_month(n):
    ds=xr.open_dataset("pottmp.sig995.mon.mean.nc")
    pottmp=ds["pottmp"].isel(time=n)
    
    fig=plt.figure()
    proj=ccrs.PlateCarree()
    proj180=ccrs.PlateCarree(central_longitude=180)
    ax = fig.add_subplot(111, projection=proj180)
    ax.coastlines()
    cl=ax.contourf(pottmp.lon,pottmp.lat,pottmp,np.arange(200,350,20),transform=proj)
    ax.set_title(pottmp.time.data)
    fig.colorbar(cl,shrink=0.5)
    plt.tight_layout()
    fig.savefig("pottmp_{n}.png".format(n=n+1))
    plt.close()
    
    ds.close()
```


```python
%%time
_ = [plot_month(n) for n in range(852)]
```

    CPU times: user 4min 27s, sys: 28.4 s, total: 4min 55s
    Wall time: 4min 57s

同じコードを並列化しない場合の実行時間は4分57秒。4コアを使っても4倍になるわけではないけれども、並列化した場合の方が速い。

## test 3 (serial)


```python
ds=xr.open_dataset("pottmp.sig995.mon.mean.nc")
pottmp=ds["pottmp"]
```


```python
def plot_month(pottmp,n):
    pottmpn=pottmp.isel(time=n)
    
    fig=plt.figure()
    proj=ccrs.PlateCarree()
    proj180=ccrs.PlateCarree(central_longitude=180)
    ax = fig.add_subplot(111, projection=proj180)
    ax.coastlines()
    cl=ax.contourf(pottmpn.lon,pottmpn.lat,pottmpn,np.arange(200,350,20),transform=proj)
    ax.set_title(pottmpn.time.data)
    fig.colorbar(cl,shrink=0.5)
    plt.tight_layout()
    fig.savefig("pottmp_{n}.png".format(n=n+1))
    plt.close()
```


```python
%%time
_ = [plot_month(pottmp,n) for n in range(852)]
```

    CPU times: user 4min 19s, sys: 12.4 s, total: 4min 31s
    Wall time: 4min 32s

test1とtest2ではfucntionの中でいちいちnetcdfを開いているが、多分並列化の場合はその方が問題がなさそう。並列化しない場合、Test3のようにnetcdfを1度開いてからそこからデータを読みにいってもたいして速いわけではない。

# 注
matplotlibを並列化すると、図にする順番によってはフォントが乱れることがある。よくわからないがフォントのキャッシュによるもの？

参考
[Matplotlibをmultiprocessing.Poolで並列化する際の覚書](https://qiita.com/1007/items/83312122b769a504dc98) (Qiita)
[Matplotlib multiprocessing fonts corruption using savefig](https://stackoverflow.com/questions/35381035/matplotlib-multiprocessing-fonts-corruption-using-savefig) (stack overflow)