From 6acd92fb18d307e003839c8df7050b422b1bdc8e Mon Sep 17 00:00:00 2001 From: Spencer Clark Date: Sun, 5 Apr 2026 11:03:38 -0400 Subject: [PATCH 1/7] Remove NAT_TYPES and silence warning --- xarray/computation/nanops.py | 2 +- xarray/core/dtypes.py | 3 --- xarray/tests/test_dtypes.py | 6 ------ 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/xarray/computation/nanops.py b/xarray/computation/nanops.py index a28078540bb..1766a77fa9c 100644 --- a/xarray/computation/nanops.py +++ b/xarray/computation/nanops.py @@ -29,7 +29,7 @@ def _maybe_null_out(result, axis, mask, min_count=1): dtype, fill_value = dtypes.maybe_promote(result.dtype) result = where(null_mask, fill_value, astype(result, dtype)) - elif getattr(result, "dtype", None) not in dtypes.NAT_TYPES: + elif hasattr(result, "dtype") and result.dtype.kind not in "mM": null_mask = mask.size - duck_array_ops.sum(mask) result = where(null_mask < min_count, np.nan, result) diff --git a/xarray/core/dtypes.py b/xarray/core/dtypes.py index 40334d58e68..c8fd9ffdc25 100644 --- a/xarray/core/dtypes.py +++ b/xarray/core/dtypes.py @@ -108,9 +108,6 @@ def maybe_promote(dtype: T_dtype) -> tuple[T_dtype, Any]: return dtype_out, fill_value -NAT_TYPES = {np.datetime64("NaT").dtype, np.timedelta64("NaT").dtype} - - def get_fill_value(dtype): """Return an appropriate fill value for this dtype. diff --git a/xarray/tests/test_dtypes.py b/xarray/tests/test_dtypes.py index a1bdd9082ef..f33e04f3a6b 100644 --- a/xarray/tests/test_dtypes.py +++ b/xarray/tests/test_dtypes.py @@ -123,12 +123,6 @@ def test_maybe_promote(kind, expected) -> None: assert str(actual[1]) == expected[1] -def test_nat_types_membership() -> None: - assert np.datetime64("NaT").dtype in dtypes.NAT_TYPES - assert np.timedelta64("NaT").dtype in dtypes.NAT_TYPES - assert np.float64 not in dtypes.NAT_TYPES - - @pytest.mark.parametrize( ["dtype", "kinds", "xp", "expected"], ( From 71c138593085357d0df9cb11a30c091f9ffc3f36 Mon Sep 17 00:00:00 2001 From: Spencer Clark Date: Sun, 5 Apr 2026 13:04:44 -0400 Subject: [PATCH 2/7] Silence warning in _determine_cmap_params --- xarray/plot/utils.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/xarray/plot/utils.py b/xarray/plot/utils.py index 167718fe9d8..59d1c5d3c57 100644 --- a/xarray/plot/utils.py +++ b/xarray/plot/utils.py @@ -189,6 +189,15 @@ def _determine_cmap_params( else: mpl = attempt_import("matplotlib") + if plot_data.dtype.kind == "m": + unit, _ = np.datetime_data(plot_data.dtype) + zero = np.timedelta64(0, unit) + elif plot_data.dtype.kind == "M": + unit, _ = np.datetime_data(plot_data.dtype) + zero = np.datetime64(0, unit) + else: + zero = 0.0 + if isinstance(levels, Iterable): levels = sorted(levels) @@ -197,7 +206,7 @@ def _determine_cmap_params( # Handle all-NaN input data gracefully if calc_data.size == 0: # Arbitrary default for when all values are NaN - calc_data = np.array(0.0) + calc_data = np.array(zero) # Setting center=False prevents a divergent cmap possibly_divergent = center is not False @@ -205,7 +214,7 @@ def _determine_cmap_params( # Set center to 0 so math below makes sense but remember its state center_is_none = False if center is None: - center = 0 + center = zero center_is_none = True # Setting both vmin and vmax prevents a divergent cmap @@ -240,10 +249,10 @@ def _determine_cmap_params( if possibly_divergent: levels_are_divergent = ( - isinstance(levels, Iterable) and levels[0] * levels[-1] < 0 + isinstance(levels, Iterable) and levels[0] * levels[-1] < zero ) # kwargs not specific about divergent or not: infer defaults from data - divergent = (vmin < 0 < vmax) or not center_is_none or levels_are_divergent + divergent = (vmin < zero < vmax) or not center_is_none or levels_are_divergent else: divergent = False From 2c12b8d193d2446ae6a7570c74130757a06ca321 Mon Sep 17 00:00:00 2001 From: Spencer Clark Date: Sun, 5 Apr 2026 13:58:40 -0400 Subject: [PATCH 3/7] Silence warning in maybe_promote --- xarray/core/dtypes.py | 6 ++++-- xarray/tests/test_dtypes.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/xarray/core/dtypes.py b/xarray/core/dtypes.py index c8fd9ffdc25..860c51843ef 100644 --- a/xarray/core/dtypes.py +++ b/xarray/core/dtypes.py @@ -88,7 +88,8 @@ def maybe_promote(dtype: T_dtype) -> tuple[T_dtype, Any]: # See https://github.com/numpy/numpy/issues/10685 # np.timedelta64 is a subclass of np.integer # Check np.timedelta64 before np.integer - fill_value = np.timedelta64("NaT") + unit, _ = np.datetime_data(dtype) + fill_value = np.timedelta64("NaT", unit) dtype_ = dtype elif isdtype(dtype, "integral"): dtype_ = np.float32 if dtype.itemsize <= 2 else np.float64 @@ -97,8 +98,9 @@ def maybe_promote(dtype: T_dtype) -> tuple[T_dtype, Any]: dtype_ = dtype fill_value = np.nan + np.nan * 1j elif np.issubdtype(dtype, np.datetime64): + unit, _ = np.datetime_data(dtype) dtype_ = dtype - fill_value = np.datetime64("NaT") + fill_value = np.datetime64("NaT", unit) else: dtype_ = object fill_value = np.nan diff --git a/xarray/tests/test_dtypes.py b/xarray/tests/test_dtypes.py index f33e04f3a6b..4ed66509725 100644 --- a/xarray/tests/test_dtypes.py +++ b/xarray/tests/test_dtypes.py @@ -102,8 +102,8 @@ def test_inf(obj) -> None: ("I", (np.float64, "nan")), # dtype('uint32') ("l", (np.float64, "nan")), # dtype('int64') ("L", (np.float64, "nan")), # dtype('uint64') - ("m", (np.timedelta64, "NaT")), # dtype(' Date: Sun, 5 Apr 2026 14:20:43 -0400 Subject: [PATCH 4/7] Ignore type errors for units-specific NaT values --- xarray/core/dtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/core/dtypes.py b/xarray/core/dtypes.py index 860c51843ef..968e9f1491f 100644 --- a/xarray/core/dtypes.py +++ b/xarray/core/dtypes.py @@ -89,7 +89,7 @@ def maybe_promote(dtype: T_dtype) -> tuple[T_dtype, Any]: # np.timedelta64 is a subclass of np.integer # Check np.timedelta64 before np.integer unit, _ = np.datetime_data(dtype) - fill_value = np.timedelta64("NaT", unit) + fill_value = np.timedelta64("NaT", unit) # type: ignore[call-overload] dtype_ = dtype elif isdtype(dtype, "integral"): dtype_ = np.float32 if dtype.itemsize <= 2 else np.float64 @@ -100,7 +100,7 @@ def maybe_promote(dtype: T_dtype) -> tuple[T_dtype, Any]: elif np.issubdtype(dtype, np.datetime64): unit, _ = np.datetime_data(dtype) dtype_ = dtype - fill_value = np.datetime64("NaT", unit) + fill_value = np.datetime64("NaT", unit) # type: ignore[call-overload] else: dtype_ = object fill_value = np.nan From c33f80fad89590dfad31b499254364d766bfcafc Mon Sep 17 00:00:00 2001 From: Spencer Clark Date: Sun, 5 Apr 2026 14:44:51 -0400 Subject: [PATCH 5/7] Use explicit cast to address type errors --- xarray/core/dtypes.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/xarray/core/dtypes.py b/xarray/core/dtypes.py index 968e9f1491f..bb2fe26d727 100644 --- a/xarray/core/dtypes.py +++ b/xarray/core/dtypes.py @@ -10,6 +10,7 @@ from xarray.compat import array_api_compat, npcompat from xarray.compat.npcompat import HAS_STRING_DTYPE from xarray.core import utils +from xarray.core.types import PDDatetimeUnitOptions if TYPE_CHECKING: from typing import Any @@ -89,7 +90,10 @@ def maybe_promote(dtype: T_dtype) -> tuple[T_dtype, Any]: # np.timedelta64 is a subclass of np.integer # Check np.timedelta64 before np.integer unit, _ = np.datetime_data(dtype) - fill_value = np.timedelta64("NaT", unit) # type: ignore[call-overload] + # np.datetime_data returns a generic str for the unit so we need to + # cast it to a valid time unit for mypy purposes. + unit = cast(PDDatetimeUnitOptions, unit) + fill_value = np.timedelta64("NaT", unit) dtype_ = dtype elif isdtype(dtype, "integral"): dtype_ = np.float32 if dtype.itemsize <= 2 else np.float64 @@ -99,8 +103,11 @@ def maybe_promote(dtype: T_dtype) -> tuple[T_dtype, Any]: fill_value = np.nan + np.nan * 1j elif np.issubdtype(dtype, np.datetime64): unit, _ = np.datetime_data(dtype) + # np.datetime_data returns a generic str for the unit so we need to + # cast it to a valid time unit for mypy purposes. + unit = cast(PDDatetimeUnitOptions, unit) dtype_ = dtype - fill_value = np.datetime64("NaT", unit) # type: ignore[call-overload] + fill_value = np.datetime64("NaT", unit) else: dtype_ = object fill_value = np.nan From 3e9d49443d27b34ee2a1da698f589670575c3c24 Mon Sep 17 00:00:00 2001 From: Spencer Clark Date: Mon, 6 Apr 2026 20:29:48 -0400 Subject: [PATCH 6/7] Incorporate suggestion from @keewis --- xarray/computation/nanops.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/computation/nanops.py b/xarray/computation/nanops.py index 1766a77fa9c..e607deb81e0 100644 --- a/xarray/computation/nanops.py +++ b/xarray/computation/nanops.py @@ -29,7 +29,9 @@ def _maybe_null_out(result, axis, mask, min_count=1): dtype, fill_value = dtypes.maybe_promote(result.dtype) result = where(null_mask, fill_value, astype(result, dtype)) - elif hasattr(result, "dtype") and result.dtype.kind not in "mM": + elif (dtype := getattr(result, "dtype", None)) and getattr( + dtype, "kind", None + ) not in "mM": null_mask = mask.size - duck_array_ops.sum(mask) result = where(null_mask < min_count, np.nan, result) From 624df9294bbaa46f750d50c515b6d8317d0b8174 Mon Sep 17 00:00:00 2001 From: Spencer Clark Date: Mon, 6 Apr 2026 20:39:52 -0400 Subject: [PATCH 7/7] Fix type error --- xarray/computation/nanops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/computation/nanops.py b/xarray/computation/nanops.py index e607deb81e0..c5a9d9bcc04 100644 --- a/xarray/computation/nanops.py +++ b/xarray/computation/nanops.py @@ -31,7 +31,7 @@ def _maybe_null_out(result, axis, mask, min_count=1): elif (dtype := getattr(result, "dtype", None)) and getattr( dtype, "kind", None - ) not in "mM": + ) not in {"m", "M"}: null_mask = mask.size - duck_array_ops.sum(mask) result = where(null_mask < min_count, np.nan, result)