Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Open In Colab Open In Kaggle

(366,) (366,)

Exercise 1: Placing axes by hand

Build a 7 × 3 inch figure with two axes positioned with fig.add_axes: a main panel covering roughly the left two-thirds, and a smaller one in the upper right. Plot the valley station’s whole year in the main panel and January alone (days[:31]) in the small one. Label the axes of both, and give the small panel a title.

Exercise 2: A grid of panels

With plt.subplots(2, 2, figsize=(9, 5)), give each of the three station records and the humidity series its own panel. Print the shape of the axes array, label every panel with its quantity and unit, give each a title, and pack the figure with tight_layout.

Exercise 3: Telling series apart without colour

Plot all three station records on one axes so that they stay distinguishable printed in black and white: all three in black, one solid, one dashed, one dotted. Then add every thirtieth point of the valley record as circular markers with no line joining them. Add a legend.

Exercise 4: Ticks, gridlines, and axis limits

Plot the valley record, then:

  1. Replace the x ticks with ["Jan", "Apr", "Jul", "Oct", "Jan"] at days [0, 90, 180, 270, 365].

  2. Put major y ticks every 5 °C and minor ones every 1 °C, with a solid major grid and a dotted minor grid.

  3. In a second figure, show only days 150 to 240, with the y-axis limited to 5 to 25 °C.

Exercise 5: Annotate the extremes

Find the warmest and the coldest day of the valley record with np.argmax and np.argmin. Mark each on the plot with ax.annotate and an arrow, and add a plain ax.text label somewhere in the summer half of the year.

Exercise 6: A parametric plot

Average both the valley record and the humidity series into twelve monthly means with [:360].reshape(12, 30).mean(axis=1), then plot humidity against temperature with a marker at each month. Label both axes. The twelve points trace a loop rather than a line — add a one-line comment saying what that loop tells you about the two quantities.

Exercise 7: Scatter and histogram

Make a 1 × 2 figure. On the left, scatter humidity against temperature, with point colour mapped to day of year (labelled colorbar) and point size mapped to each day’s absolute departure from the annual mean temperature. On the right, a 24-bin histogram of the temperature. Label every axis.

Exercise 8: Bar and barh

Compute the annual mean of each of the three stations and compare them in a 1 × 2 figure: bar on the left, barh on the right. Label the value axis with its unit in both.

Exercise 9: One field, three ways

Build the small gridded field below, then draw it three times in a 1 × 3 figure: with imshow (row 0 is the southernmost latitude, so make it appear at the bottom), with pcolormesh using the coordinates, and with contourf using 8 levels. Label the imshow panel’s axes in grid index and the other two in degrees — the difference is the point of the exercise. Give each panel a labelled colorbar, and save the figure as _files/field.svg.

lon = np.linspace(6.0, 9.0, 6)
lat = np.linspace(46.0, 47.5, 4)      # ascending: south -> north
field_celsius = 6.0 + (46.0 - lat)[:, None] * 3.0 + rng.normal(0, 1.0, size=(4, 6))

Exercise 10: A vector field

Build the synthetic wind field below — a simple rotation, like a cyclone seen from above — and draw it two ways in a 1 × 2 figure. On the left, quiver at every third grid point, over contour lines of the wind speed. On the right, streamplot coloured by wind speed, with a labelled colorbar. Label the axes in km.

x_km = np.linspace(-100, 100, 25)
y_km = np.linspace(-100, 100, 25)
xx_km, yy_km = np.meshgrid(x_km, y_km)
u_ms = -yy_km / 8.0
v_ms = xx_km / 8.0

Exercise 11: Fix the figure

The cell below draws thirty days (days 120 to 149) of the valley and ridge records, and breaks most of the best-practices checklist at the end of the lecture. First list every practice it breaks, as comments. Then redraw the same data so that it passes all of them.

<Figure size 300x200 with 1 Axes>

Exercise 12: Build a labelled DataArray

From data = np.arange(12.0).reshape(3, 4), build an xarray DataArray with dimensions ("lat", "lon"), latitude coordinates [46.0, 46.5, 47.0], longitude coordinates [6.0, 6.5, 7.0, 7.5], and a units attribute of "degC". Print its dims and its units.

Exercise 13: Select and reduce

Build the small Dataset below, then print the spatial mean of the first day (by position) and the time mean at latitude 47.0 (by label).

rng = np.random.default_rng(0)
time = np.arange("2024-01-01", "2024-01-11", dtype="datetime64[D]")
lat = np.array([46.0, 46.5, 47.0]); lon = np.array([6.0, 6.5, 7.0, 7.5])
t = rng.normal(5, 3, size=(10, 3, 4))
ds = xr.Dataset({"t2m": (("time", "lat", "lon"), t)},
                coords={"time": time, "lat": lat, "lon": lon})

Exercise 14: Resample and a monthly climatology

Build a one-year daily temperature DataArray (construction below), compute its monthly means with resample, print how many there are, then use groupby to find the warmest calendar month (1–12).

rng = np.random.default_rng(0)
time = np.arange("2024-01-01", "2025-01-01", dtype="datetime64[D]")
n = time.size; doy = np.arange(n)
t = 5 + -np.cos(2 * np.pi * doy / n) * 10 + rng.normal(0, 1.5, n)
da = xr.DataArray(t, dims="time", coords={"time": time}, attrs={"units": "degC"})

Exercise 15: Round-trip through netCDF

Create a 1D temperature DataArray named t2m with a units attribute, write it to _files/series.nc, reopen it with open_dataset, and print the variable names and the recovered units.

Exercise 16: A field on a map with cartopy

Rebuild the DataArray da from exercise 12. Plot it on a ccrs.PlateCarree() projection with .coastlines() and a labelled colorbar, using transform=ccrs.PlateCarree().

Exercise 17: NASA’s real global-temperature record

The file cached below is NASA GISS’s monthly global-mean surface temperature anomaly (°C, relative to a 1951–1980 baseline), one row per year since 1880, one column per calendar month. A handful of recent months are still missing, marked ***.

  1. Load it with np.genfromtxt(path, skip_header=1, delimiter=",", names=True, missing_values="***", filling_values=np.nan). Stack the twelve month columns (data["Jan"], ..., data["Dec"]) into a (year, month) array with np.column_stack.

  2. Wrap it in an xarray DataArray with dims ("year", "month"), coordinates year (from data["Year"]) and month (np.arange(1, 13)), and a units attribute of "degC".

  3. Plot the annual mean (.mean(dim="month"), which skips the missing months automatically) as a line against year.

  4. Plot the full (year, month) DataArray directly with .plot() — xarray draws it as a labelled pcolormesh. Compare what the warming trend looks like read month by month against the single annual line.

Replicating plots

The four exercises below each show a figure built from a real dataset and ask you to rebuild it as closely as you can. The data arrive through a pre-supplied pooch cell; everything after that is yours.

Work towards the target one element at a time — get the data onto the axes first, then the coordinates, then the colours, then the labels — rather than trying to write the whole cell in one go.

Exercise 18: Global surface air temperature and its zonal mean

The three files cached below hold one snapshot of surface air temperature from the NCEP/NCAR atmospheric reanalysis 1, as plain numpy arrays: lon (192 longitudes), lat (94 latitudes, running north to south), and temp_kelvin, the field itself with shape (94, 192) — in kelvin.

A filled contour map of global surface air temperature in magma colours with a white dashed contour at minus ten degrees, beside a line plot of the zonal mean against latitude

Figure 1:The figure to replicate. Left: the field as filled contours, with a single white contour drawn at −10 °C. Right: the zonal mean — the average along longitude — against latitude.

  1. Load the three arrays with np.load and convert the temperature to degrees celsius.

  2. Build the two panels with plt.subplots(1, 2, figsize=(11, 5), gridspec_kw={"width_ratios": [5, 1.5]}) — width_ratios is what makes the left panel wider than the right one.

  3. Draw the field with contourf, cmap="magma", levels=np.linspace(-30, 40, 15) and extend="both", then overlay contour at levels=[-10] with colors="w". matplotlib dashes a contour at a negative level on its own, which is where the dashed white line comes from.

  4. The right panel is the mean along the longitude axis, plotted against latitude — so the temperature goes on the x-axis. np.nanmean(temp_celsius, axis=1) gives it.

  5. Label both panels and the colorbar, and finish with plt.tight_layout().

  6. In a comment, say which hemisphere’s winter this snapshot was taken in, and how the zonal mean tells you.

Exercise 19: Historic significant earthquakes

The file cached below is NOAA’s catalog of significant earthquakes, reaching back to 2150 BCE: one row per event, tab-separated, 47 columns. The four you need are at positions 8 (FOCAL_DEPTH, km), 9 (EQ_PRIMARY, magnitude), 20 (LATITUDE) and 21 (LONGITUDE).

A scatter plot of earthquake longitude against latitude, coloured by the base-10 logarithm of focal depth and sized by magnitude

Figure 2:The figure to replicate: every event as one point, coloured by the base-10 logarithm of its focal depth and sized by its magnitude. No coastline is drawn — the plate boundaries are the data.

  1. Load the whole file with np.genfromtxt(path, delimiter="\t", skip_header=1) and pull out the four columns by position. Every non-numeric field becomes NaN, which is what you want here.

  2. Keep only the rows where depth and magnitude are both above zero and latitude and longitude are not NaN. Build one boolean mask for all four conditions: & combines two masks element-wise (“and”), and ~ flips one (“not”), so ~np.isnan(latitude) is True wherever the latitude is present.

  3. Scatter longitude against latitude, with colour mapped to np.log10 of the focal depth — the base-10 logarithm, applied element-wise, which spreads out depths running from under a kilometre to several hundred — and point area to the square of the magnitude. Add a labelled colorbar, axis labels, a title, and a dotted grid.

  4. In a comment, name the feature of the Earth that the empty regions of the plot trace out.

Exercise 20: Antarctic sea ice, summer against winter

Two real snapshots of Antarctic sea ice concentration from NOAA/NSIDC are cached below, as path_winter (1 August 2017) and path_summer (31 December 2017). Each holds seaice_conc_cdr, the ice concentration as a fraction from 0 to 1, on a south polar stereographic grid — its coordinates are 2D latitude/longitude arrays rather than simple 1D dimensions, so xarray’s own .plot() will not label it cleanly.

Two south polar stereographic maps of Antarctic sea ice concentration, one in austral winter with a wide ice ring and one in austral summer with much less ice

Figure 3:The figure to replicate: the same field on the same projection at two dates, with the land drawn in grey beneath the ice and one colorbar shared by both panels.

Rebuild it, and say which date shows more ice.

Exercise 21: The 2014 earthquakes over North America

The file cached below is the real USGS earthquake catalog for 2014 — one row per event worldwide, with latitude, longitude, depth and magnitude in columns 1 to 4. The target figure keeps the whole catalog and lets the map extent do the selecting, so what you see is a window onto a global dataset rather than a filtered one.

A Robinson-projection map of North America with state boundaries, lakes and rivers, and earthquake locations coloured by depth

Figure 4:The figure to replicate: magnitude 4 and above, on a Robinson projection cropped to North and Central America, over land, ocean, lake, river and state-boundary features.

Rebuild it.