Skip to content

Commit

Permalink
Allow opening datasets with nD dimenson coordinate variables. (#7989)
Browse files Browse the repository at this point in the history
* Warn instead of raising for nD index variable

Avoid automatic creating of Index variable
when nD variable shares name with one
of its dimensions.

Closes #2233

* fix tests

* Remove warning

* Add invariants check

Co-authored-by: Benoit Bovy <benbovy@gmail.com>

* Add whats-new

---------

Co-authored-by: Benoit Bovy <benbovy@gmail.com>
  • Loading branch information
dcherian and benbovy authored Jul 19, 2023
1 parent 90d5cd4 commit 2bf15f8
Show file tree
Hide file tree
Showing 6 changed files with 24 additions and 21 deletions.
4 changes: 4 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ v2023.07.1 (unreleased)

New Features
~~~~~~~~~~~~
- Allow creating Xarray objects where a multidimensional variable shares its name
with a dimension. Examples include output from finite volume models like FVCOM.
(:issue:`2233`, :pull:`7989`)
By `Deepak Cherian <https://github.com/dcherian>`_ and `Benoit Bovy <https://github.com/benbovy>`_.


Breaking changes
Expand Down
2 changes: 1 addition & 1 deletion xarray/core/indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1645,7 +1645,7 @@ def default_indexes(
coord_names = set(coords)

for name, var in coords.items():
if name in dims:
if name in dims and var.ndim == 1:
index, index_vars = create_default_index_implicit(var, coords)
if set(index_vars) <= coord_names:
indexes.update({k: index for k in index_vars})
Expand Down
10 changes: 2 additions & 8 deletions xarray/core/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,8 @@ def as_variable(obj, name=None) -> Variable | IndexVariable:
f"explicit list of dimensions: {obj!r}"
)

if name is not None and name in obj.dims:
# convert the Variable into an Index
if obj.ndim != 1:
raise MissingDimensionsError(
f"{name!r} has more than 1-dimension and the same name as one of its "
f"dimensions {obj.dims!r}. xarray disallows such variables because they "
"conflict with the coordinates used to label dimensions."
)
if name is not None and name in obj.dims and obj.ndim == 1:
# automatically convert the Variable into an Index
obj = obj.to_index_variable()

return obj
Expand Down
15 changes: 7 additions & 8 deletions xarray/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,13 @@ def _assert_dataset_invariants(ds: Dataset, check_default_indexes: bool):
assert all(
ds._dims[k] == v.sizes[k] for v in ds._variables.values() for k in v.sizes
), (ds._dims, {k: v.sizes for k, v in ds._variables.items()})
assert all(
isinstance(v, IndexVariable)
for (k, v) in ds._variables.items()
if v.dims == (k,)
), {k: type(v) for k, v in ds._variables.items() if v.dims == (k,)}
assert all(v.dims == (k,) for (k, v) in ds._variables.items() if k in ds._dims), {
k: v.dims for k, v in ds._variables.items() if k in ds._dims
}

if check_default_indexes:
assert all(
isinstance(v, IndexVariable)
for (k, v) in ds._variables.items()
if v.dims == (k,)
), {k: type(v) for k, v in ds._variables.items() if v.dims == (k,)}

if ds._indexes is not None:
_assert_indexes_invariants_checks(
Expand Down
8 changes: 6 additions & 2 deletions xarray/tests/test_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from xarray.core.indexes import Index, PandasIndex
from xarray.core.pycompat import array_type, integer_types
from xarray.core.utils import is_scalar
from xarray.testing import _assert_internal_invariants
from xarray.tests import (
DuckArrayWrapper,
InaccessibleArray,
Expand Down Expand Up @@ -467,13 +468,16 @@ def test_constructor(self) -> None:

with pytest.raises(ValueError, match=r"conflicting sizes"):
Dataset({"a": x1, "b": x2})
with pytest.raises(ValueError, match=r"disallows such variables"):
Dataset({"a": x1, "x": z})
with pytest.raises(TypeError, match=r"tuple of form"):
Dataset({"x": (1, 2, 3, 4, 5, 6, 7)})
with pytest.raises(ValueError, match=r"already exists as a scalar"):
Dataset({"x": 0, "y": ("x", [1, 2, 3])})

# nD coordinate variable "x" sharing name with dimension
actual = Dataset({"a": x1, "x": z})
assert "x" not in actual.xindexes
_assert_internal_invariants(actual, check_default_indexes=True)

# verify handling of DataArrays
expected = Dataset({"x": x1, "z": z})
actual = Dataset({"z": expected["z"]})
Expand Down
6 changes: 4 additions & 2 deletions xarray/tests/test_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -1247,8 +1247,10 @@ def test_as_variable(self):
expected = Variable(("x", "y"), data)
with pytest.raises(ValueError, match=r"without explicit dimension names"):
as_variable(data, name="x")
with pytest.raises(ValueError, match=r"has more than 1-dimension"):
as_variable(expected, name="x")

# name of nD variable matches dimension name
actual = as_variable(expected, name="x")
assert_identical(expected, actual)

# test datetime, timedelta conversion
dt = np.array([datetime(1999, 1, 1) + timedelta(days=x) for x in range(10)])
Expand Down

0 comments on commit 2bf15f8

Please sign in to comment.