Skip to content

Migrating to v0.6

Status: v0.6 is released. This guide describes the current v0.6 API.

v0.6 makes the result-object surface consistent across every encoding and decoding domain (the "naming contract"). Most of this is purely additive, but two changes are clean breaks — there is no transition shim, so you must update call sites. This page gives copy-paste BEFORE/AFTER for both, then lists the deprecations (which still work, with a warning, until removal in 0.7).

Breaking change 1 — to_xarray() returns a Dataset, not a DataArray

to_xarray() previously returned an xarray.DataArray indexed by integer neuron. It now returns a labeled xarray.Dataset whose unit_id coordinate holds your real per-unit identity labels (result.unit_ids), so units are selected by label, not position.

# BEFORE — DataArray with integer "neuron" coords
da = result.to_xarray()
firing = da.sel(neuron=0)        # select the first neuron by integer position
values = da.values               # raw rate matrix

# AFTER — Dataset with real unit_id labels
ds = result.to_xarray()
firing = ds.sel(unit_id=result.unit_ids[0])   # select by label
values = ds["firing_rate"].values              # rate matrix lives in a data var

Key points:

  • Population rate results (SpatialRatesResult, DirectionalRatesResult, ViewRatesResult, EgocentricRatesResult) → dims ("unit_id", "bin"). The rate matrix is the firing_rate data var; occupancy is a ("bin",) data var. The bin dimension carries non-index bin_center_x / bin_center_y (/bin_center_z) coordinates (or bin_center_distance / bin_center_angle for the polar egocentric result).
  • Decode results (DecodingResult) → dims ("time", "bin") — a posterior over space per time bin, with no unit_id axis. The posterior lives in the posterior data var.
  • Duplicate unit_ids now raise ValueError — label-based selection requires unique labels.
  • to_xarray() requires the optional xarray extra: uv pip install "neurospatial[xarray]" (or uv add xarray).

Breaking change 2 — batch to_dataframe() is dense; the summary moved to summary_table()

On the batch (plural) encoding resultsSpatialRatesResult, DirectionalRatesResult, ViewRatesResult, EgocentricRatesResultto_dataframe() used to return a per-unit summary (one row per neuron, with peak_x, peak_rate, spatial_info, cell_type, …). That summary has moved to the new summary_table(). to_dataframe() is now dense tidy: one row per (unit, bin), always carrying a unit_id column.

# BEFORE — to_dataframe() was the per-unit summary
df = result.to_dataframe()              # one row per neuron: neuron_id, peak_x, ...
place = df[df["cell_type"] == "place"]

# AFTER — summary_table() is the per-unit summary; to_dataframe() is dense
summary = result.summary_table()        # one row per unit, unit_id-indexed
place = summary[summary["cell_type"] == "place"]

dense = result.to_dataframe()           # one row per (unit, bin), carries unit_id

The old neuron_ids= keyword on the per-unit to_dataframe() is replaced by unit_ids= on summary_table() (defaulting to the result's own unit_ids).

Deprecations (still work; removed in 0.7)

These emit a DeprecationWarning and forward to their replacement with unchanged behavior. Update at your leisure before 0.7.

Old (deprecated) New
EgocentricRatesResult.detect_ovcs(...) EgocentricRatesResult.classify(...)
ViewRatesResult.detect_view_cells(...) ViewRatesResult.classify(...)
DirectionalRatesResult.detect_hd_cells(...) DirectionalRatesResult.classify(...)
SpatialRatesResult.detect_cell_types(...) SpatialRatesResult.label_cell_types(...)
ViewRateResult.peak_view_location() ViewRateResult.peak_location()
ViewRatesResult.peak_view_location() ViewRatesResult.peak_locations()
detect_region_crossings(position_bins, times, region_name, env, ...) detect_region_crossings(position_bins, times, env, *, region_name, ...)

Notes:

  • classify vs label_cell_types are deliberately separate. classify() is a single-type boolean predicate (NDArray[bool]); label_cell_types() is the multi-class string labeler ("place"/"grid"/"border"/"unclassified"). They are not merged because collapsing a bool predicate and a str labeler under one name would silently change a return type and break df[col == "place"] filters.
  • detect_region_crossings argument order moved env to slot 3 to match the behavioral-segmentation convention. The transitional dispatch detects the old order (3rd positional is a str) and warns; pass region_name= as a keyword to be future-proof.

Runnable migration snippet

The new way for both breaks, end to end:

import numpy as np

from neurospatial import Environment
from neurospatial.encoding import compute_spatial_rates

# --- tiny simulated session: 3 units on a small open arena ---
rng = np.random.default_rng(0)
t = np.linspace(0.0, 30.0, 600)
positions = np.column_stack(
    [
        50.0 + 30.0 * np.cos(2 * np.pi * t / 30.0),
        50.0 + 30.0 * np.sin(2 * np.pi * t / 30.0),
    ]
)
times = t
env = Environment.from_samples(positions, bin_size=5.0)

# Three units, each with a different (sparse) spike train.
spike_times = [times[::5], times[::7], times[::9]]
unit_ids = np.array(["u1", "u2", "u3"])

result = compute_spatial_rates(
    env, spike_times, times, positions, unit_ids=unit_ids
)

# --- Break 2a: summary_table() is the per-unit summary (one row per unit) ---
summary = result.summary_table()
assert len(summary) == 3  # one row per unit
assert "u2" in summary.index  # unit_id-indexed

# --- Break 2b: to_dataframe() is now dense (one row per (unit, bin)) ---
dense = result.to_dataframe()
assert "unit_id" in dense.columns
assert len(dense) == 3 * env.n_bins  # one row per (unit, bin)

# --- Break 1: to_xarray() is a labeled Dataset selected by unit_id label ---
# Requires the optional `xarray` extra; demonstrated skip-safe so this snippet
# runs on the default (no-xarray) install too.
try:
    ds = result.to_xarray()
except ImportError:
    print("xarray not installed; skipping to_xarray() demo")
else:
    assert set(ds.dims) >= {"unit_id", "bin"}
    firing = ds.sel(unit_id="u2")["firing_rate"]  # select by label, not position
    assert firing.shape == (env.n_bins,)
    print("to_xarray() Dataset OK:", dict(ds.dims))

print("migration snippet OK")