Skip to content

Commit 4f9bb46

Browse files
committed
update docs for new index
1 parent 856ad90 commit 4f9bb46

File tree

3 files changed

+330
-2
lines changed

3 files changed

+330
-2
lines changed

AGENTS.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,43 @@ Custom xarray Index implementations for keeping multiple coordinates in sync acr
2020
```bash
2121
uv run pytest # Run tests
2222
uv run jupyter execute <notebook> # Execute a notebook in place
23-
uv run jupyter-book build docs/ # Build docs
24-
uv run jupyter-book start docs/ # Preview docs locally
23+
```
24+
25+
## Documentation
26+
27+
Documentation uses [MyST](https://mystmd.org/) and is located in `docs/`.
28+
29+
### Structure
30+
31+
- `docs/myst.yml` - Configuration file including the Table of Contents (TOC)
32+
- `docs/index.md` - Main landing page
33+
- `docs/*.ipynb` - Example notebooks (rendered as documentation pages)
34+
35+
### Adding New Documentation
36+
37+
1. **Add notebooks to `docs/`**, not `examples/` (which is gitignored)
38+
2. **Update the TOC** in `docs/myst.yml` under `project.toc`
39+
3. Notebooks are executable documentation - ensure they run without errors
40+
41+
### Building Docs
42+
43+
```bash
44+
myst start docs/ # Preview docs locally with hot reload
45+
myst build docs/ # Build static docs
46+
```
47+
48+
### TOC Format (myst.yml)
49+
50+
```yaml
51+
project:
52+
toc:
53+
- file: index.md
54+
- file: my_example.ipynb
55+
title: My Example Title
56+
- title: Section Name
57+
children:
58+
- file: nested_example.ipynb
59+
title: Nested Example
2560
```
2661
2762
## Architecture Notes

docs/abs_rel_time_example.ipynb

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"id": "50fba8f7-5f8b-4b9f-8666-befa4077efc8",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": "\"\"\"\nAbsolute vs Relative Time Index Demo\n\nThis notebook demonstrates the AbsoluteRelativeIndex, which handles trial-based \ndata where each trial has both absolute time (experiment time) and relative time\n(time within the trial).\n\"\"\"\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom linked_indices import AbsoluteRelativeIndex\nfrom linked_indices.example_data import trial_based_dataset"
10+
},
11+
{
12+
"cell_type": "code",
13+
"execution_count": null,
14+
"id": "02c4977b-6a5e-493b-bf45-ec77998f615f",
15+
"metadata": {},
16+
"outputs": [],
17+
"source": [
18+
"# Generate example trial-based data\n",
19+
"# 5 trials, each 10 seconds long, sampled at 100 Hz\n",
20+
"ds = trial_based_dataset(n_trials=5, trial_length=10.0, sample_rate=100)\n",
21+
"ds"
22+
]
23+
},
24+
{
25+
"cell_type": "markdown",
26+
"id": "bzyx5r4yaz",
27+
"metadata": {},
28+
"source": "## Concept: Absolute vs Relative Time\n\nIn trial-based experiments, data is often organized as:\n- **1D view**: A single continuous stream indexed by absolute time\n- **2D view**: Multiple trials, each with relative time within the trial\n\nThe `AbsoluteRelativeIndex` lets you work with both views seamlessly:\n\n```\n1D View (absolute time):\n|-------- Trial 1 --------|-------- Trial 2 --------|-------- Trial 3 --------|\n0s 10s 20s 30s\n\n2D View (trial × rel_time):\n rel_time: 0s 5s 10s\nTrial 1: |---------|---------| (abs: 0-10s)\nTrial 2: |---------|---------| (abs: 10-20s)\nTrial 3: |---------|---------| (abs: 20-30s)\n```"
29+
},
30+
{
31+
"cell_type": "code",
32+
"execution_count": null,
33+
"id": "ihorl6iuov",
34+
"metadata": {},
35+
"outputs": [],
36+
"source": [
37+
"# Create a conceptual diagram of the data structure\n",
38+
"fig, axes = plt.subplots(2, 1, figsize=(14, 5), gridspec_kw={\"height_ratios\": [1, 2]})\n",
39+
"\n",
40+
"# 1D view - continuous data\n",
41+
"ax1 = axes[0]\n",
42+
"colors = plt.cm.tab10(np.linspace(0, 1, 5))\n",
43+
"for i in range(5):\n",
44+
" ax1.axvspan(i * 10, (i + 1) * 10, alpha=0.3, color=colors[i], label=f\"Trial {i}\")\n",
45+
" ax1.annotate(\n",
46+
" f\"Trial {i}\", ((i + 0.5) * 10, 0.5), ha=\"center\", fontsize=10, fontweight=\"bold\"\n",
47+
" )\n",
48+
"\n",
49+
"ax1.set_xlim(0, 50)\n",
50+
"ax1.set_ylim(0, 1)\n",
51+
"ax1.set_xlabel(\"Absolute Time (s)\", fontsize=12)\n",
52+
"ax1.set_title(\n",
53+
" \"1D View: Continuous Data with Absolute Time\", fontsize=14, fontweight=\"bold\"\n",
54+
")\n",
55+
"ax1.set_yticks([])\n",
56+
"\n",
57+
"# Add arrows for absolute time\n",
58+
"ax1.annotate(\n",
59+
" \"\",\n",
60+
" xy=(0, -0.1),\n",
61+
" xytext=(50, -0.1),\n",
62+
" arrowprops=dict(arrowstyle=\"<->\", color=\"green\", lw=2),\n",
63+
" annotation_clip=False,\n",
64+
")\n",
65+
"ax1.text(\n",
66+
" 25,\n",
67+
" -0.25,\n",
68+
" \"abs_time: 0 → 50s\",\n",
69+
" ha=\"center\",\n",
70+
" fontsize=10,\n",
71+
" color=\"green\",\n",
72+
" transform=ax1.get_xaxis_transform(),\n",
73+
")\n",
74+
"\n",
75+
"# 2D view - trials stacked\n",
76+
"ax2 = axes[1]\n",
77+
"trial_height = 0.8\n",
78+
"for i in range(5):\n",
79+
" y_base = 4 - i # Stack from top to bottom\n",
80+
" ax2.add_patch(\n",
81+
" plt.Rectangle(\n",
82+
" (0, y_base),\n",
83+
" 10,\n",
84+
" trial_height,\n",
85+
" facecolor=colors[i],\n",
86+
" edgecolor=\"black\",\n",
87+
" alpha=0.5,\n",
88+
" )\n",
89+
" )\n",
90+
" ax2.text(\n",
91+
" -0.5,\n",
92+
" y_base + trial_height / 2,\n",
93+
" f\"Trial {i}\",\n",
94+
" ha=\"right\",\n",
95+
" va=\"center\",\n",
96+
" fontsize=10,\n",
97+
" fontweight=\"bold\",\n",
98+
" )\n",
99+
"\n",
100+
" # Add abs_time range annotation\n",
101+
" ax2.text(\n",
102+
" 10.5,\n",
103+
" y_base + trial_height / 2,\n",
104+
" f\"abs: {i * 10}-{(i + 1) * 10}s\",\n",
105+
" ha=\"left\",\n",
106+
" va=\"center\",\n",
107+
" fontsize=9,\n",
108+
" color=\"green\",\n",
109+
" )\n",
110+
"\n",
111+
"# Add rel_time axis\n",
112+
"ax2.set_xlim(-1, 13)\n",
113+
"ax2.set_ylim(-0.5, 5.5)\n",
114+
"ax2.set_xlabel(\"Relative Time (s)\", fontsize=12)\n",
115+
"ax2.set_title(\"2D View: Trials × Relative Time\", fontsize=14, fontweight=\"bold\")\n",
116+
"ax2.set_yticks([])\n",
117+
"\n",
118+
"# Add arrows for rel_time\n",
119+
"ax2.annotate(\n",
120+
" \"\",\n",
121+
" xy=(0, -0.3),\n",
122+
" xytext=(10, -0.3),\n",
123+
" arrowprops=dict(arrowstyle=\"<->\", color=\"blue\", lw=2),\n",
124+
" annotation_clip=False,\n",
125+
")\n",
126+
"ax2.text(\n",
127+
" 5,\n",
128+
" -0.5,\n",
129+
" \"rel_time: 0 → 10s (same for all trials)\",\n",
130+
" ha=\"center\",\n",
131+
" fontsize=10,\n",
132+
" color=\"blue\",\n",
133+
")\n",
134+
"\n",
135+
"plt.tight_layout()\n",
136+
"plt.suptitle(\n",
137+
" \"AbsoluteRelativeIndex: Two Views of Trial-Based Data\",\n",
138+
" y=1.02,\n",
139+
" fontsize=16,\n",
140+
" fontweight=\"bold\",\n",
141+
")"
142+
]
143+
},
144+
{
145+
"cell_type": "code",
146+
"execution_count": null,
147+
"id": "3c9970e0-795e-4112-b10c-21885b59f138",
148+
"metadata": {},
149+
"outputs": [],
150+
"source": [
151+
"# The abs_time coordinate is 2D: maps (trial, rel_time) -> absolute time\n",
152+
"print(f\"abs_time shape: {ds.abs_time.shape}\")\n",
153+
"print(f\"trial_onset values: {ds.trial_onset.values}\")\n",
154+
"ds.abs_time"
155+
]
156+
},
157+
{
158+
"cell_type": "code",
159+
"execution_count": null,
160+
"id": "d77c71b8-4859-471b-87f3-e22c25bd313e",
161+
"metadata": {},
162+
"outputs": [],
163+
"source": [
164+
"# Apply the AbsoluteRelativeIndex\n",
165+
"# First drop the default indexes, then set our custom index\n",
166+
"ds_indexed = ds.drop_indexes([\"trial\", \"rel_time\"]).set_xindex(\n",
167+
" [\"abs_time\", \"trial\", \"rel_time\"],\n",
168+
" AbsoluteRelativeIndex,\n",
169+
")\n",
170+
"ds_indexed"
171+
]
172+
},
173+
{
174+
"cell_type": "code",
175+
"execution_count": null,
176+
"id": "63988032-4393-4470-88ff-0dbac2908356",
177+
"metadata": {},
178+
"outputs": [],
179+
"source": [
180+
"# Select by absolute time - finds the correct (trial, rel_time) point\n",
181+
"# abs_time=25 is in trial_2 (which spans 20-30s), at rel_time=5\n",
182+
"result = ds_indexed.sel(abs_time=25.0, method=\"nearest\")\n",
183+
"print(f\"Selected trial: {result.trial.values}\")\n",
184+
"print(f\"Selected rel_time: {float(result.rel_time):.2f}\")\n",
185+
"print(f\"abs_time at selection: {float(result.abs_time):.2f}\")\n",
186+
"result"
187+
]
188+
},
189+
{
190+
"cell_type": "code",
191+
"execution_count": null,
192+
"id": "03affbed-d82e-47d5-9e11-349d116692a6",
193+
"metadata": {},
194+
"outputs": [],
195+
"source": [
196+
"# Select a range of absolute time that spans multiple trials\n",
197+
"# abs_time 15-25 spans trial_1 (10-20) and trial_2 (20-30)\n",
198+
"result_range = ds_indexed.sel(abs_time=slice(15.0, 25.0))\n",
199+
"print(f\"Trials selected: {list(result_range.trial.values)}\")\n",
200+
"print(f\"Number of rel_time points: {result_range.sizes['rel_time']}\")\n",
201+
"result_range"
202+
]
203+
},
204+
{
205+
"cell_type": "code",
206+
"execution_count": null,
207+
"id": "429225ba-4ab8-4594-8a20-2afa37ec5cae",
208+
"metadata": {},
209+
"outputs": [],
210+
"source": [
211+
"# Select by trial - get all data for a specific trial\n",
212+
"trial_data = ds_indexed.sel(trial=\"trial_2\")\n",
213+
"print(\n",
214+
" f\"Trial 2 abs_time range: {float(trial_data.abs_time.min()):.1f} - {float(trial_data.abs_time.max()):.1f}\"\n",
215+
")\n",
216+
"trial_data"
217+
]
218+
},
219+
{
220+
"cell_type": "code",
221+
"execution_count": null,
222+
"id": "bc87c52c-6786-4db1-adbe-11f7806612c7",
223+
"metadata": {},
224+
"outputs": [],
225+
"source": [
226+
"# Select by relative time - same relative time across all trials\n",
227+
"# This is useful for comparing the same timepoint within each trial\n",
228+
"rel_time_data = ds_indexed.sel(rel_time=5.0, method=\"nearest\")\n",
229+
"print(f\"Selected rel_time: {float(rel_time_data.rel_time):.2f}\")\n",
230+
"print(f\"abs_time values at this rel_time: {rel_time_data.abs_time.values}\")\n",
231+
"rel_time_data"
232+
]
233+
},
234+
{
235+
"cell_type": "code",
236+
"execution_count": null,
237+
"id": "e35e4e38-5c9e-497e-af48-b155f4baee3f",
238+
"metadata": {},
239+
"outputs": [],
240+
"source": [
241+
"# Visualize the data\n",
242+
"fig, axes = plt.subplots(2, 1, figsize=(12, 6))\n",
243+
"\n",
244+
"# Plot as 1D with absolute time\n",
245+
"for i, trial in enumerate(ds_indexed.trial.values):\n",
246+
" trial_slice = ds_indexed.sel(trial=trial)\n",
247+
" # Flatten the abs_time since it's still 2D after selection\n",
248+
" abs_time_flat = trial_slice.abs_time.values.flatten()\n",
249+
" data_flat = trial_slice.data.values.flatten()\n",
250+
" axes[0].plot(abs_time_flat, data_flat, label=trial)\n",
251+
"axes[0].set_xlabel(\"Absolute Time (s)\")\n",
252+
"axes[0].set_ylabel(\"Signal\")\n",
253+
"axes[0].set_title(\"Data vs Absolute Time\")\n",
254+
"axes[0].legend(loc=\"upper right\", ncol=5)\n",
255+
"\n",
256+
"# Plot as 2D with relative time (all trials overlaid)\n",
257+
"for i, trial in enumerate(ds_indexed.trial.values):\n",
258+
" trial_slice = ds_indexed.sel(trial=trial)\n",
259+
" rel_time_flat = trial_slice.rel_time.values.flatten()\n",
260+
" data_flat = trial_slice.data.values.flatten()\n",
261+
" axes[1].plot(rel_time_flat, data_flat, alpha=0.7, label=trial)\n",
262+
"axes[1].set_xlabel(\"Relative Time (s)\")\n",
263+
"axes[1].set_ylabel(\"Signal\")\n",
264+
"axes[1].set_title(\"Data vs Relative Time (trials overlaid)\")\n",
265+
"\n",
266+
"plt.tight_layout()"
267+
]
268+
}
269+
],
270+
"metadata": {
271+
"kernelspec": {
272+
"display_name": "Python 3 (ipykernel)",
273+
"language": "python",
274+
"name": "python3"
275+
},
276+
"language_info": {
277+
"codemirror_mode": {
278+
"name": "ipython",
279+
"version": 3
280+
},
281+
"file_extension": ".py",
282+
"mimetype": "text/x-python",
283+
"name": "python",
284+
"nbconvert_exporter": "python",
285+
"pygments_lexer": "ipython3",
286+
"version": "3.11.12"
287+
}
288+
},
289+
"nbformat": 4,
290+
"nbformat_minor": 5
291+
}

docs/myst.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ project:
1616
title: IntervalIndex Example
1717
- file: onset_duration_example.ipynb
1818
title: Onset/Duration Example
19+
- file: abs_rel_time_example.ipynb
20+
title: Absolute/Relative Time
1921
- title: Gallery
2022
children:
2123
- file: cell_hierarchy_example.ipynb

0 commit comments

Comments
 (0)