diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 3cd6e877353..8c071ce8446 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -35,6 +35,11 @@ Deprecations Bug Fixes ~~~~~~~~~ +- :py:meth:`DataArray.pad`, :py:meth:`Dataset.pad` and :py:meth:`Variable.pad` + no longer raise when padding an integer array with an explicit non-finite + ``constant_values`` such as ``np.nan``; the dtype is now promoted so the fill + value can be represented, as already happens for the default fill value + (:issue:`6431`). - Fix a major performance regression in :py:meth:`Coordinates.to_index` (and consequently :py:meth:`Dataset.to_dataframe`) caused by converting the cached code ndarrays into Python lists (:issue:`11305`). diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 1b5e0d4ff69..c2ee27f0b5a 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -1305,6 +1305,28 @@ def pad( dtype, constant_values = dtypes.maybe_promote(self.dtype) else: dtype = self.dtype + if ( + mode == "constant" + and constant_values is not None + and np.issubdtype(dtype, np.integer) + ): + # Padding an integer array with a non-finite fill value such as + # NaN would raise ("cannot convert float NaN to integer"). Promote + # the dtype so the fill value can be represented, as is already + # done for the default fill value. See GH6431. Only plain + # floating scalars are inspected here, so unit-aware duck arrays + # (e.g. pint quantities) are left untouched. + def _has_nonfinite_fill(value): + if isinstance(value, dict): + return any(_has_nonfinite_fill(v) for v in value.values()) + if isinstance(value, tuple | list): + return any(_has_nonfinite_fill(v) for v in value) + return isinstance(value, float | np.floating) and not np.isfinite( + value + ) + + if _has_nonfinite_fill(constant_values): + dtype = np.result_type(dtype, float) # create pad_options_kwargs, numpy requires only relevant kwargs to be nonempty if isinstance(stat_length, dict): diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index b2619008379..f5ee93328f3 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -4675,8 +4675,15 @@ def test_pad_constant(self) -> None: expected = xr.DataArray([1, 9, 1], dims="x") assert_identical(actual, expected) - with pytest.raises(ValueError, match="cannot convert float NaN to integer"): - ar.pad(x=1, constant_values=np.nan) + # padding an integer array with a non-finite fill value promotes the + # dtype so the fill value can be represented (GH6431) + actual = ar.pad(x=1, constant_values=np.nan) + expected = xr.DataArray([np.nan, 9, np.nan], dims="x") + assert_identical(actual, expected) + + actual = ar.pad(x=(1, 1), constant_values=(0, np.nan)) + expected = xr.DataArray([0, 9, np.nan], dims="x") + assert_identical(actual, expected) def test_pad_coords(self) -> None: ar = DataArray(