diff --git a/doc/whats-new.rst b/doc/whats-new.rst index bade3532d4a..bd1130669b4 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -56,9 +56,12 @@ Breaking changes Deprecations ~~~~~~~~~~~~ +- Set the default argument for `roll_coords` to `False` for :py:meth:`DataArray.roll` + and :py:meth:`Dataset.roll`. (:pull:`5653`) + By `Tom Nicholas `_. - :py:meth:`xarray.open_mfdataset` will now error instead of warn when a value for ``concat_dim`` is - passed alongside ``combine='by_coords'``. By `Tom Nicholas `_. - + passed alongside ``combine='by_coords'``. + By `Tom Nicholas `_. Bug fixes ~~~~~~~~~ diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 30fc478d26e..ed8b393628d 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -3169,11 +3169,14 @@ def shift( fill_value: Any = dtypes.NA, **shifts_kwargs: int, ) -> "DataArray": - """Shift this array by an offset along one or more dimensions. + """Shift this DataArray by an offset along one or more dimensions. - Only the data is moved; coordinates stay in place. Values shifted from - beyond array bounds are replaced by NaN. This is consistent with the - behavior of ``shift`` in pandas. + Only the data is moved; coordinates stay in place. This is consistent + with the behavior of ``shift`` in pandas. + + Values shifted from beyond array bounds will appear at one end of + each dimension, which are filled according to `fill_value`. For periodic + offsets instead see `roll`. Parameters ---------- @@ -3212,12 +3215,15 @@ def shift( def roll( self, - shifts: Mapping[Any, int] = None, - roll_coords: bool = None, + shifts: Mapping[Hashable, int] = None, + roll_coords: bool = False, **shifts_kwargs: int, ) -> "DataArray": """Roll this array by an offset along one or more dimensions. + Unlike shift, roll treats the given dimensions as periodic, so will not + create any missing values to be filled. + Unlike shift, roll may rotate all variables, including coordinates if specified. The direction of rotation is consistent with :py:func:`numpy.roll`. @@ -3228,12 +3234,9 @@ def roll( Integer offset to rotate each of the given dimensions. Positive offsets roll to the right; negative offsets roll to the left. - roll_coords : bool - Indicates whether to roll the coordinates by the offset - The current default of roll_coords (None, equivalent to True) is - deprecated and will change to False in a future version. - Explicitly pass roll_coords to silence the warning. - **shifts_kwargs + roll_coords : bool, default: False + Indicates whether to roll the coordinates by the offset too. + **shifts_kwargs : {dim: offset, ...}, optional The keyword arguments form of ``shifts``. One of shifts or shifts_kwargs must be provided. diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 5a00539346c..4b1b1de222d 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1097,7 +1097,7 @@ def _replace( coord_names: Set[Hashable] = None, dims: Dict[Any, int] = None, attrs: Union[Dict[Hashable, Any], None, Default] = _default, - indexes: Union[Dict[Any, Index], None, Default] = _default, + indexes: Union[Dict[Hashable, Index], None, Default] = _default, encoding: Union[dict, None, Default] = _default, inplace: bool = False, ) -> "Dataset": @@ -5866,12 +5866,22 @@ def diff(self, dim, n=1, label="upper"): else: return difference - def shift(self, shifts=None, fill_value=dtypes.NA, **shifts_kwargs): + def shift( + self, + shifts: Mapping[Hashable, int] = None, + fill_value: Any = dtypes.NA, + **shifts_kwargs: int, + ) -> "Dataset": + """Shift this dataset by an offset along one or more dimensions. Only data variables are moved; coordinates stay in place. This is consistent with the behavior of ``shift`` in pandas. + Values shifted from beyond array bounds will appear at one end of + each dimension, which are filled according to `fill_value`. For periodic + offsets instead see `roll`. + Parameters ---------- shifts : mapping of hashable to int @@ -5926,32 +5936,37 @@ def shift(self, shifts=None, fill_value=dtypes.NA, **shifts_kwargs): return self._replace(variables) - def roll(self, shifts=None, roll_coords=None, **shifts_kwargs): + def roll( + self, + shifts: Mapping[Hashable, int] = None, + roll_coords: bool = False, + **shifts_kwargs: int, + ) -> "Dataset": """Roll this dataset by an offset along one or more dimensions. - Unlike shift, roll may rotate all variables, including coordinates + Unlike shift, roll treats the given dimensions as periodic, so will not + create any missing values to be filled. + + Also unlike shift, roll may rotate all variables, including coordinates if specified. The direction of rotation is consistent with :py:func:`numpy.roll`. Parameters ---------- - shifts : dict, optional + shifts : mapping of hashable to int, optional A dict with keys matching dimensions and values given by integers to rotate each of the given dimensions. Positive offsets roll to the right; negative offsets roll to the left. - roll_coords : bool - Indicates whether to roll the coordinates by the offset - The current default of roll_coords (None, equivalent to True) is - deprecated and will change to False in a future version. - Explicitly pass roll_coords to silence the warning. + roll_coords : bool, default: False + Indicates whether to roll the coordinates by the offset too. **shifts_kwargs : {dim: offset, ...}, optional The keyword arguments form of ``shifts``. One of shifts or shifts_kwargs must be provided. + Returns ------- rolled : Dataset - Dataset with the same coordinates and attributes but rolled - variables. + Dataset with the same attributes but rolled data and coordinates. See Also -------- @@ -5959,47 +5974,49 @@ def roll(self, shifts=None, roll_coords=None, **shifts_kwargs): Examples -------- - >>> ds = xr.Dataset({"foo": ("x", list("abcde"))}) + >>> ds = xr.Dataset({"foo": ("x", list("abcde"))}, coords={"x": np.arange(5)}) >>> ds.roll(x=2) Dimensions: (x: 5) - Dimensions without coordinates: x + Coordinates: + * x (x) int64 0 1 2 3 4 Data variables: foo (x) >> ds.roll(x=2, roll_coords=True) + + Dimensions: (x: 5) + Coordinates: + * x (x) int64 3 4 0 1 2 + Data variables: + foo (x) None: """Not for public consumption. Parameters @@ -459,7 +460,7 @@ def __init__(self, indexes): """ self._indexes = indexes - def __iter__(self): + def __iter__(self) -> Iterator[pd.Index]: return iter(self._indexes) def __len__(self): @@ -468,7 +469,7 @@ def __len__(self): def __contains__(self, key): return key in self._indexes - def __getitem__(self, key): + def __getitem__(self, key) -> pd.Index: return self._indexes[key] def __repr__(self): diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index c3223432b38..b1bd7576a12 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -3378,19 +3378,10 @@ def test_roll_coords(self): def test_roll_no_coords(self): arr = DataArray([1, 2, 3], coords={"x": range(3)}, dims="x") - actual = arr.roll(x=1, roll_coords=False) + actual = arr.roll(x=1) expected = DataArray([3, 1, 2], coords=[("x", [0, 1, 2])]) assert_identical(expected, actual) - def test_roll_coords_none(self): - arr = DataArray([1, 2, 3], coords={"x": range(3)}, dims="x") - - with pytest.warns(FutureWarning): - actual = arr.roll(x=1, roll_coords=None) - - expected = DataArray([3, 1, 2], coords=[("x", [2, 0, 1])]) - assert_identical(expected, actual) - def test_copy_with_data(self): orig = DataArray( np.random.random(size=(2, 2)), diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 7b17eae89c8..67dbfc358be 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -5098,25 +5098,13 @@ def test_roll_no_coords(self): coords = {"bar": ("x", list("abc")), "x": [-4, 3, 2]} attrs = {"meta": "data"} ds = Dataset({"foo": ("x", [1, 2, 3])}, coords, attrs) - actual = ds.roll(x=1, roll_coords=False) + actual = ds.roll(x=1) expected = Dataset({"foo": ("x", [3, 1, 2])}, coords, attrs) assert_identical(expected, actual) with pytest.raises(ValueError, match=r"dimensions"): - ds.roll(abc=321, roll_coords=False) - - def test_roll_coords_none(self): - coords = {"bar": ("x", list("abc")), "x": [-4, 3, 2]} - attrs = {"meta": "data"} - ds = Dataset({"foo": ("x", [1, 2, 3])}, coords, attrs) - - with pytest.warns(FutureWarning): - actual = ds.roll(x=1, roll_coords=None) - - ex_coords = {"bar": ("x", list("cab")), "x": [2, -4, 3]} - expected = Dataset({"foo": ("x", [3, 1, 2])}, ex_coords, attrs) - assert_identical(expected, actual) + ds.roll(abc=321) def test_roll_multidim(self): # regression test for 2445