OSCR

Genetic architecture of white matter microstructure captured by unsupervised deep representation learning of fractional anisotropy maps.

Code ↔ Paper

22 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 22 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § Methods › Interpretation and application of UDIP-FA ↔ FA_GWAS_all.ipynb, lines 835–963 · score 0.87 · ROI mask, parametric permutation, randomly shuffling, Bonferroni correction, permuted, ROIs
  2. [2] § Result › UDIP-FA demonstrates significant associations with risk of brain disorders and cognitive functions ↔ FA_network_drug_analysis.R, lines 41–66 · score 0.86 · autism spectrum disorder, bipolar disorder, PPI network, co expression, multiple sclerosis, attention deficit
  3. [3] § Methods › UDIP for predicting disease status and its association with cognitive function ↔ FA_GWAS_all.ipynb, lines 4220–4254 · score 0.81 · fluid intelligence score, prospective memory, correctly identify matches, cognitive
  4. [4] § Methods › UDIP for predicting disease status and its association with cognitive function ↔ FA_GWAS_all.ipynb, lines 1423–1504 · score 0.80 · LightGBM, cross validation, balanced training, healthy, imbalance, stochastic
  5. [5] § Methods › Unsupervised deep neural network for representing FA maps ↔ Model/model.py, the whole file · a weak match · score 0.79 · encoder blocks, decoder blocks, latent space, PyTorch, linearly, dimensional
  6. [6] § Result › UDIP-FA exhibited greater heritability and identified more genetic loci than conventional WM phenotypes ↔ FA_all.R, lines 191–252 · score 0.75 · genomic locus, genomic loci, single variant, lead SNPs, JAGWAS, FUMA
  7. [7] § Result › UDIP-FA characterizes the population variability of WM fiber tracts ↔ FA_GWAS_all.ipynb, lines 185–324 · score 0.72 · dimensionality reduction, linear regression, UMAP, Bonferroni corrected, volume, R2
  8. [8] § Methods › Collection of molecular networks and drug interaction networks ↔ FA_network_drug_analysis.R, lines 301–373 · score 0.72 · Gene co expression, PPI networks, TRNs, drug, interaction, UFAGs
  9. [9] § Methods › Collection of molecular networks and drug interaction networks ↔ FA_network_drug_analysis.R, lines 222–280 · score 0.71 · anti neoplastic, Drug Gene, immunotherapies, filtering, interaction, network
  10. [10] § Methods › UDIP for predicting disease status and its association with cognitive function ↔ FA_GWAS_all.ipynb, lines 1385–1421 · score 0.68 · minority class, majority class, balanced training, disease
  11. [11] § Result › UDIP-FA captures biologically informative variation associated with age, sex, and brain disorders ↔ FA_GWAS_all.ipynb, lines 416–454 · score 0.67 · fold cross validation, age prediction, accuracy, MAE, SVR, curve
  12. [12] § Result › Comparison with previous GWAS studies ↔ FA_all.R, lines 306–339 · score 0.66 · q24.2, q31.31, lead SNPs, bone, overlapped, trait
  13. [13] § Result › Genetic pleiotropy of UFAGs and risk genes in various brain disorders ↔ FA_network_drug_analysis.R, lines 301–373 · score 0.64 · TRN networks, PPI network, co expression, UFAGs, interactions, genes
  14. [14] § Methods › Unsupervised deep neural network for representing FA maps ↔ Model/Train.py, lines 70–133 · score 0.62 · mixed ethnicities, Adam, GPUs, autoencoder, training, checkpoint
  15. [15] § Methods › Genetic data preprocessing and association analysis ↔ FA_GWAS_all.ipynb, lines 4452–4529 · score 0.62 · allele frequency, genotypes, imputation, ancestry, European, MRI
  16. [16] § Result › UDIP-FA captures biologically informative variation associated with age, sex, and brain disorders ↔ FA_network_drug_analysis.R, lines 222–280 · score 0.62 · Multiple sclerosis, seizure, cerebellar, epilepsy, neurological, Parkinson
  17. [17] § Result › UDIP-FA exhibited greater heritability and identified more genetic loci than conventional WM phenotypes ↔ FA_all.R, lines 191–252 · score 0.61 · genomic loci, single variants, lead SNPs, discovery, overlap, UDIP FA
  18. [18] § Methods › UDIP for predicting disease status and its association with cognitive function ↔ FA_GWAS_all.ipynb, lines 1423–1504 · score 0.59 · cross validation, imbalanced, stratified, stochastic, split, fold
  19. [19] § Methods › Genetic data preprocessing and association analysis ↔ FA_GWAS_all.ipynb, lines 4452–4529 · score 0.57 · genome wide, location, GCTA, cohorts, variants, discovery
  20. [20] § Result › UDIP-FA characterizes the population variability of WM fiber tracts ↔ FA_GWAS_all.ipynb, lines 416–454 · score 0.56 · cross validation folds, age prediction, MAE, curve, AUC, Pearson
  21. [21] § Methods › Prioritizing UFAGs and gene expression analysis ↔ FA_all.R, lines 472–496 · score 0.53 · mvGWAS, FA meta, FUMA, tissues, MAGMA, gene
  22. [22] § Result › UDIP-FA exhibited greater heritability and identified more genetic loci than conventional WM phenotypes ↔ FA_all.R, lines 363–405 · score 0.51 · body BMD, bone mineral, LD, meta, UDIP FA, SNP

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

Jupyter notebook · 4,529 lines · 163 KB · MIT · 10 matches

  1. # %% [markdown]
  2. # # FA-GWAS Analysis Notebook
  3. #
  4. # This notebook contains the comprehensive analysis pipeline for FA (Fractional Anisotropy) GWAS studies.
  5. #
  6. # ## Table of Contents
  7. # 1. [Data Loading and Preprocessing](#data-loading)
  8. # 2. [UDIP-FA Age and Sex Prediction](#udip-prediction)
  9. # 3. [PerD Result Visualization](#perd-visualization)
  10. # 4. [GWAS Analysis](#gwas-analysis)
  11. # 5. [PRS Analysis](#prs-analysis)
  12. # 6. [Results and Visualization](#results)
  13. # %%
  14. import pandas as pd
  15. import numpy as np
  16. import matplotlib.pyplot as plt
  17. import seaborn as sns
  18. import os
  19. import pickle
  20. import re
  21. from pathlib import Path
  22. from glob import glob
  23. from tqdm import tqdm
  24. # Statistical analysis
  25. import statsmodels.api as sm
  26. from scipy import stats
  27. from scipy.stats import rankdata
  28. from statsmodels.stats.multitest import multipletests
  29. import mpmath as mp
  30. mp.mp.dps = 100 # Increase precision for small p-values
  31. # Machine learning
  32. from sklearn.metrics import roc_auc_score, accuracy_score, precision_score, recall_score, f1_score
  33. from sklearn.linear_model import LinearRegression
  34. from sklearn.metrics import r2_score
  35. import lightgbm as lgb
  36. import umap
  37. # Neuroimaging
  38. from nilearn import datasets, plotting, image
  39. # Parallel processing
  40. from multiprocessing import Pool
  41. from joblib import Parallel, delayed
  42. from subprocess import check_output, STDOUT
  43. from itertools import zip_longest, combinations
  44. # Visualization
  45. import plotly.graph_objects as go
  46. import plotly.express as px
  47. from plotly.subplots import make_subplots
  48. from matplotlib.colors import LinearSegmentedColormap, to_hex, Normalize
  49. from matplotlib.cm import ScalarMappable
  50. # Configuration
  51. FA_figure_dir = '/data/xzhao14/FA_figures'
  52. # %% [markdown]
  53. # ## Data Loading and Preprocessing
  54. #
  55. # This section handles the loading and initial preprocessing of FA data and phenotype information.
  56. # %%
  57. import pandas as pd
  58. from sklearn.metrics import roc_auc_score, accuracy_score, precision_score, recall_score, f1_score
  59. import numpy as np
  60. import lightgbm as lgb
  61. import os
  62. from pathlib import Path
  63. import os, numpy as np
  64. import re
  65. import matplotlib.pyplot as plt
  66. from tqdm import tqdm
  67. from multiprocessing import Pool
  68. from subprocess import check_output, STDOUT
  69. from itertools import zip_longest
  70. from glob import glob
  71. import pickle
  72. from statsmodels.stats.multitest import multipletests
  73. FA_figure_dir='/data/xzhao14/FA_figures'
  74. # %%
  75. import pandas as pd
  76. import numpy as np
  77. import statsmodels.api as sm
  78. from sklearn.linear_model import LinearRegression
  79. from sklearn.metrics import r2_score
  80. from scipy.stats import rankdata
  81. import umap
  82. import matplotlib.pyplot as plt
  83. import mpmath as mp
  84. # Increase mpmath precision to capture extremely small p-values
  85. mp.mp.dps = 100
  86. def upper_f_pvalue(f_stat, df1, df2):
  87. """
  88. Compute P(F(df1, df2) > f_stat) via the regularized upper incomplete beta:
  89. P = I_z(a, b) from z to 1,
  90. where a = df1/2, b = df2/2, and z = (df1 * f_stat) / (df1 * f_stat + df2).
  91. """
  92. a = mp.mpf(df1) / 2
  93. b = mp.mpf(df2) / 2
  94. z = (mp.mpf(df1) * mp.mpf(f_stat)) / (mp.mpf(df1) * mp.mpf(f_stat) + mp.mpf(df2))
  95. return mp.betainc(a, b, z, 1, regularized=True)
  96. def analyze_udip_and_csf(udip_csv, csf_csv, output_csv, umap_fig):
  97. # --- load & merge ---
  98. udip = pd.read_csv(udip_csv)
  99. csf = pd.read_csv(csf_csv)
  100. udip.columns = udip.columns.astype(str)
  101. csf.columns = csf.columns.astype(str)
  102. merged = pd.merge(
  103. udip, csf,
  104. left_on=udip.columns[0],
  105. right_on=csf.columns[0]
  106. )
  107. # Prepare X and Y
  108. X = merged[udip.columns[2:]].values
  109. Y = merged[csf.columns[1:]].values
  110. m = Y.shape[1] # number of tests for Bonferroni
  111. results = []
  112. for i, region in enumerate(csf.columns[1:]):
  113. y = Y[:, i]
  114. # 1) Compute R²
  115. lr = LinearRegression().fit(X, y)
  116. r2 = r2_score(y, lr.predict(X))
  117. # 2) Fit OLS to get F-statistic
  118. X_sm = sm.add_constant(X)
  119. model = sm.OLS(y, X_sm).fit()
  120. f_stat = float(model.fvalue)
  121. df1, df2 = int(model.df_model), int(model.df_resid)
  122. # 3) Compute high-precision p-value
  123. p_mp = upper_f_pvalue(f_stat, df1, df2)
  124. print(p_mp)
  125. # 4) Format output: if p_mp is zero at this precision, report "<1e-<precision>"
  126. if p_mp == 0:
  127. raw_str = f"<1e-{mp.mp.dps}"
  128. bonf_str = raw_str
  129. else:
  130. raw_str = mp.nstr(p_mp, 20) # 20 significant digits
  131. bonf_str = mp.nstr(min(p_mp * m, mp.mpf(1)), 20)
  132. results.append({
  133. 'Brain Region': region,
  134. 'R2 Score': f"{r2:.4f}",
  135. 'Raw P Value': raw_str,
  136. 'Bonferroni P Value': bonf_str
  137. })
  138. # Save results
  139. pd.DataFrame(results).to_csv(output_csv, index=False)
  140. # --- UMAP plotting ---
  141. csf_mean = Y.mean(axis=1)
  142. csf_percent = rankdata(csf_mean) / len(csf_mean) * 100
  143. emb = umap.UMAP(n_components=2, random_state=42).fit_transform(X)
  144. fig, ax = plt.subplots(figsize=(10, 8))
  145. sc = ax.scatter(emb[:,0], emb[:,1], c=csf_percent, cmap='hot', s=2)
  146. cbar = plt.colorbar(sc, ax=ax, fraction=0.02, pad=0.04)
  147. cbar.set_label('CSF percentile', rotation=270, labelpad=15)
  148. cbar.set_ticks([0, 50, 100])
  149. ax.set_axis_off()
  150. plt.tight_layout()
  151. plt.savefig(umap_fig, dpi=300)
  152. plt.close()
  153. # example call
  154. analyze_udip_and_csf(
  155. udip_csv='/data/xzhao14/FA_all_phenotype.csv',
  156. csf_csv='/data/xzhao14/FA_value.csv',
  157. output_csv='/data/xzhao14/outputs/FA_r2_scores.csv',
  158. umap_fig='/data/xzhao14/outputs/udip_umap.pdf'
  159. )
  160. # %%
  161. import pandas as pd
  162. import numpy as np
  163. import matplotlib.pyplot as plt
  164. import umap
  165. from decimal import Decimal, getcontext
  166. from sklearn.preprocessing import StandardScaler
  167. from sklearn.linear_model import LinearRegression
  168. from sklearn.metrics import r2_score
  169. import statsmodels.api as sm
  170. import decimal
  171. from scipy.stats import f
  172. from scipy.stats import rankdata
  173. def analyze_udip_and_csf(udip_csv, csf_csv, output_r2_csv, umap_fig_path):
  174. """
  175. Perform multivariate regression between UDIP features and CSF volumes,
  176. compute R² scores and overall p-values, and visualize UMAP embedding
  177. colored by percentile-ranked mean CSF. Includes Bonferroni correction.
  178. Parameters:
  179. - udip_csv: str, path to the UDIP CSV file
  180. - csf_csv: str, path to the CSF percentile CSV file
  181. - output_r2_csv: str, path to save R² and p-values
  182. - umap_fig_path: str, path to save UMAP figure
  183. """
  184. # Load input CSVs
  185. udip_df = pd.read_csv(udip_csv)
  186. csf_df = pd.read_csv(csf_csv)
  187. # Ensure consistent column naming
  188. udip_df.columns = udip_df.columns.astype(str)
  189. csf_df.columns = csf_df.columns.astype(str)
  190. # Merge on subject ID (first column of each file)
  191. merged_df = pd.merge(udip_df, csf_df, left_on=udip_df.columns[0], right_on=csf_df.columns[0])
  192. print(merged_df.shape)
  193. # Extract matrices
  194. feature_cols = udip_df.columns[2:] # skip ID columns
  195. csf_cols = csf_df.columns[1:] # skip ID column
  196. X = merged_df[feature_cols].values
  197. Y = merged_df[csf_cols].values
  198. # Compute R² and p-value for each brain region
  199. getcontext().prec = 200 # set high precision for Decimal
  200. results = []
  201. raw_pvals = []
  202. for i, region in enumerate(csf_cols):
  203. y = Y[:, i]
  204. # R² from sklearn
  205. lr = LinearRegression()
  206. lr.fit(X, y)
  207. y_pred = lr.predict(X)
  208. r2 = r2_score(y, y_pred)
  209. # statsmodels fit
  210. X_sm = sm.add_constant(X)
  211. model = sm.OLS(y, X_sm).fit()
  212. # manually compute the right tail of the F distribution (i.e., p-value)
  213. f_stat = float(model.fvalue)
  214. df1 = int(model.df_model)
  215. df2 = int(model.df_resid)
  216. # compute p-value using scipy
  217. pval_float = f.sf(f_stat, df1, df2)
  218. # convert to Decimal to preserve tiny values
  219. pval_decimal = Decimal(str(pval_float))
  220. raw_pvals.append(pval_decimal)
  221. results.append({
  222. 'Brain Region': region,
  223. 'R2 Score': round(r2, 4),
  224. 'Raw P Value': pval_decimal
  225. })
  226. # Create DataFrame
  227. results_df = pd.DataFrame(results)
  228. # Bonferroni correction
  229. num_tests = len(results_df)
  230. results_df['Bonferroni P Value'] = results_df['Raw P Value'].apply(lambda p: min(p * num_tests, 1.0))
  231. # Format p-values and R² for clarity
  232. results_df['R2 Score'] = results_df['R2 Score'].apply(lambda x: f"{x:.4f}")
  233. #results_df['Raw P Value'] = results_df['Raw P Value'].apply(lambda x: format(x, '.15e'))
  234. #results_df['Bonferroni P Value'] = results_df['Bonferroni P Value'].apply(lambda x: format(x, '.15e'))
  235. getcontext().prec = 200
  236. results_df['Raw P Value'] = results_df['Raw P Value'].apply(lambda x: format(Decimal(str(x)), '.15e'))
  237. results_df['Bonferroni P Value'] = results_df['Bonferroni P Value'].apply(lambda x: format(Decimal(str(x)), '.15e'))
  238. # Save results to CSV
  239. results_df.to_csv(output_r2_csv, index=False)
  240. # Compute mean CSF and convert to percentile
  241. csf_mean = Y.mean(axis=1)
  242. csf_percentile = rankdata(csf_mean, method='average') / len(csf_mean) * 100
  243. # UMAP dimensionality reduction
  244. # Fit UMAP to reduce dimensionality to 2D
  245. # Fit UMAP to reduce high-dimensional data to 2D
  246. reducer = umap.UMAP(n_components=2, random_state=42)
  247. embedding = reducer.fit_transform(X) # X: your feature matrix
  248. # Create figure and axis
  249. fig, ax = plt.subplots(figsize=(10, 8))
  250. # Use 'hot' colormap: dark (low values) -> yellow/white (high values)
  251. sc = ax.scatter(embedding[:, 0], embedding[:, 1], c=csf_percentile, cmap='hot', s=2)
  252. # Add vertical colorbar
  253. cbar = plt.colorbar(sc, ax=ax, fraction=0.02, pad=0.04)
  254. cbar.set_label('Vol. of Mean FA values (percentile)', rotation=270, labelpad=15) # Label on the right
  255. cbar.ax.tick_params(labelsize=10)
  256. cbar.set_ticks([0, 50, 100]) # Customize tick marks
  257. # Remove axis lines and ticks
  258. ax.set_title('UMAP of UDIPs colored by CSF percentile', fontsize=14)
  259. ax.set_axis_off()
  260. # Optimize layout and save
  261. plt.tight_layout()
  262. plt.savefig(umap_fig_path, dpi=300)
  263. plt.show()
  264. plt.close()
  265. ### #
  266. analyze_udip_and_csf(
  267. udip_csv='/data/xzhao14/FA_all_phenotype.csv',
  268. csf_csv='/data/xzhao14/FA_value.csv',
  269. output_r2_csv='/data/xzhao14/outputs/FA_r2_scores.csv',
  270. umap_fig_path='/data/xzhao14/outputs/udip_umap.pdf'
  271. )
  272. # %%
  273. ## calculate the mean FA R2 and association between
  274. import pandas as pd
  275. import numpy as np
  276. FA_r2=pd.read_csv('/data/xzhao14/outputs/FA_r2_scores.csv')
  277. np.mean(FA_r2['R2 Score'])
  278. # %%
  279. ## visualization of the mean FA in different brain regions
  280. import pandas as pd
  281. import matplotlib.pyplot as plt
  282. # %% [markdown]
  283. # ### Using the UDIP-FA to predicted the age and sex
  284. # - using the feature matrix to predicted the age and sex
  285. # - using the cluster methods to cluster the 128 feature
  286. # - using five fold cross-validation strategy
  287. # %%
  288. import os
  289. import pandas as pd
  290. import numpy as np
  291. import matplotlib.pyplot as plt
  292. import seaborn as sns
  293. from sklearn.cluster import KMeans
  294. from sklearn.preprocessing import StandardScaler
  295. from sklearn.metrics import silhouette_score
  296. from sklearn.model_selection import train_test_split
  297. from sklearn.svm import SVR, SVC
  298. from sklearn.linear_model import ElasticNet
  299. from sklearn.metrics import mean_absolute_error, roc_auc_score, accuracy_score, classification_report, roc_curve
  300. from scipy.stats import pearsonr
  301. from sklearn.model_selection import KFold
  302. def cluster_features(X, max_clusters=10, visualize=True, random_state=42,save_dir="/data/xzhao14/outputs"):
  303. """
  304. Cluster the feature matrix and visualize the clustering results.
  305. Parameters:
  306. - X: np.array, shape (n_samples, n_features), the original feature matrix.
  307. - max_clusters: int, maximum number of clusters to test (used to determine optimal number of clusters).
  308. - visualize: bool, whether to visualize the clustering heatmap.
  309. - random_state: int, random seed for reproducibility.
  310. Returns:
  311. - labels: cluster labels, shape (n_features,).
  312. - best_k: optimal number of clusters.
  313. """
  314. # Transpose matrix to cluster features
  315. X_T = X.T
  316. # Standardize features
  317. scaler = StandardScaler()
  318. X_scaled = scaler.fit_transform(X_T)
  319. # Automatically determine the optimal number of clusters using silhouette score
  320. best_k = 2
  321. best_score = -1
  322. for k in range(2, max_clusters + 1):
  323. kmeans = KMeans(n_clusters=k, random_state=random_state)
  324. labels = kmeans.fit_predict(X_scaled)
  325. score = silhouette_score(X_scaled, labels)
  326. if score > best_score:
  327. best_score = score
  328. best_k = k
  329. # Perform final clustering with optimal number of clusters
  330. kmeans_final = KMeans(n_clusters=best_k, random_state=random_state)
  331. final_labels = kmeans_final.fit_predict(X_scaled)
  332. # Visualize clustering results
  333. if visualize:
  334. sorted_idx = np.argsort(final_labels)
  335. sorted_X = X_scaled[sorted_idx, :]
  336. sorted_labels = final_labels[sorted_idx]
  337. plt.figure(figsize=(10, 8))
  338. sns.heatmap(sorted_X, cmap='vlag', cbar=True, xticklabels=False, yticklabels=False)
  339. plt.title(f'Feature Clustering Heatmap (k={best_k})', fontsize=14)
  340. plt.xlabel('Samples', fontsize=12)
  341. plt.ylabel('Clustered Features', fontsize=12)
  342. plt.tight_layout()
  343. plt.show()
  344. #plt.savefig("feature_clustering_heatmap.pdf")
  345. plt.savefig(os.path.join(save_dir, "feature_clustering_heatmap.pdf"))
  346. plt.close()
  347. return final_labels, best_k
  348. def predict_age_gender(X, age, gender, n_splits=5, random_state=42, save_dir="/data/xzhao14/outputs"):
  349. """
  350. Perform regression (age) and classification (gender) prediction from feature matrix using 5-fold cross validation.
  351. Parameters:
  352. - X: np.array, shape (n_samples, n_features), feature matrix.
  353. - age: np.array, shape (n_samples,), target ages.
  354. - gender: np.array, shape (n_samples,), binary gender labels (0/1).
  355. - n_splits: int, number of cross validation folds.
  356. - random_state: int, random seed for reproducibility.
  357. - save_dir: str, directory to save output figures.
  358. Returns:
  359. - age_metrics: dict, containing MAE and Pearson r (mean and std) for SVR and ElasticNet.
  360. - gender_metrics: dict, containing Accuracy, AUC (mean and std), and classification report for SVC.
  361. """
  362. os.makedirs(save_dir, exist_ok=True)
  363. # Lists to store fold-wise metrics
  364. svr_mae_list, svr_pearson_list = [], []
  365. enet_mae_list, enet_pearson_list = [], []
  366. svc_auc_list, svc_acc_list = [], []
  367. # To aggregate predictions for plotting
  368. agg_age_true_svr, agg_age_pred_svr = [], []
  369. agg_age_true_enet, agg_age_pred_enet = [], []
  370. agg_gender_true, agg_gender_prob, agg_gender_pred = [], [], []
  371. # For ROC curve: store fpr and tpr for each fold
  372. svc_fpr_list, svc_tpr_list = [], []
  373. # Set up 5-fold cross validation
  374. kf = KFold(n_splits=n_splits, shuffle=True, random_state=random_state)
  375. for train_index, test_index in kf.split(X):
  376. # Split fold data
  377. X_train, X_test = X[train_index], X[test_index]
  378. age_train, age_test = age[train_index], age[test_index]
  379. gender_train, gender_test = gender[train_index], gender[test_index]
  380. # Standardize features using training data only
  381. scaler = StandardScaler()
  382. X_train_scaled = scaler.fit_transform(X_train)
  383. X_test_scaled = scaler.transform(X_test)
  384. # --- Age prediction using SVR ---
  385. svr = SVR()
  386. svr.fit(X_train_scaled, age_train)
  387. age_pred_svr = svr.predict(X_test_scaled)
  388. mae_svr = mean_absolute_error(age_test, age_pred_svr)
  389. pearson_r_svr, _ = pearsonr(age_test, age_pred_svr)
  390. svr_mae_list.append(mae_svr)
  391. svr_pearson_list.append(pearson_r_svr)
  392. agg_age_true_svr.extend(age_test)
  393. agg_age_pred_svr.extend(age_pred_svr)
  394. # --- Age prediction using ElasticNet ---
  395. enet = ElasticNet(random_state=random_state)
  396. enet.fit(X_train_scaled, age_train)
  397. age_pred_enet = enet.predict(X_test_scaled)
  398. mae_enet = mean_absolute_error(age_test, age_pred_enet)
  399. pearson_r_enet, _ = pearsonr(age_test, age_pred_enet)
  400. enet_mae_list.append(mae_enet)
  401. enet_pearson_list.append(pearson_r_enet)
  402. agg_age_true_enet.extend(age_test)
  403. agg_age_pred_enet.extend(age_pred_enet)
  404. # --- Gender prediction using SVC ---
  405. svc = SVC(probability=True, random_state=random_state)
  406. svc.fit(X_train_scaled, gender_train)
  407. gender_pred = svc.predict(X_test_scaled)
  408. gender_prob = svc.predict_proba(X_test_scaled)[:, 1]
  409. auc = roc_auc_score(gender_test, gender_prob)
  410. acc = accuracy_score(gender_test, gender_pred)
  411. svc_auc_list.append(auc)
  412. svc_acc_list.append(acc)
  413. agg_gender_true.extend(gender_test)
  414. agg_gender_prob.extend(gender_prob)
  415. agg_gender_pred.extend(gender_pred)
  416. # Compute ROC curve for this fold and store it
  417. fpr, tpr, _ = roc_curve(gender_test, gender_prob)
  418. svc_fpr_list.append(fpr)
  419. svc_tpr_list.append(tpr)
  420. # Compute mean and standard deviation for regression metrics
  421. svr_mae_mean, svr_mae_std = np.mean(svr_mae_list), np.std(svr_mae_list)
  422. svr_pearson_mean, svr_pearson_std = np.mean(svr_pearson_list), np.std(svr_pearson_list)
  423. enet_mae_mean, enet_mae_std = np.mean(enet_mae_list), np.std(enet_mae_list)
  424. enet_pearson_mean, enet_pearson_std = np.mean(enet_pearson_list), np.std(enet_pearson_list)
  425. # Compute mean and std for classification metrics
  426. svc_auc_mean, svc_auc_std = np.mean(svc_auc_list), np.std(svc_auc_list)
  427. svc_acc_mean, svc_acc_std = np.mean(svc_acc_list), np.std(svc_acc_list)
  428. # ----- Plotting Regression Results for SVR -----
  429. plt.figure(figsize=(6, 6))
  430. plt.scatter(agg_age_true_svr, agg_age_pred_svr, alpha=0.6, label='SVR Predictions')
  431. x_min, x_max = min(agg_age_true_svr), max(agg_age_true_svr)
  432. plt.plot([x_min, x_max], [x_min, x_max], 'r--', label='Ideal')
  433. plt.xlabel('True Age')
  434. plt.ylabel('Predicted Age')
  435. plt.title('SVR Age Prediction (5-Fold CV)')
  436. plt.legend()
  437. plt.grid(True)
  438. annotation_text = (f"MAE = {svr_mae_mean:.2f} ± {svr_mae_std:.2f}\n"
  439. f"Pearson r = {svr_pearson_mean:.2f} ± {svr_pearson_std:.2f}")
  440. plt.annotate(annotation_text, xy=(0.05, 0.95), xycoords='axes fraction',
  441. fontsize=10, verticalalignment='top')
  442. plt.tight_layout()
  443. plt.savefig(os.path.join(save_dir, "svr_age_prediction_cv.pdf"))
  444. plt.close()
  445. # ----- Plotting Regression Results for ElasticNet -----
  446. plt.figure(figsize=(6, 6))
  447. plt.scatter(agg_age_true_enet, agg_age_pred_enet, alpha=0.6, label='ElasticNet Predictions')
  448. x_min, x_max = min(agg_age_true_enet), max(agg_age_true_enet)
  449. plt.plot([x_min, x_max], [x_min, x_max], 'r--', label='Ideal')
  450. plt.xlabel('True Age')
  451. plt.ylabel('Predicted Age')
  452. plt.title('ElasticNet Age Prediction (5-Fold CV)')
  453. plt.legend()
  454. plt.grid(True)
  455. annotation_text = (f"MAE = {enet_mae_mean:.2f} ± {enet_mae_std:.2f}\n"
  456. f"Pearson r = {enet_pearson_mean:.2f} ± {enet_pearson_std:.2f}")
  457. plt.annotate(annotation_text, xy=(0.05, 0.95), xycoords='axes fraction',
  458. fontsize=10, verticalalignment='top')
  459. plt.tight_layout()
  460. plt.savefig(os.path.join(save_dir, "elasticnet_age_prediction_cv.pdf"))
  461. plt.close()
  462. # ----- Plotting Mean ROC Curve for SVC -----
  463. # Create a common FPR axis and interpolate each fold's TPR to it
  464. mean_fpr = np.linspace(0, 1, 100)
  465. tprs_interp = []
  466. for fpr, tpr in zip(svc_fpr_list, svc_tpr_list):
  467. interp_tpr = np.interp(mean_fpr, fpr, tpr)
  468. interp_tpr[0] = 0.0
  469. tprs_interp.append(interp_tpr)
  470. tprs_interp = np.array(tprs_interp)
  471. mean_tpr = tprs_interp.mean(axis=0)
  472. mean_tpr[-1] = 1.0
  473. std_tpr = tprs_interp.std(axis=0)
  474. plt.figure(figsize=(6, 6))
  475. plt.plot(mean_fpr, mean_tpr, label=f'Mean ROC (AUC = {svc_auc_mean:.2f} ± {svc_auc_std:.2f})', lw=2)
  476. plt.fill_between(mean_fpr, mean_tpr - std_tpr, mean_tpr + std_tpr, color='grey',
  477. alpha=0.3, label='± 1 std. dev.')
  478. plt.plot([0, 1], [0, 1], 'k--', label='Random')
  479. plt.xlabel('False Positive Rate')
  480. plt.ylabel('True Positive Rate')
  481. plt.title('Mean ROC Curve for Gender Classification (5-Fold CV)')
  482. plt.legend(loc="lower right")
  483. plt.grid(True)
  484. annotation_text = f"Accuracy = {svc_acc_mean:.2f} ± {svc_acc_std:.2f}"
  485. plt.annotate(annotation_text, xy=(0.05, 0.05), xycoords='axes fraction', fontsize=10)
  486. plt.tight_layout()
  487. plt.savefig(os.path.join(save_dir, "svc_gender_roc_cv.pdf"))
  488. plt.close()
  489. # Prepare metrics dictionaries
  490. age_metrics = {
  491. 'SVR': {'MAE_mean': svr_mae_mean, 'MAE_std': svr_mae_std,
  492. 'Pearson_r_mean': svr_pearson_mean, 'Pearson_r_std': svr_pearson_std},
  493. 'ElasticNet': {'MAE_mean': enet_mae_mean, 'MAE_std': enet_mae_std,
  494. 'Pearson_r_mean': enet_pearson_mean, 'Pearson_r_std': enet_pearson_std}
  495. }
  496. gender_metrics = {
  497. 'SVC': {'Accuracy_mean': svc_acc_mean, 'Accuracy_std': svc_acc_std,
  498. 'AUC_mean': svc_auc_mean, 'AUC_std': svc_auc_std,
  499. 'Classification_Report': classification_report(np.array(agg_gender_true),
  500. np.array(agg_gender_pred), output_dict=True)}
  501. }
  502. return age_metrics, gender_metrics
  503. # Example Usage:
  504. data_path='/data/xzhao14/FA_all_phenotype.csv'
  505. covar_file = pd.read_csv('/data/xzhao14/PRS_covar.csv',delim_whitespace=True)
  506. phenotype_file = pd.read_csv(data_path)
  507. common_iids = phenotype_file['IID'].isin(covar_file['IID'])
  508. sample_ids = phenotype_file.iloc[:, :2] # First two columns are IDs
  509. features = phenotype_file.iloc[:, 2:].values # Remaining columns are features
  510. labels, optimal_k = cluster_features(features, max_clusters=40, visualize=True,save_dir='/data/xzhao14/FA_age_prediction')
  511. # phenotype_file = phenotype_file[common_iids]
  512. # # reorder the phenotype files ##
  513. # covar_file = covar_file.set_index('IID')
  514. # phenotype_file = phenotype_file.set_index('IID')
  515. # covar_file = covar_file.loc[phenotype_file.index]
  516. # covar_file = covar_file.reset_index()
  517. # phenotype_file = phenotype_file.reset_index()
  518. # sex=covar_file['SEX'].values
  519. # age=covar_file['AGE'].values
  520. # features = phenotype_file.iloc[:, 2:].values
  521. # age_metrics, gender_metrics = predict_age_gender(features, age, sex,save_dir='/data/xzhao14/FA_age_prediction')
  522. # %% [markdown]
  523. # ## PerD result visualization
  524. # - For different tissue(include CSF,GM and WM)
  525. # - For different regions of WM
  526. # %%
  527. #!/usr/bin/env python
  528. # -*- coding: utf-8 -*-
  529. import numpy as np
  530. import nibabel as nib
  531. import pandas as pd
  532. from nilearn import datasets
  533. from nilearn import plotting
  534. def load_nifti(file_path):
  535. """Load a NIfTI file and return the nibabel image object"""
  536. return nib.load(file_path)
  537. def load_region_info(txt_file):
  538. """
  539. Load brain region info file, assuming it contains three columns:
  540. Column 1: numerical label in the segmentation template
  541. Column 2: abbreviated brain region name
  542. Column 3: full brain region name
  543. """
  544. df = pd.read_csv(txt_file, sep='\t', header=None, names=['value', 'abbr', 'full_name'], index_col=None)
  545. print(df)
  546. return df
  547. def compute_ks_statistic(tmap_data, seg_data, region_df, top_n_ratio=0.1):
  548. """
  549. Compute Kolmogorov-Smirnov (K-S) statistic for each brain region
  550. and return the results sorted by statistic value.
  551. """
  552. tmap_data = np.abs(tmap_data)
  553. # Retain only non-zero regions in mask
  554. masked_tmap = tmap_data[seg_data > 0]
  555. # Total number of voxels in the mask
  556. total_voxels = masked_tmap.size
  557. # Number of top_n voxels
  558. top_n = int(total_voxels * top_n_ratio)
  559. # Sort t-values in descending order and get top_n indices
  560. sorted_indices = np.argsort(-masked_tmap)
  561. top_n_indices = sorted_indices[:top_n]
  562. total_top_n_voxels = len(top_n_indices)
  563. ks_stats = {}
  564. # Iterate over each unique brain region
  565. unique_regions = np.unique(seg_data)
  566. for reg_val in unique_regions:
  567. if reg_val == 0:
  568. continue
  569. mask = seg_data == reg_val
  570. region_voxels = np.sum(mask)
  571. if region_voxels == 0:
  572. continue
  573. region_t_values = tmap_data[mask]
  574. region_top_n_voxels = np.sum(region_t_values >= masked_tmap[top_n_indices[-1]])
  575. ks_stat = (region_top_n_voxels / region_voxels) - (total_top_n_voxels / total_voxels)
  576. ks_stats[reg_val] = ks_stat
  577. # Match region names and sort
  578. ks_results = []
  579. for reg_val, ks_stat in ks_stats.items():
  580. row = region_df[region_df['value'] == reg_val]
  581. region_name = row.iloc[0]['abbr'] if not row.empty else f"Region {reg_val}"
  582. ks_results.append((reg_val, region_name, ks_stat))
  583. ks_results.sort(key=lambda x: x[2], reverse=True)
  584. return ks_results
  585. def generate_ks_nifti(ks_results, seg_data, seg_img):
  586. """
  587. Generate a new NIfTI file where voxel values in each region
  588. are set to the region's K-S statistic
  589. """
  590. ks_map = np.zeros_like(seg_data)
  591. for reg_val, _, ks_stat in ks_results:
  592. ks_map[seg_data == reg_val] = ks_stat
  593. return nib.Nifti1Image(ks_map, affine=seg_img.affine, header=seg_img.header)
  594. def visualize_top_ks_regions(ks_results, seg_data, seg_img):
  595. """
  596. Visualize the top 3 brain regions with the highest K-S statistics
  597. """
  598. top_3_regions = ks_results[:3]
  599. top_3_map = np.zeros_like(seg_data)
  600. for reg_val, _, ks_stat in top_3_regions:
  601. top_3_map[seg_data == reg_val] = ks_stat
  602. top_3_img = nib.Nifti1Image(top_3_map, affine=seg_img.affine, header=seg_img.header)
  603. # You can uncomment this to use a white matter atlas background
  604. # jhu_wm = datasets.fetch_atlas_jhu_dti_81()
  605. # wm_bg_img = nib.load(jhu_wm.maps)
  606. view = plotting.view_img(
  607. top_3_img,
  608. # bg_img=wm_bg_img,
  609. symmetric_cmap=False,
  610. vmin=0,
  611. draw_cross=False,
  612. cmap="coolwarm",
  613. black_bg=True
  614. # title="White Matter"
  615. )
  616. display(view)
  617. def save_ks_to_csv(ks_results, output_csv):
  618. """
  619. Save K-S statistics to CSV file
  620. """
  621. df = pd.DataFrame(ks_results, columns=["Region Value", "Region Name", "KS Statistic"])
  622. df.to_csv(output_csv, index=False)
  623. print(f"K-S statistics saved to: {output_csv}")
  624. def main(tmap_file, seg_file, region_txt, output_file, output_csv):
  625. # Load NIfTI files
  626. tmap_img = load_nifti(tmap_file)
  627. seg_img = load_nifti(seg_file)
  628. # Get data and use absolute t-values
  629. tmap_data = np.abs(tmap_img.get_fdata())
  630. seg_data = seg_img.get_fdata()
  631. # Load region info
  632. region_df = load_region_info(region_txt)
  633. # Compute and sort K-S statistics
  634. ks_results = compute_ks_statistic(tmap_data, seg_data, region_df, top_n_ratio=0.01)
  635. # Save results
  636. save_ks_to_csv(ks_results, output_csv)
  637. print("\nK-S statistics (sorted in descending order):")
  638. region_d = []
  639. ks_stat = []
  640. for _, region, ks_val in ks_results:
  641. significance = ""
  642. if ks_val >= 0.1:
  643. significance = "*** (highly significant)"
  644. elif ks_val >= 0.05:
  645. significance = "** (significant)"
  646. elif ks_val >= 0.02:
  647. significance = "* (possibly significant)"
  648. print(f"{region}: {ks_val:.4f} {significance}")
  649. # Uncomment the following if you want to save a K-S NIfTI map
  650. # ks_img = generate_ks_nifti(ks_results, seg_data, seg_img)
  651. # nib.save(ks_img, output_file)
  652. # print(f"K-S NIfTI map saved to: {output_file}")
  653. # Uncomment the following to visualize the top 3 regions
  654. # visualize_top_ks_regions(ks_results, seg_data, seg_img)
  655. # %%
  656. import nibabel as nib
  657. import h5py
  658. import numpy as np
  659. import os
  660. import pandas as pd
  661. from tqdm import tqdm
  662. import pickle
  663. import plotly.graph_objects as go
  664. from plotly.subplots import make_subplots
  665. from nilearn import plotting, image, datasets
  666. from IPython.display import display
  667. import nibabel as nib
  668. import numpy as np
  669. import plotly.graph_objects as go
  670. from difflib import SequenceMatcher
  671. import multiprocessing as mp
  672. from functools import partial
  673. from joblib import Parallel, delayed
  674. # read the templated
  675. seg_file='/data484_2/xzhao14/FA_atlas/WM_ICBM_WMPM_1mm.nii'
  676. endopheno='/data484_2/xzhao14/FA_perd'
  677. out_put_files='/data484_2/xzhao14/FA_perd/PerD_region/'
  678. csv_out='/data484_2/xzhao14/FA_perd/PerD_region/csv_pair/'
  679. region_txt='/data484_2/xzhao14/FA_atlas/LabelLookupTable.txt'
  680. endo_num=list(range(128))
  681. for i in endo_num:
  682. # i=10
  683. tem_nii_name='paired_ttest_T1_'+str(i)+'.nii.gz'
  684. tmap_file=os.path.join(endopheno,tem_nii_name)
  685. output_file=os.path.join(out_put_files,str(i)+'.nii.gz')
  686. output_csv=os.path.join(csv_out,str(i)+'.csv')
  687. main(tmap_file,seg_file,region_txt,output_file,output_csv)
  688. ###
  689. # %%
  690. import os
  691. import numpy as np
  692. import nibabel as nib
  693. import matplotlib.pyplot as plt
  694. import pandas as pd
  695. from nilearn import plotting
  696. from tqdm import tqdm
  697. def permutation_test_on_roi_csv(tmap_path, mask_path, output_csv_path, txt_file, n_perm=5000):
  698. """
  699. Perform a non-parametric permutation test on a t-map and ROI mask, apply Bonferroni correction
  700. to the resulting p-values, and save the ROI information (including the corrected p-values in
  701. scientific notation) into a CSV file. The text file should contain three columns:
  702. 'value' (mask value),
  703. 'abbr' (abbreviation), and
  704. 'full_name' (full name).
  705. Steps:
  706. 1. Load the t-map and mask NIfTI images and convert the t-map to absolute values.
  707. 2. Load ROI information from the text file and create a mapping from mask values to ROI abbreviations.
  708. 3. For each ROI in the mask, compute the observed statistic (mean absolute t-value).
  709. 4. Perform permutation testing by shuffling t-values within the mask to build a null distribution.
  710. 5. Calculate raw p-values for each ROI using the correction (r+1)/(n_perm+1), where r is the
  711. number of permutations with statistic >= observed statistic.
  712. 6. Apply Bonferroni correction: multiply each raw p-value by the number of tests and cap at 1.
  713. 7. Compile the ROI mask value, abbreviation, full name, observed mean, and corrected p-value
  714. (displayed in scientific notation) into a CSV file.
  715. Parameters:
  716. tmap_path: Path to the t-map NIfTI file (t-values will be converted to absolute values).
  717. mask_path: Path to the ROI mask NIfTI file (background should be 0, ROIs are labeled with non-zero integers).
  718. output_csv_path: Path to save the output CSV file.
  719. txt_file: Path to the text file containing ROI labels and names.
  720. n_perm: Number of permutations to perform (default 5000).
  721. Returns:
  722. results_df: A pandas DataFrame containing the ROI information and corrected p-values.
  723. p_values: A dictionary with ROI abbreviations as keys and their corresponding corrected p-values as values.
  724. df: The DataFrame read from the ROI text file.
  725. """
  726. # 1. Load NIfTI images and convert the t-map to absolute values
  727. tmap_img = nib.load(tmap_path)
  728. mask_img = nib.load(mask_path)
  729. tmap_data = np.abs(tmap_img.get_fdata())
  730. mask_data = mask_img.get_fdata()
  731. if tmap_data.shape != mask_data.shape:
  732. raise ValueError("t-map and mask dimensions do not match!")
  733. # 2. Load ROI names from the text file
  734. df = pd.read_csv(txt_file, sep='\t', header=None, names=['value', 'abbr', 'full_name'], index_col=None)
  735. print("Loaded ROI information from the text file:\n", df)
  736. # Create a mapping from mask value to ROI abbreviation
  737. roi_mapping = df.set_index('value')['abbr'].to_dict()
  738. # 3. Preprocessing: Flatten data and retrieve non-zero mask indices
  739. tmap_flat = tmap_data.flatten()
  740. mask_flat = mask_data.flatten()
  741. # Get indices of voxels within the mask (non-zero values)
  742. mask_indices = np.where(mask_flat != 0)[0]
  743. tvals_mask = tmap_flat[mask_indices]
  744. # Get all unique non-zero ROI labels (excluding background 0)
  745. roi_labels = np.unique(mask_flat[mask_flat != 0])
  746. print("Detected ROI labels from mask:", roi_labels)
  747. # 4. Compute the observed statistic (mean absolute t-value) for each ROI
  748. roi_indices_dict = {} # Dictionary to store indices for each ROI in the flattened array
  749. observed_stats = {} # Dictionary to store the observed statistic for each ROI
  750. for roi in roi_labels:
  751. roi_voxel_indices = np.where(mask_flat == roi)[0]
  752. roi_indices_dict[roi] = roi_voxel_indices
  753. observed_stats[roi] = np.median(tmap_flat[roi_voxel_indices])
  754. # 5. Perform permutation testing: Build the null distribution
  755. perm_stats = {roi: np.zeros(n_perm) for roi in roi_labels}
  756. print("Starting permutation testing with %d iterations..." % n_perm)
  757. for i in tqdm(range(n_perm)):
  758. # Randomly shuffle t-values within the mask
  759. permuted_tvals = np.random.permutation(tvals_mask)
  760. permuted_tmap_flat = tmap_flat.copy()
  761. permuted_tmap_flat[mask_indices] = permuted_tvals
  762. # Compute the statistic for each ROI using the permuted data
  763. for roi in roi_labels:
  764. roi_voxel_indices = roi_indices_dict[roi]
  765. perm_stats[roi][i] = np.mean(permuted_tmap_flat[roi_voxel_indices])
  766. # 6. Calculate raw p-values with correction and apply Bonferroni correction
  767. p_values = {}
  768. cor_p_value={}
  769. num_tests = len(roi_labels)
  770. for roi in roi_labels:
  771. obs = observed_stats[roi]
  772. perm_array = perm_stats[roi]
  773. # Use (r + 1) / (n_perm + 1) to avoid p == 0
  774. raw_p_val = (np.sum(perm_array >= obs) + 1) / (n_perm + 1)
  775. # Bonferroni correction: multiply by the number of tests and cap at 1
  776. corr_p_val = raw_p_val * num_tests
  777. if corr_p_val > 1:
  778. corr_p_val = 1.0
  779. # Use ROI abbreviation if available; otherwise, convert to string
  780. roi_key = roi_mapping.get(roi, str(roi))
  781. p_values[roi_key] = raw_p_val
  782. cor_p_value[roi_key]=corr_p_val
  783. # Display in scientific notation
  784. print("ROI %s (mask value %s): observed mean = %.4f, raw p = %.4f, corrected p = %s" %
  785. (roi_key, roi, obs, raw_p_val, f"{corr_p_val:.2e}"))
  786. # 7. Construct the results DataFrame and write to a CSV file with scientific notation for p-values
  787. result_list = []
  788. for roi in roi_labels:
  789. abbreviation = roi_mapping.get(roi, str(roi))
  790. # Retrieve the full name from the DataFrame; if not found, set as 'NA'
  791. full_name_series = df.loc[df['value'] == roi, 'full_name']
  792. full_name = full_name_series.iloc[0] if not full_name_series.empty else 'NA'
  793. obs_mean = observed_stats[roi]
  794. # p_value is already Bonferroni-corrected and formatted in scientific notation
  795. corr_p_val = cor_p_value[abbreviation]
  796. p_val = p_values[abbreviation]
  797. result_list.append({
  798. 'ROI_value': roi,
  799. 'abbr': abbreviation,
  800. 'full_name': full_name,
  801. 'observed_mean': obs_mean,
  802. 'p_value_raw':f"{p_val:.6e}",
  803. 'p_value': f"{corr_p_val:.6e}"
  804. })
  805. results_df = pd.DataFrame(result_list)
  806. results_df.to_csv(output_csv_path, index=False)
  807. print("P-value CSV file saved to:", output_csv_path)
  808. return results_df, p_values, df
  809. # Example usage: Adjust file paths as needed
  810. if __name__ == '__main__':
  811. root_dir = '/data484_2/xzhao14/FA_perd'
  812. out_dir = '/data484_2/xzhao14/FA_perd/sig_region_statistic'
  813. all_per_files = [f for f in os.listdir(root_dir) if f.endswith('.nii.gz')]
  814. for i in range(len(all_per_files)):
  815. tmap_path = os.path.join(root_dir, all_per_files[i])
  816. print("Processing:", tmap_path)
  817. out_name = str(i) + '_sigre.csv'
  818. mask_path = '/data484_2/xzhao14/FA_atlas/WM_ICBM_WMPM_1mm.nii'
  819. output_csv_path = os.path.join(out_dir, out_name)
  820. txt_file = '/data484_2/xzhao14/FA_atlas/LabelLookupTable.txt'
  821. results_df, p_values, roi_df = permutation_test_on_roi_csv(tmap_path, mask_path, output_csv_path, txt_file, n_perm=5000)
  822. # %%
  823. import os
  824. import pandas as pd
  825. import seaborn as sns
  826. from scipy.stats import mannwhitneyu
  827. import numpy as np
  828. from itertools import combinations
  829. import matplotlib.pyplot as plt
  830. def process_and_visualize_ks_data(folder_path, output_csv="merged_ks_statistics.csv"):
  831. """
  832. Process multiple CSV files containing KS statistics, merge them into one DataFrame,
  833. and generate both a heatmap and a boxplot with statistical annotations.
  834. Args:
  835. folder_path (str): Path to the directory of CSV files.
  836. output_csv (str): Filename for the merged CSV output.
  837. Returns:
  838. pd.DataFrame: The merged DataFrame of KS statistics.
  839. """
  840. # Verify that the folder exists
  841. if not os.path.exists(folder_path):
  842. raise FileNotFoundError(f"The folder '{folder_path}' does not exist.")
  843. # List all CSV files in the folder
  844. all_files = [
  845. os.path.join(folder_path, f)
  846. for f in os.listdir(folder_path)
  847. if f.endswith('.csv')
  848. ]
  849. if not all_files:
  850. raise ValueError(f"No CSV files found in '{folder_path}'.")
  851. # Read and label each CSV, then collect into a list
  852. all_data = []
  853. file_labels = []
  854. for file in all_files:
  855. df = pd.read_csv(file)
  856. label = 'UDIP-FA_' + os.path.splitext(os.path.basename(file))[0]
  857. df["File"] = label # Tag each row with its source file
  858. all_data.append(df)
  859. file_labels.append(label)
  860. # Concatenate all data into one DataFrame and save to CSV
  861. merged_data = pd.concat(all_data, ignore_index=True)
  862. merged_data.to_csv(output_csv, index=False)
  863. print(f"Merged data saved to: {output_csv}")
  864. # Pivot to create a heatmap data matrix: rows = files, cols = regions
  865. heatmap_data = merged_data.pivot_table(
  866. index="File", columns="Region Name", values="KS Statistic"
  867. )
  868. # Ensure rows follow the original file order
  869. heatmap_data = heatmap_data.reindex(file_labels)
  870. print(heatmap_data)
  871. # Draw the heatmap
  872. plt.figure(figsize=(
  873. max(10, len(heatmap_data.columns) * 2),
  874. max(10, len(heatmap_data.index) / 5)
  875. ))
  876. sns.heatmap(
  877. heatmap_data,
  878. cmap="coolwarm",
  879. annot=False,
  880. cbar=True,
  881. linewidths=0.1
  882. )
  883. plt.title("KS Statistic Distribution Heatmap", fontsize=16)
  884. plt.ylabel("Files", fontsize=12)
  885. plt.xlabel("Regions", fontsize=12)
  886. plt.xticks(rotation=45, ha='right', fontsize=10)
  887. plt.yticks(fontsize=8)
  888. plt.tight_layout()
  889. plt.savefig(
  890. os.path.join(FA_figure_dir, "region_heatmap_sig.pdf"),
  891. format="pdf",
  892. bbox_inches="tight"
  893. )
  894. plt.show()
  895. # Prepare for boxplot: clean style and custom palette
  896. sns.set(style="whitegrid", context="talk")
  897. nature_palette = ["#88CCEE", "#44AA99", "#117733"] # Extend if more groups
  898. plt.figure(figsize=(8, 6))
  899. ax = sns.boxplot(
  900. data=merged_data,
  901. x="Region Name",
  902. y="KS Statistic",
  903. palette=nature_palette,
  904. width=0.6,
  905. fliersize=4
  906. )
  907. plt.title("Boxplot of KS Statistic for CSF, GM, and WM", fontsize=16, fontweight='bold')
  908. plt.xlabel("Regions", fontsize=14)
  909. plt.ylabel("KS Statistic", fontsize=14)
  910. # Determine group order and y-axis limits for annotation
  911. group_order = [tick.get_text() for tick in ax.get_xticklabels()]
  912. y_max = merged_data.groupby("Region Name")["KS Statistic"].max()
  913. y_min = merged_data["KS Statistic"].min()
  914. y_range = merged_data["KS Statistic"].max() - y_min
  915. offset = y_range * 0.05 # Space above boxes for significance bars
  916. # Perform pairwise comparisons and annotate significance
  917. for idx, (i, j) in enumerate(combinations(range(len(group_order)), 2)):
  918. grp1 = group_order[i]
  919. grp2 = group_order[j]
  920. data1 = merged_data.loc[merged_data["Region Name"] == grp1, "KS Statistic"]
  921. data2 = merged_data.loc[merged_data["Region Name"] == grp2, "KS Statistic"]
  922. stat, p = mannwhitneyu(data1, data2, alternative='two-sided')
  923. # Decide significance marker
  924. if p < 0.001:
  925. marker = '***'
  926. elif p < 0.01:
  927. marker = '**'
  928. elif p < 0.05:
  929. marker = '*'
  930. else:
  931. marker = 'ns'
  932. # Compute height for the annotation line
  933. h = max(y_max[grp1], y_max[grp2]) + offset + idx * offset
  934. x1, x2 = i, j
  935. x_center = (x1 + x2) / 2.0
  936. # Draw the significance bar
  937. ax.plot(
  938. [x1, x1, x2, x2],
  939. [h - offset/2, h, h, h - offset/2],
  940. lw=1.5, c='k'
  941. )
  942. # Place the text marker
  943. ax.text(
  944. x_center, h,
  945. marker,
  946. ha='center',
  947. va='bottom',
  948. fontsize=14,
  949. color='k'
  950. )
  951. plt.tight_layout()
  952. plt.savefig(
  953. os.path.join(FA_figure_dir, "region_boxplot_sig.pdf"),
  954. format="pdf",
  955. bbox_inches="tight"
  956. )
  957. return merged_data
  958. # Example usage:
  959. folder_path = "/data484_2/xzhao14/FA_perd/GM_WM_seg/"
  960. merged_df = process_and_visualize_ks_data(folder_path)
  961. # %%
  962. import os
  963. import pickle
  964. import pandas as pd
  965. import numpy as np
  966. import lightgbm as lgb
  967. from sklearn.metrics import roc_auc_score, accuracy_score, precision_score, recall_score, f1_score
  968. from sklearn.model_selection import train_test_split
  969. def calculate_metrics(y_true, y_pred, y_prob):
  970. """
  971. Compute evaluation metrics for binary classification.
  972. Args:
  973. y_true (array-like): True binary labels (0/1).
  974. y_pred (array-like): Predicted binary labels (0/1).
  975. y_prob (array-like): Predicted probabilities for the positive class.
  976. Returns:
  977. dict: A dictionary containing AUC, accuracy, sensitivity, specificity,
  978. precision, Youden index, and F1 score.
  979. """
  980. sensitivity = recall_score(y_true, y_pred)
  981. # Compute specificity, guarding against division-by-zero
  982. specificity = (
  983. np.sum((y_true == 0) & (y_pred == 0)) / np.sum(y_true == 0)
  984. if np.sum(y_true == 0) > 0 else 0
  985. )
  986. precision = precision_score(y_true, y_pred)
  987. f1 = f1_score(y_true, y_pred)
  988. youden_index = sensitivity + specificity - 1
  989. auc = roc_auc_score(y_true, y_prob)
  990. accuracy = accuracy_score(y_true, y_pred)
  991. return {
  992. 'AUC': auc,
  993. 'Accuracy': accuracy,
  994. 'Sensitivity': sensitivity,
  995. 'Specificity': specificity,
  996. 'Precision': precision,
  997. 'Youden Index': youden_index,
  998. 'F1 Score': f1
  999. }
  1000. def classify_and_evaluate(csv_path, txt_path, n_iterations=10, balance_strategy='undersample', negative_ratio=3):
  1001. """
  1002. Load data, train LightGBM classifiers with specified balance strategy,
  1003. and compute evaluation metrics over multiple iterations.
  1004. Args:
  1005. csv_path (str): Path to the CSV file containing feature matrix and IDs.
  1006. txt_path (str): Path to the text file listing disease sample IDs (column 'eid').
  1007. n_iterations (int): Number of repeated train/test splits.
  1008. balance_strategy (str): Sampling strategy: 'undersample' or 'full_weighted'.
  1009. negative_ratio (int): Ratio of healthy to disease samples when undersampling.
  1010. Returns:
  1011. pd.DataFrame: Aggregated metrics over all iterations.
  1012. list: List of top-3 feature importance Series from each iteration.
  1013. """
  1014. # 1. Read the main data CSV
  1015. df = pd.read_csv(csv_path)
  1016. # Assume the first two columns are ID fields
  1017. df_ids = df.iloc[:, :2]
  1018. # Read the disease sample IDs from txt file (must contain 'eid' column)
  1019. disease_ids = pd.read_csv(txt_path)
  1020. disease_ids = set(disease_ids['eid'])
  1021. # Mark disease vs healthy samples
  1022. df['is_disease'] = df_ids.iloc[:, 0].isin(disease_ids).astype(int)
  1023. df_disease = df[df['is_disease'] == 1]
  1024. df_healthy = df[df['is_disease'] == 0]
  1025. print(f"Disease samples: {len(df_disease)}, Healthy samples: {len(df_healthy)}")
  1026. if df_disease.empty or df_healthy.empty:
  1027. raise ValueError("Not enough disease or healthy samples for classification.")
  1028. all_metrics = [] # store metrics for each iteration
  1029. top_features_list = [] # store top-3 features for each iteration
  1030. for i in range(n_iterations):
  1031. print(f"\nIteration {i + 1}/{n_iterations}")
  1032. # Balance the data according to the chosen strategy
  1033. if balance_strategy == 'undersample':
  1034. # Randomly sample healthy controls at specified ratio
  1035. sampled_healthy = df_healthy.sample(
  1036. n=len(df_disease) * negative_ratio, random_state=i
  1037. )
  1038. balanced_df = pd.concat([df_disease, sampled_healthy])
  1039. elif balance_strategy == 'full_weighted':
  1040. # Use all samples, applying class weights instead of undersampling
  1041. balanced_df = pd.concat([df_disease, df_healthy])
  1042. else:
  1043. raise ValueError("balance_strategy must be 'undersample' or 'full_weighted'")
  1044. # Define features (columns 3 to second-last) and label
  1045. X = balanced_df.iloc[:, 2:-1]
  1046. y = balanced_df['is_disease']
  1047. # 2. Split into train/test sets with stratification
  1048. X_train, X_test, y_train, y_test = train_test_split(
  1049. X, y, test_size=0.1, random_state=42, stratify=y
  1050. )
  1051. # 3. Train LightGBM with sampling weights or class weights
  1052. if balance_strategy == 'undersample':
  1053. # Assign higher weight to negative class in training
  1054. sample_weight = y_train.map({0: negative_ratio, 1: 1}).values
  1055. clf = lgb.LGBMClassifier(random_state=42, verbose=-1)
  1056. clf.fit(X_train, y_train, sample_weight=sample_weight)
  1057. else: # full_weighted
  1058. # Compute scale_pos_weight for LightGBM
  1059. pos = np.sum(y_train == 1)
  1060. neg = np.sum(y_train == 0)
  1061. scale_pos_weight = neg / pos if pos > 0 else 1.0
  1062. clf = lgb.LGBMClassifier(
  1063. random_state=42, verbose=-1, scale_pos_weight=scale_pos_weight
  1064. )
  1065. clf.fit(X_train, y_train)
  1066. # Extract and record top-3 features by importance
  1067. feature_importances = pd.Series(clf.feature_importances_, index=X_train.columns)
  1068. top3_features = feature_importances.sort_values(ascending=False).head(3)
  1069. print("Top 3 features in this iteration:")
  1070. print(top3_features)
  1071. top_features_list.append(top3_features)
  1072. # 4. Evaluate on test set (threshold 0.5)
  1073. y_prob = clf.predict_proba(X_test)[:, 1]
  1074. y_pred = (y_prob >= 0.5).astype(int)
  1075. fold_metrics = calculate_metrics(y_test.values, y_pred, y_prob)
  1076. print("Metrics:", fold_metrics)
  1077. all_metrics.append(fold_metrics)
  1078. # Aggregate metrics across iterations
  1079. aggregated_metrics = pd.DataFrame(all_metrics)
  1080. print("\nAggregated Metrics across iterations:")
  1081. print(aggregated_metrics.describe())
  1082. return aggregated_metrics, top_features_list
  1083. if __name__ == '__main__':
  1084. # Directory containing disease sample ID files
  1085. disease_sample_dir = '/data/xzhao14/UKB_sample_ICD10'
  1086. all_disease_files = os.listdir(disease_sample_dir)
  1087. csv_file = "/data/xzhao14/FA_all_phenotype.csv"
  1088. print("Disease files:", all_disease_files)
  1089. # Optionally skip the first file
  1090. all_disease_files = all_disease_files[1:]
  1091. res_all = []
  1092. top_features_all = {}
  1093. # Choose balancing strategy and ratio
  1094. balance_strategy = 'undersample' # or 'full_weighted'
  1095. negative_ratio = 2 # healthy-to-disease ratio when undersampling
  1096. for file_name in all_disease_files:
  1097. print(f"\nProcessing: {file_name}")
  1098. txt_file = os.path.join(disease_sample_dir, file_name)
  1099. try:
  1100. metrics, top_features = classify_and_evaluate(
  1101. csv_file,
  1102. txt_file,
  1103. n_iterations=10,
  1104. balance_strategy=balance_strategy,
  1105. negative_ratio=negative_ratio
  1106. )
  1107. res_all.append(metrics)
  1108. top_features_all[file_name] = top_features
  1109. except ValueError as e:
  1110. print(f"Error: {e}")
  1111. # (Optional) Save results to a pickle file
  1112. # output_pickle = "/data/xzhao14/FA_classification_results.pkl"
  1113. # with open(output_pickle, 'wb') as f:
  1114. # pickle.dump({'metrics': res_all, 'top_features': top_features_all}, f)
  1115. # print(f"Results saved to {output_pickle}")
  1116. # %% [markdown]
  1117. # ## UDIP-FA Age and Sex Prediction
  1118. #
  1119. # Using the UDIP-FA feature matrix to predict age and sex using clustering methods and five-fold cross-validation.
  1120. # %%
  1121. ## test
  1122. import numpy as np
  1123. import pandas as pd
  1124. import pandas as pd
  1125. import pandas as pd
  1126. df = pd.read_csv("/data/xzhao14/FA_all_phenotype.csv")
  1127. id_cols = df.iloc[:, :2]
  1128. id_cols.to_csv("/data484_2/xzhao14/FA_all_sample.txt", sep='\t', index=False)
  1129. for col in df.columns[2:]:
  1130. #combined = pd.concat([id_cols, df[[col]]], axis=1)
  1131. combined = pd.concat([id_cols, df[[col]].rename(columns={col: 'PHENO'})], axis=1)
  1132. combined.to_csv(f"/data484_2/xzhao14/FA_fea_all/Feature_{col}.txt", sep='\t', index=False)
  1133. # %%
  1134. df = pd.read_csv("/data/xzhao14/FA_all_phenotype.csv")
  1135. df.shape
  1136. # %%
  1137. import os
  1138. import pickle
  1139. import numpy as np
  1140. import pandas as pd
  1141. import lightgbm as lgb
  1142. from sklearn.model_selection import StratifiedKFold
  1143. from sklearn.metrics import roc_auc_score, accuracy_score, precision_score, recall_score, f1_score
  1144. def calculate_metrics(y_true, y_pred, y_prob):
  1145. """
  1146. Calculate evaluation metrics.
  1147. Parameters:
  1148. y_true: Ground truth labels.
  1149. y_pred: Predicted labels.
  1150. y_prob: Predicted probabilities for the positive class.
  1151. Returns:
  1152. A dictionary with AUC, Accuracy, Sensitivity, Specificity, Precision, Youden Index, and F1 Score.
  1153. """
  1154. sensitivity = recall_score(y_true, y_pred)
  1155. specificity = np.sum((y_true == 0) & (y_pred == 0)) / np.sum(y_true == 0) if np.sum(y_true == 0) > 0 else 0
  1156. precision = precision_score(y_true, y_pred)
  1157. f1 = f1_score(y_true, y_pred)
  1158. youden_index = sensitivity + specificity - 1
  1159. auc = roc_auc_score(y_true, y_prob)
  1160. accuracy = accuracy_score(y_true, y_pred)
  1161. return {
  1162. 'AUC': auc,
  1163. 'Accuracy': accuracy,
  1164. 'Sensitivity': sensitivity,
  1165. 'Specificity': specificity,
  1166. 'Precision': precision,
  1167. 'Youden Index': youden_index,
  1168. 'F1 Score': f1
  1169. }
  1170. def stochastic_downsample(train_df, minority_label=1, random_state=42):
  1171. """
  1172. Balance training data using the stochastic downsampling strategy.
  1173. Steps:
  1174. 1) Split the training DataFrame into minority and majority classes.
  1175. 2) Calculate the ratio: r = |B| / |A|, where B is the minority class and A is the majority.
  1176. 3) Randomly sample floor(r * |A|) samples from the majority class.
  1177. 4) Combine the downsampled majority samples with the full minority class and shuffle.
  1178. Parameters:
  1179. train_df: DataFrame containing features and a label column 'is_disease'.
  1180. minority_label: The label of the minority class (default is 1 for disease samples).
  1181. random_state: Random seed for reproducibility.
  1182. Returns:
  1183. A balanced DataFrame.
  1184. """
  1185. df_minority = train_df[train_df['is_disease'] == minority_label]
  1186. df_majority = train_df[train_df['is_disease'] != minority_label]
  1187. # Assume majority (A) and minority (B); if minority count is actually larger, swap them.
  1188. A = df_majority
  1189. B = df_minority
  1190. if len(df_minority) > len(df_majority):
  1191. A, B = B, A
  1192. # Calculate the downsampling ratio r
  1193. r = len(B) / len(A) if len(A) > 0 else 1.0
  1194. downsample_size = int(np.floor(r * len(A)))
  1195. if downsample_size < 1:
  1196. raise ValueError("The downsampled majority class has less than 1 sample. Data too imbalanced or training set too small.")
  1197. # Downsample A and combine with B
  1198. A_prime = A.sample(n=downsample_size, random_state=random_state)
  1199. balanced_df = pd.concat([A_prime, B]).sample(frac=1, random_state=random_state).reset_index(drop=True)
  1200. return balanced_df
  1201. def classify_with_stochastic_downsampling(csv_path, disease_file, n_splits=10, random_state=42):
  1202. """
  1203. For one disease file, use the stochastic downsampling strategy to balance samples,
  1204. then perform stratified 10-fold cross-validation to train and evaluate the model.
  1205. Parameters:
  1206. csv_path: Path to the CSV file containing all sample features.
  1207. Assumes the first two columns are IDs and remaining columns are features.
  1208. disease_file: Path to a disease sample file that contains a column named 'eid'.
  1209. n_splits: Number of cross-validation splits (default is 10).
  1210. random_state: Random seed for reproducibility.
  1211. Returns:
  1212. A tuple (metrics_df, top_features_list) where:
  1213. - metrics_df is a DataFrame with aggregated metrics across folds.
  1214. - top_features_list is a list of the top 3 feature importances from each fold.
  1215. """
  1216. # Read the full feature CSV
  1217. df = pd.read_csv(csv_path)
  1218. df_ids = df.iloc[:, :2] # The first two columns are assumed to be IDs
  1219. # Read disease sample IDs from the disease file (expects a column 'eid')
  1220. disease_ids = pd.read_csv(disease_file)['eid'].unique().tolist()
  1221. # Mark disease samples based on whether the first column of df_ids is in disease_ids
  1222. df['is_disease'] = df_ids.iloc[:, 0].isin(disease_ids).astype(int)
  1223. # Print the distribution of classes
  1224. num_disease = df['is_disease'].sum()
  1225. num_healthy = len(df) - num_disease
  1226. print(f"Processing disease file: {os.path.basename(disease_file)}")
  1227. print(f" Disease samples: {num_disease}, Healthy samples: {num_healthy}")
  1228. if num_disease == 0 or num_healthy == 0:
  1229. raise ValueError("Not enough disease or healthy samples to perform classification.")
  1230. # Construct feature matrix and labels.
  1231. # Assume features are from the third column until the last (which is 'is_disease').
  1232. X = df.iloc[:, 2:-1]
  1233. y = df['is_disease'].values
  1234. # Set up stratified K-Fold cross-validation.
  1235. skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=random_state)
  1236. all_metrics = []
  1237. top_features_list = []
  1238. fold_idx = 0
  1239. for train_index, test_index in skf.split(X, y):
  1240. fold_idx += 1
  1241. print(f"\nFold {fold_idx}/{n_splits}")
  1242. X_train, X_test = X.iloc[train_index], X.iloc[test_index]
  1243. y_train = y[train_index]
  1244. y_test = y[test_index]
  1245. # Combine training features and labels into a single DataFrame.
  1246. train_df = pd.concat([X_train, pd.Series(y_train, name='is_disease')], axis=1)
  1247. # Apply the stochastic downsampling to balance the training data.
  1248. train_balanced = stochastic_downsample(train_df, minority_label=1, random_state=random_state + fold_idx)
  1249. X_train_balanced = train_balanced.drop(columns=['is_disease'])
  1250. y_train_balanced = train_balanced['is_disease']
  1251. # Train the LightGBM model using the balanced training set.
  1252. clf = lgb.LGBMClassifier(random_state=random_state + fold_idx, verbose=-1)
  1253. clf.fit(X_train_balanced, y_train_balanced)
  1254. # Retrieve feature importance and record the top 3 features.
  1255. feature_importances = pd.Series(clf.feature_importances_, index=X_train_balanced.columns)
  1256. top3_features = feature_importances.sort_values(ascending=False).head(3)
  1257. print("Top 3 features in this fold:")
  1258. print(top3_features)
  1259. top_features_list.append(top3_features)
  1260. # Evaluate on the original (imbalanced) test set.
  1261. y_prob = clf.predict_proba(X_test)[:, 1]
  1262. y_pred = (y_prob >= 0.5).astype(int)
  1263. fold_metrics = calculate_metrics(y_test, y_pred, y_prob)
  1264. print("Fold metrics:", fold_metrics)
  1265. all_metrics.append(fold_metrics)
  1266. metrics_df = pd.DataFrame(all_metrics)
  1267. print("\nAggregated Metrics across folds:")
  1268. print(metrics_df.describe())
  1269. return metrics_df, top_features_list
  1270. # ---------------- Main Process ----------------
  1271. if __name__ == '__main__':
  1272. # Path to the feature CSV file (all samples and features)
  1273. csv_file = "/data/xzhao14/FA_all_phenotype.csv"
  1274. # Directory containing multiple disease sample CSV files
  1275. disease_sample_dir = '/data/xzhao14/UKB_sample_ICD10'
  1276. all_disease_files = os.listdir(disease_sample_dir)
  1277. all_disease_files =all_disease_files[1:]
  1278. print("Disease files:", all_disease_files)
  1279. # Dictionary to store the results for each disease file.
  1280. results = {}
  1281. for disease_filename in all_disease_files:
  1282. disease_file_path = os.path.join(disease_sample_dir, disease_filename)
  1283. try:
  1284. metrics, top_feats = classify_with_stochastic_downsampling(
  1285. csv_path=csv_file,
  1286. disease_file=disease_file_path,
  1287. n_splits=10,
  1288. random_state=42
  1289. )
  1290. results[disease_filename] = {
  1291. 'metrics': metrics,
  1292. 'top_features': top_feats
  1293. }
  1294. except Exception as e:
  1295. print(f"Error processing {disease_filename}: {e}")
  1296. # Optionally, you can save the results to a pickle file.
  1297. # output_pickle_file = "/data/xzhao14/FA_classification_results.pkl"
  1298. # with open(output_pickle_file, 'wb') as f:
  1299. # pickle.dump(results, f)
  1300. # print(f"\nResults saved to {output_pickle_file}")
  1301. # %%
  1302. ### Importance visualization ###
  1303. import nibabel as nib
  1304. import numpy as np
  1305. import nibabel as nib
  1306. import numpy as np
  1307. def filter_mask(input_nii_path, values_to_keep, output_nii_path):
  1308. """
  1309. Reads a NIfTI mask file, sets all voxel values that are not in the specified list to 0,
  1310. and then saves the modified image as a new NIfTI file.
  1311. Parameters:
  1312. -----------
  1313. input_nii_path : str
  1314. Path to the input NIfTI mask file (.nii or .nii.gz).
  1315. values_to_keep : list
  1316. List of voxel values to retain; all other voxel values will be set to 0.
  1317. output_nii_path : str
  1318. Path to save the output NIfTI file (.nii or .nii.gz).
  1319. """
  1320. # 1. Load the input NIfTI file
  1321. img = nib.load(input_nii_path)
  1322. data = img.get_fdata() # Get the image data as a float array
  1323. # 2. Create a boolean mask that is True for voxels whose values are in the allowed list.
  1324. # Using np.isin should work correctly for multiple values.
  1325. mask = np.isin(data, values_to_keep)
  1326. # Alternatively, if you experience issues with np.isin, you can build the mask with a loop:
  1327. # mask = np.zeros(data.shape, dtype=bool)
  1328. # for val in values_to_keep:
  1329. # mask |= (data == val)
  1330. # 3. Apply the mask to retain allowed values and set all other voxels to 0.
  1331. # Multiplying by the boolean mask preserves the original values where mask==True.
  1332. data_filtered = data * mask
  1333. # 4. Create a new NIfTI image using the filtered data (preserving the original affine and header)
  1334. new_img = nib.Nifti1Image(data_filtered, affine=img.affine, header=img.header)
  1335. nib.save(new_img, output_nii_path)
  1336. # Example usage:
  1337. mask_file = "/data484_2/xzhao14/FA_atlas/WM_ICBM_WMPM_1mm.nii"
  1338. #keep_values = [8, 48, 35]
  1339. keep_values = [45, 1, 5]
  1340. #keep_values = [5, 3, 35]
  1341. output_file = "/data484_2/xzhao14/FA_perd/fea_importance/MS.nii"
  1342. filter_mask(mask_file, keep_values, output_file)
  1343. # %%
  1344. ### Importance visualization ###
  1345. import nibabel as nib
  1346. import numpy as np
  1347. import pandas as pd
  1348. def assign_values_to_mask(input_nii_path, value_map, output_nii_path):
  1349. """
  1350. Reads a NIfTI mask file and assigns new values to specified voxel labels.
  1351. Parameters:
  1352. -----------
  1353. input_nii_path : str
  1354. Path to the input NIfTI mask file (.nii or .nii.gz).
  1355. value_map : dict
  1356. Dictionary mapping original voxel values to new values, e.g., {45: 0.12, 1: 0.18, 5: 0.21}.
  1357. output_nii_path : str
  1358. Path to save the modified NIfTI file.
  1359. """
  1360. # Load the input NIfTI file
  1361. img = nib.load(input_nii_path)
  1362. data = img.get_fdata()
  1363. # Create a new array with the same shape, initialized to 0
  1364. new_data = np.zeros_like(data)
  1365. # Assign new values to voxels based on the value_map
  1366. for original_value, new_value in value_map.items():
  1367. new_data[data == original_value] = new_value
  1368. # Save the modified image
  1369. new_img = nib.Nifti1Image(new_data, affine=img.affine, header=img.header)
  1370. nib.save(new_img, output_nii_path)
  1371. # Example usage
  1372. mask_file = "/data484_2/xzhao14/FA_atlas/WM_ICBM_WMPM_1mm.nii"
  1373. for i in range(128):
  1374. UDIP_dimmension_x = pd.read_csv('/data484_2/xzhao14/FA_perd/sig_region_statistic/'+str(i)+'_sigre.csv')
  1375. value_map = dict(zip(UDIP_dimmension_x['ROI_value'].astype(int), UDIP_dimmension_x['observed_mean'].round(2)))
  1376. output_file = "/data484_2/xzhao14/FA_perd/fea_importance/original_T_regions_WM/Dim_"+str(i)+".nii"
  1377. # keep_values = [8, 48, 35]
  1378. # #keep_values = [45, 1, 5]
  1379. # #keep_values = [5, 3, 35]
  1380. # # Define the mapping from label values to new values
  1381. # value_map = {
  1382. # 8: 0.20,
  1383. # 48: 0.13,
  1384. # 35: 0.08
  1385. #
  1386. assign_values_to_mask(mask_file, value_map, output_file)
  1387. # %%
  1388. import pandas as pd
  1389. UDIP_dimmension_1 = pd.read_csv('/data484_2/xzhao14/FA_perd/sig_region_statistic/0_sigre.csv')
  1390. value_map = dict(zip(UDIP_dimmension_1['ROI_value'].astype(int), UDIP_dimmension_1['observed_mean'].round(2)))
  1391. print(value_map)
  1392. # %%
  1393. UDIP_dimmension_1
  1394. # %%
  1395. import pandas as pd
  1396. import matplotlib.pyplot as plt
  1397. import numpy as np
  1398. def plot_manhattan(df, chr_col='CHR', pos_col='POS', pval_col='P',
  1399. genome_wide_line=5e-8, title='Manhattan Plot', figsize=(12, 6)):
  1400. """
  1401. Plot a Manhattan plot for GWAS summary statistics.
  1402. Parameters:
  1403. -----------
  1404. df : pandas.DataFrame
  1405. DataFrame containing at least chromosome, position, and p-value columns.
  1406. chr_col : str
  1407. Column name for chromosomes.
  1408. pos_col : str
  1409. Column name for base-pair positions.
  1410. pval_col : str
  1411. Column name for p-values.
  1412. genome_wide_line : float
  1413. Genome-wide significance threshold (default = 5e-8).
  1414. title : str
  1415. Plot title.
  1416. figsize : tuple
  1417. Size of the plot (width, height).
  1418. """
  1419. df = df.copy()
  1420. # Convert chromosomes to integers (handle X/Y if present)
  1421. df[chr_col] = df[chr_col].astype(str)
  1422. df[chr_col] = df[chr_col].str.replace('X', '23').str.replace('Y', '24')
  1423. df[chr_col] = df[chr_col].astype(int)
  1424. # Sort by chromosome and position
  1425. df = df.sort_values(by=[chr_col, pos_col])
  1426. # Add an index column for plotting on x-axis
  1427. df['ind'] = range(len(df))
  1428. df_grouped = df.groupby(chr_col)
  1429. # Initialize the plot
  1430. fig, ax = plt.subplots(figsize=figsize)
  1431. colors = ['#4daf4a', '#377eb8'] # Alternate green and blue
  1432. x_labels = []
  1433. x_labels_pos = []
  1434. # Loop through each chromosome and plot
  1435. for num, (chr_name, group) in enumerate(df_grouped):
  1436. group.plot(kind='scatter', x='ind', y=-np.log10(group[pval_col]),
  1437. color=colors[num % len(colors)], ax=ax, s=10, alpha=0.6)
  1438. x_labels.append(chr_name)
  1439. x_labels_pos.append((group['ind'].iloc[-1] + group['ind'].iloc[0]) // 2)
  1440. # Add genome-wide significance line
  1441. ax.axhline(-np.log10(genome_wide_line), color='red', linestyle='--', linewidth=1)
  1442. ax.text(df['ind'].max()*0.98, -np.log10(genome_wide_line)+0.1,
  1443. f'p={genome_wide_line:.0e}', color='red', ha='right')
  1444. # Customize x-axis
  1445. ax.set_xticks(x_labels_pos)
  1446. ax.set_xticklabels(x_labels, rotation=0, fontsize=9)
  1447. ax.set_xlabel('Chromosome')
  1448. ax.set_ylabel('-log10(P-value)')
  1449. ax.set_title(title)
  1450. ax.grid(False)
  1451. plt.tight_layout()
  1452. plt.show()
  1453. # %% [markdown]
  1454. # ## Plot the classification result #
  1455. # %%
  1456. ##
  1457. # load pickle file
  1458. disease_sample = '/data/xzhao14/UKB_sample_ICD10'
  1459. all_disease_files = os.listdir(disease_sample)
  1460. with open('/data/xzhao14/FA_classification_results1.pkl', "rb") as file: # "rb" open in binary read mode
  1461. data = pickle.load(file)
  1462. # print data
  1463. data_list=data
  1464. disorder_name=['PD','AD','DEP','MS','EPI','SCZ']
  1465. selected_indices = [1,2,3,4,5,6] # indices to select ( 0 )
  1466. data_list = [data_list[i] for i in selected_indices]
  1467. auc_means = [df["AUC"].mean() for df in data_list]
  1468. auc_stds = [df["AUC"].std() for df in data_list]
  1469. print(np.mean(auc_means))
  1470. print(np.std(auc_means))
  1471. # plot bar chart
  1472. x_labels = disorder_name# group labels
  1473. x_positions = np.arange(len(data_list)) # x-axis positions
  1474. plt.figure(figsize=(10, 6))
  1475. plt.bar(x_positions, auc_means, yerr=auc_stds, capsize=5, color='skyblue', edgecolor='black', alpha=0.7)
  1476. # add title and axis labels
  1477. #plt.title("Mean AUC with Standard Deviation", fontsize=16)
  1478. plt.xlabel("Brain disorders", fontsize=14)
  1479. plt.ylabel("AUC", fontsize=14)
  1480. plt.xticks(x_positions, x_labels, fontsize=12, rotation=45)
  1481. # show figure
  1482. plt.tight_layout()
  1483. output_path = FA_figure_dir+"/disorder_classification.jpg"
  1484. plt.savefig(output_path, format='jpg', dpi=300,bbox_inches='tight')
  1485. print(f"Plot saved as {output_path}")
  1486. plt.show()
  1487. # %% [markdown]
  1488. # ## PerD Result Visualization
  1489. #
  1490. # Visualization of PerD results for different tissues (CSF, GM, WM) and different regions.
  1491. # %%
  1492. with open('/data/xzhao14/FA_classification_results1.pkl', "rb") as file: # "rb" open in binary read mode
  1493. data = pickle.load(file)
  1494. mean_all=[]
  1495. for i in range(len(data)):
  1496. cc=data[i].describe()
  1497. print(cc)
  1498. #mean_all.append(cc[1])
  1499. # %%
  1500. ## genetic correlation between different FA endophenotypes
  1501. import os
  1502. import subprocess
  1503. from multiprocessing import Pool
  1504. def find_sumstats_files_in_subfolders(directory):
  1505. """
  1506. Find all .fastGWA.sumstats.gz files in the subfolders of the given directory.
  1507. Args:
  1508. directory (str): Path to the root directory.
  1509. Returns:
  1510. list: List of file paths with .fastGWA.sumstats.gz extension.
  1511. """
  1512. sumstats_files = []
  1513. # Iterate through the subdirectories
  1514. for subfolder in os.listdir(directory):
  1515. subfolder_path = os.path.join(directory, subfolder)
  1516. if os.path.isdir(subfolder_path): # Check if it is a subdirectory
  1517. for file in os.listdir(subfolder_path):
  1518. if file.endswith(".fastGWA.sumstats.gz"):
  1519. sumstats_files.append(os.path.join(subfolder_path, file))
  1520. return sumstats_files
  1521. def calculate_genetic_correlation(file1, file2, ldsc_path, ld_ref_dir, out_dir):
  1522. """
  1523. Calculate genetic correlation between two sumstats files using ldsc.
  1524. Args:
  1525. file1 (str): Path to the first .fastGWA.sumstats.gz file.
  1526. file2 (str): Path to the second .fastGWA.sumstats.gz file.
  1527. ldsc_path (str): Path to the ldsc.py script.
  1528. ld_ref_dir (str): Path to the LD reference directory.
  1529. out_dir (str): Path to the output directory.
  1530. Returns:
  1531. str: Path to the output file generated by ldsc.
  1532. """
  1533. output_file = os.path.join(out_dir, f"{os.path.basename(file1)}_vs_{os.path.basename(file2)}.rg")
  1534. cmd = [
  1535. "python",
  1536. ldsc_path,
  1537. "--rg", file1 + "," + file2,
  1538. "--ref-ld-chr", os.path.join(ld_ref_dir, "1000G_EUR_Phase3_baselineLD."),
  1539. "--w-ld-chr", os.path.join(ld_ref_dir, "weights."),
  1540. "--out", output_file
  1541. ]
  1542. subprocess.call(cmd)
  1543. return output_file
  1544. def process_file_pair(args):
  1545. """
  1546. Wrapper for parallel processing of genetic correlation calculation.
  1547. Args:
  1548. args (tuple): Arguments for the calculate_genetic_correlation function.
  1549. Returns:
  1550. str: Path to the output file generated by ldsc.
  1551. """
  1552. return calculate_genetic_correlation(*args)
  1553. def main(directory, ldsc_path, ld_ref_dir, out_dir, num_processes=4):
  1554. """
  1555. Main function to find sumstats files, calculate genetic correlations, and store results.
  1556. Args:
  1557. directory (str): Path to the root directory containing subfolders with sumstats files.
  1558. ldsc_path (str): Path to the ldsc.py script.
  1559. ld_ref_dir (str): Path to the LD reference directory.
  1560. out_dir (str): Path to the output directory.
  1561. num_processes (int): Number of parallel processes to use.
  1562. """
  1563. if not os.path.exists(out_dir):
  1564. os.makedirs(out_dir)
  1565. sumstats_files = find_sumstats_files_in_subfolders(directory)
  1566. if not sumstats_files:
  1567. print("No .fastGWA.sumstats.gz files found in the subfolders.")
  1568. return
  1569. # Prepare arguments for genetic correlation calculation
  1570. file_pairs = [
  1571. (file1, file2, ldsc_path, ld_ref_dir, out_dir)
  1572. for i, file1 in enumerate(sumstats_files)
  1573. for file2 in sumstats_files[i + 1:]
  1574. ]
  1575. # Use multiprocessing to calculate genetic correlations in parallel
  1576. pool = Pool(num_processes)
  1577. results = pool.map(process_file_pair, file_pairs)
  1578. pool.close()
  1579. pool.join()
  1580. print("Genetic correlation calculation completed. Results:")
  1581. for result in results:
  1582. print(result)
  1583. if __name__ == "__main__":
  1584. # Example usage
  1585. main(
  1586. directory="/data/xzhao14/GWAS_output", # Path to GWAS output directory
  1587. ldsc_path="/data484_2/xzhao14/POST_GWAS/tools/ldsc/ldsc.py", # Path to ldsc.py
  1588. ld_ref_dir="/data484_2/xzhao14/POST_GWAS/prepare_data/ldsc_files/eur_w_ld_chr/", # Path to LD reference directory
  1589. out_dir="output", # Path to store results
  1590. num_processes=10 # Number of parallel processes
  1591. )
  1592. # %%
  1593. ## plot the result ##
  1594. import os
  1595. import pandas as pd
  1596. import numpy as np
  1597. import matplotlib.pyplot as plt
  1598. #root_dir = "/data484_2/xzhao14/FA_meta/genetic_correlation_results/"
  1599. #
  1600. root_dir='/data484_2/xzhao14/FA_meta/genetic_correlation_T1_FA_results'
  1601. disorders = os.listdir(root_dir)
  1602. data = []
  1603. for i in disorders:
  1604. file_path = os.path.join(root_dir, i)
  1605. with open(file_path, 'r') as f:
  1606. all_lines = f.readlines()
  1607. if len(all_lines) < 4:
  1608. print(f"Warning: File {file_path} does not have enough lines.")
  1609. continue
  1610. last_line = all_lines[-4].strip() # Extract the fourth last line
  1611. data.append(last_line.split()) # Split the line into individual values and store in the list
  1612. # Define column names for the DataFrame
  1613. columns = ['P1', 'P2', 'rg', 'SE', 'Z', 'P', 'h2_obs', 'h2_obs_se', 'h2_int', 'h2_int_se', 'gcov_int', 'gcov_int_se']
  1614. df = pd.DataFrame(data, columns=columns)
  1615. df.replace("NA", np.nan, inplace=True)
  1616. df = df.dropna()
  1617. print(len(df))
  1618. # # # Convert 'rg' and 'SE' columns to float type
  1619. # df['rg'] = df['rg'].astype(float)
  1620. # df['SE'] = df['SE'].astype(float)
  1621. # df['P1'] = df['P1'].apply(lambda x: x.split('/')[-1].split('_')[0])
  1622. # df['P2'] = df['P2'].apply(lambda x: x.split('/')[-1].split('_')[0])
  1623. # df['P2'] = df['P2'].str.replace(r'^QT', 'EP', regex=True)
  1624. # # sort P2 by QT0–QT127
  1625. # df['P2'] = pd.Categorical(df['P2'], categories=[f"EP{i}" for i in range(128)], ordered=True)
  1626. # df = df.sort_values(by='P2') # sort by P2
  1627. # df.loc[(df['rg'] >= 1.2) & (df['rg'] <= 1.3), 'rg'] = 1.1
  1628. # df.loc[(df['rg'] >= 1.3) & (df['rg'] <= 1.4), 'rg'] = 1.2
  1629. # df.loc[df['rg'] > 1.5, 'rg'] = 1.3
  1630. # print(df['rg'].mean())
  1631. # print(df['rg'].std())
  1632. # df["FDR"] = multipletests(df["P"].astype(float), alpha=0.05, method='fdr_bh')[1]
  1633. # colors = ['orange' if fdr < 0.05 else 'blue' for fdr in df['FDR']]
  1634. # print(df['FDR'].mean())
  1635. # print(df['FDR'].std())
  1636. # # Generate the forest plot
  1637. # plt.figure(figsize=(10, 20))
  1638. # # Draw scatter points with error bars
  1639. # for i in range(len(df)):
  1640. # plt.errorbar(
  1641. # df['rg'].iloc[i], df['P2'].iloc[i],
  1642. # xerr=df['SE'].iloc[i], fmt='o',
  1643. # color=colors[i], ecolor='black', capsize=3
  1644. # )
  1645. # # Add legend
  1646. # handles = [
  1647. # plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='orange', markersize=8, label='FDR < 0.05'),
  1648. # plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='blue', markersize=8, label='FDR ≥ 0.05')
  1649. # ]
  1650. # plt.legend(handles=handles, title='Significance', loc='upper left', fontsize=10)
  1651. # # Add decorations
  1652. # plt.axvline(x=0, color='gray', linestyle='--', linewidth=1) # Add a vertical line at x=0
  1653. # plt.xlabel('Genetic Correlation (rg)', fontsize=12)
  1654. # plt.ylabel('Endophenotypes', fontsize=12)
  1655. # plt.title('Genetic Correlation of discovery and replication', fontsize=14)
  1656. # plt.gca().invert_yaxis() # Reverse Y-axis to align with table layout
  1657. # plt.grid(axis='x', linestyle='--', alpha=0.7)
  1658. # output_path = FA_figure_dir+"/genetic_correlation_discover_replications.pdf"
  1659. # plt.savefig(output_path, format='pdf', bbox_inches='tight')
  1660. # print(f"Plot saved as {output_path}")
  1661. # # Display the plot
  1662. # plt.tight_layout()
  1663. # plt.show()
  1664. # %%
  1665. print(np.abs(df['rg']).mean())
  1666. print(np.abs(df['rg']).std())
  1667. # %% [markdown]
  1668. # ## comparison of heritability between our and previous
  1669. # %% [markdown]
  1670. # # get the significant SNP using the minP strategy
  1671. # %%
  1672. ## using the single variants GWAS result to get the significant result ##
  1673. from pathlib import Path
  1674. import os, numpy as np
  1675. from tqdm import tqdm
  1676. from multiprocessing import Pool
  1677. from subprocess import check_output, STDOUT
  1678. from itertools import zip_longest
  1679. from glob import glob
  1680. def extract(x, exclusion, out_path):
  1681. # extract rows in the file x not contained in exclusion and save in out_path.
  1682. x = Path(x)
  1683. out_path = Path(out_path)
  1684. cmd = f"awk 'NR == FNR {{ excl[$1]; next }} !(FNR in excl)' {exclusion} {x} > {x.parent/out_path/(x.name.split('.')[0] + '_extracted')}"
  1685. os.system(cmd)
  1686. def extract_col(x, offset=0):
  1687. out = check_output(f"awk '{{print $(NF-{offset})}}' {x}", universal_newlines=True, shell=True, stderr=STDOUT)
  1688. return np.array(list(map(float, out.strip('\n').split('\n')[1:]))), x
  1689. def create_minP(glob_list, pcol=0, mode='min'):
  1690. # pcol is indexed from right to left, 0 means last col
  1691. if mode == 'min':
  1692. op = np.argmin
  1693. elif mode == 'max':
  1694. op = np.argmax
  1695. else:
  1696. raise Exception('not implemented')
  1697. batch = 50
  1698. for i in tqdm(range(0, len(glob_list), batch)):
  1699. with Pool(batch) as q:
  1700. result = q.starmap(extract_col, zip_longest(glob_list[i:i+batch], (), fillvalue=pcol))
  1701. pnew, fnew = list(zip(*result))
  1702. if i == 0:
  1703. pnew = np.vstack(pnew)
  1704. idx = op(pnew, 0)
  1705. f = np.array(fnew)[idx]
  1706. else:
  1707. pnew = list(pnew)
  1708. pnew.append(p)
  1709. pnew = np.vstack(pnew)
  1710. idx = op(pnew, 0)
  1711. mask = (idx != (pnew.shape[0]-1))
  1712. f[mask] = np.array(fnew)[idx[mask]]
  1713. p = pnew[idx, np.arange(pnew.shape[1])]
  1714. return p, f
  1715. ## Get the minP ##
  1716. p, f = create_minP(glob("/data484_2/xzhao14/FA_meta/QT*_FA.txt"), 0)
  1717. #### ###
  1718. # %%
  1719. ### built the GWAS file for the meta single variant gWAS result ###
  1720. # Prepare a GWAS summary statisitc file and updata the p value
  1721. import pandas as pd
  1722. single_variant_gwas=pd.read_csv('/data484_2/xzhao14/FA_meta/QT97_FA.txt',sep='\t')
  1723. single_variant_gwas['P']=p
  1724. # %%
  1725. single_variant_gwas.to_csv('/data/xzhao14/FA_meta.txt',sep='\t',index=False)
  1726. # %%
  1727. ## previous Heritability
  1728. import os
  1729. from glob import glob
  1730. import os
  1731. import re
  1732. import pandas as pd
  1733. def extract_heritability_and_pval(file_path):
  1734. """
  1735. Extract heritability and p-value from a given file.
  1736. """
  1737. with open(file_path, 'r') as f:
  1738. content = f.read()
  1739. # Define the regular expression to match heritability and p-value
  1740. heritability_pattern = r"Heritability\s*=\s*([\d\.]+)\s*\(Pval\s*=\s*([\d\.eE\-]+)\)"
  1741. # Search for the pattern in the content
  1742. match = re.search(heritability_pattern, content)
  1743. if match:
  1744. heritability = float(match.group(1)) # Extract the heritability value
  1745. p_value = float(match.group(2)) # Extract the p-value
  1746. return heritability, p_value
  1747. else:
  1748. return None, None
  1749. def process_directory(directory):
  1750. """
  1751. Process all files ending with 'results.log' in the given directory.
  1752. Extract heritability and p-value, and return a DataFrame.
  1753. """
  1754. # Prepare a list to store the results
  1755. results = []
  1756. # Walk through the directory to find all matching files
  1757. for root, dirs, files in os.walk(directory):
  1758. for file in files:
  1759. if file.endswith(".log"):
  1760. file_path = os.path.join(root, file)
  1761. heritability, p_value = extract_heritability_and_pval(file_path)
  1762. results.append({
  1763. 'File': file,
  1764. 'Heritability': heritability,
  1765. 'P-value': p_value
  1766. })
  1767. # Create a DataFrame from the results
  1768. df = pd.DataFrame(results)
  1769. return df
  1770. def get_gc_file(disorders,root_dir):
  1771. data = []
  1772. for i in disorders:
  1773. file_path = os.path.join(root_dir, i)
  1774. with open(file_path, 'r') as f:
  1775. all_lines = f.readlines()
  1776. if len(all_lines) < 4:
  1777. print(f"Warning: File {file_path} does not have enough lines.")
  1778. continue
  1779. last_line = all_lines[-4].strip() # Extract the fourth last line
  1780. data.append(last_line.split()) # Split the line into individual values and store in the list
  1781. # Define column names for the DataFrame
  1782. columns = ['P1', 'P2', 'rg', 'SE', 'Z', 'P', 'h2_obs', 'h2_obs_se', 'h2_int', 'h2_int_se', 'gcov_int', 'gcov_int_se']
  1783. df = pd.DataFrame(data, columns=columns)
  1784. df.replace("NA", np.nan, inplace=True)
  1785. df = df.dropna()
  1786. # Convert 'rg' and 'SE' columns to float type
  1787. df['rg'] = df['rg'].astype(float)
  1788. df['SE'] = df['SE'].astype(float)
  1789. df['h2_obs'] = df['h2_obs'].astype(float)
  1790. return df
  1791. def get_file_list(directory, pattern):
  1792. """
  1793. Get a list of files matching the pattern from the specified directory.
  1794. Args:
  1795. directory (str): The directory to search.
  1796. pattern (str): The file name pattern to match.
  1797. Returns:
  1798. list: A list of file paths matching the pattern.
  1799. """
  1800. # Construct the full search path
  1801. search_path = os.path.join(directory, pattern)
  1802. # Use glob to find all matching files
  1803. file_list = glob(search_path)
  1804. return file_list
  1805. # # Example usage
  1806. root_dir = "/data/xzhao14/FA_previous_gc/" # Replace with your directory path
  1807. pattern = "QT127_results_ukb_phase1to3_fapcs_dec21_2019_pheno*_rg_results.log"
  1808. # # Get the file list
  1809. files = get_file_list(root_dir, pattern)
  1810. previous_gc=get_gc_file(files,root_dir)
  1811. # previous_h2_files=pd.read_csv('/data/xzhao14/fa_pc_h2.txt')
  1812. # previous_h2=previous_h2_files['h2']
  1813. # # get our now result #
  1814. # #h2_directory = "/data484_2/xzhao14/FA_statis" # Replace with your directory path
  1815. # h2_directory = "//data484_2/xzhao14/FA_fea_all/GWAS" # Replace with your directory path#
  1816. # h2_fa = process_directory(h2_directory)
  1817. # our_h2=h2_fa['Heritability']
  1818. # h2_fa["FDR"] = multipletests(h2_fa["P-value"].astype(float), alpha=0.05, method='fdr_bh')[1]
  1819. # ### plot the box plot #
  1820. # # "_results.log"
  1821. # h2_fa['File'] = h2_fa['File'].str.replace("_results.log", "", regex=False)
  1822. # # "QT" "EP"
  1823. # h2_fa['File'] = h2_fa['File'].str.replace("QT", "UDIP", regex=False)
  1824. # h2_fa['File_number'] = h2_fa['File'].str.extract(r'UDIP(\d+)', expand=False).astype(int)
  1825. # h2_fa = h2_fa.sort_values(by='File_number').drop(columns=['File_number'])
  1826. # #h2_fa.to_csv(FA_figure_dir+'/FA_h2.csv', index=False)
  1827. # %%
  1828. import pandas as pd
  1829. import numpy as np
  1830. FA_heritability_all=pd.read_csv('/data/xzhao14/FA_figures/FA_heritability.csv')
  1831. heri_value=FA_heritability_all['Heritability'].values
  1832. np.save('/data484_2/xzhao14/heritability.npy', heri_value)
  1833. # %%
  1834. dd=np.load('/data484_2/xzhao14/heritability.npy')
  1835. dd
  1836. # %% [markdown]
  1837. # %%
  1838. # Plot the heritability distribution
  1839. # Set figure size and style
  1840. plt.figure(figsize=(10, 6))
  1841. heritability_values = h2_fa['Heritability']
  1842. # Draw a histogram of heritability values
  1843. plt.hist(heritability_values, bins=20, color='skyblue', edgecolor='black', alpha=0.7)
  1844. # Compute mean heritability and define a threshold for "high heritability"
  1845. mean_heritability = np.mean(heritability_values)
  1846. threshold = 0.6 # threshold to mark high heritability
  1847. # Add vertical lines for the mean and the threshold
  1848. plt.axvline(mean_heritability, color='red', linestyle='--', linewidth=2,
  1849. label=f'Mean: {mean_heritability:.2f}')
  1850. plt.axvline(threshold, color='green', linestyle='--', linewidth=2,
  1851. label=f'Threshold: {threshold}')
  1852. # Set axis labels (and optionally a title)
  1853. # plt.title('Distribution of Heritability Across Phenotypes', fontsize=16)
  1854. plt.xlabel('SNP Heritability', fontsize=16)
  1855. plt.ylabel('Frequency', fontsize=16)
  1856. # Add a legend
  1857. plt.legend(fontsize=14)
  1858. # Annotate the number of high heritability values
  1859. high_heritability_count = sum(heritability_values > threshold)
  1860. plt.text(threshold + 0.02, 5,
  1861. f'High heritability: {high_heritability_count}',
  1862. color='green', fontsize=12)
  1863. # Improve layout and tick label sizes
  1864. plt.xticks(fontsize=16)
  1865. plt.yticks(fontsize=16)
  1866. plt.tight_layout()
  1867. # Save the figure as a PDF
  1868. output_path = FA_figure_dir + "/h2_FA.pdf"
  1869. plt.savefig(output_path, format='pdf', bbox_inches='tight')
  1870. print(f"Plot saved as {output_path}")
  1871. # Display the plot
  1872. plt.show()
  1873. # %%
  1874. import pandas as pd
  1875. import numpy as np
  1876. import seaborn as sns
  1877. import matplotlib.pyplot as plt
  1878. from sklearn.preprocessing import StandardScaler
  1879. # 1. Read the first CSV file and compute the upper triangle of the correlation matrix
  1880. file1 = '/data/xzhao14/discovery_dti_128_pheno.csv' # Replace with actual file path
  1881. df1 = pd.read_csv(file1)
  1882. # Extract columns QT0 to QT127
  1883. qt_columns = [f'QT{i}' for i in range(128)]
  1884. qt_data = df1[qt_columns]
  1885. # Compute the correlation matrix among the QT features
  1886. correlation_matrix = qt_data.corr()
  1887. # Extract the upper triangular part (excluding the diagonal)
  1888. upper_triangle = np.triu(correlation_matrix, k=1)
  1889. # 2. Read the second CSV file and fill in the lower triangle with genetic correlations
  1890. file2 = '/data/xzhao14/genetic_cor.csv' # Replace with actual file path
  1891. df2 = pd.read_csv(file2)
  1892. # Initialize a 128×128 zero matrix for the lower triangle
  1893. lower_triangle = np.zeros_like(correlation_matrix, dtype=float)
  1894. # Populate the lower triangle entries with 'rg' values from df2
  1895. for _, row in df2.iterrows():
  1896. qt1_name = row['P1'] # e.g. 'QT0'
  1897. qt2_name = row['P2'] # e.g. 'QT1'
  1898. if qt1_name.startswith('QT') and qt2_name.startswith('QT'):
  1899. try:
  1900. qt1_idx = int(qt1_name[2:]) # Convert 'QT#' to numeric index
  1901. qt2_idx = int(qt2_name[2:])
  1902. lower_triangle[qt1_idx, qt2_idx] = row['rg']
  1903. lower_triangle[qt2_idx, qt1_idx] = row['rg'] # Symmetric fill
  1904. except ValueError as e:
  1905. print(f"Skipping invalid QT pair: {qt1_name}, {qt2_name}. Error: {e}")
  1906. else:
  1907. print(f"Skipping invalid QT names: {qt1_name}, {qt2_name}")
  1908. # Retain only the lower triangle (below the diagonal)
  1909. lower_triangle = np.tril(lower_triangle)
  1910. # 3. Extract non-zero upper-triangle values and standardize them
  1911. upper_triangle_values = upper_triangle[upper_triangle != 0]
  1912. scaler = StandardScaler()
  1913. upper_standardized = scaler.fit_transform(upper_triangle_values.reshape(-1, 1)).flatten()
  1914. # Rebuild the standardized upper-triangle matrix
  1915. upper_triangle_normalized = np.zeros_like(correlation_matrix)
  1916. upper_triangle_normalized[np.triu_indices_from(upper_triangle, k=1)] = upper_standardized
  1917. # 4. Extract non-zero lower-triangle values and standardize them
  1918. lower_triangle_values = lower_triangle[lower_triangle != 0]
  1919. lower_standardized = scaler.fit_transform(lower_triangle_values.reshape(-1, 1)).flatten()
  1920. # Rebuild the standardized lower-triangle matrix
  1921. lower_triangle_normalized = np.zeros_like(correlation_matrix)
  1922. lower_triangle_normalized[np.tril_indices_from(lower_triangle, k=-1)] = lower_standardized
  1923. # 5. Combine raw upper and lower triangles into a full matrix
  1924. full_matrix = upper_triangle + lower_triangle
  1925. # 6. (Optional) Combine the standardized triangles into a normalized full matrix
  1926. full_matrix_normalized = upper_triangle_normalized + lower_triangle_normalized
  1927. # Visualize the absolute correlation heatmap
  1928. full_matrix_abs = np.abs(full_matrix)
  1929. plt.figure(figsize=(20, 20))
  1930. sns.heatmap(full_matrix_abs, cmap='coolwarm', annot=False, fmt='.2f')
  1931. # Save the heatmap to a PDF
  1932. output_path = FA_figure_dir + "/genome_pheotype_correlatio.pdf"
  1933. plt.savefig(output_path, format='pdf', bbox_inches='tight')
  1934. plt.show()
  1935. # %%
  1936. import numpy as np
  1937. import pandas as pd
  1938. import seaborn as sns
  1939. import matplotlib.pyplot as plt
  1940. from scipy.stats import wilcoxon
  1941. # Example data: absolute values of upper- and lower-triangle correlation matrices
  1942. pheno = np.abs(upper_triangle_values)
  1943. genome = np.abs(lower_triangle_values)
  1944. # Print group means for reference
  1945. print("Mean Pheno correlation:", np.mean(pheno))
  1946. print("Mean Genome correlation:", np.mean(genome))
  1947. # Perform the Wilcoxon signed-rank test for paired samples
  1948. stat, p_value = wilcoxon(pheno, genome)
  1949. # Prepare a DataFrame for plotting
  1950. data = pd.DataFrame({
  1951. 'Correlation': np.concatenate([pheno, genome]),
  1952. 'Group': ['Pheno'] * len(pheno) + ['Genome'] * len(genome)
  1953. })
  1954. # Create a boxplot comparing the two groups
  1955. plt.figure(figsize=(8, 6))
  1956. sns.boxplot(x='Group', y='Correlation', data=data, palette="Set2")
  1957. plt.title('Boxplot of Pheno vs. Genome Correlations with Wilcoxon Test', fontsize=16)
  1958. # Annotate the plot with the Wilcoxon p-value
  1959. plt.text(
  1960. 0.5,
  1961. max(data['Correlation']) + 0.2,
  1962. f'Wilcoxon p-value: {p_value:.3e}',
  1963. ha='center',
  1964. fontsize=12,
  1965. color='red'
  1966. )
  1967. # Adjust layout and display the plot
  1968. plt.tight_layout()
  1969. plt.show()
  1970. # %%
  1971. import matplotlib.pyplot as plt
  1972. import seaborn as sns
  1973. import numpy as np
  1974. from matplotlib.colors import LinearSegmentedColormap
  1975. # Create a custom color map: blue for negative values, white at zero, red for positive values
  1976. cmap = LinearSegmentedColormap.from_list(
  1977. 'custom_cmap', ['blue', 'white', 'red'], N=100
  1978. )
  1979. # Ensure the matrix values are in the range [-1, 1]; take absolute values if needed
  1980. # full_matrix_normalized = np.abs(full_matrix_normalized)
  1981. # Plot the clustermap
  1982. plt.figure(figsize=(20, 12))
  1983. clustermap = sns.clustermap(
  1984. full_matrix, # Input correlation matrix
  1985. cmap=cmap, # Use the custom color map
  1986. annot=False, # Disable cell annotations
  1987. fmt='.2f', # Format for annotation if enabled
  1988. xticklabels=True, # Show x-axis tick labels
  1989. yticklabels=True, # Show y-axis tick labels
  1990. vmin=-1, vmax=1, # Set the value range for the color scale
  1991. figsize=(12, 10), # Figure size for the clustermap
  1992. annot_kws={"size": 6}, # Font size for annotations if enabled
  1993. row_cluster=False, # Disable row clustering
  1994. col_cluster=False, # Disable column clustering
  1995. cbar_pos=(0.02, 0.8, 0.03, 0.18), # Position of the colorbar (x, y, width, height)
  1996. cbar_kws={
  1997. "label": "Correlation", # Label for the colorbar
  1998. "shrink": 0.5, # Shrink factor for the colorbar
  1999. "orientation": "vertical",
  2000. "format": "%.2f" # Format for colorbar tick labels
  2001. },
  2002. )
  2003. # Adjust the colorbar tick label font size
  2004. colorbar = clustermap.ax_heatmap.collections[0].colorbar
  2005. colorbar.ax.tick_params(labelsize=8)
  2006. # Set the colorbar label font size
  2007. colorbar.set_label("Correlation", fontsize=16)
  2008. # Adjust the axis tick label font sizes
  2009. plt.setp(clustermap.ax_heatmap.xaxis.get_majorticklabels(), fontsize=12)
  2010. plt.setp(clustermap.ax_heatmap.yaxis.get_majorticklabels(), fontsize=12)
  2011. # Optionally set a title for the plot
  2012. # plt.title('Clustermap of Correlation Matrix', fontsize=16)
  2013. # Display the plot
  2014. plt.show()
  2015. # %%
  2016. # Par Her
  2017. import os
  2018. import pandas as pd
  2019. def extract_data_from_parh_subfolders(folder_path):
  2020. """
  2021. Extracts the first row of data from '.results' files in the 'parh' folder within each subfolder,
  2022. combines them into separate DataFrames for each subfolder, and computes the mean
  2023. across all combined DataFrames.
  2024. Parameters:
  2025. folder_path (str): Path to the parent folder containing subfolders with 'parh' folders.
  2026. Returns:
  2027. pd.DataFrame: A DataFrame with the mean values of all combined DataFrames.
  2028. """
  2029. all_parh_dfs = [] # List to store combined DataFrames from each 'parh' folder
  2030. # Iterate through each subfolder in the parent folder
  2031. for subfolder_name in os.listdir(folder_path):
  2032. subfolder_path = os.path.join(folder_path, subfolder_name)
  2033. parh_folder_path = os.path.join(subfolder_path, 'parh') # Path to 'parh' folder
  2034. #print(parh_folder_path)
  2035. if os.path.isdir(parh_folder_path): # Ensure the 'parh' folder exists
  2036. # print('sss')
  2037. parh_data = [] # List to store data from files in 'parh' folder
  2038. # Process each '.results' file in the 'parh' folder
  2039. for file_name in os.listdir(parh_folder_path):
  2040. if file_name.endswith(".results"): # Process only '.results' files
  2041. file_path = os.path.join(parh_folder_path, file_name)
  2042. try:
  2043. # Read the file
  2044. df = pd.read_csv(file_path, sep="\s+", engine="python")
  2045. if len(df) > 0: # Ensure the file contains data
  2046. # Extract the first row
  2047. first_row = df.iloc[0].to_frame().T
  2048. first_row['File_Identifier'] = file_name.split('.result')[0].split('_')[2] # Extract identifier
  2049. parh_data.append(first_row)
  2050. except Exception as e:
  2051. print(f"Error reading {file_name} in {parh_folder_path}: {e}")
  2052. # Combine all rows from this 'parh' folder into a single DataFrame
  2053. if parh_data:
  2054. print(parh_data)
  2055. parh_df = pd.concat(parh_data, ignore_index=True)
  2056. all_parh_dfs.append(parh_df)
  2057. # Aggregate data: sum all 'parh' DataFrames and compute mean
  2058. if all_parh_dfs:
  2059. combined_df = pd.concat(all_parh_dfs, ignore_index=True) # Combine all 'parh' DataFrames
  2060. aggregated_df = combined_df.mean(numeric_only=True) # Compute mean of numeric columns
  2061. return aggregated_df
  2062. else:
  2063. return pd.DataFrame() # Return an empty DataFrame if no data is found
  2064. # Example usag
  2065. # Example usage
  2066. parent_folder = "/data/xzhao14/GWAS_output" # Replace with the path to your parent folder
  2067. result_df = extract_data_from_parh_subfolders(parent_folder)
  2068. # Print or save the result
  2069. print(result_df)
  2070. # %%
  2071. import pandas as pd
  2072. import os
  2073. dd=pd.read_csv(os.path.join('/data484_2/xzhao14/', "filtered_combined_genes.csv"))
  2074. cc_dd=list(dd['GENE'])
  2075. gene_symbol=pd.read_csv('/data484_2/xzhao14/POST_GWAS/prepare_data/MAGMA_files/NCBI37.3.gene.loc',sep='\t',header=None)
  2076. gene_symbol.columns = ['ID', 'CHR', 'START','END','Dir','Gene_system']
  2077. filtered_df = gene_symbol[gene_symbol['ID'].isin(cc_dd)]
  2078. # %%
  2079. filtered_df.shape
  2080. # %%
  2081. combined_results
  2082. # %%
  2083. ## previous published data #
  2084. import pandas as pd
  2085. import numpy as np
  2086. from wordcloud import WordCloud
  2087. import matplotlib.pyplot as plt
  2088. import pandas as pd
  2089. from matplotlib.colors import to_hex, Normalize
  2090. from matplotlib.cm import ScalarMappable
  2091. from collections import Counter
  2092. science_data=pd.read_csv('/data/xzhao14/previous_res.txt')
  2093. this_result=pd.read_csv('/data484_2/xzhao14/FA_FUMA_job577004/FA_meta_5e-8_lead_SNP.txt',sep='\t')
  2094. ### ###
  2095. SNP_list=list(this_result['rsID'])
  2096. def filter_gwas_catalog(snp_list, gwas_catalog_file, output_file):
  2097. """
  2098. Filters rows containing specific SNPs from the GWAS Catalog file and retains selected columns.
  2099. Parameters:
  2100. snp_list_file (str): Path to the file containing the list of SNPs (one SNP per line).
  2101. gwas_catalog_file (str): Path to the GWAS Catalog file (tab-separated).
  2102. output_file (str): Path to save the filtered results.
  2103. """
  2104. # Columns to retain
  2105. columns_to_keep = [
  2106. 'PUBMEDID', 'FIRST AUTHOR', 'JOURNAL', 'DISEASE/TRAIT',
  2107. 'INITIAL SAMPLE SIZE', 'CHR_ID', 'CHR_POS',
  2108. 'REPORTED GENE(S)', 'MAPPED_GENE', 'SNPS',
  2109. 'RISK ALLELE FREQUENCY', 'P-VALUE', 'PVALUE_MLOG',
  2110. 'P-VALUE (TEXT)', 'OR or BETA'
  2111. ]
  2112. # Read the SNP list
  2113. # Read the GWAS Catalog file
  2114. gwas_df = pd.read_csv(gwas_catalog_file, sep='\t')
  2115. # Filter rows containing specified SNPs
  2116. filtered_df = gwas_df[gwas_df['SNPS'].isin(snp_list)]
  2117. # Retain only the specified columns
  2118. filtered_df = filtered_df[columns_to_keep]
  2119. print(f"Filtering complete! Results saved to {output_file}")
  2120. # Save the filtered results to the output file
  2121. filtered_df.to_csv(output_file, sep='\t', index=False)
  2122. return filtered_df
  2123. # Example usage
  2124. gwas_catalog_file = '/data484_2/xzhao14/POST_GWAS/prepare_data/GWAS_catelog/GWAS_catelog.txt' # Path to the GWAS Catalog file
  2125. output_file = '/data484_2/xzhao14/FA_cate_log_meta_5e-8_filtered_gwas_catalog.txt' # Path to save the results
  2126. GWAS_asso=filter_gwas_catalog(SNP_list,gwas_catalog_file,output_file)
  2127. df=GWAS_asso[['DISEASE/TRAIT','P-VALUE']]
  2128. # Example input: a dataframe with two columns: 'Text' and 'P_Value'
  2129. # Calculate word frequencies
  2130. word_counts = Counter(df['DISEASE/TRAIT'])
  2131. # Find the minimum P-Value for each word
  2132. word_min_p_values = df.groupby('DISEASE/TRAIT')['P-VALUE'].min()
  2133. # Normalize P-Values for color mapping
  2134. norm = Normalize(vmin=word_min_p_values.min(), vmax=word_min_p_values.max())
  2135. # Custom color function for shades of red based on P-values
  2136. def word_color_func(word, font_size, position, orientation, random_state=None, **kwargs):
  2137. if word in word_min_p_values:
  2138. normalized_value = norm(word_min_p_values[word]) # Normalize the P-value
  2139. red_intensity = int(255 * (1 - normalized_value)) # Invert to make smaller P-values darker
  2140. return f"rgb({red_intensity}, 0, 0)"
  2141. else:
  2142. return "rgb(255, 255, 255)" # Default to white if word not found (unlikely)
  2143. # Generate the word cloud
  2144. wordcloud = WordCloud(
  2145. width=800,
  2146. height=400,
  2147. background_color="white",
  2148. color_func=word_color_func
  2149. ).generate_from_frequencies(word_counts)
  2150. # Plot the word cloud
  2151. plt.figure(figsize=(10, 5))
  2152. plt.imshow(wordcloud, interpolation="bilinear")
  2153. plt.axis("off")
  2154. plt.title("Association with other phenotype using SNP", fontsize=16)
  2155. plt.show()
  2156. # %% [markdown]
  2157. # #### replication analysis ##
  2158. # %%
  2159. import pandas as pd
  2160. import numpy as np
  2161. science_data=pd.read_csv('/data/xzhao14/previous_res.txt')
  2162. this_result=pd.read_csv('/data484_2/xzhao14/FA_FUMA_job577004/leadSNPs.txt',sep='\t')
  2163. #this_result=pd.read_csv('/data/xzhao14/FA_clump.csv')
  2164. GWAS_catelog=pd.read_csv('/data484_2/xzhao14/FA_cate_log_minp_filtered_gwas_catalog.txt',sep='\t')
  2165. GWAS_catelog = GWAS_catelog[GWAS_catelog['DISEASE/TRAIT'].str.contains(r"brain|white|cortical", case=False, na=False)]
  2166. replication_res=pd.read_csv('/data484_2/xzhao14/FA_replication5_8_sig_snp.txt',sep='\t')
  2167. print(len(set(GWAS_catelog['SNPS'])&set(this_result['rsID'])))
  2168. print(len(set(science_data['SNP_science'])&set(this_result['rsID'])))
  2169. print(len(set(replication_res['SNP'])&set(this_result['rsID'])))
  2170. # %%
  2171. this_result
  2172. # %%
  2173. # replication
  2174. import os
  2175. import numpy as np
  2176. import matplotlib.pyplot as plt
  2177. from statsmodels.stats.multitest import multipletests
  2178. import pandas as pd
  2179. root_dir='/data/xzhao14/FA_rep_gc'
  2180. disorders = os.listdir(root_dir)
  2181. data = []
  2182. for i in disorders:
  2183. file_path = os.path.join(root_dir, i)
  2184. with open(file_path, 'r') as f:
  2185. all_lines = f.readlines()
  2186. if len(all_lines) < 4:
  2187. print(f"Warning: File {file_path} does not have enough lines.")
  2188. continue
  2189. last_line = all_lines[-4].strip() # Extract the fourth last line
  2190. data.append(last_line.split()) # Split the line into individual values and store in the list
  2191. # Define column names for the DataFrame
  2192. columns = ['P1', 'P2', 'rg', 'SE', 'Z', 'P', 'h2_obs', 'h2_obs_se', 'h2_int', 'h2_int_se', 'gcov_int', 'gcov_int_se']
  2193. df = pd.DataFrame(data, columns=columns)
  2194. df['P1'] = df['P1'].str.extract(r'/([^/]+)\.sumstats\.gz')[0]
  2195. df['P2'] = df['P2'].str.extract(r'/([^/]+)\.sumstats\.gz')[0]
  2196. df.replace("NA", np.nan, inplace=True)
  2197. df = df.dropna()
  2198. print(df)
  2199. # Convert 'rg' and 'SE' columns to float type
  2200. df['rg'] = df['rg'].astype(float)
  2201. df['SE'] = df['SE'].astype(float)
  2202. df["FDR"]=multipletests(df["P"].astype(float),alpha=0.05, method='fdr_bh')[1]
  2203. # Determine color based on FDR value
  2204. # Determine color based on FDR value
  2205. colors = ['orange' if fdr < 0.05 else 'blue' for fdr in df['FDR']]
  2206. plt.figure(figsize=(10, 8))
  2207. # Draw scatter points with error bars
  2208. for i in range(len(df)):
  2209. plt.errorbar(
  2210. df['rg'].iloc[i], df['P2'].iloc[i],
  2211. xerr=df['SE'].iloc[i], fmt='o',
  2212. color=colors[i], ecolor='black', capsize=3
  2213. )
  2214. # Add legend
  2215. handles = [
  2216. plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='orange', markersize=8, label='FDR < 0.05'),
  2217. plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='blue', markersize=8, label='FDR ≥ 0.05')
  2218. ]
  2219. plt.legend(handles=handles, title='Significance', loc='upper left', fontsize=10)
  2220. # Add decorations
  2221. plt.axvline(x=0, color='gray', linestyle='--', linewidth=1) # Add a vertical line at x=0
  2222. plt.xlabel('Genetic Correlation (rg)', fontsize=12)
  2223. plt.ylabel('P2 Phenotypes', fontsize=12)
  2224. plt.title('Forest Plot of Genetic Correlation', fontsize=14)
  2225. plt.gca().invert_yaxis() # Reverse Y-axis to align with table layout
  2226. plt.grid(axis='x', linestyle='--', alpha=0.7)
  2227. # Display the plot
  2228. plt.tight_layout()
  2229. plt.show()
  2230. # %%
  2231. # partioned heritability analysis #
  2232. import os
  2233. import pandas as pd
  2234. import numpy as np
  2235. import matplotlib.pyplot as plt
  2236. from statsmodels.stats.multitest import multipletests
  2237. def extract_first_row_with_pandas(folder_path,index):
  2238. """
  2239. Extracts the first row of data from each '.results' file in the given folder,
  2240. combines them into a single DataFrame, and adds an identifier column extracted from the file name.
  2241. Parameters:
  2242. folder_path (str): Path to the folder containing '.results' files.
  2243. Returns:
  2244. pd.DataFrame: A DataFrame with combined data from all files and a file identifier column.
  2245. """
  2246. all_data = [] # List to store DataFrames from each file
  2247. # print(os.listdir(folder_path))
  2248. for file_name in os.listdir(folder_path):
  2249. #print(file_name)
  2250. if file_name.endswith(".results"): # Process only files with '.results' suffix
  2251. file_path = os.path.join(folder_path, file_name)
  2252. try:
  2253. #print(file_path)
  2254. # Read the file, automatically using the first row as column names
  2255. df = pd.read_csv(file_path,sep='\t')
  2256. if len(df) > 0: # Ensure the file contains data
  2257. # Extract the first row as a DataFrame
  2258. first_row = df.iloc[0].to_frame().T
  2259. first_row['File_Identifier'] = file_name.split('.result')[0].split('_')[2] # Extract identifier from file name
  2260. all_data.append(first_row) # Add the first row to the list
  2261. except Exception as e:
  2262. print(f"Error reading {file_name}: {e}")
  2263. #print(combined_df)
  2264. # Combine all rows into a single DataFrame
  2265. if all_data:
  2266. combined_df = pd.concat(all_data, ignore_index=True)
  2267. else:
  2268. combined_df = pd.DataFrame() # Return an empty DataFrame if no data is found
  2269. combined_df['pheno']=index
  2270. return combined_df
  2271. def plot_bubble_chart_vertical(df, p_col='Enrichment_p', identifier_col='File_Identifier', alpha=0.05):
  2272. """
  2273. Perform FDR correction on a specified p-value column, transform to -log10 scale, and plot a vertical bubble chart.
  2274. Parameters:
  2275. df (pd.DataFrame): Input DataFrame containing p-values and identifiers.
  2276. p_col (str): Column name containing p-values.
  2277. identifier_col (str): Column name containing file identifiers.
  2278. alpha (float): Significance level for FDR correction.
  2279. Returns:
  2280. None: Displays the bubble chart.
  2281. """
  2282. # Perform FDR correction
  2283. file_path = "/data/xzhao14/Supplemental_tables.csv"
  2284. p_values = df[p_col].astype(float) # Ensure p-values are in float format
  2285. corrected = multipletests(p_values, alpha=alpha, method='fdr_bh') # FDR correction
  2286. df['FDR_Adjusted_p'] = corrected[1] # Store adjusted p-values
  2287. # Transform to -log10 scale
  2288. df['-log10(FDR_Adjusted_p)'] = -np.log10(df['FDR_Adjusted_p'])
  2289. df = df.dropna()
  2290. if (df['-log10(FDR_Adjusted_p)']<1.3).any():
  2291. #plot_bubble_chart_vertical(result_df, p_col='Enrichment_p', identifier_col='File_Identifier')
  2292. print('ssdsada') # Display the first few rows of the combined DataFrame
  2293. # Bubble size based on the significance level (smaller p-values = larger bubbles)
  2294. df['Bubble_Size'] = (1 / df['FDR_Adjusted_p']) * 100 # Adjust scaling factor as needed
  2295. data = pd.read_csv(file_path)
  2296. data_cleaned = data.iloc[0:, [0, 2]]
  2297. merged_df = pd.merge(df, data_cleaned, left_on='File_Identifier', right_on='Cell subclass', how='left')
  2298. color_map = {cls: color for cls, color in zip(merged_df['Cell class'].unique(), plt.cm.tab20.colors)}
  2299. merged_df['Color'] = merged_df['Cell class'].map(color_map)
  2300. # Create the vertical bubble chart
  2301. plt.figure(figsize=(10, 12))
  2302. plt.scatter(
  2303. merged_df['-log10(FDR_Adjusted_p)'],
  2304. merged_df['File_Identifier'],
  2305. s=merged_df['Bubble_Size'],
  2306. alpha=0.7,
  2307. edgecolors="w",
  2308. c=merged_df['Color']
  2309. )
  2310. # Add plot decorations
  2311. plt.axvline(x=-np.log10(0.05), color='red', linestyle='--', linewidth=1, label='FDR threshold (0.05)')
  2312. plt.title('Bubble Chart of FDR Adjusted P-Values (Colored by Cell Class)', fontsize=14)
  2313. plt.xlabel('-log10(FDR Adjusted P-Values)', fontsize=12)
  2314. plt.ylabel('File Identifier (Cell Subclass)', fontsize=12)
  2315. plt.xticks(fontsize=10)
  2316. plt.yticks(fontsize=10)
  2317. plt.legend(handles=[plt.Line2D([0], [0], marker='o', color=color, label=cls, markersize=10, linestyle='None')
  2318. for cls, color in color_map.items()], title='Cell Class', bbox_to_anchor=(1.05, 1), loc='upper left')
  2319. plt.tight_layout()
  2320. plt.show()
  2321. # Example usage
  2322. all_result=[]
  2323. for i in range(128):
  2324. #print(i)
  2325. folder_path = "/data/xzhao14/GWAS_output/QT"+str(i)+"_results/parh/QT"+str(i)+"_results.fastGWA" # Replace with your folder path
  2326. result_df = extract_first_row_with_pandas(folder_path,str(i))
  2327. all_result.append(result_df)
  2328. #plot_bubble_chart_vertical(result_df, p_col='Enrichment_p', identifier_col='File_Identifier')
  2329. ##
  2330. # Display the first few rows of the combined DataFrame
  2331. all_result = pd.concat(all_result, ignore_index=True)
  2332. # %%
  2333. corrected = multipletests(all_result['Enrichment_p'].values, alpha=0.05, method='fdr_bh')
  2334. fdr_adjusted_p = corrected[1]
  2335. all_result['Enrichment_p']=fdr_adjusted_p
  2336. # %%
  2337. import os
  2338. import pandas as pd
  2339. import numpy as np
  2340. from statsmodels.stats.multitest import multipletests
  2341. def process_file(folder_path, file_suffix=".results", p_col="Enrichment_p"):
  2342. """
  2343. Process files in a folder to extract the specified p-value column and perform FDR correction.
  2344. Parameters:
  2345. folder_path (str): Path to the folder containing files.
  2346. file_suffix (str): File extension to process.
  2347. p_col (str): Column containing p-values.
  2348. Returns:
  2349. pd.Series: A series with -log10(FDR_Adjusted_p) for each file.
  2350. """
  2351. all_p_values = []
  2352. for file_name in os.listdir(folder_path):
  2353. if file_name.endswith(file_suffix):
  2354. file_path = os.path.join(folder_path, file_name)
  2355. try:
  2356. df = pd.read_csv(file_path, sep='\t')
  2357. if p_col in df.columns:
  2358. # Ensure p-values are numeric and replace NaN with 1
  2359. p_values = pd.to_numeric(df[p_col], errors='coerce').fillna(1)
  2360. all_p_values.append(p_values)
  2361. except Exception as e:
  2362. print(f"Error reading {file_name}: {e}")
  2363. if all_p_values:
  2364. return pd.concat(all_p_values).reset_index(drop=True)
  2365. return pd.Series()
  2366. def generate_matrix(base_folder_path, num_files=128, file_suffix=".results", p_col="Enrichment_p"):
  2367. """
  2368. Generate a 128x43 matrix with rows representing files and columns representing FDR-adjusted p-values.
  2369. Parameters:
  2370. base_folder_path (str): Path to the base folder containing all subfolders.
  2371. num_files (int): Number of subfolders to process.
  2372. file_suffix (str): File extension to process.
  2373. p_col (str): Column containing p-values.
  2374. Returns:
  2375. pd.DataFrame: A matrix with -log10(FDR_Adjusted_p) values.
  2376. """
  2377. result_matrix = []
  2378. file_names = []
  2379. for i in range(num_files):
  2380. folder_path = os.path.join(base_folder_path, f"QT{i}_results/parh/QT{i}_results.fastGWA")
  2381. print(f"Processing folder: {folder_path}")
  2382. # Process files and extract p-values
  2383. p_values = process_file(folder_path, file_suffix=file_suffix, p_col=p_col)
  2384. if not p_values.empty:
  2385. # Perform FDR correction
  2386. corrected = multipletests(p_values, alpha=0.05, method='fdr_bh')
  2387. fdr_adjusted_p = corrected[1]
  2388. # Convert to -log10 scale
  2389. log_fdr_adjusted_p = -np.log10(fdr_adjusted_p)
  2390. log_fdr_adjusted_p[~np.isfinite(log_fdr_adjusted_p)] = 0 # Replace inf/-inf with 0
  2391. result_matrix.append(log_fdr_adjusted_p[:43]) # Ensure 43 columns
  2392. file_names.append(f"QT{i}")
  2393. else:
  2394. print(f"No valid p-values found in folder: QT{i}")
  2395. result_matrix.append([np.nan] * 43) # Fill with NaN if no data
  2396. # Create DataFrame
  2397. result_df = pd.DataFrame(result_matrix, index=file_names, columns=[f"Feature_{j}" for j in range(1, 44)])
  2398. return result_df
  2399. # Base folder path
  2400. base_folder_path = "/data/xzhao14/GWAS_output"
  2401. # Generate the matrix
  2402. matrix_df = generate_matrix(base_folder_path)
  2403. # Save the matrix to a file (optional)
  2404. # matrix_df.to_csv("/data/xzhao14/result_matrix.csv", index=True)
  2405. # print("Matrix generation complete. Saved to /data/xzhao14/result_matrix.csv.")
  2406. # %%
  2407. # All threshold #
  2408. threshold = -np.log10(0.05 / 128)
  2409. # %%
  2410. import pandas as pd
  2411. import seaborn as sns
  2412. import matplotlib.pyplot as plt
  2413. import numpy as np
  2414. file_path = "/data/xzhao14/Supplemental_tables.csv"
  2415. data = pd.read_csv(file_path)
  2416. data_cleaned = data.iloc[0:, [0, 2]]
  2417. # matrix "Cell subclass"
  2418. matrix_df.columns = data_cleaned["Cell subclass"].values
  2419. # p-value 0.05/128
  2420. # define threshold
  2421. threshold = -np.log10(0.05 / 128)
  2422. #threshold=1.3
  2423. # create a new matrix and replace values below threshold with 'X'
  2424. annot_matrix = matrix_df.applymap(lambda x: "X" if x > threshold else "")
  2425. # heatmapclass
  2426. col_colors = data_cleaned.set_index("Cell subclass")["Cell class"]
  2427. # map colors
  2428. class_colors = {
  2429. "GABA": "skyblue",
  2430. "GABA+Dopa": "orange",
  2431. "GLUT": "lightgreen",
  2432. "NonN":"yellow"
  2433. }
  2434. col_colors_mapped = col_colors.map(class_colors)
  2435. # plot heatmap
  2436. plt.figure(figsize=(20, 15))
  2437. sns.heatmap(
  2438. matrix_df,
  2439. cmap="coolwarm", # colormap
  2440. cbar_kws={'label': 'Value'}, #
  2441. linewidths=0.5, #
  2442. linecolor='black', #
  2443. xticklabels=True, #
  2444. yticklabels=True, #
  2445. annot=annot_matrix, # heatmap "X"
  2446. fmt="", #
  2447. square=False #
  2448. )
  2449. # class
  2450. for i, subclass in enumerate(matrix_df.columns):
  2451. cell_class = col_colors.loc[subclass]
  2452. plt.text(
  2453. i + 0.5, -5, cell_class, ha="center", va="center", fontsize=10,
  2454. rotation=90, color=class_colors[cell_class]
  2455. )
  2456. #
  2457. plt.title("Heatmap with Cell Class Annotations and Significant Markings", fontsize=16)
  2458. plt.xlabel("Cell Subclass", fontsize=12)
  2459. plt.ylabel("FA EP", fontsize=12)
  2460. plt.tight_layout()
  2461. plt.show()
  2462. # %%
  2463. ### box plot ##
  2464. import pandas as pd
  2465. import seaborn as sns
  2466. import matplotlib.pyplot as plt
  2467. import numpy as np
  2468. # class
  2469. anno_df=data_cleaned
  2470. # compute enrichment grouped by cell class
  2471. anno_df = anno_df.set_index("Cell subclass")
  2472. cell_classes = anno_df["Cell class"]
  2473. # matrixby
  2474. # matrix
  2475. matrix_long = matrix_df.melt(var_name="Cell Subclass", value_name="Enrichment Value")
  2476. # map cell class information to each cell subclass
  2477. #matrix_long["Cell Class"] = matrix_long["Cell Subclass"].map(cell_classes)
  2478. # Enrichment Value > 3.408
  2479. filtered_matrix_long = matrix_long[matrix_long["Enrichment Value"] >3.48]
  2480. # if no rows match, print a message
  2481. # if no rows match, print a message
  2482. if filtered_matrix_long.empty:
  2483. print("No Enrichment Values greater than 3.408 were found.")
  2484. else:
  2485. # Cell Subclass mean
  2486. mean_values = filtered_matrix_long.groupby("Cell Subclass")["Enrichment Value"].median().reset_index()
  2487. # by meansort
  2488. mean_values = mean_values.sort_values(by="Enrichment Value", ascending=False)
  2489. # median_values = filtered_matrix_long.groupby("Cell Subclass")["Enrichment Value"].median().reset_index()
  2490. # # by mediansort
  2491. # median_values = median_values.sort_values(by="Enrichment Value", ascending=False)
  2492. # barplot
  2493. plt.figure(figsize=(20, 8))
  2494. # ,
  2495. unique_classes = mean_values["Cell Subclass"].nunique() # class
  2496. custom_palette = sns.color_palette("husl", unique_classes) # HUSL
  2497. sns.barplot(
  2498. data=mean_values,
  2499. x="Cell Subclass",
  2500. y="Enrichment Value",
  2501. palette=custom_palette #
  2502. )
  2503. plt.title(" Enrichment of FA EP in Cell Subclasses", fontsize=16)
  2504. plt.xlabel("Cell Subclass", fontsize=12)
  2505. plt.ylabel("Enrichment(-log10(FDR)", fontsize=12)
  2506. plt.xticks(rotation=90, fontsize=10)
  2507. plt.yticks(fontsize=10)
  2508. plt.tight_layout()
  2509. plt.show()
  2510. # %%
  2511. data
  2512. # %%
  2513. ## MAGMA gene functions ##
  2514. import os
  2515. import pandas as pd
  2516. from statsmodels.stats.multitest import multipletests
  2517. folder_path = "/data484_2/xzhao14/FA_MAGMA_output"
  2518. # pathfile path
  2519. #output_file = "filtered_results.csv"
  2520. # initialize
  2521. final_results = []
  2522. # , gsa.out
  2523. for file_name in os.listdir(folder_path):
  2524. if file_name.endswith("gsa.out"):
  2525. file_path = os.path.join(folder_path, file_name)
  2526. # ,
  2527. try:
  2528. # read file content
  2529. with open(file_path, 'r') as file:
  2530. lines = file.readlines()
  2531. #
  2532. for idx, line in enumerate(lines):
  2533. if line.startswith("VARIABLE"):
  2534. header_idx = idx
  2535. break
  2536. #
  2537. header = lines[header_idx].strip().split()
  2538. data = [line.strip().split() for line in lines[header_idx + 1:] if line.strip()]
  2539. # DataFrame
  2540. df = pd.DataFrame(data, columns=header)
  2541. data = df
  2542. except Exception as e:
  2543. print(f"Error reading {file_name}: {e}")
  2544. continue
  2545. #print(data)
  2546. # FULL_NAME"GOB"
  2547. filtered_data = data[data['FULL_NAME'].str.contains('REACTOME', na=False)]
  2548. # ,
  2549. if filtered_data.empty:
  2550. continue
  2551. # apply FDR correction to column P
  2552. # Pclass,
  2553. filtered_data['P'] = pd.to_numeric(filtered_data['P'], errors='coerce')
  2554. # Pconvert to
  2555. filtered_data = filtered_data.dropna(subset=['P'])
  2556. # ,
  2557. if filtered_data.empty:
  2558. continue
  2559. # apply FDR correction to column P
  2560. filtered_data['FDR'] = multipletests(filtered_data['P'], method='fdr_bh')[1]
  2561. # filtered_data['FDR'] = multipletests(filtered_data['P'], method='fdr_bh')[1]
  2562. # FDR < 0.05/128
  2563. threshold = 0.01
  2564. significant_pathways = filtered_data[filtered_data['FDR'] < threshold]
  2565. # ,
  2566. if significant_pathways.empty:
  2567. continue
  2568. # save results
  2569. significant_pathways['Source_File'] = file_name
  2570. final_results.append(significant_pathways)
  2571. #
  2572. if final_results:
  2573. combined_results = pd.concat(final_results, ignore_index=True)
  2574. # combined_results.to_csv(output_file, index=False)
  2575. # print(f"Filtered results saved to {output_file}")
  2576. # else:
  2577. # print("No significant pathways found in the given files.")
  2578. # %%
  2579. #
  2580. import os
  2581. import pandas as pd
  2582. import numpy as np
  2583. import matplotlib.pyplot as plt
  2584. df=combined_results
  2585. # FDR -log10(FDR)
  2586. df["-log10(FDR)"] = -np.log10(df["FDR"])
  2587. df_sorted = df.sort_values(by="-log10(FDR)", ascending=False)
  2588. # sort
  2589. plt.figure(figsize=(12, 6))
  2590. plt.barh(df_sorted["FULL_NAME"], df_sorted["-log10(FDR)"], color='skyblue')
  2591. plt.xlabel("-log10(FDR)", fontsize=12)
  2592. plt.ylabel("FULL_NAME", fontsize=12)
  2593. #plt.title("Pathways and Their -log10(FDR) (Sorted)", fontsize=14)
  2594. plt.gca().invert_yaxis() # Y ,
  2595. plt.grid(axis='x', linestyle='--', alpha=0.7)
  2596. plt.tight_layout()
  2597. plt.show()
  2598. # %% [markdown]
  2599. # # Get the FA related feature from the UKB table
  2600. # %%
  2601. import pandas as pd
  2602. def filter_csv_by_columns(input_csv_path, column_list_path, output_csv_path, chunksize=100000):
  2603. """
  2604. Filter columns from a large CSV file based on a list of column names or prefixes,
  2605. and save the filtered data to a new CSV file.
  2606. Parameters:
  2607. - input_csv_path: str, path to the input CSV file
  2608. - column_list_path: str, path to the TXT file containing column names or prefixes (one per line)
  2609. - output_csv_path: str, path to save the filtered output CSV file
  2610. - chunksize: int, number of rows to read per chunk (default: 100000)
  2611. """
  2612. # Read the list of column names or prefixes from the TXT file
  2613. with open(column_list_path, 'r') as f:
  2614. target_columns = [line.strip() for line in f if line.strip()]
  2615. # Initialize an empty DataFrame to store matched columns
  2616. filtered_data = pd.DataFrame()
  2617. # Iterate over chunks of the large CSV file
  2618. for chunk in pd.read_csv(input_csv_path, chunksize=chunksize):
  2619. # Find columns that match any target column or prefix
  2620. selected_columns = [col for col in chunk.columns if any(col.startswith(prefix) for prefix in target_columns)]
  2621. if selected_columns:
  2622. filtered_chunk = chunk[selected_columns]
  2623. filtered_data = pd.concat([filtered_data, filtered_chunk], ignore_index=True)
  2624. filtered_data_clean = filtered_data.dropna(how='any')
  2625. # Save the filtered result to a new CSV file
  2626. filtered_data_clean.to_csv(output_csv_path, index=False)
  2627. ### EID#
  2628. ####
  2629. print(f"Filtering completed. Output saved to: {output_csv_path}")
  2630. input_csv_path = '/data5/Ziqian/UKBB/UKB_data/UKB_all.csv'
  2631. output_csv_path = '/data/xzhao14/FA_value.csv'
  2632. request_txt='/data/xzhao14/UKB_request_FA.txt'
  2633. filter_csv_by_columns(input_csv_path,request_txt,output_csv_path)
  2634. # %%
  2635. import pandas as pd
  2636. # file path
  2637. input_csv_path = '/data5/Ziqian/UKBB/UKB_data/UKB_all.csv'
  2638. output_csv_path = '/data/xzhao14/FA_disease_sample.csv'
  2639. # chunksizememory
  2640. chunksize = 100000 #
  2641. # initialize DataFrame,
  2642. filtered_data = pd.DataFrame()
  2643. #
  2644. for chunk in pd.read_csv(input_csv_path, chunksize=chunksize):
  2645. # '41270'
  2646. selected_columns = [col for col in chunk.columns if col.startswith('41270')]
  2647. if selected_columns: #
  2648. filtered_chunk = chunk[selected_columns]
  2649. filtered_data = pd.concat([filtered_data, filtered_chunk], ignore_index=True)
  2650. # CSV
  2651. filtered_data.to_csv(output_csv_path, index=False)
  2652. print(f"save result {output_csv_path}")
  2653. # %%
  2654. ## AD sample choosing #
  2655. import pandas as pd
  2656. # input_csv_path = '/data5/Ziqian/UKBB/UKB_data/UKB_all.csv'
  2657. #output_csv_path = '/data/xzhao14/FA_disease_sample.csv'
  2658. # file path
  2659. file1_path = '/data/xzhao14/FA_disease_sample.csv' # CSVfile path
  2660. file2_path = '/data5/Ziqian/UKBB/UKB_data/UKB_all.csv' # CSVfile path
  2661. output_path = '/data/xzhao14/UKB_sample_ICD10/Bone_abnormality_adult__sample.txt' # file path
  2662. # : 'G309' index
  2663. df1 = pd.read_csv(file1_path)
  2664. # 'G309',
  2665. contains_g309 = df1.apply(lambda row: row.astype(str).str.contains('M83').any(), axis=1)
  2666. indices_with_g309 = df1[contains_g309].index.tolist()
  2667. # : 'eid'
  2668. df2 = pd.read_csv(file2_path, usecols=['eid'])
  2669. # index
  2670. filtered_eid = df2.iloc[indices_with_g309]
  2671. # :
  2672. filtered_eid.to_csv(output_path, index=False)
  2673. print(f"Filtering completed, results saved to {output_path}")
  2674. # %%
  2675. filtered_eid.to_csv('/data/xzhao14/AD_sample.txt', index=False)
  2676. # %%
  2677. import os
  2678. import pandas as pd
  2679. # pathfile path
  2680. folder_path = "/data484_2/xzhao14/FA_rep_stat/" # path
  2681. output_file = "/data484_2/xzhao14/FA_replication5_8_sig_snp.txt" # output file
  2682. # p-valuethreshold
  2683. p_threshold = 5e-8
  2684. # initialize DataFrame
  2685. filtered_data = pd.DataFrame()
  2686. # .fastGWA
  2687. for file_name in os.listdir(folder_path):
  2688. if file_name.endswith(".fastGWA"):
  2689. fea_name=file_name.split('_')[0]
  2690. print(fea_name)
  2691. file_path = os.path.join(folder_path, file_name)
  2692. # read file content
  2693. try:
  2694. data = pd.read_csv(file_path, sep="\t") #
  2695. except Exception as e:
  2696. print(f"Error reading file {file_name}: {e}")
  2697. continue
  2698. # p-value
  2699. if "P" not in data.columns:
  2700. print(f"Skipping file (missing 'p' column): {file_name}")
  2701. continue
  2702. # p-valuethreshold
  2703. filtered_rows = data[data["P"] < p_threshold]
  2704. # ,
  2705. if not filtered_rows.empty:
  2706. filtered_rows["feature"] = fea_name
  2707. # merge results
  2708. filtered_data = pd.concat([filtered_data, filtered_rows], ignore_index=True)
  2709. # , txt
  2710. if not filtered_data.empty:
  2711. filtered_data.to_csv(output_file, sep="\t", index=False)
  2712. print(f"Filtered data saved to: {output_file}")
  2713. else:
  2714. print("No rows passed the filtering criteria.")
  2715. # %% [markdown]
  2716. # # Disorder analysis
  2717. # - genetic correlation
  2718. # - MR analysis
  2719. # - colocalization
  2720. # %% [markdown]
  2721. # ## Genetic correlation
  2722. # %%
  2723. import os
  2724. import pandas as pd
  2725. import numpy as np
  2726. import matplotlib.pyplot as plt
  2727. from statsmodels.stats.multitest import multipletests
  2728. import seaborn as sns
  2729. def process_genetic_correlation_files(root_dir):
  2730. """
  2731. Process genetic correlation files and extract relevant data.
  2732. Parameters:
  2733. root_dir (str): Path to the directory containing genetic correlation result files.
  2734. Returns:
  2735. pd.DataFrame: Combined DataFrame with relevant data including FDR values.
  2736. """
  2737. # List all files in the directory
  2738. files = os.listdir(root_dir)
  2739. data = []
  2740. for file_name in files:
  2741. file_path = os.path.join(root_dir, file_name)
  2742. with open(file_path, 'r') as f:
  2743. all_lines = f.readlines()
  2744. if len(all_lines) < 4:
  2745. print(f"Warning: File {file_path} does not have enough lines.")
  2746. continue
  2747. last_line = all_lines[-4].strip() # Extract the fourth last line
  2748. data.append(last_line.split()) # Split the line into individual values and store in the list
  2749. # Define column names for the DataFrame
  2750. columns = ['P1', 'P2', 'rg', 'SE', 'Z', 'P', 'h2_obs', 'h2_obs_se', 'h2_int', 'h2_int_se', 'gcov_int', 'gcov_int_se']
  2751. df = pd.DataFrame(data, columns=columns)
  2752. # Extract clean names for P1 and P2
  2753. df['P1'] = df['P1'].str.extract(r'/([^/]+)\.sumstats\.gz')[0]
  2754. df['P2'] = df['P2'].str.extract(r'/([^/]+)\.sumstats\.gz')[0]
  2755. #
  2756. df['P1'] = df['P1'].str.split('_').str[0]
  2757. # Replace "NA" with NaN and drop rows with NaN values
  2758. df.replace("NA", np.nan, inplace=True)
  2759. df = df.dropna()
  2760. # Convert relevant columns to numeric
  2761. df['rg'] = df['rg'].astype(float)
  2762. df['SE'] = df['SE'].astype(float)
  2763. df['P'] = df['P'].astype(float)
  2764. # Calculate FDR
  2765. df['FDR'] = multipletests(df['P'], alpha=0.05, method='fdr_bh')[1]
  2766. return df
  2767. def create_heatmap(df, output_file=None):
  2768. """
  2769. Create a heatmap of FDR values with P1 as rows and P2 as columns.
  2770. Parameters:
  2771. df (pd.DataFrame): DataFrame containing P1, P2, and FDR values.
  2772. output_file (str, optional): Path to save the heatmap image.
  2773. """
  2774. # Pivot the data into a matrix
  2775. heatmap_data = df.pivot(index='P1', columns='P2', values='FDR')
  2776. # Plot the heatmap
  2777. plt.figure(figsize=(15, 10))
  2778. sns.heatmap(heatmap_data, annot=True, fmt=".2e", cmap='viridis', cbar_kws={'label': 'FDR'})
  2779. plt.title('Genetic Correlation Heatmap (FDR)')
  2780. plt.xlabel('P2')
  2781. plt.ylabel('P1')
  2782. if output_file:
  2783. plt.savefig(output_file, dpi=300, bbox_inches='tight')
  2784. print(f"Heatmap saved to {output_file}")
  2785. plt.show()
  2786. if __name__ == "__main__":
  2787. combined_df = pd.DataFrame()
  2788. for i in range(128):
  2789. root_dir = '/data/xzhao14/GWAS_output/QT'+str(i)+'_results/gc/QT'+str(i)+'_results.fastGWA' # Replace with your directory
  2790. #output_file = "genetic_correlation_heatmap.png" # Optional: Specify the output file for the heatmap
  2791. # Process files and create heatmap
  2792. df_gc = process_genetic_correlation_files(root_dir)
  2793. combined_df = pd.concat([combined_df, df_gc], ignore_index=True)
  2794. # print(df_gc)
  2795. #create_heatmap(df_gc, output_file)
  2796. # %%
  2797. #combined_df
  2798. import matplotlib.pyplot as plt
  2799. from matplotlib.colors import LinearSegmentedColormap
  2800. combined_df['P1'] = combined_df['P1'].str.replace(r'^QT', 'EP', regex=True)
  2801. combined_df_vio = combined_df.pivot(index='P1', columns='P2', values='rg')
  2802. combined_df_vio=np.abs(combined_df_vio)
  2803. colors = ["blue", "white", "red"]
  2804. custom_cmap = LinearSegmentedColormap.from_list("custom_cmap", colors)
  2805. # plot heatmap
  2806. plt.figure(figsize=(15, 10))
  2807. sns.heatmap(combined_df_vio, annot=False, cmap=custom_cmap, cbar_kws={'label': 'rg'},
  2808. center=0, linewidths=0.5)
  2809. plt.title('Genetic Correlation Heatmap (rg)')
  2810. plt.xlabel('P2')
  2811. plt.ylabel('P1')
  2812. plt.tight_layout()
  2813. # show heatmap
  2814. plt.show()
  2815. # %%
  2816. type(combined_df_vio)
  2817. data=combined_df_vio
  2818. col_means = data.median()
  2819. col_stddevs = data.std()
  2820. # by meansort
  2821. sorted_indices = col_means.sort_values().index
  2822. sorted_means = col_means[sorted_indices]
  2823. sorted_stddevs = col_stddevs[sorted_indices]
  2824. #
  2825. plt.figure(figsize=(8, 6))
  2826. plt.bar(range(len(sorted_means)), sorted_means, yerr=sorted_stddevs, capsize=5)
  2827. plt.xticks(range(len(sorted_means)), sorted_indices, rotation=45)
  2828. plt.xlabel("Columns (sorted by mean)")
  2829. plt.ylabel("genetic correlation")
  2830. plt.title("Mean and Standard Deviation of Each Column")
  2831. plt.tight_layout()
  2832. plt.show()
  2833. # %% [markdown]
  2834. # ### PRS information ##
  2835. # - 1. Clear up the GWAS summary statistic files
  2836. # - 2. Run the PRS-CS
  2837. # - 3. get the PRS score
  2838. # - 4. Using linear regression model to get the association between FA phenotype and PRS score
  2839. # %%
  2840. ## read the GWAS files #
  2841. import pandas as pd
  2842. AD_file=pd.read_csv('/data484_2/xzhao14/heart/heart_disorder_GWAS/finngen_R12_Q17_COMPLEX_CARD_DEFEC',sep='\t')
  2843. df_selected = AD_file[['rsids', 'ref','alt','beta','sebeta']].rename(columns={'rsids':'SNP', 'ref':'A1','alt':'A2', 'beta':'BETA','sebeta':'SE'})
  2844. # df_selected = AD_file[['SNP', 'A1','A2','OR','SE']]
  2845. # df_selected
  2846. # df_selected['A1'] = df_selected['A1'].str.upper()
  2847. # df_selected['A2'] = df_selected['A2'].str.upper()
  2848. df_selected.to_csv("/data484_2/xzhao14/heart/heart_disorder_GWAS/R12_Q17_COMPLEX_CARD_DEFEC.txt", sep="\t", index=False)
  2849. # %%
  2850. df_selected.to_csv("/data484_2/xzhao14/heart/heart_disorder_GWAS/CARDIAC_ARRHYTM.txt", sep="\t", index=False)
  2851. # %%
  2852. # combine the PRS files
  2853. import pandas as pd
  2854. import glob
  2855. # Read all 22 PRS-CS files
  2856. files = glob.glob("/data484_2/xzhao14/POST_GWAS/tools/PRScs/GWAS_novoerlap/PRS_output/HCM_pst_eff_a1_b0.5_phiauto_chr*.txt")
  2857. if not files:
  2858. print("⚠️ No PRS-CS result files found. Please check the path!")
  2859. exit()
  2860. df_list = []
  2861. for f in files:
  2862. try:
  2863. # Read the file (no header in these files), so manually specify column names
  2864. df = pd.read_csv(f, sep='\t', header=None)
  2865. # Ensure the file has at least 2 columns (SNP is in column 2)
  2866. if df.shape[1] < 2:
  2867. print(f"⚠️ File {f} might be malformed – not enough columns. Skipping!")
  2868. continue
  2869. # Manually assign generic column names (assuming consistent format across files)
  2870. df.columns = [f"col{i+1}" for i in range(df.shape[1])]
  2871. # Keep only the SNP column (2nd column) and other data columns
  2872. df.rename(columns={'col2': 'SNP'}, inplace=True)
  2873. df_list.append(df)
  2874. except Exception as e:
  2875. print(f"⚠️ Error reading file {f}: {e}")
  2876. continue
  2877. # Merge data
  2878. if df_list:
  2879. merged_df = pd.concat(df_list, ignore_index=True)
  2880. # Remove duplicates based on SNP column
  2881. merged_df.drop_duplicates(subset=['SNP'], inplace=True)
  2882. # Save the merged file
  2883. output_file = "/data484_2/xzhao14/POST_GWAS/tools/PRScs/disorder_prs/HCM_PRSCS.txt"
  2884. merged_df.to_csv(output_file, sep=" ", index=False, header=False)
  2885. print(f"✅ PRS-CS results have been merged and saved to: {output_file}")
  2886. else:
  2887. print("⚠️ No PRS-CS result files were successfully read!")
  2888. # %% [markdown]
  2889. # ## PRS linear regression
  2890. # %%
  2891. ## PRS linear regression
  2892. import pandas as pd
  2893. import statsmodels.api as sm
  2894. from sklearn.decomposition import PCA
  2895. def prs_linear_regression(pheno_file, covar_file, prs_file):
  2896. # Read data
  2897. pheno_df = pd.read_csv(pheno_file)
  2898. covar_df = pd.read_csv(covar_file, delim_whitespace=True)
  2899. prs_df = pd.read_csv(prs_file, delim_whitespace=True)
  2900. # Rename the second column (IID) as ID for consistency
  2901. pheno_df = pheno_df.rename(columns={pheno_df.columns[1]: 'IID'})
  2902. covar_df = covar_df.rename(columns={covar_df.columns[1]: 'IID'})
  2903. prs_df = prs_df.rename(columns={prs_df.columns[1]: 'IID'})
  2904. # Ensure IID is a string and remove duplicates and NaNs
  2905. for df in [pheno_df, covar_df, prs_df]:
  2906. df['IID'] = df['IID'].astype(str)
  2907. df.dropna(subset=['IID'], inplace=True)
  2908. df.drop_duplicates(subset=['IID'], inplace=True)
  2909. # Find the intersection of IDs across all three files
  2910. common_ids = set(pheno_df['IID']) & set(covar_df['IID']) & set(prs_df['IID'])
  2911. print(common_ids)
  2912. # Filter data to retain only common IDs
  2913. pheno_df = pheno_df[pheno_df['IID'].isin(common_ids)]
  2914. covar_df = covar_df[covar_df['IID'].isin(common_ids)]
  2915. prs_df = prs_df[prs_df['IID'].isin(common_ids)]
  2916. # Merge data
  2917. merged_df = pheno_df.merge(covar_df, on='IID', suffixes=('_pheno', '_covar'))
  2918. merged_df = merged_df.merge(prs_df[['IID', 'SCORESUM']], on='IID')
  2919. # Extract phenotype feature columns (excluding FID and IID)
  2920. pheno_features = merged_df.columns[2:len(pheno_df.columns)]
  2921. # Extract covariate columns (excluding IID)
  2922. covar_features = merged_df.columns[len(pheno_df.columns):len(pheno_df.columns) + len(covar_df.columns) - 2]
  2923. p_values = [] # Store p-values for each phenotype feature
  2924. results = {}
  2925. # Perform linear regression for each phenotype feature
  2926. for feature in pheno_features:
  2927. Y = merged_df[feature]
  2928. X = merged_df[['SCORESUM'] + list(covar_features)]
  2929. X = sm.add_constant(X) # Add intercept term
  2930. model = sm.OLS(Y, X).fit()
  2931. results[feature] = model.summary()
  2932. p_values.append(model.pvalues['SCORESUM'])
  2933. # Print p-values and their mean
  2934. print("P-values for each phenotype feature:", p_values)
  2935. print("Mean P-value:", sum(p_values) / len(p_values))
  2936. # Perform PCA on phenotype features
  2937. pca = PCA(n_components=2)
  2938. pheno_pca = pca.fit_transform(merged_df[pheno_features])
  2939. # Perform regression with PCA components as phenotype
  2940. pca_results = {}
  2941. for i in range(2):
  2942. Y = pheno_pca[:, i]
  2943. X = merged_df[['SCORESUM'] + list(covar_features)]
  2944. X = sm.add_constant(X) # Add intercept term
  2945. model = sm.OLS(Y, X).fit()
  2946. pca_results[f'PC{i+1}'] = model.summary()
  2947. return results, pca_results
  2948. #### Instance
  2949. phenotype='/data/xzhao14/discovery_dti_128_pheno.csv'
  2950. covar_file='/data/xzhao14/PRS_covar.csv'
  2951. prs_file='/data484_2/xzhao14/POST_GWAS/tools/PRScs/disorder_prs/Heene_mineal_individual_results.profile'
  2952. results, pca_results=prs_linear_regression(phenotype,covar_file,prs_file)
  2953. print(pca_results)
  2954. # %%
  2955. prs_residual
  2956. pheno_residuals
  2957. # %%
  2958. import numpy as np
  2959. import pandas as pd
  2960. cocar=pd.read_csv('/data4012/zxie3/gcta/T1_ccovar_discovery_v2',delim_whitespace=True)
  2961. qocar=pd.read_csv('/data4012/zxie3/gcta/T1_qcovar_discovery_v2',delim_whitespace=True)
  2962. df_combined = pd.concat([cocar, qocar.iloc[:,2:]], axis=1)
  2963. # %%
  2964. ##
  2965. import pandas as pd
  2966. import numpy as np
  2967. all_feature=pd.read_csv('/data/xzhao14/FA_all_phenotype.csv')
  2968. all_feature=all_feature.iloc[:,2:]
  2969. corr_matrix = all_feature.corr()
  2970. upper_triangle = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))
  2971. # correlation coefficient ( NaN)
  2972. mean_r = np.abs(upper_triangle.stack()).mean()
  2973. std_r = np.abs(upper_triangle.stack()).std()
  2974. print(f"\nAverage pairwise Pearson correlation (mean r): {mean_r:.4f}")
  2975. std_r
  2976. # %%
  2977. all_feature=all_feature[:,2:]
  2978. # %%
  2979. PRS_covar=df_combined[['FID', 'IID', 'PC0', 'PC1', 'PC2', 'PC3',
  2980. 'PC4', 'PC5', 'PC6', 'PC7', 'PC8', 'PC9','SEX', '54', 'AGE', 'AGE^2',
  2981. 'SEXxAGE', 'SEXxAGE^2', '25000']]
  2982. PRS_covar.to_csv('/data/xzhao14/PRS_covar.csv',sep=' ', index=False)
  2983. # %% [markdown]
  2984. # ## multi-variant association between UDIP_FA and PRS
  2985. # %%
  2986. import numpy as np
  2987. import pandas as pd
  2988. import statsmodels.api as sm
  2989. from sklearn.cross_decomposition import CCA
  2990. from sklearn.preprocessing import StandardScaler
  2991. from scipy.stats import pearsonr, f
  2992. import glob
  2993. import os
  2994. import warnings
  2995. def canoncorr_py(X, Y):
  2996. """
  2997. Canonical Correlation Analysis (CCA) with -log10(P) for higher precision.
  2998. Parameters:
  2999. X : ndarray (n_samples, p)
  3000. Y : ndarray (n_samples, q)
  3001. Returns:
  3002. stats : dict
  3003. Contains F-statistic, p-value, -log10(p-value), canonical correlation coefficients
  3004. """
  3005. X = np.asarray(X)
  3006. Y = np.asarray(Y)
  3007. if X.ndim == 1:
  3008. X = X.reshape(-1, 1)
  3009. if Y.ndim == 1:
  3010. Y = Y.reshape(-1, 1)
  3011. # Standardize
  3012. X_std = StandardScaler().fit_transform(X)
  3013. Y_std = StandardScaler().fit_transform(Y)
  3014. n_components = min(X.shape[1], Y.shape[1])
  3015. cca = CCA(n_components=n_components)
  3016. U, V = cca.fit_transform(X_std, Y_std)
  3017. # Canonical correlations
  3018. r = [pearsonr(U[:, i], V[:, i])[0] for i in range(n_components)]
  3019. # Wilks' Lambda
  3020. wilks = np.prod([1 - ri**2 for ri in r])
  3021. n = X.shape[0]
  3022. p = X.shape[1]
  3023. q = Y.shape[1]
  3024. s = n_components
  3025. df1 = p * q
  3026. df2 = n - 1 - 0.5 * (p + q + 1)
  3027. # Approximate F-statistic
  3028. if s > 0:
  3029. wilks_root = wilks**(1 / s)
  3030. approx_F = ((1 - wilks_root) / wilks_root) * (df2 / df1)
  3031. p_value = f.sf(approx_F, df1, df2) # Use survival function
  3032. with warnings.catch_warnings():
  3033. warnings.simplefilter("ignore") # Suppress warnings for log(0)
  3034. pF_log10 = -np.log10(p_value) if p_value > 0 else np.inf
  3035. else:
  3036. approx_F = np.nan
  3037. p_value = np.nan
  3038. pF_log10 = np.nan
  3039. stats = {
  3040. 'F': approx_F,
  3041. 'pF': p_value,
  3042. '-log10(pF)': pF_log10,
  3043. 'r': r
  3044. }
  3045. return stats
  3046. def prs_linear_regression(pheno_file, covar_file, prs_file):
  3047. """
  3048. Perform linear regression to obtain residuals for phenotype and PRS.
  3049. Parameters:
  3050. pheno_file : str
  3051. Path to phenotype CSV file.
  3052. covar_file : str
  3053. Path to covariates CSV file (with whitespace delimiter).
  3054. prs_file : str
  3055. Path to PRS file (with whitespace delimiter).
  3056. Returns:
  3057. pheno_resid : DataFrame
  3058. DataFrame of phenotype residuals (excluding IID column).
  3059. prs_resid : Series
  3060. Series of PRS residuals.
  3061. """
  3062. # Read files
  3063. pheno_df = pd.read_csv(pheno_file)
  3064. covar_df = pd.read_csv(covar_file, delim_whitespace=True)
  3065. prs_df = pd.read_csv(prs_file, delim_whitespace=True)
  3066. # Rename IID column to 'IID'
  3067. pheno_df = pheno_df.rename(columns={pheno_df.columns[1]: 'IID'})
  3068. covar_df = covar_df.rename(columns={covar_df.columns[1]: 'IID'})
  3069. prs_df = prs_df.rename(columns={prs_df.columns[1]: 'IID'})
  3070. # Ensure IID is string and remove duplicates/missing values
  3071. for df in [pheno_df, covar_df, prs_df]:
  3072. df['IID'] = df['IID'].astype(str)
  3073. df.dropna(subset=['IID'], inplace=True)
  3074. df.drop_duplicates(subset=['IID'], inplace=True)
  3075. # Keep common IIDs among the three files
  3076. common_ids = set(pheno_df['IID']) & set(covar_df['IID']) & set(prs_df['IID'])
  3077. print(f"Number of common IDs in {prs_file}: {len(common_ids)}")
  3078. pheno_df = pheno_df[pheno_df['IID'].isin(common_ids)]
  3079. covar_df = covar_df[covar_df['IID'].isin(common_ids)]
  3080. prs_df = prs_df[prs_df['IID'].isin(common_ids)]
  3081. # Merge data on IID
  3082. merged_df = pheno_df.merge(covar_df, on='IID', suffixes=('_pheno', '_covar'))
  3083. merged_df = merged_df.merge(prs_df[['IID', 'SCORESUM']], on='IID')
  3084. # Determine phenotype features and covariate features based on column positions
  3085. pheno_features = merged_df.columns[2:len(pheno_df.columns)]
  3086. covar_features = merged_df.columns[len(pheno_df.columns):len(pheno_df.columns) + len(covar_df.columns) - 2]
  3087. # Regress PRS on covariates to get residuals
  3088. X_covar = sm.add_constant(merged_df[covar_features])
  3089. prs_model = sm.OLS(merged_df['SCORESUM'], X_covar).fit()
  3090. prs_residual = prs_model.resid
  3091. merged_df['PRS_resid'] = prs_residual
  3092. # Regress each phenotype on covariates to get residuals
  3093. pheno_resid_df = pd.DataFrame()
  3094. pheno_resid_df['IID'] = merged_df['IID']
  3095. for feature in pheno_features:
  3096. Y = merged_df[feature]
  3097. X = sm.add_constant(merged_df[covar_features])
  3098. model = sm.OLS(Y, X).fit()
  3099. pheno_resid_df[feature + '_resid'] = model.resid
  3100. return pheno_resid_df.iloc[:, 1:], merged_df['PRS_resid']
  3101. def process_all_prs_files(pheno_file, covar_file, prs_dir, output_file):
  3102. """
  3103. Process all PRS files in a directory that end with 'individual_results.profile',
  3104. perform CCA between phenotype residuals and PRS residuals, and save the results as a CSV file.
  3105. Parameters:
  3106. pheno_file : str
  3107. Path to the phenotype CSV file.
  3108. covar_file : str
  3109. Path to the covariate CSV file.
  3110. prs_dir : str
  3111. Directory containing PRS files.
  3112. output_file : str
  3113. Path to the output CSV file where results will be saved.
  3114. """
  3115. # Find all PRS files ending with 'individual_results.profile'
  3116. prs_files = glob.glob(os.path.join(prs_dir, '*individual_results.profile'))
  3117. results = []
  3118. # Iterate over each PRS file
  3119. for prs_file in prs_files:
  3120. try:
  3121. # Obtain phenotype and PRS residuals using linear regression
  3122. pheno_residuals, prs_residual = prs_linear_regression(pheno_file, covar_file, prs_file)
  3123. # Perform CCA between phenotype residuals and PRS residuals
  3124. stats = canoncorr_py(pheno_residuals, prs_residual)
  3125. # Since y is one-dimensional, the canonical correlation list should have one element
  3126. canonical_r = stats['r'][0] if stats['r'] else np.nan
  3127. # Append results
  3128. results.append({
  3129. 'prs_file': os.path.basename(prs_file),
  3130. 'F_statistic': stats['F'],
  3131. 'p_value': stats['pF'],
  3132. 'canonical_correlation': canonical_r
  3133. })
  3134. except Exception as e:
  3135. print(f"Error processing {prs_file}: {e}")
  3136. results.append({
  3137. 'prs_file': os.path.basename(prs_file),
  3138. 'F_statistic': np.nan,
  3139. 'p_value': np.nan,
  3140. 'canonical_correlation': np.nan,
  3141. 'error': str(e)
  3142. })
  3143. # Create a DataFrame from results and save to CSV
  3144. results_df = pd.DataFrame(results)
  3145. results_df.to_csv(output_file, index=False)
  3146. print(f"Results saved to {output_file}")
  3147. return results_df
  3148. # Example usage:
  3149. if __name__ == '__main__':
  3150. phenotype_file = '/data/xzhao14/discovery_dti_128_pheno.csv'
  3151. covar_file = '/data/xzhao14/PRS_covar.csv'
  3152. prs_directory = '/data484_2/xzhao14/POST_GWAS/tools/PRScs/disorder_prs'
  3153. output_csv = '/data/xzhao14/cca_FA_all_results.csv'
  3154. result_association=process_all_prs_files(phenotype_file, covar_file, prs_directory, output_csv)
  3155. # %%
  3156. # category name : 4283: Number of rounds of numeric memory test performed 20016: Fluid intelligence score 20018: Prospective memory result 20023: Mean time to correctly identify matches
  3157. # 20139 : Number of letters correctly identified
  3158. cognitive_score_selected=cognitive_score[['eid','p4283_i2','p20016_i2','p20018_i2','p20023_i2','p20139_i2']]
  3159. # %% [markdown]
  3160. # ### Phenotype association ###
  3161. # - associated with T1 and T2
  3162. # - path: `/data4012/zxie3/MRI_AE_small_training_set_GWAS/T1_pheno_discovery`
  3163. # - build the linear regression model to get the association, adjust the covariate
  3164. # %%
  3165. ###
  3166. import pandas as pd
  3167. T1_pheno=pd.read_csv('/data4012/zxie3/MRI_AE_small_training_set_GWAS/T1_pheno_discovery',delim_whitespace=True)
  3168. T2_pheno=pd.read_csv('/data4012/zxie3/MRI_AE_small_training_set_GWAS/T2_pheno_discovery',delim_whitespace=True)
  3169. covar=pd.read_csv('/data4012/zxie3/MRI_AE_small_training_set_GWAS/T1_covar_discovery',delim_whitespace=True)
  3170. FA_pheno=pd.read_csv('/data/xzhao14/discovery_dti_128_pheno.csv')
  3171. # %%
  3172. import numpy as np
  3173. import pandas as pd
  3174. import matplotlib.pyplot as plt
  3175. import seaborn as sns
  3176. import numpy as np
  3177. import pandas as pd
  3178. import matplotlib.pyplot as plt
  3179. import seaborn as sns
  3180. def plot_blocked_heatmap(matrix, block_size=128, cmap="Blues", output_pdf="heatmap.pdf"):
  3181. """
  3182. plot the entire matrix on a single heatmap, add separators every block_size rows/columns, and save as a PDF.
  3183. Parameters:
  3184. - matrix: np.array pd.DataFrame, matrix
  3185. - block_size: , default 128 ( 128 /)
  3186. - cmap: colormap, default "coolwarm"
  3187. - output_pdf: PDF path, default "heatmap.pdf"
  3188. """
  3189. # if it's a NumPy array, convert to DataFrame= if isinstance(matrix, np.ndarray):
  3190. matrix = pd.DataFrame(matrix)
  3191. num_rows, num_cols = matrix.shape # matrix
  3192. # create heatmap
  3193. plt.figure(figsize=(12, 10))
  3194. ax = sns.heatmap(matrix, cmap=cmap, cbar=True, linewidths=0.5)
  3195. # ( 128 )
  3196. for i in range(block_size, num_rows, block_size):
  3197. ax.hlines(i, *ax.get_xlim(), colors="black", linewidth=1.5, linestyles="dashed")
  3198. # ( 128 )
  3199. for j in range(block_size, num_cols, block_size):
  3200. ax.vlines(j, *ax.get_ylim(), colors="black", linewidth=1.5, linestyles="dashed")
  3201. plt.title("Phenotype correlation Heatmap with Grid Separators", fontsize=14)
  3202. plt.xlabel("Columns", fontsize=12)
  3203. plt.ylabel("Rows", fontsize=12)
  3204. # ** PDF**
  3205. plt.savefig(output_pdf, format="pdf", bbox_inches="tight")
  3206. plt.close() # , memory
  3207. print(f"✅ save {output_pdf}")
  3208. # **Example data**
  3209. # matrix = np.random.rand(500, 500) # 500x500 randommatrix
  3210. plot_blocked_heatmap(explained_variance_matrix, block_size=128, output_pdf="/data/xzhao14/FA_figures/phenotype_blocked_heatmap.pdf")
  3211. # **Example data*
  3212. # %%
  3213. import pandas as pd
  3214. import numpy as np
  3215. import statsmodels.api as sm
  3216. def compute_feature_explained_variance_cca(df1, df2, df3, covariate_df):
  3217. """
  3218. Computes CCA-based explained variance between three datasets, adjusting for covariates.
  3219. Parameters:
  3220. df1, df2, df3 (pd.DataFrame): DataFrames with IID and 128 features (QT0-QT127)
  3221. covariate_df (pd.DataFrame): DataFrame with IID and covariates
  3222. Returns:
  3223. explained_variance_matrix (np.ndarray): 3x3 matrix of explained variance ratios
  3224. """
  3225. # Step 1: Ensure IID is consistent across all dataframes
  3226. for df in [df1, df2, df3, covariate_df]:
  3227. df['IID'] = df['IID'].astype(str)
  3228. df.dropna(subset=['IID'], inplace=True)
  3229. df.drop_duplicates(subset=['IID'], inplace=True)
  3230. # Step 2: Align samples by common IID
  3231. common_iid = set(df1['IID']) & set(df2['IID']) & set(df3['IID']) & set(covariate_df['IID'])
  3232. def filter_by_common_ids(df):
  3233. return df[df['IID'].isin(common_iid)]
  3234. df1, df2, df3, covariate_df = map(filter_by_common_ids, [df1, df2, df3, covariate_df])
  3235. # Step 3: Sort by IID to align rows
  3236. for df in [df1, df2, df3, covariate_df]:
  3237. df.sort_values('IID', inplace=True)
  3238. df.reset_index(drop=True, inplace=True)
  3239. # Step 4: Extract numeric feature matrices and covariates
  3240. features_1 = df1.iloc[:, 1:].apply(pd.to_numeric, errors='coerce').values
  3241. features_2 = df2.iloc[:, 1:].apply(pd.to_numeric, errors='coerce').values
  3242. features_3 = df3.iloc[:, 1:].apply(pd.to_numeric, errors='coerce').values
  3243. covariates = covariate_df.iloc[:, 1:].apply(pd.to_numeric, errors='coerce').values
  3244. # Step 5: Add intercept to covariates
  3245. covariates = sm.add_constant(covariates)
  3246. # Step 6: Regress out covariates to get residuals
  3247. def compute_residuals(features, covariates):
  3248. residuals = np.zeros_like(features)
  3249. for i in range(features.shape[1]):
  3250. model = sm.OLS(features[:, i], covariates).fit()
  3251. residuals[:, i] = model.resid
  3252. return residuals
  3253. residuals_1 = compute_residuals(features_1, covariates)
  3254. residuals_2 = compute_residuals(features_2, covariates)
  3255. residuals_3 = compute_residuals(features_3, covariates)
  3256. # Step 7: Function to compute explained variance from X to Y and Y to X
  3257. def cca_explained_variance(X, Y):
  3258. # Perform SVD
  3259. U1, S1, _ = np.linalg.svd(X, full_matrices=False) # U1: (n, 128), S1: (128,)
  3260. U2, S2, _ = np.linalg.svd(Y, full_matrices=False) # U2: (n, 128), S2: (128,)
  3261. # SVD of U1.T @ U2
  3262. M = U1.T @ U2 # shape (128, 128)
  3263. U, S, Vt = np.linalg.svd(M, full_matrices=False) # S: (128,)
  3264. # Compute explained variance
  3265. A = (S1[:, None] * U) * S # shape (128, 128)
  3266. B = (S2[:, None] * Vt.T) * S # shape (128, 128)
  3267. var_explained_X_by_Y = np.linalg.norm(A, 'fro')**2 / np.linalg.norm(S1, ord=2)**2
  3268. var_explained_Y_by_X = np.linalg.norm(B, 'fro')**2 / np.linalg.norm(S2, ord=2)**2
  3269. return var_explained_X_by_Y, var_explained_Y_by_X
  3270. # Step 8: Compute pairwise explained variance matrix
  3271. explained_variance_matrix = np.zeros((3, 3))
  3272. var_X_by_Y, var_Y_by_X = cca_explained_variance(residuals_1, residuals_2)
  3273. explained_variance_matrix[0, 1] = var_X_by_Y
  3274. explained_variance_matrix[1, 0] = var_Y_by_X
  3275. var_X_by_Z, var_Z_by_X = cca_explained_variance(residuals_1, residuals_3)
  3276. explained_variance_matrix[0, 2] = var_X_by_Z
  3277. explained_variance_matrix[2, 0] = var_Z_by_X
  3278. var_Y_by_Z, var_Z_by_Y = cca_explained_variance(residuals_2, residuals_3)
  3279. explained_variance_matrix[1, 2] = var_Y_by_Z
  3280. explained_variance_matrix[2, 1] = var_Z_by_Y
  3281. # Self-explained variance = 1.0
  3282. np.fill_diagonal(explained_variance_matrix, 1.0)
  3283. return explained_variance_matrix
  3284. # Example data
  3285. # CCA variance
  3286. explained_variance_matrix=compute_feature_explained_variance_cca(T1_pheno,T2_pheno,FA_pheno,covar)
  3287. # print(" CCA variancematrix: ")
  3288. # print(explained_variance_matrix)
  3289. # %%
  3290. print(explained_variance_matrix)
  3291. # %%
  3292. import pandas as pd
  3293. import numpy as np
  3294. import statsmodels.api as sm
  3295. from scipy.stats import pearsonr
  3296. def compute_feature_correlation(df1, df2, covariate_df):
  3297. """
  3298. DataFrame128correlation, the effects of covariates.
  3299. Parameters:
  3300. df1, df2, df3 (pd.DataFrame): DataFrame (IID) 128 (QT0-QT127)
  3301. covariate_df (pd.DataFrame): (IID) covariates (2)
  3302. Returns:
  3303. tuple: (correlation_matrix, p_value_matrix) - 384×384 correlationmatrix p-valuematrix
  3304. """
  3305. # **1. ensure IID , class**
  3306. for df in [df1, df2, df3, covariate_df]:
  3307. df['IID'] = df['IID'].astype(str) # class
  3308. df.dropna(subset=['IID'], inplace=True) # NaN
  3309. df.drop_duplicates(subset=['IID'], inplace=True) #
  3310. # 4 DataFrame IID
  3311. common_iid = set(df1['IID']) & set(df2['IID']) & set(df3['IID']) & set(covariate_df['IID'])
  3312. # filter data
  3313. def filter_by_common_ids(df):
  3314. return df[df['IID'].isin(common_iid)]
  3315. ##
  3316. df1, df2, df3, covariate_df = map(filter_by_common_ids, [df1, df2, df3, covariate_df])
  3317. print(df1.shape)
  3318. # **4. by IID sort, ensure**
  3319. df1 = df1.sort_values('IID').reset_index(drop=True)
  3320. df2 = df2.sort_values('IID').reset_index(drop=True)
  3321. df3 = df3.sort_values('IID').reset_index(drop=True)
  3322. covariate_df = covariate_df.sort_values('IID').reset_index(drop=True)
  3323. # **5. **
  3324. merged_features = pd.concat([df1.iloc[:, 1:], df2.iloc[:, 1:], df3.iloc[:, 1:]], axis=1) # (N, 384)
  3325. # **6. ensure all numeric columns are converted to `float` class, prevent `str` classerror**
  3326. merged_features = merged_features.apply(pd.to_numeric, errors='coerce')
  3327. # **7. covariates**
  3328. feature_matrix = merged_features.iloc[:, 1:].values # (N, 384) IID
  3329. covariates = covariate_df.iloc[:, 1:].apply(pd.to_numeric, errors='coerce').values # (N, covariate_count) IID
  3330. # **8. ()covariatesmatrix**
  3331. covariates = sm.add_constant(covariates)
  3332. # **9. correlation ()**
  3333. num_features = feature_matrix.shape[1]
  3334. correlation_matrix = np.zeros((num_features, num_features))
  3335. p_value_matrix = np.zeros((num_features, num_features))
  3336. ##
  3337. for i in range(num_features):
  3338. for j in range(i, num_features): # ,
  3339. # ** i **
  3340. model_i = sm.OLS(feature_matrix[:, i], covariates).fit()
  3341. residuals_i = model_i.resid
  3342. # ** j **
  3343. model_j = sm.OLS(feature_matrix[:, j], covariates).fit()
  3344. residuals_j = model_j.resid
  3345. # ** Pearson correlation**
  3346. corr, p_value = pearsonr(residuals_i, residuals_j)
  3347. correlation_matrix[i, j] = correlation_matrix[j, i] = corr
  3348. p_value_matrix[i, j] = p_value_matrix[j, i] = p_value
  3349. return correlation_matrix, p_value_matrix
  3350. # %%
  3351. # Associated with cognitive functions ##
  3352. import numpy as np
  3353. import pandas as pd
  3354. import statsmodels.api as sm
  3355. from sklearn.cross_decomposition import CCA
  3356. from sklearn.preprocessing import StandardScaler
  3357. from scipy.stats import pearsonr, f
  3358. import glob
  3359. import os
  3360. def canoncorr_py(X, Y):
  3361. """
  3362. Python implementation of MATLAB's canoncorr function.
  3363. Parameters:
  3364. X : ndarray (n_samples, p)
  3365. First variable group (e.g., predictors)
  3366. Y : ndarray (n_samples, q)
  3367. Second variable group (e.g., outcomes)
  3368. Returns:
  3369. stats : dict
  3370. Dictionary with F statistic, p-value (Wilks' Lambda test),
  3371. and canonical correlation coefficients (list 'r')
  3372. """
  3373. X = np.asarray(X)
  3374. Y = np.asarray(Y)
  3375. # If the input is a one-dimensional array, convert it to a two-dimensional array
  3376. if X.ndim == 1:
  3377. X = X.reshape(-1, 1)
  3378. if Y.ndim == 1:
  3379. Y = Y.reshape(-1, 1)
  3380. # Standardize X and Y
  3381. scaler_X = StandardScaler()
  3382. scaler_Y = StandardScaler()
  3383. X_std = scaler_X.fit_transform(X)
  3384. Y_std = scaler_Y.fit_transform(Y)
  3385. # Determine the number of canonical components (minimum number of columns in X and Y)
  3386. n_components = min(X.shape[1], Y.shape[1])
  3387. # Run Canonical Correlation Analysis (CCA)
  3388. cca = CCA(n_components=n_components)
  3389. U, V = cca.fit_transform(X_std, Y_std)
  3390. A = cca.x_weights_
  3391. B = cca.y_weights_
  3392. # Calculate canonical correlations for each component
  3393. r = [pearsonr(U[:, i], V[:, i])[0] for i in range(n_components)]
  3394. # Perform Wilks' Lambda test for overall significance
  3395. wilks = np.prod([1 - ri**2 for ri in r])
  3396. n = X.shape[0]
  3397. p = X.shape[1]
  3398. q = Y.shape[1]
  3399. s = n_components
  3400. df1 = p * q
  3401. df2 = n - 1 - 0.5 * (p + q + 1)
  3402. approx_F = ((1 - wilks**(1/s)) / (wilks**(1/s))) * (df2 / df1) if s > 0 else np.nan
  3403. p_value = 1 - f.cdf(approx_F, df1, df2) if s > 0 else np.nan
  3404. stats = {
  3405. 'F': approx_F,
  3406. 'pF': p_value,
  3407. 'r': r
  3408. }
  3409. return stats
  3410. def prs_linear_regression(pheno_file, covar_file, prs_file):
  3411. """
  3412. Perform linear regression to obtain residuals for phenotype and PRS.
  3413. Parameters:
  3414. pheno_file : str
  3415. Path to phenotype CSV file.
  3416. covar_file : str
  3417. Path to covariates CSV file (with whitespace delimiter).
  3418. prs_file : str
  3419. Path to PRS file (with whitespace delimiter).
  3420. Returns:
  3421. pheno_resid : DataFrame
  3422. DataFrame of phenotype residuals (excluding IID column).
  3423. prs_resid : Series
  3424. Series of PRS residuals.
  3425. """
  3426. # Read files
  3427. pheno_df = pd.read_csv(pheno_file)
  3428. covar_df = pd.read_csv(covar_file, delim_whitespace=True)
  3429. prs_df = pd.read_csv(prs_file, sep='\t')
  3430. # Rename IID column to 'IID'
  3431. pheno_df = pheno_df.rename(columns={pheno_df.columns[1]: 'IID'})
  3432. covar_df = covar_df.rename(columns={covar_df.columns[1]: 'IID'})
  3433. prs_df = prs_df.rename(columns={prs_df.columns[1]: 'IID'})
  3434. # Ensure IID is string and remove duplicates/missing values
  3435. for df in [pheno_df, covar_df, prs_df]:
  3436. df['IID'] = df['IID'].astype(str)
  3437. df.dropna(subset=['IID'], inplace=True)
  3438. df.drop_duplicates(subset=['IID'], inplace=True)
  3439. # Keep common IIDs among the three files
  3440. common_ids = set(pheno_df['IID']) & set(covar_df['IID']) & set(prs_df['IID'])
  3441. print(f"Number of common IDs in {prs_file}: {len(common_ids)}")
  3442. pheno_df = pheno_df[pheno_df['IID'].isin(common_ids)]
  3443. covar_df = covar_df[covar_df['IID'].isin(common_ids)]
  3444. prs_df = prs_df[prs_df['IID'].isin(common_ids)]
  3445. # Merge data on IID
  3446. prs_colname=prs_df.columns
  3447. print(prs_colname)
  3448. merged_df = pheno_df.merge(covar_df, on='IID', suffixes=('_pheno', '_covar'))
  3449. merged_df = merged_df.merge(prs_df[['IID', prs_colname[2]]], on='IID')
  3450. # Determine phenotype features and covariate features based on column positions
  3451. pheno_features = merged_df.columns[2:len(pheno_df.columns)]
  3452. covar_features = merged_df.columns[len(pheno_df.columns):len(pheno_df.columns) + len(covar_df.columns) - 2]
  3453. # Regress PRS on covariates to get residuals
  3454. X_covar = sm.add_constant(merged_df[covar_features])
  3455. prs_model = sm.OLS(merged_df[prs_colname[2]], X_covar).fit()
  3456. prs_residual = prs_model.resid
  3457. merged_df['PRS_resid'] = prs_residual
  3458. # Regress each phenotype on covariates to get residuals
  3459. pheno_resid_df = pd.DataFrame()
  3460. pheno_resid_df['IID'] = merged_df['IID']
  3461. for feature in pheno_features:
  3462. Y = merged_df[feature]
  3463. X = sm.add_constant(merged_df[covar_features])
  3464. model = sm.OLS(Y, X).fit()
  3465. pheno_resid_df[feature + '_resid'] = model.resid
  3466. return pheno_resid_df.iloc[:, 1:], merged_df['PRS_resid']
  3467. def process_all_prs_files(pheno_file, covar_file, prs_dir, output_file):
  3468. """
  3469. Process all PRS files in a directory that end with 'individual_results.profile',
  3470. perform CCA between phenotype residuals and PRS residuals, and save the results as a CSV file.
  3471. Parameters:
  3472. pheno_file : str
  3473. Path to the phenotype CSV file.
  3474. covar_file : str
  3475. Path to the covariate CSV file.
  3476. prs_dir : str
  3477. Directory containing PRS files.
  3478. output_file : str
  3479. Path to the output CSV file where results will be saved.
  3480. """
  3481. # Find all PRS files ending with 'individual_results.profile'
  3482. prs_files = glob.glob(os.path.join(prs_dir, '*.txt'))
  3483. results = []
  3484. # Iterate over each PRS file
  3485. for prs_file in prs_files:
  3486. try:
  3487. # Obtain phenotype and PRS residuals using linear regression
  3488. pheno_residuals, prs_residual = prs_linear_regression(pheno_file, covar_file, prs_file)
  3489. # Perform CCA between phenotype residuals and PRS residuals
  3490. stats = canoncorr_py(pheno_residuals, prs_residual)
  3491. # Since y is one-dimensional, the canonical correlation list should have one element
  3492. canonical_r = stats['r'][0] if stats['r'] else np.nan
  3493. # Append results
  3494. results.append({
  3495. 'prs_file': os.path.basename(prs_file),
  3496. 'F_statistic': stats['F'],
  3497. 'p_value': stats['pF'],
  3498. 'canonical_correlation': canonical_r
  3499. })
  3500. except Exception as e:
  3501. print(f"Error processing {prs_file}: {e}")
  3502. results.append({
  3503. 'prs_file': os.path.basename(prs_file),
  3504. 'F_statistic': np.nan,
  3505. 'p_value': np.nan,
  3506. 'canonical_correlation': np.nan,
  3507. 'error': str(e)
  3508. })
  3509. # Create a DataFrame from results and save to CSV
  3510. results_df = pd.DataFrame(results)
  3511. results_df.to_csv(output_file, index=False)
  3512. print(f"Results saved to {output_file}")
  3513. return results_df
  3514. # Example usage:
  3515. if __name__ == '__main__':
  3516. phenotype_file = '/data/xzhao14/discovery_dti_128_pheno.csv'
  3517. covar_file = '/data/xzhao14/PRS_covar.csv'
  3518. prs_directory = '/data484_2/xzhao14/cognitive_files'
  3519. output_csv = '/data/xzhao14/cca_FA_cog_results.csv'
  3520. result_association=process_all_prs_files(phenotype_file, covar_file, prs_directory, output_csv)
  3521. # %%
  3522. # Association with cognitive score,include four cognitive subclass
  3523. import pandas as pd
  3524. import os
  3525. cognitive_score=pd.read_csv('/data484_2/xzhao14/cognitive_phe.txt',sep='\t')
  3526. # category name : 4283: Number of rounds of numeric memory test performed 20016: Fluid intelligence score 20018: Prospective memory result 20023: Mean time to correctly identify matches
  3527. # 20139 : Number of letters correctly identified
  3528. cognitive_score_selected=cognitive_score[['eid','p4283_i2','p20016_i2','p20018_i2','p20023_i2','p20139_i2']]
  3529. #
  3530. df=cognitive_score_selected
  3531. # Add FID and IID columns based on 'eid'
  3532. # Define the output directory
  3533. output_dir = "/data484_2/xzhao14/cognitive_files"
  3534. # Add FID and IID columns based on 'eid'
  3535. df['FID'] = df['eid']
  3536. df['IID'] = df['eid']
  3537. os.makedirs(output_dir, exist_ok=True) # Create the directory if it doesn't exist
  3538. # Get protein columns (from column index 2 up to before FID/IID)
  3539. # Adjust this if FID/IID are not the last columns
  3540. protein_columns = df.columns[2:-2]
  3541. # Loop through each protein column
  3542. for col in protein_columns:
  3543. # Select FID, IID, and current protein column
  3544. sub_df = df[['FID', 'IID', col]].copy()
  3545. # Drop rows with missing values in the current protein column
  3546. sub_df = sub_df.dropna(subset=[col])
  3547. # Save to txt file if not empty
  3548. if not sub_df.empty:
  3549. output_path = os.path.join(output_dir, f"{col}.txt")
  3550. sub_df.to_csv(output_path, sep='\t', index=False)
  3551. # %%
  3552. import pandas as pd
  3553. # PRS
  3554. prs_file = "/data484_2/xzhao14/POST_GWAS/tools/PRScs/disorder_prs/SCZ_PRSCS.txt"
  3555. prs_df = pd.read_csv(prs_file, delim_whitespace=True, header=None)
  3556. # SNP ID 1
  3557. prs_df.rename(columns={1: 'SNP'}, inplace=True)
  3558. # statistics SNP
  3559. duplicate_snps = prs_df['SNP'].value_counts()
  3560. duplicate_snps = duplicate_snps[duplicate_snps > 1]
  3561. if not duplicate_snps.empty:
  3562. print(f"⚠️ PRS file contains {len(duplicate_snps)} duplicate SNPs, for example:")
  3563. print(duplicate_snps.head(10))
  3564. # SNP
  3565. duplicate_snps.index.to_series().to_csv("/data484_2/xzhao14/POST_GWAS/tools/PRScs/disorder_prs/duplicate_snps_from_prs.txt", index=False, header=False)
  3566. # drop duplicates SNP
  3567. prs_df.drop_duplicates(subset=['SNP'], inplace=True)
  3568. # drop duplicates PRS
  3569. output_file = "/data484_2/xzhao14/POST_GWAS/tools/PRScs/disorder_prs/SCZ_PRSCS_filtered.txt"
  3570. prs_df.to_csv(output_file, sep=" ", index=False, header=False)
  3571. print(f"✅ PRS results after removing duplicate SNPs saved to:{output_file}")
  3572. # %%
  3573. import pandas as pd
  3574. sampple_data=pd.read_csv('/data484_2/xzhao14/FA_statis/QT108_results.fastGWA',sep='\t')
  3575. meta_path='/data484_2/xzhao14/POST_GWAS/tools/generic-metal/meta/QT411.tbl'
  3576. data=pd.read_csv(meta_path,sep='\t')
  3577. match = re.search(r'/([^/]+)\d\.tbl$', meta_path)
  3578. result = match.group(1)
  3579. df1_ordered = sampple_data.set_index('SNP').loc[data['MarkerName']].reset_index()
  3580. data['CHR']=df1_ordered['CHR']
  3581. data['POS']=df1_ordered['POS']
  3582. data['A1']=df1_ordered['A1']
  3583. data['A2']=df1_ordered['A2']
  3584. data['N']=df1_ordered['N']
  3585. data['AF1']=df1_ordered['AF1']
  3586. df_selected = data[['MarkerName','CHR', 'A1','A2','AF1','Effect','StdErr','P-value','N']].rename(columns={'MarkerName': 'SNP', 'Effect': 'BETA','StdErr':'SE','P-value':'P'})
  3587. our_sample=result+'_FA.txt'
  3588. #####
  3589. df_selected.to_csv(result,sep='\t',index=None)
  3590. # %%
  3591. meta_path='/data484_2/xzhao14/POST_GWAS/tools/generic-metal/meta/QT411.tbl'
  3592. match_d = re.search(r'/([^/]+)\d\.tbl$', meta_path)
  3593. result = match_d.group(1)
  3594. # %%
  3595. ##
  3596. import pandas as pd
  3597. dd=pd.read_csv('/data484_1/kmohammed2/anaseri_bgen_processing/ccovar',delim_whitespace=True)
  3598. dd_se=dd[['FID','IID']]
  3599. # %%
  3600. dd_se.to_csv('/data484_2/xzhao14/heart/6w_sample.txt',index=False,sep='\t')
  3601. # %%
  3602. import pandas as pd
  3603. import numpy as np
  3604. import matplotlib.pyplot as plt
  3605. def manhattan_plot(data, significance_threshold=5e-8, suggestive_threshold=1e-5, title="Manhattan Plot"):
  3606. """
  3607. Draw a Manhattan plot.
  3608. Parameters:
  3609. - data: pandas DataFrame with columns "CHR", "POS", "P"
  3610. - significance_threshold: genome-wide significance threshold (default 5e-8)
  3611. - suggestive_threshold: suggestive threshold (default 1e-5)
  3612. - title: plot title
  3613. """
  3614. # Ensure numeric data types
  3615. data["CHR"] = data["CHR"].astype(int)
  3616. data["BP"] = data["POS"].astype(int)
  3617. data["-log10(P)"] = -np.log10(data["P"].astype(float).clip(lower=1e-300))
  3618. # Sort by chromosome and position
  3619. data = data.sort_values(["CHR", "BP"])
  3620. # Create cumulative index for plotting
  3621. data["ind"] = range(len(data))
  3622. chrom_df = data.groupby("CHR")["ind"].median()
  3623. # Assign colors based on chromosome
  3624. colors = ["#4daf4a", "#377eb8"]
  3625. color_list = [colors[i % 2] for i in data["CHR"]]
  3626. # Plot
  3627. plt.figure(figsize=(12, 6))
  3628. plt.scatter(data["ind"], data["-log10(P)"], c=color_list, alpha=0.6, s=10)
  3629. # Add threshold lines
  3630. plt.axhline(y=-np.log10(significance_threshold), color="red", linestyle="--", label=f"Genome-wide: 5e-8")
  3631. plt.axhline(y=-np.log10(suggestive_threshold), color="blue", linestyle="--", label=f"Suggestive: 1e-5")
  3632. # Customize axes
  3633. plt.xticks(ticks=chrom_df.values, labels=chrom_df.index, fontsize=10)
  3634. plt.xlabel("Chromosome", fontsize=12)
  3635. plt.ylabel("-log10(P-value)", fontsize=12)
  3636. plt.title(title, fontsize=14)
  3637. plt.legend()
  3638. # Grid and layout
  3639. plt.grid(True, linestyle="--", alpha=0.5)
  3640. plt.tight_layout()
  3641. plt.show()
  3642. # Load your GWAS result
  3643. data = pd.read_csv('/data484_2/xzhao14/heart/HEART_2ich_inputation_3D_GAN.txt', sep='\t')
  3644. selected_data=data[data['P']<5e-8]
  3645. print(selected_data.shape)
  3646. # Run the plot
  3647. #manhattan_plot(data, title="4ch_3D_GAN_Manhattan Plot")
  3648. # %%
  3649. #
  3650. import os
  3651. f2='/data484_2/xzhao14/FA_meta/ldsc_sumstats/QT32_FA.sumstats.sumstats.gz'
  3652. os.path.basename(f2).split('.')[0]
  3653. # %%
  3654. import pandas as pd
  3655. df=pd.read_csv('/data484_1/kmohammed2/anaseri_bgen_processing/gcta/ukb_grm.grm.id')
  3656. # %% [markdown]
  3657. # ## Upload to GWAS Catalog
  3658. #
  3659. # Here is the script for formatting the summary statistics to conform with GWAS Catalog requirements and generating the description table, you then need to use the [globus client](https://www.globus.org/globus-connect-personal) to upload the summary statistics to the network folder designated by GWAS Catalog team:
  3660. # ```Python
  3661. # # https://www.ebi.ac.uk/gwas/docs/submission
  3662. # # https://www.ebi.ac.uk/gwas/docs/summary-statistics-format#validator
  3663. # # https://www.ebi.ac.uk/gwas/deposition
  3664. # %%
  3665. # rename the files
  3666. import re
  3667. from pathlib import Path
  3668. from typing import List, Tuple
  3669. def rename_qt_to_udip(folder: str, dry_run: bool = False) -> List[Tuple[str, str]]:
  3670. """
  3671. Rename files matching 'QT<number>_results.fastGWA' to 'UDIP<number>.fastGWA' in the specified folder.
  3672. Args:
  3673. folder: Path to the directory containing the files.
  3674. dry_run: If True, do not perform actual renaming; just print planned changes.
  3675. Returns:
  3676. A list of tuples (original_path, new_path) for files that were (or would be) renamed.
  3677. """
  3678. dir_path = Path(folder)
  3679. if not dir_path.is_dir():
  3680. raise ValueError(f"Provided path is not a directory: {folder}")
  3681. # Regex to capture the numeric part in filenames like QT0_results.fastGWA or QT10_results.fastGWA
  3682. pattern = re.compile(r'^QT(\d+)_results\.fastGWA$', flags=re.IGNORECASE)
  3683. renamed = []
  3684. for entry in dir_path.iterdir():
  3685. if not entry.is_file():
  3686. continue
  3687. m = pattern.match(entry.name)
  3688. if not m:
  3689. continue
  3690. idx = m.group(1)
  3691. new_name = f"UDIP{idx}.fastGWA"
  3692. target = entry.with_name(new_name)
  3693. if target.exists():
  3694. print(f"[SKIP] Target already exists, skipping rename of '{entry.name}' -> '{new_name}'")
  3695. continue
  3696. if dry_run:
  3697. print(f"[DRY-RUN] Would rename: '{entry.name}' -> '{new_name}'")
  3698. else:
  3699. entry.rename(target)
  3700. print(f"[RENAMED] '{entry.name}' -> '{new_name}'")
  3701. renamed.append((str(entry), str(target)))
  3702. return renamed
  3703. folder='/data484_2/xzhao14/FA_rep_stat/'
  3704. results = rename_qt_to_udip(folder, dry_run=False)
  3705. print(f"Processed {len(results)} file(s).")
  3706. # %%
  3707. import pandas as pd, os
  3708. from glob import glob
  3709. from gwas_sumstats_tools.validate import validate
  3710. from multiprocessing import Pool
  3711. from tqdm import tqdm
  3712. from openpyxl import load_workbook
  3713. validator_path = "/data4012/zxie3/anaconda3/envs/workflow/lib/python3.9/site-packages/gwas_sumstats_tools/validate.py"
  3714. ##
  3715. files = glob("/data484_2/xzhao14/FA_statis/UDIP*.fastGWA")
  3716. columns = ["chromosome", "base_pair_location", "effect_allele", "other_allele", "beta", "standard_error", "effect_allele_frequency", "p_value"]
  3717. mapping = {"CHR":"chromosome", "POS":"base_pair_location", "A1":"effect_allele", "A2":"other_allele", "BETA":"beta", "SE":"standard_error", "AF1":"effect_allele_frequency", "P":"p_value"}
  3718. def parse_name(x):
  3719. modality, model, qt, cohort = x.split('/')[-1].split('_')
  3720. cohort = cohort.split('.')[0]
  3721. return f"{modality}_{qt}_{cohort}"
  3722. def proc(x, path):
  3723. df = pd.read_table(x)
  3724. df.rename(columns=mapping)[columns].to_csv(path, sep='\t', index=False)
  3725. with Pool(5) as p:
  3726. p.starmap(proc, tqdm.tqdm(zip(files[1:], map(lambda x: f"/data484_2/xzhao14/gwascatalog_submission/{parse_name(x)}.tsv", files[1:]))))
  3727. # validate(f"/data4012/zxie3/AE_revision2/gwascatalog_submission/{parse_name(files[0])}.tsv")
  3728. # https://www.ebi.ac.uk/gwas/docs/submission-summary-statistics-plus-metadata
  3729. os.system("md5sum /data484_2/xzhao14/gwascatalog_submission*.tsv > /data484_2/xzhao14/FA_checksum")
  3730. md5_df = pd.read_table("/data484_2/xzhao14/FA_checksum", header=None, sep='\s+')
  3731. Study_tag = "MRI_FA_GWAS_MODEL128_{}_UDIP{}_{}" #A5 modality T1/T2, dimension and discovery/replication
  3732. Genotyping_technology = "Genome-wide genotyping array" #B5
  3733. Array_manufacturer = "Affymetrix" #C5
  3734. Analysis_software = "GCTA fastGWA" #E5
  3735. Imputation = "Yes" #F5
  3736. Reported_trait = "Algorithmically Discovered Brain Morphology-Related Trait" #M5
  3737. Summary_statistics_file = "{}_QT{}_{}.tsv" #O5 modality T1/T2, dimension and discovery/replication
  3738. md5_sum = "" #P5 md5
  3739. citation = 'Patel, Khush, et al. "New phenotype discovery method by unsupervised deep representation learning empowers genetic association studies of brain imaging." medRxiv (2022): 2022-12.'
  3740. Readme = citation #Q5
  3741. Summary_statistics_assembly = "GRCh37" #R5
  3742. Coordinate_system = "1-based" #Z5
  3743. Stage = "discovery" # discovery/replication
  3744. Ancestry_category = "European"
  3745. Ancestry = "British White"
  3746. Country_of_recruitment = "U.K."
  3747. Variant_count_dict = {("FA", "discovery"): 8925988, ("FA", "replication"): 8925870}
  3748. Number_of_individuals_dict = {("FA", "discovery"): 22960, ("FA", "replication"): 12405}
  3749. workbook = load_workbook('/data4012/zxie3/AE_revision2/new_template.xlsx')
  3750. study = workbook['study']
  3751. sample = workbook['sample']
  3752. # modality = 'T1'
  3753. # dim = 0
  3754. # cohort = "discovery"
  3755. line = 5 # start from 5th line in the excel sheet
  3756. for modality in ["T1", "T2"]:
  3757. for cohort in ["discovery", "replication"]:
  3758. for dim in range(128):
  3759. summary_statistics_file = Summary_statistics_file.format(modality, dim, cohort)
  3760. if not os.path.exists(f"gwascatalog_submission/{summary_statistics_file}"):
  3761. print(summary_statistics_file)
  3762. continue
  3763. study_tag = Study_tag.format(modality, dim, cohort)
  3764. variant_count = Variant_count_dict[(modality, cohort)]
  3765. md5 = md5_df[md5_df[1] == f"gwascatalog_submission/{summary_statistics_file}"][0].values[0]
  3766. stage = cohort
  3767. number_of_individuals = Number_of_individuals_dict[(modality, cohort)]
  3768. study_filling = [study_tag, Genotyping_technology, Array_manufacturer, Analysis_software, Imputation, variant_count,
  3769. Reported_trait, summary_statistics_file, md5, Readme, Summary_statistics_assembly, Coordinate_system]
  3770. study_filling_cell = [f'{x}{line}' for x in ['A', 'B', 'C', 'E', 'F', 'I', 'M', 'O', 'P', 'Q', 'R', 'Z']]
  3771. for (cell, content) in zip(study_filling_cell, study_filling):
  3772. study[cell] = content
  3773. sample_filling = [study_tag, stage, number_of_individuals, Ancestry_category, Ancestry, Country_of_recruitment]
  3774. sample_filling_cell = [f'{x}{line}' for x in ['A', 'B', 'C', 'H', 'I', 'L']]
  3775. for (cell, content) in zip(sample_filling_cell, sample_filling):
  3776. sample[cell] = content
  3777. line += 1
  3778. workbook.save(f'/data4012/zxie3/AE_revision2/GWASCatalog_submission_form/all.xlsx')

FA_GWAS_all.ipynb at commit c566e2a, under MIT · at the source

Overview

  1. McWilliams School of Biomedical Informatics, University of Texas Health Science Center,Houston, TX 77030 USA
  2. School of Public Health, University of Texas Health Science Center,Houston, TX 77030 USA
  3. Rory Meyers College of Nursing, New York University,New York, NY 10010 USA
  4. McGovern Medical School, University of Texas Health Science Center,Houston, TX 77030 USA
Journal: Nature communications, volume 17, issue 1, article 7150
Dates: received 22 August 2025; accepted 19 May 2026; published online 3 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-73996-z · PMID 42236496 · PMCID PMC13396798 · OpenAlex W7163421271
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), structural MRI / diffusion (modality), human (organism), cellular / molecular (subfield)
Methods: Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, Connectivity, fMRI & imaging
Keywords: Computational biology and bioinformatics, Genome-wide association studies, Predictive markers
MeSH: Deep Learning*, White Matter*, Anisotropy, Brain, Diffusion Magnetic Resonance Imaging, Genome-Wide Association Study, Humans, Oligodendroglia, Phenotype, Polymorphism, Single Nucleotide (* major topic)
Topic: Advanced Neuroimaging Techniques and Applications (Radiology, Nuclear Medicine and Imaging, Medicine), according to OpenAlex
Funding: National Institute on Aging (U01AG070112, R01AG081398)
Citations: not cited yet (Europe PMC); 92 references in the paper

Abstract

Fractional anisotropy (FA) from diffusion MRI is a widely used marker of white matter (WM) integrity, but conventional FA-based genetic studies typically rely on tract- or atlas-defined averages that may obscure spatially distributed WM variation and limit genetic discovery. Here, we propose a deep learning framework, termed unsupervised deep representation of WM (UDR-WM), which uses voxel-wise FA maps to derive brain-wide unsupervised deep imaging phenotypes (UDIP-FA) without prior anatomical assumptions. Compared with traditional FA phenotypes, UDIP-FA shows greater sensitivity to aging and substantially higher SNP-based heritability. Multivariate GWAS identified 939 lead SNPs across 586 loci, mapping to 3,480 UDIP-FA-associated genes. These genes are enriched in glial cells, especially astrocytes and oligodendrocytes, and form disease-relevant modules in protein interaction and co-expression networks implicating myelination and axonal structure. UDIP-FA is genetically associated with multiple brain disorders, cognitive traits, and polygenic risk. Together, our results suggest that UDIP-FA provides a biologically meaningful view of white matter, complementing conventional ROI-based FA measures and offering a more refined way to study its genetic architecture.

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

Repositories

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

ZhiGroup/UDIP-FA

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: c566e2a53e32085c4553c01e8cb32f0273914f04, 24 April 2026
Languages: Python (5), R (2), Jupyter (1)
Size: 22 files, 8 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, environment (requirements.txt), 1 notebook
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: pandas (4 files), PyTorch (4 files), NumPy (3 files), Matplotlib (2 files), MONAI (2 files), NiBabel (2 files), scikit-learn (2 files), SciPy (2 files), seaborn (2 files), ggplot2 (1 file), h5py (1 file), LightGBM (1 file), PyTorch Lightning (1 file), Nilearn (1 file), Plotly (1 file), statsmodels (1 file), tidyverse (1 file), UMAP (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
9 files

Zenodo 19739801

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: pandas (4 files), PyTorch (4 files), NumPy (3 files), Matplotlib (2 files), MONAI (2 files), NiBabel (2 files), scikit-learn (2 files), SciPy (2 files), seaborn (2 files), ggplot2 (1 file), h5py (1 file), LightGBM (1 file), PyTorch Lightning (1 file), Nilearn (1 file), Plotly (1 file), statsmodels (1 file), tidyverse (1 file), UMAP (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
8 files
At the source:

Code availability

The codes for this study can be found on GitHub (https://github.com/ZhiGroup/UDIP-FA) and can be cited as 10.5281/zenodo.19739801.

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:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 16 scripts, each with its path and the digest of its content;
  • 22 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

Data availability

The GWAS summary statistics generated in this study have been deposited in the GWAS Catalog under accession code GCP001371 (https://ftp.ebi.ac.uk/pub/databases/gwas/summary_statistics/, GCST90654352-GCST90654607). The raw UK Biobank imaging, genotype, and phenotype data used in this study are available under restricted access because they contain participant-level protected data. Access can be obtained by applying through the UK Biobank Access Management System (https://www.ukbiobank.ac.uk/use-our-data/apply-for-access/). This study used UK Biobank data under approved application 24247. The raw participant-level UK Biobank data are protected and are not publicly available due to data privacy and data-use restrictions. The processed UDIP-FA visualization data generated in this study are available at https://udip-fa.github.io/Dim_visualization/. Publicly available external datasets used in this study include human brain developmental expression data from BrainSpan (https://www.brainspan.org/), single-cell and cell-type expression data from PsychENCODE (https://www.psychencode.org/resources/), gene-set annotations from MSigDB (https://www.gsea-msigdb.org/gsea/msigdb), variant–trait association records from the NHGRI-EBI GWAS Catalog (https://www.ebi.ac.uk/gwas/), and drug–gene interaction records from DGIdb (https://dgidb.org/). External GWAS summary statistics and disease risk gene sets used for downstream analyses are described in the Methods and listed in the Supplementary Data. Source data are provided with this paper.

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, 8 authors, 3 keywords, 10 MeSH terms, 1 funder, 91 references.

Cite

This paper

Zhao, X., Xie, Z., He, W., Koh, H. Y., Guo, B., Chen, H., Fornage, M., & Zhi, D. (2026). Genetic architecture of white matter microstructure captured by unsupervised deep representation learning of fractional anisotropy maps. Nature communications, 17(1), 7150. https://doi.org/10.1038/s41467-026-73996-z

BibTeX

@article{zhao2026genetic,
author = {Zhao, Xingzhong and Xie, Ziqian and He, Wei and Koh, Hyun Yong and Guo, Bohong and Chen, Han and Fornage, Myriam and Zhi, Degui},
title = {{Genetic architecture of white matter microstructure captured by unsupervised deep representation learning of fractional anisotropy maps}},
journal = {Nature communications},
year = {2026},
month = jun,
volume = {17},
number = {1},
pages = {7150},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-73996-z},
url = {https://doi.org/10.1038/s41467-026-73996-z},
pmid = {42236496},
pmcid = {PMC13396798}
}

RIS

TY - JOUR
AU - Zhao, Xingzhong
AU - Xie, Ziqian
AU - He, Wei
AU - Koh, Hyun Yong
AU - Guo, Bohong
AU - Chen, Han
AU - Fornage, Myriam
AU - Zhi, Degui
TI - Genetic architecture of white matter microstructure captured by unsupervised deep representation learning of fractional anisotropy maps
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/06/03
VL - 17
IS - 1
SP - 7150
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-73996-z
UR - https://doi.org/10.1038/s41467-026-73996-z
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-73996-z",
"type": "article-journal",
"title": "Genetic architecture of white matter microstructure captured by unsupervised deep representation learning of fractional anisotropy maps",
"container-title": "Nature communications",
"author": [
{
"family": "Zhao",
"given": "Xingzhong"
},
{
"family": "Xie",
"given": "Ziqian"
},
{
"family": "He",
"given": "Wei"
},
{
"family": "Koh",
"given": "Hyun Yong"
},
{
"family": "Guo",
"given": "Bohong"
},
{
"family": "Chen",
"given": "Han"
},
{
"family": "Fornage",
"given": "Myriam"
},
{
"family": "Zhi",
"given": "Degui"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "7150",
"DOI": "10.1038/s41467-026-73996-z",
"PMID": "42236496",
"PMCID": "PMC13396798",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-73996-z",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
3
]
]
}
}

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/s42003-026-10131-0 [code]
Shared genetic architecture between the topology of brain white matter structural connectome and fluid intelligence.
Journal: Communications biology
In common: scikit-learn, pandas, SciPy, 1 other tool, structural MRI / diffusion, genetics / omics, 15 references
[2] doi:10.1038/s41562-026-02486-5 [code]
Genome-wide association studies of infant and toddler temperament in European and multi-ancestry populations.
Journal: Nature human behaviour
In common: ggplot2, seaborn, tidyverse, 4 other tools, genetics / omics, 11 references
[3] doi:10.1038/s41467-026-73428-y [code]
Regional heterogeneity in phenotypic and genetic associations between bone and brain in humans.
Journal: Nature communications
In common: ggplot2, tidyverse, pandas, 2 other tools, genetics / omics, cellular / molecular, 12 references
[4] doi:10.64898/2026.03.06.709690
Genetic insights on the mechanisms of human cortical folding
Journal: bioRxiv (preprint)
In common: ukbiobank.ac.uk/use-our-data/apply-for-access, genetics / omics, cellular / molecular, 11 references
[5] doi:10.1371/journal.pcbi.1014422 [code]
Deciphering cell type-specific causal genetic effects on brain imaging-derived phenotypes and disorders with single-cell Mendelian randomization.
Journal: PLoS computational biology
In common: ggplot2, tidyverse, genetics / omics, cellular / molecular, 8 references, author Xingzhong Zhao
[6] doi:10.1038/s41467-026-73714-9 [code]
The genetic architecture of cortical similarity networks.
Journal: Nature communications
In common: NiBabel, ggplot2, tidyverse, 4 other tools, structural MRI / diffusion, genetics / omics, 9 references
[7] doi:10.1016/j.isci.2026.116055 [code]
Mapping the transcriptional diversity of calcium signaling in the mouse and human brain.
Journal: iScience
In common: LightGBM, UMAP, Plotly, 11 other tools, genetics / omics
[8] doi:10.1038/s41467-026-72164-7 [code]
Multivariate genetic analysis reveals three distinct pathological dimensions in musculoskeletal disorders.
Journal: Nature communications
In common: Plotly, PyTorch, ggplot2, 5 other tools, genetics / omics, 6 references
[9] doi:10.1038/s41467-026-76676-0 [code]
Determinants of functional burden pleiotropy and gene dosage responses across human traits.
Journal: Nature communications
In common: UMAP, NiBabel, statsmodels, 8 other tools, genetics / omics, cellular / molecular, 3 references
[10] doi:10.1038/s41467-026-71738-9 [code]
Genetic landscape of adult executive function reveals a cell-type-specific developmental origin.
Journal: Nature communications
In common: ggplot2, tidyverse, genetics / omics, 10 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.