Python – xarray Slicing Across the Antimeridian

antimeridiannetcdfpythonxarray

I'm just starting with using xarray for working with n-dimensional NetCDF datasets. I particularly like the techniques for slicing using both indexes and labels (isel and sel):

import xarray as xr
ds = xr.open_dataset('/path/to/data.nc', decode_times=False, chunks={'time': 1, 'depth': 1})
v = ds['my-variable'].isel(**{'time': 0, 'depth': 0}).sel(**{'lat': slice(-90,90,10), 'lon': slice(40,-80,5)})

This works great if my longitude slice is increasing (-80° to 40°), but not if I want the 'wrap' version of that, which crosses the antimeridian (40° to -80°), as in my snippet above. For example:

>>> v = ds['my-variable'].isel(**{'time': 0, 'depth': 0}).sel(**{'lat': slice(-90,90,10), 'lon': slice(40,-80,5)})
>>> v['lat'].shape
(0,)

Yet:

>>> v = ds['my-variable'].isel(**{'time': 0, 'depth': 0}).sel(**{'lat': slice(-90,90,10), 'lon': slice(-80,40,5)})
>>> v['lat'].shape
(1501,)

The only difference is 'lon': slice(-80,40,5) (returns data, but not the data I want) vs 'lon': slice(40,-80,5) (returns no data).

What is the best way to slice with longitude spanning the antimeridian with xarray?

Best Answer

Xarray is (intentionally) ignorant of coordinate systems, so it has no special handling for cyclic coordinates such as longitude. Because your longitude array has only increasing values, xarray interprets selections like slice(40, -80) in the same way that x[i:j] works if x is a NumPy array and i > j >= 0, and thus returns an empty selection.

The easiest way to work around this is to use boolean indexing instead:

ds.sel(lon=(ds.lon < -80) | (ds.lon > 40))

The inner parentheses are important, because | has higher operator precedence than < in Python.

Alternatively (maybe if you're studying the Pacific ocean), you might find it convenient to adjust the coordinate system of your data so it is centered over the anti-meridian instead. This is straightforward to accomplish with roll:

ds_rolled = ds.assign_coords(lon=(ds.lon % 360)).roll(lon=(ds.dims['lon'] // 2))
Related Question