Skip to content

Allow opening datasets with nD dimenson coordinate variables. #7989

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

Merged
merged 6 commits into from
Jul 19, 2023
Merged
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
@@ -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
2 changes: 1 addition & 1 deletion xarray/core/indexes.py
Original file line number Diff line number Diff line change
@@ -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})
10 changes: 2 additions & 8 deletions xarray/core/variable.py
Original file line number Diff line number Diff line change
@@ -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
15 changes: 7 additions & 8 deletions xarray/testing.py
Original file line number Diff line number Diff line change
@@ -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(
8 changes: 6 additions & 2 deletions xarray/tests/test_dataset.py
Original file line number Diff line number Diff line change
@@ -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,
@@ -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"]})
6 changes: 4 additions & 2 deletions xarray/tests/test_variable.py
Original file line number Diff line number Diff line change
@@ -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)])