# Diagnose and fix xarray coordinate alignment (join) surprises

Diagnose xarray alignment surprises from the symptom — shrunken results (default inner join), all-NaN (non-overlapping labels), ValueError on unlabeled dims, silently dropped conflicting coordinates, no alignment for in-place ops — then take explicit control with xr.align joins, reindex_like, combine_first, or the arithmetic_join option.

Exact reference: {"kind":"skill_version","skill_id":"skl_wvxfd_1ZleCxPQNEwJB1KQ","version_id":"skv_3pChGpb5jmN-6cphjBKdNA"}

Applicability: [{"constraint":">=0.10 for arithmetic_join option; xr.align joins and drop_indexes stable since >=0.14","technology":"xarray","version_scheme":"semver"}]

# Diagnose and fix xarray coordinate alignment (join) surprises

Use this when arithmetic on xarray objects returns fewer points than expected,
all-NaN results, a `ValueError` about alignment, or coordinates that vanish
after an operation. xarray aligns *index* coordinates automatically on every
binary op, and the default join is the **intersection** — most surprises trace
back to not knowing which join just ran. Classify the symptom first, then pick
the join.

## 1. Classify the symptom

**Result is smaller than either input.** This is normal: automatic alignment
uses the *intersection* of index coordinates, like pandas:

```python
arr = xr.DataArray(np.arange(3), [("x", range(3))])
arr + arr[:-1]
# 2-element DataArray result — x is now [0, 1], the intersection
```

If you expected the union, that is the entire bug: you wanted `join='outer'`.

**Result is all NaN (or far more NaN than expected).** Something forced an
outer-style expansion over coordinates that do not actually overlap:

- Float coordinates that *look* equal but differ in the last bits
  (`0.1 + 0.2` style grids, or lon `0..359.9` vs `0..360`). The intersection is
  empty, so an outer join is all NaN. Diagnose with
  `np.array_equal(a.x.values, b.x.values)` and
  `np.abs(a.x.values - b.x.values).max()`.
- You already widened the join (e.g. `arithmetic_join='outer'`) and the grids
  genuinely don't overlap — then NaN is the honest answer, not a bug.

**`ValueError: arguments without labels along dimension 'x' cannot be aligned
because they have different dimension size(s) {2} than the size of the aligned
dimension labels: 3`.** One side has an *index* coordinate on `x` and the other
side has the dimension `x` with **no** coordinate labels at all. xarray cannot
align what has no labels, so it demands equal sizes. Fix: give the unlabeled
side a coordinate (`b = b.assign_coords(x=a.x)`), or strip the index from the
labeled side (`a = a.drop_indexes("x")`, which keeps `x` as a non-index
coordinate), then the op broadcasts positionally.

**Coordinates vanished after arithmetic.** Only *index* coordinates (same name
as a dimension, marked `*`) are aligned. Other coordinates are carried through
only if they don't conflict — **conflicting non-index coordinates are silently
dropped**:

```python
arr[0] - arr[1]   # scalar coord 'x' differs (0 vs 1) → dropped from the result
```

If you need the conflicting coordinate, align explicitly and choose the
winner with `join='left'`/`'right'`, or reconcile the values first.

**In-place op (`+=`, `*=`) gave wrong results or raised.** There is *no*
automatic alignment for in-place operations — xarray deliberately avoids it so
in-place ops never need to change dtypes. Align first, then operate in place.

## 2. Take explicit control with `xr.align`

`xr.align` aligns any number of objects and returns them with matching indexes
and sizes, ready for math:

```python
a2, b2 = xr.align(a, b, join="outer")   # union; non-overlapping → NaN
a2, b2 = xr.align(a, b, join="inner")   # intersection (the arithmetic default)
a2, b2 = xr.align(a, b, join="left")    # keep a's labels
a2, b2 = xr.align(a, b, join="right")   # keep b's labels
a2, b2 = xr.align(a, b, join="exact")   # raise ValueError unless indexes equal
a2, b2 = xr.align(a, b, join="override")  # adopt a's indexes; sizes must match
```

Which to reach for:

- **Diagnosing**: `join='exact'` is the fail-fast probe. If it raises, your
  indexes were never equal and every "mystery" result was silent alignment.
- **Same grid, float noise**: `join='override'` skips reindexing entirely and
  adopts the first object's labels — but only when sizes already match. If
  sizes differ, fix the grids; override would mislabel data.
- **Changing the arithmetic default for a block**: 
  ```python
  with xr.set_options(arithmetic_join="outer"):
      result = a + b
  ```
  Prefer explicit `xr.align` for code others will read; use the option for
  interactive exploration.
- **Imposing one grid on another**: `b.reindex_like(a)` (fills missing with
  NaN) or `b.reindex(x=new_labels)`.
- **Filling rather than erroring**: `a.combine_first(b)` keeps a's values and
  fills its holes from b over the union of coordinates.

`xr.apply_ufunc` aligns its inputs too, and accepts `join=` /
`dataset_join=` kwargs for the same control.

## 3. Fix float-precision coordinate mismatch at the source

When two grids should be identical but aren't bitwise, don't paper over it
with `join='override'` in every script — round or rebuild the coordinate once,
at load:

```python
ds = ds.assign_coords(lon=np.round(ds.lon, 6))
# or rebuild a canonical grid and reindex onto it
ds = ds.reindex(lon=canonical_lon, method="nearest", tolerance=1e-6)
```

Then verify with `join='exact'`: it should pass silently from then on.

## 4. Performance: align once, outside loops

Every binary op re-runs alignment. Before loops or performance-critical code,
align explicitly once (or pack the arrays into one `Dataset`, which aligns at
construction) instead of paying the alignment cost per iteration.

## 5. Checklist

1. Symptom → join: shrunken result = inner (default); all-NaN = non-overlapping
   labels after outer expansion; `ValueError` = one side has no index labels;
   vanished coords = conflicting non-index coordinates dropped;
   in-place weirdness = no alignment happens for `+=`.
2. Probe with `xr.align(a, b, join='exact')` to confirm indexes were the issue.
3. Choose the join explicitly: `outer` for unions, `left`/`right` to keep one
   side's grid, `override` only for same-size float-noise grids,
   `reindex_like` to impose a grid, `combine_first` to fill holes.
4. Fix noisy coordinates at load (round/reindex), then assert with
   `join='exact'`.
5. Align once before loops; never rely on alignment inside in-place ops.


## Supporting basis and limitations

Built from the xarray indexing user guide ('Align and reindex': inner/outer/left/right joins, reindex_like, NaN infill) and the computation guide (automatic alignment uses the intersection; the exact 'arguments without labels along dimension ... cannot be aligned' ValueError; set_options(arithmetic_join='outer'); no alignment for in-place ops; conflicting non-index coordinates are dropped; xr.apply_ufunc join/dataset_join kwargs).

## Change and rationale

New skill: diagnose and fix xarray coordinate alignment (join) surprises.

Automatic alignment with a default inner join is the single most common source of silently-wrong xarray results (shrunk outputs, all-NaN, vanishing coordinates), and the fix is always choosing the join explicitly. This skill adds a symptom-to-join decision procedure grounded in the exact ValueError text and join semantics, so agents stop guessing and probe with join='exact' first.
