From 9e258633d94b80de7b79da8e77f4a00a94408a7c Mon Sep 17 00:00:00 2001 From: Ian Hunt-Isaak Date: Thu, 31 Jul 2025 20:18:36 -0400 Subject: [PATCH 1/2] fix: EmptyRangeIndex Display Following @anntzer's suggestion of changing internal definition --- xarray/indexes/range_index.py | 26 +++++++++++++++++--- xarray/tests/test_range_index.py | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/xarray/indexes/range_index.py b/xarray/indexes/range_index.py index 34e6c2f7c00..ab619c6f722 100644 --- a/xarray/indexes/range_index.py +++ b/xarray/indexes/range_index.py @@ -20,8 +20,9 @@ class RangeCoordinateTransform(CoordinateTransform): start: float stop: float + _step: float | None - __slots__ = ("start", "stop") + __slots__ = ("_step", "start", "stop") def __init__( self, @@ -39,6 +40,7 @@ def __init__( self.start = start self.stop = stop + self._step = None # Will be calculated by property @property def coord_name(self) -> Hashable: @@ -54,7 +56,13 @@ def size(self) -> int: @property def step(self) -> float: - return (self.stop - self.start) / self.size + if self._step is not None: + return self._step + if self.size > 0: + return (self.stop - self.start) / self.size + else: + # For empty arrays, default to 1.0 + return 1.0 def forward(self, dim_positions: dict[str, Any]) -> dict[Hashable, Any]: positions = dim_positions[self.dim] @@ -81,12 +89,22 @@ def equals( def slice(self, sl: slice) -> "RangeCoordinateTransform": new_range = range(self.size)[sl] new_size = len(new_range) + new_start = self.start + new_range.start * self.step new_stop = self.start + new_range.stop * self.step - return type(self)( - new_start, new_stop, new_size, self.coord_name, self.dim, dtype=self.dtype + result = type(self)( + new_start, + new_stop, + new_size, + self.coord_name, + self.dim, + dtype=self.dtype, ) + if new_size == 0: + # For empty slices, preserve step from parent + result._step = self.step + return result class RangeIndex(CoordinateTransformIndex): diff --git a/xarray/tests/test_range_index.py b/xarray/tests/test_range_index.py index d0644ba73a2..b98e7caace0 100644 --- a/xarray/tests/test_range_index.py +++ b/xarray/tests/test_range_index.py @@ -166,6 +166,48 @@ def test_range_index_isel() -> None: assert_identical(actual, expected) +def test_range_index_empty_slice() -> None: + """Test that empty slices of RangeIndex are printable and preserve step. + + Regression test for https://github.com/pydata/xarray/issues/10547 + """ + # Test with linspace + n = 30 + step = 1 + da = xr.DataArray(np.zeros(n), dims=["x"]) + da = da.assign_coords( + xr.Coordinates.from_xindex(RangeIndex.linspace(0, (n - 1) * step, n, dim="x")) + ) + + # This should not raise ZeroDivisionError + sub = da.isel(x=slice(0)) + assert sub.sizes["x"] == 0 + + # Test that it's printable + repr_str = repr(sub) + assert "RangeIndex" in repr_str + assert "step=1" in repr_str + + # Test with different step values + index = RangeIndex.arange(0, 10, 2.5, dim="y") + da2 = xr.DataArray(np.zeros(4), dims=["y"]) + da2 = da2.assign_coords(xr.Coordinates.from_xindex(index)) + empty = da2.isel(y=slice(0)) + + # Should preserve step + assert empty.sizes["y"] == 0 + assert empty._indexes["y"].step == 2.5 + + # Test negative step + index3 = RangeIndex.arange(10, 0, -1, dim="z") + da3 = xr.DataArray(np.zeros(10), dims=["z"]) + da3 = da3.assign_coords(xr.Coordinates.from_xindex(index3)) + empty3 = da3.isel(z=slice(0)) + + assert empty3.sizes["z"] == 0 + assert empty3._indexes["z"].step == -1.0 + + def test_range_index_sel() -> None: ds = create_dataset_arange(0.0, 1.0, 0.1) From 152bbb04679be222d2bae0573da26f9dd5f0caed Mon Sep 17 00:00:00 2001 From: Ian Hunt-Isaak Date: Fri, 1 Aug 2025 11:53:01 -0400 Subject: [PATCH 2/2] Add mypy type safety to empty slice test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add explicit type checks with isinstance() before accessing RangeIndex.step - Improves type safety and test robustness for issue #10547 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- xarray/tests/test_range_index.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_range_index.py b/xarray/tests/test_range_index.py index b98e7caace0..a2412b75634 100644 --- a/xarray/tests/test_range_index.py +++ b/xarray/tests/test_range_index.py @@ -196,7 +196,14 @@ def test_range_index_empty_slice() -> None: # Should preserve step assert empty.sizes["y"] == 0 - assert empty._indexes["y"].step == 2.5 + range_index_y = empty._indexes["y"] + assert isinstance(range_index_y, RangeIndex) + assert range_index_y.step == 2.5 + + # Test that it's printable + repr_str2 = repr(empty) + assert "RangeIndex" in repr_str2 + assert "step=2.5" in repr_str2 # Test negative step index3 = RangeIndex.arange(10, 0, -1, dim="z") @@ -205,7 +212,14 @@ def test_range_index_empty_slice() -> None: empty3 = da3.isel(z=slice(0)) assert empty3.sizes["z"] == 0 - assert empty3._indexes["z"].step == -1.0 + range_index_z = empty3._indexes["z"] + assert isinstance(range_index_z, RangeIndex) + assert range_index_z.step == -1.0 + + # Test that it's printable + repr_str3 = repr(empty3) + assert "RangeIndex" in repr_str3 + assert "step=-1" in repr_str3 def test_range_index_sel() -> None: