From cb8039071f50053b0b8611a17fdcea3876c7c319 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:13:43 +0200 Subject: [PATCH] docs(v1): cover the scalar-leftover-coord aux conflict (isel drop=True) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Positional indexing leaves a scalar coordinate behind: `x.isel(time=0)` drops `time` as a dimension but keeps it as a scalar coord (the first label). So a cyclic/boundary constraint like `x.isel(time=0) == x.isel(time=-1)` — extremely common in storage/energy models — hits §11's aux-coord conflict under v1 (`time` = first vs last) and raises, where legacy silently dropped it. The aux-coord row already covered this in principle, but listed only `.drop_vars` / `.assign_coords` as fixes and didn't name the scalar-leftover cause. Broaden the migration-guide row and add the natural fix — drop the coord at the indexing site with `.isel(..., drop=True)` / `.sel(..., drop=True)` — and add the same note to convention.md §11. Verified on the branch: `x.isel(time=0) == x.isel(time=-1)` raises "Auxiliary coordinate 'time' has conflicting values ... left=0, right=3" under v1; `drop=True` on both sides builds cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) --- arithmetics-design/convention.md | 6 ++++++ doc/migrating-to-v1.rst | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/arithmetics-design/convention.md b/arithmetics-design/convention.md index 3f4e84760..e757fe96f 100644 --- a/arithmetics-design/convention.md +++ b/arithmetics-design/convention.md @@ -202,6 +202,12 @@ conflict, which is the [#295] bug. The caller resolves it explicitly with `.drop_vars(name)` (remove the coord) or `.assign_coords(name=...)` (relabel one side). +A common source is a *scalar* coordinate left behind by positional indexing: +`x.isel(time=0)` drops `time` as a dimension but keeps it as a scalar coord +(the first label), so a cyclic/boundary constraint like +`x.isel(time=0) == x.isel(time=-1)` conflicts on `time` (first vs last). Drop +it at the source with `.isel(..., drop=True)` / `.sel(..., drop=True)`. + **Stacked MultiIndex dimensions.** A stacked MultiIndex dim (e.g. PyPSA's `(period, timestep)` `snapshot`) stores its *levels* as auxiliary coordinates — `period` and `timestep` are non-dimension coords on diff --git a/doc/migrating-to-v1.rst b/doc/migrating-to-v1.rst index 8dfb3562e..fd82d8e09 100644 --- a/doc/migrating-to-v1.rst +++ b/doc/migrating-to-v1.rst @@ -109,10 +109,14 @@ Every row is a legacy guess that becomes an explicit rule under v1. The - Absence propagates (the slot stays absent) instead of counting as ``0``. - Decide the intent: ``.fillna(0)`` to keep the old "treat as zero", or leave it to propagate and drop the term. - * - **Conflicting auxiliary coordinates** on a shared dim + * - **Conflicting auxiliary coordinates** on a shared dim — including a + leftover *scalar* coord from positional indexing (``x.isel(time=0)`` + keeps ``time`` as a scalar coord, so a cyclic/boundary constraint like + ``x.isel(time=0) == x.isel(time=-1)`` sees ``time`` = first vs last) - Raises instead of silently dropping one. - - ``.drop_vars(name)`` to remove the coord, or ``.assign_coords(name=...)`` - to relabel one side. + - ``.drop_vars(name)`` / ``.assign_coords(name=...)``; for a leftover index + coord, drop it at the source with ``.isel(..., drop=True)`` / + ``.sel(..., drop=True)``. * - A first-class ``pd.MultiIndex`` **dimension** — and a per-*level* input onto it (e.g. per-``period`` bounds onto a ``(period, timestep)`` ``snapshot``)