Age-related changes in behavioural and neural variability in a decision-making task.
The 16 matches
- [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] § 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] § Methods › Quality control › Trials ↔ config.py, lines 11–97 · score 0.70 · 0.08–2 s, probabilityLeft, feedbackType, 0.08 s, movement, events
- [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] § 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] § 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] § 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] § 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] § 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] § Methods › Statistical tests ↔ aging_variability/utils/data_utils.py, lines 184–212 · score 0.60 · identity link, Gaussian family, linear, variables, variability
- [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] § Results › Increased firing rate in older animals ↔ config.py, lines 11–97 · score 0.59 · causally, firing rates, CP, CA1, DG, SCm
- [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] § 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] § 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] § 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
- """
- Figure 3b,d,g
- Figure 3 S1b. Regional specificity of pre-stimulus firing rates.
- Figure 3 S2. Regional specificity of post-stimulus firing rates.
- Figure 3 S4b. Regional specificity of age-related differences in contrast modulation of firing rates.
- Figure 4b,d,f,i
- Figure 4 S2b. Regional specificity of pre-stimulus mean-subtracted Fano Factors.
- Figure 4 S2c. Regional specificity of post-stimulus mean-subtracted Fano Factors.
- Figure 4 S3. Regional specificity of mean-subtracted Fano Factor quenching.
- Figure 4 S5b Regional specificity in age-related differences in contrast modulation of the Fano Factor.
- Outputs:
- - Omnibus scatter figs per metric
- - Region slice-org figs per metric
- """
- # %%
- # =====================
- # Imports (cleaned)
- # =====================
- import os
- import numpy as np
- import pandas as pd
- import matplotlib.pyplot as plt
- import seaborn as sns
- from ibl_style.utils import MM_TO_INCH
- import figrid as fg
- import aging_variability.config as C
- from aging_variability.utils.io import read_table, save_figure, get_suffix
- from aging_variability.utils.plot_utils import figure_style, create_slice_org_axes, format_bf_annotation
- from aging_variability.utils.data_utils import add_age_group
- from statsmodels.formula.api import glm
- from statsmodels.genmod.families import Gaussian
- from aging_variability.utils.io import read_table, save_figure
- import logging
- log = logging.getLogger(__name__)
- # =====================
- # Helpers: GLM & age fields
- # =====================
- def def_glm_formula_plot(metric, mean_subtraction=False, log_transform=False):
- """
- Return a Patsy formula (no C() to avoid name collision with config alias).
- - pooled (across regions): include 'cluster_region' on RHS
- - single-region panels: we later strip 'cluster_region'
- """
- yvar = f'log_{metric}' if log_transform else metric
- if metric in ['fr_delta_modulation', 'ff_quench_modulation']:
- return f"{yvar} ~ age_years + cluster_region + n_trials"
- elif metric == 'ff_quench':
- if mean_subtraction:
- return f"{yvar} ~ age_years + cluster_region + n_trials"
- else:
- return f"{yvar} ~ age_years + cluster_region + abs_contrast + n_trials"
- else:
- if mean_subtraction:
- return f"{yvar} ~ age_years + cluster_region + n_trials"
- else:
- return f"{yvar} ~ age_years + cluster_region + abs_contrast + n_trials"
- def strip_region_factor(formula):
- """Remove 'cluster_region' term safely from a simple '+ ...' RHS formula."""
- out = formula.replace('+ cluster_region', '')
- out = out.replace('cluster_region +', '')
- out = out.replace('cluster_region', '')
- return out
- def ensure_age_years(df):
- """Ensure an 'age_years' column exists from common age columns."""
- if 'age_years' in df.columns:
- return df
- out = df.copy()
- if 'mouse_age' in out.columns:
- out['age_years'] = out['mouse_age'] / 365.0
- elif 'mouse_age_months' in out.columns:
- out['age_years'] = out['mouse_age_months'] / 12.0
- elif 'age_at_recording' in out.columns:
- out['age_years'] = out['age_at_recording'] / 365.0
- else:
- raise ValueError("Need one of ['mouse_age','mouse_age_months','age_at_recording'] to compute age_years.")
- return out
- def ensure_age_months(df):
- """Ensure a 'mouse_age_months' column exists (used on x-axis)."""
- if 'mouse_age_months' in df.columns:
- return df
- out = df.copy()
- if 'mouse_age' in out.columns:
- out['mouse_age_months'] = out['mouse_age'] / 30.0
- elif 'age_years' in out.columns:
- out['mouse_age_months'] = out['age_years'] * 12.0
- elif 'age_at_recording' in out.columns:
- out['mouse_age_months'] = out['age_at_recording'] / 30.0
- else:
- raise ValueError("Need one of ['mouse_age','age_years','age_at_recording'] to compute mouse_age_months.")
- return out
- def add_log_y_if_needed(df, metric):
- """Add log_<metric> if not present; small epsilon to avoid log(0)."""
- yvar = f'log_{metric}'
- if yvar not in df.columns:
- out = df.copy()
- out[yvar] = np.log(out[metric] + 1e-6)
- return out
- return df
- def pooled_marginal_line(model, raw_df, xgrid_years, method='median', region_weights=None):
- """
- Predict a pooled line by marginalizing cluster_region with weights.
- Default weights are proportional to sample counts per region.
- """
- agg = np.nanmedian if method == 'median' else np.nanmean
- regions, counts = np.unique(raw_df['cluster_region'].dropna(), return_counts=True)
- if region_weights is None:
- w = counts / counts.sum()
- region_weights = dict(zip(regions, w))
- else:
- # normalize supplied weights on existing regions
- s = sum(region_weights.get(r, 0) for r in regions)
- region_weights = {r: region_weights.get(r, 0) / s for r in regions}
- base = {'age_years': xgrid_years}
- if 'n_trials' in raw_df.columns:
- base['n_trials'] = agg(raw_df['n_trials'])
- if 'abs_contrast' in raw_df.columns:
- base['abs_contrast'] = agg(raw_df['abs_contrast'])
- yhats = []
- for r in regions:
- new = pd.DataFrame(base)
- new['cluster_region'] = r
- yhats.append(region_weights[r] * model.predict(new).values)
- return np.sum(np.stack(yhats, axis=0), axis=0)
- def glm_fit_predict_from_raw(raw_df, metric, mean_subtraction=False, log_transform=False,
- method='median', pooled=True):
- """
- Fit GLM and return (xgrid_years, yhat). Behavior:
- - pooled=True → include 'cluster_region' and marginalize across it
- - pooled=False → strip 'cluster_region' (single-region panel)
- """
- fam = Gaussian()
- formula = def_glm_formula_plot(metric, mean_subtraction, log_transform)
- df_fit = ensure_age_years(raw_df)
- if log_transform:
- df_fit = add_log_y_if_needed(df_fit, metric)
- if 'cluster_region' in df_fit.columns:
- df_fit = df_fit.copy()
- df_fit['cluster_region'] = df_fit['cluster_region'].astype('category')
- if pooled:
- formula_use = formula
- else:
- formula_use = strip_region_factor(formula)
- # eval_env=0 avoids collisions with user-level names
- model = glm(formula=formula_use, data=df_fit, family=fam, eval_env=0).fit()
- xgrid = np.linspace(df_fit['age_years'].min(), df_fit['age_years'].max(), 200)
- if pooled:
- yhat = pooled_marginal_line(model, raw_df=df_fit, xgrid_years=xgrid, method=method, region_weights=None)
- else:
- # single region: just predict on median covariates of that region-data (no region term present)
- base = {'age_years': xgrid}
- if 'n_trials' in df_fit.columns:
- base['n_trials'] = np.nanmedian(df_fit['n_trials']) if method == 'median' else np.nanmean(df_fit['n_trials'])
- if 'abs_contrast' in df_fit.columns and 'abs_contrast' in formula_use:
- base['abs_contrast'] = np.nanmedian(df_fit['abs_contrast']) if method == 'median' else np.nanmean(df_fit['abs_contrast'])
- new = pd.DataFrame(base)
- yhat = model.predict(new).values
- return xgrid, yhat
- def get_vmin_vmax(metric):
- """Per-metric y-limits (kept identical to your previous settings)."""
- ranges = {
- 'log_pre_fr': (0.5, 4.5), 'log_post_fr': (0.5, 4),
- 'fr_delta_modulation': (-10, 20),
- 'log_pre_ff': (-0.75, 0.75), 'log_post_ff': (-0.75, 0.75),
- 'ff_quench': (-0.6, 0.4), 'ff_quench_modulation': (-1.5, 1.2)
- }
- return ranges.get(metric, (-1, 1))
- # =====================
- # Plotting: pooled omnibus scatter
- # =====================
- def plot_scatter_pooled(df, permut_df, BF_df, y_col='pre_fr', estimator='mean',
- granularity='probe_level', ylim=(None, None),
- save=True, mean_subtraction=False):
- """
- Pooled scatter (across regions) with a marginal GLM fit line when BF supports H1.
- Text annotation uses unified format_bf_annotation.
- """
- figure_style()
- raw_df = df.copy()
- # Deduplicate for modulation metrics when not mean-subtracted
- if not mean_subtraction and y_col in ['fr_delta_modulation', 'ff_quench_modulation']:
- raw_df = raw_df.drop(columns=['signed_contrast', 'abs_contrast'], errors='ignore')
- raw_df = raw_df.drop_duplicates(subset=['uuids', 'session_pid', 'mouse_age_months', 'age_group', y_col])
- # Ensure axes age columns
- raw_df = ensure_age_years(ensure_age_months(raw_df))
- # stats
- slope_age = permut_df['observed_val'].values[0]
- p_perm = permut_df['p_perm'].values[0]
- BF_conclusion = BF_df.loc[BF_df['metric'] == y_col, 'BF10_age_category'].values[0]
- BF10 = BF_df.loc[BF_df['metric'] == y_col, 'BF10_age'].values[0]
- fig, ax = plt.subplots(1, 1, figsize=(2.36, 2.36))
- # scatter (size encodes #neurons per session)
- if y_col in ['pre_fr', 'post_fr', 'pre_ff', 'post_ff']:
- log_y = True
- raw_df[f'log_{y_col}'] = np.log(raw_df[y_col] + 1e-6)
- agg_df = (raw_df
- .groupby(['session_pid', 'mouse_age_months', 'age_group'])
- .agg(number_neurons=('uuids', 'nunique'),
- **{f'log_{y_col}': (f'log_{y_col}', 'mean')})
- ).reset_index()
- sns.scatterplot(x='mouse_age_months', y=f'log_{y_col}', data=agg_df,
- hue='age_group', marker='.', legend=False, s=agg_df['number_neurons'],
- palette=C.PALETTE, ax=ax)
- vmin, vmax = get_vmin_vmax(f'log_{y_col}')
- else:
- log_y = False
- agg_df = (raw_df
- .groupby(['session_pid', 'mouse_age_months', 'age_group'])
- .agg(number_neurons=('uuids', 'nunique'),
- **{y_col: (y_col, estimator)})
- ).reset_index()
- agg_df = agg_df.dropna(subset=['mouse_age_months', y_col])
- sns.scatterplot(x='mouse_age_months', y=y_col, data=agg_df,
- hue='age_group', marker='.', legend=False, s=agg_df['number_neurons'],
- palette=C.PALETTE, ax=ax)
- vmin, vmax = get_vmin_vmax(y_col)
- # fit line only if BF supports H1
- if BF_conclusion in ('strong H1', 'moderate H1'):
- xgrid_years, yhat = glm_fit_predict_from_raw(
- raw_df=raw_df,
- metric=y_col,
- mean_subtraction=mean_subtraction,
- log_transform=log_y,
- method='median',
- pooled=True
- )
- ax.plot(xgrid_years * 12.0, yhat, color='gray')
- # y axis config
- ax.set_ylim(vmin, vmax)
- if log_y:
- if 'fr' in y_col:
- tick_vals = [1, 2, 4, 8, 16, 32, 64]
- elif 'ff' in y_col:
- tick_vals = [0.5, 1, 2, 4]
- tick_pos = np.log(tick_vals)
- ax.set_yticks(tick_pos)
- ax.set_yticklabels([str(v) for v in tick_vals])
- ax.set_ylim(np.log(min(tick_vals)), np.log(max(tick_vals)))
- ax.set_ylabel(y_col)
- else:
- ax.set_ylabel(y_col)
- # unified stats annotation
- txt = format_bf_annotation(slope_age, p_perm, BF10, BF_conclusion,
- beta_label="age", big_bf=100)
- ax.text(0.05, 1.0, txt, transform=ax.transAxes, fontsize=5, va='top',
- bbox=dict(facecolor='white', alpha=0.5))
- ax.set_xlabel('Age (months)')
- ax.set_title(f'Omnibus test: {y_col}')
- ax.set_xticks([5, 10, 15, 20])
- sns.despine(offset=2, trim=False, ax=ax)
- plt.tight_layout()
- if save:
- fname = C.FIGPATH / f'Omnibus_{y_col}_{get_suffix(mean_subtraction)}_scatter.pdf'
- save_figure(fig, fname, add_timestamp=True)
- # =====================
- # Plotting: per-region slice-org panels
- # =====================
- def plot_scatter_by_region(df, permut_df, BF_df, y_col='pre_fr', estimator='mean',
- granularity='probe_level', save=True, mean_subtraction=False):
- """
- Slice-org figure with one small panel per region.
- Uses unified format_bf_annotation and single-region GLM line.
- """
- fig, axs = create_slice_org_axes(fg, MM_TO_INCH)
- figure_style()
- raw_df = ensure_age_years(ensure_age_months(df.copy()))
- for region in C.ROIS:
- ax = axs[region]
- sub_df = raw_df[raw_df['cluster_region'] == region].copy()
- if sub_df.empty:
- continue
- # deduplicate where necessary
- if not mean_subtraction and y_col in ['fr_delta_modulation', 'ff_quench_modulation']:
- sub_df = sub_df.drop(columns=['signed_contrast', 'abs_contrast'], errors='ignore')
- sub_df = sub_df.drop_duplicates(subset=['uuids', 'session_pid', 'mouse_age_months', 'age_group', y_col])
- # stats for this region
- sub_perm = permut_df[permut_df['cluster_region'] == region]
- sub_bf = BF_df[BF_df['cluster_region'] == region]
- if sub_perm.empty or sub_bf.empty:
- continue
- slope_age = sub_perm['observed_val'].values[0]
- p_perm = sub_perm['p_perm'].values[0]
- BF_conclusion = sub_bf['BF10_age_category'].values[0]
- BF10 = sub_bf['BF10_age'].values[0]
- # scatter (size encodes #neurons per session)
- if y_col in ['pre_fr', 'post_fr', 'pre_ff', 'post_ff']:
- log_y = True
- sub_df[f'log_{y_col}'] = np.log(sub_df[y_col] + 1e-6)
- agg_df = (sub_df
- .groupby(['session_pid', 'mouse_age_months', 'age_group'])
- .agg(number_neurons=('uuids', 'nunique'),
- **{f'log_{y_col}': (f'log_{y_col}', 'mean')})
- ).reset_index()
- agg_df = agg_df.dropna(subset=['mouse_age_months', f'log_{y_col}'])
- sns.scatterplot(x='mouse_age_months', y=f'log_{y_col}', data=agg_df,
- hue='age_group', marker='.', legend=False, s=agg_df['number_neurons'],
- palette=C.PALETTE, ax=ax)
- vmin, vmax = get_vmin_vmax(f'log_{y_col}')
- else:
- log_y = False
- agg_df = (sub_df
- .groupby(['session_pid', 'mouse_age_months', 'age_group'])
- .agg(number_neurons=('uuids', 'nunique'),
- **{y_col: (y_col, estimator)})
- ).reset_index()
- agg_df = agg_df.dropna(subset=['mouse_age_months', y_col])
- sns.scatterplot(x='mouse_age_months', y=y_col, data=agg_df,
- hue='age_group', marker='.', legend=False, s=agg_df['number_neurons'],
- palette=C.PALETTE, ax=ax)
- vmin, vmax = get_vmin_vmax(y_col)
- # fit line only if BF supports H1
- if BF_conclusion in ('strong H1', 'moderate H1'):
- xgrid_years, yhat = glm_fit_predict_from_raw(
- raw_df=sub_df,
- metric=y_col,
- mean_subtraction=mean_subtraction,
- log_transform=log_y,
- method='median',
- pooled=False # single-region panels
- )
- ax.plot(xgrid_years * 12.0, yhat, color='gray', lw=0.8)
- # axes cosmetics
- ax.set_ylim(vmin, vmax)
- if log_y:
- if 'fr' in y_col:
- tick_vals = [1, 3, 9, 27]
- elif 'ff' in y_col:
- tick_vals = [0.5, 1, 2, 4]
- tick_pos = np.log(tick_vals)
- ax.set_yticks(tick_pos)
- ax.set_yticklabels([str(v) for v in tick_vals])
- ax.set_ylim(np.log(min(tick_vals)), np.log(max(tick_vals)))
- ax.set_ylabel(y_col)
- else:
- ax.set_ylabel(y_col)
- # unified annotation
- txt = format_bf_annotation(slope_age, p_perm, BF10, BF_conclusion,
- beta_label="age", big_bf=100)
- ax.text(0.05, 1.25, txt, transform=ax.transAxes, fontsize=4, va='top', linespacing=0.8)
- sns.despine(offset=2, trim=False, ax=ax)
- if region in ['ACB', 'OLF', 'MBm', 'PO']:
- ax.set_xticks([5, 10, 15, 20])
- else:
- ax.set_xticks([])
- ax.set_xlabel(" ")
- ax.set_ylabel(" ")
- fig.suptitle(f'{granularity} level: {y_col}', font="Arial", fontsize=8)
- fig.supxlabel('Age (months)', font="Arial", fontsize=8).set_y(0.35)
- if save:
- fname = C.FIGPATH / f"supp_slice_org_{y_col}_{granularity}_{get_suffix(mean_subtraction)}_{estimator}_age_relationship_{C.ALIGN_EVENT}_2025.pdf"
- save_figure(fig, fname, add_timestamp=True)
- # =====================
- # Main
- # =====================
- def main(mean_subtraction=False):
- """
- Load neural metrics → add age_group (via utils) + ensure age fields →
- read cached permutation/BF tables → draw pooled + per-region panels.
- """
- if mean_subtraction:
- metrics_path = C.DATAPATH / "neural_metrics_summary_meansub.parquet"
- selected_metrics = C.METRICS_WITH_MEANSUB
- else:
- metrics_path = C.DATAPATH / "neural_metrics_summary_conditions.parquet"
- selected_metrics = C.METRICS_WITHOUT_MEANSUB
- print("Loading extracted neural metrics summary...")
- neural_metrics = read_table(metrics_path)
- neural_metrics = add_age_group(neural_metrics)
- # ensure x/y age fields exist even if upstream files omit them
- neural_metrics = ensure_age_months(ensure_age_years(neural_metrics))
- for metric, est in selected_metrics:
- 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"
- 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"
- df_BF_path_pooled = C.RESULTSPATH / f"omnibus_{get_suffix(mean_subtraction)}BFs_{C.ALIGN_EVENT}_{C.TRIAL_TYPE}.csv"
- df_BF_path_region = C.RESULTSPATH / f"regional_{get_suffix(mean_subtraction)}BFs_{C.ALIGN_EVENT}_{metric}_{C.TRIAL_TYPE}.csv"
- df_permut_pooled = read_table(df_permut_path_pooled)
- df_permut_region = read_table(df_permut_path_region)
- df_BF_pooled = read_table(df_BF_path_pooled)
- df_BF_region = read_table(df_BF_path_region)
- print(f"Plotting {metric}...")
- plot_scatter_pooled(neural_metrics, df_permut_pooled, df_BF_pooled,
- y_col=metric, estimator=est, granularity='probe_level',
- save=True, mean_subtraction=mean_subtraction)
- plot_scatter_by_region(neural_metrics, df_permut_region, df_BF_region,
- y_col=metric, estimator=est, granularity='probe_level',
- save=True, mean_subtraction=mean_subtraction)
- if __name__ == "__main__":
- from aging_variability.utils.io import setup_logging
- setup_logging()
- main(mean_subtraction=True)
neural_07_plot_scatters_slice_org.py at commit 7202a80, under MIT · at the source
Overview
24 affiliations
- Cognitive Psychology Unit, Institute of Psychology, Leiden University, Leiden, The Netherlands
- Department of Neurobiology, David Geffen School of Medicine, University of California, Los Angeles, Los Angeles, CA USA
- Cold Spring Harbor Laboratory, Cold Spring Harbor, NY USA
- Zuckerman Institute, Columbia University, New York, NY USA
- Department of Basic Neuroscience, University of Geneva, Geneva, Switzerland
- Gatsby Computational Neuroscience Unit, University College London, London, UK
- Center for Neural Science, New York University, New York, NY USA
- Champalimaud Center for the Unknown, Lisboa, Portugal
- Princeton Neuroscience Institute, Princeton University, Princeton, NJ USA
- Institute of Neurology, University College London, London, UK
- Department of Biological Structure, University of Washington, Seattle, WA USA
- Department of Applied Physics, Stanford University, Stanford, CA USA
- William James Center for Research (WJCR), ISPA - Instituto Universitário, Lisbon, Portugal
- Max Planck Institute for Biological Cybernetics, Tübingen, Germany
- Sainsbury-Wellcome Centre, University College London, London, UK
- Institute of Ophthalmology, University College London, London, UK
- Department of Molecular and Cell Biology, University of California, Berkeley, CA USA
- Département D’études Cognitives, École Normale Supérieure, Paris, France
- Department of Brain and Cognitive Sciences, Massachusetts Institute of Technology, Cambridge, MA USA
- Wolfson Institute of Biomedical Research, University College London, London, UK
- Center for Computational Neuroscience, University of Washington, Seattle, WA USA
- Department of Physiology, University of Yamanashi, Kofu, Yamanashi, Japan
- Department of Neuroscience, University of Minnesota, Minneapolis, MN USA
- The Allen Institute for Neural Dynamics, Seattle, WA USA
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
7202a808febdf696cf270ef42193042f34d6efb4, 27 August 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
36 files
- aging_variability/
__init__.py , Python, 1 line - aging_variability/
behavior/ , Python, 243 linesbehavior_01a_compute_met rics_permutation.py - aging_variability/
behavior/ , Python, 550 lines, 1 matchbehavior_01b_plot_main.p y - aging_variability/
behavior/ , Python, 199 linesbehavior_02a_compute_tra ining.py - aging_variability/
behavior/ , Python, 535 linesbehavior_02b_plot_supp_t raining.py - aging_variability/
behavior/ , Python, 125 linesbehavior_03_plot_supp_tr ial_counts.py - aging_variability/
behavior/ , Python, 184 lines, 1 matchbehavior_04_plot_supp_ch oice_bias.py - aging_variability/
behavior/ , Python, 198 linesbehavior_05_plot_supp_rt _variations.py - aging_variability/
config.py , Python, 202 lines, 2 matches - aging_variability/
neural/ , Python, 875 lines, 1 matchneural_01_compute_metric s_time_courses.py - aging_variability/
neural/ , Python, 202 linesneural_02_extract_metric s_summary.py - aging_variability/
neural/ , Python, 179 linesneural_03a_stats_permuta tion.py - aging_variability/
neural/ , Python, 297 linesneural_03b_stats_BFs.py - aging_variability/
neural/ , Python, 174 linesneural_04_neural_yield_s lice_org.py - aging_variability/
neural/ , Python, 182 linesneural_05_plot_timecours es_slice_org.py - aging_variability/
neural/ , Python, 241 lines, 3 matchesneural_06_plot_modulatio n_timecourses_slice_org. py - aging_variability/
neural/ , Python, 457 lines, 3 matchesneural_07_plot_scatters_ slice_org.py - aging_variability/
neural/ , Python, 112 linesneural_08_plot_Swanson_m ap.py - aging_variability/
neural/ , Python, 150 linesneural_09_plot_singleFF_ logscatter_slice_org.py - aging_variability/
preprocessing/ , Python, 220 lines, 1 matchpreprocessing_00_QC_proc ess.py - aging_variability/
preprocessing/ , Python, 59 linespreprocessing_01_generat e_merged_tables.py - aging_variability/
utils/ , Python, 1 line__init__.py - aging_variability/
utils/ , Python, 347 linesbehavior_utils.py - aging_variability/
utils/ , Python, 282 lines, 2 matchesdata_utils.py - aging_variability/
utils/ , Python, 180 linesio.py - aging_variability/
utils/ , Python, 317 linesneuron_utils.py - aging_variability/
utils/ , Python, 530 linesplot_utils.py - aging_variability/
utils/ , Python, 193 linesquery.py - aging_variability/
utils/ , Python, 318 linesstats_utils.py - config.py, Python, 203 lines, 2 matches
- query_example_notebook.i
pynb , Jupyter, 40 lines - run_all_with_R.py, Python, 53 lines
- run_all_without_R.py, Python, 53 lines
- run_figs.py, Python, 43 lines
- LICENSE, License, 21 lines
- README.md, Text, 121 lines
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://
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://
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://
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://
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/
url = {https://
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/
VL - 17
IS - 1
SP - 8156
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"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":
"volume": "17",
"issue": "1",
"page": "8156",
"DOI": "10.1038/
"PMID": "42380131",
"PMCID": "PMC13458552",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://
"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 mappingIn 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 consciousnessIn 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 communicationsIn 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/aIn 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 methodsIn 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 dataIn 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 dataIn 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: eLifeIn 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 oneIn common: rpy2, Pingouin, statsmodels, 6 other tools
- [10] doi:10.1038/s41586-026-10528-1 [code]
- A critical initialization for biological neural networks.Journal: NatureIn 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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 34 scripts, and 16 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:ef7059c56b34c3ea…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
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.
