Making multi-panel plots using Cartopy
¶
This notebook will demonstrate how to make multi-panel contour plots with Cartopy
, including adding a single colorbar and title for all panels.
Data¶
The Subseasonal Experiment (SubX)¶
Further information on SubX is available from Pegion et al. 2019 and the SubX project website
The SubX public database is hosted on the International Research Institute for Climate and Society (IRI) data server http://iridl.ldeo.columbia.edu/SOURCES/.Models/.SubX/
SubX Forecasts are hosted locally on COLA computers at the following location: /shared/subx/forecast/weekly/
Specifically, we will use the forecast for 2m temperatures anomalies made on Apr 16, 2020, located in: /shared/subx/forecast/weekly/20200416/data/fcst_20200416.anom.tas_2m.nc
[77]:
import xarray as xr
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import cartopy.feature as cfeature
import cartopy.crs as ccrs
import cartopy.mpl.ticker as cticker
from cartopy.util import add_cyclic_point
Set the path and filename
[78]:
path='/shared/subx/forecast/weekly/20200416/data/'
fname='fcst_20200416.anom.tas_2m.nc'
[79]:
ds=xr.open_dataset(path+fname)
This dataset contains weekly forecasts for 2m temperature anomalies from each of the SubX Models and the multi-model ensemble (MME). These are listed as the Data Variables. The forecats have 4 times, corresponding to weeks 1, 2, 3, and 4.
[80]:
ds
[80]:
- lat: 181
- lon: 360
- time: 4
- lat(lat)float32-90.0 -89.0 -88.0 ... 89.0 90.0
- standard_name :
- latitude
- long_name :
- latitude
- units :
- degrees_north
array([-90., -89., -88., -87., -86., -85., -84., -83., -82., -81., -80., -79., -78., -77., -76., -75., -74., -73., -72., -71., -70., -69., -68., -67., -66., -65., -64., -63., -62., -61., -60., -59., -58., -57., -56., -55., -54., -53., -52., -51., -50., -49., -48., -47., -46., -45., -44., -43., -42., -41., -40., -39., -38., -37., -36., -35., -34., -33., -32., -31., -30., -29., -28., -27., -26., -25., -24., -23., -22., -21., -20., -19., -18., -17., -16., -15., -14., -13., -12., -11., -10., -9., -8., -7., -6., -5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., 30., 31., 32., 33., 34., 35., 36., 37., 38., 39., 40., 41., 42., 43., 44., 45., 46., 47., 48., 49., 50., 51., 52., 53., 54., 55., 56., 57., 58., 59., 60., 61., 62., 63., 64., 65., 66., 67., 68., 69., 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., 80., 81., 82., 83., 84., 85., 86., 87., 88., 89., 90.], dtype=float32)
- lon(lon)float320.0 1.0 2.0 ... 357.0 358.0 359.0
- standard_name :
- longitude
- long_name :
- longitude
- units :
- degrees_east
array([ 0., 1., 2., ..., 357., 358., 359.], dtype=float32)
- time(time)datetime64[ns]2020-04-24 ... 2020-05-15
- standard_name :
- time
- long_name :
- Time of measurements
array(['2020-04-24T00:00:00.000000000', '2020-05-01T00:00:00.000000000', '2020-05-08T00:00:00.000000000', '2020-05-15T00:00:00.000000000'], dtype='datetime64[ns]')
- GEOS_V2p1(time, lat, lon)float32...
- name :
- GEOS_V2p1
- long_name :
- GMAO-GEOS_V2p1 20200411
- units :
- degC
[260640 values with dtype=float32]
- CCSM4(time, lat, lon)float32...
- name :
- CCSM4
- long_name :
- RSMAS-CCSM4 20200412
- units :
- degC
[260640 values with dtype=float32]
- FIMr1p1(time, lat, lon)float32...
- name :
- FIMr1p1
- long_name :
- ESRL-FIMr1p1 20200415
- units :
- degC
[260640 values with dtype=float32]
- GEPS6(time, lat, lon)float32...
- name :
- GEPS6
- long_name :
- ECCC-GEPS6 20200416
- units :
- degC
[260640 values with dtype=float32]
- NESM(time, lat, lon)float32...
- name :
- NESM
- long_name :
- NRL-NESM 20200414 20200413 20200412 20200411
- units :
- degC
[260640 values with dtype=float32]
- GEFS(time, lat, lon)float32...
- name :
- GEFS
- long_name :
- EMC-GEFS 20200415
- units :
- degC
[260640 values with dtype=float32]
- MME(time, lat, lon)float32...
- name :
- MME
- long_name :
- MME
- units :
- degC
[260640 values with dtype=float32]
- title :
- SubX Weekly Forecast Anomalies
- long_title :
- SubX Weekly Forecast Anomalies
- comments :
- SubX project http://cola.gmu.edu/~kpegion/subx/
- institution :
- IRI
- source :
- SubX IRI
- CreationDate :
- 2020/04/17 08:55:49
- CreatedBy :
- rfreelan
- MatlabSource :
- calcAnomsFCST
Following our previous example (Making maps using Cartopy
), we can make a map of the Multi-model Ensemble week 1 forecast using Cartopy
.
[81]:
fig = plt.figure(figsize=(11,8.5))
# Set the axes using the specified map projection
ax=plt.axes(projection=ccrs.PlateCarree())
# Choose the Multi-model ensemble (MME) and week1 (time index of 0)
data=ds['MME'][0,:,:]
# Add cyclic point to data
data, lons = add_cyclic_point(data, coord=ds['lon'])
# Make a filled contour plot
cs=ax.contourf(lons, ds['lat'], data,
transform = ccrs.PlateCarree(),cmap='coolwarm',extend='both')
# Add coastlines
ax.coastlines()
# Define the xticks for longitude
ax.set_xticks(np.arange(-180,181,60), crs=ccrs.PlateCarree())
lon_formatter = cticker.LongitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
# Define the yticks for latitude
ax.set_yticks(np.arange(-90,91,30), crs=ccrs.PlateCarree())
lat_formatter = cticker.LatitudeFormatter()
ax.yaxis.set_major_formatter(lat_formatter)
# Add colorbar
cbar = plt.colorbar(cs)
Single page with multi-panels¶
Now let’s make a single page showing the forecast for week 1 for each model. To do that, we will need to divide the page into subplots using plt.subplots
.
We need to get a list of all our models from the
xarray.Dataset
. You can see from this list that we have 7 models.
[82]:
models=list(ds.keys())
models
[82]:
['GEOS_V2p1', 'CCSM4', 'FIMr1p1', 'GEPS6', 'NESM', 'GEFS', 'MME']
We need to define the number of rows and columns on our page. With 7 models, we will need to divide our page into 3 rows and 3 columns, even though we won’t use them all.
[83]:
nrows=3
ncols=3
[84]:
# Define the figure and each axis for the 3 rows and 3 columns
fig, axs = plt.subplots(nrows=nrows,ncols=ncols,
subplot_kw={'projection': ccrs.PlateCarree()},
figsize=(11,8.5))
# axs is a 2 dimensional array of `GeoAxes`. We will flatten it into a 1-D array
axs=axs.flatten()
#Loop over all of the models
for i,model in enumerate(models):
# Select the week 1 forecast from the specified model
data=ds[model][0,:,:]
# Add the cyclic point
data,lons=add_cyclic_point(data,coord=ds['lon'])
# Contour plot
cs=axs[i].contourf(lons,ds['lat'],data,
transform = ccrs.PlateCarree(),
cmap='coolwarm',extend='both')
# Title each subplot with the name of the model
axs[i].set_title(model)
# Draw the coastines for each subplot
axs[i].coastlines()
Get rid of the extra panels¶
Since we created a 3x3 grid of GeoAxes
, the boxes for those axes appear even though we don’t want them. We can delete them.
[85]:
# Define the figure and each axis for the 3 rows and 3 columns
fig, axs = plt.subplots(nrows=nrows,ncols=ncols,
subplot_kw={'projection': ccrs.PlateCarree()},
figsize=(11,8.5))
# axs is a 2 dimensional array of `GeoAxes`. We will flatten it into a 1-D array
axs=axs.flatten()
#Loop over all of the models
for i,model in enumerate(models):
# Select the week 1 forecast from the specified model
data=ds[model][0,:,:]
# Add the cyclic point
data,lons=add_cyclic_point(data,coord=ds['lon'])
# Contour plot
cs=axs[i].contourf(lons,ds['lat'],data,
transform = ccrs.PlateCarree(),
cmap='coolwarm',extend='both')
# Title each subplot with the name of the model
axs[i].set_title(model)
# Draw the coastines for each subplot
axs[i].coastlines()
# Delete the unwanted axes
for i in [7,8]:
fig.delaxes(axs[i])
Make Consistent contour intervals across all panels¶
Typically in atmosphere, ocean, and climate science, we make multi-panel plots because we want to easily compare across the panels. To do this, we need the contour intervals to be the same for each panel. To do this, we specify the levels in the plt.contourf
call.
[86]:
# Define the contour levels to use in plt.contourf
clevs=np.arange(-12,13,1)
# Define the figure and each axis for the 3 rows and 3 columns
fig, axs = plt.subplots(nrows=nrows,ncols=ncols,
subplot_kw={'projection': ccrs.PlateCarree()},
figsize=(11,8.5))
# axs is a 2 dimensional array of `GeoAxes`. We will flatten it into a 1-D array
axs=axs.flatten()
#Loop over all of the models
for i,model in enumerate(models):
# Select the week 1 forecast from the specified model
data=ds[model][0,:,:]
# Add the cyclic point
data,lons=add_cyclic_point(data,coord=ds['lon'])
# Contour plot
cs=axs[i].contourf(lons,ds['lat'],data,clevs,
transform = ccrs.PlateCarree(),
cmap='coolwarm',extend='both')
# Title each subplot with the name of the model
axs[i].set_title(model)
# Draw the coastines for each subplot
axs[i].coastlines()
# Delete the unwanted axes
for i in [7,8]:
fig.delaxes(axs[i])
Add a single colorbar and big title¶
[87]:
# Define the contour levels to use in plt.contourf
clevs=np.arange(-12,13,1)
# Define the figure and each axis for the 3 rows and 3 columns
fig, axs = plt.subplots(nrows=nrows,ncols=ncols,
subplot_kw={'projection': ccrs.PlateCarree()},
figsize=(11,8.5))
# axs is a 2 dimensional array of `GeoAxes`. We will flatten it into a 1-D array
axs=axs.flatten()
#Loop over all of the models
for i,model in enumerate(models):
# Select the week 1 forecast from the specified model
data=ds[model][0,:,:]
# Add the cyclic point
data,lons=add_cyclic_point(data,coord=ds['lon'])
# Contour plot
cs=axs[i].contourf(lons,ds['lat'],data,clevs,
transform = ccrs.PlateCarree(),
cmap='coolwarm',extend='both')
# Title each subplot with the name of the model
axs[i].set_title(model)
# Draw the coastines for each subplot
axs[i].coastlines()
# Delete the unwanted axes
for i in [7,8]:
fig.delaxes(axs[i])
# Adjust the location of the subplots on the page to make room for the colorbar
fig.subplots_adjust(bottom=0.2, top=0.9, left=0.1, right=0.9,
wspace=0.02, hspace=0.02)
# Add a colorbar axis at the bottom of the graph
cbar_ax = fig.add_axes([0.2, 0.2, 0.6, 0.02])
# Draw the colorbar
cbar=fig.colorbar(cs, cax=cbar_ax,orientation='horizontal')
# Add a big title at the top
plt.suptitle('SubX Week 1 2m Temperature Anomalies ($^\circ$C): Apr 16, 2020 Initialized Forecasts')
[87]:
Text(0.5, 0.98, 'SubX Week 1 2m Temperature Anomalies ($^\\circ$C): Apr 16, 2020 Initialized Forecasts')
Add lat-lon labels¶
[88]:
# Define the contour levels to use in plt.contourf
clevs=np.arange(-12,13,1)
# Define the figure and each axis for the 3 rows and 3 columns
fig, axs = plt.subplots(nrows=nrows,ncols=ncols,
subplot_kw={'projection': ccrs.PlateCarree()},
figsize=(11,8.5))
# axs is a 2 dimensional array of `GeoAxes`. We will flatten it into a 1-D array
axs=axs.flatten()
#Loop over all of the models
for i,model in enumerate(models):
# Select the week 1 forecast from the specified model
data=ds[model][0,:,:]
# Add the cyclic point
data,lons=add_cyclic_point(data,coord=ds['lon'])
# Contour plot
cs=axs[i].contourf(lons,ds['lat'],data,clevs,
transform = ccrs.PlateCarree(),
cmap='coolwarm',extend='both')
# Title each subplot with the name of the model
axs[i].set_title(model)
# Draw the coastines for each subplot
axs[i].coastlines()
# Longitude labels
axs[i].set_xticks(np.arange(-180,181,60), crs=ccrs.PlateCarree())
lon_formatter = cticker.LongitudeFormatter()
axs[i].xaxis.set_major_formatter(lon_formatter)
# Latitude labels
axs[i].set_yticks(np.arange(-90,91,30), crs=ccrs.PlateCarree())
lat_formatter = cticker.LatitudeFormatter()
axs[i].yaxis.set_major_formatter(lat_formatter)
# Delete the unwanted axes
for i in [7,8]:
fig.delaxes(axs[i])
# Adjust the location of the subplots on the page to make room for the colorbar
fig.subplots_adjust(bottom=0.2, top=0.9, left=0.1, right=0.9,
wspace=0.02, hspace=0.02)
# Add a colorbar axis at the bottom of the graph
cbar_ax = fig.add_axes([0.2, 0.2, 0.6, 0.02])
# Draw the colorbar
cbar=fig.colorbar(cs, cax=cbar_ax,orientation='horizontal')
# Add a big title at the top
plt.suptitle('SubX Week 1 2m Temperature Anomalies ($^\circ$C): Apr 16, 2020 Initialized Forecasts')
[88]:
Text(0.5, 0.98, 'SubX Week 1 2m Temperature Anomalies ($^\\circ$C): Apr 16, 2020 Initialized Forecasts')
Fix the spacing¶
Change the spacing values in fig.subplots_adjust
[89]:
# Define the contour levels to use in plt.contourf
clevs=np.arange(-12,13,1)
# Define the figure and each axis for the 3 rows and 3 columns
fig, axs = plt.subplots(nrows=nrows,ncols=ncols,
subplot_kw={'projection': ccrs.PlateCarree()},
figsize=(11,8.5))
# axs is a 2 dimensional array of `GeoAxes`. We will flatten it into a 1-D array
axs=axs.flatten()
#Loop over all of the models
for i,model in enumerate(models):
# Select the week 1 forecast from the specified model
data=ds[model][0,:,:]
# Add the cyclic point
data,lons=add_cyclic_point(data,coord=ds['lon'])
# Contour plot
cs=axs[i].contourf(lons,ds['lat'],data,clevs,
transform = ccrs.PlateCarree(),
cmap='coolwarm',extend='both')
# Title each subplot with the name of the model
axs[i].set_title(model)
# Draw the coastines for each subplot
axs[i].coastlines()
# Longitude labels
axs[i].set_xticks(np.arange(-180,181,60), crs=ccrs.PlateCarree())
lon_formatter = cticker.LongitudeFormatter()
axs[i].xaxis.set_major_formatter(lon_formatter)
# Latitude labels
axs[i].set_yticks(np.arange(-90,91,30), crs=ccrs.PlateCarree())
lat_formatter = cticker.LatitudeFormatter()
axs[i].yaxis.set_major_formatter(lat_formatter)
# Delete the unwanted axes
for i in [7,8]:
fig.delaxes(axs[i])
# Adjust the location of the subplots on the page to make room for the colorbar
fig.subplots_adjust(bottom=0.25, top=0.9, left=0.05, right=0.95,
wspace=0.1, hspace=0.5)
# Add a colorbar axis at the bottom of the graph
cbar_ax = fig.add_axes([0.2, 0.15, 0.6, 0.02])
# Draw the colorbar
cbar=fig.colorbar(cs, cax=cbar_ax,orientation='horizontal')
# Add a big title at the top
plt.suptitle('SubX Week 1 2m Temperature Anomalies ($^\circ$C): Apr 16, 2020 Initialized Forecasts')
[89]:
Text(0.5, 0.98, 'SubX Week 1 2m Temperature Anomalies ($^\\circ$C): Apr 16, 2020 Initialized Forecasts')