OSCR

Glial Dysfunction and Memory Impairments in a Model of Pediatric Obstructive Sleep Apnea.

Code ↔ Paper

2 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 2 matches
  1. [1] § Methods › Image Acquisition and Analysis ↔ Scripts/Python Scripts/microglial CIH.ipynb, lines 162 to 198 · score 0.78 · RandomForestClassifier, Additive, SHAP, depth, trained, classify · Report a mismatch
  2. [2] § Results › Molecular and Cellular Changes in Microglia in POSA ↔ Scripts/Python Scripts/microglial CIH.ipynb, lines 162 to 198 · score 0.56 · random forest classifier, SHAP, trained, predictions, scores · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 523 lines · 19 KB · no license · 2 matches

  1. # %%
  2. import pandas as pd
  3. import os
  4. import glob
  5. import re
  6. import numpy as np
  7. import shap
  8. from sklearn.model_selection import StratifiedKFold
  9. from xgboost import XGBClassifier
  10. from sklearn.preprocessing import LabelEncoder
  11. from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, ConfusionMatrixDisplay, precision_recall_fscore_support
  12. import matplotlib.pyplot as plt
  13. from sklearn.ensemble import RandomForestClassifier
  14. # ==========================================
  15. # 1. SETUP OUTPUT DIRECTORY
  16. # ==========================================
  17. data_path = "...." # UPDATE BEFORE RUNNING
  18. output_dir = '....' # UPDATE BEFORE RUNNING
  19. os.makedirs(output_dir, exist_ok=True)
  20. print(f"Output directory: {output_dir}\n")
  21. #### Read in the fraclac data ###
  22. FracLac1_CIH = pd.read_csv(os.path.join(data_path, "Hull and Circle Results CIH.txt"), sep='\t', header=None)
  23. # Split the first column to extract Name and ID
  24. fraclac_cih = FracLac1_CIH[0].str.split('tif_thresholdedtif_', n=1, expand=True)
  25. fraclac_cih.columns = ['Name', 'temp']
  26. fraclac_cih['ID'] = fraclac_cih['temp'].str.split('tif', n=1, expand=True)[0]
  27. fraclac_cih = fraclac_cih[['Name', 'ID']]
  28. #### Read in the fraclac data ###
  29. FracLac1_Controls= pd.read_csv(os.path.join(data_path, "Hull and Circle Results Controls.txt"), sep='\t', header=None)
  30. # Split the first column to extract Name and ID
  31. fraclac_ctl = FracLac1_Controls[0].str.split('tif_thresholdedtif_', n=1, expand=True)
  32. fraclac_ctl.columns = ['Name', 'temp']
  33. fraclac_ctl['ID'] = fraclac_ctl['temp'].str.split('tif', n=1, expand=True)[0]
  34. fraclac_ctl = fraclac_ctl[['Name', 'ID']]
  35. # Merge the two fraclac dataframes, and deduplicate
  36. fraclac = pd.concat([fraclac_cih, fraclac_ctl]).drop_duplicates().reset_index(drop=True)
  37. skeleton_dir = os.path.join(data_path, "CIH Microglia data/SkeletonResults/")
  38. # Get immediate subdirectories only
  39. subdirs = [d for d in os.listdir(skeleton_dir)
  40. if os.path.isdir(os.path.join(skeleton_dir, d))]
  41. complete_df_list = []
  42. for subdir in subdirs:
  43. subdir_path = os.path.join(skeleton_dir, subdir)
  44. # Get CSV files in this subdirectory
  45. file_vec = glob.glob(os.path.join(subdir_path, "*.csv"))
  46. # Create an empty dataframe to add the CSV file's data to
  47. skeleton = pd.DataFrame(columns=[
  48. 'Branches', 'Junctions', 'End_point_voxels', 'Junction_voxels',
  49. 'Slab_voxels', 'Average_Branch_Length', 'Triple_points',
  50. 'Quadruple_points', 'Maximum_Branch_Length', 'Name', 'ID'
  51. ])
  52. rows = []
  53. for file_path in file_vec:
  54. # Read in the CSV
  55. open_file = pd.read_csv(file_path, header=0)
  56. # Remove the first column (assumed to be row numbers) and add Name/ID columns
  57. open_file = open_file.iloc[:, 1:].copy() # Remove first column
  58. open_file['Name'] = None
  59. open_file['ID'] = None
  60. open_file.columns = [col.replace("# ", "", 1).lstrip() if col.startswith("# ") else col.lstrip() for col in open_file.columns]
  61. open_file2 = open_file.copy()
  62. # Extract file name with extension, remove the extension, periods, spaces, and 'tif_results'
  63. base_name = os.path.basename(file_path)
  64. file_name_no_ext = os.path.splitext(base_name)[0]
  65. cleaned_name = re.sub(r' |\.|tif_results$', '', file_name_no_ext)
  66. # Extract Name and ID using regex
  67. name_match = re.search(r'(.*?)(?=tif_thresholdedtif_)', cleaned_name)
  68. id_match = re.search(r'(?<=tif_thresholdedtif_)(.*)', cleaned_name)
  69. open_file2.loc[0, 'Name'] = name_match.group(1) if name_match else None
  70. open_file2.loc[0, 'ID'] = id_match.group(1) if id_match else None
  71. # Collect just the first row for later concatenation
  72. rows.append(open_file2.iloc[[0]])
  73. rows_df = pd.concat(rows, ignore_index=True, sort=False)
  74. # Remove rows with NA values in Name or ID
  75. rows_df = rows_df.dropna(subset=['Name', 'ID']).reset_index(drop=True)
  76. merged_df = pd.merge(rows_df, fraclac, on=['Name', 'ID'], how='inner')
  77. new_cols = list(skeleton.columns) + [merged_df.columns[-1]]
  78. merged_df.columns = new_cols
  79. merged_df = merged_df.dropna(axis=1, how='all')
  80. merged_df['Groups'] = subdir
  81. complete_df_list.append(merged_df)
  82. # Combine all subdirectory dataframes into one
  83. complete_df = pd.concat(complete_df_list, ignore_index=True, sort=False)
  84. complete_df.to_csv(os.path.join(data_path, "skeleton morphology output.csv"), index=False)
  85. # Create the copy
  86. complete_copy = complete_df.copy()
  87. # Rename columns to remove underscores
  88. complete_copy.columns = complete_copy.columns.str.replace('_', ' ')
  89. # Now you can use metrics without underscores
  90. metrics = ['Branches', 'Junctions', 'End point voxels', 'Junction voxels',
  91. 'Slab voxels', 'Average Branch Length', 'Triple points',
  92. 'Quadruple points', 'Maximum Branch Length']
  93. # %%
  94. # ==========================================
  95. # 2. PREPROCESSING
  96. # ==========================================
  97. X = complete_copy[metrics]
  98. y = complete_copy['Groups']
  99. le = LabelEncoder()
  100. y_encoded = le.fit_transform(y)
  101. class_names = le.classes_
  102. print(f"Classes found: {class_names}")
  103. # ==========================================
  104. # 3. CROSS-VALIDATION TRAINING
  105. # ==========================================
  106. print("\n" + "="*40)
  107. print("STARTING 5-FOLD CROSS-VALIDATION")
  108. print("="*40)
  109. # Initialize 5-fold Stratified Cross-Validation
  110. # Stratified ensures each fold has the same proportion of Groups as the whole data
  111. skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
  112. # Lists to store metrics across folds
  113. fold_accuracies = []
  114. fold_precisions = []
  115. fold_recalls = []
  116. fold_f1s = []
  117. # Variables to aggregate results
  118. total_confusion_matrix = np.zeros((len(class_names), len(class_names)), dtype=int)
  119. all_y_test = []
  120. all_y_pred = []
  121. all_test_indices = []
  122. fold_rf_importances = []
  123. # Storage for SHAP
  124. shap_values_list = []
  125. fold_num = 1
  126. for train_index, test_index in skf.split(X, y_encoded):
  127. X_train, X_test = X.iloc[train_index], X.iloc[test_index]
  128. y_train, y_test = y_encoded[train_index], y_encoded[test_index]
  129. # Train Classifier
  130. model = RandomForestClassifier(n_estimators=100, random_state=0, max_depth=3)
  131. model.fit(X_train, y_train)
  132. fold_rf_importances.append(model.feature_importances_)
  133. # Predict
  134. y_pred = model.predict(X_test)
  135. # Store Metrics
  136. acc = accuracy_score(y_test, y_pred)
  137. fold_accuracies.append(acc)
  138. p, r, f, _ = precision_recall_fscore_support(y_test, y_pred, average=None, zero_division=0)
  139. fold_precisions.append(p)
  140. fold_recalls.append(r)
  141. fold_f1s.append(f)
  142. # Aggregate for Global Confusion Matrix
  143. total_confusion_matrix += confusion_matrix(y_test, y_pred, labels=range(len(class_names)))
  144. all_y_test.extend(y_test)
  145. all_y_pred.extend(y_pred)
  146. all_test_indices.extend(test_index)
  147. # SHAP Calculation for this fold (test set)
  148. explainer = shap.TreeExplainer(model)
  149. fold_shap_values = explainer.shap_values(X_test, check_additivity=False)
  150. shap_values_list.append(fold_shap_values)
  151. print(f"Fold {fold_num} Accuracy: {acc:.2%}")
  152. fold_num += 1
  153. # ==========================================
  154. # 4. MODEL EVALUATION (Averaged with Error Bars)
  155. # ==========================================
  156. print("\n" + "="*40)
  157. print("AVERAGED EVALUATION RESULTS")
  158. print("="*40)
  159. mean_acc = np.mean(fold_accuracies)
  160. std_acc = np.std(fold_accuracies)
  161. print(f"Mean Accuracy: {mean_acc:.2%} (+/- {std_acc:.2%})")
  162. mean_precision = np.mean(fold_precisions, axis=0)
  163. std_precision = np.std(fold_precisions, axis=0)
  164. mean_recall = np.mean(fold_recalls, axis=0)
  165. std_recall = np.std(fold_recalls, axis=0)
  166. mean_f1 = np.mean(fold_f1s, axis=0)
  167. std_f1 = np.std(fold_f1s, axis=0)
  168. # Print Text Report
  169. print("\n--- Classification Report (Aggregated) ---")
  170. class_report_str = classification_report(all_y_test, all_y_pred, target_names=class_names)
  171. print(class_report_str)
  172. # Save Classification Report
  173. with open(os.path.join(output_dir, 'classification_report.txt'), 'w') as f:
  174. f.write("CLASSIFICATION REPORT\n")
  175. f.write("="*60 + "\n\n")
  176. f.write(f"Mean Accuracy: {mean_acc:.2%} (+/- {std_acc:.2%})\n\n")
  177. f.write(class_report_str)
  178. print(f"Saved: classification_report.txt")
  179. # Save Cross-Validation Metrics
  180. cv_metrics_df = pd.DataFrame({
  181. 'Fold': range(1, 6),
  182. 'Accuracy': fold_accuracies,
  183. 'Precision_Class_0': [p[0] for p in fold_precisions],
  184. 'Precision_Class_1': [p[1] for p in fold_precisions],
  185. 'Recall_Class_0': [r[0] for r in fold_recalls],
  186. 'Recall_Class_1': [r[1] for r in fold_recalls],
  187. 'F1_Class_0': [f[0] for f in fold_f1s],
  188. 'F1_Class_1': [f[1] for f in fold_f1s]
  189. })
  190. cv_metrics_df.to_csv(os.path.join(output_dir, 'cross_validation_metrics.csv'), index=False)
  191. print(f"Saved: cross_validation_metrics.csv")
  192. # Save Confusion Matrix
  193. cm_df = pd.DataFrame(total_confusion_matrix,
  194. index=[f'True_{cls}' for cls in class_names],
  195. columns=[f'Pred_{cls}' for cls in class_names])
  196. cm_df.to_csv(os.path.join(output_dir, 'confusion_matrix.csv'))
  197. print(f"Saved: confusion_matrix.csv")
  198. # --- Visualizations ---
  199. print("--- Performance Visualizations ---")
  200. # Set larger font sizes for plots
  201. plt.rcParams.update({
  202. 'font.size': 14,
  203. 'axes.titlesize': 16,
  204. 'axes.labelsize': 14,
  205. 'xtick.labelsize': 13,
  206. 'ytick.labelsize': 13,
  207. 'legend.fontsize': 13
  208. })
  209. fig, axes = plt.subplots(1, 2, figsize=(14, 6))
  210. # Subplot 1: Aggregated Confusion Matrix
  211. disp = ConfusionMatrixDisplay(confusion_matrix=total_confusion_matrix, display_labels=class_names)
  212. disp.plot(cmap=plt.cm.Blues, values_format='d', ax=axes[0], colorbar=False)
  213. axes[0].set_title("Aggregated Confusion Matrix (Sum of 5 Folds)")
  214. # Subplot 2: Mean Precision & Recall WITH ERROR BARS
  215. x = np.arange(len(class_names))
  216. width = 0.35
  217. rects1 = axes[1].bar(x - width/2, mean_precision, width,
  218. yerr=std_precision, capsize=5,
  219. label='Mean Precision', color='#4c72b0')
  220. rects2 = axes[1].bar(x + width/2, mean_recall, width,
  221. yerr=std_recall, capsize=5,
  222. label='Mean Recall', color='#55a868')
  223. axes[1].set_ylabel('Score')
  224. axes[1].set_title('Average Precision & Recall (Mean ± SD)')
  225. axes[1].set_xticks(x)
  226. axes[1].set_xticklabels(class_names)
  227. axes[1].set_ylim(0, 1.15)
  228. axes[1].legend(loc='lower right')
  229. axes[1].grid(axis='y', linestyle='--', alpha=0.3)
  230. # Label function
  231. def autolabel(rects, std_devs):
  232. for rect, sd in zip(rects, std_devs):
  233. height = rect.get_height()
  234. # Place label at height + sd + small offset
  235. axes[1].annotate(f'{height:.2f}',
  236. xy=(rect.get_x() + rect.get_width() / 2, height + sd),
  237. xytext=(0, 3),
  238. textcoords="offset points",
  239. ha='center', va='bottom')
  240. autolabel(rects1, std_precision)
  241. autolabel(rects2, std_recall)
  242. plt.tight_layout()
  243. plt.savefig(os.path.join(output_dir, 'confusion_matrix_and_metrics.png'), dpi=300, bbox_inches='tight')
  244. print(f"Saved: confusion_matrix_and_metrics.png")
  245. plt.show()
  246. print("-" * 30)
  247. # ==========================================
  248. # 5. SHAP ANALYSIS
  249. # ==========================================
  250. shap.initjs()
  251. X_combined = X.iloc[all_test_indices]
  252. y_combined = np.array(all_y_test)
  253. sample_output = shap_values_list[0]
  254. if isinstance(sample_output, list):
  255. # RF returns list of arrays [class0, class1]
  256. print("SHAP returned a list. Concatenating positive class (index 1)...")
  257. vals_to_plot = np.concatenate([fold_vals[1] for fold_vals in shap_values_list], axis=0)
  258. elif len(sample_output.shape) == 3:
  259. # 3D Array [samples, features, classes]
  260. print("SHAP returned a 3D array. Concatenating and slicing for class index 0...")
  261. stacked = np.concatenate(shap_values_list, axis=0)
  262. vals_to_plot = stacked[:, :, 0]
  263. else:
  264. # 2D Array
  265. print("SHAP returned 2D arrays. Concatenating...")
  266. vals_to_plot = np.concatenate(shap_values_list, axis=0)
  267. print(f"Shape of combined SHAP values: {vals_to_plot.shape}")
  268. print(f"Shape of combined data: {X_combined.shape}")
  269. shap_df = pd.DataFrame(vals_to_plot, columns=metrics)
  270. shap_df['True_Class'] = le.inverse_transform(y_combined)
  271. shap_df.to_csv(os.path.join(output_dir, 'shap_values_cv_output.csv'), index=False)
  272. print(f"Saved: shap_values_cv_output.csv")
  273. # ==========================================
  274. # 6. VISUALIZATIONS
  275. # ==========================================
  276. # Plot 2: Beeswarm Plot
  277. plt.rc('font', size=14) # Increase SHAP plot font size
  278. plt.figure(figsize=(10, 6))
  279. shap.summary_plot(vals_to_plot, X_combined, show=False)
  280. plt.title("Directional Impact on Predicting Group", fontsize=16)
  281. plt.tight_layout()
  282. plt.savefig(os.path.join(output_dir, 'shap_beeswarm_plot.png'), dpi=300, bbox_inches='tight')
  283. print(f"Saved: shap_beeswarm_plot.png")
  284. plt.show()
  285. # ==========================================
  286. # 7. FEATURE HIERARCHY & AGREEMENT
  287. # ==========================================
  288. # 1. Prepare SHAP Importance (Mean Absolute SHAP)
  289. mean_abs_shap = np.mean(np.abs(vals_to_plot), axis=0)
  290. # 2. Prepare RF Gini Importance (Mean across folds)
  291. mean_rf_importance = np.mean(fold_rf_importances, axis=0)
  292. std_rf_importance = np.std(fold_rf_importances, axis=0)
  293. importance_df = pd.DataFrame({
  294. 'Metric': metrics,
  295. 'RF_Gini': mean_rf_importance,
  296. 'Gini_SD': std_rf_importance,
  297. 'SHAP_Abs': mean_abs_shap
  298. })
  299. importance_df['RF_Gini_Norm'] = importance_df['RF_Gini'] / importance_df['RF_Gini'].max()
  300. importance_df['SHAP_Abs_Norm'] = importance_df['SHAP_Abs'] / importance_df['SHAP_Abs'].max()
  301. importance_df['Agreement_Diff'] = np.abs(importance_df['RF_Gini_Norm'] - importance_df['SHAP_Abs_Norm'])
  302. importance_df = importance_df.sort_values(by='RF_Gini', ascending=False)
  303. importance_df.to_csv(os.path.join(output_dir, 'feature_importance_comparison.csv'), index=False)
  304. print(f"Saved: feature_importance_comparison.csv")
  305. print("\n" + "="*60)
  306. print(f"{'Metric':<25} | {'Gini (SD)':<15} | {'Mean |SHAP|'}")
  307. print("-" * 60)
  308. for _, row in importance_df.iterrows():
  309. print(f"{row['Metric']:<25} | {row['RF_Gini']:.4f} ({row['Gini_SD']:.3f}) | {row['SHAP_Abs']:.4f}")
  310. # --- Visualizing Agreement ---
  311. plt.rcParams.update({
  312. 'font.size': 14,
  313. 'axes.titlesize': 16,
  314. 'axes.labelsize': 14,
  315. 'xtick.labelsize': 12,
  316. 'ytick.labelsize': 14,
  317. 'legend.fontsize': 13
  318. })
  319. fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 7))
  320. # Plot 1: Side-by-Side Comparison
  321. x_indices = np.arange(len(importance_df))
  322. width = 0.35
  323. ax1.barh(x_indices + width/2, importance_df['RF_Gini_Norm'], width, label='Normalized Gini', color='steelblue', alpha=0.8)
  324. ax1.barh(x_indices - width/2, importance_df['SHAP_Abs_Norm'], width, label='Normalized SHAP', color='indianred', alpha=0.8)
  325. ax1.set_yticks(x_indices)
  326. ax1.set_yticklabels(importance_df['Metric'])
  327. ax1.set_xlabel('Normalized Importance (0 to 1)')
  328. ax1.set_title('Agreement: Gini vs. SHAP Importance')
  329. ax1.invert_yaxis()
  330. ax1.legend()
  331. ax2.scatter(importance_df['RF_Gini_Norm'], importance_df['SHAP_Abs_Norm'], s=100, color='purple')
  332. for i, txt in enumerate(importance_df['Metric']):
  333. ax2.annotate(txt, (importance_df['RF_Gini_Norm'].iloc[i], importance_df['SHAP_Abs_Norm'].iloc[i]),
  334. xytext=(5, 5), textcoords='offset points', fontsize=11)
  335. ax2.plot([0, 1], [0, 1], transform=ax2.transAxes, linestyle='--', color='gray', alpha=0.5)
  336. ax2.set_xlabel('Normalized Gini Importance')
  337. ax2.set_ylabel('Normalized SHAP Importance')
  338. ax2.set_title('Feature Importance Correlation')
  339. ax2.grid(True, linestyle=':', alpha=0.6)
  340. plt.tight_layout()
  341. plt.savefig(os.path.join(output_dir, 'feature_importance_agreement.png'), dpi=300, bbox_inches='tight')
  342. print(f"Saved: feature_importance_agreement.png")
  343. plt.show()
  344. # ==========================================
  345. # 8. MOST VARIED SAMPLES
  346. # ==========================================
  347. all_probs = model.predict_proba(X_combined)
  348. search_df = pd.DataFrame({
  349. 'True_Class': y_combined,
  350. 'Pred_Class': all_y_pred,
  351. 'Confidence': np.max(all_probs, axis=1)
  352. }, index=X_combined.index)
  353. correct_df = search_df[search_df['True_Class'] == search_df['Pred_Class']]
  354. top_3_CIH = correct_df[correct_df['True_Class'] == 0].sort_values('Confidence', ascending=False).head(3)
  355. top_3_Control = correct_df[correct_df['True_Class'] == 1].sort_values('Confidence', ascending=False).head(3)
  356. print(f"Top 3 {class_names[0]} (Indices): {top_3_CIH.index.tolist()}")
  357. print(f"Top 3 {class_names[1]} (Indices): {top_3_Control.index.tolist()}")
  358. # ==========================================
  359. # 9. CREATE DATAFRAMES FOR IMAGE EXTRACTION
  360. # ==========================================
  361. deviated_complete_CIH = complete_df.loc[top_3_CIH.index]
  362. deviated_complete_Control = complete_df.loc[top_3_Control.index]
  363. # Save most varied samples
  364. deviated_samples_info = pd.concat([deviated_complete_CIH, deviated_complete_Control], ignore_index=True)
  365. deviated_samples_info.to_csv(os.path.join(output_dir, 'most_varied_samples.csv'), index=False)
  366. print(f"Saved: most_varied_samples.csv")
  367. if 'ID' in complete_df.columns:
  368. print(f"IDs to extract for {class_names[0]}: {deviated_complete_CIH['ID'].tolist()}")
  369. print(f"IDs to extract for {class_names[1]}: {deviated_complete_Control['ID'].tolist()}")
  370. print("\n" + "="*60)
  371. print(f"ALL OUTPUTS SAVED TO: {output_dir}")
  372. print("="*60)
  373. # %%
  374. import os
  375. import shutil
  376. import pandas as pd
  377. def extract_images(target_ids, source_folders, dest_folder):
  378. """
  379. Scans source_folders for files that contain any of the target_ids in their name
  380. and copies them to dest_folder.
  381. """
  382. # 1. Create destination directory
  383. os.makedirs(dest_folder, exist_ok=True)
  384. # Clean IDs: remove duplicates and ensure they are strings
  385. unique_ids = list(set(str(x) for x in target_ids if pd.notna(x)))
  386. copied_count = 0
  387. # 2. Search and Copy
  388. for folder in source_folders:
  389. if not os.path.exists(folder):
  390. continue
  391. for root, dirs, files in os.walk(folder):
  392. for filename in files:
  393. # Check: Does the filename contain ANY of our IDs?
  394. if any(tid in filename for tid in unique_ids):
  395. src_path = os.path.join(root, filename)
  396. dst_path = os.path.join(dest_folder, filename)
  397. try:
  398. shutil.copy2(src_path, dst_path)
  399. copied_count += 1
  400. print(f"Copied: {filename}")
  401. except Exception as e:
  402. print(f"Error copying {filename}: {e}")
  403. print(f"\nFinished. Copied {copied_count} images to '{dest_folder}'.")
  404. # Concartenate the deviated samples
  405. deviated_samples_df = pd.concat([deviated_complete_CIH, deviated_complete_Control], ignore_index=True)
  406. source_dirs = [
  407. os.path.join(output_dir, 'CIH'),
  408. os.path.join(output_dir, 'Controls')
  409. ]
  410. dest_dir = os.path.join(output_dir, 'Deviated')
  411. ids_to_find = deviated_samples_df['ID'].tolist()
  412. extract_images(ids_to_find, source_dirs, dest_dir)

microglial CIH.ipynb at commit 65db408, no license · at the source

Overview

18 affiliations
  1. Program in Neurosciences and Mental Health The Hospital for Sick Children Toronto Ontario Canada
  2. Department of Physiology University of Toronto Toronto Ontario Canada
  3. Data Science Center Jan and Dan Duncan Neurological Research Institute, Texas Children's Hospital Houston Texas USA
  4. Department of Pediatrics Baylor College of Medicine Houston Texas USA
  5. Jan and Dan Duncan Neurological Research Institute Texas Children's Hospital Houston Texas USA
  6. Department of Anesthesiology Perioperative and Pain Medicine, Texas Children's Hospital Houston Texas USA
  7. Department of Anesthesiology Baylor College of Medicine Houston Texas USA
  8. Department of Neuroscience Baylor College of Medicine Houston Texas USA
  9. Center for Cell and Gene Therapy Baylor College of Medicine Houston Texas USA
  10. Department of Neurosurgery Baylor College of Medicine Houston Texas USA
  11. Department of Pediatrics Texas Children's Hospital Houston Texas USA
  12. Translational Biology and Molecular Medicine Program Baylor College of Medicine Houston Texas USA
  13. Department of Medicine Baylor College of Medicine Houston Texas USA
  14. Dan L. Duncan Comprehensive Cancer Center Baylor College of Medicine Houston Texas USA
  15. Department of Pathology and Immunology Baylor College of Medicine Houston Texas USA
  16. Center for Translational Research in Inflammatory Diseases Michael E. DeBakey VA Houston Texas USA
  17. Biology of Inflammation Center Baylor College of Medicine Houston Texas USA
  18. Center for Cancer Neuroscience Baylor College of Medicine Houston Texas USA
Journal: Glia, volume 74, issue 11, article e70230
Dates: received 26 February 2026; accepted 8 September 2026; published online 18 September 2026; in print November 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1002/glia.70230 · PMID 42760594 · PMCID PMC13589049 · OpenAlex W7213640776
Open access: hybrid, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: genetics / omics (modality), mouse (organism), sleep disorders (population)
Methods: Connectivity, Spectral & time-frequency, Statistics, Smoothing, state filtering, decompositions, Machine learning, Evoked potentials
Keywords: astrocyte, cognition, glia, microglia, neurogenesis, oligodendrocyte, sleep apnea
MeSH: Memory Disorders*, Neuroglia*, Sleep Apnea, Obstructive*, Animals, Disease Models, Animal, Hippocampus, Male, Mice, Mice, Inbred C57BL, Neural Stem Cells, Oligodendroglia (* major topic)
Topic: Obstructive Sleep Apnea Research (Physiology (medical), Medicine), according to OpenAlex
Funding: American Heart Association (25POST1363224); NIH R35 (NS132230); NIH K08 (HL161263)
Citations: not cited yet (Europe PMC); 61 references in the paper

Abstract

Pediatric obstructive sleep apnea (POSA) is a common childhood disease that often causes aberrant brain development and cognitive deficits. The pathophysiological underpinnings of cognitive impairments in POSA remain unclear. Here, we examined cellular and molecular aspects of pathology in a mouse model of POSA that features learning and memory deficits. We performed single‐nucleus RNA‐sequencing (snRNA‐seq) of the hippocampus to examine gene expression changes in an unbiased and cell type‐specific manner. This dataset revealed a striking perturbation of transcriptomes across all brain cell types, particularly within glia and neural stem cells. We validated reduced expression of several differentially expressed genes at protein level: QDPR and SOX8 in oligodendrocytes, LRRK2 and NDUFS4 in neural stem cells, TFE3 in microglia, and GLUT1 in astrocytes. Comparison of oligodendrocyte gene expression changes with proteomic datasets suggested impairments in myelination, which we confirmed in vivo. Furthermore, cellular level analyses demonstrated aberrant morphology of oligodendrocytes, astrocytes, and microglia in the hippocampus, and diminished numbers of neural stem cells in the subgranular zone. Our study identifies cellular and molecular glial cell dysfunction in POSA, validates gene targets for further study, and provides an snRNA‐seq dataset to facilitate further data‐driven hypothesis generation.

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

Repository

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

cjy8s/Glial-Dysfunction-POSA

License: none: the authors keep all their rights
State: the link answers, verified on 1 November 2026
Evidence: files inventoried
Commit: 65db40820c285873513ab41f84952ed9ff31b1f6, 23 February 2026
Languages: Jupyter (1)
Size: 7 files, 1 script
Software Heritage: not archived
Found in: “Data Availability Statement”
Holds: README, 1 notebook
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (1 file), NumPy (1 file), pandas (1 file), scikit-learn (1 file), SHAP (1 file), XGBoost (1 file)
Availability: 1 check, the latest on 1 November 2026: the link answers
  • 1 November 2026: the link answers
2 files

The paper's code and data availability statement is in the Data section.

Tracing map

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

What the map holds:

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

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

Data

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

Data Availability Statement

All raw snRNA‐seq data are available through the NCBI Gene Expression Omnibus with accession number GSE319173. Code for training the random forest model is available at https://github.com/cjy8s/Glial‐Dysfunction‐POSA (https://github.com/cjy8s/Glial-Dysfunction-POSA). Glial cell morphology data, pre‐ and post‐processing, and analysis are available upon reasonable request.

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, 1 November 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 14 authors, 7 keywords, 11 MeSH terms, 3 funders, 60 references.

Cite

This paper

Williamson, M. R., Botas, J., Hedayatpour, M. J., Zlatkov, T., Harmanci, A. S., Yates, C. J., Nobleza, K., Ailani, R., Adler, A. C., Kheradmand, F., Lee, H. K., Deneen, B., Jeong, H., & Chandrakantan, A. (2026). Glial Dysfunction and Memory Impairments in a Model of Pediatric Obstructive Sleep Apnea. Glia, 74(11), e70230. https://doi.org/10.1002/glia.70230

BibTeX

@article{williamson2026glial,
author = {Williamson, Michael R. and Botas, Jorge and Hedayatpour, Mahyar J. and Zlatkov, Thea and Harmanci, Akdes Serin and Yates, Christopher J. and Nobleza, Kenneth and Ailani, Roshan and Adler, Adam C. and Kheradmand, Farrah and Lee, Hyun Kyoung and Deneen, Benjamin and Jeong, Hyun‐Hwan and Chandrakantan, Arvind},
title = {{Glial Dysfunction and Memory Impairments in a Model of Pediatric Obstructive Sleep Apnea}},
journal = {Glia},
year = {2026},
month = nov,
volume = {74},
number = {11},
pages = {e70230},
publisher = {Wiley},
issn = {0894-1491},
doi = {10.1002/glia.70230},
url = {https://doi.org/10.1002/glia.70230},
pmid = {42760594},
pmcid = {PMC13589049}
}

RIS

TY - JOUR
AU - Williamson, Michael R.
AU - Botas, Jorge
AU - Hedayatpour, Mahyar J.
AU - Zlatkov, Thea
AU - Harmanci, Akdes Serin
AU - Yates, Christopher J.
AU - Nobleza, Kenneth
AU - Ailani, Roshan
AU - Adler, Adam C.
AU - Kheradmand, Farrah
AU - Lee, Hyun Kyoung
AU - Deneen, Benjamin
AU - Jeong, Hyun‐Hwan
AU - Chandrakantan, Arvind
TI - Glial Dysfunction and Memory Impairments in a Model of Pediatric Obstructive Sleep Apnea
T2 - Glia
J2 - Glia
PY - 2026
DA - 2026/11/01
VL - 74
IS - 11
SP - e70230
SN - 0894-1491
PB - Wiley
DO - 10.1002/glia.70230
UR - https://doi.org/10.1002/glia.70230
LA - en
ER -

CSL-JSON

{
"id": "10.1002/glia.70230",
"type": "article-journal",
"title": "Glial Dysfunction and Memory Impairments in a Model of Pediatric Obstructive Sleep Apnea",
"container-title": "Glia",
"author": [
{
"family": "Williamson",
"given": "Michael R."
},
{
"family": "Botas",
"given": "Jorge"
},
{
"family": "Hedayatpour",
"given": "Mahyar J."
},
{
"family": "Zlatkov",
"given": "Thea"
},
{
"family": "Harmanci",
"given": "Akdes Serin"
},
{
"family": "Yates",
"given": "Christopher J."
},
{
"family": "Nobleza",
"given": "Kenneth"
},
{
"family": "Ailani",
"given": "Roshan"
},
{
"family": "Adler",
"given": "Adam C."
},
{
"family": "Kheradmand",
"given": "Farrah"
},
{
"family": "Lee",
"given": "Hyun Kyoung"
},
{
"family": "Deneen",
"given": "Benjamin"
},
{
"family": "Jeong",
"given": "Hyun‐Hwan"
},
{
"family": "Chandrakantan",
"given": "Arvind"
}
],
"container-title-short": "Glia",
"volume": "74",
"issue": "11",
"page": "e70230",
"DOI": "10.1002/glia.70230",
"PMID": "42760594",
"PMCID": "PMC13589049",
"ISSN": "0894-1491",
"publisher": "Wiley",
"URL": "https://doi.org/10.1002/glia.70230",
"language": "en",
"issued": {
"date-parts": [
[
2026,
11,
1
]
]
}
}

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/s41586-026-10270-8 [code]
Dominant clones leverage developmental epigenomic states to drive ependymoma.
Journal: Nature
In common: pandas, Matplotlib, NumPy, genetics / omics, mouse, 2 authors
[2] doi:10.1158/2767-9764.crc-24-0281 [code]
Tumor Expression of CD83 Reduces Glioma Progression and Is Associated with Reduced Immunosuppression.
Journal: Cancer research communications
In common: genetics / omics, mouse, 1 reference, 2 authors
[3] doi:10.1093/noajnl/vdaf016 [code]
Inferred developmental origins of brain tumors from single-cell RNA-sequencing data.
Journal: Neuro-oncology advances
In common: SHAP, scikit-learn, pandas, 2 other tools, genetics / omics, author Akdes Serin Harmanci
[4] doi:10.1038/s41467-025-61650-z [code]
Machine learning in Alzheimer's disease genetics.
Journal: Nature communications
In common: SHAP, XGBoost, scikit-learn, 3 other tools, genetics / omics, 2 references
[5] doi:10.1038/s41586-024-07109-5 [code]
A concerted neuron-astrocyte program declines in ageing and schizophrenia.
Journal: Nature
In common: scikit-learn, pandas, Matplotlib, 1 other tool, genetics / omics, 5 references
[6] doi:10.1016/j.xgen.2025.101007 [code]
Single-nucleus transcriptome atlas of orbitofrontal cortex in ALS with a deep learning-based decoding of alternative polyadenylation mechanisms.
Journal: Cell genomics
In common: XGBoost, scikit-learn, pandas, 2 other tools, genetics / omics, 4 references
[7] doi:10.1093/brain/awad345 [code]
Machine learning nominates the inositol pathway and novel genes in Parkinson's disease.
Journal: Brain : a journal of neurology
In common: SHAP, XGBoost, scikit-learn, 3 other tools, genetics / omics, 1 reference
[8] doi:10.1038/s41592-023-01938-4 [code]
SCENIC+: single-cell multiomic inference of enhancers and gene regulatory networks.
Journal: Nature methods
In common: scikit-learn, pandas, Matplotlib, 1 other tool, genetics / omics, mouse, 4 references
[9] doi:10.1016/j.isci.2026.115329 [code]
Brain metastases converge on shared geometric architecture and transcriptomic landscape yet remain distinct from gliomas.
Journal: iScience
In common: SHAP, XGBoost, scikit-learn, 3 other tools, genetics / omics, 1 reference
[10] doi:10.1038/s41593-026-02316-x [code]
Single-cell multi-omic atlas and morphogen screening informs midbrain and hindbrain organoid engineering.
Journal: Nature neuroscience
In common: scikit-learn, pandas, Matplotlib, 1 other tool, genetics / omics, mouse, 4 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.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1002/glia.70230.svg)](https://openscicode.org/badge/doi/10.1002/glia.70230)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1002/glia.70230.svg
   :target: https://openscicode.org/badge/doi/10.1002/glia.70230
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1002/glia.70230"><img src="https://openscicode.org/badge/doi/10.1002/glia.70230.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge 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: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

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