OSCR

Linking reduced prefrontal microcircuit inhibition in schizophrenia to EEG biomarkers in silico.

Code ↔ Paper

7 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 7 matches
  1. [1] § Results ↔ Fig Codes/fig_4.py, lines 83–145 · score 0.71 · 12–20 Hz, 8–12 Hz, scz40_pv, scz20, aperiodic, beta
  2. [2] § Results ↔ Fig Codes/fig_4.py, lines 178–247 · score 0.64 · SCZ40_SST, aperiodic exponent, SCZ40_PV, bands, broadband, peak
  3. [3] § Methods ↔ Fig Codes/0_process_EEG_output.py, lines 61–69 · score 0.57 · sphere volume conductor, radii, dipole, EEG
  4. [4] § Methods ↔ L23/net_params.py, lines 1–44 · score 0.57 · Connection probability, tonic inhibition, Gtonic, Excitatory
  5. [5] § Results ↔ Fig Codes/fig_4.py, lines 178–247 · score 0.55 · aperiodic exponent, SCZ40_PV, offset, absolute, broadband, peak
  6. [6] § Results ↔ Fig Codes/fig_3.py, lines 32–173 · score 0.53 · Pyr spike rate, Peak amplitude, SNR, stimulus, oddball, ERP
  7. [7] § Results ↔ Fig Codes/fig_2.py, lines 172–235 · score 0.51 · Pyr spike rate, Peak amplitude, SNR, oddball, healthy, Figure 2

Paper

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

The paper is loaded when this pane is shown.

The authors' code

Python · 328 lines · 17 KB · CC-BY-4.0 · 3 matches

  1. import numpy as np
  2. import scipy.signal as ss
  3. import matplotlib
  4. import matplotlib.pyplot as plt
  5. from fooof import FOOOF
  6. from sklearn.utils import resample
  7. import scipy.stats as st
  8. from numpy import std, mean, sqrt
  9. def cohen_d(x, y):
  10. nx, ny = len(x), len(y)
  11. dof = nx + ny - 2
  12. return (mean(x) - mean(y)) / sqrt(((nx-1)*std(x, ddof=1)**2 + (ny-1)*std(y, ddof=1)**2) / dof)
  13. def getCI(sample, n_boot, alpha):
  14. lower_p = (alpha / 2.0) * 100
  15. upper_p = (1 - (alpha / 2.0)) * 100
  16. data_t = np.transpose(sample)
  17. bootmeans = []
  18. for row in data_t:
  19. rowmean = []
  20. for i in range(n_boot):
  21. s = resample(np.array(row), replace=True, n_samples=round(row.size * 0.8))
  22. rowmean.append(s.mean())
  23. bootmeans.append(rowmean)
  24. means = [np.array(r).mean() for r in bootmeans]
  25. CIs_lower = [np.percentile(r, lower_p) for r in bootmeans]
  26. CIs_upper = [np.percentile(r, upper_p) for r in bootmeans]
  27. return means, CIs_lower, CIs_upper
  28. def main():
  29. plt.rcParams.update({
  30. 'axes.labelsize': 12, 'xtick.labelsize': 12, 'ytick.labelsize': 12,
  31. 'legend.fontsize': 11, 'axes.titlesize': 15, 'axes.titleweight': 'bold',
  32. 'legend.labelspacing': 0.1
  33. })
  34. dp = 'output/'
  35. h_rest = np.load(dp + 'healthy.npy', allow_pickle=True).item(0)
  36. scz20 = np.load(dp + 'scz_20.npy', allow_pickle=True).item(0)
  37. scz40 = np.load(dp + 'scz_40.npy', allow_pickle=True).item(0)
  38. scz40_pv = np.load(dp + 'scz_40.npy', allow_pickle=True).item(0)
  39. scz40_sst = np.load(dp + 'scz_0_40.npy', allow_pickle=True).item(0)
  40. scz40_both = np.load(dp + 'scz_40_40.npy', allow_pickle=True).item(0)
  41. n = 50
  42. fooof_kw = dict(peak_width_limits=[1, 6.], max_n_peaks=4., min_peak_height=0.5,
  43. peak_threshold=2.5, aperiodic_mode='fixed', verbose=False)
  44. freqs_full = h_rest['mean_psd']['freq_wel']
  45. # Function to run FOOOF and bootstrap
  46. def process_fooof(cond_list):
  47. num_conds = len(cond_list)
  48. # Determine output size
  49. fm_test = FOOOF(**fooof_kw)
  50. fm_test.fit(freqs_full, cond_list[0][0], freq_range=[1, 35])
  51. n_freqs = len(fm_test.freqs)
  52. aps = np.zeros((num_conds, n, n_freqs))
  53. ps = np.zeros((num_conds, n, n_freqs))
  54. offsets = [[] for _ in range(num_conds)]
  55. exponents = [[] for _ in range(num_conds)]
  56. peaks = [[] for _ in range(num_conds)]
  57. for c, cond in enumerate(cond_list):
  58. for r, run in enumerate(cond):
  59. fm = FOOOF(**fooof_kw)
  60. fm.fit(freqs_full, run, freq_range=[1, 35])
  61. aps[c][r] = 10 ** fm._ap_fit
  62. ps[c][r] = fm._peak_fit
  63. offsets[c].append(fm.aperiodic_params_[0])
  64. exponents[c].append(fm.aperiodic_params_[1])
  65. peaks[c].append(fm.peak_params_)
  66. aps_bs = np.zeros((num_conds, 3, n_freqs))
  67. ps_bs = np.zeros((num_conds, 3, n_freqs))
  68. for c in range(num_conds):
  69. aps_bs[c][0], aps_bs[c][1], aps_bs[c][2] = getCI(aps[c], 100, 0.05)
  70. ps_bs[c][0], ps_bs[c][1], ps_bs[c][2] = getCI(ps[c], 100, 0.05)
  71. return aps_bs, ps_bs, fm_test.freqs, aps, offsets, exponents, peaks
  72. # Row 1 conditions
  73. conds_abc = [
  74. np.array([h_rest[str(i)]['ps_wel'] for i in range(n)]),
  75. np.array([scz20[str(i)]['ps_wel'] for i in range(n)]),
  76. np.array([scz40[str(i)]['ps_wel'] for i in range(n)])
  77. ]
  78. aps_bs_abc, ps_bs_abc, f_abc, aps_abc, offsets_abc, exponents_abc, peak_params_abc = process_fooof(conds_abc)
  79. # Row 2 conditions
  80. conds_def = [
  81. np.array([h_rest[str(i)]['ps_wel'] for i in range(n)]),
  82. np.array([scz40_pv[str(i)]['ps_wel'] for i in range(n)]),
  83. np.array([scz40_sst[str(i)]['ps_wel'] for i in range(n)]),
  84. np.array([scz40_both[str(i)]['ps_wel'] for i in range(n)])
  85. ]
  86. aps_bs_def, ps_bs_def, f_def, aps_def, offsets_def, exponents_def, peak_params_def = process_fooof(conds_def)
  87. # Output stats for A-C
  88. n_abc = n
  89. freqs_wel = h_rest['mean_psd']['freq_wel']
  90. end_wel = np.argmin(np.abs(freqs_wel - 30))
  91. freqs_abc = freqs_wel[:end_wel]
  92. bandlims = [[1,4],[4,8],[8,12],[12,20]]
  93. bandlim_labels = ['Delta','Theta','Alpha','Beta']
  94. condlabels_abc = ['SCZ_20_PV', 'SCZ_40_PV']
  95. seedbases_abc = [0, 0]
  96. print('=' * 60)
  97. print('STATS: A-C')
  98. print('=' * 60)
  99. print('\nAbsolute Bands — A-C [raw PSD]')
  100. for b in range(len(bandlims)):
  101. s = np.argmin(np.abs(freqs_abc - bandlims[b][0]))
  102. e = np.argmin(np.abs(freqs_abc - bandlims[b][1]))
  103. print(f'\n {bandlim_labels[b]} ({bandlims[b][0]}-{bandlims[b][1]} Hz)')
  104. h_means = np.array([h_rest[str(r)]['ps_wel'][s:e].mean() for r in range(n_abc)])
  105. print(f' HC = {h_means.mean():.3g} +/- {h_means.std():.3g}')
  106. for c, cond in enumerate([scz20, scz40]):
  107. scz_means = np.array([cond[str(seedbases_abc[c]+r)]['ps_wel'][s:e].mean() for r in range(n_abc)])
  108. pct = round(((scz_means.mean()-h_means.mean())/h_means.mean())*100)
  109. pval = round(st.ttest_ind(h_means, scz_means)[1], 5)
  110. d = round(cohen_d(scz_means, h_means), 2)
  111. print(f' {condlabels_abc[c]} = {scz_means.mean():.3g} +/- {scz_means.std():.3g} {pct:+d}% p={pval} d={d}')
  112. print('\nBroadband 1-30 Hz [aperiodic fit only]')
  113. h_bb = np.array([np.trapezoid(run, f_abc) for run in aps_abc[0]])
  114. print(f' HC = {h_bb.mean():.3g} +/- {h_bb.std():.3g}')
  115. for c, cond in enumerate([aps_abc[1], aps_abc[2]]):
  116. scz_bb = np.array([np.trapezoid(run, f_abc) for run in cond])
  117. pct = round(((scz_bb.mean()-h_bb.mean())/h_bb.mean())*100)
  118. pval = round(st.ttest_ind(h_bb, scz_bb)[1], 5)
  119. d = round(cohen_d(scz_bb, h_bb), 2)
  120. print(f' {condlabels_abc[c]} = {scz_bb.mean():.3g} +/- {scz_bb.std():.3g} {pct:+d}% p={pval} d={d}')
  121. print('\nAperiodic — Offsets')
  122. h_m, h_sd = np.mean(10**np.array(offsets_abc[0])), np.std(10**np.array(offsets_abc[0]))
  123. print(f' HC = {h_m:.3g} +/- {h_sd:.3g}')
  124. for c in range(1,3):
  125. sm, ss2 = np.mean(10**np.array(offsets_abc[c])), np.std(10**np.array(offsets_abc[c]))
  126. pct = round(((sm-h_m)/h_m)*100)
  127. pval = round(st.ttest_ind(offsets_abc[0], offsets_abc[c])[1], 5)
  128. d = round(cohen_d(offsets_abc[c], offsets_abc[0]), 2)
  129. print(f' {condlabels_abc[c-1]} = {sm:.3g} +/- {ss2:.3g} {pct:+d}% p={pval} d={d}')
  130. print('\nAperiodic — Exponents')
  131. h_m, h_sd = np.mean(exponents_abc[0]), np.std(exponents_abc[0])
  132. print(f' HC = {h_m:.3g} +/- {h_sd:.3g}')
  133. for c in range(1,3):
  134. sm, ss2 = np.mean(exponents_abc[c]), np.std(exponents_abc[c])
  135. pct = round(((sm-h_m)/h_m)*100)
  136. pval = round(st.ttest_ind(exponents_abc[0], exponents_abc[c])[1], 5)
  137. d = round(cohen_d(exponents_abc[c], exponents_abc[0]), 2)
  138. print(f' {condlabels_abc[c-1]} = {sm:.3g} +/- {ss2:.3g} {pct:+d}% p={pval} d={d}')
  139. bandlims2 = [[7,18]]
  140. bandlim_labels2= ['Alpha',]
  141. paramlabels = ['Center Freq', 'Power ', 'Bandwidth']
  142. for band, blabel in zip(bandlims2, bandlim_labels2):
  143. print(f'\n>>> {blabel} ({band[0]}-{band[1]} Hz) [FOOOF peaks]')
  144. for c in range(2):
  145. h_runs = sum(1 for run in peak_params_abc[0] if any(band[0]<=p[0]<=band[1] for p in run))
  146. scz_runs = sum(1 for run in peak_params_abc[c+1] if any(band[0]<=p[0]<=band[1] for p in run))
  147. print(f' {condlabels_abc[c]} (HC: {h_runs}/{n} runs, SCZ: {scz_runs}/{n} runs with detected peaks)')
  148. for param in range(3):
  149. h = [p[param] for run in peak_params_abc[0] for p in run if band[0]<=p[0]<=band[1]]
  150. scz = [p[param] for run in peak_params_abc[c+1] for p in run if band[0]<=p[0]<=band[1]]
  151. if not h or not scz: continue
  152. hm,hs = np.mean(h), np.std(h)
  153. sm,ss2 = np.mean(scz), np.std(scz)
  154. pct = round(((sm-hm)/hm)*100)
  155. _, p = st.ttest_ind(h, scz)
  156. d = round(cohen_d(h, scz), 2)
  157. print(f' {paramlabels[param]} HC={hm:.3g} +/- {hs:.3g} SCZ={sm:.3g} +/- {ss2:.3g} {pct:+d}% p={round(p,5)} d={d}')
  158. # Output stats for D-F
  159. n_def = n
  160. freqs_def = freqs_wel[:end_wel]
  161. condlabels_def = ['SCZ40_PV', 'SCZ40_SST', 'SCZ40_PV+SST']
  162. seedbases_def = [0, 0, 0]
  163. print('\n' + '=' * 60)
  164. print('STATS: D-F')
  165. print('=' * 60)
  166. print('\nAbsolute Bands — D-F [raw PSD]')
  167. for b in range(len(bandlims)):
  168. s = np.argmin(np.abs(freqs_def - bandlims[b][0]))
  169. e = np.argmin(np.abs(freqs_def - bandlims[b][1]))
  170. print(f'\n {bandlim_labels[b]} ({bandlims[b][0]}-{bandlims[b][1]} Hz)')
  171. h_means = np.array([h_rest[str(r)]['ps_wel'][s:e].mean() for r in range(n_def)])
  172. print(f' HC = {h_means.mean():.3g} +/- {h_means.std():.3g}')
  173. for c, cond in enumerate([scz40_pv, scz40_sst, scz40_both]):
  174. scz_means = np.array([cond[str(seedbases_def[c]+r)]['ps_wel'][s:e].mean() for r in range(n_def)])
  175. pct = round(((scz_means.mean()-h_means.mean())/h_means.mean())*100)
  176. pval = round(st.ttest_ind(h_means, scz_means)[1], 5)
  177. d = round(cohen_d(scz_means, h_means), 2)
  178. print(f' {condlabels_def[c]} = {scz_means.mean():.3g} +/- {scz_means.std():.3g} {pct:+d}% p={pval} d={d}')
  179. print('\nBroadband 1-30 Hz [aperiodic fit only]')
  180. h_bb = np.array([np.trapezoid(run, f_def) for run in aps_def[0]])
  181. print(f' HC = {h_bb.mean():.3g} +/- {h_bb.std():.3g}')
  182. for c, cond in enumerate([aps_def[1], aps_def[2], aps_def[3]]):
  183. scz_bb = np.array([np.trapezoid(run, f_def) for run in cond])
  184. pct = round(((scz_bb.mean()-h_bb.mean())/h_bb.mean())*100)
  185. pval = round(st.ttest_ind(h_bb, scz_bb)[1], 5)
  186. d = round(cohen_d(scz_bb, h_bb), 2)
  187. print(f' {condlabels_def[c]} = {scz_bb.mean():.3g} +/- {scz_bb.std():.3g} {pct:+d}% p={pval} d={d}')
  188. print('\nAperiodic — Offsets')
  189. h_m, h_sd = np.mean(10**np.array(offsets_def[0])), np.std(10**np.array(offsets_def[0]))
  190. print(f' HC = {h_m:.3g} +/- {h_sd:.3g}')
  191. nc_def = 4
  192. for c in range(1, nc_def):
  193. sm, ss2 = np.mean(10**np.array(offsets_def[c])), np.std(10**np.array(offsets_def[c]))
  194. pct = round(((sm-h_m)/h_m)*100)
  195. pval = round(st.ttest_ind(offsets_def[0], offsets_def[c])[1], 5)
  196. d = round(cohen_d(offsets_def[c], offsets_def[0]), 2)
  197. print(f' {condlabels_def[c-1]} = {sm:.3g} +/- {ss2:.3g} {pct:+d}% p={pval} d={d}')
  198. print('\nAperiodic — Exponents')
  199. h_m, h_sd = np.mean(exponents_def[0]), np.std(exponents_def[0])
  200. print(f' HC = {h_m:.3g} +/- {h_sd:.3g}')
  201. for c in range(1, nc_def):
  202. sm, ss2 = np.mean(exponents_def[c]), np.std(exponents_def[c])
  203. pct = round(((sm-h_m)/h_m)*100)
  204. pval = round(st.ttest_ind(exponents_def[0], exponents_def[c])[1], 5)
  205. d = round(cohen_d(exponents_def[c], exponents_def[0]), 2)
  206. print(f' {condlabels_def[c-1]} = {sm:.3g} +/- {ss2:.3g} {pct:+d}% p={pval} d={d}')
  207. for band, blabel in zip(bandlims2, bandlim_labels2):
  208. print(f'\n>>> {blabel} ({band[0]}-{band[1]} Hz) [FOOOF peaks]')
  209. for c in range(3):
  210. h_runs = sum(1 for run in peak_params_def[0] if any(band[0]<=p[0]<=band[1] for p in run))
  211. scz_runs = sum(1 for run in peak_params_def[c+1] if any(band[0]<=p[0]<=band[1] for p in run))
  212. print(f' {condlabels_def[c]} (HC: {h_runs}/{n} runs, SCZ: {scz_runs}/{n} runs with detected peaks)')
  213. for param in range(3):
  214. h = [p[param] for run in peak_params_def[0] for p in run if band[0]<=p[0]<=band[1]]
  215. scz = [p[param] for run in peak_params_def[c+1] for p in run if band[0]<=p[0]<=band[1]]
  216. if not h or not scz: continue
  217. hm,hs = np.mean(h), np.std(h)
  218. sm,ss2 = np.mean(scz), np.std(scz)
  219. pct = round(((sm-hm)/hm)*100)
  220. _, p = st.ttest_ind(h, scz)
  221. d = round(cohen_d(h, scz), 2)
  222. print(f' {paramlabels[param]} HC={hm:.3g} +/- {hs:.3g} SCZ={sm:.3g} +/- {ss2:.3g} {pct:+d}% p={round(p,5)} d={d}')
  223. # Combined Plotting
  224. colors_abc = ['k', plt.cm.plasma(0.345), plt.cm.plasma(0.115)]
  225. labels_abc = ['Healthy', r'SCZ$_{20\_PV}$', r'SCZ$_{40\_PV}$']
  226. colors_def = ['k', 'darkorchid', 'crimson', '#4169E1']
  227. labels_def = ['Healthy', r'SCZ$_{40\_PV}$', r'SCZ$_{40\_SST}$', r'SCZ$_{40\_PV+SST}$']
  228. band_labels = [r'$\delta$', r'$\theta$', r'$\alpha$', r'$\beta$']
  229. band_xs_main = [2.5, 5.5, 9.5, 18]
  230. band_xs_inset = [2.7, 5.0, 8.5, 18]
  231. f_plot = h_rest['mean_psd']['freq_wel']
  232. end_p = np.argmin(np.abs(f_plot - 30))
  233. fig4 = plt.figure(figsize=(11, 8))
  234. row_y = [0.56, 0.07]; row_h = 0.38
  235. col_x = [0.04, 0.37, 0.70]; col_w = 0.24
  236. axes = [fig4.add_axes([col_x[i], row_y[j], col_w, row_h]) for j in range(2) for i in range(3)]
  237. axA, axB, axC, axD, axE, axF = axes
  238. # Row 1 (A-C)
  239. insetA = axA.inset_axes([.55, .55, .45, .45])
  240. insetB = axB.inset_axes([.55, .55, .45, .45])
  241. data_abc = [h_rest, scz20, scz40]
  242. for c in range(3):
  243. for ax in [insetA, axA]:
  244. ax.plot(f_plot[:end_p], data_abc[c]['mean_psd']['ps_wel_bm'][:end_p], color=colors_abc[c], lw=2)
  245. ax.fill_between(f_plot[:end_p], data_abc[c]['mean_psd']['ps_wel_lower'][:end_p], data_abc[c]['mean_psd']['ps_wel_upper'][:end_p], color=colors_abc[c], alpha=.1)
  246. for ax in [insetB, axB]:
  247. ax.plot(f_abc, aps_bs_abc[c][0], color=colors_abc[c], lw=2)
  248. ax.fill_between(f_abc, aps_bs_abc[c][1], aps_bs_abc[c][2], color=colors_abc[c], alpha=.1)
  249. axC.plot(f_abc, ps_bs_abc[c][0], color=colors_abc[c], lw=2, label=labels_abc[c])
  250. axC.fill_between(f_abc, ps_bs_abc[c][1], ps_bs_abc[c][2], color=colors_abc[c], alpha=.1)
  251. # Row 2 (D-F)
  252. insetD = axD.inset_axes([.57, .55, .43, .45])
  253. insetE = axE.inset_axes([.55, .55, .45, .45])
  254. data_def = [h_rest, scz40_pv, scz40_sst, scz40_both]
  255. for c in range(4):
  256. for ax in [insetD, axD]:
  257. ax.plot(f_plot[:end_p], data_def[c]['mean_psd']['ps_wel_bm'][:end_p], color=colors_def[c])
  258. ax.fill_between(f_plot[:end_p], data_def[c]['mean_psd']['ps_wel_lower'][:end_p], data_def[c]['mean_psd']['ps_wel_upper'][:end_p], color=colors_def[c], alpha=.1)
  259. for ax in [insetE, axE]:
  260. ax.plot(f_def, aps_bs_def[c][0], color=colors_def[c])
  261. ax.fill_between(f_def, aps_bs_def[c][1], aps_bs_def[c][2], color=colors_def[c], alpha=.1)
  262. axF.plot(f_def, ps_bs_def[c][0], color=colors_def[c], label=labels_def[c])
  263. axF.fill_between(f_def, ps_bs_def[c][1], ps_bs_def[c][2], color=colors_def[c], alpha=.1)
  264. # Generic styling
  265. for ax, inset, lim_h, h_main, bs_main in zip([axA, axB, axD, axE], [insetA, insetB, insetD, insetE], [3.8e-14, 1.5e-14, 4.8e-14, 2.5e-14], [3.8e-14, 1.5e-14, 4.8e-14, 2.5e-14], [0.08e-14]*4):
  266. inset.set_xscale('log'); inset.set_yscale('log')
  267. inset.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
  268. inset.set_ylim(1.3e-15, lim_h); inset.set_xlim(1, 30); inset.set_yticks([]); inset.set_xticks([2, 10, 30])
  269. [inset.spines[s].set_visible(False) for s in ['top', 'right']]
  270. [inset.axvline(i, ls='--', alpha=.4, color='k') for i in [4, 8, 12]]
  271. [inset.annotate(t, xy=(x, 1.6e-15), fontsize=9) for x, t in zip(band_xs_inset, band_labels)]
  272. ax.set_ylim(0, h_main)
  273. [ax.annotate(t, xy=(x, bs_main), fontsize=9) for x, t in zip(band_xs_main, band_labels)]
  274. for ax in axes:
  275. [ax.spines[s].set_visible(False) for s in ['top', 'right']]
  276. ax.set_xlim(1, 30); ax.set_xlabel('Frequency (Hz)')
  277. [ax.axvline(i, ls='--', alpha=.4, color='k') for i in [4, 8, 12]]
  278. axA.set_ylabel(r'Power $\left( \frac{ V^{2} }{Hz} \right)$')
  279. axB.set_ylabel(r'Power $\left( \frac{ V^{2} }{Hz} \right)$')
  280. axC.set_ylabel('log(Power)'); axC.set_ylim(-0.07)
  281. [axC.annotate(t, xy=(x, -.042), fontsize=9) for x, t in zip(band_xs_main, band_labels)]
  282. axD.set_ylabel(r'Power $\left( \frac{ V^{2} }{Hz} \right)$')
  283. axE.set_ylabel(r'Power $\left( \frac{ V^{2} }{Hz} \right)$')
  284. axF.set_ylabel('log(Power)'); axF.set_ylim(-0.07)
  285. [axF.annotate(t, xy=(x, -.042), fontsize=9) for x, t in zip(band_xs_main, band_labels)]
  286. legc = axC.legend(frameon=False, loc='upper right', bbox_to_anchor=(1.05, 0.95), fontsize=10, handlelength=1)
  287. legf = axF.legend(frameon=False, loc='upper right', bbox_to_anchor=(1.05, 0.95), fontsize=10, handlelength=1)
  288. for ax, title in zip(axes, ['A', 'B', 'C', 'D', 'E', 'F']): ax.set_title(title, loc='left', x=-0.3, y=1.05)
  289. plt.savefig('Fig 4.tiff', dpi=300, facecolor='white', edgecolor='none', bbox_inches='tight')
  290. print('Saved Fig 4_repro.jpg')
  291. if __name__ == "__main__":
  292. main()

fig_4.py, under CC-BY-4.0 · at the source

Overview

Authors: Sana Rosanally1,2, Frank Mazza1,2,3, Heng Kang Yao1,2, Faraz Moghbel1,2, Hannah Seo1,2, Etay Hay1,2,4
ORCID iDs: Hannah Seo, Etay Hay
  1. Krembil Centre for Neuroinformatics, Centre for Addiction and Mental Health, Toronto, Ontario, Canada
  2. Department of Physiology, University of Toronto, Toronto, Ontario, Canada
  3. Schulich School of Medicine, University of Western Ontario, London, Ontario, Canada
  4. Department of Psychiatry, University of Toronto, Toronto, Ontario, Canada
Journal: PLoS computational biology, volume 22, issue 6, article e1014304
Dates: received 22 June 2025; accepted 7 May 2026; published online 2 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1371/journal.pcbi.1014304 · PMID 42228689 · PMCID PMC13229350 · OpenAlex W4385837043
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), human (organism), schizophrenia / psychosis (population)
Methods: Connectivity, Spectral & time-frequency, Statistics, Preprocessing, Evoked potentials, Single-unit activity, calcium imaging
MeSH: Electroencephalography*, Models, Neurological*, Prefrontal Cortex*, Schizophrenia*, Biomarkers, Computational Biology, Computer Simulation, Humans, Interneurons, Neural Inhibition, Parvalbumins, Somatostatin (* major topic)
Topic: Neural dynamics and brain function (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Citations: not cited yet (Europe PMC); 94 references in the paper

Abstract

Reduced cortical inhibition by parvalbumin-expressing (PV) interneurons in schizophrenia is thought to be associated with impaired processing in the prefrontal cortex and altered EEG signals such as oddball mismatch negativity (MMN). Recent studies also suggest loss of somatostatin (SST) interneuron inhibition. However, establishing the link between reduced interneuron inhibition and reduced MMN experimentally in humans is currently not possible. To overcome these challenges, we simulated spiking activity and EEG during baseline and oddball response in detailed models of human prefrontal microcircuits in health and schizophrenia, with reduced PV and SST interneuron inhibition as constrained by postmortem patient data. We showed that reduced PV interneuron inhibition can account for the decreased MMN amplitude seen in schizophrenia, with a threshold below which the amplitude effect was low as seen in at-risk patients. In contrast, reduced SST interneuron inhibition did not affect the MMN amplitude. We further showed that both types of inhibition loss were necessary to account for changes in resting EEG in schizophrenia, with reduced SST interneuron inhibition increasing broadband power, and reduced PV and SST interneuron inhibition both leading to a right shift from alpha to beta frequencies. Our study thus links reduced PV and SST interneuron inhibition in schizophrenia to distinct EEG biomarkers that can serve to improve stratification and early detection using non-invasive brain signals.

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 7 matches between paragraphs and lines of code.

Zenodo 15645365

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: “Data Availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NEURON (24 files), Matplotlib (6 files), NumPy (6 files), SciPy (6 files), LFPy (2 files), scikit-learn (2 files), pandas (1 file), specparam (formerly FOOOF) (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
31 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;
  • 31 scripts, each with its path and the digest of its content;
  • 7 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

All models and original code for simulations and figures have been deposited on Zenodo (https://doi.org/10.5281/zenodo.15645365).

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

Versions

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

Version 2, 28 September 2026

  • Funding: added University of Toronto; Krembil Foundation; IAMGOLD

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 12 MeSH terms, 93 references.

Cite

This paper

Rosanally, S., Mazza, F., Yao, H. K., Moghbel, F., Seo, H., & Hay, E. (2026). Linking reduced prefrontal microcircuit inhibition in schizophrenia to EEG biomarkers in silico. PLoS computational biology, 22(6), e1014304. https://doi.org/10.1371/journal.pcbi.1014304

BibTeX

@article{rosanally2026linking,
author = {Rosanally, Sana and Mazza, Frank and Yao, Heng Kang and Moghbel, Faraz and Seo, Hannah and Hay, Etay},
title = {{Linking reduced prefrontal microcircuit inhibition in schizophrenia to EEG biomarkers in silico}},
journal = {PLoS computational biology},
year = {2026},
month = jun,
volume = {22},
number = {6},
pages = {e1014304},
publisher = {PLOS},
issn = {1553-734X},
doi = {10.1371/journal.pcbi.1014304},
url = {https://doi.org/10.1371/journal.pcbi.1014304},
pmid = {42228689},
pmcid = {PMC13229350}
}

RIS

TY - JOUR
AU - Rosanally, Sana
AU - Mazza, Frank
AU - Yao, Heng Kang
AU - Moghbel, Faraz
AU - Seo, Hannah
AU - Hay, Etay
TI - Linking reduced prefrontal microcircuit inhibition in schizophrenia to EEG biomarkers in silico
T2 - PLoS computational biology
J2 - PLoS Comput Biol
PY - 2026
DA - 2026/06/02
VL - 22
IS - 6
SP - e1014304
SN - 1553-734X
PB - PLOS
DO - 10.1371/journal.pcbi.1014304
UR - https://doi.org/10.1371/journal.pcbi.1014304
LA - en
ER -

CSL-JSON

{
"id": "10.1371/journal.pcbi.1014304",
"type": "article-journal",
"title": "Linking reduced prefrontal microcircuit inhibition in schizophrenia to EEG biomarkers in silico",
"container-title": "PLoS computational biology",
"author": [
{
"family": "Rosanally",
"given": "Sana"
},
{
"family": "Mazza",
"given": "Frank"
},
{
"family": "Yao",
"given": "Heng Kang"
},
{
"family": "Moghbel",
"given": "Faraz"
},
{
"family": "Seo",
"given": "Hannah"
},
{
"family": "Hay",
"given": "Etay"
}
],
"container-title-short": "PLoS Comput Biol",
"volume": "22",
"issue": "6",
"page": "e1014304",
"DOI": "10.1371/journal.pcbi.1014304",
"PMID": "42228689",
"PMCID": "PMC13229350",
"ISSN": "1553-734X",
"publisher": "PLOS",
"URL": "https://doi.org/10.1371/journal.pcbi.1014304",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
2
]
]
}
}

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.1371/journal.pcbi.1014283 [code]
Spatial richness of neural magnetic fields.
Journal: PLoS computational biology
In common: LFPy, NEURON, scikit-learn, 3 other tools, 1 reference
[2] doi:10.1038/s41467-026-71331-0 [code]
A multimodal approach for visualizing and identifying electrophysiological cell types in vivo.
Journal: Nature communications
In common: scikit-learn, pandas, SciPy, 2 other tools, 4 references
[3] doi:10.1016/j.isci.2026.115488 [code]
An integrated &lt;i&gt;i&lt;/i&gt; &lt;i&gt;n vitro&lt;/i&gt; platform and biophysical modeling approach for studying synaptic transmission in isolated neuronal pairs.
Journal: iScience
In common: LFPy, NEURON, scikit-learn, 4 other tools
[4] doi:10.1073/pnas.2533168123 [code]
Dendritic morphology and synaptic nonlinearities enhance functional complexity in human cortical neurons.
Journal: Proceedings of the National Academy of Sciences of the United States of America
In common: NEURON, scikit-learn, pandas, 3 other tools, 2 references
[5] doi:10.1038/s41467-026-73818-2 [code]
Prefrontal parvalbumin neurons mediate working memory in a task demand-dependent manner.
Journal: Nature communications
In common: scikit-learn, pandas, SciPy, 2 other tools, schizophrenia / psychosis, 3 references
[6] doi:10.7554/elife.100605 [code]
Age-related changes in ‘cortical’ 1/f dynamics are linked to cardiac activity
Journal: n/a
In common: specparam (formerly FOOOF), scikit-learn, pandas, 3 other tools, 2 references
[7] doi:10.7554/elife.103046 [code]
Dichotomy between extracellular signatures of active dendritic chemical synapses and gap junctions.
Journal: eLife
In common: LFPy, NEURON, Matplotlib, 1 other tool, 1 reference
[8] doi:10.1038/s41467-026-72935-2 [code]
Spindle neurons in human cortex possess distinctive firing properties and transcriptomic signatures.
Journal: Nature communications
In common: scikit-learn, pandas, SciPy, 2 other tools, 3 references
[9] doi:10.1093/cercor/bhag113 [code]
Long-term reliability and stability of parameterized resting state EEG: evidence from a five-year follow-up.
Journal: Cerebral cortex (New York, N.Y. : 1991)
In common: specparam (formerly FOOOF), scikit-learn, pandas, 3 other tools, EEG, 1 reference
[10] doi:10.3389/fncom.2026.1786996 [code]
Schumann-anchored golden ratio organization of human neural oscillations.
Journal: Frontiers in computational neuroscience
In common: specparam (formerly FOOOF), scikit-learn, pandas, 3 other tools, EEG, 1 reference

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.