1.2. Matplotlib tutorial (Advanced)#

This tutorial continues on plotting with Python. Two packages will be introduced: Seaborn and Cartopy. Seaborn is a Python library for making statistical graphics based on Matplotlib and Cartopy is a Python package designed for geospatial data visualization. We will first illustrate alternative ways for plots in statistics with Seaborn, then show plotting of geospatial maps with Cartopy.

  • Plots in statistics with Seaborn: histograms, density plots, ecdf plots, bar charts, box plots, and scatter plots

  • Plotting maps with Cartopy: a simple example, adding features to the map, and plotting 2D data

1.2.1. Plots in statistics with Seaborn#

Seaborn helps you explore and understand your data. Its plotting functions operate on dataframes and arrays containing whole datasets and internally perform the necessary semantic mapping and statistical aggregation to produce informative plots. Its dataset-oriented, declarative API lets you focus on what the different elements of your plots mean, rather than on the details of how to draw them. Compared with Matplotlib, Seaborn helps you save your time and efforts on customizing the figures.

We first import the packages that might be used.

import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt

Here, we utilize the “diamonds” dataset from Seaborn package for the demonstration of plots in statistics.

diamonds = sns.load_dataset("diamonds")
diamonds.head()
carat cut color clarity depth table price x y z
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31
3 0.29 Premium I VS2 62.4 58.0 334 4.20 4.23 2.63
4 0.31 Good J SI2 63.3 58.0 335 4.34 4.35 2.75

1.2.1.1. Histograms#

There are two functions to plot histograms in Seaborn. displot provides figure-level interface for drawing distribution plots. By changing the argument kind, you can plot histograms, kernel density plots, and empirical cumulative distribution plots. The alternative functions are histplot(), kdeplot(), and ecdfplot().

# changes the global defaults
sns.set_theme(style="ticks")

f, ax = plt.subplots(figsize=(7, 5))

# removes the top and right axes spines
sns.despine(f)

# plot according to classification
sns.histplot(
    diamonds,
    x="price", hue="cut",
    multiple="stack",
    palette="light:m_r",
    edgecolor=".3",
    linewidth=.5,
    log_scale=True,  # log scale
)
ax.xaxis.set_major_formatter(mpl.ticker.ScalarFormatter())
ax.set_xticks([500, 1000, 2000, 5000, 10000])
[<matplotlib.axis.XTick at 0x2bdfb39c190>,
 <matplotlib.axis.XTick at 0x2bdfc8e73a0>,
 <matplotlib.axis.XTick at 0x2bdfc7b7e20>,
 <matplotlib.axis.XTick at 0x2bdfc7bcdf0>,
 <matplotlib.axis.XTick at 0x2bdfcab6b50>]
../../_images/7444ff52e97d0a45a8eefca00cf35498153e118c0895ea6c406f2ab19267a200.png

Alternatively, you can use displot() function. The other settings are more or less similar.

# changes the global defaults
sns.set_theme(style="ticks")

# removes the top and right axes spines
sns.despine(f)

# plot according to classification
sns.displot(
    diamonds,
    x="price", hue="cut",
    multiple="stack",
    palette="light:m_r",
    edgecolor=".3",
    linewidth=.5,
    log_scale=True,  # log scale
    kind='hist'
)
[<matplotlib.axis.XTick at 0x2bd828d8670>,
 <matplotlib.axis.XTick at 0x2bd828d8640>,
 <matplotlib.axis.XTick at 0x2bd828dec10>,
 <matplotlib.axis.XTick at 0x2bd80014f70>,
 <matplotlib.axis.XTick at 0x2bd80014130>]
../../_images/b8feadfa2f6810a9080d8769c1a75ad1a50753d539e6d94799fcff9c995ee79b.png

1.2.1.2. Density plots#

# changes the global defaults
sns.set_theme(style="ticks")

f, ax = plt.subplots(figsize=(7, 5))

# removes the top and right axes spines
sns.despine(f)

# plot according to classification
sns.kdeplot(
    data=diamonds,
    x="price", hue="cut",multiple="stack",
    fill=True, common_norm=True,  palette="crest",
    alpha=.5, 
    log_scale=True,  # log scale
)
ax.xaxis.set_major_formatter(mpl.ticker.ScalarFormatter())
ax.set_xticks([500, 1000, 2000, 5000, 10000])
[<matplotlib.axis.XTick at 0x2bdfd0cd2b0>,
 <matplotlib.axis.XTick at 0x2bdfd0cd280>,
 <matplotlib.axis.XTick at 0x2bdfd0ca430>,
 <matplotlib.axis.XTick at 0x2bdfd0f86a0>,
 <matplotlib.axis.XTick at 0x2bdfd0f8bb0>]
../../_images/d90aadc4237629fc43d9e5f9d2288465905981674902f02618f2100f69cbde6b.png

Alternatively,

# changes the global defaults
sns.set_theme(style="ticks")

# removes the top and right axes spines
sns.despine(f)

# plot according to classification
sns.displot(
    data=diamonds,
    x="price", hue="cut",multiple="stack",
    fill=True, common_norm=True,  palette="crest",
    alpha=.5, 
    log_scale=True,  # log scale
    kind='kde'
)
<seaborn.axisgrid.FacetGrid at 0x2bd80ff94f0>
../../_images/ca49860963426cac9293ebb412d03f4b46f7a3dae0c061cd842c3699b6153f1d.png

1.2.1.3. Empirical cumulative distribution functions (ecdf) plots#

An ECDF represents the proportion or count of observations falling below each unique value in a dataset. It also aids direct comparisons between multiple distributions.

f, ax = plt.subplots(figsize=(7, 5))

# plot according to classification
sns.ecdfplot(
    data=diamonds,
    x="price", hue="cut",
    palette="crest",
    alpha=.8, 
)
<AxesSubplot:xlabel='price', ylabel='Proportion'>
../../_images/d88b93ca69b934ef976bc8966ed834a1b782a926f9750e05cfe53e746246ecc8.png
# plot according to classification
sns.displot(
    data=diamonds,
    x="price", hue="cut",
    palette="crest",
    alpha=.8, 
    kind='ecdf'
)
<seaborn.axisgrid.FacetGrid at 0x2bd8791c2b0>
../../_images/f9a0a0f1c6e0a074e26be7429a8ff0a8e92e03204f709f8ef2a5eb1ac0a98e24.png

1.2.1.4. Bar charts#

Bar charts are one of the categorical plots, which could be handled by catplot() with argument kind="bar". Besides, catplot() can also generate box plots, violin plots, swarm plots, and so on.

g = sns.catplot(
    data=diamonds, kind="bar",
    x="color", y="price", 
    ci=99, palette="dark", alpha=.6, height=6
)
# ci: Size of confidence intervals to draw around estimated values. 

g.despine(left=True)
g.set_axis_labels("Color", "Price")
<seaborn.axisgrid.FacetGrid at 0x2bd81649730>
../../_images/53277afd836beb384b24f929bc8df269771247ca9fdeefb54b980726d477f025.png

The other way for bar charts is barplot() function.

f, ax = plt.subplots(figsize=(7, 5))

sns.barplot(
    data=diamonds, 
    x="color", y="price", 
    ci=99, palette="dark", alpha=.6
)
# ci: Size of confidence intervals to draw around estimated values. 

sns.despine(f)
../../_images/7aa289f0356a1982ec509647c1ef61b4baf81e8576f1bf7a98c6a8fa02882b29.png

1.2.1.5. Box plots#

The box plot is another kind of categorical plots to show the distribution of the data.

g = sns.catplot(
    data=diamonds, kind="box",
    x="cut", y="price", 
    palette="dark", 
    height=6,
    orient="v",
    whis=2,
    fliersize=0.5
)
# fliersize: Size of the markers used to indicate outlier observations.
# whis: Proportion of the IQR past the low and high quartiles to extend the plot whiskers.

g.despine(left=True)
g.set_axis_labels("Cut", "Price")
<seaborn.axisgrid.FacetGrid at 0x2bd80377ac0>
../../_images/b0ab7bb865e2844c9f882d912e48e94dfa42021b5837a5e54843c15f0d0490d2.png

Alternatively,

f, ax = plt.subplots(figsize=(7, 5))

sns.boxplot(
    data=diamonds, 
    x="cut", y="price", 
    palette="dark", 
    orient="v",
    whis=2,
    fliersize=0.5
)
# fliersize: Size of the markers used to indicate outlier observations.
# whis: Proportion of the IQR past the low and high quartiles to extend the plot whiskers.

sns.despine(f)
../../_images/3f87688a3ec179bed5c80c2939a7b11849d5979391d9fbe251806443875f01a7.png

1.2.1.6. Scatter plots#

Scatter plots could be generated by scatterplot(). For example,

sns.scatterplot(data=diamonds, x="x", y="price", alpha=0.1, edgecolor=None)
<AxesSubplot:xlabel='x', ylabel='price'>
../../_images/97ebed5f1bd61f02ce65bfc683f6ab44f026f022254fedc93de4a11469906bc8.png

In addition, Seaborn has a function called pairplot(), which shows the scatter matrix and is extremely useful for multivariate analysis. We use “penguins” dataset for this example.

df = sns.load_dataset("penguins")
sns.pairplot(df, hue="species", markers=["o", "s", "D"])
<seaborn.axisgrid.PairGrid at 0x2bd8d717040>
../../_images/cd1dd917f7b0d1f4d1fdc7b5db03f630de56eba4bb3b7982c2dff3af9abb7786.png

1.2.2. Plotting maps with Cartopy#

Mapping is a special case of a regular plot which usually involves some of these elements:

  • A map projection to transform data from the spherical shape of the earth to screen/paper

  • Additional geospatial features like coastlines, rivers, country boundaries etc.

In this section, we will demonstrate how to plot geospatial maps for climate analysis using Cartopy. Cartopy is a Python package designed for geospatial data processing in order to produce maps and other geospatial data analyses. It makes use of the powerful PROJ, NumPy and Shapely libraries and includes a programmatic interface built on top of Matplotlib for the creation of publication quality maps.

We’ll first introduce a simple example to get you familiar with Cartopy.

1.2.2.1. A simple example#

As usual, we start by importing our modules.

# import packages needed for the example
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
# plots show in the notebook
%matplotlib inline  

# additional configuration (optional)
plt.rcParams['figure.figsize'] = 12, 6
%config InlineBackend.figure_format = 'retina' 

Then we create some dummy data for plotting.

# define a regularly spaced vector of longitudes
lon = np.linspace(-80, 80, 25)
# Same for latitudes
lat = np.linspace(30, 70, 25)
# we need to create a 2d array of longitudes and latitudes to create 2D data
lon2d, lat2d = np.meshgrid(lon, lat) 

# finally we create some dummy data 
data = np.cos(np.deg2rad(lat2d) * 4) + np.sin(np.deg2rad(lon2d) * 4)

Now we plot the data as filled contour.

plt.contourf(lon, lat, data)
plt.title('My basic plot')
plt.ylabel('Latitude')
plt.xlabel('Longitude')
plt.colorbar(label='My data [unit]') 
<matplotlib.colorbar.Colorbar at 0x2bd9d0274f0>
../../_images/b4b7e1f900877694c132a424e7d36676952c6f542a4a11ec4175df035032245f.png

Then we move the above 2D filled-contour plot to the map.

ax = plt.axes(projection=ccrs.PlateCarree()) 
# projection: ccrs.PlateCarree() is one type of projection
ax.contourf(lon, lat, data)

# optional (Comment the lines with a leading # and see what changes)
ax.set_global() # zoom out as far as possible
ax.coastlines() # plot coastlines
#ax.gridlines()
<cartopy.mpl.feature_artist.FeatureArtist at 0x2bd9d0b6a30>
E:\software\anaconda3\lib\site-packages\cartopy\io\__init__.py:260: DownloadWarning: Downloading: https://naciscdn.org/naturalearth/110m/physical/ne_110m_coastline.zip
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
../../_images/37e114e5807f5bef9d6fe3d14e4ee48893126fa3b545d480832e8f4224cbf2cb.png

Note that to plot a map, you need to manually create an axis (which means plt.plot() does not work anymore) and pass a projection=... keyword to the axis. The projection argument is used when creating plots and determines the projection of the resulting plot (i.e. what the plot looks like). The line plt.axes(projection=ccrs.PlateCarree()) sets up a GeoAxes instance which exposes a variety of other map related methods. You may pick your own projection such as Miller, Orthographic, or Robinson.

projections = [ccrs.PlateCarree(),
               ccrs.Robinson(),
               ccrs.Mercator(),
               ccrs.Orthographic(),
               ccrs.InterruptedGoodeHomolosine()
              ]

for proj in projections:
    plt.figure()
    ax = plt.axes(projection=proj)
    ax.stock_img()
    ax.coastlines()
    ax.set_title(f'{type(proj)}')
../../_images/5b2e1a89f28810f109be3497fa75a727cfaf73d0e19343716a666d3835db05e8.png ../../_images/468202db57f7bc51243e0557825aaebcc217dcbca5a338192d74b81baa962b19.png ../../_images/5c2c498a3a7cc29c8beccccb46ec33653b45f85595e047e28bdce18f4b4ad165.png ../../_images/b18563d901af6a6b2a88d427e7b99def2f79d273c13035337703430b3648440e.png ../../_images/ac47e1718cf2b96af9192d4111c1b72c3c5af8b87e82b326240d5b4e8046b550.png

If we want to plot the data in another projection, we need to pass a transform=... argument to the plot command. The transform argument to plotting functions tells Cartopy what coordinate system your data are defined in.

projection = ccrs.Robinson(-20) #this is the central longitude for the projection! Try to change it
#projection = ccrs.InterruptedGoodeHomolosine() # Comment the line above and uncomment this for another projection
ax = plt.axes(projection=projection)
ax.set_global()
ax.coastlines()
ax.gridlines()
ax.contourf(lon, lat, data, transform=ccrs.PlateCarree())
<cartopy.mpl.contour.GeoContourSet at 0x2bd8ef10280>
../../_images/408bf98178186ddf72710b0b57ded7c3a600ead00746b46ab128bd3e3eb2cdcb.png

In Cartopy, we can very easily plot coastlines (ax.coastlines()) and gridlines (ax.gridlines()), which give a better context for the data than before and also show the full globe.

For regional map, use set_extent().

central_lon, central_lat = -10, 45
extent = [-40, 20, 30, 60]
ax = plt.axes(projection=ccrs.Orthographic(central_lon, central_lat))
ax.set_extent(extent)
ax.gridlines()
ax.coastlines(resolution='50m')
<cartopy.mpl.feature_artist.FeatureArtist at 0x2bd9ee4b160>
E:\software\anaconda3\lib\site-packages\cartopy\io\__init__.py:260: DownloadWarning: Downloading: https://naciscdn.org/naturalearth/50m/physical/ne_50m_coastline.zip
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
../../_images/f4f47fe7364d9091876d6cbeea92be608cf8fde78a03ab497fc4368263f13a09.png

1.2.2.2. Adding features to the map#

To give our map more styles and details, we add cartopy.feature objects. Many useful features are built in. These “default features” are at coarse (110m) resolution.

  • cartopy.feature.BORDERS: Country boundaries

  • cartopy.feature.COASTLINE: Coastline, including major islands

  • cartopy.feature.LAKES: Natural and artificial lakes

  • cartopy.feature.LAND: Land polygons, including major islands

  • cartopy.feature.OCEAN: Ocean polygons

  • cartopy.feature.RIVERS: Single-line drainages, including lake centerlines

  • cartopy.feature.STATES: (limited to the United States at this scale)

import cartopy.feature as cfeature

central_lat = 37.5
central_lon = -96
extent = [-120, -70, 24, 50.5]
central_lon = np.mean(extent[:2])
central_lat = np.mean(extent[2:])

plt.figure(figsize=(12, 6))
ax = plt.axes(projection=ccrs.AlbersEqualArea(central_lon, central_lat))
ax.set_extent(extent)

ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.STATES, edgecolor='grey')
ax.add_feature(cfeature.LAND, edgecolor='black')
ax.add_feature(cfeature.LAKES, edgecolor='black')
ax.add_feature(cfeature.RIVERS, facecolor='None', edgecolor='blue')
# ax.gridlines()
<cartopy.mpl.feature_artist.FeatureArtist at 0x2bda3623a00>
../../_images/045010d7cc875bdf6492d8679c52bc590d66068aedcb0e4a5cdacf6fa49b2434.png

1.2.2.3. Plotting 2D data#

Note that Cartopy transforms can be passed to xarray. This creates a very quick path for creating professional looking maps from netCDF data. In this section, we use the ERA5-Land reanalysis data of monthly 2m temperature in 2020 to demonstrate how to plot maps from a dataset.

import xarray as xr
# Set working directory to the folder where your .nc file is located
fn = '../../assets/data/ERA5_monthly_2020_t2m.nc'
# fn = 'ERA5_monthly_2020_tpt2m.nc'
ds = xr.open_dataset(fn)
ds
<xarray.Dataset>
Dimensions:    (latitude: 551, longitude: 1041, time: 12)
Coordinates:
  * time       (time) datetime64[ns] 2020-01-01 2020-02-01 ... 2020-12-01
  * longitude  (longitude) float32 -18.0 -17.9 -17.8 -17.7 ... 85.8 85.9 86.0
  * latitude   (latitude) float32 51.0 50.9 50.8 50.7 ... -3.7 -3.8 -3.9 -4.0
Data variables:
    t2m        (time, latitude, longitude) float32 ...
    tp         (time, latitude, longitude) float32 ...
Attributes:
    CDI:          Climate Data Interface version 1.9.9rc1 (https://mpimet.mpg...
    Conventions:  CF-1.6
    history:      Mon Oct 11 09:09:15 2021: cdo selname,tp,t2m ERA5_monthly_2...
    CDO:          Climate Data Operators version 1.9.9rc1 (https://mpimet.mpg...

Xarray has a simple chioce for plotting, which is Dataset.plot(). After specifying the projected axes and transformed axes, it generate the map automatically.

t2m = ds.t2m.sel(time='2020-01-01', method='nearest')
fig = plt.figure(figsize=(9,6))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines()
ax.gridlines()
t2m.plot(ax=ax, transform=ccrs.PlateCarree(), cbar_kwargs={'shrink': 0.4})
<matplotlib.collections.QuadMesh at 0x270e9ed32e0>
../../_images/1968dbe6df714d22016bc3c970d810499dbe0dd0f00ed12b3ceb9bdd2953d027.png

You may use xarray to manipulate the netCDF file. Alternatively, you can use netCDF4 package to import the dataset as in the following codes:

import netCDF4 as nc

# Displays the content of your NetCFD file (.nc)
# With this operation you can find the variable names, dimension, and units
ds = nc.Dataset(fn)
print(ds,'\n\n')
# ds gives us information about the variables contained in the file and their dimensions.
for var in ds.variables.values():
    print(var,'\n')

# Read data according to the variable names
lon = ds.variables['longitude'][:]
lat = ds.variables['latitude'][:]
t2m = ds.variables['t2m'][0]  # 2m-temperature for 2020-01

# Note t2m is a multidimensional array as we extracted several
# tiles specified by their longitude and latitude (first two dimensions);
# it is at first over the time of 1 year at monthly time step (12),
# but we select the first month by specifying index 0
<class 'netCDF4._netCDF4.Dataset'>
root group (NETCDF3_64BIT_OFFSET data model, file format NETCDF3):
    CDI: Climate Data Interface version 1.9.9rc1 (https://mpimet.mpg.de/cdi)
    Conventions: CF-1.6
    history: Mon Oct 11 09:09:15 2021: cdo selname,tp,t2m ERA5_monthly_2020.nc ERA5_monthly_2020_tpt2m.nc
Mon Oct 11 09:07:11 2021: cdo selyear,2020 ERA5_monthly_1985to2020.nc ERA5_monthly_2020.nc
2021-04-16 11:48:30 GMT by grib_to_netcdf-2.16.0: /opt/ecmwf/eccodes/bin/grib_to_netcdf -S param -o /cache/data8/adaptor.mars.internal-1618572459.4065554-24933-2-a4312836-aa80-4b81-9ae1-c5b6d1e86609.nc /cache/tmp/a4312836-aa80-4b81-9ae1-c5b6d1e86609-adaptor.mars.internal-1618572459.4070513-24933-1-tmp.grib
    CDO: Climate Data Operators version 1.9.9rc1 (https://mpimet.mpg.de/cdo)
    dimensions(sizes): time(12), longitude(1041), latitude(551)
    variables(dimensions): int32 time(time), float32 longitude(longitude), float32 latitude(latitude), int16 t2m(time,latitude,longitude), int16 tp(time,latitude,longitude)
    groups:  


<class 'netCDF4._netCDF4.Variable'>
int32 time(time)
    standard_name: time
    long_name: time
    units: hours since 1900-01-01 00:00:00.0
    calendar: gregorian
    axis: T
unlimited dimensions: time
current shape = (12,)
filling on, default _FillValue of -2147483647 used 

<class 'netCDF4._netCDF4.Variable'>
float32 longitude(longitude)
    standard_name: longitude
    long_name: longitude
    units: degrees_east
    axis: X
unlimited dimensions: 
current shape = (1041,)
filling on, default _FillValue of 9.969209968386869e+36 used 

<class 'netCDF4._netCDF4.Variable'>
float32 latitude(latitude)
    standard_name: latitude
    long_name: latitude
    units: degrees_north
    axis: Y
unlimited dimensions: 
current shape = (551,)
filling on, default _FillValue of 9.969209968386869e+36 used 

<class 'netCDF4._netCDF4.Variable'>
int16 t2m(time, latitude, longitude)
    long_name: 2 metre temperature
    units: K
    add_offset: 276.51379807172606
    scale_factor: 0.0011819327197568877
    _FillValue: -32767
    missing_value: -32767
unlimited dimensions: time
current shape = (12, 551, 1041)
filling on 

<class 'netCDF4._netCDF4.Variable'>
int16 tp(time, latitude, longitude)
    long_name: Total precipitation
    units: m
    add_offset: 0.07135129816751164
    scale_factor: 2.1775881619862287e-06
    _FillValue: -32767
    missing_value: -32767
unlimited dimensions: time
current shape = (12, 551, 1041)
filling on 
<ipython-input-29-86bcde68219e>:6: DeprecationWarning: tostring() is deprecated. Use tobytes() instead.
  print(ds,'\n\n')
<ipython-input-29-86bcde68219e>:9: DeprecationWarning: tostring() is deprecated. Use tobytes() instead.
  print(var,'\n')
<ipython-input-29-86bcde68219e>:12: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  lon = ds.variables['longitude'][:]
<ipython-input-29-86bcde68219e>:13: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  lat = ds.variables['latitude'][:]
<ipython-input-29-86bcde68219e>:14: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  t2m = ds.variables['t2m'][0]  # 2m-temperature for 2020-01
<ipython-input-29-86bcde68219e>:15: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  tp = ds.variables['tp'][0]    # totol precipitation for 2020-01b

The methods of plotting maps are exactly the same as 2D plotting methods we introduced in Matplotlib Tutorial (Basic). You only need to add the projection information to the axes, and transform information to the plot method (whichever it is, contourf or pcolor or scatter…). You may also add features and gridlines to make the map more attractive.

fig = plt.figure(figsize=(9,6))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines()
ax.gridlines()
ax.set_title('2020-01')
cs = ax.contourf(lon, lat, t2m, cmap='OrRd', transform=ccrs.PlateCarree())
cbar = fig.colorbar(cs, orientation="horizontal")
cbar.ax.set_xlabel("2m temperature [K]")
Text(0.5, 0, '2m temperature [K]')
../../_images/4a603c3c4d145c8aa0c4182071c6c26debe6e2fd890b48b84fe814ccc33581f4.png

For images without contours,

fig = plt.figure(figsize=(9,6))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines()
ax.gridlines()
ax.set_title('2020-01')
cs = ax.pcolormesh(lon, lat, t2m, shading='auto', cmap='OrRd', transform=ccrs.PlateCarree())
cbar = fig.colorbar(cs, orientation="horizontal")
cbar.ax.set_xlabel("2m temperature [K]")
Text(0.5, 0, '2m temperature [K]')
../../_images/2cff2bc18d044e274e42e75f6c5c1828d2996059018d39cea25b092f3f2241e6.png

Browse the Cartopy Gallery to learn more about all the different types of data and plotting methods available.

1.2.3. References#