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.

Using Xarray for Data read and selection

Use Xarray module to read in model data from nomads server.

This example uses the xarray module to access data from the nomads server for archive NAM analysis data via OPeNDAP. Xarray makes it easier to select times and levels, although you still have to know the coordinate variable name. A simple 500 hPa plot is created after selecting with xarray.

Import all of our needed modules

from datetime import datetime

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import metpy.calc as mpcalc
from metpy.units import units
import numpy as np
import xarray as xr

Accessing data using Xarray

# Specify our date/time of product desired
dt = datetime(2016, 4, 16, 18)

# Construct our OPeNDAP access URL
base_url = 'https://www.ncei.noaa.gov/thredds/dodsC/model-namanl-old/'
data = xr.open_dataset(f'{base_url}{dt:%Y%m}/{dt:%Y%m%d}/'
                       f'namanl_218_{dt:%Y%m%d}_{dt:%H}00_000.grb').metpy.parse_cf()
oc_open: server error retrieving url: code=500 message="java.lang.IllegalStateException: No files in this collection =namanl_218_20160416_1800_000.grb topdir=/san5302/nexus/namanl/201604/20160416"
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/file_manager.py:219, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    218 try:
--> 219     file = self._cache[self._key]
    220 except KeyError:

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/lru_cache.py:56, in LRUCache.__getitem__(self, key)
     55 with self._lock:
---> 56     value = self._cache[key]
     57     self._cache.move_to_end(key)

KeyError: [<class 'netCDF4._netCDF4.Dataset'>, ('https://www.ncei.noaa.gov/thredds/dodsC/model-namanl-old/201604/20160416/namanl_218_20160416_1800_000.grb',), 'r', (('clobber', True), ('diskless', False), ('format', 'NETCDF4'), ('persist', False)), '3fbb1912-9b76-482c-a896-cc8b9f3644e1']

During handling of the above exception, another exception occurred:

OSError                                   Traceback (most recent call last)
Cell In[2], line 6
      4 # Construct our OPeNDAP access URL
      5 base_url = 'https://www.ncei.noaa.gov/thredds/dodsC/model-namanl-old/'
----> 6 data = xr.open_dataset(f'{base_url}{dt:%Y%m}/{dt:%Y%m%d}/'
      7                        f'namanl_218_{dt:%Y%m%d}_{dt:%H}00_000.grb').metpy.parse_cf()

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/api.py:606, in open_dataset(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
    594 decoders = _resolve_decoders_kwargs(
    595     decode_cf,
    596     open_backend_dataset_parameters=backend.open_dataset_parameters,
   (...)    602     decode_coords=decode_coords,
    603 )
    605 overwrite_encoded_chunks = kwargs.pop("overwrite_encoded_chunks", None)
--> 606 backend_ds = backend.open_dataset(
    607     filename_or_obj,
    608     drop_variables=drop_variables,
    609     **decoders,
    610     **kwargs,
    611 )
    612 ds = _dataset_from_backend_dataset(
    613     backend_ds,
    614     filename_or_obj,
   (...)    625     **kwargs,
    626 )
    627 return ds

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/netCDF4_.py:767, in NetCDF4BackendEntrypoint.open_dataset(self, filename_or_obj, mask_and_scale, decode_times, concat_characters, decode_coords, drop_variables, use_cftime, decode_timedelta, group, mode, format, clobber, diskless, persist, auto_complex, lock, autoclose)
    745 def open_dataset(
    746     self,
    747     filename_or_obj: T_PathFileOrDataStore,
   (...)    764     autoclose=False,
    765 ) -> Dataset:
    766     filename_or_obj = _normalize_path(filename_or_obj)
--> 767     store = NetCDF4DataStore.open(
    768         filename_or_obj,
    769         mode=mode,
    770         format=format,
    771         group=group,
    772         clobber=clobber,
    773         diskless=diskless,
    774         persist=persist,
    775         auto_complex=auto_complex,
    776         lock=lock,
    777         autoclose=autoclose,
    778     )
    780     store_entrypoint = StoreBackendEntrypoint()
    781     with close_on_error(store):

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/netCDF4_.py:525, in NetCDF4DataStore.open(cls, filename, mode, format, group, clobber, diskless, persist, auto_complex, lock, lock_maker, autoclose)
    521 else:
    522     manager = CachingFileManager(
    523         netCDF4.Dataset, filename, mode=mode, kwargs=kwargs
    524     )
--> 525 return cls(manager, group=group, mode=mode, lock=lock, autoclose=autoclose)

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/netCDF4_.py:429, in NetCDF4DataStore.__init__(self, manager, group, mode, lock, autoclose)
    427 self._group = group
    428 self._mode = mode
--> 429 self.format = self.ds.data_model
    430 self._filename = self.ds.filepath()
    431 self.is_remote = is_remote_uri(self._filename)

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/netCDF4_.py:534, in NetCDF4DataStore.ds(self)
    532 @property
    533 def ds(self):
--> 534     return self._acquire()

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/netCDF4_.py:528, in NetCDF4DataStore._acquire(self, needs_lock)
    527 def _acquire(self, needs_lock=True):
--> 528     with self._manager.acquire_context(needs_lock) as root:
    529         ds = _nc4_require_group(root, self._group, self._mode)
    530     return ds

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/contextlib.py:141, in _GeneratorContextManager.__enter__(self)
    139 del self.args, self.kwds, self.func
    140 try:
--> 141     return next(self.gen)
    142 except StopIteration:
    143     raise RuntimeError("generator didn't yield") from None

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/file_manager.py:207, in CachingFileManager.acquire_context(self, needs_lock)
    204 @contextmanager
    205 def acquire_context(self, needs_lock: bool = True) -> Iterator[T_File]:
    206     """Context manager for acquiring a file."""
--> 207     file, cached = self._acquire_with_cache_info(needs_lock)
    208     try:
    209         yield file

File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/xarray/backends/file_manager.py:225, in CachingFileManager._acquire_with_cache_info(self, needs_lock)
    223     kwargs = kwargs.copy()
    224     kwargs["mode"] = self._mode
--> 225 file = self._opener(*self._args, **kwargs)
    226 if self._mode == "w":
    227     # ensure file doesn't get overridden when opened again
    228     self._mode = "a"

File src/netCDF4/_netCDF4.pyx:2517, in netCDF4._netCDF4.Dataset.__init__()

File src/netCDF4/_netCDF4.pyx:2154, in netCDF4._netCDF4._ensure_nc_success()

OSError: [Errno -70] NetCDF: DAP server error: 'https://www.ncei.noaa.gov/thredds/dodsC/model-namanl-old/201604/20160416/namanl_218_20160416_1800_000.grb'

NAM data is in a projected coordinate and you get back the projection X and Y values in km

# Create a 2-d meshgrid of our x, y coordinates
# manually converted to meters (km * 1000)
#x, y = np.meshgrid(data['x'].values * 1000, data['y'].values * 1000)
x = data.Geopotential_height_isobaric.metpy.x.metpy.convert_units('meter').values
y = data.Geopotential_height_isobaric.metpy.y.metpy.convert_units('meter').values

Getting the valid times in a more useable format

# Get the valid times from the file
vtimes = data.Geopotential_height_isobaric.metpy.time.data.astype('datetime64[ms]').astype('O')
print(vtimes)

Xarray has some nice functionality to choose the time and level that you specifically want to use. In this example the time variable is ‘time’ and the level variable is ‘isobaric1’. Unfortunately, these can be different with each file you use, so you’ll always need to check what they are by listing the coordinate variable names

# print(data.Geopotential_height.coords)
hght_500 = data.Geopotential_height_isobaric.metpy.sel(time1=vtimes[0], vertical=500*units.hPa)
uwnd_500 = data['u-component_of_wind_isobaric'].metpy.sel(time1=vtimes[0], vertical=500*units.hPa)
vwnd_500 = data['v-component_of_wind_isobaric'].metpy.sel(time1=vtimes[0], vertical=500*units.hPa)

Now make the 500-hPa map

# Must set data projection, NAM is LCC projection
datacrs = data.Geopotential_height_isobaric.metpy.cartopy_crs

# A different LCC projection for the plot.
plotcrs = ccrs.LambertConformal(central_latitude=45., central_longitude=-100.,
                                standard_parallels=[30, 60])

fig = plt.figure(figsize=(17., 11.))
ax = plt.axes(projection=plotcrs)
ax.coastlines('50m', edgecolor='black')
ax.add_feature(cfeature.STATES, linewidth=0.5)
ax.set_extent([-130, -67, 20, 50], ccrs.PlateCarree())

clev500 = np.arange(5100, 6000, 60)
cs = ax.contour(x, y, mpcalc.smooth_n_point(hght_500, 9, 5), clev500,
                colors='k', linewidths=2.5, linestyles='solid', transform=datacrs)
ax.clabel(cs, fontsize=12, colors='k', inline=1, inline_spacing=8,
          fmt='%i', rightside_up=True, use_clabeltext=True)

# Here we put boxes around the clabels with a black boarder white facecolor
# `labelTexts` necessary as ~cartopy.mpl.contour.GeoContourSet.clabel
# does not return list of texts as of 0.18
for t in cs.labelTexts:
    t.set_bbox({'fc': 'w'})

# Transform Vectors before plotting, then plot wind barbs.
wind_slice = slice(None, None, 16)
ax.barbs(x[wind_slice], y[wind_slice],
         uwnd_500.data[wind_slice, wind_slice], vwnd_500.data[wind_slice, wind_slice],
         length=7, transform=datacrs)

# Add some titles to make the plot readable by someone else
plt.title('500-hPa Geopotential Heights (m)', loc='left')
plt.title(f'VALID: {vtimes[0]}', loc='right');