Skip to content

[Bug]: KeyError: "No bounds data variables were found for the 'T' axis." when upgraded to xcdat 0.11.2 #832

@kristinchang3

Description

@kristinchang3

What happened?

I am testing dependency changes for PMP related to Pandas (<3.0) and Xarray(<2025.01.2). When running climatology metrics, I encountered the following error with xcdat's dataset.temporal.climatology(): KeyError: "No bounds data variables were found for the 'T' axis. Make sure the dataset has bound data vars and their names match the 'bounds' attributes found on their related coordinate variables. Alternatively, you can add bounds with ds.bounds.add_missing_bounds() or ds.bounds.add_bounds()."

What did you expect to happen? Are there are possible answers you came across?

I confirmed that the function runs successfully with Pandas<3.0, Xarray<2025.01.2, and xCDAT=0.11.1. Additionally, I found that the line runs successfully with xCDAT v0.11.2 when frequency is 'month' instead of 'season'.

Minimal Complete Verifiable Example (MVCE)

# Download dataset here: https://pcmdiweb.llnl.gov/pss/pmpdata/obs4MIPs_PCMDI_monthly/NASA-LaRC/CERES-EBAF-4-1/mon/rlut/gn/v20210727/rlut_mon_CERES-EBAF-4-1_PCMDI_gn_200301-201812.nc

import xcdat as xc
data_file = "/REPLACE/WITH/YOUR/PATH/TO/rlut_mon_CERES-EBAF-4-1_PCMDI_gn_200301-201812.nc"
ds = xc.open_dataset(data_file)
ds.temporal.climatology('rlut', freq='season')

Relevant log output

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[10], line 4
      2 data_file = "/Users/chang61/Documents/PCMDI/pmp_local/doc/Jupyter/demo/demo_data/obs4MIPs_PCMDI_monthly/NASA-LaRC/CERES-EBAF-4-1/mon/rlut/gn/v20210727/rlut_mon_CERES-EBAF-4-1_PCMDI_gn_200301-201812.nc"
      3 ds = xc.open_dataset(data_file)
----> 4 ds.temporal.climatology('rlut', freq='season')

File ~/miniforge3/envs/pcmdi_metrics_dev_20260210-2/lib/python3.13/site-packages/xcdat/temporal.py:657, in TemporalAccessor.climatology(self, data_var, freq, weighted, keep_weights, reference_period, season_config, skipna, min_weight)
    469 """Returns a Dataset with the climatology of a data variable.
    470 
    471 Data is grouped into the labeled time point for the averaging operation.
   (...)    653 }
    654 """
    655 self._set_data_var_attrs(data_var)
--> 657 return self._averager(
    658     data_var,
    659     "climatology",
    660     freq,
    661     weighted,
    662     keep_weights,
    663     reference_period,
    664     season_config,
    665     skipna,
    666     min_weight=min_weight,
    667 )

File ~/miniforge3/envs/pcmdi_metrics_dev_20260210-2/lib/python3.13/site-packages/xcdat/temporal.py:941, in TemporalAccessor._averager(self, data_var, mode, freq, weighted, keep_weights, reference_period, season_config, skipna, min_weight)
    939     dv_avg = self._average(ds, data_var, skipna=skipna)
    940 elif self._mode in ["group_average", "climatology", "departures"]:
--> 941     dv_avg = self._group_average(ds, data_var, skipna=skipna)
    943 # The original time dimension is dropped from the dataset because
    944 # it becomes obsolete after the data variable is averaged. When the
    945 # averaged data variable is added to the dataset, the new time dimension
    946 # and its associated coordinates are also added.
    947 ds = ds.drop_dims(self.dim)

File ~/miniforge3/envs/pcmdi_metrics_dev_20260210-2/lib/python3.13/site-packages/xcdat/temporal.py:1631, in TemporalAccessor._group_average(self, ds, data_var, skipna)
   1628 dv = dv.assign_coords({self.dim: self._labeled_time})
   1630 if self._weighted:
-> 1631     dv_avg = self._weighted_group_average(ds, dv, skipna)
   1632 else:
   1633     dv_avg = self._group_data(dv).mean(skipna=skipna)

File ~/miniforge3/envs/pcmdi_metrics_dev_20260210-2/lib/python3.13/site-packages/xcdat/temporal.py:1688, in TemporalAccessor._weighted_group_average(self, ds, dv, skipna)
   1652 """Compute the weighted group average of a data variable.
   1653 
   1654 This method applies weights to the data variable, groups the weighted data,
   (...)   1683 - The minimum weight threshold is controlled by `self._min_weight`.
   1684 """
   1685 with xr.set_options(keep_attrs=True):
   1686     # Keep the original weights for other operations and make a copy
   1687     # to avoid modifying the original weights.
-> 1688     self._weights = self._get_weights(ds, str(dv.name))
   1689     weights = self._weights.copy()
   1691     # For Dask-backed data variables, chunk the weights along the
   1692     # time dimension before broadcasting to avoid eager evaluation
   1693     # of the masking step.

File ~/miniforge3/envs/pcmdi_metrics_dev_20260210-2/lib/python3.13/site-packages/xcdat/temporal.py:1773, in TemporalAccessor._get_weights(self, ds, data_var)
   1740 def _get_weights(self, ds: xr.Dataset, data_var: str) -> xr.DataArray:
   1741     """Calculates weights for a data variable using time bounds.
   1742 
   1743     This method gets the length of time for each coordinate point by using
   (...)   1771     .. [4] https://cfconventions.org/cf-conventions/cf-conventions.html#calendar
   1772     """
-> 1773     time_bounds = ds.bounds.get_bounds("T", var_key=data_var)
   1774     time_coords = ds[data_var][self.dim]
   1776     bounds_dim = bounds._get_bounds_dim(time_coords, time_bounds)

File ~/miniforge3/envs/pcmdi_metrics_dev_20260210-2/lib/python3.13/site-packages/xcdat/bounds.py:240, in BoundsAccessor.get_bounds(self, axis, var_key)
    237     bounds_keys = self._get_bounds_from_attr(obj, axis)
    239 if len(bounds_keys) == 0:
--> 240     raise KeyError(
    241         f"No bounds data variables were found for the '{axis}' axis. Make sure "
    242         "the dataset has bound data vars and their names match the 'bounds' "
    243         "attributes found on their related coordinate variables. "
    244         "Alternatively, you can add bounds with `ds.bounds.add_missing_bounds()` "
    245         "or `ds.bounds.add_bounds()`."
    246     )
    248 bounds: xr.Dataset | xr.DataArray = self._dataset[
    249     bounds_keys if len(bounds_keys) > 1 else bounds_keys[0]
    250 ].copy()
    252 return bounds

KeyError: "No bounds data variables were found for the 'T' axis. Make sure the dataset has bound data vars and their names match the 'bounds' attributes found on their related coordinate variables. Alternatively, you can add bounds with `ds.bounds.add_missing_bounds()` or `ds.bounds.add_bounds()`."

Anything else we need to know?

No response

Environment

xCDAT v0.11.2

xr.show_versions()

INSTALLED VERSIONS

commit: None
python: 3.13.12 | packaged by conda-forge | (main, Feb 5 2026, 06:11:05) [Clang 19.1.7 ]
python-bits: 64
OS: Darwin
OS-release: 25.2.0
machine: arm64
processor: arm
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: ('en_US', 'UTF-8')
libhdf5: 1.14.6
libnetcdf: 4.9.3

xarray: 2025.1.1
pandas: 2.3.3
numpy: 2.3.5
scipy: 1.17.0
netCDF4: 1.7.4
pydap: None
h5netcdf: None
h5py: None
zarr: None
cftime: 1.6.5
nc_time_axis: 1.4.1
iris: None
bottleneck: 1.6.0
dask: 2026.1.2
distributed: 2026.1.2
matplotlib: 3.10.8
cartopy: 0.25.0
seaborn: 0.13.2
numbagg: None
fsspec: 2026.2.0
cupy: None
pint: 0.25.2
sparse: 0.17.0
flox: None
numpy_groupies: None
setuptools: 82.0.0
pip: 26.0.1
conda: None
pytest: 9.0.2
mypy: None
IPython: 9.10.0
sphinx: 8.2.3

Metadata

Metadata

Assignees

Labels

type: bugInconsistencies or issues which will cause an issue or problem for users or implementors.

Type

No type

Projects

Status

In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions