Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add coarsen.construct #5476

Merged
merged 16 commits into from
Jun 24, 2021
Merged
2 changes: 1 addition & 1 deletion doc/howdoi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ How do I ...
* - change the order of dimensions
- :py:meth:`DataArray.transpose`, :py:meth:`Dataset.transpose`
* - reshape dimensions
- :py:meth:`DataArray.stack`, :py:meth:`Dataset.stack`
- :py:meth:`DataArray.stack`, :py:meth:`Dataset.stack`, :py:meth:`Dataset.coarsen.construct`, :py:meth:`DataArray.coarsen.construct`
* - remove a variable from my object
- :py:meth:`Dataset.drop_vars`, :py:meth:`DataArray.drop_vars`
* - remove dimensions of length 1 or 0
Expand Down
2 changes: 2 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ v0.18.3 (unreleased)

New Features
~~~~~~~~~~~~
- Added :py:meth:`Dataset.coarsen.construct`, :py:meth:`DataArray.coarsen.construct` (:issue:`5454`, :pull:`5475`).
By `Deepak Cherian <https://github.com/dcherian>`_.
- New top-level function :py:func:`unify_chunks`.
By `Mattia Almansi <https://github.com/malmans2>`_.
- Allow assigning values to a subset of a dataset using positional or label-based
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ ignore =
E501 # line too long - let black worry about that
E731 # do not assign a lambda expression, use a def
W503 # line break before binary operator
per-file-ignores =
xarray/tests/*.py:F401,F811
exclude=
.eggs
doc
Expand Down
75 changes: 75 additions & 0 deletions xarray/core/rolling.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import functools
import itertools
import warnings
from typing import Any, Callable, Dict

Expand Down Expand Up @@ -845,6 +846,80 @@ def __repr__(self):
klass=self.__class__.__name__, attrs=",".join(attrs)
)

def construct(
self,
window_dim=None,
keep_attrs=None,
**window_dim_kwargs,
):
"""
Convert this Coarsen object to a Dataset,
where the window dimension is reshaped to new dimensions

Parameters
----------
window_dim: str or a mapping, optional
A mapping from dimension name to the new window dimension names.
Just a string can be used for 1d-rolling.
dcherian marked this conversation as resolved.
Show resolved Hide resolved
keep_attrs: bool, optional
Preserve attributes if True
**window_dim_kwargs : {dim: new_name, ...}, optional
The keyword arguments form of ``window_dim``.

Returns
-------
Dataset with variables converted from rolling object.
dcherian marked this conversation as resolved.
Show resolved Hide resolved
"""

from .dataarray import DataArray
from .dataset import Dataset

if window_dim is None:
if len(window_dim_kwargs) == 0:
raise ValueError(
"Either window_dim or window_dim_kwargs need to be specified."
)
window_dim = {d: window_dim_kwargs[d] for d in self.dim}

if keep_attrs is None:
keep_attrs = _get_keep_attrs(default=True)

missing_dims = set(window_dim) - set(self.windows)
if missing_dims:
raise ValueError(
f"'window_dim' must contain entries for all dimensions to coarsen. Missing {missing_dims}"
)
missing_windows = set(self.windows) - set(window_dim)
dcherian marked this conversation as resolved.
Show resolved Hide resolved
if missing_windows:
dcherian marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError(
f"'window_dim' includes dimensions that will not be coarsened: {missing_windows}"
)

reshaped = Dataset()
dcherian marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(self.obj, DataArray):
obj = self.obj._to_temp_dataset()
else:
obj = self.obj

for key, var in obj.variables.items():
reshaped_dims = tuple(
itertools.chain(*[window_dim.get(dim, [dim]) for dim in list(var.dims)])
)
if reshaped_dims != var.dims:
windows = {w: self.windows[w] for w in window_dim if w in var.dims}
reshaped_var, _ = var.coarsen_reshape(windows, self.boundary, self.side)
attrs = var.attrs if keep_attrs else {}
reshaped[key] = (reshaped_dims, reshaped_var, attrs)
else:
reshaped[key] = var

should_be_coords = set(window_dim) & set(self.obj.coords)
result = reshaped.set_coords(should_be_coords)
if isinstance(self.obj, DataArray):
return self.obj._from_temp_dataset(result)
else:
return result


class DataArrayCoarsen(Coarsen):
__slots__ = ()
Expand Down
12 changes: 7 additions & 5 deletions xarray/core/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -2158,7 +2158,7 @@ def coarsen(
if not windows:
return self._replace(attrs=_attrs)

reshaped, axes = self._coarsen_reshape(windows, boundary, side)
reshaped, axes = self.coarsen_reshape(windows, boundary, side)
if isinstance(func, str):
name = func
func = getattr(duck_array_ops, name, None)
Expand All @@ -2167,7 +2167,7 @@ def coarsen(

return self._replace(data=func(reshaped, axis=axes, **kwargs), attrs=_attrs)

def _coarsen_reshape(self, windows, boundary, side):
def coarsen_reshape(self, windows, boundary, side):
"""
Construct a reshaped-array for coarsen
"""
Expand All @@ -2183,7 +2183,9 @@ def _coarsen_reshape(self, windows, boundary, side):

for d, window in windows.items():
if window <= 0:
raise ValueError(f"window must be > 0. Given {window}")
raise ValueError(
f"window must be > 0. Given {window} for dimension {d}"
)

variable = self
for d, window in windows.items():
Expand All @@ -2193,8 +2195,8 @@ def _coarsen_reshape(self, windows, boundary, side):
if boundary[d] == "exact":
if n * window != size:
raise ValueError(
"Could not coarsen a dimension of size {} with "
"window {}".format(size, window)
f"Could not coarsen a dimension of size {size} with "
f"window {window} and boundary='exact'. Try a different 'boundary' option."
)
elif boundary[d] == "trim":
if side[d] == "left":
Expand Down
Loading