Skip to content

Commit ee0d422

Browse files
committed
Merge remote-tracking branch 'upstream/master' into fix/dask-computes
* upstream/master: Remove deprecated behavior from dataset.drop docstring (pydata#3451) jupyterlab dark theme (pydata#3443) Drop groups associated with nans in group variable (pydata#3406) Allow ellipsis (...) in transpose (pydata#3421) Another groupby.reduce bugfix. (pydata#3403) add icomoon license (pydata#3448)
2 parents e99148e + 74ca69a commit ee0d422

17 files changed

+677
-81
lines changed

README.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,7 @@ under a "3-clause BSD" license:
138138
xarray also bundles portions of CPython, which is available under the "Python
139139
Software Foundation License" in xarray/core/pycompat.py.
140140

141+
xarray uses icons from the icomoon package (free version), which is
142+
available under the "CC BY 4.0" license.
143+
141144
The full text of these licenses are included in the licenses directory.

doc/reshaping.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ Reordering dimensions
1818
---------------------
1919

2020
To reorder dimensions on a :py:class:`~xarray.DataArray` or across all variables
21-
on a :py:class:`~xarray.Dataset`, use :py:meth:`~xarray.DataArray.transpose`:
21+
on a :py:class:`~xarray.Dataset`, use :py:meth:`~xarray.DataArray.transpose`. An
22+
ellipsis (`...`) can be use to represent all other dimensions:
2223

2324
.. ipython:: python
2425
2526
ds = xr.Dataset({'foo': (('x', 'y', 'z'), [[[42]]]), 'bar': (('y', 'z'), [[24]])})
2627
ds.transpose('y', 'z', 'x')
28+
ds.transpose(..., 'x') # equivalent
2729
ds.transpose() # reverses all dimensions
2830
2931
Expand and squeeze dimensions

doc/whats-new.rst

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ Breaking changes
2525

2626
New Features
2727
~~~~~~~~~~~~
28+
- :py:meth:`Dataset.transpose` and :py:meth:`DataArray.transpose` now support an ellipsis (`...`)
29+
to represent all 'other' dimensions. For example, to move one dimension to the front,
30+
use `.transpose('x', ...)`. (:pull:`3421`)
31+
By `Maximilian Roos <https://github.com/max-sixty>`_
2832
- Changed `xr.ALL_DIMS` to equal python's `Ellipsis` (`...`), and changed internal usages to use
2933
`...` directly. As before, you can use this to instruct a `groupby` operation
3034
to reduce over all dimensions. While we have no plans to remove `xr.ALL_DIMS`, we suggest
@@ -51,10 +55,13 @@ Bug fixes
5155
~~~~~~~~~
5256
- Fix regression introduced in v0.14.0 that would cause a crash if dask is installed
5357
but cloudpickle isn't (:issue:`3401`) by `Rhys Doyle <https://github.com/rdoyle45>`_
54-
55-
- Sync with cftime by removing `dayofwk=-1` for cftime>=1.0.4.
58+
- Fix grouping over variables with NaNs. (:issue:`2383`, :pull:`3406`).
59+
By `Deepak Cherian <https://github.com/dcherian>`_.
60+
- Sync with cftime by removing `dayofwk=-1` for cftime>=1.0.4.
5661
By `Anderson Banihirwe <https://github.com/andersy005>`_.
57-
62+
- Fix :py:meth:`xarray.core.groupby.DataArrayGroupBy.reduce` and
63+
:py:meth:`xarray.core.groupby.DatasetGroupBy.reduce` when reducing over multiple dimensions.
64+
(:issue:`3402`). By `Deepak Cherian <https://github.com/dcherian/>`_
5865

5966
Documentation
6067
~~~~~~~~~~~~~

licenses/ICOMOON_LICENSE

Lines changed: 395 additions & 0 deletions
Large diffs are not rendered by default.

setup.cfg

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,7 @@ tag_prefix = v
117117
parentdir_prefix = xarray-
118118

119119
[aliases]
120-
test = pytest
120+
test = pytest
121+
122+
[pytest-watch]
123+
nobeep = True

xarray/core/dataarray.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,12 +1863,7 @@ def transpose(self, *dims: Hashable, transpose_coords: bool = None) -> "DataArra
18631863
Dataset.transpose
18641864
"""
18651865
if dims:
1866-
if set(dims) ^ set(self.dims):
1867-
raise ValueError(
1868-
"arguments to transpose (%s) must be "
1869-
"permuted array dimensions (%s)" % (dims, tuple(self.dims))
1870-
)
1871-
1866+
dims = tuple(utils.infix_dims(dims, self.dims))
18721867
variable = self.variable.transpose(*dims)
18731868
if transpose_coords:
18741869
coords: Dict[Hashable, Variable] = {}

xarray/core/dataset.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3542,7 +3542,6 @@ def drop( # noqa: F811
35423542
----------
35433543
labels : hashable or iterable of hashables
35443544
Name(s) of variables or index labels to drop.
3545-
If dim is not None, labels can be any array-like.
35463545
dim : None or hashable, optional
35473546
Dimension along which to drop index labels. By default (if
35483547
``dim is None``), drops variables rather than index labels.
@@ -3712,14 +3711,14 @@ def transpose(self, *dims: Hashable) -> "Dataset":
37123711
DataArray.transpose
37133712
"""
37143713
if dims:
3715-
if set(dims) ^ set(self.dims):
3714+
if set(dims) ^ set(self.dims) and ... not in dims:
37163715
raise ValueError(
37173716
"arguments to transpose (%s) must be "
37183717
"permuted dataset dimensions (%s)" % (dims, tuple(self.dims))
37193718
)
37203719
ds = self.copy()
37213720
for name, var in self._variables.items():
3722-
var_dims = tuple(dim for dim in dims if dim in var.dims)
3721+
var_dims = tuple(dim for dim in dims if dim in (var.dims + (...,)))
37233722
ds._variables[name] = var.transpose(*var_dims)
37243723
return ds
37253724

xarray/core/groupby.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,26 @@
1515
from .utils import (
1616
either_dict_or_kwargs,
1717
hashable,
18+
is_scalar,
1819
maybe_wrap_array,
1920
peek_at,
2021
safe_cast_to_index,
2122
)
2223
from .variable import IndexVariable, Variable, as_variable
2324

2425

26+
def check_reduce_dims(reduce_dims, dimensions):
27+
28+
if reduce_dims is not ...:
29+
if is_scalar(reduce_dims):
30+
reduce_dims = [reduce_dims]
31+
if any([dim not in dimensions for dim in reduce_dims]):
32+
raise ValueError(
33+
"cannot reduce over dimensions %r. expected either '...' to reduce over all dimensions or one or more of %r."
34+
% (reduce_dims, dimensions)
35+
)
36+
37+
2538
def unique_value_groups(ar, sort=True):
2639
"""Group an array by its unique values.
2740
@@ -348,6 +361,13 @@ def __init__(
348361
group_indices = [slice(i, i + 1) for i in group_indices]
349362
unique_coord = group
350363
else:
364+
if group.isnull().any():
365+
# drop any NaN valued groups.
366+
# also drop obj values where group was NaN
367+
# Use where instead of reindex to account for duplicate coordinate labels.
368+
obj = obj.where(group.notnull(), drop=True)
369+
group = group.dropna(group_dim)
370+
351371
# look through group to find the unique values
352372
unique_values, group_indices = unique_value_groups(
353373
safe_cast_to_index(group), sort=(bins is None)
@@ -794,15 +814,11 @@ def reduce(
794814
if keep_attrs is None:
795815
keep_attrs = _get_keep_attrs(default=False)
796816

797-
if dim is not ... and dim not in self.dims:
798-
raise ValueError(
799-
"cannot reduce over dimension %r. expected either '...' to reduce over all dimensions or one or more of %r."
800-
% (dim, self.dims)
801-
)
802-
803817
def reduce_array(ar):
804818
return ar.reduce(func, dim, axis, keep_attrs=keep_attrs, **kwargs)
805819

820+
check_reduce_dims(dim, self.dims)
821+
806822
return self.apply(reduce_array, shortcut=shortcut)
807823

808824

@@ -895,11 +911,7 @@ def reduce(self, func, dim=None, keep_attrs=None, **kwargs):
895911
def reduce_dataset(ds):
896912
return ds.reduce(func, dim, keep_attrs, **kwargs)
897913

898-
if dim is not ... and dim not in self.dims:
899-
raise ValueError(
900-
"cannot reduce over dimension %r. expected either '...' to reduce over all dimensions or one or more of %r."
901-
% (dim, self.dims)
902-
)
914+
check_reduce_dims(dim, self.dims)
903915

904916
return self.apply(reduce_dataset)
905917

xarray/core/utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
AbstractSet,
1111
Any,
1212
Callable,
13+
Collection,
1314
Container,
1415
Dict,
1516
Hashable,
@@ -660,6 +661,30 @@ def __len__(self) -> int:
660661
return len(self._data) - num_hidden
661662

662663

664+
def infix_dims(dims_supplied: Collection, dims_all: Collection) -> Iterator:
665+
"""
666+
Resolves a supplied list containing an ellispsis representing other items, to
667+
a generator with the 'realized' list of all items
668+
"""
669+
if ... in dims_supplied:
670+
if len(set(dims_all)) != len(dims_all):
671+
raise ValueError("Cannot use ellipsis with repeated dims")
672+
if len([d for d in dims_supplied if d == ...]) > 1:
673+
raise ValueError("More than one ellipsis supplied")
674+
other_dims = [d for d in dims_all if d not in dims_supplied]
675+
for d in dims_supplied:
676+
if d == ...:
677+
yield from other_dims
678+
else:
679+
yield d
680+
else:
681+
if set(dims_supplied) ^ set(dims_all):
682+
raise ValueError(
683+
f"{dims_supplied} must be a permuted list of {dims_all}, unless `...` is included"
684+
)
685+
yield from dims_supplied
686+
687+
663688
def get_temp_dimname(dims: Container[Hashable], new_dim: Hashable) -> Hashable:
664689
""" Get an new dimension name based on new_dim, that is not used in dims.
665690
If the same name exists, we add an underscore(s) in the head.

xarray/core/variable.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
OrderedSet,
2626
decode_numpy_dict_values,
2727
either_dict_or_kwargs,
28+
infix_dims,
2829
ensure_us_time_resolution,
2930
)
3031

@@ -1228,6 +1229,7 @@ def transpose(self, *dims) -> "Variable":
12281229
"""
12291230
if len(dims) == 0:
12301231
dims = self.dims[::-1]
1232+
dims = tuple(infix_dims(dims, self.dims))
12311233
axes = self.get_axis_num(dims)
12321234
if len(dims) < 2 or dims == self.dims:
12331235
# no need to transpose if only one dimension

0 commit comments

Comments
 (0)