Navigating the perils of a model grid#
Overview#
The coordinate system that is computationally practical for model simulations is not always intuitive for the slicing and dicing required by analysis.
Intuitive notion of spatial distributions in latitude-longitude coordinates
Reality of model grids
Imports#
%load_ext autoreload
%autoreload 2
import intake
import numpy as np
import xarray as xr
import cftime
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib import cm
import matplotlib.gridspec as gridspec
from matplotlib.ticker import FormatStrFormatter
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import cartopy.util as cutil
Intuition#
The world we live and move in is round, and locations can sensibly be described in terms of latitude and longitude. With this in mind, it doesn’t seem unreasonable to expect spatially resolved data to behave similarly. I know that precipitation follows a distribution of sorts. Let’s look at a quick example.
Data on a lat-lon grid#
# for Google Cloud:
col = intake.open_esm_datastore("https://storage.googleapis.com/cmip6/pangeo-cmip6.json")
query_d = dict(source_id='MIROC-ES2L',
experiment_id='historical',
grid_label='gn',
variable_id='pr',
member_id = 'r1i1p1f2',
table_id='Amon'
)
# sometimes it's necessary to request the data twice
search_res = col.search(**query_d).to_dataset_dict(require_all_on=['source_id', 'grid_label', 'table_id', 'variant_label'],#['source_id', 'experiment_id'],
xarray_open_kwargs={'consolidated': True,'use_cftime':True, 'chunks':{}},
storage_options={'token': 'anon'})
miroc_ds = list(search_res.values())[0]
--> The keys in the returned dictionary of datasets are constructed as follows:
'activity_id.institution_id.source_id.experiment_id.table_id.grid_label'
# convert prate_mean from kg/(m^2*s) to mm/day
prate_unit_conversion = 86400
miroc_ds=miroc_ds.assign(pr_mmday=miroc_ds['pr'] * prate_unit_conversion)
month = 3
year = 1877
m_snapshot_data = miroc_ds.isel(time=(miroc_ds.time.dt.year == year) & (miroc_ds.time.dt.month == month))
The data are arranged in a 64x128 array, with each entry corresponding to a (lon, lat) location. To plot a filled contour map, we make a couple of simple adjustments, appending 360 to the longitude coordinate (a vector), and appending the first value in every row of the array to the end. These make sure the data reflect longitude=0 is the same as longitude=360. Then we just pass the latitude and longitude vectors with the values at those points and are rewarded by a pleasing figure. Exceptionally orderly.
var = 'pr_mmday'
colorbar_units = '[mm/day]'
ppt_cmap = 'BuGn'
title_copy = 'Precipitation, {}/{}'
fig = plt.figure(figsize=(8, 4))
# add subplot with specified map projection and coastlines (GeoAxes)
ax2 = fig.add_subplot(projection=ccrs.Robinson(central_longitude=0))
ax2.add_feature(cfeature.COASTLINE, edgecolor='k',linewidth=.5)
# place data on coordinate system with continuous x axis (longitude axis)
tas_c, lonc = cutil.add_cyclic_point(m_snapshot_data[var], m_snapshot_data['lon'])
# plot contourf on ax2 (geosubplot)
cf2 = ax2.contourf(lonc,m_snapshot_data['lat'],tas_c.squeeze(), cmap=ppt_cmap,
transform=ccrs.PlateCarree())
# add annotations (colorbar, title)
plt.colorbar(cf2, label=colorbar_units, )
ax2.set_title(title_copy.format(month, year));
Point-wise data#
Also very tidy are datasets that reflect measurements taken at specific locations.
month = '13'
po4_woa = xr.open_dataset('https://www.ncei.noaa.gov/thredds-ocean/dodsC/ncei/woa/phosphate/all/1.00/woa18_all_p{}_01.nc'.format(month),
decode_times=False)
woa_snapshot_data = po4_woa.sel(depth=100)
woa_var = 'p_mn'
colorbar_units = r'[$\mu$mole/kg]'
ppt_cmap = 'BuGn'
title_copy = 'PO4, Winter'
fig = plt.figure(figsize=(8, 4))
# add subplot with specified map projection and coastlines (GeoAxes)
ax2 = fig.add_subplot(projection=ccrs.Robinson(central_longitude=0))
ax2.add_feature(cfeature.COASTLINE, edgecolor='k',linewidth=.5)
# place data on coordinate system with continuous x axis (longitude axis)
tas_c, lonc = cutil.add_cyclic_point(woa_snapshot_data[woa_var], woa_snapshot_data['lon'])
# plot contourf on ax2 (geosubplot)
cf2 = ax2.contourf(lonc,woa_snapshot_data['lat'],tas_c.squeeze(), cmap=ppt_cmap,
transform=ccrs.PlateCarree())
# add annotations (colorbar, title)
plt.colorbar(cf2, label=colorbar_units)
ax2.set_title(r'PO$_4$, Winter);
The reality of model output#
While we live in a continuous world, the model version of the world is discritized, and the spatial evolution of a variable through time is calculated using equations that describe fluxes and changes in state between and within gridcells. Suddenly a consistently-spaced latitude-longitude grid is a burden because the optimal way to set up the calculations is nowhere near our favored way of sailing the seas. To make things simpler, modelers choose a grid that will make their calculations simpler (not necessarily based on latitude or longitude), and then report the location the latitude-longitude location of each grid cell as part of their output.
Sometimes, output will be reprocessed such that it is reported with respect to a standardized grid. Those are pleasant days :)
PMIP LGM#
Let’s look at some output from a CMIP6 PMIP LGM experiment. First we’ll load it in, then extract a surface (data that fall on a single depth surface) at a particular timestamp.
# for Google Cloud:
col = intake.open_esm_datastore("https://storage.googleapis.com/cmip6/pangeo-cmip6.json")
query_d = dict(
experiment_id='lgm',
variable_id='po4',
)
# sometimes it's necessary to request the data twice
search_res = col.search(**query_d).to_dataset_dict(require_all_on=['source_id', 'grid_label', 'table_id', 'variant_label'],#['source_id', 'experiment_id'],
xarray_open_kwargs={'consolidated': True,'use_cftime':True, 'chunks':{}},
storage_options={'token': 'anon'})
miroc_ds_lgm = list(search_res.values())[0]
--> The keys in the returned dictionary of datasets are constructed as follows:
'activity_id.institution_id.source_id.experiment_id.table_id.grid_label'
month = 3
year = 3290
var = 'po4'
lev= 7.800e+01#2.590e+03#3.5#7.800e+01
colorbar_units = r'[$\mu$mole/kg]'
ppt_cmap = 'BuGn'
title_copy = 'Phosphate, {}/{}'
mlgm_snapshot_data = miroc_ds_lgm.isel(time=(miroc_ds_lgm.time.dt.year == year) & (miroc_ds_lgm.time.dt.month == month))
mlgm_snapshot_data = mlgm_snapshot_data.sel(lev=lev)
mlgm_snapshot_data = mlgm_snapshot_data[var].squeeze()
mlgm_snapshot_data = mlgm_snapshot_data.compute()
And let’s plot…
nc = 21
fig = plt.figure(figsize=(8, 4))
ax2 = fig.add_subplot()
ax2.contourf(mlgm_snapshot_data['longitude'],mlgm_snapshot_data['latitude'],mlgm_snapshot_data, nc, cmap='BuGn')#nc,**cf2_kwargs)
<matplotlib.contour.QuadContourSet at 0x1460000d0>
A mess… curious. Let’s have a look at the underlying grid.
plt.scatter(mlgm_snapshot_data.longitude, mlgm_snapshot_data.latitude, s=.0051);
Very rectangular south of about 60N, and potentially regular north of that. Let’s have a quick look a the distribution of the data to see whether there are outliers to be aware of might throw off a colorbar.
bins =mlgm_snapshot_data.plot.hist()
bin_edges = bins[1]
Might not hurt to curb that a bit.
# establish scale
max_lim = bin_edges[5]#c_snapshot_data.max()
min_lim = bin_edges[0]#c_snapshot_data.min()
def make_scalar_mappable(lims, cmap, n=None):
ax_norm = mpl.colors.Normalize(vmin=min(lims), vmax=max(lims), clip=False)
if type(cmap)==list:
if n is None:
ax_cmap = mpl.colors.LinearSegmentedColormap.from_list("MyCmapName",cmap)
else:
ax_cmap = mpl.colors.LinearSegmentedColormap.from_list("MyCmapName",cmap, N=n)
else:
if n is None:
ax_cmap = plt.get_cmap(cmap)
else:
ax_cmap = plt.get_cmap(cmap, n)
ax_sm = cm.ScalarMappable(norm=ax_norm, cmap=ax_cmap)
return ax_sm
Other notebooks in this PaleoBook make use of make_scalar_mappable
for producing scalar mappables, so instead of reproducing this function elsewhere, it will be added to plotting_helpers.py
in the support_functions directory. This keeps things to keep things neat and avoids the possibility of multiple versions of the functions.
n_levels= 60
ax2_levels = np.around(np.linspace(min_lim, max_lim, n_levels), decimals=6)#0.0026, .003, 15), decimals=6)
# make scalar mappable
ax2_sm_po4 = make_scalar_mappable([min_lim, max_lim],'BuGn' , n_levels)#[0.0026, .003]
cf2_kwargs_po4 = {'cmap':ax2_sm_po4.cmap,'levels':ax2_levels, 'norm' : ax2_sm_po4.norm}#mpl.colors.TwoSlopeNorm(vmin=min_val, vcenter=0.0026, vmax=max_val), 'levels':ax2_levels}#SymLogNorm(.00152, linscale=.003)}#, vmin=None, vmax=None, clip=False)ax2_sm.norm}
fig = plt.figure(figsize=(8, 4))
# add subplot with specified map projection and coastlines (GeoAxes)
ax2 = fig.add_subplot(projection=ccrs.Robinson(central_longitude=0))
ax2.add_feature(cfeature.COASTLINE, edgecolor='k',linewidth=.5)
# plot contourf on ax2 (geosubplot)
cf2 = ax2.contourf(mlgm_snapshot_data['longitude'],mlgm_snapshot_data['latitude'],
mlgm_snapshot_data, nc,
transform=ccrs.PlateCarree(),extend='both', **cf2_kwargs_po4)
# add annotations (colorbar, title)
plt.colorbar(cf2, label=colorbar_units, format=FormatStrFormatter('%g'))
ax2.set_title(title_copy.format(month, year));
Odd. And honestly, I have not yet fully unravelled the mystery here. However, thninking about the streaky one above, there may be something to be investigated. The longitude data does not appear to be sorted. This may be part of our problem.
Let’s pull the longitude grid, use argsort
which will yield an array that indicates how the values of each row would need to be reordered so that longitude would me monotonically increasing.
inds = mlgm_snapshot_data.longitude.argsort()
As an experiment, let’s reorder all the data accordingly. Longitude, latitude, and phosphate. Also, let’s append the first value of each row to the end of each row to account for the fact taht 0E=360E. (If we were working with an array of longitude values as we did with precipitation, we could use add_cyclic_point
.)
tmp = mlgm_snapshot_data
tmp_data = tmp.squeeze().data
tmp_lon = tmp.longitude.data
tmp_lat = tmp.latitude.data
numpy_tmp = np.array([tmp_data[row_num][inds[row_num]] for row_num in range(inds.shape[0])])
numpy_tmp_lon = np.array([tmp_lon[row_num][inds[row_num]] for row_num in range(inds.shape[0])])
numpy_tmp_lat = np.array([tmp_lat[row_num][inds[row_num]] for row_num in range(inds.shape[0])])
numpy_tmp= np.concatenate([numpy_tmp, numpy_tmp[:,0].reshape((len(numpy_tmp),1))], axis=1)
numpy_tmp_lat= np.concatenate([numpy_tmp_lat, numpy_tmp_lat[:,0].reshape((len(numpy_tmp_lat),1))], axis=1)
numpy_tmp_lon= np.concatenate([numpy_tmp_lon, np.ones((len(tmp_lon), 1))*360], axis=1)
And we’ll try again…
fig = plt.figure(figsize=(8, 4))
# 1 row, 2 columns, .05 space between columns, 8:.3 ratio of left column to right column
gs = gridspec.GridSpec(1, 2, wspace=0.05, width_ratios=[8, .3])
# add subplot with specified map projection and coastlines (GeoAxes)
ax2 = fig.add_subplot(gs[0, 0], projection=ccrs.Robinson(central_longitude=0))
ax2.add_feature(cfeature.COASTLINE, edgecolor='k',linewidth=.5)
cf2 = ax2.contourf(numpy_tmp_lon,numpy_tmp_lat,numpy_tmp, nc,
transform=ccrs.PlateCarree(),extend='both', **cf2_kwargs_po4)#transform=ccrs.PlateCarree()# levels=ax2_levels,
# add annotations (colorbar, title)
ax2_cb = fig.add_subplot(gs[0, 1])
cb2 = plt.colorbar(ax2_sm_po4,cax=ax2_cb, orientation='vertical',label=colorbar_units,
format=FormatStrFormatter('%g'))
ax2.set_title(title_copy.format(month, year));
With further investigation, it turns out that the restructuring we went through above is similar to specifying transform_first=True
. Nice to have some intuition for what that parameter affects. Have a look below:
fig = plt.figure(figsize=(8, 4))
# 1 row, 2 columns, .05 space between columns, 8:.3 ratio of left column to right column
gs = gridspec.GridSpec(1, 2, wspace=0.05, width_ratios=[8, .3])
# add subplot with specified map projection and coastlines (GeoAxes)
ax2 = fig.add_subplot(gs[0, 0], projection=ccrs.Robinson(central_longitude=0))
ax2.add_feature(cfeature.COASTLINE, edgecolor='k',linewidth=.5)
cf2 = ax2.contourf(mlgm_snapshot_data['longitude'],mlgm_snapshot_data['latitude'],mlgm_snapshot_data, nc,
transform=ccrs.PlateCarree(),extend='both',transform_first=True, **cf2_kwargs_po4)#transform=ccrs.PlateCarree()# levels=ax2_levels,
# add annotations (colorbar, title)
ax2_cb = fig.add_subplot(gs[0, 1])
cb2 = plt.colorbar(ax2_sm_po4,cax=ax2_cb, orientation='vertical',label=colorbar_units,
format=FormatStrFormatter('%g'))
ax2.set_title(title_copy.format(month, year));
Another option is to use pcolormesh
which avoids the smoothing that goes into calculating contours and colors based on the value associated with each cell. If the underlying grid is regular, filled contours are an option, otherwise, pcolormesh
is likely a better choice.
# plt.figure(figsize=(9,5));
fig = plt.figure(figsize=(8, 4))
# 1 row, 2 columns, .05 space between columns, 8:.3 ratio of left column to right column
gs = gridspec.GridSpec(1, 2, wspace=0.05, width_ratios=[8, .3])
# add subplot with specified map projection and coastlines (GeoAxes)
ax2 = fig.add_subplot(gs[0, 0], projection=ccrs.Robinson(central_longitude=0))
mlgm_snapshot_data.plot.pcolormesh(ax=ax2,
transform=ccrs.PlateCarree(),
x='longitude', y='latitude',
levels=ax2_levels,
cmap=cf2_kwargs_po4['cmap'],
norm=cf2_kwargs_po4['norm'],
add_colorbar=False);
ax2.coastlines(linewidth=.5)
# ax2 = add_lat_lon_labels(ax2, lon_locs=[-180, -30, 0, 45, 170])
ax2_cb = fig.add_subplot(gs[0, 1])
cb2 = plt.colorbar(ax2_sm_po4,cax=ax2_cb, orientation='vertical',label=colorbar_units,
format=FormatStrFormatter('%g')
)
cb2.minorticks_off()
C-iTRACE#
Now, let’s look at how this presents for the C-iTRACE output. The data arrray is the set of values at 11,600 locations arranged in a 116x100 array. The TLAT
and TLONG
coordinates each describe the latitude or longitude of each location.
Let’s load a slice of output and plot TLAT
and TLONG
to get a sense of the grid.
var = 'd18O'
colorbar_units = r'[\textperthousand]'
time = -21.99
depth = 750
title_var = r'$\delta^{18}$O'
traits = 'depth={} m, time={} kya'
import fsspec
url = 'https://g-f750ca.a78b8.36fe.data.globus.org/C-iTRACE/ctrace.decadal.{}.zarr'.format(var)
mapper = fsspec.get_mapper(url)
ctrace = xr.open_zarr(mapper)
def convert_z_to_meters(ds):
cm_to_m = 1/100
z_t = ds.z_t.data
ds['z_t_m'] = ("z_t", z_t*cm_to_m)
ds = ds.swap_dims({'z_t':'z_t_m'})
return ds
# convert depth from cm to m
ctrace =convert_z_to_meters(ctrace)
ctrace
<xarray.Dataset> Dimensions: (nlat: 116, nlon: 100, time: 2200, z_t_m: 60, d2: 2) Coordinates: TLAT (nlat, nlon) float64 dask.array<chunksize=(116, 100), meta=np.ndarray> TLONG (nlat, nlon) float64 dask.array<chunksize=(116, 100), meta=np.ndarray> * time (time) float64 -22.0 -21.99 -21.98 -21.97 ... -0.03 -0.02 -0.01 z_t (z_t_m) float32 500.0 1.5e+03 2.5e+03 ... 5.125e+05 5.375e+05 * z_t_m (z_t_m) float32 5.0 15.0 25.0 ... 4.875e+03 5.125e+03 5.375e+03 Dimensions without coordinates: nlat, nlon, d2 Data variables: d18O (time, z_t_m, nlat, nlon) float32 dask.array<chunksize=(275, 8, 15, 25), meta=np.ndarray> time_bound (time, d2) float64 dask.array<chunksize=(2200, 2), meta=np.ndarray> Attributes: (12/16) Conventions: CF-1.0; http://www.cgd.ucar.edu/cms/eaton/net... NCO: netCDF Operators version 4.9.5 (Homepage = ht... calendar: All years have exactly 365 days. cell_methods: cell_methods = time: mean ==> the variable va... contents: Diagnostic and Prognostic Variables conventions: CF-1.0; http://www.cgd.ucar.edu/cms/eaton/net... ... ... revision: $Name: ccsm3_0_1_beta22 $ source: POP, the NCAR/CSM Ocean Component start_time: This dataset was created on 2007-07-29 at 23:... tavg_sum: 2592000.0 tavg_sum_qflux: 2592000.0 title: b30.22_0kaDVT
- nlat: 116
- nlon: 100
- time: 2200
- z_t_m: 60
- d2: 2
- TLAT(nlat, nlon)float64dask.array<chunksize=(116, 100), meta=np.ndarray>
- long_name :
- array of t-grid latitudes
- units :
- degrees_north
Array Chunk Bytes 90.62 kiB 90.62 kiB Shape (116, 100) (116, 100) Dask graph 1 chunks in 2 graph layers Data type float64 numpy.ndarray - TLONG(nlat, nlon)float64dask.array<chunksize=(116, 100), meta=np.ndarray>
- long_name :
- array of t-grid longitudes
- units :
- degrees_east
Array Chunk Bytes 90.62 kiB 90.62 kiB Shape (116, 100) (116, 100) Dask graph 1 chunks in 2 graph layers Data type float64 numpy.ndarray - time(time)float64-22.0 -21.99 -21.98 ... -0.02 -0.01
- bounds :
- time_bound
- calendar :
- noleap
- cell_methods :
- time: mean
- long_name :
- time
- units :
- ka BP
array([-2.200000e+01, -2.199000e+01, -2.198000e+01, ..., -3.000069e-02, -2.000046e-02, -1.000023e-02])
- z_t(z_t_m)float32500.0 1.5e+03 ... 5.375e+05
- long_name :
- depth from surface to midpoint of layer
- positive :
- down
- units :
- centimeters
- valid_max :
- 537500.0
- valid_min :
- 500.0
array([5.000000e+02, 1.500000e+03, 2.500000e+03, 3.500000e+03, 4.500000e+03, 5.500000e+03, 6.500000e+03, 7.500000e+03, 8.500000e+03, 9.500000e+03, 1.050000e+04, 1.150000e+04, 1.250000e+04, 1.350000e+04, 1.450000e+04, 1.550000e+04, 1.650984e+04, 1.754790e+04, 1.862913e+04, 1.976603e+04, 2.097114e+04, 2.225783e+04, 2.364088e+04, 2.513702e+04, 2.676542e+04, 2.854837e+04, 3.051192e+04, 3.268680e+04, 3.510935e+04, 3.782276e+04, 4.087846e+04, 4.433777e+04, 4.827367e+04, 5.277280e+04, 5.793729e+04, 6.388626e+04, 7.075633e+04, 7.870025e+04, 8.788252e+04, 9.847059e+04, 1.106204e+05, 1.244567e+05, 1.400497e+05, 1.573946e+05, 1.764003e+05, 1.968944e+05, 2.186457e+05, 2.413972e+05, 2.649001e+05, 2.889385e+05, 3.133405e+05, 3.379793e+05, 3.627670e+05, 3.876452e+05, 4.125768e+05, 4.375392e+05, 4.625190e+05, 4.875083e+05, 5.125028e+05, 5.375000e+05], dtype=float32)
- z_t_m(z_t_m)float325.0 15.0 ... 5.125e+03 5.375e+03
array([5.000000e+00, 1.500000e+01, 2.500000e+01, 3.500000e+01, 4.500000e+01, 5.500000e+01, 6.500000e+01, 7.500000e+01, 8.500000e+01, 9.500000e+01, 1.050000e+02, 1.150000e+02, 1.250000e+02, 1.350000e+02, 1.450000e+02, 1.550000e+02, 1.650984e+02, 1.754790e+02, 1.862913e+02, 1.976603e+02, 2.097114e+02, 2.225783e+02, 2.364088e+02, 2.513701e+02, 2.676542e+02, 2.854836e+02, 3.051192e+02, 3.268680e+02, 3.510935e+02, 3.782276e+02, 4.087846e+02, 4.433777e+02, 4.827367e+02, 5.277280e+02, 5.793729e+02, 6.388626e+02, 7.075633e+02, 7.870025e+02, 8.788252e+02, 9.847058e+02, 1.106204e+03, 1.244567e+03, 1.400497e+03, 1.573946e+03, 1.764003e+03, 1.968944e+03, 2.186457e+03, 2.413971e+03, 2.649001e+03, 2.889385e+03, 3.133405e+03, 3.379793e+03, 3.627670e+03, 3.876452e+03, 4.125768e+03, 4.375393e+03, 4.625190e+03, 4.875083e+03, 5.125028e+03, 5.375000e+03], dtype=float32)
- d18O(time, z_t_m, nlat, nlon)float32dask.array<chunksize=(275, 8, 15, 25), meta=np.ndarray>
- cell_methods :
- time: mean
- grid_loc :
- 3111
- long_name :
- d18O
- units :
- permil
Array Chunk Bytes 5.70 GiB 3.15 MiB Shape (2200, 60, 116, 100) (275, 8, 15, 25) Dask graph 2048 chunks in 2 graph layers Data type float32 numpy.ndarray - time_bound(time, d2)float64dask.array<chunksize=(2200, 2), meta=np.ndarray>
- bounds :
- time_bound
- calendar :
- noleap
- cell_methods :
- time: mean
- long_name :
- time
- units :
- ka BP
Array Chunk Bytes 34.38 kiB 34.38 kiB Shape (2200, 2) (2200, 2) Dask graph 1 chunks in 2 graph layers Data type float64 numpy.ndarray
- timePandasIndex
PandasIndex(Index([ -22.0, -21.9899997711182, -21.9799995422363, -21.9699993133545, -21.9599990844727, -21.9500007629395, -21.9400005340576, -21.9300003051758, -21.9200000762939, -21.9099998474121, ... -0.100000381469727, -0.0900001525878906, -0.0799999237060547, -0.0699996948242188, -0.0600013732910156, -0.0500011444091797, -0.0400009155273438, -0.0300006866455078, -0.0200004577636719, -0.0100002288818359], dtype='float64', name='time', length=2200))
- z_t_mPandasIndex
PandasIndex(Index([ 5.0, 15.0, 25.0, 35.0, 45.0, 55.0, 65.0, 75.0, 85.0, 95.0, 105.0, 115.0, 125.0, 135.0, 145.0, 155.0, 165.098388671875, 175.47903442382812, 186.291259765625, 197.66026306152344, 209.7113800048828, 222.57827758789062, 236.40882873535156, 251.37014770507812, 267.6542053222656, 285.483642578125, 305.11920166015625, 326.86798095703125, 351.0934753417969, 378.22760009765625, 408.7846374511719, 443.377685546875, 482.7366943359375, 527.7279663085938, 579.3728637695312, 638.8626098632812, 707.5632934570312, 787.0025024414062, 878.8251953125, 984.705810546875, 1106.2042236328125, 1244.56689453125, 1400.4971923828125, 1573.9464111328125, 1764.0032958984375, 1968.9442138671875, 2186.45654296875, 2413.971435546875, 2649.001220703125, 2889.384521484375, 3133.404541015625, 3379.79345703125, 3627.670166015625, 3876.451904296875, 4125.76806640625, 4375.392578125, 4625.1904296875, 4875.08349609375, 5125.02783203125, 5375.0], dtype='float32', name='z_t_m'))
- Conventions :
- CF-1.0; http://www.cgd.ucar.edu/cms/eaton/netcdf/CF-current.htm
- NCO :
- netCDF Operators version 4.9.5 (Homepage = http://nco.sf.net, Code = http://github.com/nco/nco)
- calendar :
- All years have exactly 365 days.
- cell_methods :
- cell_methods = time: mean ==> the variable values are averaged over the time interval between the previous time coordinate and the current one. cell_methods absent ==> the variable values are at the time given by the current time coordinate.
- contents :
- Diagnostic and Prognostic Variables
- conventions :
- CF-1.0; http://www.cgd.ucar.edu/cms/eaton/netcdf/CF-current.htm
- history :
- Tue May 11 11:24:12 2021: ncks -A /glade/scratch/strandwg/AJahn/subset_time_TraCE.nc /glade/scratch/zanowski/citrace_output/ctrace.decadal.d18O.nc Wed Apr 21 09:42:57 2021: ncks -v d18O /glade/scratch/sgu28/ctrace_transient/ctrace_decadal_all/ctrace.decadal.evo.nc ctrace.decadal.d18O.nc Fri Apr 26 10:41:02 2019: ncrcat c40.22_0ka.ctrace.t01.pop.h.00012000.decade.nc c40.20_0ka.ctrace.t01.pop.h.00011000.decade.nc c40.19_0ka.ctrace.t01.pop.h.00010500.decade.nc c40.18_5ka.ctrace.t01.pop.h.00010100.decade.nc c40.18_4ka.ctrace.t01.pop.h.00010900.decade.nc c40.17_5ka.ctrace.t01.pop.h.00010500.decade.nc c40.17_0ka.ctrace.t01.pop.h.00011000.decade.nc c40.16_0ka.ctrace.t01.pop.h.00011000.decade.nc c40.15_0ka.ctrace.t01.pop.h.00010100.decade.nc c40.14_9ka.ctrace.t01.pop.h.00010550.decade.nc c40.14_35ka.ctrace.t01.pop.h.00010480.decade.nc c40.13_87ka.ctrace.t01.pop.h.00010770.decade.nc c40.13_1ka.ctrace.t01.pop.h.00010200.decade.nc c40.12_9ka.ctrace.t01.pop.h.00010400.decade.nc c40.12_5ka.ctrace.t01.pop.h.00010500.decade.nc c40.12_0ka.ctrace.t01.pop.h.00010300.decade.nc c40.11_7ka.ctrace.t01.pop.h.00010400.decade.nc c40.11_3ka.ctrace.t01.pop.h.00010500.decade.nc c40.10_8ka.ctrace.t01.pop.h.00010600.decade.nc c40.10_2ka.ctrace.t01.pop.h.00010500.decade.nc c40.09_7ka.ctrace.t01.pop.h.00010500.decade.nc c40.09_2ka.ctrace.t01.pop.h.00010500.decade.nc c40.08_7ka.ctrace.t01.pop.h.00010200.decade.nc c40.08_5ka.ctrace.t01.pop.h.00010500.decade.nc c40.08_0ka.ctrace.t01.pop.h.00010400.decade.nc c40.07_6ka.ctrace.t01.pop.h.00010400.decade.nc c40.07_2ka.ctrace.t02.pop.h.02410491.decade.nc c40.07_2ka.ctrace.t01.pop.h.00010241.decade.nc c40.06_7ka.ctrace.t01.pop.h.00010500.decade.nc c40.06_2ka.ctrace.t01.pop.h.00010500.decade.nc c40.05_7ka.ctrace.t01.pop.h.00010700.decade.nc c40.05_0ka.ctrace.t01.pop.h.00011000.decade.nc c40.04_0ka.ctrace.t01.pop.h.00010800.decade.nc c40.03_2ka.ctrace.t01.pop.h.00010800.decade.nc c40.02_4ka.ctrace.t01.pop.h.00011000.decade.nc c40.01_4ka.ctrace.t01.pop.h.00011000.decade.nc c40.00_4ka.ctrace.t01.pop.h.00010400.decade.nc ctrace.decadal.evo.nc Wed Jun 28 05:49:25 2017: ncrcat c40.22_0ka.ctrace.t01.pop.h.decade.0001.nc c40.22_0ka.ctrace.t01.pop.h.decade.0011.nc c40.22_0ka.ctrace.t01.pop.h.decade.0021.nc c40.22_0ka.ctrace.t01.pop.h.decade.0031.nc c40.22_0ka.ctrace.t01.pop.h.decade.0041.nc c40.22_0ka.ctrace.t01.pop.h.decade.0051.nc c40.22_0ka.ctrace.t01.pop.h.decade.0061.nc c40.22_0ka.ctrace.t01.pop.h.decade.0071.nc c40.22_0ka.ctrace.t01.pop.h.decade.0081.nc c40.22_0ka.ctrace.t01.pop.h.decade.0091.nc c40.22_0ka.ctrace.t01.pop.h.decade.0101.nc c40.22_0ka.ctrace.t01.pop.h.decade.0111.nc c40.22_0ka.ctrace.t01.pop.h.decade.0121.nc c40.22_0ka.ctrace.t01.pop.h.decade.0131.nc c40.22_0ka.ctrace.t01.pop.h.decade.0141.nc c40.22_0ka.ctrace.t01.pop.h.decade.0151.nc c40.22_0ka.ctrace.t01.pop.h.decade.0161.nc c40.22_0ka.ctrace.t01.pop.h.decade.0171.nc c40.22_0ka.ctrace.t01.pop.h.decade.0181.nc c40.22_0ka.ctrace.t01.pop.h.decade.0191.nc c40.22_0ka.ctrace.t01.pop.h.decade.0201.nc c40.22_0ka.ctrace.t01.pop.h.decade.0211.nc c40.22_0ka.ctrace.t01.pop.h.decade.0221.nc c40.22_0ka.ctrace.t01.pop.h.decade.0231.nc c40.22_0ka.ctrace.t01.pop.h.decade.0241.nc c40.22_0ka.ctrace.t01.pop.h.decade.0251.nc c40.22_0ka.ctrace.t01.pop.h.decade.0261.nc c40.22_0ka.ctrace.t01.pop.h.decade.0271.nc c40.22_0ka.ctrace.t01.pop.h.decade.0281.nc c40.22_0ka.ctrace.t01.pop.h.decade.0291.nc c40.22_0ka.ctrace.t01.pop.h.decade.0301.nc c40.22_0ka.ctrace.t01.pop.h.decade.0311.nc c40.22_0ka.ctrace.t01.pop.h.decade.0321.nc c40.22_0ka.ctrace.t01.pop.h.decade.0331.nc c40.22_0ka.ctrace.t01.pop.h.decade.0341.nc c40.22_0ka.ctrace.t01.pop.h.decade.0351.nc c40.22_0ka.ctrace.t01.pop.h.decade.0361.nc c40.22_0ka.ctrace.t01.pop.h.decade.0371.nc c40.22_0ka.ctrace.t01.pop.h.decade.0381.nc c40.22_0ka.ctrace.t01.pop.h.decade.0391.nc c40.22_0ka.ctrace.t01.pop.h.decade.0401.nc c40.22_0ka.ctrace.t01.pop.h.decade.0411.nc c40.22_0ka.ctrace.t01.pop.h.decade.0421.nc c40.22_0ka.ctrace.t01.pop.h.decade.0431.nc c40.22_0ka.ctrace.t01.pop.h.decade.0441.nc c40.22_0ka.ctrace.t01.pop.h.decade.0451.nc c40.22_0ka.ctrace.t01.pop.h.decade.0461.nc c40.22_0ka.ctrace.t01.pop.h.decade.0471.nc c40.22_0ka.ctrace.t01.pop.h.decade.0481.nc c40.22_0ka.ctrace.t01.pop.h.decade.0491.nc c40.22_0ka.ctrace.t01.pop.h.decade.0501.nc c40.22_0ka.ctrace.t01.pop.h.decade.0511.nc c40.22_0ka.ctrace.t01.pop.h.decade.0521.nc c40.22_0ka.ctrace.t01.pop.h.decade.0531.nc c40.22_0ka.ctrace.t01.pop.h.decade.0541.nc c40.22_0ka.ctrace.t01.pop.h.decade.0551.nc c40.22_0ka.ctrace.t01.pop.h.decade.0561.nc c40.22_0ka.ctrace.t01.pop.h.decade.0571.nc c40.22_0ka.ctrace.t01.pop.h.decade.0581.nc c40.22_0ka.ctrace.t01.pop.h.decade.0591.nc c40.22_0ka.ctrace.t01.pop.h.decade.0601.nc c40.22_0ka.ctrace.t01.pop.h.decade.0611.nc c40.22_0ka.ctrace.t01.pop.h.decade.0621.nc c40.22_0ka.ctrace.t01.pop.h.decade.0631.nc c40.22_0ka.ctrace.t01.pop.h.decade.0641.nc c40.22_0ka.ctrace.t01.pop.h.decade.0651.nc c40.22_0ka.ctrace.t01.pop.h.decade.0661.nc c40.22_0ka.ctrace.t01.pop.h.decade.0671.nc c40.22_0ka.ctrace.t01.pop.h.decade.0681.nc c40.22_0ka.ctrace.t01.pop.h.decade.0691.nc c40.22_0ka.ctrace.t01.pop.h.decade.0701.nc c40.22_0ka.ctrace.t01.pop.h.decade.0711.nc c40.22_0ka.ctrace.t01.pop.h.decade.0721.nc c40.22_0ka.ctrace.t01.pop.h.decade.0731.nc c40.22_0ka.ctrace.t01.pop.h.decade.0741.nc c40.22_0ka.ctrace.t01.pop.h.decade.0751.nc c40.22_0ka.ctrace.t01.pop.h.decade.0761.nc c40.22_0ka.ctrace.t01.pop.h.decade.0771.nc c40.22_0ka.ctrace.t01.pop.h.decade.0781.nc c40.22_0ka.ctrace.t01.pop.h.decade.0791.nc c40.22_0ka.ctrace.t01.pop.h.decade.0801.nc c40.22_0ka.ctrace.t01.pop.h.decade.0811.nc c40.22_0ka.ctrace.t01.pop.h.decade.0821.nc c40.22_0ka.ctrace.t01.pop.h.decade.0831.nc c40.22_0ka.ctrace.t01.pop.h.decade.0841.nc c40.22_0ka.ctrace.t01.pop.h.decade.0851.nc c40.22_0ka.ctrace.t01.pop.h.decade.0861.nc c40.22_0ka.ctrace.t01.pop.h.decade.0871.nc c40.22_0ka.ctrace.t01.pop.h.decade.0881.nc c40.22_0ka.ctrace.t01.pop.h.decade.0891.nc c40.22_0ka.ctrace.t01.pop.h.decade.0901.nc c40.22_0ka.ctrace.t01.pop.h.decade.0911.nc c40.22_0ka.ctrace.t01.pop.h.decade.0921.nc c40.22_0ka.ctrace.t01.pop.h.decade.0931.nc c40.22_0ka.ctrace.t01.pop.h.decade.0941.nc c40.22_0ka.ctrace.t01.pop.h.decade.0951.nc c40.22_0ka.ctrace.t01.pop.h.decade.0961.nc c40.22_0ka.ctrace.t01.pop.h.decade.0971.nc c40.22_0ka.ctrace.t01.pop.h.decade.0981.nc c40.22_0ka.ctrace.t01.pop.h.decade.0991.nc c40.22_0ka.ctrace.t01.pop.h.decade.1001.nc c40.22_0ka.ctrace.t01.pop.h.decade.1011.nc c40.22_0ka.ctrace.t01.pop.h.decade.1021.nc c40.22_0ka.ctrace.t01.pop.h.decade.1031.nc c40.22_0ka.ctrace.t01.pop.h.decade.1041.nc c40.22_0ka.ctrace.t01.pop.h.decade.1051.nc c40.22_0ka.ctrace.t01.pop.h.decade.1061.nc c40.22_0ka.ctrace.t01.pop.h.decade.1071.nc c40.22_0ka.ctrace.t01.pop.h.decade.1081.nc c40.22_0ka.ctrace.t01.pop.h.decade.1091.nc c40.22_0ka.ctrace.t01.pop.h.decade.1101.nc c40.22_0ka.ctrace.t01.pop.h.decade.1111.nc c40.22_0ka.ctrace.t01.pop.h.decade.1121.nc c40.22_0ka.ctrace.t01.pop.h.decade.1131.nc c40.22_0ka.ctrace.t01.pop.h.decade.1141.nc c40.22_0ka.ctrace.t01.pop.h.decade.1151.nc c40.22_0ka.ctrace.t01.pop.h.decade.1161.nc c40.22_0ka.ctrace.t01.pop.h.decade.1171.nc c40.22_0ka.ctrace.t01.pop.h.decade.1181.nc c40.22_0ka.ctrace.t01.pop.h.decade.1191.nc c40.22_0ka.ctrace.t01.pop.h.decade.1201.nc c40.22_0ka.ctrace.t01.pop.h.decade.1211.nc c40.22_0ka.ctrace.t01.pop.h.decade.1221.nc c40.22_0ka.ctrace.t01.pop.h.decade.1231.nc c40.22_0ka.ctrace.t01.pop.h.decade.1241.nc c40.22_0ka.ctrace.t01.pop.h.decade.1251.nc c40.22_0ka.ctrace.t01.pop.h.decade.1261.nc c40.22_0ka.ctrace.t01.pop.h.decade.1271.nc c40.22_0ka.ctrace.t01.pop.h.decade.1281.nc c40.22_0ka.ctrace.t01.pop.h.decade.1291.nc c40.22_0ka.ctrace.t01.pop.h.decade.1301.nc c40.22_0ka.ctrace.t01.pop.h.decade.1311.nc c40.22_0ka.ctrace.t01.pop.h.decade.1321.nc c40.22_0ka.ctrace.t01.pop.h.decade.1331.nc c40.22_0ka.ctrace.t01.pop.h.decade.1341.nc c40.22_0ka.ctrace.t01.pop.h.decade.1351.nc c40.22_0ka.ctrace.t01.pop.h.decade.1361.nc c40.22_0ka.ctrace.t01.pop.h.decade.1371.nc c40.22_0ka.ctrace.t01.pop.h.decade.1381.nc c40.22_0ka.ctrace.t01.pop.h.decade.1391.nc c40.22_0ka.ctrace.t01.pop.h.decade.1401.nc c40.22_0ka.ctrace.t01.pop.h.decade.1411.nc c40.22_0ka.ctrace.t01.pop.h.decade.1421.nc c40.22_0ka.ctrace.t01.pop.h.decade.1431.nc c40.22_0ka.ctrace.t01.pop.h.decade.1441.nc c40.22_0ka.ctrace.t01.pop.h.decade.1451.nc c40.22_0ka.ctrace.t01.pop.h.decade.1461.nc c40.22_0ka.ctrace.t01.pop.h.decade.1471.nc c40.22_0ka.ctrace.t01.pop.h.decade.1481.nc c40.22_0ka.ctrace.t01.pop.h.decade.1491.nc c40.22_0ka.ctrace.t01.pop.h.decade.1501.nc c40.22_0ka.ctrace.t01.pop.h.decade.1511.nc c40.22_0ka.ctrace.t01.pop.h.decade.1521.nc c40.22_0ka.ctrace.t01.pop.h.decade.1531.nc c40.22_0ka.ctrace.t01.pop.h.decade.1541.nc c40.22_0ka.ctrace.t01.pop.h.decade.1551.nc c40.22_0ka.ctrace.t01.pop.h.decade.1561.nc c40.22_0ka.ctrace.t01.pop.h.decade.1571.nc c40.22_0ka.ctrace.t01.pop.h.decade.1581.nc c40.22_0ka.ctrace.t01.pop.h.decade.1591.nc c40.22_0ka.ctrace.t01.pop.h.decade.1601.nc c40.22_0ka.ctrace.t01.pop.h.decade.1611.nc c40.22_0ka.ctrace.t01.pop.h.decade.1621.nc c40.22_0ka.ctrace.t01.pop.h.decade.1631.nc c40.22_0ka.ctrace.t01.pop.h.decade.1641.nc c40.22_0ka.ctrace.t01.pop.h.decade.1651.nc c40.22_0ka.ctrace.t01.pop.h.decade.1661.nc c40.22_0ka.ctrace.t01.pop.h.decade.1671.nc c40.22_0ka.ctrace.t01.pop.h.decade.1681.nc c40.22_0ka.ctrace.t01.pop.h.decade.1691.nc c40.22_0ka.ctrace.t01.pop.h.decade.1701.nc c40.22_0ka.ctrace.t01.pop.h.decade.1711.nc c40.22_0ka.ctrace.t01.pop.h.decade.1721.nc c40.22_0ka.ctrace.t01.pop.h.decade.1731.nc c40.22_0ka.ctrace.t01.pop.h.decade.1741.nc c40.22_0ka.ctrace.t01.pop.h.decade.1751.nc c40.22_0ka.ctrace.t01.pop.h.decade.1761.nc c40.22_0ka.ctrace.t01.pop.h.decade.1771.nc c40.22_0ka.ctrace.t01.pop.h.decade.1781.nc c40.22_0ka.ctrace.t01.pop.h.decade.1791.nc c40.22_0ka.ctrace.t01.pop.h.decade.1801.nc c40.22_0ka.ctrace.t01.pop.h.decade.1811.nc c40.22_0ka.ctrace.t01.pop.h.decade.1821.nc c40.22_0ka.ctrace.t01.pop.h.decade.1831.nc c40.22_0ka.ctrace.t01.pop.h.decade.1841.nc c40.22_0ka.ctrace.t01.pop.h.decade.1851.nc c40.22_0ka.ctrace.t01.pop.h.decade.1861.nc c40.22_0ka.ctrace.t01.pop.h.decade.1871.nc c40.22_0ka.ctrace.t01.pop.h.decade.1881.nc c40.22_0ka.ctrace.t01.pop.h.decade.1891.nc c40.22_0ka.ctrace.t01.pop.h.decade.1901.nc c40.22_0ka.ctrace.t01.pop.h.decade.1911.nc c40.22_0ka.ctrace.t01.pop.h.decade.1921.nc c40.22_0ka.ctrace.t01.pop.h.decade.1931.nc c40.22_0ka.ctrace.t01.pop.h.decade.1941.nc c40.22_0ka.ctrace.t01.pop.h.decade.1951.nc c40.22_0ka.ctrace.t01.pop.h.decade.1961.nc c40.22_0ka.ctrace.t01.pop.h.decade.1971.nc c40.22_0ka.ctrace.t01.pop.h.decade.1981.nc c40.22_0ka.ctrace.t01.pop.h.decade.1991.nc c40.22_0ka.ctrace.t01.pop.h.00012000.decade.nc Fri Jun 2 12:07:42 2017: ncea -O -n 10,4,1 c40.22_0ka.ctrace.t01.pop.h.0001.nc c40.22_0ka.ctrace.t01.pop.h.decade.0001.nc Fri Jun 2 10:15:12 2017: ncra -O c40.22_0ka.ctrace.t01.pop.h.0001.nc c40.22_0ka.ctrace.t01.pop.h.0001.ann.nc Fri Jun 2 10:14:44 2017: ncrcat -O c40.22_0ka.ctrace.t01.pop.h.0001-01.nc c40.22_0ka.ctrace.t01.pop.h.0001-02.nc c40.22_0ka.ctrace.t01.pop.h.0001-03.nc c40.22_0ka.ctrace.t01.pop.h.0001-04.nc c40.22_0ka.ctrace.t01.pop.h.0001-05.nc c40.22_0ka.ctrace.t01.pop.h.0001-06.nc c40.22_0ka.ctrace.t01.pop.h.0001-07.nc c40.22_0ka.ctrace.t01.pop.h.0001-08.nc c40.22_0ka.ctrace.t01.pop.h.0001-09.nc c40.22_0ka.ctrace.t01.pop.h.0001-10.nc c40.22_0ka.ctrace.t01.pop.h.0001-11.nc c40.22_0ka.ctrace.t01.pop.h.0001-12.nc c40.22_0ka.ctrace.t01.pop.h.0001.nc none
- history_of_appended_files :
- Tue May 11 11:24:12 2021: Appended file /glade/scratch/strandwg/AJahn/subset_time_TraCE.nc had no "history" attribute
- nco_openmp_thread_number :
- 1
- nsteps_total :
- 390
- revision :
- $Name: ccsm3_0_1_beta22 $
- source :
- POP, the NCAR/CSM Ocean Component
- start_time :
- This dataset was created on 2007-07-29 at 23:13:09.2
- tavg_sum :
- 2592000.0
- tavg_sum_qflux :
- 2592000.0
- title :
- b30.22_0kaDVT
We will want to do this same conversion throughout this PaleoBook, so let’s stash convert_z_to_meters
in data_processing.py
in the support_functions directory.
c_snapshot_data = ctrace.sel(time = time, method='nearest')
c_snapshot_data = c_snapshot_data.sel(z_t_m=depth, method='nearest')
c_snapshot_data = c_snapshot_data[var].squeeze()
c_snapshot_data = c_snapshot_data.compute()
fig = plt.figure(figsize=(8, 4))
ax2 = fig.add_subplot()
ax2.contourf(c_snapshot_data['TLONG'],c_snapshot_data['TLAT'],c_snapshot_data, nc, cmap='BuGn')#nc,**cf2_kwargs)
<matplotlib.contour.QuadContourSet at 0x14e41cd90>
fig = plt.figure(figsize=(6,6))
ax = fig.add_subplot()
ax.scatter(c_snapshot_data.TLONG, c_snapshot_data.TLAT, s=.51);
ax.set_xlabel(r'Longitude ($^{\circ}$E)')
ax.set_ylabel(r'Latitude ($^{\circ}$N)')
Text(0, 0.5, 'Latitude ($^{\\circ}$N)')
So while the data sit on a 116 x 100 grid, neither the rows, nor the columns follow lines of constant latitude or longitude north of about 25N.
bins =c_snapshot_data.plot.hist()
bin_edges = bins[1]
# establish scale
max_lim = bin_edges[3]#c_snapshot_data.max()
min_lim = bin_edges[0]#c_snapshot_data.min()
n_levels= 60
ax2_levels = np.around(np.linspace(min_lim, max_lim, n_levels), decimals=4)
# make scalar mappable
ax2_sm_d18O = make_scalar_mappable([min_lim, max_lim],'BuGn' , n_levels)
cf2_kwargs_d18O = {'cmap':ax2_sm_d18O.cmap,'levels':ax2_levels, 'norm' : ax2_sm_d18O.norm}
Given the model grid issue, we can go about this in one of three ways:
1. Scatterplot#
fig = plt.figure(figsize=(8, 4))
# 1 row, 2 columns, .05 space between columns, 8:.3 ratio of left column to right column
gs = gridspec.GridSpec(1, 2, wspace=0.05, width_ratios=[8, .3])
# add subplot with specified map projection and coastlines (GeoAxes)
ax2 = fig.add_subplot(gs[0, 0], projection=ccrs.Robinson(central_longitude=0))
ax2.add_feature(cfeature.COASTLINE, edgecolor='k',linewidth=.5)
ax2.scatter(c_snapshot_data.TLONG,c_snapshot_data.TLAT,c=c_snapshot_data, transform=ccrs.PlateCarree(),
cmap=cf2_kwargs_d18O['cmap'], norm=cf2_kwargs_d18O['norm'])
ax2.add_feature(cfeature.LAND, zorder=14)
ax2_cb = fig.add_subplot(gs[0, 1])
cb2 = plt.colorbar(ax2_sm_d18O,cax=ax2_cb, orientation='vertical',
label=' '.join([title_var,colorbar_units]),
format=FormatStrFormatter('%g'))
ax2.set_title(traits.format(depth, -1*time));
2. Pcolormesh#
# plt.figure(figsize=(9,5));
fig = plt.figure(figsize=(8, 4))
# 1 row, 2 columns, .05 space between columns, 8:.3 ratio of left column to right column
gs = gridspec.GridSpec(1, 2, wspace=0.05, width_ratios=[8, .3])
# add subplot with specified map projection and coastlines (GeoAxes)
ax2 = fig.add_subplot(gs[0, 0], projection=ccrs.Robinson(central_longitude=0))
c_snapshot_data.plot.pcolormesh(ax=ax2,
transform=ccrs.PlateCarree(),
x='TLONG', y='TLAT',
levels=ax2_levels,
cmap=cf2_kwargs_d18O['cmap'],
norm=cf2_kwargs_d18O['norm'],
add_colorbar=False);
ax2.coastlines(linewidth=.5)
ax2.add_feature(cfeature.LAND, zorder=14)
ax2_cb = fig.add_subplot(gs[0, 1])
cb2 = plt.colorbar(ax2_sm_d18O,cax=ax2_cb, orientation='vertical',
label=' '.join([title_var,colorbar_units]),
format=FormatStrFormatter('%g'))
ax2.set_title(traits.format(depth, -1*time));
3. Filled Contour#
Or as a filled contour, but ONLY if we specify to transform the data before doing the contour calculations.
fig = plt.figure(figsize=(8, 4))
# 1 row, 2 columns, .05 space between columns, 8:.3 ratio of left column to right column
gs = gridspec.GridSpec(1, 2, wspace=0.05, width_ratios=[8, .3])
# add subplot with specified map projection and coastlines (GeoAxes)
ax2 = fig.add_subplot(gs[0, 0], projection=ccrs.Robinson(central_longitude=0))
ax2.add_feature(cfeature.COASTLINE, edgecolor='k',linewidth=.5)
cf2 = ax2.contourf(c_snapshot_data.TLONG,c_snapshot_data.TLAT,c_snapshot_data, ax2_levels,#cmap='BuGn',
transform=ccrs.PlateCarree(),cmap=cf2_kwargs_d18O['cmap'], transform_first=True,
norm=cf2_kwargs_d18O['norm'], extent='both'#, **cf2_kwargs)#transform=ccrs.PlateCarree()# levels=ax2_levels,
)
ax2.add_feature(cfeature.LAND, zorder=14)
# add annotations (colorbar, title)
ax2_cb = fig.add_subplot(gs[0, 1])
cb2 = plt.colorbar(ax2_sm_d18O,cax=ax2_cb, orientation='vertical',label=' '.join([title_var,colorbar_units]),
format=FormatStrFormatter('%g'))
ax2.set_title(traits.format(depth, -1*time));
Selecting along lat/lon#
From the perspective of xarray there are dimensions, which describe degrees of freedom, and coordinates which create context around those dimensions. For example, in the examples below, there are spatial dimensions that describe the number of points in each aspect of the underlying grid, however there are separate coordinates that associate a point on the grid with a lat-lon pair.
When a dimension is also a coordinate, we can use .sel()
to select using either the dimension system or the coordinate system. Furthermore, we can specify .sel(method='nearest')
to return the nearest match.
Selecting using coordinate-dimensions#
time and depth are consistent throughout the dataset
# select data closest to specified time and depth values
snapshot_ds = ctrace.sel(time = time, z_t_m=depth, method='nearest')
Selecting using dimensions#
The histogram of data on the lon dimension with index 55 shows that though there is a dominent longitude band, there are multiple longitudes involved.
np.median(snapshot_ds.sel(nlon=55).TLONG.data)
161.30101007080071
# select data on a single dimension value
sns.histplot(x=snapshot_ds.sel(nlon=55).TLONG.data)
<Axes: ylabel='Count'>
Selecting using where
#
If we were resolute about filtering by longitude, we could filter the TLONG arrays for values in a certain window.
def between(ds, var, lims):
ds.coords[var] = ds.coords[var].load()
_ds = ds.where((ds.coords[var]<max(lims)) & (ds.coords[var]>min(lims)), drop=True)
return _ds
We’ll add between
to data_processing.py
for future use.
section_ds = between(snapshot_ds, 'TLONG',[160, 163])
section_ds['d18O'].TLONG.mean(dim='nlat')
<xarray.DataArray 'TLONG' (nlon: 5)> array([147.29784258, 151.13834525, 154.95023564, 158.73244459, 162.48678877]) Coordinates: time float64 -21.99 z_t float32 7.87e+04 z_t_m float32 787.0 Dimensions without coordinates: nlon
- nlon: 5
- 147.3 151.1 155.0 158.7 162.5
array([147.29784258, 151.13834525, 154.95023564, 158.73244459, 162.48678877])
- time()float64-21.99
- long_name :
- time
- units :
- ka BP
- bounds :
- time_bound
- calendar :
- noleap
- cell_methods :
- time: mean
array(-21.98999977)
- z_t()float327.87e+04
- long_name :
- depth from surface to midpoint of layer
- units :
- centimeters
- positive :
- down
- valid_min :
- 500.0
- valid_max :
- 537500.0
array(78700.25, dtype=float32)
- z_t_m()float32787.0
array(787.0025, dtype=float32)
# nan_filter = np.isnan(section_ds['d18O'].data)
sns.histplot(x=section_ds['d18O'].TLONG.data.ravel())
<Axes: ylabel='Count'>
That’s thought provoking…we filtered for (160, 163)… Let’s fix up a scatterplot of the the latitude-longitude pairs still part of this filtered dataset.
plt.scatter(section_ds['d18O'].TLONG.data, section_ds['d18O'].TLAT.data)
<matplotlib.collections.PathCollection at 0x1d41d9e70>
It becomes clear! The filtered dataset retains all nlon values that have an TLONG value in our range. If we pull the data out and use numpy to make a masking array for the nans, and then apply that mask to the TLONG array, we will hopefully find that the values still in our filtered dataset correspond to longitude values in our desired range. Let’s see…
nan_filter = np.isnan(section_ds['d18O'].data)
sns.histplot(x=section_ds.TLONG.data[~nan_filter])
<Axes: ylabel='Count'>
Success! This exercise has also clarified a bit more about how Xarray treats dimensions and coordinates. Below are a pair of maps illustrating that the range of TLONG
values yielded by a where
filter is more restricted than that based on a single nlon
dimension value. The color is simply to highlight which locations are contained in each set.
fig = plt.figure(figsize=(10, 4))
# 1 row, 2 columns, .05 space between columns, 8:.3 ratio of left column to right column
gs = gridspec.GridSpec(1, 3, wspace=0.05, width_ratios=[8,8, .3])
# add subplot with specified map projection and coastlines (GeoAxes)
ax_sel = fig.add_subplot(gs[0, 0], projection=ccrs.Robinson(central_longitude=0))
ax_sel.add_feature(cfeature.COASTLINE, edgecolor='k',linewidth=.5)
scatter = ax_sel.scatter(section_ds.TLONG,section_ds.TLAT,c=section_ds['d18O'].squeeze(), transform=ccrs.PlateCarree(),
s=5,cmap='viridis', norm=cf2_kwargs['norm'])
ax_sel.add_feature(cfeature.LAND, zorder=14)
ax_sel.set_global()
ax_sel.set_title('filtered by "where"')
ax_nlon = fig.add_subplot(gs[0, 1], projection=ccrs.Robinson(central_longitude=0))
ax_nlon.add_feature(cfeature.COASTLINE, edgecolor='k',linewidth=.5)
scatter = ax_nlon.scatter(snapshot_ds.sel(nlon=55).TLONG,snapshot_ds.sel(nlon=55).TLAT,c=snapshot_ds.sel(nlon=55)['d18O'].squeeze(), transform=ccrs.PlateCarree(),
cmap='viridis', norm=cf2_kwargs_d18O['norm'], s=5)
ax_nlon.add_feature(cfeature.LAND, zorder=14)
ax_nlon.set_global()
ax_nlon.set_title('filtered by nlon=55');
Summary#
Model grids are worthy of many hours of rabbit hole time (if you’re keen to stare at a wall and think about some fascinating topology questions), but a few exploratory plots can go a long way to understanding what is in the mix.
Resources and references#
Citations |
---|
Gu, Sifan, Liu, Zhengyu, Jahn, Alexandra, Zanowski, Hannah. (2021). C-iTRACE. Version 1.0. UCAR/NCAR - DASH Repository. https://doi.org/10.5065/hanq-bn92. |
Ohgaito, R. & Yamamoto, Akitomo & Hajima, Tomohiro & O’ishi, Ryouta & Abe, Manabu & Tatebe, Hiroaki & Abe-Ouchi, Ayako & Kawamiya, M.. (2020). PMIP4 experiments using MIROC-ES2L Earth System Model. 10.5194/gmd-2020-64. |
Kageyama, M., Braconnot, P., Harrison, S. P., Haywood, A. M., Jungclaus, J. H., Otto-Bliesner, B. L., Peterschmitt, J.-Y., Abe-Ouchi, A., Albani, S., Bartlein, P. J., Brierley, C., Crucifix, M., Dolan, A., Fernandez-Donado, L., Fischer, H., Hopcroft, P. O., Ivanovic, R. F., Lambert, F., Lunt, D. J., Mahowald, N. M., Peltier, W. R., Phipps, S. J., Roche, D. M., Schmidt, G. A., Tarasov, L., Valdes, P. J., Zhang, Q., and Zhou, T.: The PMIP4 contribution to CMIP6 – Part 1: Overview and over-arching analysis plan, Geosci. Model Dev., 11, 1033–1057, https://doi.org/10.5194/gmd-11-1033-2018, 2018. |
Garcia, H. E., K. Weathers, C. R. Paver, I. Smolyar, T. P. Boyer, R. A. Locarnini, M. M. Zweng, A. V. Mishonov, O. K. Baranova, D. Seidov, and J. R. Reagan, 2018. World Ocean Atlas 2018, Volume 4: Dissolved Inorganic Nutrients (phosphate, nitrate and nitrate+nitrite, silicate). A. Mishonov Technical Ed.; NOAA Atlas NESDIS 84, 35pp. |