Breaking the norm: population-scale deviations of brain structure in depression and anxiety.
The 15 matches
- [1] § Materials and methods › Group definitions ↔ ukb_validation/directional_analysis.py, lines 342–419 · score 0.83 · 15–19, 15–21, 20–27, 10–14, 0–4, 5–9
- [2] § Results › Shift analysis ↔ normative_modeling/2_1_significance_testing.py, lines 1–37 · score 0.74 · Benjamini Hochberg correction, age squared, HC reference, healthy control, Mahalanobis distances, variable
- [3] § Materials and methods › Group definitions ↔ ukb_validation/genetic_and_shift.py, lines 423–483 · score 0.71 · 15–19, 20–27, 10–14, 5–9, scores, PHQ
- [4] § Materials and methods › Significance testing of mahalanobis distances ↔ normative_modeling/2_1_significance_testing.py, lines 1–37 · score 0.71 · Benjamini Hochberg, discovery rate, ANOVA, HCs, covariate, mahalanobis
- [5] § Materials and methods › Classification analysis ↔ ukb_validation/classification.py, lines 50–115 · score 0.62 · logistic regression, classifications, fold, ratio, elastic, stratified
- [6] § Materials and methods › Classification analysis ↔ ukb_validation/classification.py, lines 50–115 · score 0.61 · balanced accuracy, ROC, fold, AUC, BACC, stratified
- [7] § Materials and methods › Normative modeling using autoencoder ↔ normative_modeling/1_train_autoencoder.py, lines 234–316 · score 0.60 · fully connected autoencoder, Architectural, reconstruction, space, normative, trained
- [8] § Materials and methods › Normative modeling using autoencoder ↔ ukb_validation/directional_analysis.py, lines 66–148 · score 0.59 · fully connected autoencoder, Architectural, reconstruction, validation, space, normative
- [9] § Materials and methods › Shift analysis of mahalanobis distances ↔ normative_modeling/2_3_mahalanobis_shift_analysis.py, lines 21–105 · score 0.57 · covariance matrix, Mahalanobis distance, subset, HCs, Shift
- [10] § Materials and methods › Normative modeling using autoencoder ↔ normative_modeling/1_train_autoencoder.py, lines 351–384 · score 0.56 · trained autoencoder, age squared, linear, sex, errors, modeling
- [11] § Materials and methods › Shift analysis of mahalanobis distances ↔ normative_modeling/2_4_shift_analysis.py, lines 50–115 · score 0.56 · bootstrap resampling, confidence intervals, shift
- [12] § Results › Directional analysis ↔ ukb_validation/directional_analysis.py, lines 704–756 · score 0.53 · moderately severe, deviation vectors, mild, centroid, ellipses, magnitudes
- [13] § Materials and methods › Shift analysis of mahalanobis distances ↔ normative_modeling/2_4_shift_analysis.py, lines 140–192 · score 0.53 · weighted area, emphasizes, metric, quantified, shift, distance
- [14] § Materials and methods › Cohorts - UK biobank (UKB) ↔ ukb_validation/genetic_and_shift.py, lines 299–356 · score 0.53 · neurodegenerative diseases, volume, UKB, sex, biobank, age
- [15] § Materials and methods › Normative modeling using autoencoder ↔ normative_modeling/1_train_autoencoder.py, lines 443–501 · score 0.52 · min max, TIV, autoencoder, linearly, age, sex
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 · 850 lines · 30 KB · GPL-3.0 · 3 matches
- import torch.nn as nn
- import torch
- import joblib
- import matplotlib.pyplot as plt
- import numpy as np
- import pandas as pd
- import pickle
- from sklearn.preprocessing import StandardScaler
- from sklearn.preprocessing import OneHotEncoder, MinMaxScaler
- from sklearn.linear_model import LinearRegression
- from matplotlib.patches import Ellipse, Patch
- from matplotlib.lines import Line2D
- from shapely.geometry import MultiPoint
- class Deconfounder:
- def __init__(self):
- self.models = []
- def train(self, embeddings, confounders):
- """
- Train the deconfounding models on the provided embeddings and confounders.
- Args:
- embeddings (numpy.ndarray): The embeddings to be deconfounded (N x D).
- confounders (numpy.ndarray): The confounders (N x C).
- Returns:
- numpy.ndarray: Deconfounded embeddings for the training data.
- """
- self.models = []
- residuals = np.zeros_like(embeddings)
- for i in range(embeddings.shape[1]):
- model = LinearRegression()
- model.fit(confounders, embeddings[:, i])
- self.models.append(model)
- predicted = model.predict(confounders)
- residuals[:, i] = embeddings[:, i] - predicted
- return residuals
- def apply(self, embeddings, confounders):
- """
- Apply the trained deconfounding models on new embeddings and confounders.
- Args:
- embeddings (numpy.ndarray): The new embeddings to be deconfounded (N x D).
- confounders (numpy.ndarray): The confounders for the new embeddings (N x C).
- Returns:
- numpy.ndarray: Deconfounded embeddings.
- """
- if not self.models:
- raise ValueError("Deconfounding models have not been trained. Please train them first.")
- residuals = np.zeros_like(embeddings)
- for i, model in enumerate(self.models):
- predicted = model.predict(confounders)
- residuals[:, i] = embeddings[:, i] - predicted
- return residuals
- class Autoencoder(nn.Module):
- """
- Normative autoencoder model for structural MRI embeddings.
- This fully connected autoencoder compresses input features into a low-dimensional latent space
- and reconstructs them back, aiming to model normative (healthy) data distributions.
- Architecture:
- - Encoder: 4 linear layers with ReLU activations reducing to a low-dimensional latent space.
- - Decoder: 4 linear layers with ReLU activations reconstructing the input, ending with a Sigmoid activation.
- Methods
- -------
- encode(x):
- Encodes input into a lower-dimensional latent representation.
- decode(x):
- Decodes latent representations back to input space.
- forward(x, train=True):
- Full autoencoding pass; if train=False, returns the reconstruction only.
- """
- def __init__(self, input_dim=512, hidden_dim1=150, hidden_dim2=100, hidden_dim3=75, hidden_dim4=50):
- super(Autoencoder, self).__init__()
- # Encoder
- self.enc_linear1 = nn.Linear(input_dim, hidden_dim1)
- self.enc_relu1 = nn.ReLU()
- self.enc_linear2 = nn.Linear(hidden_dim1, hidden_dim2)
- self.enc_relu2 = nn.ReLU()
- self.enc_linear3 = nn.Linear(hidden_dim2, hidden_dim3)
- self.enc_relu3 = nn.ReLU()
- self.enc_linear4 = nn.Linear(hidden_dim3, hidden_dim4)
- self.enc_relu4 = nn.ReLU()
- # Decoder
- self.dec_linear1 = nn.Linear(hidden_dim4, hidden_dim3)
- self.dec_relu1 = nn.ReLU()
- self.dec_linear2 = nn.Linear(hidden_dim3, hidden_dim2)
- self.dec_relu2 = nn.ReLU()
- self.dec_linear3 = nn.Linear(hidden_dim2, hidden_dim1)
- self.dec_relu3 = nn.ReLU()
- self.dec_linear4 = nn.Linear(hidden_dim1, input_dim)
- self.dec_relu4 = nn.ReLU()
- self.sig = nn.Sigmoid()
- def encode(self, x):
- x = self.enc_linear1(x)
- x = self.enc_relu1(x)
- x = self.enc_linear2(x)
- x = self.enc_relu2(x)
- x = self.enc_linear3(x)
- x = self.enc_relu3(x)
- x = self.enc_linear4(x)
- x = self.enc_relu4(x)
- return x
- def decode(self, x):
- x = self.dec_linear1(x)
- x = self.dec_relu1(x)
- x = self.dec_linear2(x)
- x = self.dec_relu2(x)
- x = self.dec_linear3(x)
- x = self.dec_relu3(x)
- x = self.dec_linear4(x)
- x = self.sig(x)
- return x
- def forward(self, x, train=True):
- z = self.encode(x)
- decoded = self.decode(z)
- if not train:
- return decoded
- return decoded
- def assign_color_from_label(label):
- label_lower = label.lower()
- if "and_depression" in label_lower:
- return group_colors_map["GAD+PHQ-9"]
- elif "depression_ad" in label_lower:
- return group_colors_map["GAD/PHQ-9+AUDIT-C"]
- elif "audit" in label_lower or "aud_" in label_lower:
- return group_colors_map["AUDIT-C"]
- elif "ad_" in label_lower or "gad_" in label_lower or "ad_ic" in label_lower or "self_rep_anx" in label_lower:
- return group_colors_map["GAD"]
- elif "depression" in label_lower or "dep_ic" in label_lower or "self_rep_mdd" in label_lower:
- return group_colors_map["PHQ-9"]
- return "gray"
- def sample_ellipse_points(center, width, height, angle, n_points=200):
- t = np.linspace(0, 2 * np.pi, n_points)
- ellipse = np.column_stack([np.cos(t) * width / 2, np.sin(t) * height / 2])
- theta = np.radians(angle)
- R = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
- ellipse_rot = ellipse @ R.T + center
- return ellipse_rot
- def encapsulate_meta_group(meta_color_list):
- all_points = []
- for group in strict_diag_cols:
- mask = df[group] == 1
- if mask.sum() == 0: continue
- group_color = assign_color_from_label(group)
- if group_color in meta_color_list:
- center, width, height, angle = bootstrap_ci_ellipse(residuals_2d[mask])
- points = sample_ellipse_points(center, width, height, angle)
- all_points.append(points)
- if all_points:
- all_points = np.vstack(all_points)
- hull = MultiPoint(all_points).convex_hull
- return hull
- return None
- def geom_median(X, eps=1e-6, max_iter=1000):
- """Weiszfeld algorithm for the spatial (geometric) median in 2D."""
- X = np.asarray(X, dtype=float)
- m = X.mean(axis=0)
- for _ in range(max_iter):
- d = np.linalg.norm(X - m, axis=1)
- if np.any(d < eps):
- return X[d.argmin()]
- w = 1.0 / d
- m_new = (X * w[:, None]).sum(axis=0) / w.sum()
- if np.linalg.norm(m_new - m) < eps:
- return m_new
- m = m_new
- return m
- def bootstrap_ci_ellipse(points_2d, n_bootstrap=1000):
- """
- Returns a 1-SD ellipse of the bootstrap-median distribution (NOT a CI).
- Interface unchanged: (center, width, height, angle).
- """
- X = np.asarray(points_2d, dtype=float)
- n = X.shape[0]
- meds = np.zeros((n_bootstrap, 2), dtype=float)
- for i in range(n_bootstrap):
- sample = X[np.random.randint(0, n, n)]
- meds[i] = geom_median(sample)
- center = geom_median(meds)
- cov = np.cov(meds, rowvar=False) + 1e-9 * np.eye(2)
- vals, vecs = np.linalg.eigh(cov)
- order = vals.argsort()[::-1]
- vals, vecs = vals[order], vecs[:, order]
- angle = np.degrees(np.arctan2(vecs[1, 0], vecs[0, 0]))
- # 1-SD ellipse: diameters = 2 * sqrt(eigenvalues)
- width, height = 2.0 * np.sqrt(vals[0]), 2.0 * np.sqrt(vals[1])
- return center, width, height, angle
- def bootstrap_ci(data, n_bootstrap=1000, ci=95):
- """
- Bootstraps the confidence interval for the median vector.
- """
- medians = []
- n_samples = data.shape[0]
- for _ in range(n_bootstrap):
- sample = data[np.random.choice(n_samples, n_samples, replace=True)]
- medians.append(np.median(sample, axis=0))
- medians = np.array(medians)
- lower = np.percentile(medians, (100 - ci) / 2, axis=0)
- upper = np.percentile(medians, 100 - (100 - ci) / 2, axis=0)
- return lower, upper
- def replace_terms(text):
- replacements = {
- "DEP_ICD10": "ICD-10",
- "ALZ_ICD10": "ND ICD-10",
- "depression_1_no_comor": "(1)",
- "depression_2_no_comor": "(2)",
- "depression_3_no_comor": "(3)",
- "depression_4_no_comor": "(4)",
- "AUD_ICD10": "AUD ICD-10",
- "diagnosis_audit_8_no_comor": "(8)",
- "diagnosis_audit_9_no_comor": "(9)",
- "diagnosis_audit_10_no_comor": "(>= 10)",
- "GAD_ICD10": "GAD ICD-10",
- "AD_ICD10": "ANX ICD-10",
- "ad_2_no_comor": "(2)",
- "ad_3_no_comor": "(3)",
- "ad_4_no_comor": "(4)",
- "ad_2_and_depression_2_comor": "(2)",
- "ad_3_and_depression_3_comor": "(3)",
- "ad_4_and_depression_4_comor": "(4)",
- "depression_ad_aud_2": "(2)",
- "depression_ad_aud_3": "(3)", "depression_ad_aud_4": "(4)",
- "depression_ad_audit_2": "MDD/GAD (2) and AUDIT-C >= 8", "depression_ad_audit_3": "and AUDIT-C >= 9",
- "depression_ad_audit_4": "and AUDIT-C >= 10"
- }
- for key, value in replacements.items():
- text = text.replace(key, value)
- return text
- df_prs_meta = pd.read_csv("/opt/notebooks/studies_info.tsv", sep="\t")
- hc_embeddings_anxiety_path = "/opt/notebooks/all_embdes_raw_40k.csv"
- df = pd.read_csv(hc_embeddings_anxiety_path)
- df["id_"] = df["id_"].astype(str)
- df['embedding'] = df['embedding'].apply(
- lambda x: [np.fromstring(x.strip('[]'), sep=' ')])
- freesurfer_path = "/opt/notebooks/aseg.volume.tsv"
- free = pd.read_csv(freesurfer_path, sep="\t")
- free.rename(columns={"Measure:volume": "Participant ID"}, inplace=True)
- free["Participant ID"] = free["Participant ID"].str.replace("sub-", "").astype(str)
- df["Participant ID"] = df["Participant ID"].astype(str)
- merged_df = df.merge(free, on=["Participant ID"], how="inner")
- columns_to_check = [
- "Sex",
- "Age when attended assessment centre | Instance 2",
- "EstimatedTotalIntraCranialVol",
- "UK Biobank assessment centre | Instance 2",
- "embedding"
- ]
- merged_df = merged_df.dropna(subset=columns_to_check)
- encoder = OneHotEncoder(drop='first', sparse_output=False)
- sex_labels = merged_df['Sex'].map({1: 0, 2: 1}).values.reshape(-1, 1)
- mean_age = pd.to_numeric(merged_df["Age when attended assessment centre | Instance 2"], errors="coerce").mean()
- ages = merged_df["Age when attended assessment centre | Instance 2"].values.reshape(-1, 1).astype(float)
- tiv = merged_df["EstimatedTotalIntraCranialVol"].values.reshape(-1, 1).astype(float)
- basis_uort = merged_df["UK Biobank assessment centre | Instance 2"].values.reshape(-1, 1)
- basis_uort_onehot = encoder.fit_transform(basis_uort.reshape(-1, 1))
- confounders = np.concatenate([sex_labels, ages, (ages - mean_age) ** 2, tiv], axis=1)
- all_embeddings = np.vstack(merged_df['embedding'].values)
- deconfounder = joblib.load("/opt/notebooks/first_deconfounder.joblib")
- embeddings = deconfounder.apply(all_embeddings, confounders)
- deconfounder = Deconfounder()
- deconfounder.train(embeddings, basis_uort_onehot)
- embeddings = deconfounder.apply(embeddings, basis_uort_onehot)
- merged_df["embeddings_deconfounded"] = list(embeddings)
- gad7 = pd.read_csv("/opt/notebooks/gad7_filtered.csv")
- only_one_missing = gad7.isnull().sum(axis=1) == 1
- more_than_one_missing = gad7.isnull().sum(axis=1) > 1
- gad7 = gad7[~more_than_one_missing].copy()
- gad7.loc[only_one_missing, :] = gad7.loc[only_one_missing, :].fillna(gad7.mode().iloc[0])
- gad7.rename(columns={'eid': 'Participant ID'}, inplace=True)
- gad7_mapping = {
- "Not at all": 0,
- "Several days": 1,
- "More than half the days": 2,
- "Nearly every day": 3,
- "Prefer not to answer": -3
- }
- for col in gad7.columns:
- if col != "Participant ID":
- gad7[col] = gad7[col].map(gad7_mapping)
- gad7 = gad7[~gad7.isin([-3]).any(axis=1)].dropna()
- gad7["gad7_sum"] = gad7.drop(columns=["Participant ID"]).sum(axis=1)
- gad7["ad_1"] = (gad7["gad7_sum"].between(0, 4)).astype(int)
- gad7["ad_2"] = (gad7["gad7_sum"].between(5, 9)).astype(int)
- gad7["ad_3"] = (gad7["gad7_sum"].between(10, 14)).astype(int)
- gad7["ad_4"] = (gad7["gad7_sum"].between(15, 21)).astype(int)
- merged_df["Participant ID"] = merged_df["Participant ID"].astype(str)
- gad7["Participant ID"] = gad7["Participant ID"].astype(str)
- merged_df = merged_df.merge(gad7, on='Participant ID', how="inner")
- phq = pd.read_csv("/opt/notebooks/PHQ9_filtered.csv")
- only_one_missing = phq.isnull().sum(axis=1) == 1
- more_than_one_missing = phq.isnull().sum(axis=1) > 1
- phq = phq[~more_than_one_missing].copy()
- phq.loc[only_one_missing, :] = phq.loc[only_one_missing, :].fillna(phq.mode().iloc[0])
- phq.rename(columns={'eid': 'Participant ID'}, inplace=True)
- phq_mapping = {
- "Not at all": 0,
- "Several days": 1,
- "More than half the days": 2,
- "Nearly every day": 3,
- "Prefer not to answer": -1
- }
- for col in phq.columns:
- if col != "Participant ID":
- phq[col] = phq[col].map(phq_mapping)
- phq = phq[~phq.isin([-1]).any(axis=1)].dropna()
- phq["phq9_sum"] = phq.drop(columns=["Participant ID"]).sum(axis=1)
- phq["depression_1"] = (phq["phq9_sum"].between(5, 9)).astype(int)
- phq["depression_2"] = (phq["phq9_sum"].between(10, 14)).astype(int)
- phq["depression_3"] = (phq["phq9_sum"].between(15, 19)).astype(int)
- phq["depression_4"] = (phq["phq9_sum"].between(20, 27)).astype(int)
- merged_df["Participant ID"] = merged_df["Participant ID"].astype(str)
- phq["Participant ID"] = phq["Participant ID"].astype(str)
- merged_df = merged_df.merge(phq, on='Participant ID', how="inner")
- alc = pd.read_csv("/opt/notebooks/alcohol_amount.csv")
- six = pd.read_csv("/opt/notebooks/alc_six_units.csv")
- six.rename(columns={'p29093': 'six_alc_frq'}, inplace=True)
- alc = pd.merge(six, alc, on="eid", how="inner")
- alc = alc.fillna(alc.mode().iloc[0])
- alcohol_per_day = {
- "1 or 2": 1.5,
- "3 or 4": 3.5,
- "5 or 6": 5.5,
- "7, 8 or 9": 8,
- "10 or more": 11
- }
- audit_freq_score = {
- "Never": 0,
- "Monthly or less": 1,
- "2 to 4 times a month": 2,
- "2 to 3 times a week": 3,
- "4 or more times a week": 4
- }
- audit_amount_score = {
- "1 or 2": 0,
- "3 or 4": 1,
- "5 or 6": 2,
- "7, 8 or 9": 3,
- "10 or more": 4
- }
- audit_six_score = {
- "Never": 0,
- "Less than monthly": 1,
- "Monthly": 2,
- "Weekly": 3,
- "Daily or almost daily": 4
- }
- # Compute AUDIT-C score
- alc["audit_freq"] = alc["drinking_frequency"].map(audit_freq_score).fillna(0)
- alc["audit_amount"] = alc["amount_per_day"].map(audit_amount_score).fillna(0)
- alc["audit_six"] = alc["six_alc_frq"].map(audit_six_score).fillna(0)
- alc["audit_c_score"] = alc["audit_freq"] + alc["audit_amount"] + alc["audit_six"]
- alc["diagnosis_audit_8"] = (alc["audit_c_score"] == 8).astype(int)
- alc["diagnosis_audit_9"] = (alc["audit_c_score"] == 9).astype(int)
- alc["diagnosis_audit_10"] = (alc["audit_c_score"] >= 10).astype(int)
- merged_df["Participant ID"] = merged_df["Participant ID"].astype(str)
- alc["Participant ID"] = alc["Participant ID"].astype(str)
- merged_df = merged_df.merge(alc, on='Participant ID', how="inner")
- icd = pd.read_csv("/opt/notebooks/ICD_10_Diagnosis.csv")
- icd.rename(columns={'eid': 'Participant ID'}, inplace=True)
- icd["GAD_ICD10"] = 0
- icd["AUD_ICD10"] = 0
- icd["ALZ_ICD10"] = 0
- icd["DEP_ICD10"] = 0
- icd["AD_ICD10"] = 0
- icd["has_anything"] = 0
- mask = icd.drop(columns=["Participant ID"]).astype(str).applymap(
- lambda x:
- ("anxiety" in x.lower())
- )
- icd.loc[mask.any(axis=1), "AD_ICD10"] = 1
- mask = icd.drop(columns=["Participant ID"]).astype(str).applymap(
- lambda x: ("generalized anxiety" in x.lower()) or
- ("generalised anxiety" in x.lower())
- )
- icd.loc[mask.any(axis=1), "GAD_ICD10"] = 1
- mask = icd.drop(columns=["Participant ID"]).astype(str).applymap(
- lambda x: ("alcohol use disorder" in x.lower()) or
- ("f10" in x.lower())
- )
- icd.loc[mask.any(axis=1), "AUD_ICD10"] = 1
- dep_mask = icd.drop(columns=["Participant ID"]).astype(str).applymap(
- lambda x: ("mdd" in x.lower()) or
- ("depression" in x.lower()) or
- ("f32" in x.lower()) or
- ("f33" in x.lower())
- )
- icd.loc[dep_mask.any(axis=1), "DEP_ICD10"] = 1
- merged_df["Participant ID"] = merged_df["Participant ID"].astype(str)
- icd["Participant ID"] = icd["Participant ID"].astype(str)
- merged_df = merged_df.merge(icd, on='Participant ID', how="inner")
- diag = pd.read_csv("/opt/notebooks/merged_multitarget_df_with_demographics.csv")
- filtered_columns = [col for col in diag.columns]
- diag.rename(columns={'p29058': 'frequency_a_attack'}, inplace=True)
- diag["Participant ID"] = diag["ID"]
- neurodegenerative_diseases = pd.read_csv("/opt/notebooks/neurodegenerative_disease_all.csv")
- diag = diag.merge(neurodegenerative_diseases, on="Participant ID", how="inner")
- diag = diag[diag["neurodegenerative_disease"] != 1].reset_index(drop=True)
- merged_df["Participant ID"] = merged_df["Participant ID"].astype(str)
- diag["Participant ID"] = diag["Participant ID"].astype(str)
- df = merged_df.merge(diag, on="Participant ID", how="inner")
- columns_to_check = [
- 'GAD_ICD10', 'DEP_ICD10', "ALZ_ICD10", 'depression_1', "ad_2", "ad_3", "ad_4", "diagnosis_audit_8",
- "diagnosis_audit_9", "diagnosis_audit_10",
- 'depression_2', 'depression_3', 'depression_4', 'AUD_ICD10',
- "AD_ICD10", "AUD_ICD10",
- ]
- columns_to_check_comors = ['depression_1', "ad_2", "ad_3", "ad_4", "diagnosis_audit_8", "diagnosis_audit_9",
- "diagnosis_audit_10",
- 'depression_2', 'depression_3', 'depression_4', "ALZ_ICD10"]
- coloumns_ro_check_comors_depr = ["ad_2", "ad_3", "ad_4", 'GAD_ICD10']
- df["is_healthy"] = (
- (df[columns_to_check] != 1).all(axis=1)
- )
- for col in columns_to_check_comors:
- df[f"{col}_no_comor"] = df[col].where(
- (df[col] == 1) & (df[columns_to_check_comors].sum(axis=1) == 1), 0
- )
- for threshold in [2, 3, 4]:
- ad_col = f"ad_{threshold}"
- dep_col = f"depression_{threshold}"
- comor_col = f"ad_{threshold}_and_depression_{threshold}_comor"
- if ad_col in df.columns and dep_col in df.columns:
- df[comor_col] = ((df[ad_col] == 1) & (df[dep_col] == 1)).astype(int)
- else:
- print(f"Missing column(s): {ad_col} or {dep_col}")
- df.to_csv("Diagnosis_tmp.csv")
- healthy_embeddings = np.stack(df.loc[df["is_healthy"], 'embeddings_deconfounded'].values)
- scaler = joblib.load("/opt/notebooks/scaler.pkl")
- df["embeddings_deconfounded"] = list(scaler.transform(np.stack(df['embeddings_deconfounded'].values)))
- deconfounded_embeddings = np.stack(df['embeddings_deconfounded'].values)
- mdd_cols = ['depression_1', 'depression_2', 'depression_3', 'depression_4', 'DEP_ICD10']
- anx_cols = ['ad_2', 'ad_3', 'ad_4', 'GAD_ICD10', 'AD_ICD10']
- aud_cols = ['diagnosis_audit_8', 'diagnosis_audit_9', 'diagnosis_audit_10', 'AUD_ICD10']
- mdd_df = pd.read_csv('bd_mdd_self_reported')
- df["Participant ID"] = df["Participant ID"].astype(str)
- mdd_df["eid"] = mdd_df["eid"].astype(str)
- merged_df = df.merge(mdd_df, left_on='Participant ID', right_on='eid', how='left')
- mdd_df = pd.read_csv("self_reported_mdd_anx.csv")
- mdd_df["eid"] = mdd_df["eid"].astype(str)
- merged_df = df.merge(mdd_df, left_on="Participant ID", right_on="eid", how="left")
- mask_mdd = merged_df["p20544"].str.contains("Depression", case=False, na=False)
- mask_anx = merged_df["p20544"].str.contains("Anxiety", case=False, na=False)
- if "is_healthy" not in df.columns:
- raise ValueError("Expected 'is_healthy' in smri_prs.csv")
- df.loc[mask_mdd | mask_anx, "is_healthy"] = 0
- df["self_rep_mdd"] = mask_mdd.astype(int)
- df["self_rep_anx"] = mask_anx.astype(int)
- input_dim = deconfounded_embeddings.shape[1]
- model = Autoencoder(input_dim)
- checkpoint = torch.load("/opt/notebooks/model_ae.pth", map_location=torch.device('cpu'))
- model.load_state_dict(checkpoint)
- model.eval()
- deconfounded_embeddings = torch.tensor(deconfounded_embeddings, dtype=torch.float32)
- with torch.no_grad():
- deconfounded_embeddings_reconstructions = model(deconfounded_embeddings)
- l1_loss = nn.MSELoss(reduction='none')
- all_l1s_raw = l1_loss(deconfounded_embeddings_reconstructions, deconfounded_embeddings)
- dev_raw = deconfounded_embeddings_reconstructions - deconfounded_embeddings
- all_l1s_raw = all_l1s_raw.detach().cpu().numpy()
- all_l1s = np.mean(all_l1s_raw, axis=1)
- df['l1_loss'] = all_l1s
- df['l1_loss_raw'] = list(all_l1s_raw)
- df['dev_loss_raw'] = list(dev_raw)
- # Build the confounder matrix (shape: [n_samples, 2])
- confounders_df = df[['Sex', 'Age when attended assessment centre | Instance 2']].copy()
- confounders_df['Sex'] = confounders_df['Sex'].map({1: 0, 2: 1}).astype(float)
- confounders_df = confounders_df.astype(float)
- confounder_values = confounders_df.values # shape: (n_samples, 2)
- # Load regression params
- with open("/opt/notebooks/linear_regression_params.pkl", "rb") as f:
- regression_params = pickle.load(f)
- coefficients = regression_params["coefficients"]
- intercepts = regression_params["intercepts"]
- dev_raw_np = dev_raw.detach().cpu().numpy() if isinstance(dev_raw, torch.Tensor) else dev_raw.copy()
- dev_raw_np = StandardScaler().fit_transform(dev_raw_np)
- predicted = confounder_values @ coefficients.T + intercepts
- residuals = dev_raw_np - predicted
- df["dev_loss_raw_resid"] = list(residuals)
- print(f"Final Data Count: {df.shape}")
- healthies = df[df['is_healthy'] == 1]
- healthies["label"] = 0
- print(f"Number of Healthies: {healthies.shape}")
- final_results = []
- diagnosis_to_test = [
- 'DEP_ICD10', 'depression_1_no_comor',
- 'depression_2_no_comor', 'depression_3_no_comor', 'depression_4_no_comor', 'AUD_ICD10',
- "diagnosis_audit_8_no_comor", "diagnosis_audit_9_no_comor", "diagnosis_audit_10_no_comor", "AD_ICD10", 'GAD_ICD10',
- "ad_2_no_comor", "ad_3_no_comor", "ad_4_no_comor",
- "ad_2_and_depression_2_comor", "ad_3_and_depression_3_comor", "ad_4_and_depression_4_comor", "depression_ad_audit_2",
- "depression_ad_audit_3", "depression_ad_audit_4", "depression_ad_aud_3",
- "depression_ad_aud_4", "depression_ad_aud_2", "depression_1", "depression_2", "depression_3", "depression_4",
- "ad_2", "ad_3", "ad_4"
- ]
- ad_base = [
- "ad_2_no_comor",
- "ad_3_no_comor",
- "ad_4_no_comor"
- ]
- comor_base = [
- "ad_2_and_depression_2_comor",
- "ad_3_and_depression_3_comor",
- "ad_4_and_depression_4_comor"
- ]
- depr_base = [
- 'depression_1_no_comor',
- 'depression_2_no_comor',
- 'depression_3_no_comor',
- 'depression_4_no_comor', 'self_rep_mdd'
- ]
- df["depression_ad_audit_2"] = (
- (((df["depression_2"] == 1) | (df["depression_3"] == 1) | (df["depression_4"] == 1)) |
- ((df["ad_2"] == 1) | (df["ad_3"] == 1) | (df["ad_4"] == 1))) &
- ((df["diagnosis_audit_8"] == 1) | (df["diagnosis_audit_9"] == 1) | (df["diagnosis_audit_10"] == 1))
- ).astype(int)
- df["depression_ad_audit_3"] = (
- (((df["depression_3"] == 1) | (df["depression_4"] == 1)) |
- ((df["ad_3"] == 1) | (df["ad_4"] == 1))) &
- ((df["diagnosis_audit_8"] == 1) | (df["diagnosis_audit_9"] == 1) | (df["diagnosis_audit_10"] == 1))
- ).astype(int)
- df["depression_ad_audit_4"] = (
- ((df["depression_4"] == 1) |
- (df["ad_4"] == 1)) &
- ((df["diagnosis_audit_8"] == 1) | (df["diagnosis_audit_9"] == 1) | (df["diagnosis_audit_10"] == 1))
- ).astype(int)
- df["depression_ad_aud_2"] = ((
- ((df["depression_2"] == 1) | (df["depression_3"] == 1) | (
- df["depression_4"] == 1)) |
- ((df["ad_2"] == 1) | (df["ad_3"] == 1) | (df["ad_4"] == 1))) &
- (df["AUD_ICD10"] == 1)
- ).astype(int)
- df["depression_ad_aud_3"] = (
- (((df["depression_3"] == 1) | (df["depression_4"] == 1)) |
- ((df["ad_3"] == 1) | (df["ad_4"] == 1))) &
- (df["AUD_ICD10"] == 1)
- ).astype(int)
- df["depression_ad_aud_4"] = (
- (df["depression_4"] == 1) |
- (df["ad_4"] == 1) &
- (df["AUD_ICD10"] == 1)
- ).astype(int)
- diagnosis_to_test.append("is_healthy")
- for col in diagnosis_to_test:
- subset = df[df[col] == 1]
- fraction_females = (subset['Sex'] == 2).mean()
- age_col = "Age when attended assessment centre | Instance 2"
- mean_age = subset[age_col].mean()
- std_age = subset[age_col].std()
- print(f"Diagnosis: {col}")
- print(f"Fraction of females: {fraction_females:.2%}")
- print(f"Mean age: {mean_age:.2f}, Std age: {std_age:.2f}\n")
- dev_dict = {}
- dev_dict_resid = {}
- dev_healthies = np.stack(healthies["dev_loss_raw"].to_list())
- mse_healthies = np.stack(healthies["l1_loss_raw"].to_list())
- for diagnosis in diagnosis_to_test:
- patients = df[
- (df[diagnosis] == 1)
- ]
- patients["label"] = 1
- dev_dict[diagnosis] = np.stack(patients["dev_loss_raw"].to_list())
- dev_dict_resid[diagnosis] = np.stack(patients["dev_loss_raw_resid"].to_list())
- dev_healthies = np.stack(healthies["dev_loss_raw_resid"].to_list())
- hc_embeds_combined = dev_healthies
- normative_centroid = np.mean(hc_embeds_combined, axis=0)
- hc_dev = hc_embeds_combined - normative_centroid
- color_list = plt.cm.tab10(np.linspace(0, 1, len(dev_dict)))
- deviation_vectors = {}
- scaling_factor = 5
- angles = []
- magnitudes = []
- deviation_vectors_256d = []
- deviation_vectors_256d_perc_25 = []
- deviation_vectors_256d_perc_75 = []
- std_angles = []
- std_magnitudes = []
- group_colors = []
- used_cols = []
- group_labels = {
- 'alkohol': ["diagnosis_audit_8_no_comor", "diagnosis_audit_9_no_comor", "diagnosis_audit_10_no_comor"],
- 'alkohol_custom': ["AUD_ICD10"],
- 'depression': ['depression_1_no_comor', 'depression_2_no_comor', 'depression_3_no_comor',
- 'depression_4_no_comor'],
- 'depression_custom': ["DEP_ICD10", "self_rep_mdd"],
- 'angst': ["ad_2_no_comor", "ad_3_no_comor", "ad_4_no_comor"],
- 'angst_icd10': ["AD_ICD10", "GAD_ICD10"],
- 'depression_and_angst': ["ad_2_and_depression_2_comor", "ad_3_and_depression_3_comor",
- "ad_4_and_depression_4_comor"],
- 'mdd_aud_gad': ["depression_ad_aud_2", "depression_ad_aud_3", "depression_ad_aud_4"]
- }
- general_mapping = {
- '1': 'mild',
- '2': 'moderate',
- '3': 'moderate severe',
- '4': 'severe'
- }
- with open("/opt/notebooks/pca_2d_projection_allpoints.pkl", "rb") as f:
- pca = pickle.load(f)
- residuals_2d = pca.transform(residuals)
- hc_mask = df["is_healthy"] == 1
- hc_embeds_2d = residuals_2d[hc_mask]
- patient_embeds_2d = residuals_2d[~hc_mask]
- hc_center, hc_width, hc_height, hc_angle = bootstrap_ci_ellipse(hc_embeds_2d)
- shift_vector = hc_center
- hc_embeds_2d -= shift_vector
- patient_embeds_2d -= shift_vector
- plot_groups = {
- 'alkohol': [
- 'diagnosis_audit_10_no_comor', 'diagnosis_audit_8_no_comor',
- 'diagnosis_audit_9_no_comor', 'AUD_ICD10'
- ],
- 'depression': [
- 'depression_1_no_comor', 'depression_2_no_comor',
- 'depression_3_no_comor', 'depression_4_no_comor', 'DEP_ICD10', 'self_rep_mdd'
- ],
- 'angst': [
- 'ad_4_no_comor', 'ad_3_no_comor', 'ad_2_no_comor', "AD_ICD10", "GAD_ICD10", "self_rep_anx"
- ],
- 'comor': [
- 'ad_2_and_depression_2_comor', 'ad_3_and_depression_3_comor',
- 'ad_4_and_depression_4_comor'
- ],
- 'aud_gad_mdd': [
- 'depression_ad_audit_2', 'depression_ad_audit_3',
- 'depression_ad_audit_4'
- ]
- }
- strict_diag_cols = [c for group in plot_groups.values() for c in group]
- group_colors_map = {
- "AUDIT-C": "blue",
- "GAD": "green",
- "PHQ-9": "red",
- "GAD+PHQ-9": "black",
- "GAD/PHQ-9+AUDIT-C": "purple"
- }
- fig, ax = plt.subplots(figsize=(12, 12), dpi=300)
- hc_center, hc_width, hc_height, hc_angle = bootstrap_ci_ellipse(hc_embeds_2d)
- hc_ellipse = Ellipse(xy=hc_center, width=hc_width, height=hc_height, angle=hc_angle,
- edgecolor='black', facecolor='black', linestyle='--', linewidth=1.5, alpha=0.05)
- ax.add_patch(hc_ellipse)
- line_styles = ['solid', 'dashed', 'dotted', 'dashdot']
- style_tracker = {c: 0 for c in list(group_colors_map.values()) + ["gray"]}
- for group in strict_diag_cols:
- mask = df[group] == 1
- if mask.sum() == 0: continue
- center, width, height, angle = bootstrap_ci_ellipse(residuals_2d[mask])
- color = assign_color_from_label(group)
- linestyle = line_styles[style_tracker[color] % len(line_styles)]
- style_tracker[color] += 1
- ellipse = Ellipse(xy=center, width=width, height=height, angle=angle,
- edgecolor=color, facecolor=color, alpha=0.05)
- ax.add_patch(ellipse)
- ax.arrow(
- 0, 0, center[0], center[1],
- head_width=0.05, head_length=0.05,
- fc=color, ec=color,
- linewidth=1.5
- )
- ax.add_patch(ellipse)
- ax.text(center[0], center[1], replace_terms(group), fontsize=20, color=color)
- aud_hull = encapsulate_meta_group(["blue"])
- internalizing_hull = encapsulate_meta_group(["red", "black"])
- gad_hull = encapsulate_meta_group(["green", "black"])
- mix_hull = encapsulate_meta_group(["purple"])
- for hull, color, label in [(mix_hull, "purple", ""), (aud_hull, "blue", "AUD meta"),
- (internalizing_hull, "red", "Internalizing meta"), (gad_hull, "green", "GAD")]:
- if hull:
- x, y = hull.exterior.xy
- ax.plot(x, y, linestyle=(0, (5, 5)), color=color, linewidth=2)
- legend_elements = [
- Patch(facecolor='blue', edgecolor='blue', label='AUDIT-C-only'),
- Patch(facecolor='green', edgecolor='green', label='GAD-7-only'),
- Patch(facecolor='red', edgecolor='red', label='PHQ-9-only'),
- Patch(facecolor='black', edgecolor='black', label='GAD-7 + PHQ-9'),
- Patch(facecolor='purple', edgecolor='purple', label='GAD-7/PHQ-9 + AUDIT-C'),
- Line2D([0], [0], linestyle='--', color='gray', label='HC median ellipse', linewidth=1.5)
- ]
- ax.legend(handles=legend_elements, loc='best', fontsize=18)
- ax.tick_params(axis='both', which='major', labelsize=18)
- ax.tick_params(axis='both', which='minor', labelsize=18)
- ax.axhline(0, color='black', linewidth=1)
- ax.axvline(0, color='black', linewidth=1)
- ax.set_xlabel("PC1", fontsize=18)
- ax.set_ylabel("PC2", fontsize=18)
- ax.set_xlim(-2, 2)
- ax.set_ylim(-3, 1.5)
- plt.tight_layout()
- plt.savefig("/opt/notebooks/arrow_plot_STRICT_2Dcentroids_validation.pdf", dpi=300)
- plt.savefig("/opt/notebooks/arrow_plot_STRICT_2Dcentroids_validation.png", dpi=300)
- plt.show()
directional_analysis.py at commit 11cd20b, under GPL-3.0 · at the source
Overview
and 15 other authors
Kerstin Ritter20,21,22, Annette Peters23,24,25, Tobias Pischon26,27,28, Stephanie Witt7,29, Johannes Nitsche1,2, Joonas Naamanka1,2,30, Sebastian Volkmer1,2, Antonia Mai1,2, Amrou Abas1,2, Xiuzhi Li1,2, Andreas Meyer-Lindenberg2, Tobias Gradinger1,2, Fabian Streit1,2,29, Urs Braun1,2, Emanuel Schwarz1,2,2930 affiliations
- Hector Institute for Artificial Intelligence in Psychiatry, Central Institute of Mental Health, Medical Faculty Mannheim, Heidelberg University,Mannheim, Germany
- Department of Psychiatry and Psychotherapy, Central Institute of Mental Health, Medical Faculty Mannheim, Heidelberg University,Mannheim, Germany
- Universität Leipzig, Institute for Medical Informatics, Statistics and Epidemiology,Leipzig, Germany
- Institute of Epidemiology and Social Medicine, University of Münster,Münster, Germany
- German Cancer Research Center (DKFZ),Heidelberg, Germany
- Network Aging Research, Heidelberg University,Heidelberg, Germany
- Department of Genetic Epidemiology in Psychiatry, Central Institute of Mental Health, Medical Faculty Mannheim, Heidelberg University,Mannheim, Germany
- Department of Psychiatry and Psychotherapy, University Medicine Greifswald,Greifswald, Germany
- Division of Cancer Epidemiology, German Cancer Research Center (DKFZ),Heidelberg, Germany
- Department of Epidemiology and Preventive Medicine, University of Regensburg,Regensburg, Germany
- Institute for Community Medicine, University Medicine Greifswald,Greifswald, Germany
- Institute for Medical Epidemiology, Biometrics, and Informatics, Medical Faculty of the Martin Luther University Halle-Wittenberg,Halle (Saale), Germany
- German Center for Mental Health (DZPG), partner site Halle-Jena-Magdeburg,Halle (Saale), Germany
- Institute of Medical Psychology and Medical Sociology, University Medical Center Schleswig-Holstein, Kiel University,Kiel, Germany
- Department of Child and Adolescent Psychiatry and Psychotherapy, Central Institute of Mental Health, Medical Faculty Mannheim, Heidelberg University,Mannheim, Germany
- Institute of Medical Psychology, Faculty of Medicine, Ludwig-Maximilians-Universität München,Munich, Germany
- Berlin Ultrahigh Field Facility (B.U.F.F.), Max Delbrueck Center for Molecular Medicine in the Helmholtz Association (MDC),Berlin, Germany
- Clinic for Rheumatology and Hiller Research Center, University Hospital, Heinrich-Heine-University Düsseldorf,Düsseldorf, Germany
- Institute of Social Medicine, Occupational Health and Public Health (ISAP), Leipzig University,Leipzig, Germany
- Department of Machine Learning, Hertie Institute for AI in Brain Health, University of Tübingen,Tübingen, Germany
- Department of Psychiatry and Neurosciences, Charité - Universitätsmedizin Berlin (corporate member of Freie Universität Berlin, Humboldt-Universität zu Berlin, and Berlin Institute of Health),Berlin, Germany
- Tübingen AI Center,Tübingen, Germany
- Institute of Epidemiology, Helmholtz Zentrum München - German Research Center for Environmental Health (GmbH),Neuherberg, Germany
- Chair of Epidemiology, Institute for Medical Information Processing, Biometry and Epidemiology, Medical Faculty, Ludwig-Maximilians-Universität München,Munich, Germany
- German Center for Mental Health (DZPG), partner site Munich,Munich, Germany
- Max Delbrück Center for Molecular Medicine in the Helmholtz Association (MDC), Molecular Epidemiology Research Group,Berlin, Germany
- Charité - Universitätsmedizin Berlin, corporate member of Freie Universität Berlin and Humboldt-Universität zu Berlin,Berlin, Germany
- Max Delbrück Center for Molecular Medicine in the Helmholtz Association (MDC), Biobank Technology Platform,Berlin, Germany
- German Center for Mental Health (DZPG), Partner Site Mannheim - Heidelberg - Ulm,Heidelberg, Germany
- SleepWell Research Program, Faculty of Medicine, University of Helsinki,Helsinki, Finland
Abstract
Structural brain alterations associated with depression and anxiety are subtle, heterogeneous, and difficult to characterize. We applied autoencoder-based normative modeling to contrastively learned structural MRI representations from two large population-based cohorts (German National Cohort, N ≈ 29,000; UK Biobank, N ≈ 25,000) to quantify individual deviations from normative brain structure across symptom dimensions of depression, anxiety, and, for contextualization, alcohol use.
Deviation magnitude increased with symptom severity for depressive and anxiety symptoms and was most pronounced in individuals with high alcohol use. Directional analyses revealed shared deviation patterns for depression and anxiety that were largely distinct from alcohol-related deviations, and these patterns generalized across cohorts. These affective-symptom-relate
Together, these findings indicate that affective symptoms are associated with reproducible, dimensional patterns of regional brain-structural deviation that extend beyond normative population variability, supporting transdiagnostic models of internalizing psychopathology.
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 15 matches between paragraphs and lines of code.
wiegertj/DeepNormativeModeling
11cd20b2a6f5a7d74601c9b677b2243e78b764f2, 9 October 2025Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
23 files
- image_preprocessing_NAKO
/ , Python, 54 linespreprocessing.py - moco/
augmented_dataset.py , Python, 38 lines - moco/
augmentor.py , Python, 116 lines - moco/
encoder.py , Python, 144 lines - moco/
training.py , Python, 273 lines - moco_ukb/
get_embeddings.py , Python, 351 lines - moco_ukb/
warp_to_cat12.sh , Shell, 26 lines - normative_modeling/
1_train_autoencoder.py , Python, 740 lines, 3 matches - normative_modeling/
2_1_significance_testing , Python, 112 lines, 2 matches.py - normative_modeling/
2_2_stability_testing.py , Python, 71 lines - normative_modeling/
2_3_mahalanobis_shift_an , Python, 105 lines, 1 matchalysis.py - normative_modeling/
2_4_shift_analysis.py , Python, 287 lines, 2 matches - normative_modeling/
3_1_directional_analysis , Python, 470 lines.py - normative_modeling/
3_2_cosine_similarity_te , Python, 147 linessting.py - normative_modeling/
4_1_correlation_analysis , Python, 223 lines.py - normative_modeling/
4_2_brain_region_associa , Python, 179 linestions.py - normative_modeling/
4_3_integrating_correlat , Python, 171 linesions.py - normative_modeling/
4_4_brain_region_importa , Python, 94 linesnce_plotting.py - ukb_validation/
classification.py , Python, 115 lines, 2 matches - ukb_validation/
directional_analysis.py , Python, 850 lines, 3 matches - ukb_validation/
genetic_and_shift.py , Python, 1,143 lines, 2 matches - LICENSE, License, 674 lines
- README.md, Text, 26 lines
Code availability
All analysis were conducted in Python (version 3.12). All analysis scripts and the utilized packages are available via GitHub 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;
- 21 scripts, each with its path and the digest of its content;
- 15 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
Datasets cited
- ukbiobank.ac.uk/
register-apply , at UK Biobank; found in “Data availability”
Data availability
Access to and use of NAKO data and biosamples can be obtained via the electronic application portal (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 2, 28 September 2026
- Publisher: n/a → Springer Nature
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 35 authors, 3 keywords, 14 MeSH terms, 1 funder, 52 references.
Cite
This paper
Wiegert, J., Marty-Lombardi, S., Oweda, J., Lenz, E., Ahnert, P., Berger, K., Brenner, H., Frank, J., Grabe, H. J., Greiser, K. H., Klinger-König, J., Karch, A., Leitzmann, M., Meinke-Franze, C., Mikolajczyk, R., Nees, F., Niendorf, T., Sander, O., Schmidt, C. O., . . . Schwarz, E. (2026). Breaking the norm: population-scale deviations of brain structure in depression and anxiety. Molecular psychiatry, 31(10), 6034-6045. https://
BibTeX
@article{wiegert2026brea
author = {Wiegert, Julius and Marty-Lombardi, Sebastián and Oweda, Jailan and Lenz, Esra and Ahnert, Peter and Berger, Klaus and Brenner, Hermann and Frank, Josef and Grabe, Hans J. and Greiser, Karin Halina and Klinger-König, Johanna and Karch, André and Leitzmann, Michael and Meinke-Franze, Claudia and Mikolajczyk, Rafael and Nees, Frauke and Niendorf, Thoralf and Sander, Oliver and Schmidt, Carsten Oliver and Riedel-Heller, Steffi G. and Ritter, Kerstin and Peters, Annette and Pischon, Tobias and Witt, Stephanie and Nitsche, Johannes and Naamanka, Joonas and Volkmer, Sebastian and Mai, Antonia and Abas, Amrou and Li, Xiuzhi and Meyer-Lindenberg, Andreas and Gradinger, Tobias and Streit, Fabian and Braun, Urs and Schwarz, Emanuel},
title = {{Breaking the norm: population-scale deviations of brain structure in depression and anxiety}},
journal = {Molecular psychiatry},
year = {2026},
month = jul,
volume = {31},
number = {10},
pages = {6034--6045},
publisher = {Springer Nature},
issn = {1359-4184},
doi = {10.1038/
url = {https://
pmid = {42471446},
pmcid = {PMC13569451}
}
RIS
TY - JOUR
AU - Wiegert, Julius
AU - Marty-Lombardi, Sebastián
AU - Oweda, Jailan
AU - Lenz, Esra
AU - Ahnert, Peter
AU - Berger, Klaus
AU - Brenner, Hermann
AU - Frank, Josef
AU - Grabe, Hans J.
AU - Greiser, Karin Halina
AU - Klinger-König, Johanna
AU - Karch, André
AU - Leitzmann, Michael
AU - Meinke-Franze, Claudia
AU - Mikolajczyk, Rafael
AU - Nees, Frauke
AU - Niendorf, Thoralf
AU - Sander, Oliver
AU - Schmidt, Carsten Oliver
AU - Riedel-Heller, Steffi G.
AU - Ritter, Kerstin
AU - Peters, Annette
AU - Pischon, Tobias
AU - Witt, Stephanie
AU - Nitsche, Johannes
AU - Naamanka, Joonas
AU - Volkmer, Sebastian
AU - Mai, Antonia
AU - Abas, Amrou
AU - Li, Xiuzhi
AU - Meyer-Lindenberg, Andreas
AU - Gradinger, Tobias
AU - Streit, Fabian
AU - Braun, Urs
AU - Schwarz, Emanuel
TI - Breaking the norm: population-scale deviations of brain structure in depression and anxiety
T2 - Molecular psychiatry
J2 - Mol Psychiatry
PY - 2026
DA - 2026/
VL - 31
IS - 10
SP - 6034
EP - 6045
SN - 1359-4184
PB - Springer Nature
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "Breaking the norm: population-scale deviations of brain structure in depression and anxiety",
"container-title": "Molecular psychiatry",
"author": [
{
"family": "Wiegert",
"given": "Julius"
},
{
"family": "Marty-Lombardi",
"given": "Sebastián"
},
{
"family": "Oweda",
"given": "Jailan"
},
{
"family": "Lenz",
"given": "Esra"
},
{
"family": "Ahnert",
"given": "Peter"
},
{
"family": "Berger",
"given": "Klaus"
},
{
"family": "Brenner",
"given": "Hermann"
},
{
"family": "Frank",
"given": "Josef"
},
{
"family": "Grabe",
"given": "Hans J."
},
{
"family": "Greiser",
"given": "Karin Halina"
},
{
"family": "Klinger-König",
"given": "Johanna"
},
{
"family": "Karch",
"given": "André"
},
{
"family": "Leitzmann",
"given": "Michael"
},
{
"family": "Meinke-Franze",
"given": "Claudia"
},
{
"family": "Mikolajczyk",
"given": "Rafael"
},
{
"family": "Nees",
"given": "Frauke"
},
{
"family": "Niendorf",
"given": "Thoralf"
},
{
"family": "Sander",
"given": "Oliver"
},
{
"family": "Schmidt",
"given": "Carsten Oliver"
},
{
"family": "Riedel-Heller",
"given": "Steffi G."
},
{
"family": "Ritter",
"given": "Kerstin"
},
{
"family": "Peters",
"given": "Annette"
},
{
"family": "Pischon",
"given": "Tobias"
},
{
"family": "Witt",
"given": "Stephanie"
},
{
"family": "Nitsche",
"given": "Johannes"
},
{
"family": "Naamanka",
"given": "Joonas"
},
{
"family": "Volkmer",
"given": "Sebastian"
},
{
"family": "Mai",
"given": "Antonia"
},
{
"family": "Abas",
"given": "Amrou"
},
{
"family": "Li",
"given": "Xiuzhi"
},
{
"family": "Meyer-Lindenberg",
"given": "Andreas"
},
{
"family": "Gradinger",
"given": "Tobias"
},
{
"family": "Streit",
"given": "Fabian"
},
{
"family": "Braun",
"given": "Urs"
},
{
"family": "Schwarz",
"given": "Emanuel"
}
],
"container-title-short":
"volume": "31",
"issue": "10",
"page": "6034-6045",
"DOI": "10.1038/
"PMID": "42471446",
"PMCID": "PMC13569451",
"ISSN": "1359-4184",
"publisher": "Springer Nature",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
18
]
]
}
}
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.1038/s41398-026-04131-1 [code]
- Multimodal phenotypic classification of generalized anxiety and panic using structural MRI data and psychosocial factors: machine learning results from the German National Cohort (NAKO) study.Journal: Translational psychiatryIn common: structural MRI / diffusion, clinical / translational, other condition, 2 references, 4 authors
- [2] doi:10.1162/imag.a.1269 [code]
- From early to contemporary normative modeling: Mapping individual differences in neurophysiological signals.Journal: Imaging neuroscience (Cambridge, Mass.)In common: Nilearn, NiBabel, statsmodels, 6 other tools, 9 references
- [3] doi:10.1038/s41398-026-04426-3 [code]
- Resting-state brain age is associated with cognitive and sensorimotor abnormalities in schizophrenia spectrum disorders: a validation &
amp; longitudinal study. Journal: Translational psychiatryIn common: NiBabel, statsmodels, pandas, 1 other tool, structural MRI / diffusion, 1 reference, 3 authors - [4] doi:10.1038/s41398-026-04025-2 [code]
- Brain energetic landscapes shape state dysregulation in major depressive disorder: a morphological network controllability perspective.Journal: Translational psychiatryIn common: FSL, Nilearn, NiBabel, 7 other tools, depression, 2 references
- [5] doi:10.1371/journal.pmed.1004809 [code]
- Brain morphology in Anorexia Nervosa and its subtypes: A multi-cohort study of individual participant data.Journal: PLoS medicineIn common: NiBabel, statsmodels, seaborn, 5 other tools, structural MRI / diffusion, other condition, 4 references
- [6] doi:10.64898/2026.08.13.26360304 [code]
- Lifespan brain structural variation reveals shared organization across mental health conditionsJournal: medRxiv (preprint)In common: Nilearn, NiBabel, statsmodels, 6 other tools, clinical / translational, 3 references
- [7] doi:10.1038/s41593-026-02359-0 [code]
- The cross-site reproducibility of MRI morphometric phenotypes in psychiatric disorders.Journal: Nature neuroscienceIn common: FSL, NiBabel, scikit-learn, 4 other tools, structural MRI / diffusion, 4 references
- [8] doi:10.1038/s43856-026-01722-3 [code]
- Local and global patterns support medical imaging as a biomarker of ageing.Journal: Communications medicineIn common: NiBabel, statsmodels, PyTorch, 5 other tools, clinical / translational, author Tobias Pischon
- [9] doi:10.1038/s44220-026-00680-y [code]
- The neuroimaging correlates of depression established across six large-scale population datasets.Journal: Nature. Mental healthIn common: statsmodels, seaborn, scikit-learn, 4 other tools, depression, 4 references
- [10] doi:10.1371/journal.pbio.3003856 [code]
- Aging and metabolism contribute separately to brain-body health.Journal: PLoS biologyIn common: FSL, Nilearn, NiBabel, 8 other tools, structural MRI / diffusion, clinical / translational
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, 21 scripts, and 15 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:5374298dcf91f9c4…
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.
