Repository: pydata/xarray

Issue:
Failing main branch — test_save_mfdataset_compute_false_roundtrip
<!-- Please include a self-contained copy-pastable example that generates the issue if possible.

Please be concise with code posted. See guidelines below on how to provide a good bug report:

- Craft Minimal Bug Reports: http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports
- Minimal Complete Verifiable Examples: https://stackoverflow.com/help/mcve

Bug reports that follow these guidelines are easier to diagnose, and so are often handled much more quickly.
-->

We had the main branch passing for a while, but unfortunately another test failure. Now in our new `Linux py38-backend-api-v2` test case, in`test_save_mfdataset_compute_false_roundtrip`

[link](https://dev.azure.com/xarray/xarray/_build/results?buildId=4089&view=ms.vss-test-web.build-test-results-tab&runId=63370&resultId=101311&paneView=debug)

```
self = <xarray.tests.test_backends.TestDask object at 0x7f821a0d6190>

    def test_save_mfdataset_compute_false_roundtrip(self):
        from dask.delayed import Delayed
    
        original = Dataset({"foo": ("x", np.random.randn(10))}).chunk()
        datasets = [original.isel(x=slice(5)), original.isel(x=slice(5, 10))]
        with create_tmp_file(allow_cleanup_failure=ON_WINDOWS) as tmp1:
            with create_tmp_file(allow_cleanup_failure=ON_WINDOWS) as tmp2:
                delayed_obj = save_mfdataset(
                    datasets, [tmp1, tmp2], engine=self.engine, compute=False
                )
                assert isinstance(delayed_obj, Delayed)
                delayed_obj.compute()
                with open_mfdataset(
                    [tmp1, tmp2], combine="nested", concat_dim="x"
                ) as actual:
>                   assert_identical(actual, original)
E                   AssertionError: Left and right Dataset objects are not identical
E                   
E                   
E                   Differing data variables:
E                   L   foo      (x) float64 dask.array<chunksize=(5,), meta=np.ndarray>
E                   R   foo      (x) float64 dask.array<chunksize=(10,), meta=np.ndarray>

/home/vsts/work/1/s/xarray/tests/test_backends.py:3274: AssertionError

AssertionError: Left and right Dataset objects are not identical

Differing data variables:
L   foo      (x) float64 dask.array<chunksize=(5,), meta=np.ndarray>
R   foo      (x) float64 dask.array<chunksize=(10,), meta=np.ndarray>
```

@aurghs & @alexamici — are you familiar with this? Thanks in advance

Discussion:

Patch:
diff --git a/asv_bench/benchmarks/rolling.py b/asv_bench/benchmarks/rolling.py
index 1d3713f1..579f4f00 100644
--- a/asv_bench/benchmarks/rolling.py
+++ b/asv_bench/benchmarks/rolling.py
@@ -5,10 +5,10 @@ import xarray as xr
 
 from . import parameterized, randn, requires_dask
 
-nx = 300
+nx = 3000
 long_nx = 30000
 ny = 200
-nt = 100
+nt = 1000
 window = 20
 
 randn_xy = randn((nx, ny), frac_nan=0.1)
@@ -115,6 +115,11 @@ class DataArrayRollingMemory(RollingMemory):
             roll = self.ds.var3.rolling(t=100)
             getattr(roll, func)()
 
+    @parameterized(["stride"], ([None, 5, 50]))
+    def peakmem_1drolling_construct(self, stride):
+        self.ds.var2.rolling(t=100).construct("w", stride=stride)
+        self.ds.var3.rolling(t=100).construct("w", stride=stride)
+
 
 class DatasetRollingMemory(RollingMemory):
     @parameterized(["func", "use_bottleneck"], (["sum", "max", "mean"], [True, False]))
@@ -128,3 +133,7 @@ class DatasetRollingMemory(RollingMemory):
         with xr.set_options(use_bottleneck=use_bottleneck):
             roll = self.ds.rolling(t=100)
             getattr(roll, func)()
+
+    @parameterized(["stride"], ([None, 5, 50]))
+    def peakmem_1drolling_construct(self, stride):
+        self.ds.rolling(t=100).construct("w", stride=stride)
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index ee74411a..67429ed7 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -74,9 +74,12 @@ Bug fixes
   of :py:meth:`DataArray.__setitem__` lose dimension names.
   (:issue:`7030`, :pull:`8067`) By `Darsh Ranjan <https://github.com/dranjan>`_.
 - Return ``float64`` in presence of ``NaT`` in :py:class:`~core.accessor_dt.DatetimeAccessor` and
-  special case ``NaT`` handling in :py:meth:`~core.accessor_dt.DatetimeAccessor.isocalendar()`
+  special case ``NaT`` handling in :py:meth:`~core.accessor_dt.DatetimeAccessor.isocalendar`
   (:issue:`7928`, :pull:`8084`).
   By `Kai Mühlbauer <https://github.com/kmuehlbauer>`_.
+- Fix :py:meth:`~core.rolling.DatasetRolling.construct` with stride on Datasets without indexes.
+  (:issue:`7021`, :pull:`7578`).
+  By `Amrest Chinkamol <https://github.com/p4perf4ce>`_ and `Michael Niklas <https://github.com/headtr1ck>`_.
 - Calling plot with kwargs ``col``, ``row`` or ``hue`` no longer squeezes dimensions passed via these arguments
   (:issue:`7552`, :pull:`8174`).
   By `Wiktor Kraśnicki <https://github.com/wkrasnicki>`_.
diff --git a/xarray/core/common.py b/xarray/core/common.py
index ade70145..224b4154 100644
--- a/xarray/core/common.py
+++ b/xarray/core/common.py
@@ -1066,6 +1066,9 @@ class DataWithCoords(AttrAccessMixin):
     ) -> T_DataWithCoords:
         """Filter elements from this object according to a condition.
 
+        Returns elements from 'DataArray', where 'cond' is True,
+        otherwise fill in 'other'.
+
         This operation follows the normal broadcasting and alignment rules that
         xarray uses for binary arithmetic.
 
diff --git a/xarray/core/rolling.py b/xarray/core/rolling.py
index d49cb6e1..c6911cbe 100644
--- a/xarray/core/rolling.py
+++ b/xarray/core/rolling.py
@@ -785,11 +785,14 @@ class DatasetRolling(Rolling["Dataset"]):
             if not keep_attrs:
                 dataset[key].attrs = {}
 
+        # Need to stride coords as well. TODO: is there a better way?
+        coords = self.obj.isel(
+            {d: slice(None, None, s) for d, s in zip(self.dim, strides)}
+        ).coords
+
         attrs = self.obj.attrs if keep_attrs else {}
 
-        return Dataset(dataset, coords=self.obj.coords, attrs=attrs).isel(
-            {d: slice(None, None, s) for d, s in zip(self.dim, strides)}
-        )
+        return Dataset(dataset, coords=coords, attrs=attrs)
 
 
 class Coarsen(CoarsenArithmetic, Generic[T_Xarray]):


Test patch:
diff --git a/xarray/tests/test_distributed.py b/xarray/tests/test_distributed.py
index 6a8cd9c4..bfc37121 100644
--- a/xarray/tests/test_distributed.py
+++ b/xarray/tests/test_distributed.py
@@ -168,6 +168,10 @@ def test_open_mfdataset_multiple_files_parallel_distributed(parallel, tmp_path):
 @requires_netCDF4
 @pytest.mark.parametrize("parallel", (True, False))
 def test_open_mfdataset_multiple_files_parallel(parallel, tmp_path):
+    if parallel:
+        pytest.skip(
+            "Flaky in CI. Would be a welcome contribution to make a similar test reliable."
+        )
     lon = np.arange(100)
     time = xr.cftime_range("20010101", periods=100, calendar="360_day")
     data = np.random.random((time.size, lon.size))
diff --git a/xarray/tests/test_rolling.py b/xarray/tests/test_rolling.py
index 9a15696b..72d1b907 100644
--- a/xarray/tests/test_rolling.py
+++ b/xarray/tests/test_rolling.py
@@ -175,7 +175,7 @@ class TestDataArrayRolling:
 
     @pytest.mark.parametrize("center", (True, False))
     @pytest.mark.parametrize("window", (1, 2, 3, 4))
-    def test_rolling_construct(self, center, window) -> None:
+    def test_rolling_construct(self, center: bool, window: int) -> None:
         s = pd.Series(np.arange(10))
         da = DataArray.from_series(s)
 
@@ -610,7 +610,7 @@ class TestDatasetRolling:
 
     @pytest.mark.parametrize("center", (True, False))
     @pytest.mark.parametrize("window", (1, 2, 3, 4))
-    def test_rolling_construct(self, center, window) -> None:
+    def test_rolling_construct(self, center: bool, window: int) -> None:
         df = pd.DataFrame(
             {
                 "x": np.random.randn(20),
@@ -627,12 +627,6 @@ class TestDatasetRolling:
         np.testing.assert_allclose(df_rolling["x"].values, ds_rolling_mean["x"].values)
         np.testing.assert_allclose(df_rolling.index, ds_rolling_mean["index"])
 
-        # with stride
-        ds_rolling_mean = ds_rolling.construct("window", stride=2).mean("window")
-        np.testing.assert_allclose(
-            df_rolling["x"][::2].values, ds_rolling_mean["x"].values
-        )
-        np.testing.assert_allclose(df_rolling.index[::2], ds_rolling_mean["index"])
         # with fill_value
         ds_rolling_mean = ds_rolling.construct("window", stride=2, fill_value=0.0).mean(
             "window"
@@ -640,6 +634,51 @@ class TestDatasetRolling:
         assert (ds_rolling_mean.isnull().sum() == 0).to_array(dim="vars").all()
         assert (ds_rolling_mean["x"] == 0.0).sum() >= 0
 
+    @pytest.mark.parametrize("center", (True, False))
+    @pytest.mark.parametrize("window", (1, 2, 3, 4))
+    def test_rolling_construct_stride(self, center: bool, window: int) -> None:
+        df = pd.DataFrame(
+            {
+                "x": np.random.randn(20),
+                "y": np.random.randn(20),
+                "time": np.linspace(0, 1, 20),
+            }
+        )
+        ds = Dataset.from_dataframe(df)
+        df_rolling_mean = df.rolling(window, center=center, min_periods=1).mean()
+
+        # With an index (dimension coordinate)
+        ds_rolling = ds.rolling(index=window, center=center)
+        ds_rolling_mean = ds_rolling.construct("w", stride=2).mean("w")
+        np.testing.assert_allclose(
+            df_rolling_mean["x"][::2].values, ds_rolling_mean["x"].values
+        )
+        np.testing.assert_allclose(df_rolling_mean.index[::2], ds_rolling_mean["index"])
+
+        # Without index (https://github.com/pydata/xarray/issues/7021)
+        ds2 = ds.drop_vars("index")
+        ds2_rolling = ds2.rolling(index=window, center=center)
+        ds2_rolling_mean = ds2_rolling.construct("w", stride=2).mean("w")
+        np.testing.assert_allclose(
+            df_rolling_mean["x"][::2].values, ds2_rolling_mean["x"].values
+        )
+
+        # Mixed coordinates, indexes and 2D coordinates
+        ds3 = xr.Dataset(
+            {"x": ("t", range(20)), "x2": ("y", range(5))},
+            {
+                "t": range(20),
+                "y": ("y", range(5)),
+                "t2": ("t", range(20)),
+                "y2": ("y", range(5)),
+                "yt": (["t", "y"], np.ones((20, 5))),
+            },
+        )
+        ds3_rolling = ds3.rolling(t=window, center=center)
+        ds3_rolling_mean = ds3_rolling.construct("w", stride=2).mean("w")
+        for coord in ds3.coords:
+            assert coord in ds3_rolling_mean.coords
+
     @pytest.mark.slow
     @pytest.mark.parametrize("ds", (1, 2), indirect=True)
     @pytest.mark.parametrize("center", (True, False))