Creating nice maps with xarray

xarray basic plotting options are enough to make quick plots, but for your paper’s figures you probably want to have more options. Let’s look at what xarray can do! NB: For this to work, you need the version of xarray >= 0.16.0

[1]:
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')
[2]:
import xarray as xr
import cmocean

Load the sample dataset and geolon/geolat from static file (without holes on eliminated processors):

[4]:
dataurl = 'http://35.188.34.63:8080/thredds/dodsC/OM4p5/'

ds = xr.open_dataset(f'{dataurl}/ocean_monthly_z.200301-200712.nc4',
                     chunks={'time':1, 'z_l': 1}, drop_variables=['average_DT', 'average_T1', 'average_T2'], engine='pydap')
[5]:
grid = xr.open_dataset('./data/ocean_grid_sym_OM4_05.nc')

Now assign the coordinates from the static file:

[6]:
ds = ds.assign_coords({'geolon': grid['geolon'],
                       'geolat': grid['geolat']})

Select the month and vertical level we want to plot:

[7]:
sst_plot = ds['thetao'].sel(time='2003-01', z_l=2.5)

A simple cylindrical equidistant map

This is what a quick SST plot would look like:

[8]:
sst_plot.plot()
[8]:
<matplotlib.collections.QuadMesh at 0x7f99a845cc50>
../_images/notebooks_Plotting_13_1.png

Not great, right? First xarray infers data limits and a corresponding colormaps and is completely off. Figure is too small and coordinates are not right (we want geolon/geolat, see getting started notebook). Let’s fix this right now by setting the figure size, limits

[9]:
import matplotlib.pyplot as plt
plt.figure(figsize=[12,8])
sst_plot.plot(x='geolon', y='geolat',
              vmin=-2, vmax=32,
              cmap=cmocean.cm.thermal)
[9]:
<matplotlib.collections.QuadMesh at 0x7f99a82ef2b0>
../_images/notebooks_Plotting_15_1.png

Ok at least now the plot is accurate and can be useful scientifically but we still have a bit of work to make it publication ready: we’re gonna want a real map projection, continents filled in a different color,… In order to do this, we’re going to use cartopy projections and change axes attributes:

[10]:
import cartopy.crs as ccrs

subplot_kws=dict(projection=ccrs.PlateCarree(),
                 facecolor='grey')

plt.figure(figsize=[12,8])
sst_plot.plot(x='geolon', y='geolat',
              vmin=-2, vmax=32,
              cmap=cmocean.cm.thermal,
              subplot_kws=subplot_kws,
              transform=ccrs.PlateCarree())
[10]:
<matplotlib.collections.QuadMesh at 0x7f99f0849470>
../_images/notebooks_Plotting_17_1.png

You can pick any projection from the cartopy list but, whichever projection you use, you still have to set

transform=ccrs.PlateCarree()

facecolor can be one of the pre-defined colors (‘white’, ‘w’, ‘red’, ‘r’, …) or a RGB triplet (e.g. [0.5, 0.5, 0.5])

Now I don’t like the labels xarray puts and I don’t have control on the colormaps so I’m going to suppress them and add them a posteriori

[11]:
subplot_kws=dict(projection=ccrs.PlateCarree(),
                 facecolor='grey')

plt.figure(figsize=[12,8])
p = sst_plot.plot(x='geolon', y='geolat',
                  vmin=-2, vmax=32,
                  cmap=cmocean.cm.thermal,
                  subplot_kws=subplot_kws,
                  transform=ccrs.PlateCarree(),
                  add_labels=False,
                  add_colorbar=False)

# add separate colorbar
cb = plt.colorbar(p, ticks=[0,4,8,12,16,20,24,28,32], shrink=0.6)
cb.ax.tick_params(labelsize=18)

# optional add grid lines
p.axes.gridlines(color='black', alpha=0.5, linestyle='--')
[11]:
<cartopy.mpl.gridliner.Gridliner at 0x7f9989e860b8>
../_images/notebooks_Plotting_20_1.png

Next step is to add parallels/meridiens and their labels, this gets a little more involved:

[12]:
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import numpy as np

subplot_kws=dict(projection=ccrs.PlateCarree(),
                 facecolor='grey')

plt.figure(figsize=[12,8])
p = sst_plot.plot(x='geolon', y='geolat',
                  vmin=-2, vmax=32,
                  cmap=cmocean.cm.thermal,
                  subplot_kws=subplot_kws,
                  transform=ccrs.PlateCarree(),
                  add_labels=False,
                  add_colorbar=False)

# add separate colorbar
cb = plt.colorbar(p, ticks=[0,4,8,12,16,20,24,28,32], shrink=0.6)
cb.ax.tick_params(labelsize=18)

# draw parallels/meridiens and write labels
gl = p.axes.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
                      linewidth=2, color='gray', alpha=0.5, linestyle='--')

# adjust labels to taste
gl.xlabels_top = False
gl.ylabels_right = False
gl.ylocator = mticker.FixedLocator([-90, -60, -30, 0, 30, 60, 90])
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
gl.xlabel_style = {'size': 18, 'color': 'black'}
gl.ylabel_style = {'size': 18, 'color': 'black'}
../_images/notebooks_Plotting_22_0.png

Ok this looks decent, now let’s look at other options.

Pushing cartopy’s limits

We can try a different projection but the labels for parallels/meridiens labels are not available:

[13]:
subplot_kws=dict(projection=ccrs.Robinson(),
                 facecolor='grey')

plt.figure(figsize=[12,8])
p = sst_plot.plot(x='geolon', y='geolat',
                  vmin=-2, vmax=32,
                  cmap=cmocean.cm.thermal,
                  subplot_kws=subplot_kws,
                  transform=ccrs.PlateCarree(),
                  add_labels=False,
                  add_colorbar=False)

# add separate colorbar
cb = plt.colorbar(p, ticks=[0,4,8,12,16,20,24,28,32], shrink=0.6)
cb.ax.tick_params(labelsize=18)

p.axes.gridlines(color='black', alpha=0.5, linestyle='--')
[13]:
<cartopy.mpl.gridliner.Gridliner at 0x7f9969cbcc88>
../_images/notebooks_Plotting_26_1.png

We can also go fancy on the land background:

[14]:
subplot_kws=dict(projection=ccrs.Robinson())

plt.figure(figsize=[12,8])
p = sst_plot.plot(x='geolon', y='geolat',
                  vmin=-2, vmax=32,
                  cmap=cmocean.cm.thermal,
                  subplot_kws=subplot_kws,
                  transform=ccrs.PlateCarree(),
                  add_labels=False,
                  add_colorbar=False)

# add separate colorbar
cb = plt.colorbar(p, ticks=[0,4,8,12,16,20,24,28,32], shrink=0.6)
cb.ax.tick_params(labelsize=18)

# background
url = 'http://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi'
p.axes.add_wmts(url, 'BlueMarble_NextGeneration')
[14]:
<cartopy.mpl.slippy_image_artist.SlippyImageArtist at 0x7f99699980f0>
../_images/notebooks_Plotting_28_1.png

If you’re having problem with generating plots using wmts, make sure you have owslib version of 0.20 (you may need to pip uninstall owslib, clone owslib source and python setup.py install), cartopy 0.17 and matplotlib 3.2.2 You can use the environment.yml file in the binder directory to build a suitable environment. Now if you don’t like the blue marble background (or other web mab service image), we can also fill the continents with a uniform color like we did before.

Polar projections

Disclaimer: Polar projections can have some issues (especially when trying to change central longitude) but these examples are a good starting point:

[17]:
subplot_kws=dict(projection=ccrs.NorthPolarStereo(central_longitude=-30.0),
                 facecolor='grey')

plt.figure(figsize=[12,8])
p = sst_plot.plot(x='geolon', y='geolat',
                  vmin=-2, vmax=12,
                  cmap=cmocean.cm.thermal,
                  subplot_kws=subplot_kws,
                  transform=ccrs.PlateCarree(),
                  add_labels=False,
                  add_colorbar=False)

# add separate colorbar
cb = plt.colorbar(p, ticks=[-2,0,2,4,6,8,10,12], shrink=0.99)
cb.ax.tick_params(labelsize=18)

p.axes.gridlines(color='black', alpha=0.5, linestyle='--')
p.axes.set_extent([-300, 60, 50, 90], ccrs.PlateCarree())
../_images/notebooks_Plotting_32_0.png
[18]:
subplot_kws=dict(projection=ccrs.SouthPolarStereo(central_longitude=-120.),
                 facecolor='grey')

plt.figure(figsize=[12,8])
p = sst_plot.plot(x='geolon', y='geolat',
                  vmin=-2, vmax=24,
                  cmap=cmocean.cm.thermal,
                  subplot_kws=subplot_kws,
                  transform=ccrs.PlateCarree(),
                  add_labels=False,
                  add_colorbar=False)

# add separate colorbar
cb = plt.colorbar(p, ticks=[-2,0,2,4,6,8,10,12,14,16,18,20,22,24], shrink=0.99)
cb.ax.tick_params(labelsize=18)

p.axes.gridlines(color='black', alpha=0.5, linestyle='--')
p.axes.set_extent([-300, 60, -40, -90], ccrs.PlateCarree())
../_images/notebooks_Plotting_33_0.png

Now go write that paper! :)