Skip to content

Commit 3dafcf9

Browse files
benbovydcherian
andauthored
Add Coordinates.from_xindex method (+ refactor API doc) (#10000)
* add Coordinates.from_xindex method * doc: refactor Coordinates API reference Make it more consistent with ``Dataset``, ``DataArray`` and ``DataTree``. The ``Coordinates`` class is 2nd order compared to the former ones, but it is public API and useful (for creating coordinates from indexes and merging coordinates together) so it deserves its own (expanded) section + summary tables in the API reference doc. * add tests * update what's new * fix doc build? * docstring tweaks * doc (api): add missing Coordinates.sizes property * update what's new (documentation) * improve docstrings on Coordinates creation * doc: move what's new entries after last release --------- Co-authored-by: Deepak Cherian <[email protected]>
1 parent 414b6b2 commit 3dafcf9

File tree

5 files changed

+175
-46
lines changed

5 files changed

+175
-46
lines changed

doc/api-hidden.rst

+21-19
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,12 @@
99
.. autosummary::
1010
:toctree: generated/
1111

12-
Coordinates.from_pandas_multiindex
13-
Coordinates.get
14-
Coordinates.items
15-
Coordinates.keys
16-
Coordinates.values
17-
Coordinates.dims
18-
Coordinates.dtypes
19-
Coordinates.variables
20-
Coordinates.xindexes
21-
Coordinates.indexes
22-
Coordinates.to_dataset
23-
Coordinates.to_index
24-
Coordinates.update
25-
Coordinates.assign
26-
Coordinates.merge
27-
Coordinates.copy
28-
Coordinates.equals
29-
Coordinates.identical
30-
3112
core.coordinates.DatasetCoordinates.get
3213
core.coordinates.DatasetCoordinates.items
3314
core.coordinates.DatasetCoordinates.keys
3415
core.coordinates.DatasetCoordinates.values
3516
core.coordinates.DatasetCoordinates.dims
17+
core.coordinates.DatasetCoordinates.sizes
3618
core.coordinates.DatasetCoordinates.dtypes
3719
core.coordinates.DatasetCoordinates.variables
3820
core.coordinates.DatasetCoordinates.xindexes
@@ -74,6 +56,7 @@
7456
core.coordinates.DataArrayCoordinates.keys
7557
core.coordinates.DataArrayCoordinates.values
7658
core.coordinates.DataArrayCoordinates.dims
59+
core.coordinates.DataArrayCoordinates.sizes
7760
core.coordinates.DataArrayCoordinates.dtypes
7861
core.coordinates.DataArrayCoordinates.variables
7962
core.coordinates.DataArrayCoordinates.xindexes
@@ -104,6 +87,25 @@
10487
core.weighted.DataArrayWeighted.obj
10588
core.weighted.DataArrayWeighted.weights
10689

90+
core.coordinates.DataTreeCoordinates.get
91+
core.coordinates.DataTreeCoordinates.items
92+
core.coordinates.DataTreeCoordinates.keys
93+
core.coordinates.DataTreeCoordinates.values
94+
core.coordinates.DataTreeCoordinates.dims
95+
core.coordinates.DataTreeCoordinates.sizes
96+
core.coordinates.DataTreeCoordinates.dtypes
97+
core.coordinates.DataTreeCoordinates.variables
98+
core.coordinates.DataTreeCoordinates.xindexes
99+
core.coordinates.DataTreeCoordinates.indexes
100+
core.coordinates.DataTreeCoordinates.to_dataset
101+
core.coordinates.DataTreeCoordinates.to_index
102+
core.coordinates.DataTreeCoordinates.update
103+
core.coordinates.DataTreeCoordinates.assign
104+
core.coordinates.DataTreeCoordinates.merge
105+
core.coordinates.DataTreeCoordinates.copy
106+
core.coordinates.DataTreeCoordinates.equals
107+
core.coordinates.DataTreeCoordinates.identical
108+
107109
core.accessor_dt.DatetimeAccessor.ceil
108110
core.accessor_dt.DatetimeAccessor.floor
109111
core.accessor_dt.DatetimeAccessor.round

doc/api.rst

+80-21
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Attributes
6767
Dataset.attrs
6868
Dataset.encoding
6969
Dataset.indexes
70+
Dataset.xindexes
7071
Dataset.chunks
7172
Dataset.chunksizes
7273
Dataset.nbytes
@@ -275,6 +276,7 @@ Attributes
275276
DataArray.attrs
276277
DataArray.encoding
277278
DataArray.indexes
279+
DataArray.xindexes
278280
DataArray.chunksizes
279281

280282
ndarray attributes
@@ -897,6 +899,84 @@ Methods copied from :py:class:`numpy.ndarray` objects, here applying to the data
897899
.. DataTree.sortby
898900
.. DataTree.broadcast_like
899901
902+
Coordinates
903+
===========
904+
905+
Creating coordinates
906+
--------------------
907+
908+
.. autosummary::
909+
:toctree: generated/
910+
911+
Coordinates
912+
Coordinates.from_xindex
913+
Coordinates.from_pandas_multiindex
914+
915+
Attributes
916+
----------
917+
918+
.. autosummary::
919+
:toctree: generated/
920+
921+
Coordinates.dims
922+
Coordinates.sizes
923+
Coordinates.dtypes
924+
Coordinates.variables
925+
Coordinates.indexes
926+
Coordinates.xindexes
927+
928+
Dictionary Interface
929+
--------------------
930+
931+
Coordinates implement the mapping interface with keys given by variable names
932+
and values given by ``DataArray`` objects.
933+
934+
.. autosummary::
935+
:toctree: generated/
936+
937+
Coordinates.__getitem__
938+
Coordinates.__setitem__
939+
Coordinates.__delitem__
940+
Coordinates.update
941+
Coordinates.get
942+
Coordinates.items
943+
Coordinates.keys
944+
Coordinates.values
945+
946+
Coordinates contents
947+
--------------------
948+
949+
.. autosummary::
950+
:toctree: generated/
951+
952+
Coordinates.to_dataset
953+
Coordinates.to_index
954+
Coordinates.assign
955+
Coordinates.merge
956+
Coordinates.copy
957+
958+
Comparisons
959+
-----------
960+
961+
.. autosummary::
962+
:toctree: generated/
963+
964+
Coordinates.equals
965+
Coordinates.identical
966+
967+
Proxies
968+
-------
969+
970+
Coordinates that are accessed from the ``coords`` property of Dataset, DataArray
971+
and DataTree objects, respectively.
972+
973+
.. autosummary::
974+
:toctree: generated/
975+
976+
core.coordinates.DatasetCoordinates
977+
core.coordinates.DataArrayCoordinates
978+
core.coordinates.DataTreeCoordinates
979+
900980
Universal functions
901981
===================
902982

@@ -1107,27 +1187,6 @@ Coder objects
11071187

11081188
coders.CFDatetimeCoder
11091189

1110-
Coordinates objects
1111-
===================
1112-
1113-
Dataset
1114-
-------
1115-
1116-
.. autosummary::
1117-
:toctree: generated/
1118-
1119-
core.coordinates.DatasetCoordinates
1120-
core.coordinates.DatasetCoordinates.dtypes
1121-
1122-
DataArray
1123-
---------
1124-
1125-
.. autosummary::
1126-
:toctree: generated/
1127-
1128-
core.coordinates.DataArrayCoordinates
1129-
core.coordinates.DataArrayCoordinates.dtypes
1130-
11311190
Plotting
11321191
========
11331192

doc/whats-new.rst

+10-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ v2025.02.0 (unreleased)
2121

2222
New Features
2323
~~~~~~~~~~~~
24+
- Added :py:meth:`Coordinates.from_xindex` as convenience for creating a new :py:class:`Coordinates` object
25+
directly from an existing Xarray index object if the latter supports it (:pull:`10000`)
26+
By `Benoit Bovy <https://github.com/benbovy>`_.
2427
- Allow kwargs in :py:meth:`DataTree.map_over_datasets` and :py:func:`map_over_datasets` (:issue:`10009`, :pull:`10012`).
2528
By `Kai Mühlbauer <https://github.com/kmuehlbauer>`_.
2629
- support python 3.13 (no free-threading) (:issue:`9664`, :pull:`9681`)
@@ -52,7 +55,8 @@ Bug fixes
5255

5356
Documentation
5457
~~~~~~~~~~~~~
55-
58+
- Better expose the :py:class:`Coordinates` class in API reference (:pull:`10000`)
59+
By `Benoit Bovy <https://github.com/benbovy>`_.
5660

5761
Internal Changes
5862
~~~~~~~~~~~~~~~~
@@ -131,6 +135,11 @@ New Features
131135
:py:class:`pandas.DatetimeIndex` (:pull:`9965`). By `Spencer Clark
132136
<https://github.com/spencerkclark>`_ and `Kai Mühlbauer
133137
<https://github.com/kmuehlbauer>`_.
138+
- :py:meth:`DatasetGroupBy.first` and :py:meth:`DatasetGroupBy.last` can now use ``flox`` if available. (:issue:`9647`)
139+
By `Deepak Cherian <https://github.com/dcherian>`_.
140+
141+
Breaking changes
142+
~~~~~~~~~~~~~~~~
134143
- Adds shards to the list of valid_encodings in the zarr backend, so that
135144
sharded Zarr V3s can be written (:issue:`9947`, :pull:`9948`).
136145
By `Jacob Prince_Bieker <https://github.com/jacobbieker>`_

xarray/core/coordinates.py

+40-4
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,17 @@ class Coordinates(AbstractCoordinates):
200200
201201
- returned via the :py:attr:`Dataset.coords`, :py:attr:`DataArray.coords`,
202202
and :py:attr:`DataTree.coords` properties,
203-
- built from Pandas or other index objects
204-
(e.g., :py:meth:`Coordinates.from_pandas_multiindex`),
205-
- built directly from coordinate data and Xarray ``Index`` objects (beware that
206-
no consistency check is done on those inputs),
203+
- built from Xarray or Pandas index objects
204+
(e.g., :py:meth:`Coordinates.from_xindex` or
205+
:py:meth:`Coordinates.from_pandas_multiindex`),
206+
- built manually from input coordinate data and Xarray ``Index`` objects via
207+
:py:meth:`Coordinates.__init__` (beware that no consistency check is done
208+
on those inputs).
209+
210+
To create new coordinates from an existing Xarray ``Index`` object, use
211+
:py:meth:`Coordinates.from_xindex` instead of
212+
:py:meth:`Coordinates.__init__`. The latter is useful, e.g., for creating
213+
coordinates with no default index.
207214
208215
Parameters
209216
----------
@@ -352,6 +359,35 @@ def _construct_direct(
352359
)
353360
return obj
354361

362+
@classmethod
363+
def from_xindex(cls, index: Index) -> Self:
364+
"""Create Xarray coordinates from an existing Xarray index.
365+
366+
Parameters
367+
----------
368+
index : Index
369+
Xarray index object. The index must support generating new
370+
coordinate variables from itself.
371+
372+
Returns
373+
-------
374+
coords : Coordinates
375+
A collection of Xarray indexed coordinates created from the index.
376+
377+
"""
378+
variables = index.create_variables()
379+
380+
if not variables:
381+
raise ValueError(
382+
"`Coordinates.from_xindex()` only supports index objects that can generate "
383+
"new coordinate variables from scratch. The given index (shown below) did not "
384+
f"create any coordinate.\n{index!r}"
385+
)
386+
387+
indexes = {name: index for name in variables}
388+
389+
return cls(coords=variables, indexes=indexes)
390+
355391
@classmethod
356392
def from_pandas_multiindex(cls, midx: pd.MultiIndex, dim: Hashable) -> Self:
357393
"""Wrap a pandas multi-index as Xarray coordinates (dimension + levels).

xarray/tests/test_coordinates.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from collections.abc import Mapping
4+
35
import numpy as np
46
import pandas as pd
57
import pytest
@@ -8,7 +10,7 @@
810
from xarray.core.coordinates import Coordinates
911
from xarray.core.dataarray import DataArray
1012
from xarray.core.dataset import Dataset
11-
from xarray.core.indexes import PandasIndex, PandasMultiIndex
13+
from xarray.core.indexes import Index, PandasIndex, PandasMultiIndex
1214
from xarray.core.variable import IndexVariable, Variable
1315
from xarray.tests import assert_identical, source_ndarray
1416

@@ -73,6 +75,27 @@ def test_init_dim_sizes_conflict(self) -> None:
7375
with pytest.raises(ValueError):
7476
Coordinates(coords={"foo": ("x", [1, 2]), "bar": ("x", [1, 2, 3, 4])})
7577

78+
def test_from_xindex(self) -> None:
79+
idx = PandasIndex([1, 2, 3], "x")
80+
coords = Coordinates.from_xindex(idx)
81+
82+
assert isinstance(coords.xindexes["x"], PandasIndex)
83+
assert coords.xindexes["x"].equals(idx)
84+
85+
expected = PandasIndex(idx, "x").create_variables()
86+
assert list(coords.variables) == list(expected)
87+
assert_identical(expected["x"], coords.variables["x"])
88+
89+
def test_from_xindex_error(self) -> None:
90+
class CustomIndexNoCoordsGenerated(Index):
91+
def create_variables(self, variables: Mapping | None = None):
92+
return {}
93+
94+
idx = CustomIndexNoCoordsGenerated()
95+
96+
with pytest.raises(ValueError, match=".*index.*did not create any coordinate"):
97+
Coordinates.from_xindex(idx)
98+
7699
def test_from_pandas_multiindex(self) -> None:
77100
midx = pd.MultiIndex.from_product([["a", "b"], [1, 2]], names=("one", "two"))
78101
coords = Coordinates.from_pandas_multiindex(midx, "x")

0 commit comments

Comments
 (0)