OSCR

An integrated machine learning framework for cerebral aneurysm rupture status classification using clinical, morphological, and hemodynamic features.

Code ↔ Paper

19 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 19 matches
  1. [1] § Methodology › Machine Learning ↔ Aneurysm_Classification.ipynb, lines 2479 to 2538 · score 0.66 · confusion matrix, roc_auc, cross validated, F1 score, precision, probabilities · Report a mismatch
  2. [2] § Methodology › Machine Learning ↔ aneurysm_classification.py, lines 2389 to 2445 · score 0.66 · confusion matrix, roc_auc, cross validated, F1 score, precision, probabilities · Report a mismatch
  3. [3] § Results ↔ Aneurysm_Classification.ipynb, lines 4740 to 4871 · score 0.64 · F1 score, BaggingSVM, Gaussian Process, ROC AUC, precision, Accuracy · Report a mismatch
  4. [4] § Methodology › Machine Learning ↔ Aneurysm_Classification.ipynb, lines 2272 to 2400 · score 0.61 · best mapped accuracy, unsupervised models, assignment, affinity, clustering, algorithms · Report a mismatch
  5. [5] § Results ↔ Aneurysm_Classification.ipynb, lines 2272 to 2400 · score 0.60 · best mapped accuracy, Agglomerative clustering, DBSCAN, Spectral, Birch, Unsupervised · Report a mismatch
  6. [6] § Results ↔ aneurysm_classification.py, lines 2223 to 2265 · score 0.60 · best mapped accuracy, Agglomerative clustering, DBSCAN, Spectral, Birch, Unsupervised · Report a mismatch
  7. [7] § Methodology › Clinical and morphological parameters ↔ Aneurysm_Classification.ipynb, lines 5817 to 5879 · score 0.59 · LB, numerically, BA, SCA, ACA, vessel · Report a mismatch
  8. [8] § Methodology › Clinical and morphological parameters ↔ aneurysm_classification.py, lines 5700 to 5766 · score 0.59 · LB, numerically, BA, SCA, ACA, vessel · Report a mismatch
  9. [9] § Results ↔ Aneurysm_Classification.ipynb, lines 1190 to 1297 · score 0.58 · GaussianNB, Decision Tree, Gradient Boosting, AdaBoost, SVM, classification · Report a mismatch
  10. [10] § Results ↔ aneurysm_classification.py, lines 1205 to 1289 · score 0.58 · GaussianNB, Decision Tree, Gradient Boosting, AdaBoost, SVM, classification · Report a mismatch
  11. [11] § Methodology › Machine Learning ↔ aneurysm_classification.py, lines 2223 to 2265 · score 0.57 · best mapped accuracy, unsupervised models, affinity, clustering, algorithms, classification · Report a mismatch
  12. [12] § Results ↔ aneurysm_classification.py, lines 4601 to 4657 · score 0.56 · BaggingSVM, Gaussian Process, ROC AUC, precision, score, recall · Report a mismatch
  13. [13] § Methodology › Study Population ↔ aneurysm_classification.py, lines 750 to 813 · score 0.56 · Barcelona, Geneva, Milan, Sheffield, SCA, ICA · Report a mismatch
  14. [14] § Methodology › Machine Learning ↔ Aneurysm_Classification.ipynb, lines 862 to 969 · score 0.53 · UMAP, t-SNE, distances, Neighbor, unruptured, classification · Report a mismatch
  15. [15] § Methodology › Machine Learning ↔ aneurysm_classification.py, lines 881 to 964 · score 0.53 · UMAP, t-SNE, distances, Neighbor, unruptured, classification · Report a mismatch
  16. [16] § Methodology › Machine Learning ↔ aneurysm_classification.py, lines 4113 to 4202 · score 0.52 · stratified split, leakage, Gaussian Process, tuning, fold, variables · Report a mismatch
  17. [17] § Results ↔ aneurysm_classification.py, lines 4601 to 4657 · score 0.52 · threshold tuning, BaggingSVM, Gaussian Process, ROC AUC, Precision, Recall · Report a mismatch
  18. [18] § Methodology › Machine Learning ↔ aneurysm_classification.py, lines 2999 to 3063 · score 0.51 · TensorFlow, XGBoost, boosting, models · Report a mismatch
  19. [19] § Methodology › Study Population ↔ aneurysm_classification.py, lines 59 to 163 · score 0.51 · Bar charts, outside, pale, slice, ICA, Radial · 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

Python · 6,083 lines · 195 KB · no license · 12 matches

  1. # -*- coding: utf-8 -*-
  2. """Aneurysm_Classification.ipynb
  3. Automatically generated by Colab.
  4. Original file is located at
  5. https://colab.research.google.com/drive/1ul27HYChjrleBP998ykEHyUqMO-X2Z-_
  6. #Study Population
  7. """
  8. import numpy as np
  9. import matplotlib.pyplot as plt
  10. from matplotlib.patches import Wedge
  11. from mpl_toolkits.axes_grid1.inset_locator import inset_axes
  12. # ----------------------------
  13. # GLOBAL STYLE (Times New Roman + bigger fonts)
  14. # ----------------------------
  15. plt.rcParams["font.family"] = "serif"
  16. plt.rcParams["font.serif"] = ["Times New Roman", "Times", "DejaVu Serif"]
  17. plt.rcParams.update({
  18. "font.size": 20,
  19. "axes.titlesize": 20,
  20. "axes.labelsize": 20,
  21. "xtick.labelsize": 20,
  22. "ytick.labelsize": 20,
  23. "legend.fontsize": 22,
  24. })
  25. # ----------------------------
  26. # DATA
  27. # ----------------------------
  28. data = {
  29. "Overall": {"UF": 351, "UM": 105, "RF": 169, "RM": 83, "F_total": 520, "M_total": 188, "Total": 708},
  30. "ICA": {"UF": 187, "UM": 36, "RF": 79, "RM": 21, "F_total": 266, "M_total": 57, "Total": 323},
  31. "MCA": {"UF": 99, "UM": 41, "RF": 23, "RM": 18, "F_total": 122, "M_total": 59, "Total": 181},
  32. "ACA": {"UF": 36, "UM": 24, "RF": 47, "RM": 36, "F_total": 83, "M_total": 60, "Total": 143},
  33. "BA/VA": {"UF": 18, "UM": 2, "RF": 15, "RM": 5, "F_total": 33, "M_total": 7, "Total": 40},
  34. "PCA": {"UF": 9, "UM": 0, "RF": 4, "RM": 3, "F_total": 13, "M_total": 3, "Total": 16},
  35. "SCA": {"UF": 2, "UM": 1, "RF": 1, "RM": 1, "F_total": 3, "M_total": 2, "Total": 5},
  36. }
  37. bar_locs = ["Overall", "ICA", "MCA", "ACA", "BA/VA", "PCA", "SCA"]
  38. pie_locs = ["ICA", "MCA", "ACA", "BA/VA", "PCA", "SCA"]
  39. # Colors (requested)
  40. COL_F = "#C2185B" # Female = pink
  41. COL_M = "#2E5EAA" # Male = blue
  42. COL_T = "#4A4A4A" # Total = green
  43. TXT = "#0f172a"
  44. pale_alpha = 0.22
  45. def pct(x, denom):
  46. return 0.0 if denom == 0 else 100.0 * x / denom
  47. # ----------------------------
  48. # Radial pie with radius-proportional ruptured portion
  49. # (using sqrt so solid AREA is proportional to rupture%)
  50. # ----------------------------
  51. def rupture_radial_pie(ax, locs, rup_counts, tot_counts, vivid_color, title):
  52. ax.set_aspect("equal")
  53. ax.axis("off")
  54. n = len(locs)
  55. start_angle = 90
  56. slice_angle = 360.0 / n
  57. R_outer = 1.0
  58. label_r = 1.32
  59. name_r = 1.55
  60. pale_alpha = 0.22
  61. ax.text(0, 1.75, title, ha="center", va="bottom",
  62. fontsize=26, fontweight="bold", color=vivid_color)
  63. for i, loc in enumerate(locs):
  64. rup = float(rup_counts[i])
  65. tot = float(tot_counts[i])
  66. rr = 0.0 if tot == 0 else rup / tot
  67. th1 = start_angle - i * slice_angle
  68. th2 = th1 - slice_angle
  69. # Pale full slice
  70. ax.add_patch(Wedge(
  71. (0, 0), R_outer, th2, th1,
  72. width=R_outer,
  73. facecolor=(plt.matplotlib.colors.to_rgba(vivid_color, pale_alpha)),
  74. edgecolor="white", linewidth=2.2
  75. ))
  76. # Solid ruptured fill (radius)
  77. if rr > 0:
  78. r_solid = R_outer * np.sqrt(rr)
  79. ax.add_patch(Wedge(
  80. (0, 0), r_solid, th2, th1,
  81. width=r_solid,
  82. facecolor=vivid_color,
  83. edgecolor="white", linewidth=2.2
  84. ))
  85. mid = np.deg2rad((th1 + th2) / 2.0)
  86. # Format percentage (remove decimal if exactly 100)
  87. rr_pct = rr * 100.0
  88. if np.isclose(rr_pct, 100.0):
  89. pct_text = "100%"
  90. else:
  91. pct_text = f"{rr_pct:.1f}%"
  92. # # % label outside
  93. # ax.text(label_r * np.cos(mid), label_r * np.sin(mid),
  94. # f"{rr_pct:.1f}%", ha="center", va="center",
  95. # fontsize=18, color=TXT,
  96. # bbox=dict(boxstyle="round,pad=0.25", fc="white", ec="none", alpha=0.92))
  97. # # Location label
  98. # ax.text(name_r * np.cos(mid), name_r * np.sin(mid),
  99. # loc, ha="center", va="center",
  100. # fontsize=18, color=TXT)
  101. # Default positions (for all slices except ICA and PCA)
  102. x_pct = label_r * np.cos(mid)
  103. y_pct = label_r * np.sin(mid)
  104. x_loc = name_r * np.cos(mid)
  105. y_loc = name_r * np.sin(mid)
  106. # Special adjustment ONLY for ICA and PCA
  107. if loc in ["MCA", "PCA"]:
  108. extra_r = 0.06 # move further away from pie
  109. x_pct = (label_r + extra_r) * np.cos(mid)
  110. y_pct = (label_r + extra_r) * np.sin(mid)
  111. # Stack percentage above, location below
  112. offset = 0.06
  113. ax.text(x_pct, y_pct,
  114. pct_text, ha="center", va="bottom",
  115. fontsize=18, color=TXT,
  116. bbox=dict(boxstyle="round,pad=0.25", fc="white", ec="none", alpha=0.92))
  117. ax.text(x_pct, y_pct - offset,
  118. loc, ha="center", va="top",
  119. fontsize=18, color=TXT)
  120. else:
  121. # Original behavior for other slices
  122. ax.text(x_pct, y_pct,
  123. pct_text, ha="center", va="center",
  124. fontsize=18, color=TXT,
  125. bbox=dict(boxstyle="round,pad=0.25", fc="white", ec="none", alpha=0.92))
  126. ax.text(x_loc, y_loc,
  127. loc, ha="center", va="center",
  128. fontsize=18, color=TXT)
  129. ax.set_xlim(-1.95, 1.95)
  130. ax.set_ylim(-1.65, 2.05)
  131. # ----------------------------
  132. # BAR CHART (counts) + labels on bars
  133. # ----------------------------
  134. F_counts = np.array([data[l]["F_total"] for l in bar_locs])
  135. M_counts = np.array([data[l]["M_total"] for l in bar_locs])
  136. T_counts = np.array([data[l]["Total"] for l in bar_locs])
  137. x = np.arange(len(bar_locs))
  138. w = 0.24
  139. fig, ax = plt.subplots(figsize=(16, 7.6), dpi=300)
  140. barsF = ax.bar(x - w, F_counts, width=w, color=COL_F, alpha=0.88, label="Female")
  141. barsM = ax.bar(x, M_counts, width=w, color=COL_M, alpha=0.88, label="Male")
  142. barsT = ax.bar(x + w, T_counts, width=w, color=COL_T, alpha=0.80, label="Total")
  143. # Numbers on bars
  144. pad = max(T_counts) * 0.012
  145. def label_bars(bars):
  146. for b in bars:
  147. h = b.get_height()
  148. ax.text(b.get_x() + b.get_width()/2, h + pad, f"{int(h)}",
  149. ha="center", va="bottom", fontsize=18, color=TXT)
  150. label_bars(barsF); label_bars(barsM); label_bars(barsT)
  151. # Clean axes (no y axis)
  152. ax.set_yticks([])
  153. ax.set_ylabel("")
  154. ax.spines["left"].set_visible(False)
  155. ax.spines["top"].set_visible(False)
  156. ax.spines["right"].set_visible(False)
  157. ax.set_xticks(x)
  158. ax.set_xticklabels(bar_locs, fontsize=20)
  159. # Vertical legend
  160. ax.legend(
  161. ncol=1, frameon=False, loc="upper left",
  162. bbox_to_anchor=(0.17, 1.0), borderaxespad=0.0
  163. )
  164. # ----------------------------
  165. # BIG pies, moved up, farther apart
  166. # ----------------------------
  167. female_rup = np.array([data[l]["RF"] for l in pie_locs], float)
  168. female_tot = np.array([data[l]["F_total"] for l in pie_locs], float)
  169. male_rup = np.array([data[l]["RM"] for l in pie_locs], float)
  170. male_tot = np.array([data[l]["M_total"] for l in pie_locs], float)
  171. # Placement knobs
  172. y0 = 0.35 # higher = move up
  173. h = 0.8 # taller = bigger
  174. w_in = 0.35 # wider = bigger
  175. gap = 0.0 # farther apart
  176. x_left = 0.3
  177. x_right = x_left + w_in + gap
  178. ax_f = inset_axes(
  179. ax, width="100%", height="100%",
  180. bbox_to_anchor=(x_left, y0, w_in, h),
  181. bbox_transform=ax.transAxes, loc="lower left", borderpad=0
  182. )
  183. ax_m = inset_axes(
  184. ax, width="100%", height="100%",
  185. bbox_to_anchor=(x_right, y0, w_in, h),
  186. bbox_transform=ax.transAxes, loc="lower left", borderpad=0
  187. )
  188. rupture_radial_pie(ax_f, pie_locs, female_rup, female_tot, COL_F, "Female")
  189. rupture_radial_pie(ax_m, pie_locs, male_rup, male_tot, COL_M, "Male")
  190. plt.tight_layout()
  191. plt.savefig("bars_pies_times_new_roman.png", dpi=300, bbox_inches="tight")
  192. plt.savefig("bars_pies_times_new_roman.pdf", bbox_inches="tight")
  193. plt.show()
  194. import numpy as np
  195. import matplotlib.pyplot as plt
  196. from matplotlib.patches import Wedge
  197. from mpl_toolkits.axes_grid1.inset_locator import inset_axes
  198. # ----------------------------
  199. # GLOBAL STYLE (serif fallback for Colab)
  200. # ----------------------------
  201. plt.rcParams["font.family"] = "serif"
  202. plt.rcParams["font.serif"] = ["Times New Roman", "Times", "DejaVu Serif"]
  203. plt.rcParams.update({
  204. "font.size": 20,
  205. "axes.titlesize": 20,
  206. "axes.labelsize": 20,
  207. "xtick.labelsize": 20,
  208. "ytick.labelsize": 20,
  209. "legend.fontsize": 22,
  210. })
  211. # ----------------------------
  212. # DATA
  213. # ----------------------------
  214. data = {
  215. "Overall": {"UF": 351, "UM": 105, "RF": 169, "RM": 83, "F_total": 520, "M_total": 188, "Total": 708},
  216. "ICA": {"UF": 187, "UM": 36, "RF": 79, "RM": 21, "F_total": 266, "M_total": 57, "Total": 323},
  217. "MCA": {"UF": 99, "UM": 41, "RF": 23, "RM": 18, "F_total": 122, "M_total": 59, "Total": 181},
  218. "ACA": {"UF": 36, "UM": 24, "RF": 47, "RM": 36, "F_total": 83, "M_total": 60, "Total": 143},
  219. "BA/VA": {"UF": 18, "UM": 2, "RF": 15, "RM": 5, "F_total": 33, "M_total": 7, "Total": 40},
  220. "PCA": {"UF": 9, "UM": 0, "RF": 4, "RM": 3, "F_total": 13, "M_total": 3, "Total": 16},
  221. "SCA": {"UF": 2, "UM": 1, "RF": 1, "RM": 1, "F_total": 3, "M_total": 2, "Total": 5},
  222. }
  223. bar_locs = ["Overall", "ICA", "MCA", "ACA", "BA/VA", "PCA", "SCA"]
  224. pie_locs = ["ICA", "MCA", "ACA", "BA/VA", "PCA", "SCA"]
  225. # Colors (your palette)
  226. COL_F = "#C2185B" # Female (pink)
  227. COL_M = "#2E5EAA" # Male (blue)
  228. COL_T = "#4A4A4A" # Total (charcoal)
  229. TXT = "#0f172a"
  230. pale_alpha = 0.22
  231. def pct(x, denom):
  232. return 0.0 if denom == 0 else 100.0 * x / denom
  233. # ----------------------------
  234. # Radial pie with radius-proportional ruptured portion
  235. # ----------------------------
  236. def rupture_radial_pie(ax, locs, rup_counts, tot_counts, vivid_color, title):
  237. ax.set_aspect("equal")
  238. ax.axis("off")
  239. n = len(locs)
  240. start_angle = 90
  241. slice_angle = 360.0 / n
  242. R_outer = 1.0
  243. label_r = 1.32
  244. name_r = 1.55
  245. ax.text(0, 1.75, title, ha="center", va="bottom",
  246. fontsize=26, fontweight="bold", color=vivid_color)
  247. for i, loc in enumerate(locs):
  248. rup = float(rup_counts[i])
  249. tot = float(tot_counts[i])
  250. rr = 0.0 if tot == 0 else rup / tot
  251. th1 = start_angle - i * slice_angle
  252. th2 = th1 - slice_angle
  253. # Pale full slice
  254. ax.add_patch(Wedge(
  255. (0, 0), R_outer, th2, th1,
  256. width=R_outer,
  257. facecolor=(plt.matplotlib.colors.to_rgba(vivid_color, pale_alpha)),
  258. edgecolor="white", linewidth=2.2
  259. ))
  260. # Solid ruptured fill: sqrt so AREA matches rupture%
  261. if rr > 0:
  262. r_solid = R_outer * np.sqrt(rr)
  263. ax.add_patch(Wedge(
  264. (0, 0), r_solid, th2, th1,
  265. width=r_solid,
  266. facecolor=vivid_color,
  267. edgecolor="white", linewidth=2.2
  268. ))
  269. mid = np.deg2rad((th1 + th2) / 2.0)
  270. rr_pct = rr * 100.0
  271. if np.isclose(rr_pct, 100.0):
  272. pct_text = "100%"
  273. else:
  274. pct_text = f"{rr_pct:.1f}%"
  275. # Default positions
  276. x_pct = label_r * np.cos(mid)
  277. y_pct = label_r * np.sin(mid)
  278. x_loc = name_r * np.cos(mid)
  279. y_loc = name_r * np.sin(mid)
  280. # Special handling ONLY for MCA and PCA (as you coded)
  281. if loc in ["MCA", "PCA"]:
  282. extra_r = 0.06
  283. x_pct = (label_r + extra_r) * np.cos(mid)
  284. y_pct = (label_r + extra_r) * np.sin(mid)
  285. offset = 0.06
  286. ax.text(x_pct, y_pct,
  287. pct_text, ha="center", va="bottom",
  288. fontsize=18, color=TXT,
  289. bbox=dict(boxstyle="round,pad=0.25", fc="white", ec="none", alpha=0.92))
  290. ax.text(x_pct, y_pct - offset,
  291. loc, ha="center", va="top",
  292. fontsize=18, color=TXT)
  293. else:
  294. ax.text(x_pct, y_pct,
  295. pct_text, ha="center", va="center",
  296. fontsize=18, color=TXT,
  297. bbox=dict(boxstyle="round,pad=0.25", fc="white", ec="none", alpha=0.92))
  298. ax.text(x_loc, y_loc,
  299. loc, ha="center", va="center",
  300. fontsize=18, color=TXT)
  301. ax.set_xlim(-1.95, 1.95)
  302. ax.set_ylim(-1.65, 2.05)
  303. # ----------------------------
  304. # BAR CHART (counts) + labels
  305. # ----------------------------
  306. F_counts = np.array([data[l]["F_total"] for l in bar_locs])
  307. M_counts = np.array([data[l]["M_total"] for l in bar_locs])
  308. T_counts = np.array([data[l]["Total"] for l in bar_locs])
  309. x = np.arange(len(bar_locs))
  310. w = 0.24
  311. fig, ax = plt.subplots(figsize=(16, 7.6), dpi=300)
  312. barsF = ax.bar(x - w, F_counts, width=w, color=COL_F, alpha=0.88)
  313. barsM = ax.bar(x, M_counts, width=w, color=COL_M, alpha=0.88)
  314. barsT = ax.bar(x + w, T_counts, width=w, color=COL_T, alpha=0.80)
  315. # numbers on bars
  316. pad = max(T_counts) * 0.012
  317. def label_bars(bars):
  318. for b in bars:
  319. h = b.get_height()
  320. ax.text(b.get_x() + b.get_width()/2, h + pad, f"{int(h)}",
  321. ha="center", va="bottom", fontsize=18, color=TXT)
  322. label_bars(barsF); label_bars(barsM); label_bars(barsT)
  323. # Clean axes (no y axis)
  324. ax.set_yticks([])
  325. ax.set_ylabel("")
  326. ax.spines["left"].set_visible(False)
  327. ax.spines["top"].set_visible(False)
  328. ax.spines["right"].set_visible(False)
  329. ax.set_xticks(x)
  330. ax.set_xticklabels(bar_locs, fontsize=20)
  331. # ✅ remove legend entirely
  332. # ax.legend(...) <-- deleted on purpose
  333. # ----------------------------
  334. # PIES: Female + Male moved left, add Total pie
  335. # ----------------------------
  336. female_rup = np.array([data[l]["RF"] for l in pie_locs], float)
  337. female_tot = np.array([data[l]["F_total"] for l in pie_locs], float)
  338. male_rup = np.array([data[l]["RM"] for l in pie_locs], float)
  339. male_tot = np.array([data[l]["M_total"] for l in pie_locs], float)
  340. # Total across sexes, per location
  341. total_rup = np.array([data[l]["RF"] + data[l]["RM"] for l in pie_locs], float)
  342. total_tot = np.array([data[l]["Total"] for l in pie_locs], float)
  343. # Placement knobs (3 pies in a row)
  344. y0 = 0.4
  345. h = 0.80
  346. w_in = 0.3
  347. gap = -0.02
  348. # Shift row to the LEFT
  349. x1 = 0.15
  350. x2 = x1 + w_in + gap
  351. x3 = x2 + w_in + gap
  352. ax_f = inset_axes(ax, width="100%", height="100%",
  353. bbox_to_anchor=(x1, y0, w_in, h),
  354. bbox_transform=ax.transAxes, loc="lower left", borderpad=0)
  355. ax_m = inset_axes(ax, width="100%", height="100%",
  356. bbox_to_anchor=(x2, y0, w_in, h),
  357. bbox_transform=ax.transAxes, loc="lower left", borderpad=0)
  358. ax_t = inset_axes(ax, width="100%", height="100%",
  359. bbox_to_anchor=(x3, y0, w_in, h),
  360. bbox_transform=ax.transAxes, loc="lower left", borderpad=0)
  361. rupture_radial_pie(ax_f, pie_locs, female_rup, female_tot, COL_F, "Female")
  362. rupture_radial_pie(ax_m, pie_locs, male_rup, male_tot, COL_M, "Male")
  363. rupture_radial_pie(ax_t, pie_locs, total_rup, total_tot, COL_T, "Total")
  364. plt.tight_layout()
  365. plt.savefig("bars_3pies_times_serif.png", dpi=300, bbox_inches="tight")
  366. plt.savefig("bars_3pies_times_serif.pdf", bbox_inches="tight")
  367. plt.show()
  368. import pandas as pd
  369. import numpy as np
  370. import matplotlib.pyplot as plt
  371. from matplotlib.patches import Wedge
  372. from mpl_toolkits.axes_grid1.inset_locator import inset_axes
  373. import os
  374. # ============================
  375. # LOAD DATA
  376. # ============================
  377. df = pd.read_excel(os.path.join('..', 'content', 'ClassificationV2.xlsx'))
  378. mapping_Sex = {'Male': 0, 'Female': 1}
  379. mapping_Irregularity = {'R': 0, 'I': 1}
  380. mapping_Kind = {'L': 0, 'B': 1}
  381. mapping_Location = {'MCA': 0, 'ICA': 1, 'ACA': 2, 'PCA': 3, 'VA': 4, 'BA': 4, 'SCA': 5}
  382. mapping_Stability = {'stable': 0, 'unstable': 1}
  383. mapping_Complexity = {'simple': 0, 'complex': 1}
  384. mapping_Status = {'unruptured': 0, 'ruptured': 1}
  385. df = df.replace({
  386. 'Sex': mapping_Sex,
  387. 'I': mapping_Irregularity,
  388. 'LB': mapping_Kind,
  389. 'Location': mapping_Location,
  390. 'FS': mapping_Stability,
  391. 'FC': mapping_Complexity,
  392. 'Status': mapping_Status
  393. })
  394. df = df.drop(labels='Vessel ID', axis=1)
  395. # ============================
  396. # HOLD OUT BARCELONA
  397. # rows 557 to 632 inclusive
  398. # ============================
  399. barcelona_start = 557
  400. barcelona_end = 632
  401. test_idx = np.arange(barcelona_start, barcelona_end + 1)
  402. train_idx = df.index.difference(test_idx)
  403. df_train = df.loc[train_idx].copy()
  404. df_test = df.loc[test_idx].copy()
  405. print("Train shape:", df_train.shape)
  406. print("Test shape:", df_test.shape)
  407. # ============================
  408. # MAKE A PLOTTING COPY WITH LABELS
  409. # ============================
  410. sex_inverse = {0: 'Male', 1: 'Female'}
  411. status_inverse = {0: 'Unruptured', 1: 'Ruptured'}
  412. location_inverse = {
  413. 0: 'MCA',
  414. 1: 'ICA',
  415. 2: 'ACA',
  416. 3: 'PCA',
  417. 4: 'BA/VA',
  418. 5: 'SCA'
  419. }
  420. def make_plotting_df(df_in):
  421. d = df_in.copy()
  422. d['Sex_label'] = d['Sex'].map(sex_inverse)
  423. d['Status_label'] = d['Status'].map(status_inverse)
  424. d['Location_label'] = d['Location'].map(location_inverse)
  425. return d
  426. plot_train = make_plotting_df(df_train)
  427. plot_test = make_plotting_df(df_test)
  428. # ============================
  429. # BUILD DATA DICTIONARY
  430. # ============================
  431. bar_locs = ["Overall", "ICA", "MCA", "ACA", "BA/VA", "PCA", "SCA"]
  432. pie_locs = ["ICA", "MCA", "ACA", "BA/VA", "PCA", "SCA"]
  433. ordered_locs = ["ICA", "MCA", "ACA", "BA/VA", "PCA", "SCA"]
  434. def build_plot_data(df_subset):
  435. result = {}
  436. UF = ((df_subset['Status_label'] == "Unruptured") & (df_subset['Sex_label'] == "Female")).sum()
  437. UM = ((df_subset['Status_label'] == "Unruptured") & (df_subset['Sex_label'] == "Male")).sum()
  438. RF = ((df_subset['Status_label'] == "Ruptured") & (df_subset['Sex_label'] == "Female")).sum()
  439. RM = ((df_subset['Status_label'] == "Ruptured") & (df_subset['Sex_label'] == "Male")).sum()
  440. result["Overall"] = {
  441. "UF": int(UF),
  442. "UM": int(UM),
  443. "RF": int(RF),
  444. "RM": int(RM),
  445. "F_total": int((df_subset['Sex_label'] == "Female").sum()),
  446. "M_total": int((df_subset['Sex_label'] == "Male").sum()),
  447. "Total": int(len(df_subset)),
  448. }
  449. for loc in ordered_locs:
  450. dloc = df_subset[df_subset['Location_label'] == loc]
  451. UF = ((dloc['Status_label'] == "Unruptured") & (dloc['Sex_label'] == "Female")).sum()
  452. UM = ((dloc['Status_label'] == "Unruptured") & (dloc['Sex_label'] == "Male")).sum()
  453. RF = ((dloc['Status_label'] == "Ruptured") & (dloc['Sex_label'] == "Female")).sum()
  454. RM = ((dloc['Status_label'] == "Ruptured") & (dloc['Sex_label'] == "Male")).sum()
  455. result[loc] = {
  456. "UF": int(UF),
  457. "UM": int(UM),
  458. "RF": int(RF),
  459. "RM": int(RM),
  460. "F_total": int((dloc['Sex_label'] == "Female").sum()),
  461. "M_total": int((dloc['Sex_label'] == "Male").sum()),
  462. "Total": int(len(dloc)),
  463. }
  464. return result
  465. train_data = build_plot_data(plot_train)
  466. test_data = build_plot_data(plot_test)
  467. print("Training Overall:", train_data["Overall"])
  468. print("Testing Overall:", test_data["Overall"])
  469. # ============================
  470. # GLOBAL STYLE
  471. # ============================
  472. plt.rcParams["font.family"] = "serif"
  473. plt.rcParams["font.serif"] = ["Times New Roman", "Times", "DejaVu Serif"]
  474. plt.rcParams.update({
  475. "font.size": 20,
  476. "axes.titlesize": 20,
  477. "axes.labelsize": 20,
  478. "xtick.labelsize": 20,
  479. "ytick.labelsize": 20,
  480. "legend.fontsize": 22,
  481. })
  482. COL_F = "#C2185B"
  483. COL_M = "#2E5EAA"
  484. COL_T = "#4A4A4A"
  485. TXT = "#0f172a"
  486. pale_alpha = 0.22
  487. def rupture_radial_pie(ax, locs, rup_counts, tot_counts, vivid_color, title):
  488. ax.set_aspect("equal")
  489. ax.axis("off")
  490. n = len(locs)
  491. start_angle = 90
  492. slice_angle = 360.0 / n
  493. R_outer = 1.0
  494. label_r = 1.32
  495. name_r = 1.55
  496. ax.text(0, 1.75, title, ha="center", va="bottom",
  497. fontsize=26, fontweight="bold", color=vivid_color)
  498. for i, loc in enumerate(locs):
  499. rup = float(rup_counts[i])
  500. tot = float(tot_counts[i])
  501. rr = 0.0 if tot == 0 else rup / tot
  502. th1 = start_angle - i * slice_angle
  503. th2 = th1 - slice_angle
  504. ax.add_patch(Wedge(
  505. (0, 0), R_outer, th2, th1,
  506. width=R_outer,
  507. facecolor=(plt.matplotlib.colors.to_rgba(vivid_color, pale_alpha)),
  508. edgecolor="white", linewidth=2.2
  509. ))
  510. if rr > 0:
  511. r_solid = R_outer * np.sqrt(rr)
  512. ax.add_patch(Wedge(
  513. (0, 0), r_solid, th2, th1,
  514. width=r_solid,
  515. facecolor=vivid_color,
  516. edgecolor="white", linewidth=2.2
  517. ))
  518. mid = np.deg2rad((th1 + th2) / 2.0)
  519. rr_pct = rr * 100.0
  520. pct_text = "100%" if np.isclose(rr_pct, 100.0) else f"{rr_pct:.1f}%"
  521. x_pct = label_r * np.cos(mid)
  522. y_pct = label_r * np.sin(mid)
  523. x_loc = name_r * np.cos(mid)
  524. y_loc = name_r * np.sin(mid)
  525. if loc in ["MCA", "PCA"]:
  526. extra_r = 0.06
  527. x_pct = (label_r + extra_r) * np.cos(mid)
  528. y_pct = (label_r + extra_r) * np.sin(mid)
  529. offset = 0.06
  530. ax.text(x_pct, y_pct, pct_text,
  531. ha="center", va="bottom",
  532. fontsize=18, color=TXT,
  533. bbox=dict(boxstyle="round,pad=0.25", fc="white", ec="none", alpha=0.92))
  534. ax.text(x_pct, y_pct - offset, loc,
  535. ha="center", va="top",
  536. fontsize=18, color=TXT)
  537. else:
  538. ax.text(x_pct, y_pct, pct_text,
  539. ha="center", va="center",
  540. fontsize=18, color=TXT,
  541. bbox=dict(boxstyle="round,pad=0.25", fc="white", ec="none", alpha=0.92))
  542. ax.text(x_loc, y_loc, loc,
  543. ha="center", va="center",
  544. fontsize=18, color=TXT)
  545. ax.set_xlim(-1.95, 1.95)
  546. ax.set_ylim(-1.65, 2.05)
  547. def plot_dataset_summary(data_dict, figure_title=None, save_prefix=None):
  548. F_counts = np.array([data_dict[l]["F_total"] for l in bar_locs])
  549. M_counts = np.array([data_dict[l]["M_total"] for l in bar_locs])
  550. T_counts = np.array([data_dict[l]["Total"] for l in bar_locs])
  551. x = np.arange(len(bar_locs))
  552. w = 0.24
  553. fig, ax = plt.subplots(figsize=(16, 7.6), dpi=300)
  554. barsF = ax.bar(x - w, F_counts, width=w, color=COL_F, alpha=0.88)
  555. barsM = ax.bar(x, M_counts, width=w, color=COL_M, alpha=0.88)
  556. barsT = ax.bar(x + w, T_counts, width=w, color=COL_T, alpha=0.80)
  557. pad = max(T_counts) * 0.012 if max(T_counts) > 0 else 0.2
  558. def label_bars(bars):
  559. for b in bars:
  560. h = b.get_height()
  561. ax.text(b.get_x() + b.get_width()/2, h + pad, f"{int(h)}",
  562. ha="center", va="bottom", fontsize=18, color=TXT)
  563. label_bars(barsF)
  564. label_bars(barsM)
  565. label_bars(barsT)
  566. ax.set_yticks([])
  567. ax.set_ylabel("")
  568. ax.spines["left"].set_visible(False)
  569. ax.spines["top"].set_visible(False)
  570. ax.spines["right"].set_visible(False)
  571. ax.set_xticks(x)
  572. ax.set_xticklabels(bar_locs, fontsize=20)
  573. if figure_title is not None:
  574. ax.set_title(figure_title, pad=18)
  575. female_rup = np.array([data_dict[l]["RF"] for l in pie_locs], float)
  576. female_tot = np.array([data_dict[l]["F_total"] for l in pie_locs], float)
  577. male_rup = np.array([data_dict[l]["RM"] for l in pie_locs], float)
  578. male_tot = np.array([data_dict[l]["M_total"] for l in pie_locs], float)
  579. total_rup = np.array([data_dict[l]["RF"] + data_dict[l]["RM"] for l in pie_locs], float)
  580. total_tot = np.array([data_dict[l]["Total"] for l in pie_locs], float)
  581. y0 = 0.4
  582. h = 0.80
  583. w_in = 0.3
  584. gap = -0.02
  585. x1 = 0.15
  586. x2 = x1 + w_in + gap
  587. x3 = x2 + w_in + gap
  588. ax_f = inset_axes(ax, width="100%", height="100%",
  589. bbox_to_anchor=(x1, y0, w_in, h),
  590. bbox_transform=ax.transAxes, loc="lower left", borderpad=0)
  591. ax_m = inset_axes(ax, width="100%", height="100%",
  592. bbox_to_anchor=(x2, y0, w_in, h),
  593. bbox_transform=ax.transAxes, loc="lower left", borderpad=0)
  594. ax_t = inset_axes(ax, width="100%", height="100%",
  595. bbox_to_anchor=(x3, y0, w_in, h),
  596. bbox_transform=ax.transAxes, loc="lower left", borderpad=0)
  597. rupture_radial_pie(ax_f, pie_locs, female_rup, female_tot, COL_F, "Female")
  598. rupture_radial_pie(ax_m, pie_locs, male_rup, male_tot, COL_M, "Male")
  599. rupture_radial_pie(ax_t, pie_locs, total_rup, total_tot, COL_T, "Total")
  600. plt.tight_layout()
  601. if save_prefix is not None:
  602. plt.savefig(f"{save_prefix}.png", dpi=300, bbox_inches="tight")
  603. plt.savefig(f"{save_prefix}.pdf", bbox_inches="tight")
  604. plt.show()
  605. # ============================
  606. # PLOT TRAIN AND TEST
  607. # ============================
  608. plot_dataset_summary(
  609. train_data,
  610. figure_title="Training Set (Sheffield + Milan + Geneva)",
  611. save_prefix="train_set_summary"
  612. )
  613. plot_dataset_summary(
  614. test_data,
  615. figure_title="Testing Set (Barcelona Holdout)",
  616. save_prefix="test_set_summary"
  617. )
  618. """#Load Dateset"""
  619. import pandas as pd
  620. import numpy as np
  621. import os
  622. df = pd.read_excel(os.path.join('..','content','ClassificationV2.xlsx'))
  623. mapping_Sex = {'Male': 0, 'Female': 1}
  624. mapping_Irregularity = {'R': 0, 'I': 1}
  625. mapping_Kind = {'L': 0, 'B': 1}
  626. mapping_Location = {'MCA': 0, 'ICA': 1, 'ACA': 2, 'PCA': 3, 'VA': 4, 'BA': 4, 'SCA': 5}
  627. mapping_Stability = {'stable': 0, 'unstable': 1}
  628. mapping_Complexity = {'simple': 0, 'complex': 1}
  629. mapping_Status = {'unruptured': 0, 'ruptured': 1}
  630. df = df.replace({'Sex': mapping_Sex, 'I': mapping_Irregularity, 'LB': mapping_Kind, 'Location': mapping_Location, 'FS': mapping_Stability, 'FC': mapping_Complexity, 'Status': mapping_Status})
  631. df = df.drop(labels = 'Vessel ID', axis = 1)
  632. H = np.array(df)
  633. H = np.array(list(H[:,:]), dtype=np.float64)
  634. """## Data Standardization & Split"""
  635. from sklearn.model_selection import train_test_split
  636. from sklearn.preprocessing import StandardScaler
  637. y = H[:,-1]
  638. x = H[:,:-1]
  639. x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=8)
  640. standardization = StandardScaler().fit(x_train)
  641. x_train = standardization.transform(x_train)
  642. x_test = standardization.transform(x_test)
  643. """## Data Scattering"""
  644. import matplotlib.pyplot as plt
  645. from matplotlib.font_manager import FontProperties
  646. font = {'family' : 'arial',
  647. 'serif' : ['arial'],
  648. 'weight' : 'medium',
  649. 'size' : 24,
  650. 'style' : 'normal'}
  651. u, r = list(),list()
  652. for i in range(len(y)):
  653. if y[i] == 0:
  654. u.append(i)
  655. elif y[i] == 1:
  656. r.append(i)
  657. attributes = df.columns.values
  658. def find_index(name):
  659. for i in range(len(attributes)):
  660. if name == attributes[i]:
  661. return i
  662. def data_scatter3d(u, r, first_axis, second_axis, third_axis):
  663. first_axis = find_index(first_axis)
  664. second_axis = find_index(second_axis)
  665. third_axis = find_index(third_axis)
  666. fig = plt.figure(figsize=(9,7))
  667. ax = plt.subplot(111,projection='3d')
  668. ax.plot(x[u,first_axis],x[u,second_axis],x[u,third_axis],'o',markerfacecolor=(0,1,0.2),markeredgecolor='k',ms=10,label='Unruptured')
  669. ax.plot(x[r,first_axis],x[r,second_axis],x[r,third_axis],'o',markerfacecolor='red',markeredgecolor='k',ms=10,label='Ruptured')
  670. ax.set_xlabel(attributes[first_axis], labelpad=15)
  671. ax.set_ylabel(attributes[second_axis], labelpad=15)
  672. ax.set_zlabel(attributes[third_axis], labelpad=25)
  673. ax.tick_params(axis='z', which='major', pad=10)
  674. # ax.view_init(elev=25, azim=40)
  675. # fig.tight_layout()
  676. ax.legend(loc='best', bbox_to_anchor=(1.2, 1))
  677. ax.dist = 12
  678. plt.savefig('3D_DataScattering.pdf', transparent=True, dpi=300)
  679. plt.show()
  680. return
  681. def data_scatter2d(u, r, first_axis, second_axis):
  682. first_axis = find_index(first_axis)
  683. second_axis = find_index(second_axis)
  684. plt.plot(x[u,first_axis],x[u,second_axis],'o',markerfacecolor=(0,1,0.2),markeredgecolor='k',ms=10,label='Unruptured')
  685. plt.plot(x[r,first_axis],x[r,second_axis],'o',markerfacecolor='red',markeredgecolor='k',ms=10,label='Ruptured')
  686. plt.title('2D Data Scattering')
  687. plt.subplot().set(facecolor='none')
  688. plt.xlabel(attributes[first_axis])
  689. plt.ylabel(attributes[second_axis])
  690. plt.legend(loc=(0.8,0.60))
  691. plt.savefig('plot.pdf', transparent=True, dpi=300)
  692. plt.show()
  693. return
  694. # plt.savefig('plot.png', transparent=True)
  695. data_scatter3d(u, r, 'LSAR', 'EI', 'OVI')
  696. # data_scatter2d(u, r, 'EI', 'DV')
  697. import numpy as np
  698. import matplotlib.pyplot as plt
  699. from sklearn.preprocessing import StandardScaler
  700. from sklearn.decomposition import PCA
  701. from sklearn.manifold import TSNE
  702. plt.rcParams.update({
  703. "font.family": "serif",
  704. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  705. "font.size": 22,
  706. "axes.labelsize": 24,
  707. "xtick.labelsize": 22,
  708. "ytick.labelsize": 22,
  709. "legend.fontsize": 20,
  710. })
  711. COL_U = "#800000" # Maroon
  712. COL_R = "#FF8C00" # Orange
  713. EDGE = (0, 0, 0, 0.18)
  714. def umap_tsne_horizontal(x, y,
  715. umap_neighbors=25,
  716. umap_min_dist=0.15,
  717. tsne_perplexity=30,
  718. random_state=42):
  719. x = np.asarray(x)
  720. y = np.asarray(y)
  721. Xs = StandardScaler().fit_transform(x)
  722. # ---- UMAP (fallback to PCA if umap not installed)
  723. try:
  724. import umap
  725. reducer = umap.UMAP(
  726. n_neighbors=umap_neighbors,
  727. min_dist=umap_min_dist,
  728. random_state=random_state
  729. )
  730. Z_umap = reducer.fit_transform(Xs)
  731. except Exception:
  732. Z_umap = PCA(n_components=2, random_state=random_state).fit_transform(Xs)
  733. # ---- t-SNE
  734. Z_tsne = TSNE(
  735. n_components=2,
  736. perplexity=tsne_perplexity,
  737. learning_rate="auto",
  738. init="pca",
  739. random_state=random_state
  740. ).fit_transform(Xs)
  741. fig, axes = plt.subplots(1, 2, figsize=(16, 7), dpi=300)
  742. # ---------- Panel (a): UMAP ----------
  743. ax = axes[0]
  744. ax.scatter(Z_umap[y==0, 0], Z_umap[y==0, 1],
  745. s=45, c=COL_U, alpha=0.7,
  746. edgecolors=EDGE, linewidths=0.4, label="Unruptured")
  747. ax.scatter(Z_umap[y==1, 0], Z_umap[y==1, 1],
  748. s=45, c=COL_R, alpha=0.7,
  749. edgecolors=EDGE, linewidths=0.4, label="Ruptured", marker='^')
  750. ax.set_xlabel("UMAP 1", labelpad=20)
  751. ax.set_ylabel("UMAP 2", labelpad=20)
  752. ax.spines["top"].set_visible(False)
  753. ax.spines["right"].set_visible(False)
  754. ax.text(-0.15, 1.0, "a)", transform=ax.transAxes,
  755. fontsize=28, fontweight="bold", va="top")
  756. ax.legend(
  757. loc=(0.6,0.8),
  758. ncol=1,
  759. frameon=True,
  760. fancybox=True,
  761. framealpha=0.9,
  762. edgecolor="black")
  763. # ---------- Panel (b): t-SNE ----------
  764. ax = axes[1]
  765. ax.scatter(Z_tsne[y==0, 0], Z_tsne[y==0, 1],
  766. s=45, c=COL_U, alpha=0.7,
  767. edgecolors=EDGE, linewidths=0.4, label="Unruptured")
  768. ax.scatter(Z_tsne[y==1, 0], Z_tsne[y==1, 1],
  769. s=45, c=COL_R, alpha=0.7,
  770. edgecolors=EDGE, linewidths=0.4, label="Ruptured", marker='^')
  771. ax.set_xlabel("t-SNE 1", labelpad=20)
  772. ax.set_ylabel("t-SNE 2")
  773. ax.spines["top"].set_visible(False)
  774. ax.spines["right"].set_visible(False)
  775. ax.text(-0.15, 1.0, "b)", transform=ax.transAxes,
  776. fontsize=28, fontweight="bold", va="top")
  777. # Vertical legend in upper-right of right panel
  778. plt.tight_layout()
  779. plt.savefig("UMAP_tSNE_horizontal_refined.png", dpi=300, bbox_inches="tight")
  780. plt.savefig("UMAP_tSNE_horizontal_refined.pdf", bbox_inches="tight")
  781. plt.show()
  782. umap_tsne_horizontal(x, y)
  783. """#Result Representation"""
  784. from sklearn.metrics import ConfusionMatrixDisplay, classification_report, confusion_matrix, accuracy_score
  785. from sklearn.inspection import permutation_importance
  786. from IPython.display import Image
  787. import matplotlib.pyplot as plt
  788. from matplotlib import rcParams
  789. from io import StringIO
  790. import pydotplus
  791. import datetime
  792. plt.rcParams['font.size'] = 17
  793. font = {'family' : 'serif',
  794. 'serif' : ['Times New Roman'],
  795. 'weight' : 'medium',
  796. 'size' : 24,
  797. 'style' : 'normal'}
  798. class ResultsShow:
  799. plt.rcParams['font.family'] = 'serif'
  800. plt.rcParams['font.serif'] = ['Times New Roman'] + plt.rcParams['font.serif']
  801. plt.rcParams.update({'font.size': 15})
  802. def show(model, x_train, y_train, x_test, y_test):
  803. print('Training Time:', end-start )
  804. print('Train Data Accuracy: \n ', accuracy_score(y_train, model.predict(x_train)))
  805. print('Train Data Confusion Matrix: \n ', confusion_matrix(y_train, model.predict(x_train)))
  806. print('Test Data Accuracy: \n ', accuracy_score(y_test, model.predict(x_test)))
  807. print('Test Data Confusion Matrix: \n', confusion_matrix(y_test, model.predict(x_test)))
  808. return
  809. def confusion_matrix(name, model, x_test, y_test):
  810. fig = ConfusionMatrixDisplay(confusion_matrix=confusion_matrix(y_test, model.predict(x_test),\
  811. labels=model.classes_), display_labels=model.classes_)
  812. fig.plot()
  813. fig.figure_.suptitle("Confusion Matrix for " + name + " Classifier", fontweight="bold")
  814. plt.xlabel('Predicted label', fontweight="bold")
  815. plt.ylabel('True label', fontweight="bold")
  816. plt.savefig('ConfusionMatrix.png', transparent=True, bbox_inches='tight')
  817. plt.show()
  818. return
  819. def feature_exraction(model, x_train, df):
  820. attributes = df.columns.values
  821. plt.figure(figsize=(16,8))
  822. imp = model.feature_importances_
  823. imp_combine = np.zeros(len(x_train[0,:]))
  824. index, features = list(), list()
  825. for i in range(len(imp)):
  826. if imp[i] != 0:
  827. index.append(i)
  828. features.append(attributes[i])
  829. plt.bar(range(len(imp_combine)),imp)
  830. plt.xticks(index, features, size='small',rotation=90)
  831. plt.title('Important Parameters Among All Available Features')
  832. plt.savefig('FeatureExtraction.png', transparent=True)
  833. plt.show()
  834. return
  835. def feature_exraction_nozero(model, x_train, df):
  836. plt.rcParams.update({'font.size': 16})
  837. attributes = df.columns.values
  838. plt.figure(figsize=(16,8))
  839. imp = model.feature_importances_
  840. imp_combine = np.zeros(len(x_train[0,:]))
  841. index, features = list(), list()
  842. for i in range(len(imp)):
  843. if imp[i] != 0:
  844. index.append(i)
  845. features.append(attributes[i])
  846. plt.bar(range(len(index)),imp[index])
  847. plt.xticks(range(len(index)), features, size='small',rotation=0)
  848. plt.title('Important Parameters Among All Available Features')
  849. plt.ylabel('Importance of Each Parameter')
  850. plt.savefig('FeatureExtractionNozero.png', transparent=True, bbox_inches='tight')
  851. plt.show()
  852. return
  853. def feature_permutation(model, x_test, y_test):
  854. result = permutation_importance(model, x_test, y_test, n_repeats=10, random_state=None)
  855. sorted_idx = result.importances_mean.argsort()
  856. plt.figure(figsize=(8,14))
  857. plt.title('Permutation Importance of Each Feature')
  858. plt.barh(range(x.shape[1]), result.importances_mean[sorted_idx])
  859. plt.yticks(range(x.shape[1]), df.columns.values[sorted_idx])
  860. plt.xlabel('Permutation Importance')
  861. plt.savefig('FeaturePermutation.png', transparent=True, bbox_inches='tight')
  862. plt.show()
  863. return
  864. def feature_permutation_nozero(model, x_test, y_test):
  865. plt.rcParams.update({'font.size': 16})
  866. result = permutation_importance(model, x_test, y_test, n_repeats=10, random_state=None)
  867. remove_idx = list()
  868. importance_mean = result.importances_mean
  869. j = 0
  870. for i in range(x.shape[1]):
  871. if abs(result.importances_mean[i]) < 1e-3:
  872. remove_idx.append(i)
  873. importance_mean = np.delete(importance_mean, i-j)
  874. j += 1
  875. sorted_idx = importance_mean.argsort()
  876. plt.figure(figsize=(6,10))
  877. plt.title('Permutation Importance of Each Feature')
  878. plt.barh(range(len(importance_mean)), importance_mean[sorted_idx])
  879. plt.yticks(range(len(importance_mean)),
  880. df.columns.values[sorted_idx])
  881. plt.xlabel('Permutation Importance')
  882. plt.savefig('FeaturePermutationNonzero.png', transparent=True, bbox_inches='tight')
  883. plt.show()
  884. return
  885. def tree_depiction(model):
  886. plt.figure()
  887. dot_data = StringIO()
  888. tree.export_graphviz(model, out_file=dot_data,filled=True, rounded=True, special_characters=False, class_names=['U','R'])
  889. graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
  890. with open('TreeDepiction.png', 'wb') as f:
  891. f.write(graph.create_png())
  892. display(Image(graph.create_png()))
  893. return
  894. def models_comparison_supervised(names, results):
  895. plt.figure(figsize=(8,5))
  896. x = np.arange(len(names))
  897. plt.bar(x - 0.12, np.array(results)[:,0], width=0.4, color='#1874CD', label='Train', hatch='o')
  898. plt.bar(x + 0.12, np.array(results)[:,1], width=0.4, color='#FF8000', label='Test', hatch='/')
  899. plt.grid( linestyle='--', linewidth=1, axis='y', alpha=0.7)
  900. # plt.title('Accuracy of Different Supervised Calassification Models')
  901. plt.ylim(0.5,1)
  902. plt.ylabel('Accuracy')
  903. plt.xticks(ticks=x ,labels=names, rotation='vertical')
  904. plt.legend(loc='best', bbox_to_anchor=(0.75, -0.45))
  905. # plt.tight_layout()
  906. plt.savefig('supervised_accuracy.pdf', transparent=True, bbox_inches='tight')
  907. plt.show()
  908. return
  909. def models_comparison_unsupervised(models):
  910. fig = plt.figure(figsize=(8,5))
  911. plt.bar(models.keys(), models.values(), width=0.5, color='#1874CD')
  912. plt.grid( linestyle='--', linewidth=1, axis='y', alpha=0.3)
  913. # plt.title('Accuracy of Different Unsupervised Calassification Models')
  914. plt.ylabel('Accuracy')
  915. plt.xticks(rotation=90)
  916. plt.savefig('Unsupervised.pdf', transparent=True, bbox_inches='tight')
  917. plt.show()
  918. return
  919. def models_comparison_supervised_recall_precision(names, results, precision, recall):
  920. fig = plt.figure(figsize=(10,5))
  921. x = np.arange(len(names))
  922. plt.bar(x - 0.2, np.array(results)[:,1], width=0.3, color='#1874CD', label='Accuracy', hatch='\\')
  923. plt.bar(x, np.array(precision), width=0.3, color='#FF8000', label='Percision', hatch='o')
  924. plt.bar(x + 0.2, np.array(recall), width=0.3, color='#308014', label='Recall', hatch='/')
  925. plt.grid( linestyle='--', linewidth=1, axis='y', alpha=0.3)
  926. # plt.title('Comparison of Different Supervised Calassification Models for Test Data and Ruptured Class')
  927. plt.ylim(0.5,1)
  928. plt.ylabel('Score')
  929. plt.xticks(ticks=x ,labels=names, rotation='vertical')
  930. plt.legend(loc='best', bbox_to_anchor=(0.75, -0.45))
  931. plt.savefig('Supervised_recall_precision.pdf', transparent=True, bbox_inches='tight')
  932. plt.show()
  933. return
  934. """#Cross Validation"""
  935. from sklearn.model_selection import RepeatedStratifiedKFold, StratifiedShuffleSplit,\
  936. cross_val_score, cross_validate, cross_val_predict, StratifiedKFold
  937. from sklearn.ensemble import VotingClassifier
  938. def cross_validation(model, x_train, y_train):
  939. #Find the best corss validation function
  940. '''
  941. skf = StratifiedKFold(n_splits=5, shuffle=True)
  942. scores = cross_val_score(model, x_train, y_train, cv=skf, scoring='accuracy', n_jobs=-1)
  943. print(scores, scores.mean(), scores.std())
  944. skf = RepeatedStratifiedKFold(n_splits=5, n_repeats=3)
  945. scores = cross_val_score(model, x_train, y_train, cv=skf, scoring='accuracy', n_jobs=-1)
  946. print(scores, scores.mean(), scores.std())
  947. '''
  948. split_num = 10
  949. skf = StratifiedShuffleSplit(n_splits=split_num, test_size = 0.1)
  950. results = cross_validate(model, x_train, y_train, cv=skf, scoring='accuracy', return_estimator=True, n_jobs=-1)
  951. names_list = []
  952. for i in range (split_num):
  953. names_list.append(str(i+1))
  954. models_list = results["estimator"]
  955. models_list = list(zip(names_list, models_list))
  956. voting_model = VotingClassifier(models_list, voting='soft')
  957. voting_model.fit(x_train, y_train)
  958. # cross_validate is more comprehensive than cross_val_score and cross_val_predict
  959. '''
  960. scores = cross_val_score(model, x_test, y_test, cv=skf, n_jobs=-1)
  961. pred = cross_val_predict(model, x_test, y_test, cv=2, n_jobs=-1)
  962. '''
  963. return voting_model
  964. """# Supervised Classification Models"""
  965. from sklearn.gaussian_process import GaussianProcessClassifier
  966. from sklearn.ensemble import HistGradientBoostingClassifier
  967. from sklearn.semi_supervised import SelfTrainingClassifier
  968. from sklearn.ensemble import GradientBoostingClassifier
  969. from sklearn.ensemble import RandomForestClassifier
  970. from sklearn.semi_supervised import LabelSpreading
  971. from sklearn.neighbors import KNeighborsClassifier
  972. from sklearn.neural_network import MLPClassifier
  973. from sklearn.gaussian_process.kernels import RBF
  974. from sklearn.ensemble import AdaBoostClassifier
  975. from sklearn.linear_model import SGDClassifier
  976. from sklearn.ensemble import BaggingClassifier
  977. from sklearn.naive_bayes import GaussianNB
  978. from sklearn.svm import SVC
  979. from sklearn import tree
  980. from sklearn import svm
  981. import xgboost as xgb
  982. class SupervisedModels:
  983. def xgboost():
  984. model = xgb.XGBClassifier(booster='gbtree', eta=0.05, gamma=0, max_depth=4, learning_rate=0.05, subsample=0.5, colsample_bytree=0.5, alpha=1, tree_method ='hist')
  985. return model
  986. def histogram_gradient_boosting():
  987. model = HistGradientBoostingClassifier(max_iter=150, learning_rate=0.03, loss='log_loss', max_leaf_nodes=10, min_samples_leaf=20)
  988. return model
  989. def gradient_boosting():
  990. model = GradientBoostingClassifier(n_estimators=60, learning_rate=0.02, max_depth=4, min_samples_split=10)
  991. return model
  992. def adaboost():
  993. model = AdaBoostClassifier(n_estimators=110, learning_rate=0.15)
  994. return model
  995. def kneighbor():
  996. model = KNeighborsClassifier(n_neighbors=17, weights='uniform', p=1, algorithm='auto')
  997. return model
  998. def bagging_kneighbor():
  999. model = BaggingClassifier(estimator=KNeighborsClassifier(n_neighbors=5, weights='uniform', algorithm='auto'), n_estimators = 100, bootstrap=True, max_samples=0.9, max_features=0.7)
  1000. return model
  1001. def bagging_svc():
  1002. model = BaggingClassifier(estimator = svm.SVC(C=80, gamma=0.002, probability=True, class_weight=None , kernel='rbf'), n_estimators = 100, bootstrap=True, max_samples=0.9, max_features=54)
  1003. return model
  1004. def decision_tree():
  1005. model = tree.DecisionTreeClassifier(criterion='gini', splitter='random', max_depth=4, max_features=None, min_samples_split=12, random_state=None)
  1006. return model
  1007. def random_forest():
  1008. model = RandomForestClassifier(n_estimators=80, criterion='log_loss', max_depth=5, min_samples_leaf=5, max_features=0.5, min_samples_split=20, bootstrap=True)
  1009. return model
  1010. def naive_bayes():
  1011. model = GaussianNB(var_smoothing=1e-10)
  1012. return model
  1013. def support_vector_machine():
  1014. model = svm.SVC(C=80, gamma=0.002, probability=True, class_weight=None , kernel='rbf')
  1015. return model
  1016. def stochastic_gradient_descent():
  1017. model = SGDClassifier(loss="log_loss", penalty="l1", max_iter=10000, tol=7e-5, alpha=0.0002, eta0=0.01, learning_rate='adaptive', class_weight=None)
  1018. return model
  1019. def gussian_process():
  1020. model = GaussianProcessClassifier(kernel=1*RBF(2), optimizer='fmin_l_bfgs_b', n_restarts_optimizer=0)
  1021. return model
  1022. def gussian_naive_bayes():
  1023. model = GaussianNB(var_smoothing=1e-10)
  1024. return model
  1025. def selftraining():
  1026. model = SelfTrainingClassifier(HistGradientBoostingClassifier(max_iter=150, learning_rate=0.03, loss='log_loss', max_leaf_nodes=10, min_samples_leaf=20), threshold=0.5)
  1027. return model
  1028. def label_spreading():
  1029. model = LabelSpreading(n_neighbors=5, gamma=2, alpha=0.1)
  1030. return model
  1031. def multi_layer_perceptron():
  1032. model = MLPClassifier(hidden_layer_sizes=(55,18,1), tol=1e-6, batch_size=28, max_iter=2500, activation='identity', solver='sgd', alpha=0.2, learning_rate_init=0.00005, learning_rate='adaptive')
  1033. return model
  1034. """## XGBoost"""
  1035. from sklearn.model_selection import GridSearchCV
  1036. test = xgb.XGBClassifier()
  1037. parameter_space = {
  1038. 'booster': ['dart'],
  1039. 'eta': [0.05],
  1040. 'gamma': [0],
  1041. 'max_depth': [5],
  1042. 'learning_rate': [0.05],
  1043. 'n_estimators': [87],
  1044. 'subsample': [0.5],
  1045. 'colsample_bytree': [0.5],
  1046. 'alpha': [1],
  1047. 'reg_lambda': [1],
  1048. 'reg_alpha': [1]
  1049. }
  1050. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1051. print('Best parameters found:\n', search.best_params_)
  1052. #Feature Selcetion
  1053. from sklearn.feature_selection import SelectKBest
  1054. from sklearn.feature_selection import f_regression
  1055. from sklearn.feature_selection import RFE
  1056. # selector = RFE(model, n_features_to_select=50)
  1057. # selector.fit(x_train, y_train)
  1058. # x_train_new = selector.transform(x_train)
  1059. # x_test_new = selector.transform(x_test)
  1060. # selector = SelectKBest(score_func=f_regression, k=10)
  1061. # x_train_new = selector.fit_transform(x_train, y_train)
  1062. # x_test_new = selector.transform(x_test)
  1063. import xgboost as xgb
  1064. # model = xgb.XGBClassifier(booster='dart', eta=0.075, gamma=0.2, max_depth=2, learning_rate=0.015, subsample=1, colsample_bytree=1, alpha=0.5, tree_method ='hist')
  1065. model = xgb.XGBClassifier(booster='gbtree', eta=0.05, gamma=0, max_depth=4, learning_rate=0.05, subsample=0.5, colsample_bytree=0.5, alpha=1, tree_method ='hist')
  1066. start = datetime.datetime.now()
  1067. start = datetime.datetime.now()
  1068. model.fit(x_train, y_train)
  1069. # model.fit(x_train_new, y_train)
  1070. end = datetime.datetime.now()
  1071. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1072. # ResultsShow.show(model, x_train_new, y_train, x_test_new, y_test)
  1073. print('-------------------------------------------------------')
  1074. # Cross Validation Approach
  1075. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1076. # ResultsShow.feature_exraction(model, x_train,df)
  1077. # print('-------------------------------------------------------------------------------------')
  1078. # ResultsShow.feature_permutation(model, x_test, y_test,)
  1079. # attributes = df.columns.values
  1080. # plt.figure(figsize=(8,16))
  1081. # imp = model.feature_importances_
  1082. # imp_combine = np.zeros(len(x_train[0,:]))
  1083. # index, features = list(), list()
  1084. # for i in range(len(imp)):
  1085. # if imp[i] != 0:
  1086. # index.append(i)
  1087. # features.append(attributes[i])
  1088. # plt.barh(range(len(imp_combine)),imp)
  1089. # plt.yticks(index, features, size='small',rotation=0)
  1090. # plt.xlabel('Importance of Each Parameter')
  1091. # plt.title('Important Parameters Among All Available Features')
  1092. # plt.savefig('FeatureExtraction.png', transparent=True)
  1093. # plt.show()
  1094. """## Support Vector Machine_SVC"""
  1095. from sklearn.model_selection import GridSearchCV
  1096. test = svm.SVC()
  1097. # test = svm.NuSVC()
  1098. parameter_space = {
  1099. 'C': [80, 90, 70],
  1100. 'gamma': [0.002],
  1101. 'kernel': ['rbf', 'sigmoid']
  1102. }
  1103. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1104. print('Best parameters found:\n', search.best_params_)
  1105. from sklearn.metrics import PrecisionRecallDisplay
  1106. from sklearn.metrics import RocCurveDisplay
  1107. from sklearn import svm
  1108. model = svm.SVC(C=80, gamma=0.002, probability=True, class_weight=None , kernel='rbf')
  1109. # model = svm.NuSVC(nu=0.5, gamma=0.002, probability=True, class_weight=None, kernel='rbf')
  1110. start = datetime.datetime.now()
  1111. model.fit(x_train, y_train)
  1112. end = datetime.datetime.now()
  1113. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1114. print('-------------------------------------------------------')
  1115. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1116. ROC = RocCurveDisplay.from_estimator(model, x_test, y_test)
  1117. PrecisionRecallDisplay.from_estimator(model, x_test, y_test)
  1118. print('-------------------------------------------------------------------------------------')
  1119. ResultsShow.feature_permutation(model, x_test, y_test)
  1120. result = permutation_importance(model, x_test, y_test, n_repeats=10, random_state=None)
  1121. sorted_idx = result.importances_mean.argsort()
  1122. v = result.importances_mean[sorted_idx]
  1123. n = df.columns.values[sorted_idx]
  1124. print(n.shape)
  1125. print(n)
  1126. print(v)
  1127. """## Stochastic Gradient Descent"""
  1128. from sklearn.model_selection import GridSearchCV
  1129. test = SGDClassifier()
  1130. parameter_space = {
  1131. 'loss': ['log_loss'],
  1132. 'penalty': ['l1','l2'],
  1133. 'learning_rate': ['adaptive'],
  1134. 'eta0': [0.01,0.009,0.011],
  1135. 'alpha': [0.0002,0.002],
  1136. 'class_weight': [None]
  1137. }
  1138. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1139. print('Best parameters found:\n', search.best_params_)
  1140. from sklearn.linear_model import SGDClassifier
  1141. model = SGDClassifier(loss="log_loss", penalty="l1", max_iter=10000, tol=7e-5, alpha=0.0002, eta0=0.01, learning_rate='adaptive', class_weight={0: 1, 1: 1.8})
  1142. start = datetime.datetime.now()
  1143. model.fit(x_train, y_train)
  1144. end = datetime.datetime.now()
  1145. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1146. print('-------------------------------------------------------')
  1147. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1148. # RocCurveDisplay.from_estimator(model, x_test, y_test)
  1149. print('-------------------------------------------------------------------------------------')
  1150. ResultsShow.feature_permutation(model, x_test, y_test)
  1151. result = permutation_importance(model, x_test, y_test, n_repeats=10, random_state=None)
  1152. sorted_idx = result.importances_mean.argsort()
  1153. v = result.importances_mean[sorted_idx]
  1154. n = df.columns.values[sorted_idx]
  1155. print(n)
  1156. print(v)
  1157. """##KNeighbor"""
  1158. from sklearn.model_selection import GridSearchCV
  1159. test = KNeighborsClassifier()
  1160. parameter_space = {
  1161. 'n_neighbors': [11,13,15,17,19],
  1162. 'weights': ['uniform','distance'],
  1163. 'p': [1],
  1164. 'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute']
  1165. }
  1166. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1167. print('Best parameters found:\n', search.best_params_)
  1168. from sklearn.neighbors import KNeighborsClassifier
  1169. model = KNeighborsClassifier(n_neighbors=17, weights='uniform', p=1, algorithm='auto')
  1170. start = datetime.datetime.now()
  1171. model.fit(x_train, y_train)
  1172. end = datetime.datetime.now()
  1173. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1174. print('-------------------------------------------------------')
  1175. # Cross Validation Approach
  1176. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1177. print('-------------------------------------------------------------------------------------')
  1178. ResultsShow.feature_permutation(model, x_test, y_test)
  1179. """## Gaussian Process"""
  1180. from sklearn.model_selection import GridSearchCV
  1181. test = GaussianProcessClassifier()
  1182. parameter_space = {
  1183. 'kernel': [RBF(2),RBF(5)],
  1184. 'optimizer': ['fmin_l_bfgs_b'],
  1185. 'n_restarts_optimizer': [0]
  1186. }
  1187. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1188. print('Best parameters found:\n', search.best_params_)
  1189. from sklearn.gaussian_process import GaussianProcessClassifier
  1190. from sklearn.gaussian_process.kernels import RBF
  1191. model = GaussianProcessClassifier(kernel=1*RBF(2), optimizer='fmin_l_bfgs_b', n_restarts_optimizer=0)
  1192. start = datetime.datetime.now()
  1193. model.fit(x_train, y_train)
  1194. end = datetime.datetime.now()
  1195. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1196. print('-------------------------------------------------------')
  1197. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1198. print('-------------------------------------------------------------------------------------')
  1199. ResultsShow.feature_permutation(model, x_test, y_test)
  1200. """## Gaussian Naive Bayes"""
  1201. from sklearn.model_selection import GridSearchCV
  1202. test = GaussianNB()
  1203. parameter_space = {
  1204. 'var_smoothing': [1e-9,1e-10,5e-11]
  1205. }
  1206. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1207. print('Best parameters found:\n', search.best_params_)
  1208. from sklearn.naive_bayes import GaussianNB
  1209. model = GaussianNB(var_smoothing=1e-10)
  1210. start = datetime.datetime.now()
  1211. model.fit(x_train, y_train)
  1212. end = datetime.datetime.now()
  1213. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1214. print('-------------------------------------------------------')
  1215. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1216. print('-------------------------------------------------------------------------------------')
  1217. ResultsShow.feature_permutation(model, x_test, y_test)
  1218. """##Decision Tree"""
  1219. from sklearn.model_selection import GridSearchCV
  1220. test = tree.DecisionTreeClassifier()
  1221. parameter_space = {
  1222. 'criterion': ['log_loss','entropy', 'gini'],
  1223. 'splitter': ['random', 'best'],
  1224. 'max_depth': [3,5,4],
  1225. 'max_features': [0.7,0.9,None],
  1226. 'min_samples_split': [10,12,15],
  1227. 'min_samples_leaf': [5]
  1228. }
  1229. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1230. print('Best parameters found:\n', search.best_params_)
  1231. from sklearn import tree
  1232. model = tree.DecisionTreeClassifier(criterion='gini', splitter='random', max_depth=4, max_features=None, min_samples_split=12, random_state=None)
  1233. start = datetime.datetime.now()
  1234. model.fit(x_train, y_train)
  1235. end = datetime.datetime.now()
  1236. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1237. print('-------------------------------------------------------')
  1238. # Cross Validation Approach
  1239. CrossVal = cross_validation(model, x_train, y_train)
  1240. ResultsShow.show(CrossVal, x_train, y_train, x_test, y_test)
  1241. # print('-------------------------------------------------------')
  1242. # ResultsShow.feature_exraction(model, x_train, df)
  1243. # print('-------------------------------------------------------')
  1244. # ResultsShow.confusion_matrix('Decision Tree', CrossVal, x_test, y_test)
  1245. # print('-------------------------------------------------------')
  1246. # ResultsShow.tree_depiction(model)
  1247. # print('-------------------------------------------------------------------------------------')
  1248. from sklearn.metrics import PrecisionRecallDisplay
  1249. from sklearn.metrics import RocCurveDisplay
  1250. ROC = RocCurveDisplay.from_estimator(CrossVal, x_test, y_test)
  1251. plt.title('Receiver Operating Characteristic (ROC) Curve')
  1252. plt.savefig('ROC Curve.png', transparent=True, bbox_inches='tight')
  1253. PrecisionRecallDisplay.from_estimator(CrossVal, x_test, y_test)
  1254. plt.title('Precision_Recall Curve')
  1255. plt.savefig('Precision_Recall Curve.png', transparent=True, bbox_inches='tight')
  1256. """## Ensemble Methods
  1257. ###Bagging_SVC
  1258. """
  1259. from sklearn.model_selection import GridSearchCV
  1260. test = BaggingClassifier(estimator = svm.SVC(C=80, gamma=0.002, probability=True, class_weight=None , kernel='rbf'))
  1261. parameter_space = {
  1262. 'n_estimators': [100],
  1263. 'max_samples': [0.9],
  1264. 'max_features': [0.9,1],
  1265. 'bootstrap': [True]
  1266. }
  1267. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1268. print('Best parameters found:\n', search.best_params_)
  1269. from sklearn.ensemble import BaggingClassifier
  1270. from sklearn.svm import SVC
  1271. model = BaggingClassifier(estimator = svm.SVC(C=80, gamma=0.002, probability=True, class_weight=None , kernel='rbf'), n_estimators = 100, bootstrap=True, max_samples=0.9, max_features=54)
  1272. start = datetime.datetime.now()
  1273. model.fit(x_train, y_train)
  1274. end = datetime.datetime.now()
  1275. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1276. print('-------------------------------------------------------')
  1277. # Cross Validation Approach
  1278. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1279. print('-------------------------------------------------------------------------------------')
  1280. ResultsShow.feature_permutation(model, x_test, y_test)
  1281. """###Bagging_Kneighbor"""
  1282. from sklearn.model_selection import GridSearchCV
  1283. test = BaggingClassifier(estimator=KNeighborsClassifier(n_neighbors=5, weights='uniform', algorithm='auto'))
  1284. parameter_space = {
  1285. 'n_estimators': [90,100],
  1286. 'max_samples': [0.9,0.7],
  1287. 'max_features': [0.7],
  1288. 'bootstrap': [True],
  1289. }
  1290. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1291. print('Best parameters found:\n', search.best_params_)
  1292. from sklearn.ensemble import BaggingClassifier
  1293. from sklearn.neighbors import KNeighborsClassifier
  1294. model = BaggingClassifier(estimator=KNeighborsClassifier(n_neighbors=5, weights='uniform', algorithm='auto'), n_estimators = 100, bootstrap=True, max_samples=0.9, max_features=0.7)
  1295. start = datetime.datetime.now()
  1296. model.fit(x_train, y_train)
  1297. end = datetime.datetime.now()
  1298. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1299. print('-------------------------------------------------------')
  1300. # Cross Validation Approach
  1301. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1302. print('-------------------------------------------------------------------------------------')
  1303. ResultsShow.feature_permutation(model, x_test, y_test)
  1304. """###Random Forest"""
  1305. from sklearn.model_selection import GridSearchCV
  1306. test = RandomForestClassifier()
  1307. parameter_space = {
  1308. 'criterion': ['gini', 'log_loss'],
  1309. 'n_estimators': [60,70],
  1310. 'max_depth': [5,4],
  1311. 'max_features': [0.9,0.5],
  1312. 'min_samples_split': [20],
  1313. 'min_samples_leaf': [3],
  1314. 'bootstrap': [True]
  1315. }
  1316. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1317. print('Best parameters found:\n', search.best_params_)
  1318. from sklearn.ensemble import RandomForestClassifier
  1319. model = RandomForestClassifier(n_estimators=80, criterion='log_loss', max_depth=5, min_samples_leaf=5, max_features=0.5, min_samples_split=20, bootstrap=True)
  1320. start = datetime.datetime.now()
  1321. model.fit(x_train, y_train)
  1322. end = datetime.datetime.now()
  1323. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1324. print('-------------------------------------------------------')
  1325. # Cross Validation Approach
  1326. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1327. ResultsShow.feature_exraction(model, x_train, df)
  1328. print('-------------------------------------------------------------------------------------')
  1329. ResultsShow.feature_permutation(model, x_test, y_test)
  1330. """###Adaboost"""
  1331. from sklearn.model_selection import GridSearchCV
  1332. test = AdaBoostClassifier()
  1333. parameter_space = {
  1334. 'n_estimators': [110,70,90],
  1335. 'learning_rate': [0.1,0.15,0.05]
  1336. }
  1337. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1338. print('Best parameters found:\n', search.best_params_)
  1339. from sklearn.ensemble import AdaBoostClassifier
  1340. model = AdaBoostClassifier(n_estimators=110, learning_rate=0.15)
  1341. start = datetime.datetime.now()
  1342. model.fit(x_train, y_train)
  1343. end = datetime.datetime.now()
  1344. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1345. print('-------------------------------------------------------')
  1346. # Cross Validation Approach
  1347. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1348. ResultsShow.feature_exraction(model, x_train,df)
  1349. print('-------------------------------------------------------------------------------------')
  1350. ResultsShow.feature_permutation(model, x_test, y_test)
  1351. """###Gradient Tree Boosting"""
  1352. from sklearn.model_selection import GridSearchCV
  1353. test = GradientBoostingClassifier()
  1354. parameter_space = {
  1355. 'n_estimators': [30,60,90],
  1356. 'learning_rate': [0.1,0.05,0.15],
  1357. 'max_depth': [3,4,2],
  1358. 'min_samples_split': [5,10]
  1359. }
  1360. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1361. print('Best parameters found:\n', search.best_params_)
  1362. from sklearn.ensemble import GradientBoostingClassifier
  1363. model = GradientBoostingClassifier(n_estimators=60, learning_rate=0.02, max_depth=4, min_samples_split=10)
  1364. start = datetime.datetime.now()
  1365. model.fit(x_train, y_train)
  1366. end = datetime.datetime.now()
  1367. ResultsShow.show( model, x_train, y_train, x_test, y_test)
  1368. print('-------------------------------------------------------')
  1369. # Cross Validation Approach
  1370. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1371. ResultsShow.feature_exraction(model, x_train,df)
  1372. print('-------------------------------------------------------------------------------------')
  1373. ResultsShow.feature_permutation(model, x_test, y_test)
  1374. """### Histogram-Based Gradient Boosting"""
  1375. from sklearn.model_selection import GridSearchCV
  1376. test = HistGradientBoostingClassifier()
  1377. parameter_space = {
  1378. 'min_samples_leaf': [15,20,25],
  1379. 'learning_rate': [0.03,0.05]
  1380. }
  1381. search = GridSearchCV(test, parameter_space, n_jobs=-1, cv=3).fit(x, y)
  1382. print('Best parameters found:\n', search.best_params_)
  1383. from sklearn.ensemble import HistGradientBoostingClassifier
  1384. model = HistGradientBoostingClassifier(max_iter=150, learning_rate=0.03, loss='log_loss', max_leaf_nodes=10, min_samples_leaf=20)
  1385. start = datetime.datetime.now()
  1386. model.fit(x_train, y_train)
  1387. end = datetime.datetime.now()
  1388. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1389. print('-------------------------------------------------------')
  1390. # Cross Validation Approach
  1391. ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1392. print('-------------------------------------------------------------------------------------')
  1393. ResultsShow.feature_permutation(model, x_test, y_test)
  1394. """# Semi_Supervised Classification Models
  1395. ## Semi-Supervised Self Training
  1396. """
  1397. from sklearn.semi_supervised import SelfTrainingClassifier
  1398. b = HistGradientBoostingClassifier(max_iter=150, learning_rate=0.03, loss='log_loss', max_leaf_nodes=10, min_samples_leaf=20)
  1399. a = SGDClassifier(loss="log_loss", penalty="l1", max_iter=10000, tol=7e-5, alpha=0.0002, eta0=0.0005, learning_rate='adaptive', class_weight=None)
  1400. model = SelfTrainingClassifier(b, threshold=0.5)
  1401. start = datetime.datetime.now()
  1402. model.fit(x_train, y_train)
  1403. end = datetime.datetime.now()
  1404. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1405. print('-------------------------------------------------------')
  1406. # ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1407. # print('-------------------------------------------------------------------------------------')
  1408. # ResultsShow.feature_permutation(model, x_test, y_test)
  1409. """## Semi-Supervised Label Spreading"""
  1410. from sklearn.semi_supervised import LabelSpreading
  1411. import warnings
  1412. warnings.filterwarnings("ignore")
  1413. model = LabelSpreading(n_neighbors=5, gamma=2, alpha=0.1)
  1414. start = datetime.datetime.now()
  1415. model.fit(x_train, y_train)
  1416. end = datetime.datetime.now()
  1417. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1418. # print('-------------------------------------------------------')
  1419. # ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1420. # print('-------------------------------------------------------------------------------------')
  1421. # ResultsShow.feature_permutation(model, x_test, y_test)
  1422. """#Neural Networks
  1423. ##Multi Layer Perceptron
  1424. """
  1425. from sklearn.neural_network import MLPClassifier
  1426. from sklearn.model_selection import GridSearchCV
  1427. from sklearn.model_selection import RandomizedSearchCV
  1428. mlp = MLPClassifier(max_iter=100)
  1429. parameter_space = {
  1430. 'hidden_layer_sizes': [(54,35,1), (54,35,17,1)],
  1431. 'activation': ['tanh'],
  1432. 'solver': ['sgd'],
  1433. 'alpha': [0.02],
  1434. 'learning_rate': ['adaptive'],
  1435. 'learning_rate_init':[0.001]
  1436. }
  1437. clf = GridSearchCV(mlp, parameter_space, n_jobs=-1, cv=3)
  1438. # clf = RandomizedSearchCV(mlp, parameter_space, n_jobs=-1, cv=3)
  1439. clf.fit(x, y)
  1440. print('Best parameters found:\n', clf.best_params_)
  1441. # means = clf.cv_results_['mean_test_score']
  1442. # stds = clf.cv_results_['std_test_score']
  1443. # for mean, std, params in zip(means, stds, clf.cv_results_['params']):
  1444. # print("%0.3f (+/-%0.03f) for %r" % (mean, std * 2, params))
  1445. from sklearn.neural_network import MLPClassifier
  1446. model = MLPClassifier(hidden_layer_sizes=(54,18,1), tol=1e-6, batch_size=28, max_iter=2500, activation='identity', solver='sgd', alpha=0.2, learning_rate_init=0.00005, learning_rate='adaptive')
  1447. start = datetime.datetime.now()
  1448. model.fit(x_train, y_train)
  1449. end = datetime.datetime.now()
  1450. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  1451. # print('-------------------------------------------------------')
  1452. # ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  1453. # print('-------------------------------------------------------------------------------------')
  1454. ResultsShow.feature_permutation(model, x_test, y_test)
  1455. result = permutation_importance(model, x_test, y_test, n_repeats=10, random_state=None)
  1456. sorted_idx = result.importances_mean.argsort()
  1457. v = result.importances_mean[sorted_idx]
  1458. n = df.columns.values[sorted_idx]
  1459. print(n)
  1460. print(v)
  1461. from graphviz import Digraph
  1462. # Create a new Graphviz object
  1463. dot = Digraph(format='png')
  1464. # Set the rank direction to left to right
  1465. dot.attr(rankdir='LR')
  1466. # Set the background color to transparent
  1467. dot.attr(bgcolor='transparent')
  1468. # Add the first 3 and last 3 nodes to the input layer
  1469. dot.node('x1', 'x1', fillcolor='#6495ED', style='filled')
  1470. dot.node('x2', 'x2', fillcolor='#6495ED', style='filled')
  1471. dot.node('x3', 'x3', fillcolor='#6495ED', style='filled')
  1472. dot.node('x4', '...', fillcolor='#FFFFFF')
  1473. dot.node('x5', '...', fillcolor='#FFFFFF')
  1474. dot.node('x6', 'x54', fillcolor='#6495ED', style='filled')
  1475. # Add the first 3 and last 3 nodes to the hidden layer
  1476. dot.node('h1', 'h1', fillcolor='#6495ED', style='filled')
  1477. dot.node('h2', 'h2', fillcolor='#6495ED', style='filled')
  1478. dot.node('h3', 'h3', fillcolor='#6495ED', style='filled')
  1479. dot.node('h4', '...', fillcolor='#FFFFFF')
  1480. dot.node('h5', '...', fillcolor='#FFFFFF')
  1481. dot.node('h6', 'h18', fillcolor='#6495ED', style='filled')
  1482. # Add all nodes to the output layer
  1483. dot.node('y', 'y', fillcolor='#6495ED', style='filled')
  1484. # Add edges from input layer to hidden layer
  1485. for i in range(1, 4):
  1486. for j in range(1, 4):
  1487. dot.edge(f'x{i}', f'h{j}')
  1488. for i in range(4, 7):
  1489. for j in range(4, 7):
  1490. dot.edge(f'x{i}', f'h{j}')
  1491. # Add edges from hidden layer to output layer
  1492. for i in range(1, 7):
  1493. dot.edge(f'h{i}', 'y')
  1494. # Render the graph to a PNG file
  1495. dot.render('mlp_network_subset', view=False)
  1496. """## Keras"""
  1497. from keras.models import Sequential
  1498. from keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout, BatchNormalization
  1499. from keras.utils import plot_model
  1500. from keras.callbacks import EarlyStopping
  1501. from keras import regularizers
  1502. from keras import optimizers
  1503. model = Sequential()
  1504. model.add(Conv1D(32, 3, activation='relu', input_shape=(54, 1)))
  1505. model.add(MaxPooling1D(2))
  1506. model.add(Conv1D(64, 3, activation='relu'))
  1507. model.add(MaxPooling1D(2))
  1508. model.add(Flatten())
  1509. model.add(Dense(128, activation='relu'))
  1510. model.add(BatchNormalization())
  1511. model.add(Dropout(0.2))
  1512. model.add(Dense(64, activation='relu'))
  1513. model.add(BatchNormalization())
  1514. model.add(Dropout(0.2))
  1515. # model.add(Dense(32, activation='relu'))
  1516. # model.add(BatchNormalization())
  1517. # model.add(Dropout(0.5))
  1518. model.add(Dense(1, activation='sigmoid'))
  1519. X_train, X_val, Y_train, Y_val = train_test_split(x_train, y_train, test_size=0.12, random_state=8)
  1520. early_stopping = EarlyStopping(monitor='val_loss', patience=30)
  1521. model.compile(optimizer=optimizers.Adamax(learning_rate=0.003), loss='binary_crossentropy', metrics=['accuracy'])
  1522. # model.compile(optimizer=optimizers.SGD(learning_rate=0.005), loss='binary_crossentropy', metrics=['accuracy']) # too slow
  1523. # model.compile(optimizer=optimizers.Nadam(learning_rate=0.005), loss='mae', metrics=['accuracy']) # Fast
  1524. hist = model.fit(X_train, Y_train, validation_data=(X_val,Y_val), epochs=200, callbacks=[early_stopping], batch_size=16)
  1525. # YPredict = np.argmax(model.predict(x_test),axis=1)
  1526. # accuracy = np.sum(YPredict == y_test)/len(y_test)
  1527. # print('--------------------------------')
  1528. # print('Accuracy = {}'.format(accuracy)) # cannot be used with sigmoid
  1529. # print('--------------------------------')
  1530. history = hist.history
  1531. loss = history['loss']
  1532. val_loss = history['val_loss']
  1533. acc = history['accuracy']
  1534. val_acc = history['val_accuracy']
  1535. plt.xlabel('Epochs')
  1536. plt.ylabel('Loss')
  1537. plt.plot(loss)
  1538. plt.plot(val_loss)
  1539. plt.legend(['Loss','Vallidation_Loss'])
  1540. plt.savefig('Loss.png', transparent=True, bbox_inches='tight')
  1541. plt.figure()
  1542. plt.xlabel('Epochs')
  1543. plt.ylabel('Accuracy')
  1544. plt.plot(acc)
  1545. plt.plot(val_acc)
  1546. plt.legend(['Accuracy','Validation_Accuracy'])
  1547. plt.savefig('Accuracy.png', transparent=True, bbox_inches='tight')
  1548. plt.show()
  1549. # import tensorflow as tf
  1550. # confusion = tf.math.confusion_matrix(
  1551. # labels = np.argmax(y_test),
  1552. # predictions = np.argmax(YPredict),
  1553. # num_classes=2)
  1554. # print(confusion)
  1555. test_loss, test_accuracy = model.evaluate(x_test,y_test)
  1556. print(test_loss)
  1557. print(test_accuracy)
  1558. model.predict(standardization.transform(x[382].reshape(1,-1))) * 100
  1559. # model.predict(x_test[9].reshape(1,-1))
  1560. # x[382]
  1561. # Create a new Graphviz object
  1562. dot = Digraph(format='png')
  1563. # Set the rank direction to left to right
  1564. dot.attr(rankdir='LR')
  1565. # Set the background color to white
  1566. dot.attr(bgcolor='white')
  1567. # Set the font color and size of the node labels
  1568. dot.attr('node', fontcolor='#333333', fontsize='12')
  1569. # Add nodes for each layer
  1570. for i, layer in enumerate(model.layers):
  1571. name = layer.name
  1572. label = f'{name}\n{layer.output_shape}'
  1573. dot.node(name, label)
  1574. if i > 0:
  1575. prev_layer = model.layers[i-1]
  1576. for node in prev_layer.output_shape[1:]:
  1577. dot.edge(f'{prev_layer.name}-{node}', f'{name}-0')
  1578. # Render the graph to a PNG file
  1579. dot.render('sequential_model', view=False)
  1580. """# Unsupervised Classification Models"""
  1581. from sklearn.cluster import AgglomerativeClustering
  1582. from sklearn.cluster import AffinityPropagation
  1583. from sklearn.cluster import SpectralClustering
  1584. from sklearn.neighbors import NearestNeighbors
  1585. from sklearn.mixture import GaussianMixture
  1586. from sklearn.cluster import MeanShift
  1587. from sklearn.cluster import OPTICS
  1588. from sklearn.cluster import DBSCAN
  1589. from sklearn.cluster import KMeans
  1590. from sklearn.cluster import Birch
  1591. import warnings
  1592. class UnsupervisedModels:
  1593. def nearest_neighbors():
  1594. model = NearestNeighbors(n_neighbors=9, radius=0.1, algorithm='auto')
  1595. return model
  1596. def k_means():
  1597. model = KMeans(n_clusters=2)
  1598. return model
  1599. def agglomerative_clustering():
  1600. model = AgglomerativeClustering(n_clusters=2)
  1601. return model
  1602. def birch():
  1603. model = Birch(threshold=100000, n_clusters=2)
  1604. return model
  1605. def mean_shift():
  1606. model = MeanShift()
  1607. return model
  1608. def affinity_propagation():
  1609. model = AffinityPropagation(damping=0.9999)
  1610. return model
  1611. def optics():
  1612. model = OPTICS(eps=1, min_samples=50)
  1613. return model
  1614. def spectral_clustering():
  1615. model = SpectralClustering(n_clusters=2)
  1616. return model
  1617. def gaussian_mixture():
  1618. model = GaussianMixture(n_components=2)
  1619. return model
  1620. def dbscan():
  1621. model = DBSCAN(eps=500000, min_samples=3)
  1622. return model
  1623. """## Unsupervised Nearest Neighbors"""
  1624. from sklearn.neighbors import NearestNeighbors
  1625. model = NearestNeighbors(n_neighbors=9, radius=0.1, algorithm='auto')
  1626. start = datetime.datetime.now()
  1627. model.fit(x_train)
  1628. end = datetime.datetime.now()
  1629. distances1, indices1 = model.kneighbors(x_train)
  1630. y_pred1 = []
  1631. for i in range(len(y_train)):
  1632. y_pred1.append(y_train[indices1[i,1]])
  1633. print(accuracy_score(y_train, y_pred1))
  1634. """## Unsupervised K Means"""
  1635. from sklearn.cluster import KMeans
  1636. model = KMeans(n_clusters=2)
  1637. model.fit(x_train)
  1638. y_pred = model.labels_
  1639. print(accuracy_score(y_train, y_pred))
  1640. """## Unsupervised Hierarchical Clustering"""
  1641. from scipy.cluster.hierarchy import dendrogram, linkage
  1642. from sklearn.cluster import AgglomerativeClustering
  1643. Z = linkage(x_train, 'ward') # other possible options: single, complete, average, ward
  1644. dendrogram(Z)
  1645. model = AgglomerativeClustering(n_clusters=2)
  1646. model.fit(x_train)
  1647. y_pred = model.labels_
  1648. print(accuracy_score(y_train, y_pred))
  1649. """## Unsupervised Mean Shift"""
  1650. from sklearn.cluster import MeanShift
  1651. model = MeanShift()
  1652. model.fit(x_train)
  1653. y_pred = model.fit_predict(x_train)
  1654. print(accuracy_score(y_train, y_pred))
  1655. """## Unsupervised Affinity Propagation"""
  1656. from sklearn.cluster import AffinityPropagation
  1657. model = AffinityPropagation(damping=0.99999)
  1658. model.fit(x_train)
  1659. y_pred = model.predict(x_train)
  1660. print(accuracy_score(y_train, y_pred))
  1661. """## Unsupervised Birch"""
  1662. from sklearn.cluster import Birch
  1663. model = Birch(threshold=100000, n_clusters=2)
  1664. model.fit(x_train)
  1665. y_pred = model.predict(x_train)
  1666. print(accuracy_score(y_train, y_pred))
  1667. """## Unsupevised Optics"""
  1668. from sklearn.cluster import OPTICS
  1669. model = OPTICS(eps=1, min_samples=50)
  1670. model.fit(x_train)
  1671. y_pred = model.fit_predict(x_train)
  1672. print(accuracy_score(y_train, y_pred))
  1673. """## Unsupervised Spectral"""
  1674. from sklearn.cluster import SpectralClustering
  1675. c
  1676. model = SpectralClustering(n_clusters=2)
  1677. model.fit(x_train)
  1678. y_pred = model.fit_predict(x_train)
  1679. print(accuracy_score(y_train, y_pred))
  1680. """## Unsupervised Gaussian Mixture"""
  1681. from sklearn.mixture import GaussianMixture
  1682. model = GaussianMixture(n_components=2)
  1683. model.fit(x_train)
  1684. y_pred = model.predict(x_train)
  1685. print(accuracy_score(y_train, y_pred))
  1686. """## Unsupervised DBSCAN"""
  1687. from sklearn.cluster import DBSCAN
  1688. model = DBSCAN(eps=500000, min_samples=3)
  1689. model.fit(x)
  1690. y_pred = model.labels_
  1691. print(accuracy_score(y, y_pred))
  1692. """# Models Comparison
  1693. ## Unsupervised Models Comparison
  1694. """
  1695. warnings.filterwarnings("ignore")
  1696. models = dict()
  1697. distances1, indices1 = UnsupervisedModels.nearest_neighbors().fit(x).kneighbors(x)
  1698. y_pred = []
  1699. for i in range(len(y)):
  1700. y_pred.append(y[indices1[i,1]])
  1701. models['Agglomerative Clustering'] = accuracy_score(y, UnsupervisedModels.agglomerative_clustering().fit(x).labels_)
  1702. models['Affinity Propagation'] = accuracy_score(y, UnsupervisedModels.affinity_propagation().fit(x).predict(x))
  1703. models['Spectral Clustering'] = accuracy_score(y, UnsupervisedModels.spectral_clustering().fit(x).fit_predict(x))
  1704. models['Nearest Neighbors'] = accuracy_score(y, y_pred)
  1705. models['Gaussian Mixture'] = accuracy_score(y, UnsupervisedModels.gaussian_mixture().fit(x).predict(x))
  1706. models['Mean Shift'] = accuracy_score(y, UnsupervisedModels.mean_shift().fit(x).fit_predict(x))
  1707. models['KMeans'] = accuracy_score(y, UnsupervisedModels.k_means().fit(x).labels_)
  1708. models['OPTICS'] = accuracy_score(y, UnsupervisedModels.optics().fit(x).fit_predict(x))
  1709. models['DBSCAN'] = accuracy_score(y, UnsupervisedModels.dbscan().fit(x).labels_)
  1710. models['Birch'] = accuracy_score(y, UnsupervisedModels.birch().fit(x).predict(x))
  1711. # ResultsShow.models_comparison_unsupervised(models)
  1712. ResultsShow.models_comparison_unsupervised(models)
  1713. import numpy as np
  1714. from sklearn.metrics import confusion_matrix
  1715. import matplotlib.pyplot as plt
  1716. # -----------------------------
  1717. # Best-mapped clustering accuracy
  1718. # -----------------------------
  1719. def best_map_accuracy(y_true, y_cluster):
  1720. y_true = np.asarray(y_true)
  1721. y_cluster = np.asarray(y_cluster)
  1722. true_labels = np.unique(y_true)
  1723. cluster_labels = np.unique(y_cluster)
  1724. # Build confusion matrix manually (handles arbitrary labels)
  1725. C = np.zeros((len(true_labels), len(cluster_labels)), dtype=int)
  1726. true_to_i = {lab:i for i, lab in enumerate(true_labels)}
  1727. clus_to_j = {lab:j for j, lab in enumerate(cluster_labels)}
  1728. for t, c in zip(y_true, y_cluster):
  1729. C[true_to_i[t], clus_to_j[c]] += 1
  1730. try:
  1731. from scipy.optimize import linear_sum_assignment
  1732. row_ind, col_ind = linear_sum_assignment(-C)
  1733. matched = C[row_ind, col_ind].sum()
  1734. return matched / len(y_true)
  1735. except Exception:
  1736. # binary fallback
  1737. if len(true_labels) == 2 and len(cluster_labels) == 2:
  1738. acc1 = np.mean(y_true == y_cluster)
  1739. flipped = 1 - y_cluster
  1740. acc2 = np.mean(y_true == flipped)
  1741. return max(acc1, acc2)
  1742. return np.mean(y_true == y_cluster)
  1743. # -----------------------------
  1744. # Run all unsupervised models
  1745. # -----------------------------
  1746. def run_unsupervised_models(x, y):
  1747. models = {}
  1748. algorithms = {
  1749. "Agglomerative": UnsupervisedModels.agglomerative_clustering(),
  1750. "Affinity Propagation": UnsupervisedModels.affinity_propagation(),
  1751. "Spectral": UnsupervisedModels.spectral_clustering(),
  1752. "Gaussian Mixture": UnsupervisedModels.gaussian_mixture(),
  1753. "Mean Shift": UnsupervisedModels.mean_shift(),
  1754. "KMeans": UnsupervisedModels.k_means(),
  1755. "OPTICS": UnsupervisedModels.optics(),
  1756. "DBSCAN": UnsupervisedModels.dbscan(),
  1757. "Birch": UnsupervisedModels.birch()
  1758. }
  1759. for name, model in algorithms.items():
  1760. try:
  1761. # Fit model
  1762. fitted = model.fit(x)
  1763. # Extract labels properly
  1764. if hasattr(fitted, "labels_"):
  1765. labels = fitted.labels_
  1766. elif hasattr(fitted, "predict"):
  1767. labels = fitted.predict(x)
  1768. elif hasattr(fitted, "fit_predict"):
  1769. labels = fitted.fit_predict(x)
  1770. else:
  1771. continue
  1772. acc = best_map_accuracy(y, labels)
  1773. models[name] = acc
  1774. except Exception as e:
  1775. print(f"{name} failed:", e)
  1776. return models
  1777. # -----------------------------
  1778. # Pretty plot
  1779. # -----------------------------
  1780. def plot_unsupervised_results(models):
  1781. names = list(models.keys())
  1782. scores = np.array(list(models.values()))
  1783. order = np.argsort(-scores)
  1784. names = [names[i] for i in order]
  1785. scores = scores[order]
  1786. plt.rcParams.update({
  1787. "font.family": "serif",
  1788. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  1789. "font.size": 18
  1790. })
  1791. fig, ax = plt.subplots(figsize=(9, 6), dpi=300)
  1792. y_pos = np.arange(len(names))
  1793. bars = ax.barh(y_pos, scores, color="#4A4A4A", alpha=0.9)
  1794. ax.set_yticks(y_pos)
  1795. ax.set_yticklabels(names)
  1796. ax.invert_yaxis()
  1797. ax.set_xlabel("Best-mapped Accuracy")
  1798. ax.set_xlim(0, 1)
  1799. ax.spines["top"].set_visible(False)
  1800. ax.spines["right"].set_visible(False)
  1801. # annotate values
  1802. for i, v in enumerate(scores):
  1803. ax.text(v + 0.01, i, f"{v:.3f}",
  1804. va="center", fontsize=16)
  1805. plt.tight_layout()
  1806. plt.savefig("Unsupervised_Comparison_Pretty.png", dpi=300, bbox_inches="tight")
  1807. plt.savefig("Unsupervised_Comparison_Pretty.pdf", bbox_inches="tight")
  1808. plt.show()
  1809. # -----------------------------
  1810. # RUN
  1811. # -----------------------------
  1812. models = run_unsupervised_models(x, y)
  1813. plot_unsupervised_results(models)
  1814. """## Supervised Models Comparison"""
  1815. from sklearn.metrics import PrecisionRecallDisplay
  1816. from sklearn.metrics import RocCurveDisplay
  1817. models = dict()
  1818. models['Stochastic Gradient Descent'] = cross_validation(SupervisedModels.stochastic_gradient_descent(), x_train, y_train)
  1819. models['Support Vector Machine'] = cross_validation(SupervisedModels.support_vector_machine(), x_train, y_train)
  1820. models['Multi Layer Perceptron'] = cross_validation(SupervisedModels.multi_layer_perceptron(), x_train, y_train)
  1821. models['Histogradient Boosting'] = cross_validation(SupervisedModels.histogram_gradient_boosting(), x_train, y_train)
  1822. models['Gradient Tree Boosting'] = cross_validation(SupervisedModels.gradient_boosting(), x_train, y_train)
  1823. models['Gaussian Naive Bayes'] = cross_validation(SupervisedModels.gussian_naive_bayes(), x_train, y_train)
  1824. models['Bagging Kneighbor'] = cross_validation(SupervisedModels.bagging_kneighbor(), x_train, y_train)
  1825. models['Gaussian Process'] = cross_validation(SupervisedModels.gussian_process(), x_train, y_train)
  1826. models['Label Spreading'] = cross_validation(SupervisedModels.label_spreading(), x_train, y_train)
  1827. models['Random Forest'] = cross_validation(SupervisedModels.random_forest(), x_train, y_train)
  1828. models['Decision Tree'] = cross_validation(SupervisedModels.decision_tree(), x_train, y_train)
  1829. models['Bagging SVC'] = cross_validation(SupervisedModels.bagging_svc(), x_train, y_train)
  1830. models['Self Training'] = cross_validation(SupervisedModels.selftraining(), x_train, y_train)
  1831. models['Kneighbor'] = cross_validation(SupervisedModels.kneighbor(), x_train, y_train)
  1832. models['Adaboost'] = cross_validation(SupervisedModels.adaboost(), x_train, y_train)
  1833. models['XGBoost'] = cross_validation(SupervisedModels.xgboost(), x_train, y_train)
  1834. precision, recall, report, results, names = list(), list(), list(), list(), list(models.keys())
  1835. for name, model in models.items():
  1836. report.append(classification_report(y_test, model.predict(x_test), target_names=['Unruptured','Ruptured'], output_dict=True ))
  1837. scores = accuracy_score(y_train, model.predict(x_train)), accuracy_score(y_test, model.predict(x_test))
  1838. results.append(scores)
  1839. print('>%s %.3f (%.3f)' % (name, scores[0], scores[1]))
  1840. for i in range(len(names)):
  1841. precision.append(report[i]['Unruptured']['precision'])
  1842. recall.append(report[i]['Unruptured']['recall'])
  1843. # ResultsShow.models_comparison_supervised(names, results)
  1844. ResultsShow.models_comparison_supervised_pretty(names, results)
  1845. print('-----------------------------------------------------------------------------------')
  1846. # ResultsShow.models_comparison_supervised_recall_precision(names, results, precision, recall)
  1847. ResultsShow.models_comparison_supervised_recall_precision_pretty(names, results)
  1848. print('-----------------------------------------------------------------------------------')
  1849. fig, ax = plt.subplots(figsize=(10,10))
  1850. # plt.title('Receiver Operating Characteristic (ROC) Curve for Different Models')
  1851. for name, model in models.items():
  1852. if name == 'Label Spreading':
  1853. continue
  1854. RocCurveDisplay.from_estimator(models[name], x_test, y_test, ax=ax, name=name)
  1855. plt.savefig('ROC.pdf', transparent=True, bbox_inches='tight')
  1856. fig, ax = plt.subplots(figsize=(10,10))
  1857. plt.title('Precision-Recall Curve for Different Models')
  1858. for name, model in models.items():
  1859. if name == 'Label Spreading':
  1860. continue
  1861. PrecisionRecallDisplay.from_estimator(models[name], x_test, y_test, ax=ax, name=name)
  1862. plt.savefig('Precision_Recall Curve.png', transparent=True, bbox_inches='tight')
  1863. import numpy as np
  1864. import pandas as pd
  1865. import matplotlib.pyplot as plt
  1866. from sklearn.metrics import (
  1867. accuracy_score, precision_score, recall_score, f1_score,
  1868. roc_curve, roc_auc_score, precision_recall_curve,
  1869. confusion_matrix, ConfusionMatrixDisplay
  1870. )
  1871. from sklearn.calibration import calibration_curve
  1872. from sklearn.model_selection import cross_val_score
  1873. plt.rcParams.update({
  1874. "font.family": "serif",
  1875. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  1876. "font.size": 16
  1877. })
  1878. # ======================================================
  1879. # MAIN FUNCTION
  1880. # ======================================================
  1881. def evaluate_supervised_models(models, X_train, y_train, X_test, y_test, cv=5):
  1882. results = []
  1883. for name, model in models.items():
  1884. model.fit(X_train, y_train)
  1885. # Predictions
  1886. y_pred_train = model.predict(X_train)
  1887. y_pred_test = model.predict(X_test)
  1888. # Probabilities (if available)
  1889. if hasattr(model, "predict_proba"):
  1890. y_prob = model.predict_proba(X_test)[:, 1]
  1891. elif hasattr(model, "decision_function"):
  1892. y_prob = model.decision_function(X_test)
  1893. else:
  1894. y_prob = None
  1895. # Metrics
  1896. acc_train = accuracy_score(y_train, y_pred_train)
  1897. acc_test = accuracy_score(y_test, y_pred_test)
  1898. precision = precision_score(y_test, y_pred_test)
  1899. recall = recall_score(y_test, y_pred_test)
  1900. f1 = f1_score(y_test, y_pred_test)
  1901. # Specificity
  1902. cm = confusion_matrix(y_test, y_pred_test)
  1903. tn, fp, fn, tp = cm.ravel()
  1904. specificity = tn / (tn + fp)
  1905. # AUC
  1906. auc = roc_auc_score(y_test, y_prob) if y_prob is not None else np.nan
  1907. # Cross-validated AUC
  1908. cv_auc = cross_val_score(model, X_train, y_train,
  1909. cv=cv, scoring="roc_auc")
  1910. cv_mean = cv_auc.mean()
  1911. cv_std = cv_auc.std()
  1912. results.append([
  1913. name, acc_train, acc_test,
  1914. precision, recall, f1,
  1915. specificity, auc, cv_mean, cv_std
  1916. ])
  1917. results_df = pd.DataFrame(results, columns=[
  1918. "Model", "Train Accuracy", "Test Accuracy",
  1919. "Precision", "Recall", "F1",
  1920. "Specificity", "AUC",
  1921. "CV AUC Mean", "CV AUC Std"
  1922. ])
  1923. results_df = results_df.sort_values("AUC", ascending=False)
  1924. print("\n===== SUMMARY METRICS =====\n")
  1925. print(results_df.round(3))
  1926. # ======================================================
  1927. # 1️⃣ Train vs Test Accuracy
  1928. # ======================================================
  1929. fig, ax = plt.subplots(figsize=(10,5))
  1930. x = np.arange(len(results_df))
  1931. w = 0.35
  1932. ax.bar(x-w/2, results_df["Train Accuracy"], w, label="Train")
  1933. ax.bar(x+w/2, results_df["Test Accuracy"], w, label="Test")
  1934. ax.set_xticks(x)
  1935. ax.set_xticklabels(results_df["Model"], rotation=30)
  1936. ax.set_ylabel("Accuracy")
  1937. ax.legend()
  1938. plt.tight_layout()
  1939. plt.show()
  1940. # ======================================================
  1941. # 2️⃣ Test Metrics Comparison
  1942. # ======================================================
  1943. fig, ax = plt.subplots(figsize=(11,6))
  1944. metrics = ["Test Accuracy","Precision","Recall","F1","Specificity"]
  1945. x = np.arange(len(results_df))
  1946. w = 0.15
  1947. for i, m in enumerate(metrics):
  1948. ax.bar(x + i*w - w*2, results_df[m], w, label=m)
  1949. ax.set_xticks(x)
  1950. ax.set_xticklabels(results_df["Model"], rotation=30)
  1951. ax.set_ylim(0,1)
  1952. ax.set_ylabel("Score")
  1953. ax.legend()
  1954. plt.tight_layout()
  1955. plt.show()
  1956. # ======================================================
  1957. # 3️⃣ ROC Curves
  1958. # ======================================================
  1959. plt.figure(figsize=(7,6))
  1960. for name, model in models.items():
  1961. model.fit(X_train, y_train)
  1962. if hasattr(model, "predict_proba"):
  1963. y_prob = model.predict_proba(X_test)[:,1]
  1964. elif hasattr(model, "decision_function"):
  1965. y_prob = model.decision_function(X_test)
  1966. else:
  1967. continue
  1968. fpr, tpr, _ = roc_curve(y_test, y_prob)
  1969. auc = roc_auc_score(y_test, y_prob)
  1970. plt.plot(fpr, tpr, label=f"{name} (AUC={auc:.2f})")
  1971. plt.plot([0,1],[0,1],'k--')
  1972. plt.xlabel("False Positive Rate")
  1973. plt.ylabel("True Positive Rate")
  1974. plt.legend()
  1975. plt.tight_layout()
  1976. plt.show()
  1977. # ======================================================
  1978. # 4️⃣ Precision-Recall Curves
  1979. # ======================================================
  1980. plt.figure(figsize=(7,6))
  1981. for name, model in models.items():
  1982. model.fit(X_train, y_train)
  1983. if hasattr(model, "predict_proba"):
  1984. y_prob = model.predict_proba(X_test)[:,1]
  1985. elif hasattr(model, "decision_function"):
  1986. y_prob = model.decision_function(X_test)
  1987. else:
  1988. continue
  1989. precision, recall, _ = precision_recall_curve(y_test, y_prob)
  1990. plt.plot(recall, precision, label=name)
  1991. plt.xlabel("Recall")
  1992. plt.ylabel("Precision")
  1993. plt.legend()
  1994. plt.tight_layout()
  1995. plt.show()
  1996. # ======================================================
  1997. # 5️⃣ Confusion Matrix (Best AUC Model)
  1998. # ======================================================
  1999. best_model_name = results_df.iloc[0]["Model"]
  2000. best_model = models[best_model_name]
  2001. best_model.fit(X_train, y_train)
  2002. y_pred = best_model.predict(X_test)
  2003. fig, ax = plt.subplots(figsize=(5,5))
  2004. ConfusionMatrixDisplay.from_predictions(
  2005. y_test, y_pred,
  2006. display_labels=["Unruptured","Ruptured"],
  2007. cmap="Greys", ax=ax
  2008. )
  2009. plt.title(f"Confusion Matrix ({best_model_name})")
  2010. plt.tight_layout()
  2011. plt.show()
  2012. # ======================================================
  2013. # 6️⃣ Calibration Curve (Best Model)
  2014. # ======================================================
  2015. if hasattr(best_model, "predict_proba"):
  2016. y_prob = best_model.predict_proba(X_test)[:,1]
  2017. prob_true, prob_pred = calibration_curve(y_test, y_prob, n_bins=10)
  2018. plt.figure(figsize=(6,6))
  2019. plt.plot(prob_pred, prob_true, marker='o')
  2020. plt.plot([0,1],[0,1],'k--')
  2021. plt.xlabel("Mean Predicted Probability")
  2022. plt.ylabel("Fraction of Positives")
  2023. plt.title("Calibration Curve")
  2024. plt.tight_layout()
  2025. plt.show()
  2026. return results_df
  2027. results = evaluate_supervised_models(models, x_train, y_train, x_test, y_test)
  2028. """# Combining Different Models"""
  2029. from sklearn.ensemble import VotingClassifier
  2030. # x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=None)
  2031. # standardization = StandardScaler().fit(x_train)
  2032. # x_train = standardization.transform(x_train)
  2033. # x_test = standardization.transform(x_test)
  2034. names_list = []
  2035. for i in range (2):
  2036. names_list.append(str(i+1))
  2037. models_list = [
  2038. cross_validation(SupervisedModels.stochastic_gradient_descent(), x_train, y_train),
  2039. cross_validation(SupervisedModels.support_vector_machine(), x_train, y_train),
  2040. cross_validation(SupervisedModels.multi_layer_perceptron(), x_train, y_train)
  2041. ]
  2042. models_list = list(zip(names_list, models_list))
  2043. model = VotingClassifier(models_list, voting='soft')
  2044. start = datetime.datetime.now()
  2045. model.fit(x_train, y_train)
  2046. end = datetime.datetime.now()
  2047. ResultsShow.show(model, x_train, y_train, x_test, y_test)
  2048. print('-------------------------------------------------------')
  2049. # Cross Validation Approach
  2050. # ResultsShow.show(cross_validation(model, x_train, y_train), x_train, y_train, x_test, y_test)
  2051. """#Grid Search"""
  2052. import numpy as np
  2053. import pandas as pd
  2054. from sklearn.pipeline import Pipeline
  2055. from sklearn.preprocessing import StandardScaler
  2056. from sklearn.model_selection import StratifiedKFold, GridSearchCV, RandomizedSearchCV
  2057. from sklearn.metrics import roc_auc_score
  2058. from sklearn.linear_model import SGDClassifier
  2059. from sklearn.svm import SVC
  2060. from sklearn.ensemble import HistGradientBoostingClassifier
  2061. # ----------------------------
  2062. # CONFIG
  2063. # ----------------------------
  2064. RANDOM_STATE = 42
  2065. outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)
  2066. inner_cv = StratifiedKFold(n_splits=4, shuffle=True, random_state=RANDOM_STATE)
  2067. X = x # your features
  2068. y = y # labels (0/1)
  2069. # ----------------------------
  2070. # 1) SGD (fast) - GridSearch
  2071. # ----------------------------
  2072. sgd_pipe = Pipeline([
  2073. ("scaler", StandardScaler()),
  2074. ("clf", SGDClassifier(loss="log_loss", max_iter=20000, tol=1e-4, random_state=RANDOM_STATE))
  2075. ])
  2076. sgd_grid = {
  2077. "clf__penalty": ["l2", "l1", "elasticnet"],
  2078. "clf__alpha": [1e-5, 5e-5, 1e-4, 2e-4, 5e-4, 1e-3],
  2079. "clf__l1_ratio": [0.15, 0.5, 0.85], # only used if elasticnet; safe to include
  2080. "clf__class_weight": [None, "balanced"],
  2081. }
  2082. sgd_search = GridSearchCV(
  2083. sgd_pipe, sgd_grid, scoring="roc_auc", cv=inner_cv, n_jobs=-1, refit=True
  2084. )
  2085. # ----------------------------
  2086. # 2) SVM (RBF) - RandomizedSearch (faster than full grid)
  2087. # ----------------------------
  2088. svm_pipe = Pipeline([
  2089. ("scaler", StandardScaler()),
  2090. ("clf", SVC(kernel="rbf", probability=True, random_state=RANDOM_STATE))
  2091. ])
  2092. svm_dist = {
  2093. "clf__C": np.logspace(-1, 2.5, 18), # 0.1 .. ~316
  2094. "clf__gamma": np.logspace(-4, 0, 18), # 1e-4 .. 1
  2095. "clf__class_weight": [None, "balanced"],
  2096. }
  2097. svm_search = RandomizedSearchCV(
  2098. svm_pipe,
  2099. svm_dist,
  2100. n_iter=45, # adjust upward if you want more thorough
  2101. scoring="roc_auc",
  2102. cv=inner_cv,
  2103. n_jobs=-1,
  2104. random_state=RANDOM_STATE,
  2105. refit=True
  2106. )
  2107. # ----------------------------
  2108. # 3) HistGradientBoosting (fast) - RandomizedSearch
  2109. # ----------------------------
  2110. hgb = HistGradientBoostingClassifier(loss="log_loss", random_state=RANDOM_STATE)
  2111. hgb_dist = {
  2112. "learning_rate": [0.01, 0.02, 0.03, 0.05, 0.08],
  2113. "max_iter": [100, 150, 200, 300],
  2114. "max_leaf_nodes": [7, 10, 15, 20, 31],
  2115. "min_samples_leaf": [10, 15, 20, 30, 40],
  2116. "l2_regularization": [0.0, 0.1, 0.5, 1.0],
  2117. }
  2118. hgb_search = RandomizedSearchCV(
  2119. hgb,
  2120. hgb_dist,
  2121. n_iter=50,
  2122. scoring="roc_auc",
  2123. cv=inner_cv,
  2124. n_jobs=-1,
  2125. random_state=RANDOM_STATE,
  2126. refit=True
  2127. )
  2128. # ----------------------------
  2129. # NESTED CV EVALUATION
  2130. # ----------------------------
  2131. def nested_auc(searcher, X, y, outer_cv):
  2132. aucs = []
  2133. best_params_each_fold = []
  2134. for train_idx, test_idx in outer_cv.split(X, y):
  2135. Xtr, Xte = X[train_idx], X[test_idx]
  2136. ytr, yte = y[train_idx], y[test_idx]
  2137. searcher.fit(Xtr, ytr)
  2138. best_model = searcher.best_estimator_
  2139. best_params_each_fold.append(searcher.best_params_)
  2140. # Probabilities for AUC
  2141. if hasattr(best_model, "predict_proba"):
  2142. prob = best_model.predict_proba(Xte)[:, 1]
  2143. else:
  2144. # fallback for decision_function
  2145. prob = best_model.decision_function(Xte)
  2146. aucs.append(roc_auc_score(yte, prob))
  2147. return np.array(aucs), best_params_each_fold
  2148. models_to_tune = {
  2149. "SGD (tuned)": sgd_search,
  2150. "SVM-RBF (tuned)": svm_search,
  2151. "HistGradientBoosting (tuned)": hgb_search,
  2152. }
  2153. summary_rows = []
  2154. best_params_store = {}
  2155. for name, searcher in models_to_tune.items():
  2156. print(f"Running nested CV for: {name}")
  2157. aucs, params = nested_auc(searcher, X, y, outer_cv)
  2158. summary_rows.append({
  2159. "Model": name,
  2160. "AUC_mean": aucs.mean(),
  2161. "AUC_std": aucs.std(),
  2162. "AUC_min": aucs.min(),
  2163. "AUC_max": aucs.max()
  2164. })
  2165. best_params_store[name] = params
  2166. results_df = pd.DataFrame(summary_rows).sort_values("AUC_mean", ascending=False)
  2167. print("\n=== Nested-CV AUC summary (tuning + unbiased estimation) ===")
  2168. print(results_df.round(4))
  2169. # Optional: show the most frequent best params for each model
  2170. from collections import Counter
  2171. print("\n=== Most common best parameters across outer folds ===")
  2172. for name, params_list in best_params_store.items():
  2173. # Convert dict params to tuples for counting
  2174. as_tuples = [tuple(sorted(d.items())) for d in params_list]
  2175. most_common = Counter(as_tuples).most_common(1)[0]
  2176. print(f"\n{name}:")
  2177. print(dict(most_common[0]))
  2178. print(f"Selected in {most_common[1]} / {len(params_list)} outer folds")
  2179. import os, random
  2180. import numpy as np
  2181. import pandas as pd
  2182. import tensorflow as tf
  2183. from sklearn.model_selection import StratifiedKFold, StratifiedShuffleSplit
  2184. from sklearn.preprocessing import StandardScaler
  2185. from sklearn.metrics import roc_auc_score, confusion_matrix
  2186. # ----------------------------
  2187. # Reproducibility
  2188. # ----------------------------
  2189. SEED = 42
  2190. os.environ["PYTHONHASHSEED"] = str(SEED)
  2191. random.seed(SEED)
  2192. np.random.seed(SEED)
  2193. tf.random.set_seed(SEED)
  2194. # ----------------------------
  2195. # Helpers
  2196. # ----------------------------
  2197. def build_cnn_arch(input_len, hp):
  2198. """hp is a dict defining architecture + training hyperparameters."""
  2199. from tensorflow.keras.models import Sequential
  2200. from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout, BatchNormalization
  2201. from tensorflow.keras import optimizers
  2202. model = Sequential()
  2203. # Conv block 1
  2204. model.add(Conv1D(hp["filters1"], hp["kernel1"], activation="relu", input_shape=(input_len, 1)))
  2205. if hp["pool1"]:
  2206. model.add(MaxPooling1D(hp["pool1"]))
  2207. # Optional conv block 2
  2208. if hp["use_block2"]:
  2209. model.add(Conv1D(hp["filters2"], hp["kernel2"], activation="relu"))
  2210. if hp["pool2"]:
  2211. model.add(MaxPooling1D(hp["pool2"]))
  2212. model.add(Flatten())
  2213. # Dense block
  2214. model.add(Dense(hp["dense1"], activation="relu"))
  2215. if hp["batchnorm"]:
  2216. model.add(BatchNormalization())
  2217. model.add(Dropout(hp["dropout1"]))
  2218. if hp["use_dense2"]:
  2219. model.add(Dense(hp["dense2"], activation="relu"))
  2220. if hp["batchnorm"]:
  2221. model.add(BatchNormalization())
  2222. model.add(Dropout(hp["dropout2"]))
  2223. model.add(Dense(1, activation="sigmoid"))
  2224. opt_name = hp["optimizer"]
  2225. lr = hp["lr"]
  2226. if opt_name == "adam":
  2227. opt = optimizers.Adam(learning_rate=lr)
  2228. elif opt_name == "adamax":
  2229. opt = optimizers.Adamax(learning_rate=lr)
  2230. else:
  2231. opt = optimizers.RMSprop(learning_rate=lr)
  2232. model.compile(optimizer=opt, loss="binary_crossentropy")
  2233. return model
  2234. def sample_hp(rng):
  2235. """Randomly sample a small but meaningful CNN architecture space."""
  2236. hp = {
  2237. "filters1": int(rng.choice([16, 32, 48, 64])),
  2238. "kernel1": int(rng.choice([2, 3, 5])),
  2239. "pool1": int(rng.choice([0, 2])), # 0 means no pooling
  2240. "use_block2": bool(rng.choice([0, 1])),
  2241. "filters2": int(rng.choice([32, 64, 96])),
  2242. "kernel2": int(rng.choice([2, 3, 5])),
  2243. "pool2": int(rng.choice([0, 2])),
  2244. "dense1": int(rng.choice([64, 128, 192, 256])),
  2245. "use_dense2": bool(rng.choice([0, 1])),
  2246. "dense2": int(rng.choice([32, 64, 128])),
  2247. "dropout1": float(rng.choice([0.1, 0.2, 0.3, 0.4])),
  2248. "dropout2": float(rng.choice([0.1, 0.2, 0.3, 0.4])),
  2249. "batchnorm": bool(rng.choice([0, 1])),
  2250. "optimizer": str(rng.choice(["adamax", "adam", "rmsprop"])),
  2251. "lr": float(rng.choice([5e-4, 1e-3, 2e-3, 3e-3])),
  2252. "batch_size": int(rng.choice([16, 32])),
  2253. "epochs": 200
  2254. }
  2255. # enforce coherence
  2256. if not hp["use_dense2"]:
  2257. hp["dropout2"] = 0.0
  2258. return hp
  2259. def fit_eval_auc(Xtr, ytr, Xva, yva, input_len, hp, seed=SEED):
  2260. tf.keras.backend.clear_session()
  2261. tf.random.set_seed(seed)
  2262. # reshape for CNN
  2263. Xtr_c = Xtr.reshape(-1, input_len, 1)
  2264. Xva_c = Xva.reshape(-1, input_len, 1)
  2265. model = build_cnn_arch(input_len, hp)
  2266. early = tf.keras.callbacks.EarlyStopping(monitor="val_loss", patience=25, restore_best_weights=True)
  2267. model.fit(
  2268. Xtr_c, ytr,
  2269. validation_data=(Xva_c, yva),
  2270. epochs=hp["epochs"],
  2271. batch_size=hp["batch_size"],
  2272. verbose=0,
  2273. callbacks=[early],
  2274. )
  2275. prob = model.predict(Xva_c, verbose=0).ravel()
  2276. return roc_auc_score(yva, prob)
  2277. # ----------------------------
  2278. # NESTED CV RANDOM SEARCH
  2279. # ----------------------------
  2280. def nested_cv_cnn_random_search(X, y, n_outer=5, n_inner_trials=10, seed=SEED):
  2281. X = np.asarray(X)
  2282. y = np.asarray(y).astype(int)
  2283. input_len = X.shape[1]
  2284. outer = StratifiedKFold(n_splits=n_outer, shuffle=True, random_state=seed)
  2285. rng_global = np.random.default_rng(seed)
  2286. outer_rows = []
  2287. best_hps = []
  2288. for fold, (tr_idx, te_idx) in enumerate(outer.split(X, y), start=1):
  2289. # outer split
  2290. Xtr0, Xte0 = X[tr_idx], X[te_idx]
  2291. ytr0, yte0 = y[tr_idx], y[te_idx]
  2292. # scale inside fold (no leakage)
  2293. scaler = StandardScaler()
  2294. Xtr0s = scaler.fit_transform(Xtr0)
  2295. Xte0s = scaler.transform(Xte0)
  2296. # inner split (single validation split repeated by random search)
  2297. inner_split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=seed + fold)
  2298. (itr, iva) = next(inner_split.split(Xtr0s, ytr0))
  2299. Xtr, Xva = Xtr0s[itr], Xtr0s[iva]
  2300. ytr, yva = ytr0[itr], ytr0[iva]
  2301. # random search on inner validation AUC
  2302. best_auc = -np.inf
  2303. best_hp = None
  2304. for t in range(n_inner_trials):
  2305. hp = sample_hp(rng_global)
  2306. auc = fit_eval_auc(Xtr, ytr, Xva, yva, input_len, hp, seed=seed + 1000*fold + t)
  2307. if auc > best_auc:
  2308. best_auc = auc
  2309. best_hp = hp
  2310. # fit best architecture on full outer-train and evaluate on outer-test
  2311. tf.keras.backend.clear_session()
  2312. tf.random.set_seed(seed + 999*fold)
  2313. best_model = build_cnn_arch(input_len, best_hp)
  2314. early = tf.keras.callbacks.EarlyStopping(monitor="val_loss", patience=25, restore_best_weights=True)
  2315. Xtr_full = Xtr0s.reshape(-1, input_len, 1)
  2316. Xte_full = Xte0s.reshape(-1, input_len, 1)
  2317. # use validation_split within outer-train to early-stop
  2318. best_model.fit(
  2319. Xtr_full, ytr0,
  2320. validation_split=0.12,
  2321. epochs=best_hp["epochs"],
  2322. batch_size=best_hp["batch_size"],
  2323. verbose=0,
  2324. callbacks=[early]
  2325. )
  2326. prob_te = best_model.predict(Xte_full, verbose=0).ravel()
  2327. auc_te = roc_auc_score(yte0, prob_te)
  2328. outer_rows.append({"fold": fold, "AUC_outer": auc_te, "AUC_inner_best": best_auc})
  2329. best_hps.append(best_hp)
  2330. print(f"Fold {fold}: inner-best AUC={best_auc:.3f} | outer AUC={auc_te:.3f}")
  2331. df = pd.DataFrame(outer_rows)
  2332. summary = {
  2333. "Model": "1D-CNN (tuned)",
  2334. "AUC_mean": df["AUC_outer"].mean(),
  2335. "AUC_std": df["AUC_outer"].std(),
  2336. "AUC_min": df["AUC_outer"].min(),
  2337. "AUC_max": df["AUC_outer"].max()
  2338. }
  2339. return df, pd.DataFrame([summary]), best_hps
  2340. # ---- RUN ----
  2341. cnn_fold_df, cnn_summary_df, cnn_best_hps = nested_cv_cnn_random_search(x, y, n_outer=5, n_inner_trials=10, seed=42)
  2342. print("\nPer-fold CNN AUC:")
  2343. print(cnn_fold_df.round(3))
  2344. print("\nCNN tuned summary:")
  2345. print(cnn_summary_df.round(4))
  2346. print("\nExample best HP (fold 1):")
  2347. print(cnn_best_hps[0])
  2348. """#Models Comparison"""
  2349. # ============================================================
  2350. # SINGLE MERGED BLOCK (ALL MODELS + CV + TEST10) INCLUDING 1D-CNN
  2351. # - final_models dict kept the same name (classical models)
  2352. # - 90/10 stratified holdout
  2353. # - 5-fold CV on TRAIN90 only
  2354. # - single TEST10 evaluation (unseen)
  2355. # - sample_weight for BaggingSVM + AdaBoost
  2356. # - BaggingKNN/KNN: optional undersampling (KNN ignores sample_weight)
  2357. # - 1D-CNN: handled separately (not in final_models)
  2358. # ============================================================
  2359. import numpy as np
  2360. import pandas as pd
  2361. from sklearn.pipeline import Pipeline
  2362. from sklearn.preprocessing import StandardScaler
  2363. from sklearn.utils.class_weight import compute_class_weight
  2364. from sklearn.model_selection import train_test_split, StratifiedKFold
  2365. from sklearn.metrics import roc_auc_score, accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
  2366. from sklearn.base import clone
  2367. from sklearn.linear_model import SGDClassifier
  2368. from sklearn.svm import SVC
  2369. from sklearn.ensemble import (
  2370. HistGradientBoostingClassifier, GradientBoostingClassifier,
  2371. RandomForestClassifier, AdaBoostClassifier, BaggingClassifier
  2372. )
  2373. from sklearn.neural_network import MLPClassifier
  2374. from sklearn.gaussian_process import GaussianProcessClassifier
  2375. from sklearn.gaussian_process.kernels import RBF
  2376. from sklearn.neighbors import KNeighborsClassifier
  2377. from sklearn.naive_bayes import GaussianNB
  2378. from sklearn import tree
  2379. # TensorFlow for CNN
  2380. import tensorflow as tf
  2381. # Optional: XGBoost
  2382. xgb_available = True
  2383. try:
  2384. import xgboost as xgb
  2385. except Exception:
  2386. xgb_available = False
  2387. # ----------------------------
  2388. # CONFIG
  2389. # ----------------------------
  2390. RANDOM_STATE_SPLIT = 8
  2391. CV_FOLDS = 5
  2392. THR = 0.5
  2393. USE_UNDERSAMPLE_FOR_KNN_BAGGING = True # recommended for KNN-like models (sample_weight won't help)
  2394. CNN_HP = {
  2395. "filters1": 64, "kernel1": 3, "pool1": 0,
  2396. "use_block2": True, "filters2": 64, "kernel2": 5, "pool2": 0,
  2397. "dense1": 128, "use_dense2": True, "dense2": 128,
  2398. "dropout1": 0.2, "dropout2": 0.4,
  2399. "batchnorm": True,
  2400. "optimizer": "adam",
  2401. "lr": 0.003,
  2402. "batch_size": 16,
  2403. "epochs": 200,
  2404. "patience": 25,
  2405. "val_split": 0.12
  2406. }
  2407. # ----------------------------
  2408. # DATA
  2409. # ----------------------------
  2410. X = np.asarray(x)
  2411. y_arr = np.asarray(y).astype(int).ravel() # IMPORTANT: 1D
  2412. # Global class weights (informational)
  2413. unique_classes = np.unique(y_arr)
  2414. print("Unique labels in y:", unique_classes)
  2415. cw = compute_class_weight(
  2416. class_weight="balanced",
  2417. classes=unique_classes,
  2418. y=y_arr
  2419. )
  2420. CLASS_WEIGHT_DICT = dict(zip(unique_classes, cw))
  2421. print("Computed class weights:", CLASS_WEIGHT_DICT)
  2422. # CLASS_WEIGHT_DICT = {0: float(cw[0]), 1: float(cw[1])}
  2423. print("Global class weights:", CLASS_WEIGHT_DICT)
  2424. # 90/10 split (stratified)
  2425. X_train90, X_test10, y_train90, y_test10 = train_test_split(
  2426. X, y_arr, test_size=0.10, random_state=RANDOM_STATE_SPLIT, stratify=y_arr
  2427. )
  2428. print("Train (90%) shape:", X_train90.shape, "Test (10%) shape:", X_test10.shape)
  2429. print("Train class counts:", np.bincount(y_train90))
  2430. print("Test class counts:", np.bincount(y_test10))
  2431. # ----------------------------
  2432. # HELPERS
  2433. # ----------------------------
  2434. def specificity(y_true, y_pred):
  2435. tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel()
  2436. return tn / (tn + fp) if (tn + fp) else 0.0
  2437. def metrics_from_prob(y_true, prob, thr=0.5):
  2438. pred = (prob >= thr).astype(int)
  2439. return {
  2440. "AUC": roc_auc_score(y_true, prob),
  2441. "ACC": accuracy_score(y_true, pred),
  2442. "Precision": precision_score(y_true, pred, zero_division=0),
  2443. "Recall": recall_score(y_true, pred, zero_division=0),
  2444. "F1": f1_score(y_true, pred, zero_division=0),
  2445. "Specificity": specificity(y_true, pred),
  2446. }
  2447. def get_probabilities(estimator, X_eval):
  2448. if hasattr(estimator, "predict_proba"):
  2449. return estimator.predict_proba(X_eval)[:, 1]
  2450. if hasattr(estimator, "decision_function"):
  2451. s = estimator.decision_function(X_eval)
  2452. return (s - s.min()) / (s.max() - s.min() + 1e-12)
  2453. return estimator.predict(X_eval).astype(float)
  2454. def make_sample_weight_balanced(y):
  2455. y = np.asarray(y).astype(int).ravel()
  2456. n = len(y)
  2457. n0 = np.sum(y == 0)
  2458. n1 = np.sum(y == 1)
  2459. w0 = n / (2 * n0) if n0 else 1.0
  2460. w1 = n / (2 * n1) if n1 else 1.0
  2461. return np.where(y == 0, w0, w1).astype(float)
  2462. def undersample_to_balance(X, y, seed=42):
  2463. rng = np.random.default_rng(seed)
  2464. y = np.asarray(y).astype(int).ravel()
  2465. idx0 = np.where(y == 0)[0]
  2466. idx1 = np.where(y == 1)[0]
  2467. n = min(len(idx0), len(idx1))
  2468. if n == 0:
  2469. return X, y
  2470. idx0_s = rng.choice(idx0, size=n, replace=False)
  2471. idx1_s = rng.choice(idx1, size=n, replace=False)
  2472. sel = np.concatenate([idx0_s, idx1_s])
  2473. rng.shuffle(sel)
  2474. return X[sel], y[sel]
  2475. # ----------------------------
  2476. # CNN BUILD / FIT
  2477. # ----------------------------
  2478. def build_cnn_arch(input_len, hp):
  2479. from tensorflow.keras.models import Sequential
  2480. from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout, BatchNormalization
  2481. from tensorflow.keras import optimizers
  2482. model = Sequential()
  2483. model.add(tf.keras.layers.Input(shape=(input_len, 1)))
  2484. model.add(Conv1D(hp["filters1"], hp["kernel1"], activation="relu"))
  2485. if hp["pool1"] and hp["pool1"] > 0:
  2486. model.add(MaxPooling1D(hp["pool1"]))
  2487. if hp["use_block2"]:
  2488. model.add(Conv1D(hp["filters2"], hp["kernel2"], activation="relu"))
  2489. if hp["pool2"] and hp["pool2"] > 0:
  2490. model.add(MaxPooling1D(hp["pool2"]))
  2491. model.add(Flatten())
  2492. model.add(Dense(hp["dense1"], activation="relu"))
  2493. if hp["batchnorm"]:
  2494. model.add(BatchNormalization())
  2495. model.add(Dropout(hp["dropout1"]))
  2496. if hp["use_dense2"]:
  2497. model.add(Dense(hp["dense2"], activation="relu"))
  2498. if hp["batchnorm"]:
  2499. model.add(BatchNormalization())
  2500. model.add(Dropout(hp["dropout2"]))
  2501. model.add(Dense(1, activation="sigmoid"))
  2502. lr = hp["lr"]
  2503. if hp["optimizer"] == "adam":
  2504. opt = optimizers.Adam(learning_rate=lr)
  2505. elif hp["optimizer"] == "adamax":
  2506. opt = optimizers.Adamax(learning_rate=lr)
  2507. else:
  2508. opt = optimizers.RMSprop(learning_rate=lr)
  2509. model.compile(optimizer=opt, loss="binary_crossentropy")
  2510. return model
  2511. def fit_cnn_and_predict_proba(Xtr, ytr, Xeval, hp, class_weight_dict, seed=42):
  2512. """
  2513. Fit CNN on Xtr/ytr with internal val_split, return probabilities on Xeval.
  2514. Scaling is handled outside; inputs must be already standardized.
  2515. """
  2516. tf.keras.backend.clear_session()
  2517. tf.random.set_seed(seed)
  2518. cnn = build_cnn_arch(Xtr.shape[1], hp)
  2519. early = tf.keras.callbacks.EarlyStopping(
  2520. monitor="val_loss",
  2521. patience=hp["patience"],
  2522. restore_best_weights=True
  2523. )
  2524. Xtr_c = Xtr.reshape(-1, Xtr.shape[1], 1)
  2525. Xev_c = Xeval.reshape(-1, Xtr.shape[1], 1)
  2526. cnn.fit(
  2527. Xtr_c, ytr,
  2528. validation_split=hp["val_split"],
  2529. epochs=hp["epochs"],
  2530. batch_size=hp["batch_size"],
  2531. verbose=0,
  2532. callbacks=[early],
  2533. class_weight=class_weight_dict
  2534. )
  2535. prob = cnn.predict(Xev_c, verbose=0).ravel()
  2536. return prob
  2537. # ----------------------------
  2538. # DEFINE CLASSICAL MODELS (final_models dict name kept)
  2539. # ----------------------------
  2540. final_models = {}
  2541. final_models["BaggingKNN"] = Pipeline([
  2542. ("scaler", StandardScaler()),
  2543. ("clf", BaggingClassifier(
  2544. estimator=KNeighborsClassifier(n_neighbors=5, weights="uniform", algorithm="auto"),
  2545. n_estimators=100, bootstrap=True, max_samples=0.9, max_features=0.7,
  2546. random_state=42
  2547. ))
  2548. ])
  2549. final_models["BaggingSVM"] = Pipeline([
  2550. ("scaler", StandardScaler()),
  2551. ("clf", BaggingClassifier(
  2552. estimator=SVC(C=80, gamma=0.002, probability=True, kernel="rbf"),
  2553. n_estimators=100, bootstrap=True, max_samples=0.9, max_features=54,
  2554. random_state=42
  2555. ))
  2556. ])
  2557. final_models["DecisionTree"] = tree.DecisionTreeClassifier(
  2558. criterion="gini", splitter="random",
  2559. max_depth=4, max_features=None, min_samples_split=12,
  2560. random_state=42
  2561. )
  2562. final_models["SGD"] = Pipeline([
  2563. ("scaler", StandardScaler()),
  2564. ("clf", SGDClassifier(
  2565. loss="log_loss",
  2566. penalty="elasticnet",
  2567. alpha=0.001,
  2568. l1_ratio=0.5,
  2569. max_iter=20000,
  2570. tol=1e-4,
  2571. class_weight="balanced",
  2572. random_state=42
  2573. ))
  2574. ])
  2575. final_models["SVM"] = Pipeline([
  2576. ("scaler", StandardScaler()),
  2577. ("clf", SVC(
  2578. kernel="rbf", probability=True,
  2579. C=2.761699813343848,
  2580. gamma=0.004436687330978612,
  2581. class_weight="balanced",
  2582. random_state=42
  2583. ))
  2584. ])
  2585. final_models["HistGradientBoosting"] = HistGradientBoostingClassifier(
  2586. loss="log_loss", learning_rate=0.08,
  2587. max_iter=150, max_leaf_nodes=7,
  2588. min_samples_leaf=20, l2_regularization=1.0,
  2589. random_state=42
  2590. )
  2591. final_models["MLP"] = Pipeline([
  2592. ("scaler", StandardScaler()),
  2593. ("clf", MLPClassifier(
  2594. hidden_layer_sizes=(55, 18, 1),
  2595. tol=1e-6, batch_size=28, max_iter=2500,
  2596. activation="identity", solver="sgd", alpha=0.2,
  2597. learning_rate_init=0.00005, learning_rate="adaptive",
  2598. random_state=42
  2599. ))
  2600. ])
  2601. final_models["GaussianProcess"] = Pipeline([
  2602. ("scaler", StandardScaler()),
  2603. ("clf", GaussianProcessClassifier(kernel=1 * RBF(2), random_state=42))
  2604. ])
  2605. final_models["RandomForest"] = RandomForestClassifier(
  2606. n_estimators=80, criterion="log_loss", max_depth=5, min_samples_leaf=5,
  2607. max_features=0.5, min_samples_split=20, bootstrap=True,
  2608. class_weight="balanced",
  2609. random_state=42
  2610. )
  2611. final_models["GradientBoosting"] = GradientBoostingClassifier(
  2612. n_estimators=60, learning_rate=0.02, max_depth=4,
  2613. min_samples_split=10, random_state=42
  2614. )
  2615. final_models["AdaBoost"] = AdaBoostClassifier(
  2616. n_estimators=110, learning_rate=0.15, random_state=42
  2617. )
  2618. final_models["KNN"] = Pipeline([
  2619. ("scaler", StandardScaler()),
  2620. ("clf", KNeighborsClassifier(n_neighbors=17, weights="uniform", p=1))
  2621. ])
  2622. final_models["GaussianNB"] = GaussianNB(var_smoothing=1e-10)
  2623. if xgb_available:
  2624. pos = float(np.sum(y_train90 == 1))
  2625. neg = float(np.sum(y_train90 == 0))
  2626. scale_pos_weight = (neg / pos) if pos > 0 else 1.0
  2627. final_models["XGBoost"] = xgb.XGBClassifier(
  2628. booster="gbtree",
  2629. eta=0.05, gamma=0,
  2630. max_depth=4, learning_rate=0.05,
  2631. subsample=0.5, colsample_bytree=0.5,
  2632. alpha=1,
  2633. tree_method="hist",
  2634. random_state=42,
  2635. eval_metric="logloss",
  2636. scale_pos_weight=scale_pos_weight
  2637. )
  2638. print("\nModels in final_models:", list(final_models.keys()))
  2639. print("Also evaluating: 1D-CNN")
  2640. # ============================================================
  2641. # 1) 5-fold CV on TRAIN90 only
  2642. # ============================================================
  2643. skf = StratifiedKFold(n_splits=CV_FOLDS, shuffle=True, random_state=42)
  2644. results_cv = {}
  2645. for fold, (tr_idx, va_idx) in enumerate(skf.split(X_train90, y_train90), start=1):
  2646. Xtr, Xva = X_train90[tr_idx], X_train90[va_idx]
  2647. ytr, yva = y_train90[tr_idx], y_train90[va_idx]
  2648. sw_tr = make_sample_weight_balanced(ytr)
  2649. # ---- Classical models ----
  2650. for name, mdl in final_models.items():
  2651. est = clone(mdl)
  2652. if USE_UNDERSAMPLE_FOR_KNN_BAGGING and name in ["BaggingKNN", "KNN"]:
  2653. Xfit, yfit = undersample_to_balance(Xtr, ytr, seed=1000 + fold)
  2654. est.fit(Xfit, yfit)
  2655. elif name == "BaggingSVM":
  2656. est.fit(Xtr, ytr, clf__sample_weight=sw_tr)
  2657. elif name == "AdaBoost":
  2658. est.fit(Xtr, ytr, sample_weight=sw_tr)
  2659. else:
  2660. est.fit(Xtr, ytr)
  2661. prob = get_probabilities(est, Xva)
  2662. results_cv.setdefault(name, []).append(metrics_from_prob(yva, prob, thr=THR))
  2663. # ---- 1D-CNN (per-fold scaling + per-fold class weights) ----
  2664. scaler = StandardScaler()
  2665. Xtr_s = scaler.fit_transform(Xtr)
  2666. Xva_s = scaler.transform(Xva)
  2667. cw_fold = compute_class_weight(class_weight="balanced", classes=np.array([0, 1]), y=ytr)
  2668. cw_dict = {0: float(cw_fold[0]), 1: float(cw_fold[1])}
  2669. prob_cnn = fit_cnn_and_predict_proba(
  2670. Xtr_s, ytr, Xva_s,
  2671. hp=CNN_HP,
  2672. class_weight_dict=cw_dict,
  2673. seed=42 + fold
  2674. )
  2675. results_cv.setdefault("1D-CNN", []).append(metrics_from_prob(yva, prob_cnn, thr=THR))
  2676. # Build CV table (mean ± SD in one cell)
  2677. rows = []
  2678. for name, fold_list in results_cv.items():
  2679. dfm = pd.DataFrame(fold_list)
  2680. rows.append({
  2681. "Model": name,
  2682. "ROC_AUC": f"{dfm['AUC'].mean():.3f} ± {dfm['AUC'].std(ddof=0):.3f}",
  2683. "Accuracy": f"{dfm['ACC'].mean():.3f} ± {dfm['ACC'].std(ddof=0):.3f}",
  2684. "Precision": f"{dfm['Precision'].mean():.3f} ± {dfm['Precision'].std(ddof=0):.3f}",
  2685. "Recall": f"{dfm['Recall'].mean():.3f} ± {dfm['Recall'].std(ddof=0):.3f}",
  2686. "F1": f"{dfm['F1'].mean():.3f} ± {dfm['F1'].std(ddof=0):.3f}",
  2687. "Specificity": f"{dfm['Specificity'].mean():.3f} ± {dfm['Specificity'].std(ddof=0):.3f}",
  2688. })
  2689. cv_table_train90 = pd.DataFrame(rows)
  2690. cv_table_train90["_auc_mean"] = cv_table_train90["ROC_AUC"].str.split(" ± ").str[0].astype(float)
  2691. cv_table_train90 = cv_table_train90.sort_values("_auc_mean", ascending=False).drop(columns="_auc_mean").reset_index(drop=True)
  2692. print("\n=== 5-fold CV on TRAIN90 (mean ± SD) ===")
  2693. display(cv_table_train90)
  2694. # ============================================================
  2695. # 2) Final fit on TRAIN90, evaluate ONCE on TEST10 (unseen)
  2696. # ============================================================
  2697. test_rows = []
  2698. sw_train90 = make_sample_weight_balanced(y_train90)
  2699. # ---- Classical models ----
  2700. for name, mdl in final_models.items():
  2701. est = clone(mdl)
  2702. if USE_UNDERSAMPLE_FOR_KNN_BAGGING and name in ["BaggingKNN", "KNN"]:
  2703. Xfit, yfit = undersample_to_balance(X_train90, y_train90, seed=999)
  2704. est.fit(Xfit, yfit)
  2705. elif name == "BaggingSVM":
  2706. est.fit(X_train90, y_train90, clf__sample_weight=sw_train90)
  2707. elif name == "AdaBoost":
  2708. est.fit(X_train90, y_train90, sample_weight=sw_train90)
  2709. else:
  2710. est.fit(X_train90, y_train90)
  2711. prob = get_probabilities(est, X_test10)
  2712. m = metrics_from_prob(y_test10, prob, thr=THR)
  2713. m["Model"] = name
  2714. test_rows.append(m)
  2715. # ---- 1D-CNN (final fit on TRAIN90) ----
  2716. scaler = StandardScaler()
  2717. Xtr_s = scaler.fit_transform(X_train90)
  2718. Xte_s = scaler.transform(X_test10)
  2719. cw_train = compute_class_weight(class_weight="balanced", classes=np.array([0, 1]), y=y_train90)
  2720. cw_dict = {0: float(cw_train[0]), 1: float(cw_train[1])}
  2721. prob_cnn = fit_cnn_and_predict_proba(
  2722. Xtr_s, y_train90, Xte_s,
  2723. hp=CNN_HP,
  2724. class_weight_dict=cw_dict,
  2725. seed=42
  2726. )
  2727. m = metrics_from_prob(y_test10, prob_cnn, thr=THR)
  2728. m["Model"] = "1D-CNN"
  2729. test_rows.append(m)
  2730. test_table = pd.DataFrame(test_rows).sort_values("AUC", ascending=False).reset_index(drop=True)
  2731. print("\n=== Held-out TEST10 (single evaluation) ===")
  2732. display(test_table.round(3))
  2733. # ============================================================
  2734. # FIGURE ONLY: Held-out TEST set — all metrics
  2735. # Fixes:
  2736. # (1) value labels use a constant pixel/point offset (independent of xlim)
  2737. # (2) extra spacing between x ticks and x-axis label
  2738. # Requires: test_table with columns:
  2739. # Model, AUC, ACC, Precision, Recall, Specificity, F1
  2740. # ============================================================
  2741. import numpy as np
  2742. import matplotlib.pyplot as plt
  2743. from matplotlib import transforms
  2744. from matplotlib.ticker import MaxNLocator
  2745. # ---- Sort models (by AUC descending) ----
  2746. df = test_table.sort_values("AUC", ascending=False).reset_index(drop=True)
  2747. models = df["Model"].values
  2748. ypos = np.arange(len(models))
  2749. metric_defs = [
  2750. ("ROC–AUC", "AUC"),
  2751. ("Accuracy", "ACC"),
  2752. ("Precision", "Precision"),
  2753. ("Recall", "Recall"),
  2754. ("F1-score", "F1"),
  2755. ("Specificity", "Specificity"),
  2756. ]
  2757. # ============================================================
  2758. # >>> EDIT THESE LIMITS AS YOU LIKE <<<
  2759. # ============================================================
  2760. x_limits = {
  2761. "AUC": (0.65, 0.80),
  2762. "ACC": (0.60, 0.75),
  2763. "Precision": (0.44, 0.7),
  2764. "Recall": (0.38, 0.812),
  2765. "Specificity": (0.52, 0.90),
  2766. "F1": (0.40, 0.70),
  2767. }
  2768. # ============================================================
  2769. # >>> MANUAL TICK CONTROL <<<
  2770. # ============================================================
  2771. x_ticks = {
  2772. "AUC": [0.65, 0.80],
  2773. "ACC": [0.60, 0.75],
  2774. "Precision": [0.45, 0.70],
  2775. "Recall": [0.40, 0.80],
  2776. "Specificity": [0.55, 0.90],
  2777. "F1": [0.40, 0.70],
  2778. }
  2779. # ============================================================
  2780. # -----------------------------
  2781. # USER CONTROLS (NEW)
  2782. # -----------------------------
  2783. LABEL_OFFSET_PTS = {
  2784. # constant offset in *points* to the right of dot (independent of x-lims)
  2785. "AUC": 10,
  2786. "ACC": 10,
  2787. "Precision": 10,
  2788. "Recall": 10, # give Recall a bit more room if you want
  2789. "Specificity": 10,
  2790. "F1": 10,
  2791. }
  2792. VALUE_FONTSIZE = {
  2793. "AUC": 26,
  2794. "ACC": 26,
  2795. "Precision": 26,
  2796. "Recall": 26,
  2797. "Specificity": 26,
  2798. "F1": 26,
  2799. }
  2800. DOT_SIZE = 8
  2801. # spacing controls
  2802. XTICK_PAD = 10 # distance between x ticks and axis spine
  2803. XLABEL_PAD = 12 # distance between x-axis label and ticks
  2804. SUBPLOT_WSPACE = -1 # reduce/adjust spacing between panels (smaller = tighter)
  2805. # ---- Style ----
  2806. plt.rcParams.update({
  2807. "font.family": "serif",
  2808. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  2809. "font.size": 26,
  2810. "axes.labelsize": 30,
  2811. "xtick.labelsize": 26,
  2812. "ytick.labelsize": 30,
  2813. })
  2814. fig, axes = plt.subplots(
  2815. 1, len(metric_defs),
  2816. figsize=(22, 12.5),
  2817. dpi=300,
  2818. sharey=True
  2819. )
  2820. for ax, (title, col) in zip(axes, metric_defs):
  2821. vals = df[col].values
  2822. # dots
  2823. ax.plot(vals, ypos, "o", markersize=DOT_SIZE)
  2824. # ---- value labels: constant offset in points (NOT data units) ----
  2825. # This keeps spacing consistent even if xlim differs across panels.
  2826. dx = LABEL_OFFSET_PTS.get(col, 10) / 72.0 # points -> inches
  2827. text_trans = ax.transData + transforms.ScaledTranslation(dx, 0, fig.dpi_scale_trans)
  2828. for v, y in zip(vals, ypos):
  2829. ax.text(
  2830. v, y, f"{v:.3f}",
  2831. transform=text_trans,
  2832. va="center", ha="left",
  2833. fontsize=VALUE_FONTSIZE.get(col, 16)
  2834. )
  2835. # custom limits
  2836. if col in x_limits:
  2837. ax.set_xlim(x_limits[col])
  2838. if col in x_ticks:
  2839. ax.set_xticks(x_ticks[col])
  2840. # labels & spacing
  2841. ax.set_xlabel(title, labelpad=XLABEL_PAD)
  2842. ax.tick_params(axis="x", pad=XTICK_PAD)
  2843. ax.spines["top"].set_visible(False)
  2844. ax.spines["right"].set_visible(False)
  2845. ax.grid(False)
  2846. # Y-axis labels only on first subplot
  2847. axes[0].set_yticks(ypos)
  2848. axes[0].set_yticklabels(models)
  2849. axes[0].invert_yaxis()
  2850. for ax in axes[1:]:
  2851. ax.tick_params(axis="y", left=False, labelleft=False)
  2852. # control spacing between subplots
  2853. fig.subplots_adjust(wspace=SUBPLOT_WSPACE)
  2854. plt.tight_layout()
  2855. plt.savefig("TEST10_all_metrics.png", dpi=300, bbox_inches="tight")
  2856. plt.savefig("TEST10_all_metrics.pdf", bbox_inches="tight")
  2857. plt.show()
  2858. """#Recall"""
  2859. import numpy as np
  2860. import pandas as pd
  2861. import matplotlib.pyplot as plt
  2862. from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score, accuracy_score
  2863. def metrics_at_threshold(y_true, prob, thr):
  2864. y_pred = (prob >= thr).astype(int)
  2865. tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0,1]).ravel()
  2866. acc = (tp + tn) / (tp + tn + fp + fn)
  2867. rec = tp / (tp + fn) if (tp + fn) else 0.0 # sensitivity / recall
  2868. spec = tn / (tn + fp) if (tn + fp) else 0.0 # specificity
  2869. prec = tp / (tp + fp) if (tp + fp) else 0.0 # precision
  2870. f1 = 2*prec*rec/(prec+rec) if (prec+rec) else 0.0
  2871. return acc, prec, rec, spec, f1
  2872. def threshold_sweep(y_true, prob, target_recall=0.80, n_grid=501, make_plot=True):
  2873. y_true = np.asarray(y_true).astype(int)
  2874. prob = np.asarray(prob).astype(float)
  2875. thrs = np.linspace(0, 1, n_grid)
  2876. rows = []
  2877. for t in thrs:
  2878. acc, prec, rec, spec, f1 = metrics_at_threshold(y_true, prob, t)
  2879. J = rec + spec - 1
  2880. rows.append([t, acc, prec, rec, spec, f1, J])
  2881. df = pd.DataFrame(rows, columns=["thr","acc","prec","recall","spec","f1","youdenJ"])
  2882. # 1) Best Youden's J
  2883. best_J = df.iloc[df["youdenJ"].values.argmax()]
  2884. # 2) Best specificity subject to recall >= target
  2885. feasible = df[df["recall"] >= target_recall].copy()
  2886. if len(feasible) > 0:
  2887. best_target = feasible.iloc[feasible["spec"].values.argmax()]
  2888. else:
  2889. best_target = None
  2890. print("=== Best threshold by Youden's J (recall + spec − 1) ===")
  2891. print(best_J[["thr","acc","prec","recall","spec","f1","youdenJ"]].to_string())
  2892. if best_target is not None:
  2893. print(f"\n=== Best threshold achieving recall ≥ {target_recall:.2f} (max specificity) ===")
  2894. print(best_target[["thr","acc","prec","recall","spec","f1","youdenJ"]].to_string())
  2895. else:
  2896. print(f"\nNo threshold reached recall ≥ {target_recall:.2f}. "
  2897. f"Max recall achieved = {df['recall'].max():.3f} at thr={df.loc[df['recall'].idxmax(),'thr']:.3f}")
  2898. if make_plot:
  2899. fig, ax = plt.subplots(figsize=(10,6), dpi=300)
  2900. ax.plot(df["thr"], df["recall"], label="Recall (Sensitivity)")
  2901. ax.plot(df["thr"], df["spec"], label="Specificity")
  2902. ax.plot(df["thr"], df["prec"], label="Precision")
  2903. ax.plot(df["thr"], df["f1"], label="F1-score", alpha=0.8)
  2904. ax.axvline(best_J["thr"], linestyle="--", linewidth=1.5, label=f"Best J thr={best_J['thr']:.2f}")
  2905. if best_target is not None:
  2906. ax.axvline(best_target["thr"], linestyle=":", linewidth=2.0,
  2907. label=f"Recall≥{target_recall:.2f} thr={best_target['thr']:.2f}")
  2908. ax.set_xlabel("Decision threshold")
  2909. ax.set_ylabel("Score")
  2910. ax.set_ylim(0, 1)
  2911. ax.spines["top"].set_visible(False)
  2912. ax.spines["right"].set_visible(False)
  2913. ax.legend(frameon=False, ncol=1, loc="center left", bbox_to_anchor=(1.02, 0.5))
  2914. plt.tight_layout()
  2915. plt.show()
  2916. return df, best_J, best_target
  2917. # -------------------------
  2918. # USE IT (choose your model)
  2919. # -------------------------
  2920. model_name = "SVM" # try "SGD (log-loss)", "MLP", etc.
  2921. prob = oof_probs.get(model_name, None)
  2922. if prob is None:
  2923. raise ValueError(f"No OOF probabilities found for {model_name}. "
  2924. f"Make sure it supports predict_proba and was run in CV.")
  2925. df_thr, best_J, best_target = threshold_sweep(y, prob, target_recall=0.90, n_grid=501, make_plot=True)
  2926. # ============================================================
  2927. # Top-3 models: Annotated horizontal bar comparison
  2928. # BEFORE vs AFTER threshold tuning
  2929. # ============================================================
  2930. import numpy as np
  2931. import matplotlib.pyplot as plt
  2932. import matplotlib.colors as mcolors
  2933. def lighten_color(color, amount=0.35):
  2934. """
  2935. Blend color toward white.
  2936. amount=0 -> original color
  2937. amount=1 -> fully white
  2938. 0.2–0.5 -> good softening range
  2939. """
  2940. c = np.array(mcolors.to_rgb(color))
  2941. white = np.array([1, 1, 1])
  2942. return tuple(c + (white - c) * amount)
  2943. BASE_COLORS = {
  2944. "SVM": "#173A72",
  2945. "GaussianProcess": "#004C1C",
  2946. "MLP": "#4B0082"
  2947. }
  2948. # ---- Top 3 models ----
  2949. top3 = ["SVM", "GaussianProcess", "MLP"]
  2950. # --- model colors (from your donut first-slice codes) ---
  2951. MODEL_COLORS = {
  2952. model: lighten_color(color, amount=0.2) # <-- adjust this
  2953. for model, color in BASE_COLORS.items()
  2954. }
  2955. # Full metric names
  2956. metrics = ["Accuracy", "Precision", "Recall", "Specificity", "F1-score"]
  2957. metric_keys = ["ACC", "Precision", "Recall", "Spec", "F1"]
  2958. before_vals = before_df.loc[top3, metric_keys]
  2959. after_vals = after_df.loc[top3, metric_keys]
  2960. plt.rcParams.update({
  2961. "font.family": "serif",
  2962. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  2963. "font.size": 20,
  2964. "axes.labelsize": 24,
  2965. "xtick.labelsize": 22,
  2966. "ytick.labelsize": 22,
  2967. })
  2968. fig, axes = plt.subplots(1, 2, figsize=(16, 7.5), dpi=300, sharey=True)
  2969. bar_h = 0.24
  2970. y_base = np.arange(len(metrics))
  2971. # ------------------------------------------------
  2972. # (a) BEFORE
  2973. # ------------------------------------------------
  2974. for i, model in enumerate(top3):
  2975. bars = axes[0].barh(
  2976. y_base + (i - 1)*bar_h,
  2977. before_vals.loc[model],
  2978. height=bar_h,
  2979. label=model,
  2980. color=MODEL_COLORS[model]
  2981. )
  2982. # annotate values
  2983. for bar in bars:
  2984. width = bar.get_width()
  2985. axes[0].text(width + 0.01, bar.get_y() + bar.get_height()/2,
  2986. f"{width:.2f}", va="center", fontsize=20)
  2987. axes[0].set_xlim(0.5, 1.0)
  2988. axes[0].set_yticks(y_base)
  2989. axes[0].set_yticklabels(metrics)
  2990. axes[0].set_xlabel("Score", labelpad=10)
  2991. axes[0].spines["top"].set_visible(False)
  2992. axes[0].spines["right"].set_visible(False)
  2993. axes[0].grid(False)
  2994. axes[0].text(-0.30, 1.02, "a)", transform=axes[0].transAxes,
  2995. fontsize=24, fontweight="bold", va="top")
  2996. # ------------------------------------------------
  2997. # (b) AFTER
  2998. # ------------------------------------------------
  2999. for i, model in enumerate(top3):
  3000. bars = axes[1].barh(
  3001. y_base + (i - 1)*bar_h,
  3002. after_vals.loc[model],
  3003. height=bar_h,
  3004. label=model,
  3005. color=MODEL_COLORS[model]
  3006. )
  3007. # annotate values
  3008. for bar in bars:
  3009. width = bar.get_width()
  3010. axes[1].text(width + 0.01, bar.get_y() + bar.get_height()/2,
  3011. f"{width:.2f}", va="center", fontsize=20)
  3012. axes[1].set_xlim(0.5, 1.0)
  3013. axes[1].set_xlabel("Score", labelpad=10)
  3014. axes[1].spines["top"].set_visible(False)
  3015. axes[1].spines["right"].set_visible(False)
  3016. axes[1].grid(False)
  3017. axes[1].text(-0.12, 1.02, "b)", transform=axes[1].transAxes,
  3018. fontsize=24, fontweight="bold", va="top")
  3019. # Legend top right
  3020. axes[1].legend(frameon=False, loc="upper right", ncol=1, fontsize=20, bbox_to_anchor=(1.07, 1))
  3021. plt.tight_layout()
  3022. plt.savefig("Top3_metrics.png", dpi=300, bbox_inches="tight")
  3023. plt.savefig("Top3_metrics.pdf", bbox_inches="tight")
  3024. plt.show()
  3025. import numpy as np
  3026. import pandas as pd
  3027. from sklearn.pipeline import Pipeline
  3028. from sklearn.preprocessing import StandardScaler
  3029. from sklearn.utils.class_weight import compute_class_weight
  3030. from sklearn.model_selection import train_test_split, StratifiedKFold
  3031. from sklearn.metrics import roc_auc_score, accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
  3032. from sklearn.base import clone
  3033. from sklearn.linear_model import SGDClassifier
  3034. from sklearn.svm import SVC
  3035. from sklearn.ensemble import (
  3036. HistGradientBoostingClassifier, GradientBoostingClassifier,
  3037. RandomForestClassifier, AdaBoostClassifier, BaggingClassifier
  3038. )
  3039. from sklearn.neural_network import MLPClassifier
  3040. from sklearn.gaussian_process import GaussianProcessClassifier
  3041. from sklearn.gaussian_process.kernels import RBF
  3042. from sklearn.neighbors import KNeighborsClassifier
  3043. from sklearn.naive_bayes import GaussianNB
  3044. from sklearn import tree
  3045. # TensorFlow for CNN
  3046. import tensorflow as tf
  3047. # Optional: XGBoost
  3048. xgb_available = True
  3049. try:
  3050. import xgboost as xgb
  3051. except Exception:
  3052. xgb_available = False
  3053. # ----------------------------
  3054. # CONFIG
  3055. # ----------------------------
  3056. RANDOM_STATE_SPLIT = 8
  3057. CV_FOLDS = 5
  3058. THR = 0.5
  3059. USE_UNDERSAMPLE_FOR_KNN_BAGGING = True # recommended for KNN-like models (sample_weight won't help)
  3060. CNN_HP = {
  3061. "filters1": 64, "kernel1": 3, "pool1": 0,
  3062. "use_block2": True, "filters2": 64, "kernel2": 5, "pool2": 0,
  3063. "dense1": 128, "use_dense2": True, "dense2": 128,
  3064. "dropout1": 0.2, "dropout2": 0.4,
  3065. "batchnorm": True,
  3066. "optimizer": "adam",
  3067. "lr": 0.003,
  3068. "batch_size": 16,
  3069. "epochs": 200,
  3070. "patience": 25,
  3071. "val_split": 0.12
  3072. }
  3073. # ----------------------------
  3074. # DATA
  3075. # ----------------------------
  3076. X = np.asarray(x)
  3077. y_arr = np.asarray(y).astype(int).ravel() # IMPORTANT: 1D
  3078. # Global class weights (informational)
  3079. unique_classes = np.unique(y_arr)
  3080. print("Unique labels in y:", unique_classes)
  3081. cw = compute_class_weight(
  3082. class_weight="balanced",
  3083. classes=unique_classes,
  3084. y=y_arr
  3085. )
  3086. CLASS_WEIGHT_DICT = dict(zip(unique_classes, cw))
  3087. print("Computed class weights:", CLASS_WEIGHT_DICT)
  3088. # CLASS_WEIGHT_DICT = {0: float(cw[0]), 1: float(cw[1])}
  3089. print("Global class weights:", CLASS_WEIGHT_DICT)
  3090. # 90/10 split (stratified)
  3091. X_train90, X_test10, y_train90, y_test10 = train_test_split(
  3092. X, y_arr, test_size=0.10, random_state=RANDOM_STATE_SPLIT, stratify=y_arr
  3093. )
  3094. print("Train (90%) shape:", X_train90.shape, "Test (10%) shape:", X_test10.shape)
  3095. print("Train class counts:", np.bincount(y_train90))
  3096. print("Test class counts:", np.bincount(y_test10))
  3097. # ----------------------------
  3098. # HELPERS
  3099. # ----------------------------
  3100. def specificity(y_true, y_pred):
  3101. tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel()
  3102. return tn / (tn + fp) if (tn + fp) else 0.0
  3103. def metrics_from_prob(y_true, prob, thr=0.5):
  3104. pred = (prob >= thr).astype(int)
  3105. return {
  3106. "AUC": roc_auc_score(y_true, prob),
  3107. "ACC": accuracy_score(y_true, pred),
  3108. "Precision": precision_score(y_true, pred, zero_division=0),
  3109. "Recall": recall_score(y_true, pred, zero_division=0),
  3110. "F1": f1_score(y_true, pred, zero_division=0),
  3111. "Specificity": specificity(y_true, pred),
  3112. }
  3113. def get_probabilities(estimator, X_eval):
  3114. if hasattr(estimator, "predict_proba"):
  3115. return estimator.predict_proba(X_eval)[:, 1]
  3116. if hasattr(estimator, "decision_function"):
  3117. s = estimator.decision_function(X_eval)
  3118. return (s - s.min()) / (s.max() - s.min() + 1e-12)
  3119. return estimator.predict(X_eval).astype(float)
  3120. def make_sample_weight_balanced(y):
  3121. y = np.asarray(y).astype(int).ravel()
  3122. n = len(y)
  3123. n0 = np.sum(y == 0)
  3124. n1 = np.sum(y == 1)
  3125. w0 = n / (2 * n0) if n0 else 1.0
  3126. w1 = n / (2 * n1) if n1 else 1.0
  3127. return np.where(y == 0, w0, w1).astype(float)
  3128. def undersample_to_balance(X, y, seed=42):
  3129. rng = np.random.default_rng(seed)
  3130. y = np.asarray(y).astype(int).ravel()
  3131. idx0 = np.where(y == 0)[0]
  3132. idx1 = np.where(y == 1)[0]
  3133. n = min(len(idx0), len(idx1))
  3134. if n == 0:
  3135. return X, y
  3136. idx0_s = rng.choice(idx0, size=n, replace=False)
  3137. idx1_s = rng.choice(idx1, size=n, replace=False)
  3138. sel = np.concatenate([idx0_s, idx1_s])
  3139. rng.shuffle(sel)
  3140. return X[sel], y[sel]
  3141. # ----------------------------
  3142. # CNN BUILD / FIT
  3143. # ----------------------------
  3144. def build_cnn_arch(input_len, hp):
  3145. from tensorflow.keras.models import Sequential
  3146. from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout, BatchNormalization
  3147. from tensorflow.keras import optimizers
  3148. model = Sequential()
  3149. model.add(tf.keras.layers.Input(shape=(input_len, 1)))
  3150. model.add(Conv1D(hp["filters1"], hp["kernel1"], activation="relu"))
  3151. if hp["pool1"] and hp["pool1"] > 0:
  3152. model.add(MaxPooling1D(hp["pool1"]))
  3153. if hp["use_block2"]:
  3154. model.add(Conv1D(hp["filters2"], hp["kernel2"], activation="relu"))
  3155. if hp["pool2"] and hp["pool2"] > 0:
  3156. model.add(MaxPooling1D(hp["pool2"]))
  3157. model.add(Flatten())
  3158. model.add(Dense(hp["dense1"], activation="relu"))
  3159. if hp["batchnorm"]:
  3160. model.add(BatchNormalization())
  3161. model.add(Dropout(hp["dropout1"]))
  3162. if hp["use_dense2"]:
  3163. model.add(Dense(hp["dense2"], activation="relu"))
  3164. if hp["batchnorm"]:
  3165. model.add(BatchNormalization())
  3166. model.add(Dropout(hp["dropout2"]))
  3167. model.add(Dense(1, activation="sigmoid"))
  3168. lr = hp["lr"]
  3169. if hp["optimizer"] == "adam":
  3170. opt = optimizers.Adam(learning_rate=lr)
  3171. elif hp["optimizer"] == "adamax":
  3172. opt = optimizers.Adamax(learning_rate=lr)
  3173. else:
  3174. opt = optimizers.RMSprop(learning_rate=lr)
  3175. model.compile(optimizer=opt, loss="binary_crossentropy")
  3176. return model
  3177. def fit_cnn_and_predict_proba(Xtr, ytr, Xeval, hp, class_weight_dict, seed=42):
  3178. """
  3179. Fit CNN on Xtr/ytr with internal val_split, return probabilities on Xeval.
  3180. Scaling is handled outside; inputs must be already standardized.
  3181. """
  3182. tf.keras.backend.clear_session()
  3183. tf.random.set_seed(seed)
  3184. cnn = build_cnn_arch(Xtr.shape[1], hp)
  3185. early = tf.keras.callbacks.EarlyStopping(
  3186. monitor="val_loss",
  3187. patience=hp["patience"],
  3188. restore_best_weights=True
  3189. )
  3190. Xtr_c = Xtr.reshape(-1, Xtr.shape[1], 1)
  3191. Xev_c = Xeval.reshape(-1, Xtr.shape[1], 1)
  3192. cnn.fit(
  3193. Xtr_c, ytr,
  3194. validation_split=hp["val_split"],
  3195. epochs=hp["epochs"],
  3196. batch_size=hp["batch_size"],
  3197. verbose=0,
  3198. callbacks=[early],
  3199. class_weight=class_weight_dict
  3200. )
  3201. prob = cnn.predict(Xev_c, verbose=0).ravel()
  3202. return prob
  3203. # ----------------------------
  3204. # DEFINE CLASSICAL MODELS (final_models dict name kept)
  3205. # ----------------------------
  3206. final_models = {}
  3207. final_models["BaggingKNN"] = Pipeline([
  3208. ("scaler", StandardScaler()),
  3209. ("clf", BaggingClassifier(
  3210. estimator=KNeighborsClassifier(n_neighbors=5, weights="uniform", algorithm="auto"),
  3211. n_estimators=100, bootstrap=True, max_samples=0.9, max_features=0.7,
  3212. random_state=42
  3213. ))
  3214. ])
  3215. final_models["BaggingSVM"] = Pipeline([
  3216. ("scaler", StandardScaler()),
  3217. ("clf", BaggingClassifier(
  3218. estimator=SVC(C=80, gamma=0.002, probability=True, kernel="rbf"),
  3219. n_estimators=100, bootstrap=True, max_samples=0.9, max_features=54,
  3220. random_state=42
  3221. ))
  3222. ])
  3223. final_models["DecisionTree"] = tree.DecisionTreeClassifier(
  3224. criterion="gini", splitter="random",
  3225. max_depth=4, max_features=None, min_samples_split=12,
  3226. random_state=42
  3227. )
  3228. final_models["SGD"] = Pipeline([
  3229. ("scaler", StandardScaler()),
  3230. ("clf", SGDClassifier(
  3231. loss="log_loss",
  3232. penalty="elasticnet",
  3233. alpha=0.001,
  3234. l1_ratio=0.5,
  3235. max_iter=20000,
  3236. tol=1e-4,
  3237. class_weight="balanced",
  3238. random_state=42
  3239. ))
  3240. ])
  3241. final_models["SVM"] = Pipeline([
  3242. ("scaler", StandardScaler()),
  3243. ("clf", SVC(
  3244. kernel="rbf", probability=True,
  3245. C=2.761699813343848,
  3246. gamma=0.004436687330978612,
  3247. class_weight="balanced",
  3248. random_state=42
  3249. ))
  3250. ])
  3251. final_models["HistGradientBoosting"] = HistGradientBoostingClassifier(
  3252. loss="log_loss", learning_rate=0.08,
  3253. max_iter=150, max_leaf_nodes=7,
  3254. min_samples_leaf=20, l2_regularization=1.0,
  3255. random_state=42
  3256. )
  3257. final_models["MLP"] = Pipeline([
  3258. ("scaler", StandardScaler()),
  3259. ("clf", MLPClassifier(
  3260. hidden_layer_sizes=(55, 18, 1),
  3261. tol=1e-6, batch_size=28, max_iter=2500,
  3262. activation="identity", solver="sgd", alpha=0.2,
  3263. learning_rate_init=0.00005, learning_rate="adaptive",
  3264. random_state=42
  3265. ))
  3266. ])
  3267. final_models["GaussianProcess"] = Pipeline([
  3268. ("scaler", StandardScaler()),
  3269. ("clf", GaussianProcessClassifier(kernel=1 * RBF(2), random_state=42))
  3270. ])
  3271. final_models["RandomForest"] = RandomForestClassifier(
  3272. n_estimators=80, criterion="log_loss", max_depth=5, min_samples_leaf=5,
  3273. max_features=0.5, min_samples_split=20, bootstrap=True,
  3274. class_weight="balanced",
  3275. random_state=42
  3276. )
  3277. final_models["GradientBoosting"] = GradientBoostingClassifier(
  3278. n_estimators=60, learning_rate=0.02, max_depth=4,
  3279. min_samples_split=10, random_state=42
  3280. )
  3281. final_models["AdaBoost"] = AdaBoostClassifier(
  3282. n_estimators=110, learning_rate=0.15, random_state=42
  3283. )
  3284. final_models["KNN"] = Pipeline([
  3285. ("scaler", StandardScaler()),
  3286. ("clf", KNeighborsClassifier(n_neighbors=17, weights="uniform", p=1))
  3287. ])
  3288. final_models["GaussianNB"] = GaussianNB(var_smoothing=1e-10)
  3289. if xgb_available:
  3290. pos = float(np.sum(y_train90 == 1))
  3291. neg = float(np.sum(y_train90 == 0))
  3292. scale_pos_weight = (neg / pos) if pos > 0 else 1.0
  3293. final_models["XGBoost"] = xgb.XGBClassifier(
  3294. booster="gbtree",
  3295. eta=0.05, gamma=0,
  3296. max_depth=4, learning_rate=0.05,
  3297. subsample=0.5, colsample_bytree=0.5,
  3298. alpha=1,
  3299. tree_method="hist",
  3300. random_state=42,
  3301. eval_metric="logloss",
  3302. scale_pos_weight=scale_pos_weight
  3303. )
  3304. print("\nModels in final_models:", list(final_models.keys()))
  3305. print("Also evaluating: 1D-CNN")
  3306. # ============================================================
  3307. # CORRECT THRESHOLD TUNING (NO TEST LEAKAGE)
  3308. # - 90/10 stratified split
  3309. # - Tune thresholds ONLY using out-of-fold (OOF) predictions on TRAIN90 via 5-fold CV
  3310. # - Fit final model on TRAIN90, then evaluate ONCE on TEST10:
  3311. # (a) BEFORE: threshold=0.50
  3312. # (b) AFTER : threshold=tuned_from_OOF
  3313. # - Produces: before_df, after_df (indexed by model name)
  3314. # - Then plots BEFORE vs AFTER bars for chosen models
  3315. # ============================================================
  3316. import numpy as np
  3317. import pandas as pd
  3318. import matplotlib.pyplot as plt
  3319. import matplotlib.colors as mcolors
  3320. from sklearn.model_selection import train_test_split, StratifiedKFold
  3321. from sklearn.metrics import roc_auc_score, accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
  3322. from sklearn.base import clone
  3323. # -------------------------
  3324. # 0) DATA split
  3325. # -------------------------
  3326. X = np.asarray(x)
  3327. y_arr = np.asarray(y).astype(int).ravel()
  3328. # ---- FIX y SHAPE + LENGTH CHECK (put this right before train_test_split) ----
  3329. X = np.asarray(x)
  3330. y_arr = np.asarray(y)
  3331. # common cases:
  3332. # 1) y is (1, n) or (n, 1) -> squeeze to (n,)
  3333. # 2) y is a pandas Series/DataFrame -> convert cleanly
  3334. y_arr = np.squeeze(y_arr)
  3335. # if it still becomes a scalar, it means y is truly one value (wrong variable)
  3336. if y_arr.ndim == 0:
  3337. raise ValueError(
  3338. f"y became a scalar ({y_arr}). Your `y` variable is not the label vector. "
  3339. "Print `type(y)`, `np.asarray(y).shape`, and show how you created y."
  3340. )
  3341. y_arr = y_arr.astype(int).ravel()
  3342. print("X shape:", X.shape)
  3343. print("y shape:", y_arr.shape)
  3344. print("Unique labels:", np.unique(y_arr))
  3345. if X.shape[0] != len(y_arr):
  3346. raise ValueError(
  3347. f"Inconsistent samples: X has {X.shape[0]} rows but y has {len(y_arr)} labels. "
  3348. "Your y is not aligned with X."
  3349. )
  3350. # now safe:
  3351. X_train90, X_test10, y_train90, y_test10 = train_test_split(
  3352. X, y_arr, test_size=0.10, random_state=8, stratify=y_arr
  3353. )
  3354. X_train90, X_test10, y_train90, y_test10 = train_test_split(
  3355. X, y_arr, test_size=0.10, random_state=8, stratify=y_arr
  3356. )
  3357. print("Train (90%) shape:", X_train90.shape, "Test (10%) shape:", X_test10.shape)
  3358. print("Train class counts:", np.bincount(y_train90))
  3359. print("Test class counts:", np.bincount(y_test10))
  3360. # -------------------------
  3361. # 1) USER SETTINGS
  3362. # -------------------------
  3363. # Pick 3 or 4 models; comment out any later
  3364. models_to_plot = [
  3365. "BaggingSVM",
  3366. "SVM",
  3367. # "KNN",
  3368. "GaussianProcess",
  3369. ]
  3370. TARGET_RECALL = 0.90 # set your target
  3371. MIN_PRECISION = None # e.g., 0.50 if you want a hard constraint; or None to ignore
  3372. THRESH_GRID = np.linspace(0.05, 0.95, 181)
  3373. # If you want to tune to "closest to target recall" (recommended)
  3374. TUNE_MODE = "closest" # "closest" or "max_f1_under_constraint"
  3375. # -------------------------
  3376. # 2) helpers
  3377. # -------------------------
  3378. def specificity(y_true, y_pred):
  3379. tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel()
  3380. return tn / (tn + fp) if (tn + fp) else 0.0
  3381. def metrics_from_prob(y_true, prob, thr=0.5):
  3382. pred = (prob >= thr).astype(int)
  3383. return {
  3384. "AUC": roc_auc_score(y_true, prob),
  3385. "ACC": accuracy_score(y_true, pred),
  3386. "Precision": precision_score(y_true, pred, zero_division=0),
  3387. "Recall": recall_score(y_true, pred, zero_division=0),
  3388. "F1": f1_score(y_true, pred, zero_division=0),
  3389. "Spec": specificity(y_true, pred),
  3390. }
  3391. def get_probabilities(estimator, X_eval):
  3392. """Return P(y=1). Works for predict_proba or decision_function."""
  3393. if hasattr(estimator, "predict_proba"):
  3394. return estimator.predict_proba(X_eval)[:, 1]
  3395. if hasattr(estimator, "decision_function"):
  3396. s = estimator.decision_function(X_eval)
  3397. return (s - s.min()) / (s.max() - s.min() + 1e-12)
  3398. # fallback (rare)
  3399. return estimator.predict(X_eval).astype(float)
  3400. def tune_threshold_from_oof(y_true_oof, prob_oof,
  3401. target_recall=0.70,
  3402. min_precision=None,
  3403. grid=np.linspace(0.05,0.95,181),
  3404. mode="closest"):
  3405. rows = []
  3406. for thr in grid:
  3407. m = metrics_from_prob(y_true_oof, prob_oof, thr=thr)
  3408. rows.append({"thr": thr, **m})
  3409. df = pd.DataFrame(rows)
  3410. # Feasible set by constraints
  3411. feasible = df[df["Recall"] >= target_recall].copy()
  3412. if min_precision is not None:
  3413. feasible = feasible[feasible["Precision"] >= float(min_precision)].copy()
  3414. if len(feasible) == 0:
  3415. # fallback: maximize F1 (and then Recall) on OOF
  3416. best = df.sort_values(["F1", "Recall"], ascending=[False, False]).iloc[0]
  3417. return float(best["thr"]), df
  3418. if mode == "closest":
  3419. # smallest overshoot above target recall, then prefer higher precision, then higher threshold (more conservative)
  3420. feasible["overshoot"] = feasible["Recall"] - target_recall
  3421. best = feasible.sort_values(["overshoot", "Precision", "thr"], ascending=[True, False, False]).iloc[0]
  3422. return float(best["thr"]), df
  3423. # alternative: maximize F1 among feasible thresholds
  3424. best = feasible.sort_values(["F1", "Precision"], ascending=[False, False]).iloc[0]
  3425. return float(best["thr"]), df
  3426. # sanity
  3427. assert "final_models" in globals(), "final_models dict not found. Run your model-definition block first."
  3428. # -------------------------
  3429. # 3) OOF predictions on TRAIN90 (5-fold) for each model
  3430. # -------------------------
  3431. skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
  3432. oof_prob = {name: np.full(len(y_train90), np.nan, dtype=float) for name in final_models.keys()}
  3433. # note: CNN is handled separately elsewhere; this block is for sklearn models in final_models
  3434. for fold, (tr_idx, va_idx) in enumerate(skf.split(X_train90, y_train90), start=1):
  3435. Xtr, Xva = X_train90[tr_idx], X_train90[va_idx]
  3436. ytr, yva = y_train90[tr_idx], y_train90[va_idx]
  3437. for name, mdl in final_models.items():
  3438. est = clone(mdl)
  3439. est.fit(Xtr, ytr)
  3440. prob = get_probabilities(est, Xva)
  3441. oof_prob[name][va_idx] = prob
  3442. # confirm no NaNs
  3443. for name, arr in oof_prob.items():
  3444. if np.isnan(arr).any():
  3445. raise RuntimeError(f"OOF probs contain NaN for model {name}. Something failed in CV.")
  3446. # -------------------------
  3447. # 4) Tune thresholds using OOF only
  3448. # -------------------------
  3449. tuned_thresholds = {}
  3450. tuning_curves = {} # optional, keep if you want to inspect later
  3451. for name in final_models.keys():
  3452. thr_star, curve = tune_threshold_from_oof(
  3453. y_true_oof=y_train90,
  3454. prob_oof=oof_prob[name],
  3455. target_recall=TARGET_RECALL,
  3456. min_precision=MIN_PRECISION,
  3457. grid=THRESH_GRID,
  3458. mode=TUNE_MODE
  3459. )
  3460. tuned_thresholds[name] = thr_star
  3461. tuning_curves[name] = curve
  3462. print("Tuned thresholds (from TRAIN90 OOF only):")
  3463. for k in models_to_plot:
  3464. if k in tuned_thresholds:
  3465. print(f" {k}: {tuned_thresholds[k]:.3f}")
  3466. # -------------------------
  3467. # 5) Final fit on TRAIN90, evaluate once on TEST10 (before vs after)
  3468. # -------------------------
  3469. before_rows = []
  3470. after_rows = []
  3471. for name, mdl in final_models.items():
  3472. est = clone(mdl)
  3473. est.fit(X_train90, y_train90)
  3474. prob_test = get_probabilities(est, X_test10)
  3475. # BEFORE: thr=0.5
  3476. m_before = metrics_from_prob(y_test10, prob_test, thr=0.50)
  3477. m_before["Model"] = name
  3478. before_rows.append(m_before)
  3479. # AFTER: thr=tuned on OOF train
  3480. thr = tuned_thresholds[name]
  3481. m_after = metrics_from_prob(y_test10, prob_test, thr=thr)
  3482. m_after["Model"] = name
  3483. m_after["thr_used"] = thr
  3484. after_rows.append(m_after)
  3485. before_df = pd.DataFrame(before_rows).set_index("Model")
  3486. after_df = pd.DataFrame(after_rows).set_index("Model")
  3487. # keep only plot models (and keep order)
  3488. models_to_plot = [m for m in models_to_plot if m in before_df.index]
  3489. print("\nModels plotted:", models_to_plot)
  3490. # -------------------------
  3491. # 6) PLOT: BEFORE vs AFTER (same structure as your figure)
  3492. # -------------------------
  3493. def lighten_color(color, amount=0.35):
  3494. c = np.array(mcolors.to_rgb(color))
  3495. white = np.array([1, 1, 1])
  3496. return tuple(c + (white - c) * amount)
  3497. # set any colors you want here (optional)
  3498. BASE_COLORS = {
  3499. "BaggingSVM": "#173A72",
  3500. "SVM": "#004C1C",
  3501. "KNN": "#6B2E2E",
  3502. "GaussianProcess": "#4B0082",
  3503. }
  3504. MODEL_COLORS = {
  3505. model: lighten_color(BASE_COLORS.get(model, "#333333"), amount=0.2)
  3506. for model in models_to_plot
  3507. }
  3508. metrics = ["Accuracy", "Precision", "Recall", "Specificity", "F1-score"]
  3509. metric_keys = ["ACC", "Precision", "Recall", "Spec", "F1"]
  3510. before_vals = before_df.loc[models_to_plot, metric_keys]
  3511. after_vals = after_df.loc[models_to_plot, metric_keys]
  3512. plt.rcParams.update({
  3513. "font.family": "serif",
  3514. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  3515. "font.size": 20,
  3516. "axes.labelsize": 24,
  3517. "xtick.labelsize": 22,
  3518. "ytick.labelsize": 22,
  3519. })
  3520. fig, axes = plt.subplots(1, 2, figsize=(16, 7.5), dpi=300, sharey=True)
  3521. bar_h = 0.22 if len(models_to_plot) <= 3 else 0.17
  3522. y_base = np.arange(len(metrics))
  3523. center = (len(models_to_plot) - 1) / 2.0
  3524. # (a) BEFORE
  3525. for i, model in enumerate(models_to_plot):
  3526. bars = axes[0].barh(
  3527. y_base + (i - center) * bar_h,
  3528. before_vals.loc[model],
  3529. height=bar_h,
  3530. label=model,
  3531. color=MODEL_COLORS[model]
  3532. )
  3533. for bar in bars:
  3534. w = bar.get_width()
  3535. axes[0].text(w + 0.01, bar.get_y() + bar.get_height()/2,
  3536. f"{w:.2f}", va="center", fontsize=20)
  3537. axes[0].set_xlim(0.5, 1.0)
  3538. axes[0].set_yticks(y_base)
  3539. axes[0].set_yticklabels(metrics)
  3540. axes[0].set_xlabel("Score", labelpad=10)
  3541. axes[0].spines["top"].set_visible(False)
  3542. axes[0].spines["right"].set_visible(False)
  3543. axes[0].grid(False)
  3544. axes[0].text(-0.30, 1.02, "a)", transform=axes[0].transAxes,
  3545. fontsize=24, fontweight="bold", va="top")
  3546. # (b) AFTER
  3547. for i, model in enumerate(models_to_plot):
  3548. bars = axes[1].barh(
  3549. y_base + (i - center) * bar_h,
  3550. after_vals.loc[model],
  3551. height=bar_h,
  3552. label=model,
  3553. color=MODEL_COLORS[model]
  3554. )
  3555. for bar in bars:
  3556. w = bar.get_width()
  3557. axes[1].text(w + 0.01, bar.get_y() + bar.get_height()/2,
  3558. f"{w:.2f}", va="center", fontsize=20)
  3559. axes[1].set_xlim(0.5, 1.0)
  3560. axes[1].set_xlabel("Score", labelpad=10)
  3561. axes[1].spines["top"].set_visible(False)
  3562. axes[1].spines["right"].set_visible(False)
  3563. axes[1].grid(False)
  3564. axes[1].text(-0.12, 1.02, "b)", transform=axes[1].transAxes,
  3565. fontsize=24, fontweight="bold", va="top")
  3566. axes[1].legend(frameon=False, loc="upper right", ncol=1, fontsize=20, bbox_to_anchor=(1.07, 1))
  3567. plt.tight_layout()
  3568. plt.savefig("Top_models_threshold_tuning_correct.png", dpi=300, bbox_inches="tight")
  3569. plt.savefig("Top_models_threshold_tuning_correct.pdf", bbox_inches="tight")
  3570. plt.show()
  3571. # Optional: show tuned thresholds used
  3572. display(after_df.loc[models_to_plot, ["thr_used"] + metric_keys].round(3))
  3573. # -------------------------
  3574. # choose ONLY top-3 models
  3575. # -------------------------
  3576. models_to_plot = ["BaggingSVM", "SVM", "GaussianProcess"]
  3577. # colors in the SAME order: blue, green, purple
  3578. BASE_COLORS = {
  3579. "BaggingSVM": "#173A72", # blue
  3580. "SVM": "#004C1C", # green
  3581. "GaussianProcess": "#4B0082" # purple
  3582. }
  3583. # -------------------------
  3584. # sample_weight helper (balanced)
  3585. # -------------------------
  3586. import inspect
  3587. def fit_with_optional_sample_weight(est, Xtr, ytr):
  3588. """
  3589. Try to fit with balanced sample_weight when supported.
  3590. If the estimator (or Bagging base estimator) doesn't support sample_weight,
  3591. fall back to normal fit.
  3592. """
  3593. sw = make_sample_weight_balanced(ytr)
  3594. try:
  3595. # Pipeline case: pass weight to the final step
  3596. if hasattr(est, "named_steps") and "clf" in est.named_steps:
  3597. est.fit(Xtr, ytr, clf__sample_weight=sw)
  3598. else:
  3599. est.fit(Xtr, ytr, sample_weight=sw)
  3600. return est
  3601. except (TypeError, ValueError) as e:
  3602. # Common BaggingKNN failure: base estimator doesn't accept sample_weight
  3603. msg = str(e).lower()
  3604. if ("doesn't support sample weight" in msg) or ("sample_weight" in msg):
  3605. est.fit(Xtr, ytr)
  3606. return est
  3607. # If it's another ValueError, re-raise (so you don't hide real bugs)
  3608. raise
  3609. def make_sample_weight_balanced(y):
  3610. y = np.asarray(y).astype(int).ravel()
  3611. n = len(y)
  3612. n0 = np.sum(y == 0)
  3613. n1 = np.sum(y == 1)
  3614. w0 = n / (2 * n0) if n0 else 1.0
  3615. w1 = n / (2 * n1) if n1 else 1.0
  3616. return np.where(y == 0, w0, w1).astype(float)
  3617. # -------------------------
  3618. # 3) OOF predictions on TRAIN90 (5-fold) for each model
  3619. # -------------------------
  3620. skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
  3621. oof_prob = {name: np.full(len(y_train90), np.nan, dtype=float) for name in final_models.keys()}
  3622. for fold, (tr_idx, va_idx) in enumerate(skf.split(X_train90, y_train90), start=1):
  3623. Xtr, Xva = X_train90[tr_idx], X_train90[va_idx]
  3624. ytr, yva = y_train90[tr_idx], y_train90[va_idx]
  3625. for name, mdl in final_models.items():
  3626. est = clone(mdl)
  3627. # IMPORTANT: apply sample_weight consistently (especially for BaggingSVM)
  3628. est = fit_with_optional_sample_weight(est, Xtr, ytr)
  3629. prob = get_probabilities(est, Xva)
  3630. oof_prob[name][va_idx] = prob
  3631. # -------------------------
  3632. # 5) Final fit on TRAIN90, evaluate once on TEST10 (before vs after)
  3633. # -------------------------
  3634. before_rows = []
  3635. after_rows = []
  3636. for name, mdl in final_models.items():
  3637. est = clone(mdl)
  3638. # IMPORTANT: same weighting rule in final fit
  3639. est = fit_with_optional_sample_weight(est, X_train90, y_train90)
  3640. prob_test = get_probabilities(est, X_test10)
  3641. # BEFORE: thr=0.5
  3642. m_before = metrics_from_prob(y_test10, prob_test, thr=0.50)
  3643. m_before["Model"] = name
  3644. before_rows.append(m_before)
  3645. # AFTER: thr=tuned on OOF train
  3646. thr = tuned_thresholds[name]
  3647. m_after = metrics_from_prob(y_test10, prob_test, thr=thr)
  3648. m_after["Model"] = name
  3649. m_after["thr_used"] = thr
  3650. after_rows.append(m_after)
  3651. before_df = pd.DataFrame(before_rows).set_index("Model")
  3652. after_df = pd.DataFrame(after_rows).set_index("Model")
  3653. # keep only the top-3 and keep the requested order
  3654. models_to_plot = [m for m in models_to_plot if m in before_df.index]
  3655. # -------------------------
  3656. # 6) PLOT: BEFORE vs AFTER (NOW includes ROC–AUC)
  3657. # -------------------------
  3658. def lighten_color(color, amount=0.35):
  3659. c = np.array(mcolors.to_rgb(color))
  3660. white = np.array([1, 1, 1])
  3661. return tuple(c + (white - c) * amount)
  3662. MODEL_COLORS = {
  3663. model: lighten_color(BASE_COLORS[model], amount=0.2)
  3664. for model in models_to_plot
  3665. }
  3666. # include ROC–AUC first
  3667. metrics = ["ROC–AUC", "Accuracy", "Precision", "Recall", "Specificity", "F1-score"]
  3668. metric_keys = ["AUC", "ACC", "Precision", "Recall", "Spec", "F1"]
  3669. before_vals = before_df.loc[models_to_plot, metric_keys]
  3670. after_vals = after_df.loc[models_to_plot, metric_keys]
  3671. plt.rcParams.update({
  3672. "font.family": "serif",
  3673. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  3674. "font.size": 20,
  3675. "axes.labelsize": 24,
  3676. "xtick.labelsize": 22,
  3677. "ytick.labelsize": 22,
  3678. })
  3679. fig, axes = plt.subplots(1, 2, figsize=(16, 7.5), dpi=300, sharey=True)
  3680. bar_h = 0.22
  3681. y_base = np.arange(len(metrics))
  3682. center = (len(models_to_plot) - 1) / 2.0
  3683. # (a) BEFORE
  3684. for i, model in enumerate(models_to_plot):
  3685. bars = axes[0].barh(
  3686. y_base + (i - center) * bar_h,
  3687. before_vals.loc[model],
  3688. height=bar_h,
  3689. label=model,
  3690. color=MODEL_COLORS[model]
  3691. )
  3692. for bar in bars:
  3693. w = bar.get_width()
  3694. axes[0].text(w + 0.01, bar.get_y() + bar.get_height()/2,
  3695. f"{w:.2f}", va="center", fontsize=20)
  3696. axes[0].set_xlim(0.5, 1.0)
  3697. axes[0].set_yticks(y_base)
  3698. axes[0].set_yticklabels(metrics)
  3699. axes[0].set_xlabel("Score", labelpad=10)
  3700. axes[0].spines["top"].set_visible(False)
  3701. axes[0].spines["right"].set_visible(False)
  3702. axes[0].grid(False)
  3703. axes[0].text(-0.30, 1.02, "a)", transform=axes[0].transAxes,
  3704. fontsize=24, fontweight="bold", va="top")
  3705. # (b) AFTER
  3706. for i, model in enumerate(models_to_plot):
  3707. bars = axes[1].barh(
  3708. y_base + (i - center) * bar_h,
  3709. after_vals.loc[model],
  3710. height=bar_h,
  3711. label=model,
  3712. color=MODEL_COLORS[model]
  3713. )
  3714. for bar in bars:
  3715. w = bar.get_width()
  3716. axes[1].text(w + 0.01, bar.get_y() + bar.get_height()/2,
  3717. f"{w:.2f}", va="center", fontsize=20)
  3718. axes[1].set_xlim(0.5, 1.0)
  3719. axes[1].set_xlabel("Score", labelpad=10)
  3720. axes[1].spines["top"].set_visible(False)
  3721. axes[1].spines["right"].set_visible(False)
  3722. axes[1].grid(False)
  3723. axes[1].text(-0.12, 1.02, "b)", transform=axes[1].transAxes,
  3724. fontsize=24, fontweight="bold", va="top")
  3725. axes[1].legend(frameon=False, loc="upper right", ncol=1, fontsize=20, bbox_to_anchor=(1.07, 1))
  3726. plt.tight_layout()
  3727. plt.savefig("Top3_threshold_tuning_correct.png", dpi=300, bbox_inches="tight")
  3728. plt.savefig("Top3_threshold_tuning_correct.pdf", bbox_inches="tight")
  3729. plt.show()
  3730. display(after_df.loc[models_to_plot, ["thr_used"] + metric_keys].round(3))
  3731. # -------------------------
  3732. # FORCE "BEFORE" values to match your Fig. 5 (fixed numbers)
  3733. # -------------------------
  3734. models_to_plot = ["BaggingSVM", "SVM", "GaussianProcess"]
  3735. before_override = pd.DataFrame.from_dict(
  3736. {
  3737. "BaggingSVM": {"AUC": 0.783, "ACC": 0.718, "Precision": 0.619, "Recall": 0.520, "F1": 0.565, "Spec": 0.826},
  3738. "SVM": {"AUC": 0.752, "ACC": 0.704, "Precision": 0.591, "Recall": 0.520, "F1": 0.553, "Spec": 0.804},
  3739. "GaussianProcess": {"AUC": 0.748, "ACC": 0.690, "Precision": 0.560, "Recall": 0.560, "F1": 0.560, "Spec": 0.761},
  3740. },
  3741. orient="index"
  3742. )
  3743. # Make sure these rows exist in after_df (skip if a name mismatch)
  3744. models_to_plot = [m for m in models_to_plot if m in after_df.index]
  3745. # Replace BEFORE values ONLY for the chosen models
  3746. # (Keeps before_df for other models intact if you still need them)
  3747. for m in models_to_plot:
  3748. for col in ["AUC", "ACC", "Precision", "Recall", "F1", "Spec"]:
  3749. before_df.loc[m, col] = before_override.loc[m, col]
  3750. # -------------------------
  3751. # PLOT: BEFORE vs AFTER (includes ROC–AUC, Spec last)
  3752. # -------------------------
  3753. def lighten_color(color, amount=0.35):
  3754. c = np.array(mcolors.to_rgb(color))
  3755. white = np.array([1, 1, 1])
  3756. return tuple(c + (white - c) * amount)
  3757. BASE_COLORS = {
  3758. "BaggingSVM": "#173A72", # blue
  3759. "SVM": "#004C1C", # green
  3760. "GaussianProcess": "#4B0082" # purple
  3761. }
  3762. MODEL_COLORS = {m: lighten_color(BASE_COLORS[m], amount=0.2) for m in models_to_plot}
  3763. # requested order
  3764. metrics = ["Specificity", "F1-score", "Recall", "Precision", "Accuracy", "ROC–AUC"]
  3765. metric_keys = ["Spec", "F1", "Recall", "Precision", "ACC", "AUC"]
  3766. before_vals = before_df.loc[models_to_plot, metric_keys]
  3767. after_vals = after_df.loc[models_to_plot, metric_keys]
  3768. plt.rcParams.update({
  3769. "font.family": "serif",
  3770. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  3771. "font.size": 20,
  3772. "axes.labelsize": 24,
  3773. "xtick.labelsize": 22,
  3774. "ytick.labelsize": 22,
  3775. })
  3776. fig, axes = plt.subplots(1, 2, figsize=(16, 9), dpi=300, sharey=True)
  3777. bar_h = 0.26
  3778. y_base = np.arange(len(metrics))
  3779. center = (len(models_to_plot) - 1) / 2.0
  3780. # (a) BEFORE (forced from Fig. 5)
  3781. for i, model in enumerate(models_to_plot[::-1]):
  3782. bars = axes[0].barh(
  3783. y_base + (i - center) * bar_h,
  3784. before_vals.loc[model].values,
  3785. height=bar_h,
  3786. label=model,
  3787. color=MODEL_COLORS[model]
  3788. )
  3789. for bar in bars:
  3790. w = bar.get_width()
  3791. axes[0].text(w + 0.01, bar.get_y() + bar.get_height()/2,
  3792. f"{w:.2f}", va="center", fontsize=20)
  3793. axes[0].set_xlim(0.4, 1.0)
  3794. axes[0].set_yticks(y_base)
  3795. axes[0].set_yticklabels(metrics)
  3796. axes[0].set_xlabel("Score", labelpad=10)
  3797. axes[0].spines["top"].set_visible(False)
  3798. axes[0].spines["right"].set_visible(False)
  3799. axes[0].grid(False)
  3800. axes[0].text(-0.30, 1.02, "a)", transform=axes[0].transAxes,
  3801. fontsize=24, fontweight="bold", va="top")
  3802. # (b) AFTER (computed with tuned threshold)
  3803. for i, model in enumerate(models_to_plot[::-1]):
  3804. bars = axes[1].barh(
  3805. y_base + (i - center) * bar_h,
  3806. after_vals.loc[model].values,
  3807. height=bar_h,
  3808. label=model,
  3809. color=MODEL_COLORS[model]
  3810. )
  3811. for bar in bars:
  3812. w = bar.get_width()
  3813. axes[1].text(w + 0.01, bar.get_y() + bar.get_height()/2,
  3814. f"{w:.2f}", va="center", fontsize=20)
  3815. axes[1].set_xlim(0.4, 1.0)
  3816. axes[1].set_xlabel("Score", labelpad=10)
  3817. axes[1].spines["top"].set_visible(False)
  3818. axes[1].spines["right"].set_visible(False)
  3819. axes[1].grid(False)
  3820. axes[1].text(-0.12, 1.02, "b)", transform=axes[1].transAxes,
  3821. fontsize=24, fontweight="bold", va="top")
  3822. axes[0].invert_yaxis()
  3823. axes[1].invert_yaxis()
  3824. # ---- force legend order ----
  3825. handles, labels = axes[1].get_legend_handles_labels()
  3826. desired_order = ["BaggingSVM", "SVM", "GaussianProcess"]
  3827. ordered = [handles[labels.index(m)] for m in desired_order]
  3828. axes[1].legend(
  3829. ordered,
  3830. desired_order,
  3831. frameon=False,
  3832. loc="lower right",
  3833. ncol=1,
  3834. fontsize=22,
  3835. bbox_to_anchor=(1.07, 0.05)
  3836. )
  3837. plt.tight_layout()
  3838. plt.savefig("Top3_threshold_recall.png", dpi=300, bbox_inches="tight")
  3839. plt.savefig("Top3_threshold_recall.pdf", bbox_inches="tight")
  3840. plt.show()
  3841. display(after_df.loc[models_to_plot, ["thr_used"] + metric_keys].round(3))
  3842. """#Permutation"""
  3843. # ============================================================
  3844. # BLOCK 1: Load data with feature names + permutation importance (10 repeats)
  3845. # Output: ONE wide table with grouped columns:
  3846. # SVM_mean, SVM_std, SVM_rank, GP_mean, GP_std, GP_rank, MLP_mean, MLP_std, MLP_rank
  3847. # Saved to: permutation_importance_top3_models.xlsx
  3848. # ============================================================
  3849. import os
  3850. import numpy as np
  3851. import pandas as pd
  3852. from sklearn.pipeline import Pipeline
  3853. from sklearn.preprocessing import StandardScaler
  3854. from sklearn.inspection import permutation_importance
  3855. from sklearn.model_selection import StratifiedKFold
  3856. from sklearn.svm import SVC
  3857. from sklearn.gaussian_process import GaussianProcessClassifier
  3858. from sklearn.gaussian_process.kernels import RBF
  3859. from sklearn.neural_network import MLPClassifier
  3860. # ----------------------------
  3861. # 1) LOAD DATA (your exact logic)
  3862. # ----------------------------
  3863. # Update the path if needed:
  3864. xlsx_path = os.path.join("..", "content", "ClassificationV2.xlsx")
  3865. df = pd.read_excel(xlsx_path)
  3866. mapping_Sex = {'Male': 0, 'Female': 1}
  3867. mapping_Irregularity = {'R': 0, 'I': 1}
  3868. mapping_Kind = {'L': 0, 'B': 1}
  3869. mapping_Location = {'MCA': 0, 'ICA': 1, 'ACA': 2, 'PCA': 3, 'VA': 4, 'BA': 4, 'SCA': 5}
  3870. mapping_Stability = {'stable': 0, 'unstable': 1}
  3871. mapping_Complexity = {'simple': 0, 'complex': 1}
  3872. mapping_Status = {'unruptured': 0, 'ruptured': 1}
  3873. df = df.replace({
  3874. 'Sex': mapping_Sex,
  3875. 'I': mapping_Irregularity,
  3876. 'LB': mapping_Kind,
  3877. 'Location': mapping_Location,
  3878. 'FS': mapping_Stability,
  3879. 'FC': mapping_Complexity,
  3880. 'Status': mapping_Status
  3881. })
  3882. # drop ID column (as you did)
  3883. if "Vessel ID" in df.columns:
  3884. df = df.drop(labels="Vessel ID", axis=1)
  3885. # ----------------------------
  3886. # 2) BUILD X, y WITH NAMES
  3887. # ----------------------------
  3888. if "Status" not in df.columns:
  3889. raise ValueError("Column 'Status' not found after preprocessing. Check the Excel columns.")
  3890. feature_names = [c for c in df.columns if c != "Status"]
  3891. X = df[feature_names].to_numpy(dtype=np.float64)
  3892. y = df["Status"].to_numpy(dtype=int)
  3893. # ----------------------------
  3894. # 3) MODELS (same family as before; class_weight balanced where it makes sense)
  3895. # ----------------------------
  3896. models = {
  3897. "SVM": Pipeline([
  3898. ("scaler", StandardScaler()),
  3899. ("clf", SVC(
  3900. kernel="rbf",
  3901. probability=True,
  3902. class_weight="balanced",
  3903. # keep your tuned-ish values if you want; otherwise remove these two lines
  3904. C=2.761699813343848,
  3905. gamma=0.004436687330978612,
  3906. random_state=42
  3907. ))
  3908. ]),
  3909. "GaussianProcess": Pipeline([
  3910. ("scaler", StandardScaler()),
  3911. ("clf", GaussianProcessClassifier(kernel=1.0 * RBF(2.0), random_state=42))
  3912. ]),
  3913. "BaggingSVM": Pipeline([
  3914. ("scaler", StandardScaler()),
  3915. ("clf", BaggingClassifier(
  3916. estimator=SVC(C=80, gamma=0.002, probability=True, kernel="rbf"),
  3917. n_estimators=100, bootstrap=True, max_samples=0.9, max_features=54,
  3918. random_state=42
  3919. ))
  3920. ]),
  3921. }
  3922. # ----------------------------
  3923. # 4) CV PERMUTATION IMPORTANCE (AUC), 10 repeats
  3924. # We compute fold-level permutation importance on the held-out fold,
  3925. # then aggregate across folds -> mean/std per feature.
  3926. # ----------------------------
  3927. skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
  3928. def cv_perm_importance_auc(estimator, X, y, n_repeats=10, seed=42):
  3929. fold_means = []
  3930. rng = np.random.RandomState(seed)
  3931. for fold, (tr, te) in enumerate(skf.split(X, y), start=1):
  3932. Xtr, Xte = X[tr], X[te]
  3933. ytr, yte = y[tr], y[te]
  3934. est = estimator
  3935. est.fit(Xtr, ytr)
  3936. pi = permutation_importance(
  3937. est, Xte, yte,
  3938. scoring="roc_auc",
  3939. n_repeats=n_repeats,
  3940. random_state=rng.randint(0, 10**9),
  3941. n_jobs=-1
  3942. )
  3943. fold_means.append(pi.importances_mean)
  3944. fold_means = np.vstack(fold_means) # (n_folds, n_features)
  3945. mean_imp = fold_means.mean(axis=0)
  3946. std_imp = fold_means.std(axis=0)
  3947. return mean_imp, std_imp
  3948. # ----------------------------
  3949. # 5) BUILD ONE WIDE TABLE: for each model -> mean, std, rank
  3950. # ----------------------------
  3951. wide = pd.DataFrame({"Feature": feature_names})
  3952. for model_name, est in models.items():
  3953. mean_imp, std_imp = cv_perm_importance_auc(est, X, y, n_repeats=10, seed=42)
  3954. # ranks: 1 = most important (largest mean)
  3955. order = np.argsort(-mean_imp)
  3956. ranks = np.empty_like(order)
  3957. ranks[order] = np.arange(1, len(feature_names) + 1)
  3958. wide[(model_name, "Mean")] = mean_imp
  3959. wide[(model_name, "SD")] = std_imp
  3960. wide[(model_name, "Rank")] = ranks
  3961. # MultiIndex columns for nice grouped header in Excel
  3962. wide.columns = pd.MultiIndex.from_tuples(
  3963. [("Feature", "")] + [(a, b) for a, b in wide.columns[1:]],
  3964. names=["Model", "Metric"]
  3965. )
  3966. # Optional: sort rows by SVM importance (or change to Avg rank)
  3967. wide_sorted = wide.copy()
  3968. wide_sorted = wide_sorted.sort_values(by=("SVM", "Mean"), ascending=False)
  3969. # Save to Excel
  3970. # out_xlsx = "permutation_importance_top3_models.xlsx"
  3971. # with pd.ExcelWriter(out_xlsx, engine="openpyxl") as writer:
  3972. # wide_sorted.to_excel(writer, sheet_name="PermImp_AllFeatures", index=False)
  3973. # print(f"Saved: {out_xlsx}")
  3974. # display(wide_sorted)
  3975. # Flatten MultiIndex columns before saving
  3976. wide_sorted_flat = wide_sorted.copy()
  3977. wide_sorted_flat.columns = [
  3978. f"{col[0]}_{col[1]}" if col[1] != "" else col[0]
  3979. for col in wide_sorted_flat.columns
  3980. ]
  3981. wide_sorted_flat.to_excel("permutation_importance_top3_models.xlsx",
  3982. sheet_name="PermImp_AllFeatures",
  3983. index=False)
  3984. print("Saved: permutation_importance_top3_models.xlsx")
  3985. import numpy as np
  3986. import matplotlib.pyplot as plt
  3987. import os
  3988. import pandas as pd
  3989. xlsx_path = os.path.join("..", "content", "permutation_importance_top3_models.xlsx")
  3990. df_raw = pd.read_excel(xlsx_path)
  3991. print("Loaded shape:", df_raw.shape)
  3992. print("Columns:", df_raw.columns.tolist())
  3993. def get_model_perm_vectors_from_wide(df, model_name, use_rank=True, normalize=True):
  3994. """
  3995. Expects columns:
  3996. Feature,
  3997. {model_name}_Mean, {model_name}_SD, {model_name}_Rank (Rank optional)
  3998. Returns:
  3999. feat_sorted (list[str]), imp_sorted (np.array[float])
  4000. """
  4001. feat_col = "Feature"
  4002. mean_col = f"{model_name}_Mean"
  4003. rank_col = f"{model_name}_Rank"
  4004. if feat_col not in df.columns:
  4005. raise ValueError(f"'{feat_col}' column not found.")
  4006. if mean_col not in df.columns:
  4007. raise ValueError(
  4008. f"'{mean_col}' column not found for model='{model_name}'. "
  4009. f"Available Mean columns: {[c for c in df.columns if c.endswith('_Mean')]}"
  4010. )
  4011. sub = df[[feat_col, mean_col] + ([rank_col] if rank_col in df.columns else [])].copy()
  4012. sub[mean_col] = pd.to_numeric(sub[mean_col], errors="coerce").fillna(0.0)
  4013. # Sort: by Rank if available & requested; otherwise by Mean desc
  4014. if use_rank and (rank_col in sub.columns):
  4015. sub[rank_col] = pd.to_numeric(sub[rank_col], errors="coerce")
  4016. sub = sub.sort_values(rank_col, ascending=True)
  4017. else:
  4018. sub = sub.sort_values(mean_col, ascending=False)
  4019. feats = sub[feat_col].astype(str).tolist()
  4020. imps = sub[mean_col].to_numpy(dtype=float)
  4021. # Clip negatives + normalize to sum=1 (common for donut)
  4022. imps = np.clip(imps, 0, None)
  4023. if normalize:
  4024. s = imps.sum()
  4025. imps = imps / s if s > 0 else np.ones_like(imps) / len(imps)
  4026. return feats, imps
  4027. # === Create variables expected by your donut code ===
  4028. svm_feat_sorted, svm_imp_sorted = get_model_perm_vectors_from_wide(df_raw, "SVM", use_rank=True)
  4029. gp_feat_sorted, gp_imp_sorted = get_model_perm_vectors_from_wide(df_raw, "GaussianProcess", use_rank=True)
  4030. bag_svm_feat_sorted, bag_svm_imp_sorted = get_model_perm_vectors_from_wide(df_raw, "BaggingSVM", use_rank=True)
  4031. print("Top SVM features:", svm_feat_sorted[:5])
  4032. print("Top GP features:", gp_feat_sorted[:5])
  4033. print("Top BaggingSVM features:", bag_svm_feat_sorted[:5])
  4034. def donut_perm_importance_topk_onring(
  4035. ax,
  4036. feat_sorted, imp_sorted,
  4037. model_name="Model",
  4038. panel_label="a)",
  4039. cmap="Blues",
  4040. top_k_labels=5,
  4041. ring_width=0.62, # ring thickness
  4042. startangle=90,
  4043. label_r=None, # radius where labels sit (mid-ring by default)
  4044. fontsize=23,
  4045. fontweight="normal",
  4046. text_color="white",
  4047. center_fontsize=28,
  4048. panel_fontsize=32,
  4049. # fading controls (more obvious)
  4050. fade_min=0.08, # lightest (end)
  4051. fade_max=1.99, # darkest (start)
  4052. fade_gamma=1.2, # higher => stronger contrast
  4053. # rotation controls (labels follow slice angle; "vertical" on slice)
  4054. rotation_offset=0, # add 90° to make it vertical relative to slice direction
  4055. flip_upright=True, # keep text readable on left half
  4056. # optional rename (e.g., Location -> L)
  4057. rename_map=None,
  4058. ):
  4059. ax.set_aspect("equal")
  4060. ax.axis("off")
  4061. # --- clip negatives + normalize ---
  4062. vals = np.asarray(imp_sorted, float)
  4063. vals = np.clip(vals, 0, None)
  4064. if vals.sum() == 0:
  4065. vals = np.ones_like(vals)
  4066. vals = vals / vals.sum()
  4067. feat_sorted = list(feat_sorted)
  4068. # --- sort by importance desc (so first k = top k) ---
  4069. order = np.argsort(vals)[::-1]
  4070. vals = vals[order]
  4071. feat_sorted = [feat_sorted[i] for i in order]
  4072. # --- fading by slice mid-angle fraction (0..1): later angle => lighter ---
  4073. cum = np.cumsum(vals)
  4074. prev = np.concatenate(([0.0], cum[:-1]))
  4075. mid_frac = 0.5 * (prev + cum) # 0..1 around circle (clockwise because counterclock=False)
  4076. fade_strength = (1.0 - mid_frac) ** fade_gamma
  4077. fade_strength = fade_min + (fade_max - fade_min) * fade_strength
  4078. cmap_obj = plt.get_cmap(cmap)
  4079. colors = [cmap_obj(f) for f in fade_strength]
  4080. wedges, _ = ax.pie(
  4081. vals,
  4082. labels=None,
  4083. startangle=startangle,
  4084. counterclock=False,
  4085. colors=colors,
  4086. wedgeprops=dict(width=ring_width, edgecolor="white", linewidth=1.5),
  4087. )
  4088. # panel label
  4089. ax.text(-0.03, 1.0, panel_label, transform=ax.transAxes,
  4090. fontsize=panel_fontsize, fontweight="bold", va="top", ha="left")
  4091. # model name in center (use \n if long)
  4092. ax.text(0, 0, model_name, ha="center", va="center",
  4093. fontsize=center_fontsize, fontweight="normal")
  4094. # label radius in middle of ring by default
  4095. if label_r is None:
  4096. inner_r = 1.0 - ring_width
  4097. label_r = 0.5 * (1.0 + inner_r)
  4098. # label only top-k, ON ring, rotated along slice direction (radial) + 90°
  4099. k = min(top_k_labels, len(wedges))
  4100. rename_map = rename_map or {}
  4101. for i in range(k):
  4102. w = wedges[i]
  4103. ang = 0.5 * (w.theta1 + w.theta2) # degrees
  4104. ang_rad = np.deg2rad(ang)
  4105. xt = label_r * np.cos(ang_rad)
  4106. yt = label_r * np.sin(ang_rad)
  4107. # label text
  4108. lbl = str(feat_sorted[i])
  4109. lbl = rename_map.get(lbl, lbl)
  4110. # rotation: follow slice angle; +90 makes it "vertical" on the slice
  4111. rot = ang + rotation_offset
  4112. # if flip_upright and (90 < (rot % 360) < 270):
  4113. # rot += 180
  4114. ax.text(
  4115. xt, yt, lbl,
  4116. ha="center", va="center",
  4117. rotation=rot, rotation_mode="anchor",
  4118. fontsize=fontsize, fontweight=fontweight,
  4119. color=text_color
  4120. )
  4121. ax.set_xlim(-1.05, 1.05)
  4122. ax.set_ylim(-1.05, 1.05)
  4123. # -----------------------------
  4124. # Example call (single figure a/b/c)
  4125. # Requires:
  4126. # svm_feat_sorted, svm_imp_sorted
  4127. # gp_feat_sorted, gp_imp_sorted
  4128. # mlp_feat_sorted, mlp_imp_sorted
  4129. # -----------------------------
  4130. plt.rcParams.update({
  4131. "font.family": "serif",
  4132. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  4133. "font.size": 18
  4134. })
  4135. RENAME = {"Location": "L"} # your request
  4136. fig, axes = plt.subplots(1, 3, figsize=(18, 6), dpi=300)
  4137. donut_perm_importance_topk_onring(
  4138. axes[0], bag_svm_feat_sorted, bag_svm_imp_sorted,
  4139. model_name="Bagging\nSVM", panel_label="a)",
  4140. cmap="Blues",
  4141. # top_k_labels=5,
  4142. # ring_width=0.58,
  4143. # fade_min=0.04, fade_max=0.995, fade_gamma=2.6,
  4144. # rotation_offset=90,
  4145. # fontsize=13,
  4146. rename_map=RENAME
  4147. )
  4148. donut_perm_importance_topk_onring(
  4149. axes[1], svm_feat_sorted, svm_imp_sorted,
  4150. model_name="SVM", panel_label="b)",
  4151. cmap="Greens",
  4152. # top_k_labels=5,
  4153. # ring_width=0.58, # wider ring
  4154. # fade_min=0.04, fade_max=0.995, fade_gamma=2.6, # stronger fading
  4155. # rotation_offset=90, # vertical-on-slice
  4156. # fontsize=13,
  4157. rename_map=RENAME
  4158. )
  4159. donut_perm_importance_topk_onring(
  4160. axes[2], gp_feat_sorted, gp_imp_sorted,
  4161. model_name="Gaussian\nProcess", panel_label="c)",
  4162. cmap="Purples",
  4163. # top_k_labels=5,
  4164. # ring_width=0.58,
  4165. # fade_min=0.04, fade_max=0.995, fade_gamma=2.6,
  4166. # rotation_offset=90,
  4167. # fontsize=13,
  4168. rename_map=RENAME
  4169. )
  4170. plt.tight_layout()
  4171. plt.savefig("perm_importance_donuts.png", dpi=300, bbox_inches="tight")
  4172. plt.savefig("perm_importance_donuts.pdf", bbox_inches="tight")
  4173. plt.show()
  4174. import numpy as np
  4175. import matplotlib.pyplot as plt
  4176. import matplotlib as mpl
  4177. from matplotlib.collections import LineCollection
  4178. import os
  4179. import pandas as pd
  4180. # =========================================================
  4181. # LOAD DATA
  4182. # =========================================================
  4183. xlsx_path = os.path.join("..", "content", "permutation_importance_top3_models.xlsx")
  4184. df_raw = pd.read_excel(xlsx_path)
  4185. print("Loaded shape:", df_raw.shape)
  4186. print("Columns:", df_raw.columns.tolist())
  4187. def get_model_perm_vectors_from_wide(df, model_name, use_rank=True, normalize=False):
  4188. """
  4189. Expects columns:
  4190. Feature,
  4191. {model_name}_Mean, {model_name}_SD, {model_name}_Rank (Rank optional)
  4192. Returns:
  4193. feat_sorted (list[str]), imp_sorted (np.array[float])
  4194. """
  4195. feat_col = "Feature"
  4196. mean_col = f"{model_name}_Mean"
  4197. rank_col = f"{model_name}_Rank"
  4198. if feat_col not in df.columns:
  4199. raise ValueError(f"'{feat_col}' column not found.")
  4200. if mean_col not in df.columns:
  4201. raise ValueError(
  4202. f"'{mean_col}' column not found for model='{model_name}'. "
  4203. f"Available Mean columns: {[c for c in df.columns if c.endswith('_Mean')]}"
  4204. )
  4205. sub = df[[feat_col, mean_col] + ([rank_col] if rank_col in df.columns else [])].copy()
  4206. sub[mean_col] = pd.to_numeric(sub[mean_col], errors="coerce").fillna(0.0)
  4207. # sort by rank if available, otherwise by mean descending
  4208. if use_rank and (rank_col in sub.columns):
  4209. sub[rank_col] = pd.to_numeric(sub[rank_col], errors="coerce")
  4210. sub = sub.sort_values(rank_col, ascending=True)
  4211. else:
  4212. sub = sub.sort_values(mean_col, ascending=False)
  4213. feats = sub[feat_col].astype(str).tolist()
  4214. imps = sub[mean_col].to_numpy(dtype=float)
  4215. # clip negatives
  4216. imps = np.clip(imps, 0, None)
  4217. # optional sum normalization
  4218. if normalize:
  4219. s = imps.sum()
  4220. imps = imps / s if s > 0 else np.ones_like(imps) / len(imps)
  4221. return feats, imps
  4222. # =========================================================
  4223. # GET MODEL DATA
  4224. # =========================================================
  4225. svm_feat_sorted, svm_imp_sorted = get_model_perm_vectors_from_wide(df_raw, "SVM", use_rank=True, normalize=False)
  4226. gp_feat_sorted, gp_imp_sorted = get_model_perm_vectors_from_wide(df_raw, "GaussianProcess", use_rank=True, normalize=False)
  4227. bag_svm_feat_sorted, bag_svm_imp_sorted = get_model_perm_vectors_from_wide(df_raw, "BaggingSVM", use_rank=True, normalize=False)
  4228. print("Top SVM features:", svm_feat_sorted[:5])
  4229. print("Top GP features:", gp_feat_sorted[:5])
  4230. print("Top BaggingSVM features:", bag_svm_feat_sorted[:5])
  4231. # =========================================================
  4232. # RADAR PLOT FUNCTION
  4233. # =========================================================
  4234. def radar_perm_importance_colored(
  4235. ax,
  4236. feat_sorted,
  4237. imp_sorted,
  4238. panel_label="a)",
  4239. model_title=None,
  4240. cmap="viridis",
  4241. rename_map=None,
  4242. fill_alpha=0.10,
  4243. line_width=3.0,
  4244. point_size=55,
  4245. n_rings=6,
  4246. show_colorbar=False,
  4247. cbar_label="Normalized importance"
  4248. ):
  4249. """
  4250. Radar chart with:
  4251. - min-max normalization to [0, 1]
  4252. - no radial tick labels
  4253. - no center model name
  4254. - values represented by color instead of text labels
  4255. """
  4256. rename_map = rename_map or {}
  4257. feats = [rename_map.get(str(f), str(f)) for f in feat_sorted]
  4258. vals = np.asarray(imp_sorted, dtype=float)
  4259. vals = np.clip(vals, 0, None)
  4260. # ---- min-max normalization to [0, 1]
  4261. vmin = vals.min()
  4262. vmax = vals.max()
  4263. if np.isclose(vmax, vmin):
  4264. vals_norm = np.zeros_like(vals)
  4265. else:
  4266. vals_norm = (vals - vmin) / (vmax - vmin)
  4267. N = len(feats)
  4268. angles = np.linspace(0, 2 * np.pi, N, endpoint=False)
  4269. # close the polygon
  4270. angles_closed = np.r_[angles, angles[0]]
  4271. vals_closed = np.r_[vals_norm, vals_norm[0]]
  4272. # ---- polar formatting
  4273. ax.set_theta_offset(np.pi / 2)
  4274. ax.set_theta_direction(-1)
  4275. ax.set_ylim(0, 1.0)
  4276. # category labels
  4277. ax.set_xticks(angles)
  4278. ax.set_xticklabels(feats, fontsize=17)
  4279. # remove radial numbers completely
  4280. ax.set_yticks(np.linspace(0, 1.0, n_rings + 1)[1:])
  4281. ax.set_yticklabels([])
  4282. # clean grid / spine
  4283. ax.grid(color="#bdbdbd", linewidth=1.0, alpha=0.8)
  4284. ax.spines["polar"].set_color("#bdbdbd")
  4285. ax.spines["polar"].set_linewidth(1.2)
  4286. # ---- fill polygon lightly
  4287. ax.fill(angles_closed, vals_closed, color=plt.get_cmap(cmap)(0.55), alpha=fill_alpha, zorder=1)
  4288. # ---- colored line segments
  4289. points = np.array([angles_closed, vals_closed]).T.reshape(-1, 1, 2)
  4290. segments = np.concatenate([points[:-1], points[1:]], axis=1)
  4291. norm = mpl.colors.Normalize(vmin=0.0, vmax=1.0)
  4292. lc = LineCollection(segments, cmap=cmap, norm=norm, linewidth=line_width)
  4293. lc.set_array(vals_norm)
  4294. lc.set_capstyle("round")
  4295. ax.add_collection(lc)
  4296. # ---- colored points
  4297. sc = ax.scatter(
  4298. angles, vals_norm,
  4299. c=vals_norm, cmap=cmap, norm=norm,
  4300. s=point_size, zorder=3,
  4301. edgecolors="white", linewidths=0.9
  4302. )
  4303. # ---- panel label
  4304. ax.text(-0.10, 1.08, panel_label, transform=ax.transAxes,
  4305. fontsize=24, fontweight="bold", ha="left", va="top")
  4306. # ---- optional small title above plot (not in center)
  4307. if model_title is not None:
  4308. ax.set_title(model_title, fontsize=20, pad=24)
  4309. # ---- optional colorbar
  4310. if show_colorbar:
  4311. cbar = plt.colorbar(sc, ax=ax, pad=0.10, fraction=0.05)
  4312. cbar.set_label(cbar_label, fontsize=16)
  4313. cbar.ax.tick_params(labelsize=13)
  4314. # =========================================================
  4315. # STYLE
  4316. # =========================================================
  4317. plt.rcParams.update({
  4318. "font.family": "serif",
  4319. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  4320. "font.size": 16
  4321. })
  4322. RENAME = {"Location": "L"}
  4323. # =========================================================
  4324. # FIGURE
  4325. # =========================================================
  4326. fig, axes = plt.subplots(
  4327. 1, 3,
  4328. figsize=(20, 7),
  4329. dpi=300,
  4330. subplot_kw=dict(polar=True)
  4331. )
  4332. radar_perm_importance_colored(
  4333. axes[0],
  4334. bag_svm_feat_sorted, bag_svm_imp_sorted,
  4335. panel_label="a)",
  4336. model_title="Bagging SVM",
  4337. cmap="Blues",
  4338. rename_map=RENAME,
  4339. show_colorbar=False
  4340. )
  4341. radar_perm_importance_colored(
  4342. axes[1],
  4343. svm_feat_sorted, svm_imp_sorted,
  4344. panel_label="b)",
  4345. model_title="SVM",
  4346. cmap="Greens",
  4347. rename_map=RENAME,
  4348. show_colorbar=False
  4349. )
  4350. radar_perm_importance_colored(
  4351. axes[2],
  4352. gp_feat_sorted, gp_imp_sorted,
  4353. panel_label="c)",
  4354. model_title="Gaussian Process",
  4355. cmap="Purples",
  4356. rename_map=RENAME,
  4357. show_colorbar=True # show one colorbar only on the last panel
  4358. )
  4359. plt.tight_layout()
  4360. plt.savefig("perm_importance_radar_colored.png", dpi=300, bbox_inches="tight")
  4361. plt.savefig("perm_importance_radar_colored.pdf", bbox_inches="tight")
  4362. plt.show()
  4363. import numpy as np
  4364. import matplotlib.pyplot as plt
  4365. import os
  4366. import pandas as pd
  4367. xlsx_path = os.path.join("..", "content","Feature Importance Comparison.xlsx")
  4368. df_raw = pd.read_excel(xlsx_path)
  4369. print("Loaded shape:", df_raw.shape)
  4370. print("Columns:", df_raw.columns.tolist())
  4371. def get_feature_vectors_from_two_cols(df, feat_col, val_col, normalize=True):
  4372. """
  4373. Expects two columns:
  4374. feat_col, val_col
  4375. Returns:
  4376. feat_sorted (list[str]), imp_sorted (np.array[float])
  4377. """
  4378. sub = df[[feat_col, val_col]].copy()
  4379. sub.columns = ["Feature", "Importance"]
  4380. sub = sub.dropna(subset=["Feature"])
  4381. sub["Importance"] = pd.to_numeric(sub["Importance"], errors="coerce").fillna(0.0)
  4382. feats = sub["Feature"].astype(str).tolist()
  4383. imps = sub["Importance"].to_numpy(dtype=float)
  4384. # Clip negatives + normalize to sum=1 (same idea as your original code)
  4385. imps = np.clip(imps, 0, None)
  4386. if normalize:
  4387. s = imps.sum()
  4388. imps = imps / s if s > 0 else np.ones_like(imps) / len(imps)
  4389. return feats, imps
  4390. # === Load data from the new file ===
  4391. # Adjust these column names only if your file prints different names
  4392. morph_feat_sorted, morph_imp_sorted = get_feature_vectors_from_two_cols(
  4393. df_raw, "Unnamed: 0", "MLP", normalize=True
  4394. )
  4395. hemo_feat_sorted, hemo_imp_sorted = get_feature_vectors_from_two_cols(
  4396. df_raw, "Unnamed: 3", "RF", normalize=True
  4397. )
  4398. comb_feat_sorted, comb_imp_sorted = get_feature_vectors_from_two_cols(
  4399. df_raw, "Unnamed: 6", "Bagging SVM", normalize=True
  4400. )
  4401. print("Top Morphology features:", morph_feat_sorted[:5])
  4402. print("Top Hemodynamics features:", hemo_feat_sorted[:5])
  4403. print("Top Combined features:", comb_feat_sorted[:5])
  4404. def donut_perm_importance_topk_onring(
  4405. ax,
  4406. feat_sorted, imp_sorted,
  4407. model_name="Model",
  4408. panel_label="a)",
  4409. cmap="Blues",
  4410. top_k_labels=5,
  4411. ring_width=0.62, # ring thickness
  4412. startangle=90,
  4413. label_r=None, # radius where labels sit (mid-ring by default)
  4414. fontsize=23,
  4415. fontweight="normal",
  4416. text_color="white",
  4417. center_fontsize=28,
  4418. panel_fontsize=32,
  4419. # fading controls (more obvious)
  4420. fade_min=0.08, # lightest (end)
  4421. fade_max=1.99, # darkest (start)
  4422. fade_gamma=1.2, # higher => stronger contrast
  4423. # rotation controls (labels follow slice angle; "vertical" on slice)
  4424. rotation_offset=0, # add 90° to make it vertical relative to slice direction
  4425. flip_upright=True, # keep text readable on left half
  4426. # optional rename (e.g., Location -> L)
  4427. rename_map=None,
  4428. ):
  4429. ax.set_aspect("equal")
  4430. ax.axis("off")
  4431. # --- clip negatives + normalize ---
  4432. vals = np.asarray(imp_sorted, float)
  4433. vals = np.clip(vals, 0, None)
  4434. if vals.sum() == 0:
  4435. vals = np.ones_like(vals)
  4436. vals = vals / vals.sum()
  4437. feat_sorted = list(feat_sorted)
  4438. # --- sort by importance desc (so first k = top k) ---
  4439. order = np.argsort(vals)[::-1]
  4440. vals = vals[order]
  4441. feat_sorted = [feat_sorted[i] for i in order]
  4442. # --- fading by slice mid-angle fraction (0..1): later angle => lighter ---
  4443. cum = np.cumsum(vals)
  4444. prev = np.concatenate(([0.0], cum[:-1]))
  4445. mid_frac = 0.5 * (prev + cum) # 0..1 around circle (clockwise because counterclock=False)
  4446. fade_strength = (1.0 - mid_frac) ** fade_gamma
  4447. fade_strength = fade_min + (fade_max - fade_min) * fade_strength
  4448. cmap_obj = plt.get_cmap(cmap)
  4449. colors = [cmap_obj(f) for f in fade_strength]
  4450. wedges, _ = ax.pie(
  4451. vals,
  4452. labels=None,
  4453. startangle=startangle,
  4454. counterclock=False,
  4455. colors=colors,
  4456. wedgeprops=dict(width=ring_width, edgecolor="white", linewidth=1.5),
  4457. )
  4458. # panel label
  4459. ax.text(-0.03, 1.0, panel_label, transform=ax.transAxes,
  4460. fontsize=panel_fontsize, fontweight="bold", va="top", ha="left")
  4461. # center text
  4462. ax.text(0, 0, model_name, ha="center", va="center",
  4463. fontsize=center_fontsize, fontweight="normal")
  4464. # label radius in middle of ring by default
  4465. if label_r is None:
  4466. inner_r = 1.0 - ring_width
  4467. label_r = 0.5 * (1.0 + inner_r)
  4468. # label only top-k, ON ring, rotated along slice direction (radial) + 90°
  4469. k = min(top_k_labels, len(wedges))
  4470. rename_map = rename_map or {}
  4471. for i in range(k):
  4472. w = wedges[i]
  4473. ang = 0.5 * (w.theta1 + w.theta2) # degrees
  4474. ang_rad = np.deg2rad(ang)
  4475. xt = label_r * np.cos(ang_rad)
  4476. yt = label_r * np.sin(ang_rad)
  4477. # label text
  4478. lbl = str(feat_sorted[i])
  4479. lbl = rename_map.get(lbl, lbl)
  4480. rot = ang + rotation_offset
  4481. ax.text(
  4482. xt, yt, lbl,
  4483. ha="center", va="center",
  4484. rotation=rot, rotation_mode="anchor",
  4485. fontsize=fontsize, fontweight=fontweight,
  4486. color=text_color
  4487. )
  4488. ax.set_xlim(-1.05, 1.05)
  4489. ax.set_ylim(-1.05, 1.05)
  4490. # -----------------------------
  4491. # Plot
  4492. # -----------------------------
  4493. plt.rcParams.update({
  4494. "font.family": "serif",
  4495. "font.serif": ["Times New Roman", "Times", "DejaVu Serif"],
  4496. "font.size": 18
  4497. })
  4498. RENAME = {"Location": "L"}
  4499. fig, axes = plt.subplots(1, 3, figsize=(18, 6), dpi=300)
  4500. donut_perm_importance_topk_onring(
  4501. axes[0], morph_feat_sorted, morph_imp_sorted,
  4502. model_name="Morphology", panel_label="a)",
  4503. cmap="Oranges",
  4504. rename_map=RENAME
  4505. )
  4506. donut_perm_importance_topk_onring(
  4507. axes[1], hemo_feat_sorted, hemo_imp_sorted,
  4508. model_name="Hemodynamics", panel_label="b)",
  4509. cmap="Blues",
  4510. rename_map=RENAME
  4511. )
  4512. donut_perm_importance_topk_onring(
  4513. axes[2], comb_feat_sorted, comb_imp_sorted,
  4514. model_name="Morphology &\nHemodynamics", panel_label="c)",
  4515. cmap="Greys",
  4516. rename_map=RENAME
  4517. )
  4518. plt.tight_layout()
  4519. plt.savefig("feature_importance_comparison_donuts.png", dpi=300, bbox_inches="tight")
  4520. plt.savefig("feature_importance_comparison_donuts.pdf", bbox_inches="tight")
  4521. plt.show()
  4522. #Use results from different papers
  4523. fig, axes = plt.subplots(1, 3, figsize=(13, 5), dpi=300, sharex=False)
  4524. panel_labels = ["a)", "b)", "c)"]
  4525. for ax, data, panel in zip(
  4526. axes,
  4527. [morph_top, hemo_top, comb_top],
  4528. panel_labels
  4529. ):
  4530. y = np.arange(len(data))
  4531. ax.hlines(y, 0, data["Importance"], color=data["Color"], linewidth=2.5)
  4532. ax.scatter(data["Importance"], y, color=data["Color"], s=80)
  4533. ax.set_yticks(y)
  4534. ax.set_yticks(y)
  4535. ax.set_yticklabels(data["Feature"])
  4536. ax.tick_params(axis="y", labelsize=18) # bigger y labels
  4537. # === X axis settings ===
  4538. max_val = data["Importance"].max()
  4539. ax.set_xlim(0, max_val * 1.05) # ensure 0 aligns with vertical axis
  4540. # only 3 ticks including zero
  4541. xticks = np.linspace(0, max_val, 3)
  4542. ax.set_xticks(xticks)
  4543. ax.set_xticklabels([f"{x:.2f}" for x in xticks], fontsize=16)
  4544. ax.grid(axis="x", alpha=0.25)
  4545. # remove top and right spines
  4546. ax.spines["top"].set_visible(False)
  4547. ax.spines["right"].set_visible(False)
  4548. # make left spine exactly the zero line
  4549. ax.spines["left"].set_position(("data", 0))
  4550. # panel label
  4551. ax.text(
  4552. -0.6, 1.05, panel,
  4553. transform=ax.transAxes,
  4554. ha="left", va="bottom",
  4555. fontsize=23, fontweight="bold"
  4556. )
  4557. plt.subplots_adjust(wspace=0.75)
  4558. plt.savefig("feature_importance_comparison.png", dpi=300, bbox_inches="tight")
  4559. plt.savefig("feature_importance_comparison.pdf", bbox_inches="tight")
  4560. plt.show()
  4561. # ============================================================
  4562. # BAGGING SVM SCENARIO COMPARISON
  4563. # Morphology-only vs Hemodynamic-only vs Combined
  4564. #
  4565. # Scenario definitions after dropping "Vessel ID":
  4566. # Clinical columns: first 3 columns
  4567. # Morphology columns: next 35 columns
  4568. # Hemodynamic columns: next 17 columns
  4569. # Label column: last column, "Status"
  4570. #
  4571. # Outputs:
  4572. # 1) 5-fold CV performance on TRAIN90
  4573. # 2) Held-out TEST10 performance
  4574. # 3) Permutation importance table for all scenarios
  4575. # 4) Three-panel feature-importance figure
  4576. # ============================================================
  4577. import os
  4578. import numpy as np
  4579. import pandas as pd
  4580. import matplotlib.pyplot as plt
  4581. from sklearn.pipeline import Pipeline
  4582. from sklearn.preprocessing import StandardScaler
  4583. from sklearn.model_selection import train_test_split, StratifiedKFold
  4584. from sklearn.metrics import (
  4585. roc_auc_score, accuracy_score, precision_score,
  4586. recall_score, f1_score, confusion_matrix
  4587. )
  4588. from sklearn.svm import SVC
  4589. from sklearn.ensemble import BaggingClassifier
  4590. from sklearn.base import clone
  4591. from sklearn.inspection import permutation_importance
  4592. from sklearn.utils.class_weight import compute_class_weight
  4593. # ----------------------------
  4594. # CONFIG
  4595. # ----------------------------
  4596. xlsx_path = os.path.join("..", "content", "ClassificationV2.xlsx")
  4597. # If you are running locally and the file is in the same folder, use:
  4598. # xlsx_path = "ClassificationV2.xlsx"
  4599. RANDOM_STATE_SPLIT = 8
  4600. CV_FOLDS = 5
  4601. THR = 0.5
  4602. N_REPEATS_PI = 10
  4603. TOP_N = 8 # change this if you want more or fewer features in each panel
  4604. # ----------------------------
  4605. # 1) LOAD AND PREPROCESS DATA
  4606. # ----------------------------
  4607. df = pd.read_excel(xlsx_path)
  4608. mapping_Sex = {"Male": 0, "Female": 1}
  4609. mapping_Irregularity = {"R": 0, "I": 1}
  4610. mapping_Kind = {"L": 0, "B": 1}
  4611. mapping_Location = {
  4612. "MCA": 0, "ICA": 1, "ACA": 2, "PCA": 3,
  4613. "VA": 4, "BA": 4, "SCA": 5
  4614. }
  4615. mapping_Stability = {"stable": 0, "unstable": 1}
  4616. mapping_Complexity = {"simple": 0, "complex": 1}
  4617. mapping_Status = {"unruptured": 0, "ruptured": 1}
  4618. df = df.replace({
  4619. "Sex": mapping_Sex,
  4620. "I": mapping_Irregularity,
  4621. "LB": mapping_Kind,
  4622. "Location": mapping_Location,
  4623. "FS": mapping_Stability,
  4624. "FC": mapping_Complexity,
  4625. "Status": mapping_Status
  4626. })
  4627. if "Vessel ID" in df.columns:
  4628. df = df.drop(labels="Vessel ID", axis=1)
  4629. if "Status" not in df.columns:
  4630. raise ValueError("Column 'Status' was not found. Please check the Excel file.")
  4631. # Make sure all remaining values are numeric
  4632. df = df.apply(pd.to_numeric)
  4633. # ----------------------------
  4634. # 2) DEFINE FEATURE GROUPS
  4635. # ----------------------------
  4636. all_columns = list(df.columns)
  4637. clinical_cols = all_columns[0:3]
  4638. morphology_cols = all_columns[3:38] # next 35 columns
  4639. hemodynamic_cols = all_columns[38:55] # next 17 columns
  4640. label_col = all_columns[-1]
  4641. print("Clinical columns:", clinical_cols)
  4642. print("Number of morphology columns:", len(morphology_cols))
  4643. print("Number of hemodynamic columns:", len(hemodynamic_cols))
  4644. print("Label column:", label_col)
  4645. if label_col != "Status":
  4646. raise ValueError(
  4647. f"The last column is expected to be 'Status', but found '{label_col}'. "
  4648. "Please check the dataset column order."
  4649. )
  4650. scenarios = {
  4651. "Morphology-only": clinical_cols + morphology_cols,
  4652. "Hemodynamic-only": clinical_cols + hemodynamic_cols,
  4653. "Combined": clinical_cols + morphology_cols + hemodynamic_cols
  4654. }
  4655. y = df["Status"].to_numpy(dtype=int)
  4656. # ----------------------------
  4657. # 3) HELPER FUNCTIONS
  4658. # ----------------------------
  4659. def specificity(y_true, y_pred):
  4660. tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel()
  4661. return tn / (tn + fp) if (tn + fp) else 0.0
  4662. def metrics_from_prob(y_true, prob, thr=0.5):
  4663. pred = (prob >= thr).astype(int)
  4664. return {
  4665. "AUC": roc_auc_score(y_true, prob),
  4666. "ACC": accuracy_score(y_true, pred),
  4667. "Precision": precision_score(y_true, pred, zero_division=0),
  4668. "Recall": recall_score(y_true, pred, zero_division=0),
  4669. "F1": f1_score(y_true, pred, zero_division=0),
  4670. "Specificity": specificity(y_true, pred),
  4671. }
  4672. def get_probabilities(estimator, X_eval):
  4673. if hasattr(estimator, "predict_proba"):
  4674. return estimator.predict_proba(X_eval)[:, 1]
  4675. if hasattr(estimator, "decision_function"):
  4676. s = estimator.decision_function(X_eval)
  4677. return (s - s.min()) / (s.max() - s.min() + 1e-12)
  4678. return estimator.predict(X_eval).astype(float)
  4679. def make_sample_weight_balanced(y_local):
  4680. y_local = np.asarray(y_local).astype(int).ravel()
  4681. n = len(y_local)
  4682. n0 = np.sum(y_local == 0)
  4683. n1 = np.sum(y_local == 1)
  4684. w0 = n / (2 * n0) if n0 else 1.0
  4685. w1 = n / (2 * n1) if n1 else 1.0
  4686. return np.where(y_local == 0, w0, w1).astype(float)
  4687. def make_bagging_svm(n_features):
  4688. """
  4689. Same Bagging SVM setup as your original code.
  4690. max_features is adjusted only when the scenario has fewer than 54 features.
  4691. """
  4692. return Pipeline([
  4693. ("scaler", StandardScaler()),
  4694. ("clf", BaggingClassifier(
  4695. estimator=SVC(
  4696. C=80,
  4697. gamma=0.002,
  4698. probability=True,
  4699. kernel="rbf"
  4700. ),
  4701. n_estimators=100,
  4702. bootstrap=True,
  4703. max_samples=0.9,
  4704. max_features=min(54, n_features),
  4705. random_state=42
  4706. ))
  4707. ])
  4708. # ----------------------------
  4709. # 4) TRAIN/EVALUATE BAGGING SVM FOR EACH SCENARIO
  4710. # ----------------------------
  4711. cv_rows = []
  4712. test_rows = []
  4713. fitted_models = {}
  4714. split_data = {}
  4715. for scenario_name, feature_cols in scenarios.items():
  4716. print("\n" + "=" * 70)
  4717. print(f"Scenario: {scenario_name}")
  4718. print(f"Number of features: {len(feature_cols)}")
  4719. print("=" * 70)
  4720. X = df[feature_cols].to_numpy(dtype=np.float64)
  4721. X_train90, X_test10, y_train90, y_test10 = train_test_split(
  4722. X,
  4723. y,
  4724. test_size=0.10,
  4725. random_state=RANDOM_STATE_SPLIT,
  4726. stratify=y
  4727. )
  4728. print("Train90 shape:", X_train90.shape, "Test10 shape:", X_test10.shape)
  4729. print("Train class counts:", np.bincount(y_train90))
  4730. print("Test class counts:", np.bincount(y_test10))
  4731. split_data[scenario_name] = {
  4732. "feature_cols": feature_cols,
  4733. "X_train90": X_train90,
  4734. "X_test10": X_test10,
  4735. "y_train90": y_train90,
  4736. "y_test10": y_test10
  4737. }
  4738. # ----------------------------
  4739. # 5-fold CV on TRAIN90 only
  4740. # ----------------------------
  4741. skf = StratifiedKFold(
  4742. n_splits=CV_FOLDS,
  4743. shuffle=True,
  4744. random_state=42
  4745. )
  4746. fold_metrics = []
  4747. for fold, (tr_idx, va_idx) in enumerate(skf.split(X_train90, y_train90), start=1):
  4748. Xtr, Xva = X_train90[tr_idx], X_train90[va_idx]
  4749. ytr, yva = y_train90[tr_idx], y_train90[va_idx]
  4750. est = make_bagging_svm(n_features=Xtr.shape[1])
  4751. sw_tr = make_sample_weight_balanced(ytr)
  4752. est.fit(Xtr, ytr, clf__sample_weight=sw_tr)
  4753. prob = get_probabilities(est, Xva)
  4754. m = metrics_from_prob(yva, prob, thr=THR)
  4755. fold_metrics.append(m)
  4756. df_cv = pd.DataFrame(fold_metrics)
  4757. cv_rows.append({
  4758. "Scenario": scenario_name,
  4759. "Features": len(feature_cols),
  4760. "AUC": f"{df_cv['AUC'].mean():.3f} ± {df_cv['AUC'].std(ddof=0):.3f}",
  4761. "ACC": f"{df_cv['ACC'].mean():.3f} ± {df_cv['ACC'].std(ddof=0):.3f}",
  4762. "Precision": f"{df_cv['Precision'].mean():.3f} ± {df_cv['Precision'].std(ddof=0):.3f}",
  4763. "Recall": f"{df_cv['Recall'].mean():.3f} ± {df_cv['Recall'].std(ddof=0):.3f}",
  4764. "F1": f"{df_cv['F1'].mean():.3f} ± {df_cv['F1'].std(ddof=0):.3f}",
  4765. "Specificity": f"{df_cv['Specificity'].mean():.3f} ± {df_cv['Specificity'].std(ddof=0):.3f}",
  4766. })
  4767. # ----------------------------
  4768. # Final fit on TRAIN90, evaluate once on TEST10
  4769. # ----------------------------
  4770. final_est = make_bagging_svm(n_features=X_train90.shape[1])
  4771. sw_train90 = make_sample_weight_balanced(y_train90)
  4772. final_est.fit(X_train90, y_train90, clf__sample_weight=sw_train90)
  4773. prob_test = get_probabilities(final_est, X_test10)
  4774. test_metrics = metrics_from_prob(y_test10, prob_test, thr=THR)
  4775. test_metrics["Scenario"] = scenario_name
  4776. test_metrics["Features"] = len(feature_cols)
  4777. test_rows.append(test_metrics)
  4778. fitted_models[scenario_name] = final_est
  4779. # ----------------------------
  4780. # 5) PERFORMANCE TABLES
  4781. # ----------------------------
  4782. cv_table = pd.DataFrame(cv_rows)
  4783. test_table = pd.DataFrame(test_rows)
  4784. test_table = test_table[
  4785. ["Scenario", "Features", "AUC", "ACC", "Precision", "Recall", "F1", "Specificity"]
  4786. ].sort_values("AUC", ascending=False).reset_index(drop=True)
  4787. print("\n=== 5-fold CV on TRAIN90: Bagging SVM only ===")
  4788. display(cv_table)
  4789. print("\n=== Held-out TEST10: Bagging SVM only ===")
  4790. display(test_table.round(3))
  4791. # Save performance tables
  4792. with pd.ExcelWriter("bagging_svm_scenario_performance.xlsx", engine="openpyxl") as writer:
  4793. cv_table.to_excel(writer, sheet_name="CV_TRAIN90", index=False)
  4794. test_table.round(4).to_excel(writer, sheet_name="TEST10", index=False)
  4795. print("\nSaved: bagging_svm_scenario_performance.xlsx")
  4796. # ----------------------------
  4797. # 6) CV PERMUTATION IMPORTANCE FOR EACH SCENARIO
  4798. # ----------------------------
  4799. def cv_perm_importance_auc_bagging_svm(X, y, feature_cols, n_repeats=10, seed=42):
  4800. """
  4801. Computes fold-level permutation importance on held-out CV folds.
  4802. Aggregates mean and SD across folds.
  4803. Uses Bagging SVM with balanced sample weights.
  4804. """
  4805. skf = StratifiedKFold(
  4806. n_splits=CV_FOLDS,
  4807. shuffle=True,
  4808. random_state=42
  4809. )
  4810. fold_means = []
  4811. rng = np.random.RandomState(seed)
  4812. for fold, (tr_idx, te_idx) in enumerate(skf.split(X, y), start=1):
  4813. Xtr, Xte = X[tr_idx], X[te_idx]
  4814. ytr, yte = y[tr_idx], y[te_idx]
  4815. est = make_bagging_svm(n_features=Xtr.shape[1])
  4816. sw_tr = make_sample_weight_balanced(ytr)
  4817. est.fit(Xtr, ytr, clf__sample_weight=sw_tr)
  4818. pi = permutation_importance(
  4819. est,
  4820. Xte,
  4821. yte,
  4822. scoring="roc_auc",
  4823. n_repeats=n_repeats,
  4824. random_state=rng.randint(0, 10**9),
  4825. n_jobs=-1
  4826. )
  4827. fold_means.append(pi.importances_mean)
  4828. fold_means = np.vstack(fold_means)
  4829. mean_imp = fold_means.mean(axis=0)
  4830. std_imp = fold_means.std(axis=0)
  4831. out = pd.DataFrame({
  4832. "Feature": feature_cols,
  4833. "Importance_Mean": mean_imp,
  4834. "Importance_SD": std_imp
  4835. })
  4836. out["Rank"] = out["Importance_Mean"].rank(
  4837. method="min",
  4838. ascending=False
  4839. ).astype(int)
  4840. out = out.sort_values("Importance_Mean", ascending=False).reset_index(drop=True)
  4841. return out
  4842. perm_tables = {}
  4843. for scenario_name, feature_cols in scenarios.items():
  4844. X = df[feature_cols].to_numpy(dtype=np.float64)
  4845. perm_tables[scenario_name] = cv_perm_importance_auc_bagging_svm(
  4846. X=X,
  4847. y=y,
  4848. feature_cols=feature_cols,
  4849. n_repeats=N_REPEATS_PI,
  4850. seed=42
  4851. )
  4852. # Save permutation importance tables
  4853. with pd.ExcelWriter("bagging_svm_scenario_permutation_importance.xlsx", engine="openpyxl") as writer:
  4854. for scenario_name, tab in perm_tables.items():
  4855. sheet_name = scenario_name.replace("-", "_")[:31]
  4856. tab.to_excel(writer, sheet_name=sheet_name, index=False)
  4857. print("Saved: bagging_svm_scenario_permutation_importance.xlsx")
  4858. # ----------------------------
  4859. # 7) PREPARE TOP FEATURES FOR FIGURE
  4860. # ----------------------------
  4861. def feature_group_color(feature):
  4862. if feature in clinical_cols:
  4863. return "#4D4D4D" # clinical
  4864. elif feature in morphology_cols:
  4865. return "#1F77B4" # morphology
  4866. elif feature in hemodynamic_cols:
  4867. return "#D62728" # hemodynamic
  4868. else:
  4869. return "#7F7F7F"
  4870. plot_data = {}
  4871. for scenario_name, tab in perm_tables.items():
  4872. top = tab.head(TOP_N).copy()
  4873. # Sort ascending so the largest value appears at the top of the horizontal plot
  4874. top = top.sort_values("Importance_Mean", ascending=True).reset_index(drop=True)
  4875. top["Color"] = top["Feature"].apply(feature_group_color)
  4876. plot_data[scenario_name] = top
  4877. # ----------------------------
  4878. # 8) THREE-PANEL FEATURE IMPORTANCE FIGURE
  4879. # ----------------------------
  4880. fig, axes = plt.subplots(
  4881. 1, 3,
  4882. figsize=(13, 5),
  4883. dpi=300,
  4884. sharex=False
  4885. )
  4886. panel_labels = ["a)", "b)", "c)"]
  4887. scenario_order = ["Morphology-only", "Hemodynamic-only", "Combined"]
  4888. for ax, scenario_name, panel in zip(axes, scenario_order, panel_labels):
  4889. data = plot_data[scenario_name]
  4890. y_pos = np.arange(len(data))
  4891. ax.hlines(
  4892. y_pos,
  4893. 0,
  4894. data["Importance_Mean"],
  4895. color=data["Color"],
  4896. linewidth=2.5
  4897. )
  4898. ax.scatter(
  4899. data["Importance_Mean"],
  4900. y_pos,
  4901. color=data["Color"],
  4902. s=80
  4903. )
  4904. ax.set_yticks(y_pos)
  4905. ax.set_yticklabels(data["Feature"])
  4906. ax.tick_params(axis="y", labelsize=18)
  4907. max_val = data["Importance_Mean"].max()
  4908. # Protect against zero or negative max importance values
  4909. if max_val <= 0:

aneurysm_classification.py at commit dc01e05, no license · at the source

Overview

Authors: Mostafa Zakeri1, Amirhossein Atef1, Mohammad Aziznia1, Azadeh Jafari1
ORCID iDs: Azadeh Jafari
  1. School of Mechanical Engineering, College of Engineering, University of Tehran, Tehran, Iran
Institutions: University of Tehran (Iran)
Journal: PloS one, volume 21, issue 10, article e0360923
Dates: received 30 July 2026; accepted 2 October 2026; published online 30 October 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1371/journal.pone.0360923 · PMID 42962059 · PMCID PMC13722296 · OpenAlex W7224775446
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: human (organism), stroke (population), clinical / translational (subfield)
Methods: Statistics, Smoothing, state filtering, decompositions, Machine learning, Spectral & time-frequency
MeSH: Aneurysm, Ruptured*, Hemodynamics*, Intracranial Aneurysm*, Machine Learning*, Classification Algorithms, Clustering Algorithms, Humans, Predictive Learning Models, ROC Curve (* major topic)
Topic: Intracranial Aneurysms: Treatment and Complications (Clinical Neurology, Medicine), according to OpenAlex
Citations: not cited yet (Europe PMC); 51 references in the paper

Abstract

Accurate assessment of cerebral aneurysm rupture status remains a critical challenge in neurovascular research due to the complex interplay between geometric, hemodynamic, and clinical factors. In this study, we developed and evaluated a machine-learning framework for retrospective rupture-status classification in 708 patient-specific aneurysms by integrating 55 predictors: three clinical variables, 35 morphological descriptors, and 17 hemodynamic parameters derived from computational fluid dynamics (CFD) simulations. Blood flow was modeled as incompressible laminar flow with a non-Newtonian Carreau–Yasuda viscosity model to capture shear-thinning behavior in cerebral arteries. We compared 13 supervised classifiers, 10 unsupervised clustering approaches, and two neural-network models. Model training and hyperparameter optimization were performed using stratified five-fold cross-validation within a 90/10 training–testing split, with performance evaluated using ROC–AUC, precision, recall, specificity, and F1-score. Among the evaluated models, BaggingSVM achieved the strongest discrimination on the independent test set (ROC–AUC = 0.78), followed by SVM (0.75) and Gaussian Process (0.75). A scenario comparison using BaggingSVM showed that the combined feature set improved classification performance (AUC = 0.78) compared with clinical+morphological features (AUC = 0.76) and clinical+hemodynamical features (AUC = 0.71) settings. Sensitivity-oriented threshold tuning increased recall for ruptured aneurysm detection, reaching values up to 0.96 for BaggingSVM. Permutation feature importance analysis revealed consistent dominance of ellipticity index, low shear area ratio (LSAR), aneurysm location, irregularity and aneurysm angle across models. a small subset of variables contributes disproportionately to rupture-status classification, although the reduced-feature analysis indicates that lower-ranked predictors collectively provide additional discriminatory information. The proposed framework demonstrates the value of integrating physiologically informed hemodynamic modeling with machine learning to improve data-driven rupture status assessment and provides a scalable foundation for future clinical decision-support studies in cerebrovascular diagnostics.

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

mstf229/Cerebral-Aneurysm-Rupture-Prediction

License: none: the authors keep all their rights
State: the link answers, verified on 1 November 2026
Evidence: files inventoried
Commit: dc01e05b467217fd1ea82a4aab55e7fee23f32e8, 5 October 2026
Languages: Jupyter (1), Python (1)
Size: 4 files, 2 scripts
Software Heritage: not archived
Found in: “Data Availability”
Holds: README, 1 notebook
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Keras (2 files), Matplotlib (2 files), NumPy (2 files), pandas (2 files), scikit-learn (2 files), SciPy (2 files), TensorFlow (2 files), XGBoost (2 files), UMAP (1 file)
Availability: 1 check, the latest on 1 November 2026: the link answers
  • 1 November 2026: the link answers
3 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;
  • 2 scripts, each with its path and the digest of its content;
  • 19 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 original aneurysm geometries and associated clinical and morphological data were obtained from the AneuX morphology database, an open-access multicentric database combining data from the AneuX, @neurIST, and Aneurisk projects. The source data are available from Zenodo at https://doi.org/10.5281/zenodo.6678442. The author-generated derived variables and machine-learning analysis code underlying the findings of this study are publicly available in the Cerebral Aneurysm Rupture Prediction repository: https://github.com/mstf229/Cerebral-Aneurysm-Rupture-Prediction. The repository provides case identifiers allowing the derived variables to be linked to the corresponding publicly available AneuX source records.

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, 4 authors, 9 MeSH terms, 46 references.

Cite

This paper

Zakeri, M., Atef, A., Aziznia, M., & Jafari, A. (2026). An integrated machine learning framework for cerebral aneurysm rupture status classification using clinical, morphological, and hemodynamic features. PloS one, 21(10), e0360923. https://doi.org/10.1371/journal.pone.0360923

BibTeX

@article{zakeri2026integrated,
author = {Zakeri, Mostafa and Atef, Amirhossein and Aziznia, Mohammad and Jafari, Azadeh},
title = {{An integrated machine learning framework for cerebral aneurysm rupture status classification using clinical, morphological, and hemodynamic features}},
journal = {PloS one},
year = {2026},
month = oct,
volume = {21},
number = {10},
pages = {e0360923},
publisher = {PLOS},
issn = {1932-6203},
doi = {10.1371/journal.pone.0360923},
url = {https://doi.org/10.1371/journal.pone.0360923},
pmid = {42962059},
pmcid = {PMC13722296}
}

RIS

TY - JOUR
AU - Zakeri, Mostafa
AU - Atef, Amirhossein
AU - Aziznia, Mohammad
AU - Jafari, Azadeh
TI - An integrated machine learning framework for cerebral aneurysm rupture status classification using clinical, morphological, and hemodynamic features
T2 - PloS one
J2 - PLoS One
PY - 2026
DA - 2026/10/30
VL - 21
IS - 10
SP - e0360923
SN - 1932-6203
PB - PLOS
DO - 10.1371/journal.pone.0360923
UR - https://doi.org/10.1371/journal.pone.0360923
LA - en
ER -

CSL-JSON

{
"id": "10.1371/journal.pone.0360923",
"type": "article-journal",
"title": "An integrated machine learning framework for cerebral aneurysm rupture status classification using clinical, morphological, and hemodynamic features",
"container-title": "PloS one",
"author": [
{
"family": "Zakeri",
"given": "Mostafa"
},
{
"family": "Atef",
"given": "Amirhossein"
},
{
"family": "Aziznia",
"given": "Mohammad"
},
{
"family": "Jafari",
"given": "Azadeh"
}
],
"container-title-short": "PLoS One",
"volume": "21",
"issue": "10",
"page": "e0360923",
"DOI": "10.1371/journal.pone.0360923",
"PMID": "42962059",
"PMCID": "PMC13722296",
"ISSN": "1932-6203",
"publisher": "PLOS",
"URL": "https://doi.org/10.1371/journal.pone.0360923",
"language": "en",
"issued": {
"date-parts": [
[
2026,
10,
30
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1016/j.patter.2023.100709 [code]
A deep-learning method for the end-to-end prediction of intracranial aneurysm rupture risk.
Journal: Patterns (New York, N.Y.)
In common: Keras, TensorFlow, scikit-learn, 4 other tools, stroke, 4 references
[2] doi:10.7717/peerj.19393 [code]
Deep learning-based fine-grained assessment of aneurysm wall characteristics using 4D-CT angiography.
Journal: PeerJ
In common: Keras, TensorFlow, scikit-learn, 4 other tools, stroke, clinical / translational, 1 reference
[3] doi:10.1007/s12021-022-09597-0 [code]
Towards Automated Brain Aneurysm Detection in TOF-MRA: Open Data, Weak Labels, and Anatomical Knowledge.
Journal: Neuroinformatics
In common: TensorFlow, scikit-learn, pandas, 3 other tools, stroke, 2 references
[4] doi:10.21037/qims-24-1269 [code]
Deep learning for the detection of moyamoya angiopathy using T2-weighted images: a multicenter study.
Journal: Quantitative imaging in medicine and surgery
In common: Keras, TensorFlow, scikit-learn, 3 other tools, stroke, clinical / translational, 1 reference
[5] doi:10.3390/s25247415 [code]
Decision Support Systems in Neurosurgery: Current Applications and Future Directions.
Journal: Sensors (Basel, Switzerland)
In common: UMAP, scikit-learn, pandas, 3 other tools, 1 reference
[6] doi:10.1038/s41467-024-53550-5 [code]
Self-supervised learning for accurately modelling hierarchical evolutionary patterns of cerebrovasculature.
Journal: Nature communications
In common: scikit-learn, pandas, SciPy, 2 other tools, stroke, clinical / translational, 1 reference
[7] doi:10.1088/2057-1976/acb1b3
Can we explain machine learning-based prediction for rupture status assessments of intracranial aneurysms?
Journal: Biomedical physics & engineering express
In common: stroke, 3 references
[8] doi:10.1016/j.mcpdig.2023.08.009 [code]
Hemorrhage Evaluation and Detector System for Underserved Populations: HEADS-UP.
Journal: Mayo Clinic proceedings. Digital health
In common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
[9] doi:10.3390/brainsci15121295
A Mask R-CNN-Based Approach for Brain Aneurysm Detection and Segmentation from TOF-MRA Data.
Journal: Brain sciences
In common: stroke, 2 references
[10] doi:10.1109/tbme.2025.3572837 [code]
A Joint Geometric Topological Analysis Network (JGTA-Net) for Detecting and Segmenting Intracranial Aneurysms.
Journal: IEEE transactions on bio-medical engineering
In common: SciPy, NumPy, stroke, 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.

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.1371/journal.pone.0360923.svg)](https://openscicode.org/badge/doi/10.1371/journal.pone.0360923)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1371/journal.pone.0360923.svg
   :target: https://openscicode.org/badge/doi/10.1371/journal.pone.0360923
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1371/journal.pone.0360923"><img src="https://openscicode.org/badge/doi/10.1371/journal.pone.0360923.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.