From b0005ac256cdcff8ad6aac4e4fda7e13127351cd Mon Sep 17 00:00:00 2001 From: PranavAchar01 Date: Fri, 3 Jul 2026 00:15:28 -0700 Subject: [PATCH 1/2] Promote dtype when padding an integer array with non-finite constant_values Variable.pad only promoted the dtype for the default fill value (dtypes.NA), so padding an integer array with an explicit np.nan raised 'cannot convert float NaN to integer'. Promote the dtype when an explicit non-finite fill value can't be represented by an integer dtype, matching the default-fill behaviour. Finite fills keep numpy's casting behaviour. Closes #6431 --- doc/whats-new.rst | 5 +++++ xarray/core/variable.py | 18 ++++++++++++++++++ xarray/tests/test_dataarray.py | 11 +++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) 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..d9f56179cf0 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -1305,6 +1305,24 @@ 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. + if isinstance(constant_values, dict): + fill_arrays = [np.asarray(v) for v in constant_values.values()] + else: + fill_arrays = [np.asarray(constant_values)] + if any( + a.dtype.kind == "f" and not np.isfinite(a).all() + for a in fill_arrays + ): + dtype = np.result_type(dtype, *(a.dtype for a in fill_arrays)) # 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( From 268fef22e3d13c4538b6c3362d0db230146bce2e Mon Sep 17 00:00:00 2001 From: PranavAchar01 Date: Fri, 3 Jul 2026 15:29:20 -0700 Subject: [PATCH 2/2] Avoid stripping units from pint constant_values in pad Only inspect plain floating scalars for non-finiteness so unit-aware duck arrays (e.g. pint quantities) passed as constant_values are left untouched, fixing a UnitStrippedWarning in test_pad_unit_constant_value. --- xarray/core/variable.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/xarray/core/variable.py b/xarray/core/variable.py index d9f56179cf0..c2ee27f0b5a 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -1313,16 +1313,20 @@ def pad( # 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. - if isinstance(constant_values, dict): - fill_arrays = [np.asarray(v) for v in constant_values.values()] - else: - fill_arrays = [np.asarray(constant_values)] - if any( - a.dtype.kind == "f" and not np.isfinite(a).all() - for a in fill_arrays - ): - dtype = np.result_type(dtype, *(a.dtype for a in fill_arrays)) + # 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):