From dc6ca017ea51fcb5092b5e9d2185e2c3524d2b33 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Wed, 26 Feb 2020 18:58:49 -0500 Subject: [PATCH 01/16] Add test of DataWithCoords.coarsen() for #3376 --- xarray/tests/test_dataset.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 4e51e229b29..fb39bbcc48e 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -5654,6 +5654,29 @@ def test_coarsen_coords_cftime(): np.testing.assert_array_equal(actual.time, expected_times) +def test_coarsen_keep_attrs(self): + _attrs = {"units": "test", "long_name": "testing"} + + var1 = np.linspace(10, 15, 100) + var2 = np.linspace(5, 10, 100) + coords = np.linspace(1, 10, 100) + + ds = Dataset( + data_vars={"var1": ("coord", var1), "var2": ("coord", var2)}, + coords={"coord": coords}, + ) + ds.attrs["units"] = "test" + ds.attrs["long_name"] = "testing" + + # Test dropped attrs + dat = ds.coarsen(coord=5).mean() + assert dat.attrs == {} + + # Test kept attrs + dat = ds.coarsen(coord=5, keep_attrs=True).mean() + assert dat.attrs == _attrs + + def test_rolling_properties(ds): # catching invalid args with pytest.raises(ValueError, match="exactly one dim/window should"): From cb1e6c4c5d0e8809b6cae4282198f2dbffb0993a Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Wed, 26 Feb 2020 19:02:50 -0500 Subject: [PATCH 02/16] Add test of Variable.coarsen() for #3376 --- xarray/tests/test_variable.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_variable.py b/xarray/tests/test_variable.py index 62fde920b1e..b463380a934 100644 --- a/xarray/tests/test_variable.py +++ b/xarray/tests/test_variable.py @@ -9,7 +9,7 @@ import pytz from xarray import Coordinate, Dataset, IndexVariable, Variable, set_options -from xarray.core import dtypes, indexing +from xarray.core import dtypes, indexing, duck_array_ops from xarray.core.common import full_like, ones_like, zeros_like from xarray.core.indexing import ( BasicIndexer, @@ -1880,6 +1880,27 @@ def test_coarsen_2d(self): assert_equal(actual, expected) + # perhaps @pytest.mark.parametrize("operation", [f for f in duck_array_ops]) + def test_coarsen_keep_attrs(self, operation="mean"): + _attrs = {"units": "test", "long_name": "testing"} + + coord = Variable(["coord"], np.linspace(1, 10, 100), attrs=_attrs) + + test_func = getattr(duck_array_ops, operation, None) + + # Test dropped attrs + with set_options(keep_attrs=False): + new = coord.coarsen(windows={"coord": 1}, func=test_func, + boundary="exact", side="left") + assert new.attrs == {} + + # Test kept attrs + with set_options(keep_attrs=True): + new = coord.coarsen(windows={"coord": 1}, func=test_func, + boundary="exact", side="left") + assert new.attrs == _attrs + + @requires_dask class TestVariableWithDask(VariableSubclassobjects): cls = staticmethod(lambda *args: Variable(*args).chunk()) From ade6a49e722810dcab836f9e47decefc2d367ee2 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Wed, 26 Feb 2020 19:08:07 -0500 Subject: [PATCH 03/16] Add keep_attrs kwarg to DataWithCoords.coarsen() for #3376 --- xarray/core/common.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/xarray/core/common.py b/xarray/core/common.py index e908c69dd14..164688aa319 100644 --- a/xarray/core/common.py +++ b/xarray/core/common.py @@ -848,6 +848,7 @@ def coarsen( boundary: str = "exact", side: Union[str, Mapping[Hashable, str]] = "left", coord_func: str = "mean", + keep_attrs: bool = None, **window_kwargs: int, ): """ @@ -870,6 +871,10 @@ def coarsen( side : 'left' or 'right' or mapping from dimension to 'left' or 'right' coord_func : function (name) that is applied to the coordintes, or a mapping from coordinate name to function (name). + keep_attrs : bool, optional + If True, the object's attributes (`attrs`) will be copied from + the original object to the new one. If False (default), the new + object will be returned without attributes. Returns ------- @@ -904,6 +909,9 @@ def coarsen( core.rolling.DataArrayCoarsen core.rolling.DatasetCoarsen """ + if keep_attrs is None: + keep_attrs = _get_keep_attrs(default=False) + dim = either_dict_or_kwargs(dim, window_kwargs, "coarsen") return self._coarsen_cls( self, dim, boundary=boundary, side=side, coord_func=coord_func From 523cca4562ba115168ca764c7acc6a7cb7e7455d Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Wed, 26 Feb 2020 19:13:36 -0500 Subject: [PATCH 04/16] Style and spelling fixes (#3376) --- xarray/core/common.py | 2 +- xarray/tests/test_dataset.py | 8 ++++---- xarray/tests/test_variable.py | 13 +++++++------ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/xarray/core/common.py b/xarray/core/common.py index 164688aa319..04c5c353794 100644 --- a/xarray/core/common.py +++ b/xarray/core/common.py @@ -869,7 +869,7 @@ def coarsen( multiple of the window size. If 'trim', the excess entries are dropped. If 'pad', NA will be padded. side : 'left' or 'right' or mapping from dimension to 'left' or 'right' - coord_func : function (name) that is applied to the coordintes, + coord_func : function (name) that is applied to the coordinates, or a mapping from coordinate name to function (name). keep_attrs : bool, optional If True, the object's attributes (`attrs`) will be copied from diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index fb39bbcc48e..b739827bcd3 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -5656,22 +5656,22 @@ def test_coarsen_coords_cftime(): def test_coarsen_keep_attrs(self): _attrs = {"units": "test", "long_name": "testing"} - + var1 = np.linspace(10, 15, 100) var2 = np.linspace(5, 10, 100) coords = np.linspace(1, 10, 100) - + ds = Dataset( data_vars={"var1": ("coord", var1), "var2": ("coord", var2)}, coords={"coord": coords}, ) ds.attrs["units"] = "test" ds.attrs["long_name"] = "testing" - + # Test dropped attrs dat = ds.coarsen(coord=5).mean() assert dat.attrs == {} - + # Test kept attrs dat = ds.coarsen(coord=5, keep_attrs=True).mean() assert dat.attrs == _attrs diff --git a/xarray/tests/test_variable.py b/xarray/tests/test_variable.py index b463380a934..1d071da0dfd 100644 --- a/xarray/tests/test_variable.py +++ b/xarray/tests/test_variable.py @@ -9,7 +9,7 @@ import pytz from xarray import Coordinate, Dataset, IndexVariable, Variable, set_options -from xarray.core import dtypes, indexing, duck_array_ops +from xarray.core import dtypes, duck_array_ops, indexing from xarray.core.common import full_like, ones_like, zeros_like from xarray.core.indexing import ( BasicIndexer, @@ -1879,7 +1879,6 @@ def test_coarsen_2d(self): expected = self.cls(("x", "y"), [[10, 18], [42, 35]]) assert_equal(actual, expected) - # perhaps @pytest.mark.parametrize("operation", [f for f in duck_array_ops]) def test_coarsen_keep_attrs(self, operation="mean"): _attrs = {"units": "test", "long_name": "testing"} @@ -1890,14 +1889,16 @@ def test_coarsen_keep_attrs(self, operation="mean"): # Test dropped attrs with set_options(keep_attrs=False): - new = coord.coarsen(windows={"coord": 1}, func=test_func, - boundary="exact", side="left") + new = coord.coarsen( + windows={"coord": 1}, func=test_func, boundary="exact", side="left" + ) assert new.attrs == {} # Test kept attrs with set_options(keep_attrs=True): - new = coord.coarsen(windows={"coord": 1}, func=test_func, - boundary="exact", side="left") + new = coord.coarsen( + windows={"coord": 1}, func=test_func, boundary="exact", side="left" + ) assert new.attrs == _attrs From 5113bf3ec11c2c9bf2aa644c8d1397b1d9463b37 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Thu, 27 Feb 2020 11:19:16 -0500 Subject: [PATCH 05/16] Fix test_coarsen_keep_attrs by removing self from input --- xarray/tests/test_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index b739827bcd3..04fe346f90e 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -5654,7 +5654,7 @@ def test_coarsen_coords_cftime(): np.testing.assert_array_equal(actual.time, expected_times) -def test_coarsen_keep_attrs(self): +def test_coarsen_keep_attrs(): _attrs = {"units": "test", "long_name": "testing"} var1 = np.linspace(10, 15, 100) From df1b0dcc19fa89de2f21970bf6390cbb2ed382ee Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Thu, 27 Feb 2020 11:22:09 -0500 Subject: [PATCH 06/16] Pass keep_attrs through to _coarsen_cls and _rolling_cls returns (#3376) --- xarray/core/common.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/xarray/core/common.py b/xarray/core/common.py index 04c5c353794..c941d36cc4e 100644 --- a/xarray/core/common.py +++ b/xarray/core/common.py @@ -742,6 +742,7 @@ def rolling( dim: Mapping[Hashable, int] = None, min_periods: int = None, center: bool = False, + keep_attrs: bool = None, **window_kwargs: int, ): """ @@ -758,6 +759,10 @@ def rolling( setting min_periods equal to the size of the window. center : boolean, default False Set the labels at the center of the window. + keep_attrs : bool, optional + If True, the object's attributes (`attrs`) will be copied from + the original object to the new one. If False (default), the new + object will be returned without attributes. **window_kwargs : optional The keyword arguments form of ``dim``. One of dim or window_kwargs must be provided. @@ -799,8 +804,11 @@ def rolling( core.rolling.DataArrayRolling core.rolling.DatasetRolling """ + if keep_attrs is None: + keep_attrs = _get_keep_attrs(default=False) + dim = either_dict_or_kwargs(dim, window_kwargs, "rolling") - return self._rolling_cls(self, dim, min_periods=min_periods, center=center) + return self._rolling_cls(self, dim, min_periods=min_periods, center=center, keep_attrs=keep_attrs) def rolling_exp( self, @@ -914,7 +922,7 @@ def coarsen( dim = either_dict_or_kwargs(dim, window_kwargs, "coarsen") return self._coarsen_cls( - self, dim, boundary=boundary, side=side, coord_func=coord_func + self, dim, boundary=boundary, side=side, coord_func=coord_func, keep_attrs=keep_attrs ) def resample( From af7bd10fa22d126b03639dff3c1e3ddd1a1ddef3 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Thu, 27 Feb 2020 13:57:11 -0500 Subject: [PATCH 07/16] Move keyword from coarsen to mean in test_coarsen_keep_attrs --- xarray/tests/test_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 04fe346f90e..0adebceac60 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -5673,7 +5673,7 @@ def test_coarsen_keep_attrs(): assert dat.attrs == {} # Test kept attrs - dat = ds.coarsen(coord=5, keep_attrs=True).mean() + dat = ds.coarsen(coord=5).mean(keep_attrs=True) assert dat.attrs == _attrs From 4a458403ffb6db5227e19ad7c5a55fe8712ada80 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Thu, 27 Feb 2020 13:58:40 -0500 Subject: [PATCH 08/16] Start handling keep_attrs in rolling class constructors (#3376) --- xarray/core/rolling.py | 44 +++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/xarray/core/rolling.py b/xarray/core/rolling.py index ea6d72b2e03..326b165c891 100644 --- a/xarray/core/rolling.py +++ b/xarray/core/rolling.py @@ -7,6 +7,7 @@ from . import dtypes, duck_array_ops, utils from .dask_array_ops import dask_rolling_wrapper from .ops import inject_reduce_methods +from .options import _get_keep_attrs from .pycompat import dask_array_type try: @@ -42,10 +43,10 @@ class Rolling: DataArray.rolling """ - __slots__ = ("obj", "window", "min_periods", "center", "dim") - _attributes = ("window", "min_periods", "center", "dim") + __slots__ = ("obj", "window", "min_periods", "center", "dim", "keep_attrs") + _attributes = ("window", "min_periods", "center", "dim", "keep_attrs") - def __init__(self, obj, windows, min_periods=None, center=False): + def __init__(self, obj, windows, min_periods=None, center=False, keep_attrs=None): """ Moving window object. @@ -65,6 +66,10 @@ def __init__(self, obj, windows, min_periods=None, center=False): setting min_periods equal to the size of the window. center : boolean, default False Set the labels at the center of the window. + keep_attrs : bool, optional + If True, the object's attributes (`attrs`) will be copied from + the original object to the new one. If False (default), the new + object will be returned without attributes. Returns ------- @@ -89,6 +94,10 @@ def __init__(self, obj, windows, min_periods=None, center=False): self.center = center self.dim = dim + if keep_attrs is None: + keep_attrs = _get_keep_attrs(default=False) + self.keep_attrs = keep_attrs + @property def _min_periods(self): return self.min_periods if self.min_periods is not None else self.window @@ -143,7 +152,7 @@ def count(self): class DataArrayRolling(Rolling): __slots__ = ("window_labels",) - def __init__(self, obj, windows, min_periods=None, center=False): + def __init__(self, obj, windows, min_periods=None, center=False, keep_attrs=None): """ Moving window object for DataArray. You should use DataArray.rolling() method to construct this object @@ -165,6 +174,10 @@ def __init__(self, obj, windows, min_periods=None, center=False): setting min_periods equal to the size of the window. center : boolean, default False Set the labels at the center of the window. + keep_attrs : bool, optional + If True, the object's attributes (`attrs`) will be copied from + the original object to the new one. If False (default), the new + object will be returned without attributes. Returns ------- @@ -177,7 +190,9 @@ def __init__(self, obj, windows, min_periods=None, center=False): Dataset.rolling Dataset.groupby """ - super().__init__(obj, windows, min_periods=min_periods, center=center) + if keep_attrs is None: + keep_attrs = _get_keep_attrs(default=False) + super().__init__(obj, windows, min_periods=min_periods, center=center, keep_attrs=keep_attrs) self.window_labels = self.obj[self.dim] @@ -374,7 +389,7 @@ def _numpy_or_bottleneck_reduce( class DatasetRolling(Rolling): __slots__ = ("rollings",) - def __init__(self, obj, windows, min_periods=None, center=False): + def __init__(self, obj, windows, min_periods=None, center=False, keep_attrs=None): """ Moving window object for Dataset. You should use Dataset.rolling() method to construct this object @@ -396,6 +411,10 @@ def __init__(self, obj, windows, min_periods=None, center=False): setting min_periods equal to the size of the window. center : boolean, default False Set the labels at the center of the window. + keep_attrs : bool, optional + If True, the object's attributes (`attrs`) will be copied from + the original object to the new one. If False (default), the new + object will be returned without attributes. Returns ------- @@ -408,7 +427,7 @@ def __init__(self, obj, windows, min_periods=None, center=False): Dataset.groupby DataArray.groupby """ - super().__init__(obj, windows, min_periods, center) + super().__init__(obj, windows, min_periods, center, keep_attrs) if self.dim not in self.obj.dims: raise KeyError(self.dim) # Keep each Rolling object as a dictionary @@ -416,7 +435,7 @@ def __init__(self, obj, windows, min_periods=None, center=False): for key, da in self.obj.data_vars.items(): # keeps rollings only for the dataset depending on slf.dim if self.dim in da.dims: - self.rollings[key] = DataArrayRolling(da, windows, min_periods, center) + self.rollings[key] = DataArrayRolling(da, windows, min_periods, center, keep_attrs) def _dataset_implementation(self, func, **kwargs): from .dataset import Dataset @@ -466,7 +485,7 @@ def _numpy_or_bottleneck_reduce( **kwargs, ) - def construct(self, window_dim, stride=1, fill_value=dtypes.NA): + def construct(self, window_dim, stride=1, fill_value=dtypes.NA, keep_attrs=None): """ Convert this rolling object to xr.Dataset, where the window dimension is stacked as a new dimension @@ -487,6 +506,9 @@ def construct(self, window_dim, stride=1, fill_value=dtypes.NA): from .dataset import Dataset + if keep_attrs is None: + keep_attrs = _get_keep_attrs(default=True) + dataset = {} for key, da in self.obj.data_vars.items(): if self.dim in da.dims: @@ -509,10 +531,10 @@ class Coarsen: DataArray.coarsen """ - __slots__ = ("obj", "boundary", "coord_func", "windows", "side", "trim_excess") + __slots__ = ("obj", "boundary", "coord_func", "windows", "side", "trim_excess", "keep_attrs") _attributes = ("windows", "side", "trim_excess") - def __init__(self, obj, windows, boundary, side, coord_func): + def __init__(self, obj, windows, boundary, side, coord_func, keep_attrs): """ Moving window object. From 28f705fb8f4d52d71854b5dc2edd893ad78ba8b9 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Thu, 27 Feb 2020 21:19:46 -0500 Subject: [PATCH 09/16] Update Coarsen constructor and DatasetCoarsen class method (GH3376) Assign keep_attrs keyword value to Coarsen objects in constructor Add conditional inside _reduce_method.wrapped_func branching on self.keep_attrs and pass back to returned Dataset --- xarray/core/rolling.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/xarray/core/rolling.py b/xarray/core/rolling.py index 326b165c891..b5ec80ab129 100644 --- a/xarray/core/rolling.py +++ b/xarray/core/rolling.py @@ -563,6 +563,7 @@ def __init__(self, obj, windows, boundary, side, coord_func, keep_attrs): self.windows = windows self.side = side self.boundary = boundary + self.keep_attrs = keep_attrs absent_dims = [dim for dim in windows.keys() if dim not in self.obj.dims] if absent_dims: @@ -608,7 +609,7 @@ def wrapped_func(self, **kwargs): from .dataarray import DataArray reduced = self.obj.variable.coarsen( - self.windows, func, self.boundary, self.side, **kwargs + self.windows, func, self.boundary, self.side, self.keep_attrs, **kwargs ) coords = {} for c, v in self.obj.coords.items(): @@ -648,6 +649,11 @@ def _reduce_method(cls, func: Callable, include_skipna: bool, numeric_only: bool def wrapped_func(self, **kwargs): from .dataset import Dataset + if self.keep_attrs == True: + _attrs = self.obj.attrs + else: + attrs = {} + reduced = {} for key, da in self.obj.data_vars.items(): reduced[key] = da.variable.coarsen( @@ -666,7 +672,7 @@ def wrapped_func(self, **kwargs): ) else: coords[c] = v.variable - return Dataset(reduced, coords=coords) + return Dataset(reduced, coords=coords, attrs=attrs) return wrapped_func From 2c61115a97b620a68dcd7c8e3246f414b46910d3 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Fri, 28 Feb 2020 19:22:46 -0500 Subject: [PATCH 10/16] Incorporate code review from @max-sixty --- xarray/core/rolling.py | 2 +- xarray/tests/test_dataset.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/xarray/core/rolling.py b/xarray/core/rolling.py index b5ec80ab129..bf67fc867ca 100644 --- a/xarray/core/rolling.py +++ b/xarray/core/rolling.py @@ -649,7 +649,7 @@ def _reduce_method(cls, func: Callable, include_skipna: bool, numeric_only: bool def wrapped_func(self, **kwargs): from .dataset import Dataset - if self.keep_attrs == True: + if self.keep_attrs: _attrs = self.obj.attrs else: attrs = {} diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 0adebceac60..a830145cac5 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -5664,9 +5664,8 @@ def test_coarsen_keep_attrs(): ds = Dataset( data_vars={"var1": ("coord", var1), "var2": ("coord", var2)}, coords={"coord": coords}, + attrs={'units':'test', 'long_name':'testing'} ) - ds.attrs["units"] = "test" - ds.attrs["long_name"] = "testing" # Test dropped attrs dat = ds.coarsen(coord=5).mean() From e05ca96b559f8ca730e6dc161f50d7c0ca814d39 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Fri, 28 Feb 2020 19:55:55 -0500 Subject: [PATCH 11/16] Fix Dataset.coarsen and Variable.coarsen for GH3376 Handle global keep_attrs setting inside Variable._coarsen_reshape Pass attrs through consistently inside DatasetCoarsen._reduce_method Don't pass Variable.coarsen a keyword argument it doesn't expect inside DataArrayCoarsen._reduce_method --- xarray/core/rolling.py | 4 ++-- xarray/core/variable.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/xarray/core/rolling.py b/xarray/core/rolling.py index bf67fc867ca..5173c5c0733 100644 --- a/xarray/core/rolling.py +++ b/xarray/core/rolling.py @@ -609,7 +609,7 @@ def wrapped_func(self, **kwargs): from .dataarray import DataArray reduced = self.obj.variable.coarsen( - self.windows, func, self.boundary, self.side, self.keep_attrs, **kwargs + self.windows, func, self.boundary, self.side, **kwargs ) coords = {} for c, v in self.obj.coords.items(): @@ -650,7 +650,7 @@ def wrapped_func(self, **kwargs): from .dataset import Dataset if self.keep_attrs: - _attrs = self.obj.attrs + attrs = self.obj.attrs else: attrs = {} diff --git a/xarray/core/variable.py b/xarray/core/variable.py index daa8678157b..6f9e379feee 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -1949,6 +1949,11 @@ def _coarsen_reshape(self, windows, boundary, side): else: shape.append(variable.shape[i]) + keep_attrs = _get_keep_attrs(default=False) + if keep_attrs is None: + keep_attrs = _get_keep_attrs(default=False) + variable.attrs = variable._attrs if keep_attrs else {} + return variable.data.reshape(shape), tuple(axes) @property From d326404164ab727de43df7a50f10be6a84d31c03 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Fri, 28 Feb 2020 20:06:02 -0500 Subject: [PATCH 12/16] Update tests for GH3376 --- xarray/tests/test_dataset.py | 13 +++++++++++-- xarray/tests/test_variable.py | 13 +++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index a830145cac5..d2ffa4a8a27 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -5671,8 +5671,17 @@ def test_coarsen_keep_attrs(): dat = ds.coarsen(coord=5).mean() assert dat.attrs == {} - # Test kept attrs - dat = ds.coarsen(coord=5).mean(keep_attrs=True) + # Test kept attrs using dataset keyword + dat = ds.coarsen(coord=5, keep_attrs=True).mean() + assert dat.attrs == _attrs + + # # Test kept attrs using wrapper function keyword + # dat = ds.coarsen(coord=5).mean(keep_attrs=True) + # assert dat.attrs == _attrs + + # Test kept attrs using global option + with set_options(keep_attrs=True): + dat = ds.coarsen(coord=5).mean() assert dat.attrs == _attrs diff --git a/xarray/tests/test_variable.py b/xarray/tests/test_variable.py index 1d071da0dfd..a0ee551298e 100644 --- a/xarray/tests/test_variable.py +++ b/xarray/tests/test_variable.py @@ -1883,25 +1883,22 @@ def test_coarsen_2d(self): def test_coarsen_keep_attrs(self, operation="mean"): _attrs = {"units": "test", "long_name": "testing"} - coord = Variable(["coord"], np.linspace(1, 10, 100), attrs=_attrs) - test_func = getattr(duck_array_ops, operation, None) # Test dropped attrs with set_options(keep_attrs=False): - new = coord.coarsen( - windows={"coord": 1}, func=test_func, boundary="exact", side="left" - ) + new = (Variable(["coord"], np.linspace(1, 10, 100), attrs=_attrs) + .coarsen(windows={"coord": 1}, func=test_func, boundary="exact", side="left")) assert new.attrs == {} # Test kept attrs with set_options(keep_attrs=True): - new = coord.coarsen( - windows={"coord": 1}, func=test_func, boundary="exact", side="left" - ) + new = (Variable(["coord"], np.linspace(1, 10, 100), attrs=_attrs) + .coarsen(windows={"coord": 1}, func=test_func, boundary="exact", side="left")) assert new.attrs == _attrs + @requires_dask class TestVariableWithDask(VariableSubclassobjects): cls = staticmethod(lambda *args: Variable(*args).chunk()) From 735dc5a10af23f2c4c147f203e9540343ef6337d Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Sat, 29 Feb 2020 12:08:17 -0500 Subject: [PATCH 13/16] Incorporate review changes to test_dataset for GH3376 Remove commented-out test from test_coarsen_keep_attrs Add test_rolling_keep_attrs --- xarray/tests/test_dataset.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index d2ffa4a8a27..07ab7f6d2d6 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -5664,7 +5664,7 @@ def test_coarsen_keep_attrs(): ds = Dataset( data_vars={"var1": ("coord", var1), "var2": ("coord", var2)}, coords={"coord": coords}, - attrs={'units':'test', 'long_name':'testing'} + attrs=_attrs ) # Test dropped attrs @@ -5675,16 +5675,39 @@ def test_coarsen_keep_attrs(): dat = ds.coarsen(coord=5, keep_attrs=True).mean() assert dat.attrs == _attrs - # # Test kept attrs using wrapper function keyword - # dat = ds.coarsen(coord=5).mean(keep_attrs=True) - # assert dat.attrs == _attrs - # Test kept attrs using global option with set_options(keep_attrs=True): dat = ds.coarsen(coord=5).mean() assert dat.attrs == _attrs +def test_rolling_keep_attrs(): + _attrs = {"units": "test", "long_name": "testing"} + + var1 = np.linspace(10, 15, 100) + var2 = np.linspace(5, 10, 100) + coords = np.linspace(1, 10, 100) + + ds = Dataset( + data_vars={"var1": ("coord", var1), "var2": ("coord", var2)}, + coords={"coord": coords}, + attrs=_attrs + ) + + # Test dropped attrs + dat = ds.rolling(dim={'coord':5}, min_periods=None, center=False).mean() + assert dat.attrs == {} + + # Test kept attrs using dataset keyword + dat = ds.rolling(dim={'coord':5}, min_periods=None, center=False, keep_attrs=True).mean() + assert dat.attrs == _attrs + + # Test kept attrs using global option + with set_options(keep_attrs=True): + dat = ds.rolling(dim={'coord':5}, min_periods=None, center=False).mean() + assert dat.attrs == _attrs + + def test_rolling_properties(ds): # catching invalid args with pytest.raises(ValueError, match="exactly one dim/window should"): From 496838c175d8ead5a8fca421acc72bc6028c16f3 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Sat, 29 Feb 2020 12:19:58 -0500 Subject: [PATCH 14/16] Change Rolling._dataset_implementation for GH3376 Return a Dataset object that results in test_rolling_keep_attrs Passing --- xarray/core/rolling.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xarray/core/rolling.py b/xarray/core/rolling.py index 5173c5c0733..b5dbfd4e02d 100644 --- a/xarray/core/rolling.py +++ b/xarray/core/rolling.py @@ -446,7 +446,8 @@ def _dataset_implementation(self, func, **kwargs): reduced[key] = func(self.rollings[key], **kwargs) else: reduced[key] = self.obj[key] - return Dataset(reduced, coords=self.obj.coords) + attrs = self.obj.attrs if self.keep_attrs else {} + return Dataset(reduced, coords=self.obj.coords, attrs=attrs) def reduce(self, func, **kwargs): """Reduce the items in this group by applying `func` along some From 975a7969d7fa19e7413cd2d50aa51f0f244a418b Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Sat, 29 Feb 2020 12:41:56 -0500 Subject: [PATCH 15/16] style fixes --- xarray/core/common.py | 11 +++++++++-- xarray/core/rolling.py | 20 ++++++++++++++++---- xarray/tests/test_dataset.py | 12 +++++++----- xarray/tests/test_variable.py | 11 ++++++----- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/xarray/core/common.py b/xarray/core/common.py index c941d36cc4e..cf9d711d65d 100644 --- a/xarray/core/common.py +++ b/xarray/core/common.py @@ -808,7 +808,9 @@ def rolling( keep_attrs = _get_keep_attrs(default=False) dim = either_dict_or_kwargs(dim, window_kwargs, "rolling") - return self._rolling_cls(self, dim, min_periods=min_periods, center=center, keep_attrs=keep_attrs) + return self._rolling_cls( + self, dim, min_periods=min_periods, center=center, keep_attrs=keep_attrs + ) def rolling_exp( self, @@ -922,7 +924,12 @@ def coarsen( dim = either_dict_or_kwargs(dim, window_kwargs, "coarsen") return self._coarsen_cls( - self, dim, boundary=boundary, side=side, coord_func=coord_func, keep_attrs=keep_attrs + self, + dim, + boundary=boundary, + side=side, + coord_func=coord_func, + keep_attrs=keep_attrs, ) def resample( diff --git a/xarray/core/rolling.py b/xarray/core/rolling.py index b5dbfd4e02d..61178cfb15f 100644 --- a/xarray/core/rolling.py +++ b/xarray/core/rolling.py @@ -192,7 +192,9 @@ def __init__(self, obj, windows, min_periods=None, center=False, keep_attrs=None """ if keep_attrs is None: keep_attrs = _get_keep_attrs(default=False) - super().__init__(obj, windows, min_periods=min_periods, center=center, keep_attrs=keep_attrs) + super().__init__( + obj, windows, min_periods=min_periods, center=center, keep_attrs=keep_attrs + ) self.window_labels = self.obj[self.dim] @@ -435,7 +437,9 @@ def __init__(self, obj, windows, min_periods=None, center=False, keep_attrs=None for key, da in self.obj.data_vars.items(): # keeps rollings only for the dataset depending on slf.dim if self.dim in da.dims: - self.rollings[key] = DataArrayRolling(da, windows, min_periods, center, keep_attrs) + self.rollings[key] = DataArrayRolling( + da, windows, min_periods, center, keep_attrs + ) def _dataset_implementation(self, func, **kwargs): from .dataset import Dataset @@ -446,7 +450,7 @@ def _dataset_implementation(self, func, **kwargs): reduced[key] = func(self.rollings[key], **kwargs) else: reduced[key] = self.obj[key] - attrs = self.obj.attrs if self.keep_attrs else {} + attrs = self.obj.attrs if self.keep_attrs else {} return Dataset(reduced, coords=self.obj.coords, attrs=attrs) def reduce(self, func, **kwargs): @@ -532,7 +536,15 @@ class Coarsen: DataArray.coarsen """ - __slots__ = ("obj", "boundary", "coord_func", "windows", "side", "trim_excess", "keep_attrs") + __slots__ = ( + "obj", + "boundary", + "coord_func", + "windows", + "side", + "trim_excess", + "keep_attrs", + ) _attributes = ("windows", "side", "trim_excess") def __init__(self, obj, windows, boundary, side, coord_func, keep_attrs): diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 33fe8decaeb..7bcf9379ae8 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -5674,7 +5674,7 @@ def test_coarsen_keep_attrs(): ds = Dataset( data_vars={"var1": ("coord", var1), "var2": ("coord", var2)}, coords={"coord": coords}, - attrs=_attrs + attrs=_attrs, ) # Test dropped attrs @@ -5701,20 +5701,22 @@ def test_rolling_keep_attrs(): ds = Dataset( data_vars={"var1": ("coord", var1), "var2": ("coord", var2)}, coords={"coord": coords}, - attrs=_attrs + attrs=_attrs, ) # Test dropped attrs - dat = ds.rolling(dim={'coord':5}, min_periods=None, center=False).mean() + dat = ds.rolling(dim={"coord": 5}, min_periods=None, center=False).mean() assert dat.attrs == {} # Test kept attrs using dataset keyword - dat = ds.rolling(dim={'coord':5}, min_periods=None, center=False, keep_attrs=True).mean() + dat = ds.rolling( + dim={"coord": 5}, min_periods=None, center=False, keep_attrs=True + ).mean() assert dat.attrs == _attrs # Test kept attrs using global option with set_options(keep_attrs=True): - dat = ds.rolling(dim={'coord':5}, min_periods=None, center=False).mean() + dat = ds.rolling(dim={"coord": 5}, min_periods=None, center=False).mean() assert dat.attrs == _attrs diff --git a/xarray/tests/test_variable.py b/xarray/tests/test_variable.py index a0ee551298e..c86ecd0121f 100644 --- a/xarray/tests/test_variable.py +++ b/xarray/tests/test_variable.py @@ -1887,18 +1887,19 @@ def test_coarsen_keep_attrs(self, operation="mean"): # Test dropped attrs with set_options(keep_attrs=False): - new = (Variable(["coord"], np.linspace(1, 10, 100), attrs=_attrs) - .coarsen(windows={"coord": 1}, func=test_func, boundary="exact", side="left")) + new = Variable(["coord"], np.linspace(1, 10, 100), attrs=_attrs).coarsen( + windows={"coord": 1}, func=test_func, boundary="exact", side="left" + ) assert new.attrs == {} # Test kept attrs with set_options(keep_attrs=True): - new = (Variable(["coord"], np.linspace(1, 10, 100), attrs=_attrs) - .coarsen(windows={"coord": 1}, func=test_func, boundary="exact", side="left")) + new = Variable(["coord"], np.linspace(1, 10, 100), attrs=_attrs).coarsen( + windows={"coord": 1}, func=test_func, boundary="exact", side="left" + ) assert new.attrs == _attrs - @requires_dask class TestVariableWithDask(VariableSubclassobjects): cls = staticmethod(lambda *args: Variable(*args).chunk()) From 98edaa85c9ae60ba38a1c62aa2aa3fac30e3b059 Mon Sep 17 00:00:00 2001 From: Andrew McNichols Date: Mon, 2 Mar 2020 09:49:40 -0500 Subject: [PATCH 16/16] Remove duplicate variable assignment and document change (GH3776) --- doc/whats-new.rst | 5 +++++ xarray/core/variable.py | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 6a5491e34dd..4431bb49763 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -48,6 +48,11 @@ Bug fixes - xarray now respects the over, under and bad colors if set on a provided colormap. (:issue:`3590`, :pull:`3601`) By `johnomotani `_. +- :py:func:`coarsen` now respects ``xr.set_options(keep_attrs=True)`` + to preserve attributes. :py:meth:`Dataset.coarsen` accepts a keyword + argument ``keep_attrs`` to change this setting. (:issue:`3376`, + :pull:`3801`) By `Andrew Thomas `_. + Documentation ~~~~~~~~~~~~~ diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 6f9e379feee..62f9fde6a2e 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -1950,8 +1950,6 @@ def _coarsen_reshape(self, windows, boundary, side): shape.append(variable.shape[i]) keep_attrs = _get_keep_attrs(default=False) - if keep_attrs is None: - keep_attrs = _get_keep_attrs(default=False) variable.attrs = variable._attrs if keep_attrs else {} return variable.data.reshape(shape), tuple(axes)