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" : " \"\"\"\n Absolute vs Relative Time Index Demo\n\n This notebook demonstrates the AbsoluteRelativeIndex, which handles trial-based \n data where each trial has both absolute time (experiment time) and relative time\n (time within the trial).\n\"\"\"\n import matplotlib.pyplot as plt\n import numpy as np\n\n from linked_indices import AbsoluteRelativeIndex\n from 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\n In 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\n The `AbsoluteRelativeIndex` lets you work with both views seamlessly:\n\n ```\n 1D View (absolute time):\n |-------- Trial 1 --------|-------- Trial 2 --------|-------- Trial 3 --------|\n 0s 10s 20s 30s\n\n 2D View (trial × rel_time):\n rel_time: 0s 5s 10s\n Trial 1: |---------|---------| (abs: 0-10s)\n Trial 2: |---------|---------| (abs: 10-20s)\n Trial 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+ }
0 commit comments