OSCR

Age-related changes in behavioural and neural variability in a decision-making task.

Code ↔ Paper

16 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 16 matches
  1. [1] § Results ↔ aging_variability/behavior/behavior_01b_plot_main.py, lines 495–542 · score 0.72 · chronometric curve, RT distribution, psychometric curve, Median RT, RT variability, mouse age
  2. [2] § Methods › Quality control › Trials ↔ aging_variability/config.py, lines 10–96 · score 0.70 · 0.08–2 s, probabilityLeft, feedbackType, 0.08 s, movement, events
  3. [3] § Methods › Quality control › Trials ↔ config.py, lines 11–97 · score 0.70 · 0.08–2 s, probabilityLeft, feedbackType, 0.08 s, movement, events
  4. [4] § Results › Large-scale Neuropixels recordings across the mouse lifespan ↔ aging_variability/preprocessing/preprocessing_00_QC_process.py, lines 50–143 · score 0.69 · single unit QC, Error trials, missing events, Brain regions, Atlas, ROIs
  5. [5] § Results › Age effects on neural variability persist after accounting for training duration ↔ aging_variability/neural/neural_07_plot_scatters_slice_org.py, lines 1–17 · score 0.67 · pre stimulus firing, post stimulus firing, Fano factor quenching, contrast modulation, firing rate, metric
  6. [6] § Results › Age effects are robust to video-based movement covariates ↔ aging_variability/utils/data_utils.py, lines 215–248 · score 0.65 · moderate H0, weak H1, moderate H1, qualitative
  7. [7] § Methods › Statistical tests ↔ aging_variability/neural/neural_01_compute_metrics_time_courses.py, lines 709–798 · score 0.64 · probe insertion, Neural yield, Neural metrics, Fano factor, firing rate, window
  8. [8] § Methods › Statistical tests › Neural metrics › Extended models including age and movement ↔ aging_variability/neural/neural_07_plot_scatters_slice_org.py, lines 1–17 · score 0.63 · post stimulus firing, Fano factor quench, contrast modulation, firing rate, pre, metric
  9. [9] § Methods › Statistical tests ↔ aging_variability/neural/neural_07_plot_scatters_slice_org.py, lines 201–297 · score 0.63 · modulation metrics, log transformed, FF quench, slope, median, age
  10. [10] § Methods › Statistical tests ↔ aging_variability/utils/data_utils.py, lines 184–212 · score 0.60 · identity link, Gaussian family, linear, variables, variability
  11. [11] § Results › Increased firing rate in older animals ↔ aging_variability/config.py, lines 10–96 · score 0.59 · causally, firing rates, CP, CA1, DG, SCm
  12. [12] § Results › Increased firing rate in older animals ↔ config.py, lines 11–97 · score 0.59 · causally, firing rates, CP, CA1, DG, SCm
  13. [13] § Methods › Statistical tests › Neural metrics › Extended models including age and movement ↔ aging_variability/neural/neural_06_plot_modulation_timecourses_slice_org.py, lines 133–200 · score 0.57 · neural metrics, 160 ms, stimulus onset, 260 ms, median, windows
  14. [14] § Results › Older mice show more variable behaviour on a standardized decision-making task ↔ aging_variability/behavior/behavior_04_plot_supp_choice_bias.py, lines 1–49 · score 0.55 · choice bias, history dependent, blocks, age related, fit, mice
  15. [15] § Results › Increased firing rate in older animals ↔ aging_variability/neural/neural_06_plot_modulation_timecourses_slice_org.py, lines 43–92 · score 0.54 · baseline corrected, 160 ms, stimulus onset, 260 ms, omnibus, window
  16. [16] § Results › Reduced stimulus-induced variability quenching in older animals ↔ aging_variability/neural/neural_06_plot_modulation_timecourses_slice_org.py, lines 43–92 · score 0.53 · baseline corrected, stimulus onset, 160 ms, 260 ms, windows, modulation

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 457 lines · 18 KB · MIT · 3 matches

  1. """
  2. Figure 3b,d,g
  3. Figure 3 S1b. Regional specificity of pre-stimulus firing rates.
  4. Figure 3 S2. Regional specificity of post-stimulus firing rates.
  5. Figure 3 S4b. Regional specificity of age-related differences in contrast modulation of firing rates.
  6. Figure 4b,d,f,i
  7. Figure 4 S2b. Regional specificity of pre-stimulus mean-subtracted Fano Factors.
  8. Figure 4 S2c. Regional specificity of post-stimulus mean-subtracted Fano Factors.
  9. Figure 4 S3. Regional specificity of mean-subtracted Fano Factor quenching.
  10. Figure 4 S5b Regional specificity in age-related differences in contrast modulation of the Fano Factor.
  11. Outputs:
  12. - Omnibus scatter figs per metric
  13. - Region slice-org figs per metric
  14. """
  15. # %%
  16. # =====================
  17. # Imports (cleaned)
  18. # =====================
  19. import os
  20. import numpy as np
  21. import pandas as pd
  22. import matplotlib.pyplot as plt
  23. import seaborn as sns
  24. from ibl_style.utils import MM_TO_INCH
  25. import figrid as fg
  26. import aging_variability.config as C
  27. from aging_variability.utils.io import read_table, save_figure, get_suffix
  28. from aging_variability.utils.plot_utils import figure_style, create_slice_org_axes, format_bf_annotation
  29. from aging_variability.utils.data_utils import add_age_group
  30. from statsmodels.formula.api import glm
  31. from statsmodels.genmod.families import Gaussian
  32. from aging_variability.utils.io import read_table, save_figure
  33. import logging
  34. log = logging.getLogger(__name__)
  35. # =====================
  36. # Helpers: GLM & age fields
  37. # =====================
  38. def def_glm_formula_plot(metric, mean_subtraction=False, log_transform=False):
  39. """
  40. Return a Patsy formula (no C() to avoid name collision with config alias).
  41. - pooled (across regions): include 'cluster_region' on RHS
  42. - single-region panels: we later strip 'cluster_region'
  43. """
  44. yvar = f'log_{metric}' if log_transform else metric
  45. if metric in ['fr_delta_modulation', 'ff_quench_modulation']:
  46. return f"{yvar} ~ age_years + cluster_region + n_trials"
  47. elif metric == 'ff_quench':
  48. if mean_subtraction:
  49. return f"{yvar} ~ age_years + cluster_region + n_trials"
  50. else:
  51. return f"{yvar} ~ age_years + cluster_region + abs_contrast + n_trials"
  52. else:
  53. if mean_subtraction:
  54. return f"{yvar} ~ age_years + cluster_region + n_trials"
  55. else:
  56. return f"{yvar} ~ age_years + cluster_region + abs_contrast + n_trials"
  57. def strip_region_factor(formula):
  58. """Remove 'cluster_region' term safely from a simple '+ ...' RHS formula."""
  59. out = formula.replace('+ cluster_region', '')
  60. out = out.replace('cluster_region +', '')
  61. out = out.replace('cluster_region', '')
  62. return out
  63. def ensure_age_years(df):
  64. """Ensure an 'age_years' column exists from common age columns."""
  65. if 'age_years' in df.columns:
  66. return df
  67. out = df.copy()
  68. if 'mouse_age' in out.columns:
  69. out['age_years'] = out['mouse_age'] / 365.0
  70. elif 'mouse_age_months' in out.columns:
  71. out['age_years'] = out['mouse_age_months'] / 12.0
  72. elif 'age_at_recording' in out.columns:
  73. out['age_years'] = out['age_at_recording'] / 365.0
  74. else:
  75. raise ValueError("Need one of ['mouse_age','mouse_age_months','age_at_recording'] to compute age_years.")
  76. return out
  77. def ensure_age_months(df):
  78. """Ensure a 'mouse_age_months' column exists (used on x-axis)."""
  79. if 'mouse_age_months' in df.columns:
  80. return df
  81. out = df.copy()
  82. if 'mouse_age' in out.columns:
  83. out['mouse_age_months'] = out['mouse_age'] / 30.0
  84. elif 'age_years' in out.columns:
  85. out['mouse_age_months'] = out['age_years'] * 12.0
  86. elif 'age_at_recording' in out.columns:
  87. out['mouse_age_months'] = out['age_at_recording'] / 30.0
  88. else:
  89. raise ValueError("Need one of ['mouse_age','age_years','age_at_recording'] to compute mouse_age_months.")
  90. return out
  91. def add_log_y_if_needed(df, metric):
  92. """Add log_<metric> if not present; small epsilon to avoid log(0)."""
  93. yvar = f'log_{metric}'
  94. if yvar not in df.columns:
  95. out = df.copy()
  96. out[yvar] = np.log(out[metric] + 1e-6)
  97. return out
  98. return df
  99. def pooled_marginal_line(model, raw_df, xgrid_years, method='median', region_weights=None):
  100. """
  101. Predict a pooled line by marginalizing cluster_region with weights.
  102. Default weights are proportional to sample counts per region.
  103. """
  104. agg = np.nanmedian if method == 'median' else np.nanmean
  105. regions, counts = np.unique(raw_df['cluster_region'].dropna(), return_counts=True)
  106. if region_weights is None:
  107. w = counts / counts.sum()
  108. region_weights = dict(zip(regions, w))
  109. else:
  110. # normalize supplied weights on existing regions
  111. s = sum(region_weights.get(r, 0) for r in regions)
  112. region_weights = {r: region_weights.get(r, 0) / s for r in regions}
  113. base = {'age_years': xgrid_years}
  114. if 'n_trials' in raw_df.columns:
  115. base['n_trials'] = agg(raw_df['n_trials'])
  116. if 'abs_contrast' in raw_df.columns:
  117. base['abs_contrast'] = agg(raw_df['abs_contrast'])
  118. yhats = []
  119. for r in regions:
  120. new = pd.DataFrame(base)
  121. new['cluster_region'] = r
  122. yhats.append(region_weights[r] * model.predict(new).values)
  123. return np.sum(np.stack(yhats, axis=0), axis=0)
  124. def glm_fit_predict_from_raw(raw_df, metric, mean_subtraction=False, log_transform=False,
  125. method='median', pooled=True):
  126. """
  127. Fit GLM and return (xgrid_years, yhat). Behavior:
  128. - pooled=True → include 'cluster_region' and marginalize across it
  129. - pooled=False → strip 'cluster_region' (single-region panel)
  130. """
  131. fam = Gaussian()
  132. formula = def_glm_formula_plot(metric, mean_subtraction, log_transform)
  133. df_fit = ensure_age_years(raw_df)
  134. if log_transform:
  135. df_fit = add_log_y_if_needed(df_fit, metric)
  136. if 'cluster_region' in df_fit.columns:
  137. df_fit = df_fit.copy()
  138. df_fit['cluster_region'] = df_fit['cluster_region'].astype('category')
  139. if pooled:
  140. formula_use = formula
  141. else:
  142. formula_use = strip_region_factor(formula)
  143. # eval_env=0 avoids collisions with user-level names
  144. model = glm(formula=formula_use, data=df_fit, family=fam, eval_env=0).fit()
  145. xgrid = np.linspace(df_fit['age_years'].min(), df_fit['age_years'].max(), 200)
  146. if pooled:
  147. yhat = pooled_marginal_line(model, raw_df=df_fit, xgrid_years=xgrid, method=method, region_weights=None)
  148. else:
  149. # single region: just predict on median covariates of that region-data (no region term present)
  150. base = {'age_years': xgrid}
  151. if 'n_trials' in df_fit.columns:
  152. base['n_trials'] = np.nanmedian(df_fit['n_trials']) if method == 'median' else np.nanmean(df_fit['n_trials'])
  153. if 'abs_contrast' in df_fit.columns and 'abs_contrast' in formula_use:
  154. base['abs_contrast'] = np.nanmedian(df_fit['abs_contrast']) if method == 'median' else np.nanmean(df_fit['abs_contrast'])
  155. new = pd.DataFrame(base)
  156. yhat = model.predict(new).values
  157. return xgrid, yhat
  158. def get_vmin_vmax(metric):
  159. """Per-metric y-limits (kept identical to your previous settings)."""
  160. ranges = {
  161. 'log_pre_fr': (0.5, 4.5), 'log_post_fr': (0.5, 4),
  162. 'fr_delta_modulation': (-10, 20),
  163. 'log_pre_ff': (-0.75, 0.75), 'log_post_ff': (-0.75, 0.75),
  164. 'ff_quench': (-0.6, 0.4), 'ff_quench_modulation': (-1.5, 1.2)
  165. }
  166. return ranges.get(metric, (-1, 1))
  167. # =====================
  168. # Plotting: pooled omnibus scatter
  169. # =====================
  170. def plot_scatter_pooled(df, permut_df, BF_df, y_col='pre_fr', estimator='mean',
  171. granularity='probe_level', ylim=(None, None),
  172. save=True, mean_subtraction=False):
  173. """
  174. Pooled scatter (across regions) with a marginal GLM fit line when BF supports H1.
  175. Text annotation uses unified format_bf_annotation.
  176. """
  177. figure_style()
  178. raw_df = df.copy()
  179. # Deduplicate for modulation metrics when not mean-subtracted
  180. if not mean_subtraction and y_col in ['fr_delta_modulation', 'ff_quench_modulation']:
  181. raw_df = raw_df.drop(columns=['signed_contrast', 'abs_contrast'], errors='ignore')
  182. raw_df = raw_df.drop_duplicates(subset=['uuids', 'session_pid', 'mouse_age_months', 'age_group', y_col])
  183. # Ensure axes age columns
  184. raw_df = ensure_age_years(ensure_age_months(raw_df))
  185. # stats
  186. slope_age = permut_df['observed_val'].values[0]
  187. p_perm = permut_df['p_perm'].values[0]
  188. BF_conclusion = BF_df.loc[BF_df['metric'] == y_col, 'BF10_age_category'].values[0]
  189. BF10 = BF_df.loc[BF_df['metric'] == y_col, 'BF10_age'].values[0]
  190. fig, ax = plt.subplots(1, 1, figsize=(2.36, 2.36))
  191. # scatter (size encodes #neurons per session)
  192. if y_col in ['pre_fr', 'post_fr', 'pre_ff', 'post_ff']:
  193. log_y = True
  194. raw_df[f'log_{y_col}'] = np.log(raw_df[y_col] + 1e-6)
  195. agg_df = (raw_df
  196. .groupby(['session_pid', 'mouse_age_months', 'age_group'])
  197. .agg(number_neurons=('uuids', 'nunique'),
  198. **{f'log_{y_col}': (f'log_{y_col}', 'mean')})
  199. ).reset_index()
  200. sns.scatterplot(x='mouse_age_months', y=f'log_{y_col}', data=agg_df,
  201. hue='age_group', marker='.', legend=False, s=agg_df['number_neurons'],
  202. palette=C.PALETTE, ax=ax)
  203. vmin, vmax = get_vmin_vmax(f'log_{y_col}')
  204. else:
  205. log_y = False
  206. agg_df = (raw_df
  207. .groupby(['session_pid', 'mouse_age_months', 'age_group'])
  208. .agg(number_neurons=('uuids', 'nunique'),
  209. **{y_col: (y_col, estimator)})
  210. ).reset_index()
  211. agg_df = agg_df.dropna(subset=['mouse_age_months', y_col])
  212. sns.scatterplot(x='mouse_age_months', y=y_col, data=agg_df,
  213. hue='age_group', marker='.', legend=False, s=agg_df['number_neurons'],
  214. palette=C.PALETTE, ax=ax)
  215. vmin, vmax = get_vmin_vmax(y_col)
  216. # fit line only if BF supports H1
  217. if BF_conclusion in ('strong H1', 'moderate H1'):
  218. xgrid_years, yhat = glm_fit_predict_from_raw(
  219. raw_df=raw_df,
  220. metric=y_col,
  221. mean_subtraction=mean_subtraction,
  222. log_transform=log_y,
  223. method='median',
  224. pooled=True
  225. )
  226. ax.plot(xgrid_years * 12.0, yhat, color='gray')
  227. # y axis config
  228. ax.set_ylim(vmin, vmax)
  229. if log_y:
  230. if 'fr' in y_col:
  231. tick_vals = [1, 2, 4, 8, 16, 32, 64]
  232. elif 'ff' in y_col:
  233. tick_vals = [0.5, 1, 2, 4]
  234. tick_pos = np.log(tick_vals)
  235. ax.set_yticks(tick_pos)
  236. ax.set_yticklabels([str(v) for v in tick_vals])
  237. ax.set_ylim(np.log(min(tick_vals)), np.log(max(tick_vals)))
  238. ax.set_ylabel(y_col)
  239. else:
  240. ax.set_ylabel(y_col)
  241. # unified stats annotation
  242. txt = format_bf_annotation(slope_age, p_perm, BF10, BF_conclusion,
  243. beta_label="age", big_bf=100)
  244. ax.text(0.05, 1.0, txt, transform=ax.transAxes, fontsize=5, va='top',
  245. bbox=dict(facecolor='white', alpha=0.5))
  246. ax.set_xlabel('Age (months)')
  247. ax.set_title(f'Omnibus test: {y_col}')
  248. ax.set_xticks([5, 10, 15, 20])
  249. sns.despine(offset=2, trim=False, ax=ax)
  250. plt.tight_layout()
  251. if save:
  252. fname = C.FIGPATH / f'Omnibus_{y_col}_{get_suffix(mean_subtraction)}_scatter.pdf'
  253. save_figure(fig, fname, add_timestamp=True)
  254. # =====================
  255. # Plotting: per-region slice-org panels
  256. # =====================
  257. def plot_scatter_by_region(df, permut_df, BF_df, y_col='pre_fr', estimator='mean',
  258. granularity='probe_level', save=True, mean_subtraction=False):
  259. """
  260. Slice-org figure with one small panel per region.
  261. Uses unified format_bf_annotation and single-region GLM line.
  262. """
  263. fig, axs = create_slice_org_axes(fg, MM_TO_INCH)
  264. figure_style()
  265. raw_df = ensure_age_years(ensure_age_months(df.copy()))
  266. for region in C.ROIS:
  267. ax = axs[region]
  268. sub_df = raw_df[raw_df['cluster_region'] == region].copy()
  269. if sub_df.empty:
  270. continue
  271. # deduplicate where necessary
  272. if not mean_subtraction and y_col in ['fr_delta_modulation', 'ff_quench_modulation']:
  273. sub_df = sub_df.drop(columns=['signed_contrast', 'abs_contrast'], errors='ignore')
  274. sub_df = sub_df.drop_duplicates(subset=['uuids', 'session_pid', 'mouse_age_months', 'age_group', y_col])
  275. # stats for this region
  276. sub_perm = permut_df[permut_df['cluster_region'] == region]
  277. sub_bf = BF_df[BF_df['cluster_region'] == region]
  278. if sub_perm.empty or sub_bf.empty:
  279. continue
  280. slope_age = sub_perm['observed_val'].values[0]
  281. p_perm = sub_perm['p_perm'].values[0]
  282. BF_conclusion = sub_bf['BF10_age_category'].values[0]
  283. BF10 = sub_bf['BF10_age'].values[0]
  284. # scatter (size encodes #neurons per session)
  285. if y_col in ['pre_fr', 'post_fr', 'pre_ff', 'post_ff']:
  286. log_y = True
  287. sub_df[f'log_{y_col}'] = np.log(sub_df[y_col] + 1e-6)
  288. agg_df = (sub_df
  289. .groupby(['session_pid', 'mouse_age_months', 'age_group'])
  290. .agg(number_neurons=('uuids', 'nunique'),
  291. **{f'log_{y_col}': (f'log_{y_col}', 'mean')})
  292. ).reset_index()
  293. agg_df = agg_df.dropna(subset=['mouse_age_months', f'log_{y_col}'])
  294. sns.scatterplot(x='mouse_age_months', y=f'log_{y_col}', data=agg_df,
  295. hue='age_group', marker='.', legend=False, s=agg_df['number_neurons'],
  296. palette=C.PALETTE, ax=ax)
  297. vmin, vmax = get_vmin_vmax(f'log_{y_col}')
  298. else:
  299. log_y = False
  300. agg_df = (sub_df
  301. .groupby(['session_pid', 'mouse_age_months', 'age_group'])
  302. .agg(number_neurons=('uuids', 'nunique'),
  303. **{y_col: (y_col, estimator)})
  304. ).reset_index()
  305. agg_df = agg_df.dropna(subset=['mouse_age_months', y_col])
  306. sns.scatterplot(x='mouse_age_months', y=y_col, data=agg_df,
  307. hue='age_group', marker='.', legend=False, s=agg_df['number_neurons'],
  308. palette=C.PALETTE, ax=ax)
  309. vmin, vmax = get_vmin_vmax(y_col)
  310. # fit line only if BF supports H1
  311. if BF_conclusion in ('strong H1', 'moderate H1'):
  312. xgrid_years, yhat = glm_fit_predict_from_raw(
  313. raw_df=sub_df,
  314. metric=y_col,
  315. mean_subtraction=mean_subtraction,
  316. log_transform=log_y,
  317. method='median',
  318. pooled=False # single-region panels
  319. )
  320. ax.plot(xgrid_years * 12.0, yhat, color='gray', lw=0.8)
  321. # axes cosmetics
  322. ax.set_ylim(vmin, vmax)
  323. if log_y:
  324. if 'fr' in y_col:
  325. tick_vals = [1, 3, 9, 27]
  326. elif 'ff' in y_col:
  327. tick_vals = [0.5, 1, 2, 4]
  328. tick_pos = np.log(tick_vals)
  329. ax.set_yticks(tick_pos)
  330. ax.set_yticklabels([str(v) for v in tick_vals])
  331. ax.set_ylim(np.log(min(tick_vals)), np.log(max(tick_vals)))
  332. ax.set_ylabel(y_col)
  333. else:
  334. ax.set_ylabel(y_col)
  335. # unified annotation
  336. txt = format_bf_annotation(slope_age, p_perm, BF10, BF_conclusion,
  337. beta_label="age", big_bf=100)
  338. ax.text(0.05, 1.25, txt, transform=ax.transAxes, fontsize=4, va='top', linespacing=0.8)
  339. sns.despine(offset=2, trim=False, ax=ax)
  340. if region in ['ACB', 'OLF', 'MBm', 'PO']:
  341. ax.set_xticks([5, 10, 15, 20])
  342. else:
  343. ax.set_xticks([])
  344. ax.set_xlabel(" ")
  345. ax.set_ylabel(" ")
  346. fig.suptitle(f'{granularity} level: {y_col}', font="Arial", fontsize=8)
  347. fig.supxlabel('Age (months)', font="Arial", fontsize=8).set_y(0.35)
  348. if save:
  349. fname = C.FIGPATH / f"supp_slice_org_{y_col}_{granularity}_{get_suffix(mean_subtraction)}_{estimator}_age_relationship_{C.ALIGN_EVENT}_2025.pdf"
  350. save_figure(fig, fname, add_timestamp=True)
  351. # =====================
  352. # Main
  353. # =====================
  354. def main(mean_subtraction=False):
  355. """
  356. Load neural metrics → add age_group (via utils) + ensure age fields →
  357. read cached permutation/BF tables → draw pooled + per-region panels.
  358. """
  359. if mean_subtraction:
  360. metrics_path = C.DATAPATH / "neural_metrics_summary_meansub.parquet"
  361. selected_metrics = C.METRICS_WITH_MEANSUB
  362. else:
  363. metrics_path = C.DATAPATH / "neural_metrics_summary_conditions.parquet"
  364. selected_metrics = C.METRICS_WITHOUT_MEANSUB
  365. print("Loading extracted neural metrics summary...")
  366. neural_metrics = read_table(metrics_path)
  367. neural_metrics = add_age_group(neural_metrics)
  368. # ensure x/y age fields exist even if upstream files omit them
  369. neural_metrics = ensure_age_months(ensure_age_years(neural_metrics))
  370. for metric, est in selected_metrics:
  371. df_permut_path_pooled = C.RESULTSPATH / f"Omnibus_{metric}_{C.N_PERMUT_NEURAL_OMNIBUS}permutation_{C.ALIGN_EVENT}_{C.TRIAL_TYPE}_{get_suffix(mean_subtraction)}.csv"
  372. df_permut_path_region = C.RESULTSPATH / f"Regional_{metric}_{C.N_PERMUT_NEURAL_OMNIBUS}permutation_{C.ALIGN_EVENT}_{C.TRIAL_TYPE}_{get_suffix(mean_subtraction)}.csv"
  373. df_BF_path_pooled = C.RESULTSPATH / f"omnibus_{get_suffix(mean_subtraction)}BFs_{C.ALIGN_EVENT}_{C.TRIAL_TYPE}.csv"
  374. df_BF_path_region = C.RESULTSPATH / f"regional_{get_suffix(mean_subtraction)}BFs_{C.ALIGN_EVENT}_{metric}_{C.TRIAL_TYPE}.csv"
  375. df_permut_pooled = read_table(df_permut_path_pooled)
  376. df_permut_region = read_table(df_permut_path_region)
  377. df_BF_pooled = read_table(df_BF_path_pooled)
  378. df_BF_region = read_table(df_BF_path_region)
  379. print(f"Plotting {metric}...")
  380. plot_scatter_pooled(neural_metrics, df_permut_pooled, df_BF_pooled,
  381. y_col=metric, estimator=est, granularity='probe_level',
  382. save=True, mean_subtraction=mean_subtraction)
  383. plot_scatter_by_region(neural_metrics, df_permut_region, df_BF_region,
  384. y_col=metric, estimator=est, granularity='probe_level',
  385. save=True, mean_subtraction=mean_subtraction)
  386. if __name__ == "__main__":
  387. from aging_variability.utils.io import setup_logging
  388. setup_logging()
  389. main(mean_subtraction=True)

neural_07_plot_scatters_slice_org.py at commit 7202a80, under MIT · at the source

Overview

Authors: Fenying Zang1, Anup Khanal2,3, Sonja Förster1, International Brain Laboratory4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, Anne K Churchland2,3, Anne E Urai1,3
24 affiliations
  1. Cognitive Psychology Unit, Institute of Psychology, Leiden University, Leiden, The Netherlands
  2. Department of Neurobiology, David Geffen School of Medicine, University of California, Los Angeles, Los Angeles, CA USA
  3. Cold Spring Harbor Laboratory, Cold Spring Harbor, NY USA
  4. Zuckerman Institute, Columbia University, New York, NY USA
  5. Department of Basic Neuroscience, University of Geneva, Geneva, Switzerland
  6. Gatsby Computational Neuroscience Unit, University College London, London, UK
  7. Center for Neural Science, New York University, New York, NY USA
  8. Champalimaud Center for the Unknown, Lisboa, Portugal
  9. Princeton Neuroscience Institute, Princeton University, Princeton, NJ USA
  10. Institute of Neurology, University College London, London, UK
  11. Department of Biological Structure, University of Washington, Seattle, WA USA
  12. Department of Applied Physics, Stanford University, Stanford, CA USA
  13. William James Center for Research (WJCR), ISPA - Instituto Universitário, Lisbon, Portugal
  14. Max Planck Institute for Biological Cybernetics, Tübingen, Germany
  15. Sainsbury-Wellcome Centre, University College London, London, UK
  16. Institute of Ophthalmology, University College London, London, UK
  17. Department of Molecular and Cell Biology, University of California, Berkeley, CA USA
  18. Département D’études Cognitives, École Normale Supérieure, Paris, France
  19. Department of Brain and Cognitive Sciences, Massachusetts Institute of Technology, Cambridge, MA USA
  20. Wolfson Institute of Biomedical Research, University College London, London, UK
  21. Center for Computational Neuroscience, University of Washington, Seattle, WA USA
  22. Department of Physiology, University of Yamanashi, Kofu, Yamanashi, Japan
  23. Department of Neuroscience, University of Minnesota, Minneapolis, MN USA
  24. The Allen Institute for Neural Dynamics, Seattle, WA USA
Institutions: Leiden University (Netherlands); University of California, Los Angeles (United States); Cold Spring Harbor Laboratory (United States)
Journal: Nature communications, volume 17, issue 1, article 8156
Dates: received 15 September 2025; accepted 28 May 2026; published online 30 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-74227-1 · PMID 42380131 · PMCID PMC13458552 · OpenAlex W7166668010
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: behavior only (modality), mouse (organism), cognitive (subfield)
Methods: Statistics, fMRI & imaging, Single-unit activity, calcium imaging, Connectivity, Physiology & signal measures
Keywords: Cognitive ageing, Decision
MeSH: Aging*, Behavior, Animal*, Brain*, Decision Making*, Neurons*, Animals, Male, Mice, Mice, Inbred C57BL, Reaction Time (* major topic)
Topic: Neural dynamics and brain function (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Wellcome Trust (216324); Dutch Research Council (NWO) (VI.Veni.212.184)
Citations: cited by 1 paper (Europe PMC); 103 references in the paper

Abstract

Age-related cognitive decline in learning and decision-making may arise from increased variability of neural responses. Here, we investigated how ageing affects behavioural and neural variability by recording >18,000 neurons across 16 brain regions (including cortex, hippocampus, thalamus, midbrain, and basal ganglia) in younger and older mice performing a visual decision-making task. Older mice showed more variable response times, reproducing a common finding in human ageing studies. Ageing globally increased firing rates and post-stimulus neural variability (quantified using the Fano factor), and decreased variability quenching–the reduction in neural variability upon stimulus presentation. Older animals showed higher overall firing rates across areas of visual and motor cortex, striatum, midbrain, and hippocampus, but lower firing rates in thalamic areas. Age-related attenuation in stimulus-induced variability quenching was most prominent in visual and motor cortex, striatum, and thalamic area. These findings show how large-scale neural recordings can help uncover regional specificity of ageing effects in single neurons, ultimately improving our understanding of the neural basis of age-related cognitive decline.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 16 matches between paragraphs and lines of code.

Fenying-Zang/Ageing_behavioral_and_neural_variability

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 7202a808febdf696cf270ef42193042f34d6efb4, 27 August 2026
Languages: Python (33), Jupyter (1)
Size: 496 files, 34 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (pyproject.toml, requirements.txt), 1 notebook
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: pandas (26 files), NumPy (19 files), Matplotlib (11 files), seaborn (11 files), statsmodels (8 files), Pingouin (2 files), SciPy (2 files), BayesFactor (1 file), rpy2 (1 file), scikit-learn (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
36 files

Code availability

Data analyses were performed using custom Python scripts in a reproducible Python environment. All code required to reproduce the figures and analyses is available at https://github.com/Fenying-Zang/Ageing_behavioral_and_neural_variability, and has been archived on Zenodo101.

Reproduced under the paper's license (CC BY), from the paper cited above.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 34 scripts, each with its path and the digest of its content;
  • 16 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data Availability Statement

The data analysed in this study are publicly available through the International Brain Laboratory website (https://www.internationalbrainlab.com/data), under the tag “2025_Q3_Zang_et_al_Aging”. Instructions for accessing these data are provided in the GitHub repository associated with this study (https://github.com/Fenying-Zang/Ageing_behavioral_and_neural_variability). The analysis results underlying the figures are also available in the same repository.

Data analyses were performed using custom Python scripts in a reproducible Python environment. All code required to reproduce the figures and analyses is available at https://github.com/Fenying-Zang/Ageing_behavioral_and_neural_variability, and has been archived on Zenodo101.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 2 keywords, 10 MeSH terms, 2 funders, 96 references.

Cite

This paper

Zang, F., Khanal, A., Förster, S., International Brain Laboratory, Churchland, A. K., & Urai, A. E. (2026). Age-related changes in behavioural and neural variability in a decision-making task. Nature communications, 17(1), 8156. https://doi.org/10.1038/s41467-026-74227-1

BibTeX

@article{zang2026age,
author = {Zang, Fenying and Khanal, Anup and Förster, Sonja and {International Brain Laboratory} and Churchland, Anne K and Urai, Anne E},
title = {{Age-related changes in behavioural and neural variability in a decision-making task}},
journal = {Nature communications},
year = {2026},
month = jun,
volume = {17},
number = {1},
pages = {8156},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-74227-1},
url = {https://doi.org/10.1038/s41467-026-74227-1},
pmid = {42380131},
pmcid = {PMC13458552}
}

RIS

TY - JOUR
AU - Zang, Fenying
AU - Khanal, Anup
AU - Förster, Sonja
AU - International Brain Laboratory
AU - Churchland, Anne K
AU - Urai, Anne E
TI - Age-related changes in behavioural and neural variability in a decision-making task
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/06/30
VL - 17
IS - 1
SP - 8156
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-74227-1
UR - https://doi.org/10.1038/s41467-026-74227-1
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-74227-1",
"type": "article-journal",
"title": "Age-related changes in behavioural and neural variability in a decision-making task",
"container-title": "Nature communications",
"author": [
{
"family": "Zang",
"given": "Fenying"
},
{
"family": "Khanal",
"given": "Anup"
},
{
"family": "Förster",
"given": "Sonja"
},
{
"literal": "International Brain Laboratory"
},
{
"family": "Churchland",
"given": "Anne K"
},
{
"family": "Urai",
"given": "Anne E"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "8156",
"DOI": "10.1038/s41467-026-74227-1",
"PMID": "42380131",
"PMCID": "PMC13458552",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-74227-1",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
30
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1002/hbm.70496 [code]
Transdiagnostic Profiles of BOLD Signal Variability in Autism and Schizophrenia Spectrum Disorders: Associations With Cognition and Functioning.
Journal: Human brain mapping
In common: statsmodels, seaborn, scikit-learn, 4 other tools, cognitive, 3 references
[2] doi:10.1093/nc/niag029 [code]
A data-driven approach to identifying and evaluating connectivity-based neural correlates of conscious visual perception.
Journal: Neuroscience of consciousness
In common: BayesFactor, Pingouin, statsmodels, 6 other tools, cognitive
[3] doi:10.1038/s41467-026-71725-0 [code]
Interactions across hemispheres in prefrontal cortex reflect global cognitive processing.
Journal: Nature communications
In common: scikit-learn, pandas, SciPy, 2 other tools, cognitive, 5 references
[4] doi:10.7554/elife.100605 [code]
Age-related changes in ‘cortical’ 1/f dynamics are linked to cardiac activity
Journal: n/a
In common: Pingouin, statsmodels, seaborn, 5 other tools, 2 references
[5] doi:10.1016/j.crmeth.2026.101421 [code]
EthoPy provides an accessible platform for reproducible behavioral neuroscience.
Journal: Cell reports methods
In common: seaborn, scikit-learn, pandas, 3 other tools, behavior only, mouse, 3 references
[6] doi:10.1038/s41597-026-07350-9 [code]
An open multi-center MEG-EEG dataset for studying conscious visual perception.
Journal: Scientific data
In common: BayesFactor, Pingouin, statsmodels, 6 other tools
[7] doi:10.1038/s41597-026-07377-y [code]
An open-access multi-site fMRI dataset for investigating conscious visual perception.
Journal: Scientific data
In common: BayesFactor, Pingouin, statsmodels, 6 other tools
[8] doi:10.7554/elife.108673 [code]
Adaptive behavior is guided by integrated representations of controlled and non-controlled information.
Journal: eLife
In common: rpy2, Pingouin, statsmodels, 6 other tools
[9] doi:10.1371/journal.pone.0343722 [code]
Comprehensive methodology for sample enrichment in EEG biomarker studies for Alzheimer's risk classification.
Journal: PloS one
In common: rpy2, Pingouin, statsmodels, 6 other tools
[10] doi:10.1038/s41586-026-10528-1 [code]
A critical initialization for biological neural networks.
Journal: Nature
In common: scikit-learn, SciPy, Matplotlib, 1 other tool, mouse, 5 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.