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

Allow opening datasets with nD dimenson coordinate variables. #7989

Merged
merged 6 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1362,7 +1362,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 @@ -158,14 +158,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