comparison test-data/sample_output.html @ 0:375c36923da1 draft default tip

planemo upload for repository https://github.com/goeckslab/gleam.git commit 1c6c1ad7a1b2bd3645aa0eafa2167784820b52e0
author goeckslab
date Tue, 09 Dec 2025 23:49:47 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:375c36923da1
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="UTF-8">
5 <title>Galaxy-Ludwig Report</title>
6 <style>
7 body {
8 font-family: Arial, sans-serif;
9 margin: 0;
10 padding: 20px;
11 background-color: #f4f4f4;
12 }
13 .container {
14 max-width: 1200px;
15 margin: auto;
16 background: white;
17 padding: 20px;
18 box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
19 overflow-x: auto;
20 }
21 h1 {
22 text-align: center;
23 color: #333;
24 }
25 h2 {
26 border-bottom: 2px solid #4CAF50;
27 color: #4CAF50;
28 padding-bottom: 5px;
29 margin-top: 28px;
30 }
31 h3 {
32 margin-top: 20px;
33 color: #333;
34 }
35 .section {
36 margin-bottom: 30px;
37 }
38 .section-title {
39 border-bottom: 2px solid #4CAF50;
40 color: #4CAF50;
41 padding-bottom: 5px;
42 margin-top: 28px;
43 }
44 .card {
45 background: #fff;
46 padding: 15px;
47 border: 1px solid #ddd;
48 border-radius: 4px;
49 margin: 10px 0;
50 }
51
52 /* baseline table setup */
53 table {
54 border-collapse: collapse;
55 margin: 20px 0;
56 width: 100%;
57 table-layout: fixed;
58 background: #fff;
59 }
60 table, th, td {
61 border: 1px solid #ddd;
62 }
63 th, td {
64 padding: 10px;
65 text-align: center;
66 vertical-align: middle;
67 word-break: break-word;
68 white-space: normal;
69 overflow-wrap: anywhere;
70 }
71 th {
72 background-color: #4CAF50;
73 color: white;
74 }
75 .metric-table {
76 width: 100%;
77 }
78 .kv-table {
79 width: 100%;
80 }
81 .kv-table th:first-child,
82 .kv-table td:first-child {
83 text-align: left;
84 width: 30%;
85 }
86
87 .plot {
88 text-align: center;
89 margin: 20px 0;
90 }
91 .plot img {
92 max-width: 100%;
93 height: auto;
94 border: 1px solid #ddd;
95 }
96
97 /* -------------------
98 sortable columns (3-state: none ⇅, asc ↑, desc ↓)
99 ------------------- */
100 table.performance-summary th.sortable {
101 cursor: pointer;
102 position: relative;
103 user-select: none;
104 }
105 /* default icon space */
106 table.performance-summary th.sortable::after {
107 content: '⇅';
108 position: absolute;
109 right: 12px;
110 top: 50%;
111 transform: translateY(-50%);
112 font-size: 0.8em;
113 color: #eaf5ea; /* light on green */
114 text-shadow: 0 0 1px rgba(0,0,0,0.15);
115 }
116 /* three states override the default */
117 table.performance-summary th.sortable.sorted-none::after { content: '⇅'; color: #eaf5ea; }
118 table.performance-summary th.sortable.sorted-asc::after { content: '↑'; color: #ffffff; }
119 table.performance-summary th.sortable.sorted-desc::after { content: '↓'; color: #ffffff; }
120
121 /* show ~30 rows with a scrollbar (tweak if you want) */
122 .scroll-rows-30 {
123 max-height: 900px; /* ~30 rows depending on row height */
124 overflow-y: auto; /* vertical scrollbar ("sidebar") */
125 overflow-x: auto;
126 }
127
128 /* Tabs + Help button (used by build_tabbed_html) */
129 .tabs {
130 display: flex;
131 align-items: center;
132 border-bottom: 2px solid #ccc;
133 margin-bottom: 1rem;
134 gap: 6px;
135 flex-wrap: wrap;
136 }
137 .tab {
138 padding: 10px 20px;
139 cursor: pointer;
140 border: 1px solid #ccc;
141 border-bottom: none;
142 background: #f9f9f9;
143 margin-right: 5px;
144 border-top-left-radius: 8px;
145 border-top-right-radius: 8px;
146 }
147 .tab.active {
148 background: white;
149 font-weight: bold;
150 }
151 .help-btn {
152 margin-left: auto;
153 padding: 6px 12px;
154 font-size: 0.9rem;
155 border: 1px solid #4CAF50;
156 border-radius: 4px;
157 background: #4CAF50;
158 color: white;
159 cursor: pointer;
160 }
161 .tab-content {
162 display: none;
163 padding: 20px;
164 border: 1px solid #ccc;
165 border-top: none;
166 background: #fff;
167 }
168 .tab-content.active {
169 display: block;
170 }
171 .plotly-center {
172 display: flex;
173 justify-content: center;
174 }
175 .plotly-center .plotly-graph-div, .plotly-center .js-plotly-plot {
176 margin: 0 auto !important;
177 }
178 .js-plotly-plot, .plotly-graph-div {
179 margin-left: auto !important;
180 margin-right: auto !important;
181 }
182
183 /* Modal (used by get_metrics_help_modal) */
184 .modal {
185 display: none;
186 position: fixed;
187 z-index: 9999;
188 left: 0; top: 0;
189 width: 100%; height: 100%;
190 overflow: auto;
191 background-color: rgba(0,0,0,0.4);
192 }
193 .modal-content {
194 background-color: #fefefe;
195 margin: 8% auto;
196 padding: 20px;
197 border: 1px solid #888;
198 width: 90%;
199 max-width: 900px;
200 border-radius: 8px;
201 }
202 .modal .close {
203 color: #777;
204 float: right;
205 font-size: 28px;
206 font-weight: bold;
207 line-height: 1;
208 margin-left: 8px;
209 }
210 .modal .close:hover,
211 .modal .close:focus {
212 color: black;
213 text-decoration: none;
214 cursor: pointer;
215 }
216 .metrics-guide h3 { margin-top: 20px; }
217 .metrics-guide p { margin: 6px 0; }
218 .metrics-guide ul { margin: 10px 0; padding-left: 20px; }
219 </style>
220
221 <script>
222 // Guard to avoid double-initialization if this block is included twice
223 (function(){
224 if (window.__perfSummarySortInit) return;
225 window.__perfSummarySortInit = true;
226
227 function initPerfSummarySorting() {
228 // Record original order for "back to original"
229 document.querySelectorAll('table.performance-summary tbody').forEach(tbody => {
230 Array.from(tbody.rows).forEach((row, i) => { row.dataset.originalOrder = i; });
231 });
232
233 const getText = td => (td?.innerText || '').trim();
234 const cmp = (idx, asc) => (a, b) => {
235 const v1 = getText(a.children[idx]);
236 const v2 = getText(b.children[idx]);
237 const n1 = parseFloat(v1), n2 = parseFloat(v2);
238 if (!isNaN(n1) && !isNaN(n2)) return asc ? n1 - n2 : n2 - n1; // numeric
239 return asc ? v1.localeCompare(v2) : v2.localeCompare(v1); // lexical
240 };
241
242 document.querySelectorAll('table.performance-summary th.sortable').forEach(th => {
243 // initialize to "none"
244 th.classList.remove('sorted-asc','sorted-desc');
245 th.classList.add('sorted-none');
246
247 th.addEventListener('click', () => {
248 const table = th.closest('table');
249 const headerRow = th.parentNode;
250 const allTh = headerRow.querySelectorAll('th.sortable');
251 const tbody = table.querySelector('tbody');
252
253 // Determine current state BEFORE clearing
254 const isAsc = th.classList.contains('sorted-asc');
255 const isDesc = th.classList.contains('sorted-desc');
256
257 // Reset all headers in this row
258 allTh.forEach(x => x.classList.remove('sorted-asc','sorted-desc','sorted-none'));
259
260 // Compute next state
261 let next;
262 if (!isAsc && !isDesc) {
263 next = 'asc';
264 } else if (isAsc) {
265 next = 'desc';
266 } else {
267 next = 'none';
268 }
269 th.classList.add('sorted-' + next);
270
271 // Sort rows according to the chosen state
272 const rows = Array.from(tbody.rows);
273 if (next === 'none') {
274 rows.sort((a, b) => (a.dataset.originalOrder - b.dataset.originalOrder));
275 } else {
276 const idx = Array.from(headerRow.children).indexOf(th);
277 rows.sort(cmp(idx, next === 'asc'));
278 }
279 rows.forEach(r => tbody.appendChild(r));
280 });
281 });
282 }
283
284 // Run after DOM is ready
285 if (document.readyState === 'loading') {
286 document.addEventListener('DOMContentLoaded', initPerfSummarySorting);
287 } else {
288 initPerfSummarySorting();
289 }
290 })();
291 </script>
292 </head>
293 <body>
294 <div class="container">
295 <script src="https://cdn.plot.ly/plotly-2.30.0.min.js"></script>
296 <style>
297 .plotly-center {
298 display: flex;
299 justify-content: center;
300 margin: 20px 0;
301 width: 100%;
302 }
303 .plotly-center .plotly-graph-div, .plotly-center .js-plotly-plot {
304 margin: 0 auto !important;
305 }
306 .js-plotly-plot, .plotly-graph-div {
307 margin-left: auto !important;
308 margin-right: auto !important;
309 }
310 #learning-curve-accuracy, #learning-curve-loss, #threshold-plot,
311 #confusion-matrix, #per-class-metrics, #roc-curve, #pr-curve {
312 min-height: 400px;
313 }
314 </style>
315
316 <div class="tabs">
317 <div class="tab active" onclick="showTab('summary')">Model Summary & Config</div>
318 <div class="tab" onclick="showTab('train')">Train/Validation Summary</div>
319 <div class="tab" onclick="showTab('test')">Test Summary</div>
320 <div class="tab" onclick="showTab('feature')">Feature Importance</div>
321 <button id="openMetricsHelp" class="help-btn">Help</button>
322 </div>
323
324 <!-- Summary Tab -->
325 <div id="summary" class="tab-content active">
326 <section class="section">
327 <h2 class="section-title">Model Performance Summary</h2>
328 <div class="card">
329 <table class="metric-table">
330 <thead><tr><th>Metric</th><th>Train</th><th>Validation</th><th>Test</th></tr></thead>
331 <tbody>
332 <tr><td>accuracy</td><td>0.9234</td><td>0.8912</td><td>0.8856</td></tr>
333 <tr><td>f1</td><td>0.9201</td><td>0.8876</td><td>0.8823</td></tr>
334 <tr><td>precision</td><td>0.9156</td><td>0.8845</td><td>0.8798</td></tr>
335 <tr><td>recall</td><td>0.9245</td><td>0.8907</td><td>0.8849</td></tr>
336 <tr><td>roc_auc</td><td>0.9789</td><td>0.9543</td><td>0.9512</td></tr>
337 <tr><td>log_loss</td><td>0.2134</td><td>0.2876</td><td>0.3012</td></tr>
338 </tbody>
339 </table>
340 </div>
341 </section>
342
343 <section class="section">
344 <h2 class="section-title">Run Configuration</h2>
345 <div class="card">
346 <table class="kv-table">
347 <thead><tr><th>Key</th><th>Value</th></tr></thead>
348 <tbody>
349 <tr><td>Predictor type</td><td>MultiModalPredictor</td></tr>
350 <tr><td>Framework</td><td>AutoGluon Multimodal</td></tr>
351 <tr><td>Model architecture</td><td>timm_image=resnet50, hf_text=bert-base-uncased</td></tr>
352 <tr><td>Modalities & Inputs</td><td>Images + Tabular</td></tr>
353 <tr><td>Label column</td><td>target</td></tr>
354 <tr><td>Image columns</td><td>image_path</td></tr>
355 <tr><td>Tabular columns</td><td>15</td></tr>
356 <tr><td>Presets</td><td>medium_quality</td></tr>
357 <tr><td>Eval metric</td><td>accuracy</td></tr>
358 <tr><td>Decision threshold calibration</td><td>enabled</td></tr>
359 <tr><td>Decision threshold (Test only)</td><td>0.500</td></tr>
360 <tr><td>Seed</td><td>42</td></tr>
361 <tr><td>time limit(s)</td><td>3600</td></tr>
362 </tbody>
363 </table>
364 </div>
365 </section>
366
367 <section class="section">
368 <h2 class="section-title">Class Balance (Train Full)</h2>
369 <div class="card">
370 <h3>Class Balance (Train Full)</h3>
371 <table class="table">
372 <thead><tr><th>Class</th><th>Count</th><th>Percent</th></tr></thead>
373 <tbody>
374 <tr><td>0</td><td>1245</td><td>45.23%</td></tr>
375 <tr><td>1</td><td>1508</td><td>54.77%</td></tr>
376 </tbody>
377 </table>
378 </div>
379 </section>
380 </div>
381
382 <!-- Train Tab -->
383 <div id="train" class="tab-content">
384 <h2>Train/Validation Performance Summary</h2>
385
386 <div class="card">
387 <table class="metric-table">
388 <thead><tr><th>Metric</th><th>Train</th><th>Validation</th></tr></thead>
389 <tbody>
390 <tr><td>accuracy</td><td>0.9234</td><td>0.8912</td></tr>
391 <tr><td>f1</td><td>0.9201</td><td>0.8876</td></tr>
392 <tr><td>precision</td><td>0.9156</td><td>0.8845</td></tr>
393 <tr><td>recall</td><td>0.9245</td><td>0.8907</td></tr>
394 <tr><td>roc_auc</td><td>0.9789</td><td>0.9543</td></tr>
395 <tr><td>log_loss</td><td>0.2134</td><td>0.2876</td></tr>
396 </tbody>
397 </table>
398 </div>
399
400 <h2>Learning Curves — Label Accuracy</h2>
401 <div class="plotly-center">
402 <div id="learning-curve-accuracy" style="width:900px;height:500px;"></div>
403 </div>
404
405 <h2>Learning Curves — Label Loss</h2>
406 <div class="plotly-center">
407 <div id="learning-curve-loss" style="width:900px;height:500px;"></div>
408 </div>
409 </div>
410
411 <!-- Test Tab -->
412 <div id="test" class="tab-content">
413 <h2>Test Performance Summary</h2>
414 <table class="performance-summary">
415 <thead>
416 <tr>
417 <th class="sortable">Metric</th>
418 <th class="sortable">Test</th>
419 </tr>
420 </thead>
421 <tbody>
422 <tr><td>accuracy</td><td>0.8856</td></tr>
423 <tr><td>f1</td><td>0.8823</td></tr>
424 <tr><td>precision</td><td>0.8798</td></tr>
425 <tr><td>recall</td><td>0.8849</td></tr>
426 <tr><td>roc_auc</td><td>0.9512</td></tr>
427 <tr><td>log_loss</td><td>0.3012</td></tr>
428 <tr><td>specificity (TNR)</td><td>0.8765</td></tr>
429 <tr><td>sensitivity (Sensitivity/TPR)</td><td>0.8923</td></tr>
430 </tbody>
431 </table>
432
433 <h2>Confusion Matrix</h2>
434 <div class="plotly-center">
435 <div id="confusion-matrix" style="width:700px;height:600px;"></div>
436 </div>
437
438 <h2>Per-Class Metrics</h2>
439 <div class="plotly-center">
440 <div id="per-class-metrics" style="width:900px;height:500px;"></div>
441 </div>
442
443 <h2>ROC Curve</h2>
444 <div class="plotly-center">
445 <div id="roc-curve" style="width:800px;height:600px;"></div>
446 </div>
447
448 <h2>Precision–Recall Curve</h2>
449 <div class="plotly-center">
450 <div id="pr-curve" style="width:800px;height:600px;"></div>
451 </div>
452
453 <h2>Threshold Plot</h2>
454 <div class="plotly-center">
455 <div id="threshold-plot" style="width:900px;height:500px;"></div>
456 </div>
457 </div>
458
459 <!-- Feature Importance Tab -->
460 <div id="feature" class="tab-content">
461 <section class="section">
462 <h2 class="section-title">Feature Importance</h2>
463 <div class="card">
464 <p>Permutation importance is not supported for MultiModalPredictor in this tool. For tabular-only runs, this section shows permutation importance.</p>
465 </div>
466 </section>
467
468 <section class="section">
469 <h2 class="section-title">Modalities & Inputs</h2>
470 <div class="card">
471 <h3>Modalities & Inputs</h3>
472 <p>This run used <b>MultiModalPredictor</b> (images + tabular).</p>
473 <p><b>Label column:</b> target</p>
474 <p><b>Image column:</b> image_path</p>
475 <div><b>Tabular columns:</b> 15
476 <ul>
477 <li>feature_1</li>
478 <li>feature_2</li>
479 <li>feature_3</li>
480 <li>feature_4</li>
481 <li>feature_5</li>
482 <li>feature_6</li>
483 <li>feature_7</li>
484 <li>feature_8</li>
485 <li>feature_9</li>
486 <li>feature_10</li>
487 <li>feature_11</li>
488 <li>feature_12</li>
489 <li>feature_13</li>
490 <li>feature_14</li>
491 <li>feature_15</li>
492 </ul>
493 </div>
494 </div>
495 </section>
496 </div>
497
498 <script>
499 function showTab(id) {
500 document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
501 document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
502 document.getElementById(id).classList.add('active');
503 document.querySelector(`.tab[onclick*="${id}"]`).classList.add('active');
504 }
505
506 // Fixed random data for reproducibility
507 const epochs = Array.from({length: 31}, (_, i) => i);
508 const accuracy_train = [0.552, 0.568, 0.581, 0.595, 0.612, 0.628, 0.645, 0.662, 0.678, 0.692, 0.708, 0.722, 0.735, 0.748, 0.761, 0.773, 0.784, 0.795, 0.805, 0.814, 0.823, 0.831, 0.839, 0.846, 0.853, 0.859, 0.865, 0.870, 0.875, 0.880, 0.884];
509 const accuracy_val = [0.752, 0.768, 0.782, 0.795, 0.807, 0.818, 0.828, 0.837, 0.845, 0.852, 0.859, 0.865, 0.871, 0.876, 0.881, 0.885, 0.889, 0.893, 0.896, 0.899, 0.902, 0.905, 0.907, 0.909, 0.911, 0.913, 0.915, 0.917, 0.918, 0.920, 0.921];
510 const loss_train = [1.352, 1.285, 1.221, 1.158, 1.098, 1.041, 0.987, 0.936, 0.888, 0.842, 0.799, 0.758, 0.720, 0.684, 0.650, 0.618, 0.588, 0.560, 0.534, 0.510, 0.487, 0.466, 0.447, 0.429, 0.412, 0.397, 0.383, 0.370, 0.358, 0.347, 0.337];
511 const loss_val = [0.802, 0.765, 0.730, 0.697, 0.666, 0.637, 0.610, 0.585, 0.561, 0.539, 0.518, 0.499, 0.481, 0.464, 0.448, 0.433, 0.419, 0.406, 0.394, 0.383, 0.372, 0.362, 0.353, 0.345, 0.337, 0.330, 0.323, 0.317, 0.311, 0.306, 0.301];
512
513 // Wait for Plotly to load before creating plots
514 function createPlots() {
515 if (typeof Plotly === 'undefined') {
516 console.log('Waiting for Plotly...');
517 setTimeout(createPlots, 100);
518 return;
519 }
520
521 console.log('Plotly loaded, creating plots...');
522
523 // Learning Curves — Label Accuracy (with both training and validation)
524 try {
525 Plotly.newPlot('learning-curve-accuracy', [
526 {
527 x: epochs,
528 y: accuracy_train,
529 type: 'scatter',
530 mode: 'lines+markers',
531 name: 'Training',
532 line: { width: 3, color: '#1f77b4', shape: 'spline' },
533 marker: { size: 8, color: '#1f77b4' }
534 },
535 {
536 x: epochs,
537 y: accuracy_val,
538 type: 'scatter',
539 mode: 'lines+markers',
540 name: 'Validation',
541 line: { width: 3, color: '#ff7f0e', shape: 'spline' },
542 marker: { size: 8, color: '#ff7f0e' }
543 }
544 ], {
545 template: 'plotly_white',
546 xaxis: {
547 title: 'Epoch',
548 gridcolor: '#e0e0e0',
549 showgrid: true,
550 zeroline: false
551 },
552 yaxis: {
553 title: 'Accuracy',
554 gridcolor: '#e0e0e0',
555 showgrid: true,
556 zeroline: false,
557 range: [0.5, 1.0]
558 },
559 legend: {
560 orientation: 'h',
561 yanchor: 'bottom',
562 y: 1.02,
563 xanchor: 'right',
564 x: 1.0
565 },
566 margin: { l: 60, r: 20, t: 40, b: 50 },
567 plot_bgcolor: 'white',
568 paper_bgcolor: 'white'
569 });
570 console.log('Accuracy plot created');
571 } catch(e) {
572 console.error('Error creating accuracy plot:', e);
573 }
574
575 // Learning Curves — Label Loss (with both training and validation)
576 try {
577 Plotly.newPlot('learning-curve-loss', [
578 {
579 x: epochs,
580 y: loss_train,
581 type: 'scatter',
582 mode: 'lines+markers',
583 name: 'Training Loss',
584 line: { width: 3, color: '#1f77b4', shape: 'spline' },
585 marker: { size: 8, color: '#1f77b4' }
586 },
587 {
588 x: epochs,
589 y: loss_val,
590 type: 'scatter',
591 mode: 'lines+markers',
592 name: 'Validation Loss',
593 line: { width: 3, color: '#ff7f0e', shape: 'spline' },
594 marker: { size: 8, color: '#ff7f0e' }
595 }
596 ], {
597 template: 'plotly_white',
598 xaxis: {
599 title: 'Epoch',
600 gridcolor: '#e0e0e0',
601 showgrid: true,
602 zeroline: false
603 },
604 yaxis: {
605 title: 'Loss',
606 gridcolor: '#e0e0e0',
607 showgrid: true,
608 zeroline: false,
609 range: [0.2, 1.4]
610 },
611 legend: {
612 orientation: 'h',
613 yanchor: 'bottom',
614 y: 1.02,
615 xanchor: 'right',
616 x: 1.0
617 },
618 margin: { l: 60, r: 20, t: 40, b: 50 },
619 plot_bgcolor: 'white',
620 paper_bgcolor: 'white'
621 });
622 console.log('Loss plot created');
623 } catch(e) {
624 console.error('Error creating loss plot:', e);
625 }
626
627 // Threshold Plot (with fixed data matching the expected visualization)
628 // Precision: starts low (~0.42), increases to peak (~0.95) around threshold 0.5, then drops sharply to 0
629 // Recall: starts at 1, stays at 1 until ~0.1, then decreases to 0 around 0.9
630 // F1: starts around 0.6, peaks around 0.5 at ~0.95, then drops to 0
631 const thresholds = Array.from({length: 101}, (_, i) => i / 100);
632
633 // Generate precision values: low at start, peak around 0.5, drop to 0
634 const precision_vals = thresholds.map(t => {
635 if (t <= 0.5) {
636 return 0.42 + (t / 0.5) * 0.53; // 0.42 to 0.95
637 } else if (t <= 0.9) {
638 return 0.95 - ((t - 0.5) / 0.4) * 0.95; // 0.95 to 0
639 } else {
640 return 0;
641 }
642 });
643
644 // Generate recall values: starts at 1, stays at 1 until ~0.1, then decreases to 0 around 0.9
645 const recall_vals = thresholds.map(t => {
646 if (t <= 0.1) {
647 return 1.0;
648 } else if (t <= 0.9) {
649 return 1.0 - ((t - 0.1) / 0.8) * 1.0; // 1.0 to 0
650 } else {
651 return 0;
652 }
653 });
654
655 // Calculate F1 from precision and recall
656 const f1_vals = thresholds.map((t, i) => {
657 const p = precision_vals[i];
658 const r = recall_vals[i];
659 if (p + r === 0) return 0;
660 return 2 * (p * r) / (p + r);
661 });
662
663 // Queue Rate: decreases linearly from 1 to 0
664 const queue_rate = thresholds.map(t => 1 - t);
665
666 try {
667 Plotly.newPlot('threshold-plot', [
668 {
669 x: thresholds,
670 y: precision_vals,
671 type: 'scatter',
672 mode: 'lines',
673 name: 'Precision',
674 line: { width: 3, color: '#1f77b4' }
675 },
676 {
677 x: thresholds,
678 y: recall_vals,
679 type: 'scatter',
680 mode: 'lines',
681 name: 'Recall',
682 line: { width: 3, color: '#ff7f0e' }
683 },
684 {
685 x: thresholds,
686 y: f1_vals,
687 type: 'scatter',
688 mode: 'lines',
689 name: 'F1',
690 line: { width: 3, color: '#2ca02c' }
691 },
692 {
693 x: thresholds,
694 y: queue_rate,
695 type: 'scatter',
696 mode: 'lines',
697 name: 'Queue Rate',
698 line: { width: 2, color: '#808080', dash: 'dash' }
699 }
700 ], {
701 template: 'plotly_white',
702 xaxis: {
703 title: 'Discrimination Threshold',
704 gridcolor: '#e0e0e0',
705 showgrid: true,
706 zeroline: false
707 },
708 yaxis: {
709 title: 'Score',
710 gridcolor: '#e0e0e0',
711 showgrid: true,
712 zeroline: false,
713 range: [0, 1]
714 },
715 legend: {
716 orientation: 'h',
717 yanchor: 'bottom',
718 y: 1.02,
719 xanchor: 'right',
720 x: 1.0
721 },
722 margin: { l: 60, r: 20, t: 40, b: 50 },
723 plot_bgcolor: 'white',
724 paper_bgcolor: 'white',
725 shapes: [{
726 type: 'line',
727 xref: 'x',
728 yref: 'y',
729 x0: 0.51,
730 y0: 0,
731 x1: 0.51,
732 y1: 1,
733 line: {
734 color: 'black',
735 width: 2,
736 dash: 'dash'
737 }
738 }],
739 annotations: [{
740 x: 0.51,
741 y: 0.85,
742 text: 't* = 0.51',
743 showarrow: false,
744 font: { size: 12, color: 'black' }
745 }]
746 });
747 console.log('Threshold plot created');
748 } catch(e) {
749 console.error('Error creating threshold plot:', e);
750 }
751
752 // Confusion Matrix (matching imagelearner style with Blues colorscale)
753 const cm_data = [[542, 78], [65, 515]];
754 const total = cm_data.flat().reduce((a, b) => a + b, 0);
755 const labels = ['0', '1'];
756
757 // Build annotations array with all white text
758 const annotations = [];
759 cm_data.forEach((row, i) => {
760 row.forEach((val, j) => {
761 const pct = ((val / total) * 100).toFixed(1);
762 // All text is white
763 const textColor = 'white';
764 // Count annotation (bold, bottom)
765 annotations.push({
766 x: labels[j],
767 y: labels[i],
768 text: '<b>' + val + '</b>',
769 showarrow: false,
770 font: { color: textColor, size: 14 },
771 xanchor: 'center',
772 yanchor: 'bottom',
773 yshift: 2
774 });
775 // Percentage annotation (top)
776 annotations.push({
777 x: labels[j],
778 y: labels[i],
779 text: pct + '%',
780 showarrow: false,
781 font: { color: textColor, size: 13 },
782 xanchor: 'center',
783 yanchor: 'top',
784 yshift: -2
785 });
786 });
787 });
788
789 try {
790 Plotly.newPlot('confusion-matrix', [{
791 z: cm_data,
792 x: labels,
793 y: labels,
794 type: 'heatmap',
795 colorscale: 'Blues',
796 showscale: true,
797 colorbar: { title: 'Count' },
798 xgap: 2,
799 ygap: 2,
800 hovertemplate: 'True=%{y}<br>Pred=%{x}<br>Count=%{z}<extra></extra>',
801 zmin: 0
802 }], {
803 xaxis: { title: 'Predicted label', type: 'category' },
804 yaxis: { title: 'True label', type: 'category', autorange: 'reversed' },
805 margin: { l: 80, r: 20, t: 40, b: 80 },
806 template: 'plotly_white',
807 plot_bgcolor: 'white',
808 paper_bgcolor: 'white',
809 annotations: annotations
810 });
811 console.log('Confusion matrix created');
812 } catch(e) {
813 console.error('Error creating confusion matrix:', e);
814 }
815
816 // Per-Class Metrics
817 const classes = ['Class 0', 'Class 1'];
818 const precision_per_class = [0.8929, 0.8685];
819 const recall_per_class = [0.8742, 0.8879];
820 const f1_per_class = [0.8835, 0.8781];
821
822 try {
823 Plotly.newPlot('per-class-metrics', [
824 {
825 x: classes,
826 y: precision_per_class,
827 type: 'bar',
828 name: 'Precision',
829 marker: { color: '#4CAF50' }
830 },
831 {
832 x: classes,
833 y: recall_per_class,
834 type: 'bar',
835 name: 'Recall',
836 marker: { color: '#2196F3' }
837 },
838 {
839 x: classes,
840 y: f1_per_class,
841 type: 'bar',
842 name: 'F1',
843 marker: { color: '#FF9800' }
844 }
845 ], {
846 template: 'plotly_white',
847 xaxis: {
848 title: 'Class',
849 gridcolor: '#e0e0e0',
850 showgrid: true
851 },
852 yaxis: {
853 title: 'Score',
854 gridcolor: '#e0e0e0',
855 showgrid: true,
856 range: [0, 1]
857 },
858 barmode: 'group',
859 legend: {
860 orientation: 'h',
861 yanchor: 'bottom',
862 y: 1.02,
863 xanchor: 'right',
864 x: 1.0
865 },
866 margin: { l: 60, r: 20, t: 40, b: 50 },
867 plot_bgcolor: 'white',
868 paper_bgcolor: 'white'
869 });
870 console.log('Per-class metrics created');
871 } catch(e) {
872 console.error('Error creating per-class metrics:', e);
873 }
874
875 // ROC Curve (with fixed data)
876 const fpr = [0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.10,0.11,0.12,0.13,0.14,0.15,0.16,0.17,0.18,0.19,0.20,0.21,0.22,0.23,0.24,0.25,0.26,0.27,0.28,0.29,0.30,0.31,0.32,0.33,0.34,0.35,0.36,0.37,0.38,0.39,0.40,0.41,0.42,0.43,0.44,0.45,0.46,0.47,0.48,0.49,0.50,0.51,0.52,0.53,0.54,0.55,0.56,0.57,0.58,0.59,0.60,0.61,0.62,0.63,0.64,0.65,0.66,0.67,0.68,0.69,0.70,0.71,0.72,0.73,0.74,0.75,0.76,0.77,0.78,0.79,0.80,0.81,0.82,0.83,0.84,0.85,0.86,0.87,0.88,0.89,0.90,0.91,0.92,0.93,0.94,0.95,0.96,0.97,0.98,0.99,1.00];
877 const tpr = [0,0.12,0.23,0.33,0.42,0.50,0.57,0.63,0.69,0.74,0.78,0.82,0.85,0.88,0.90,0.92,0.94,0.95,0.96,0.97,0.98,0.985,0.99,0.992,0.994,0.996,0.997,0.998,0.999,0.9995,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0];
878 const auc_score = 0.935;
879
880 try {
881 Plotly.newPlot('roc-curve', [
882 {
883 x: [0, 1],
884 y: [0, 1],
885 type: 'scatter',
886 mode: 'lines',
887 name: 'Random (AUC = 0.50)',
888 line: { dash: 'dash', color: 'gray', width: 2 }
889 },
890 {
891 x: fpr,
892 y: tpr,
893 type: 'scatter',
894 mode: 'lines',
895 name: `ROC Curve (AUC = ${auc_score.toFixed(3)})`,
896 line: { width: 3, color: '#1f77b4' }
897 }
898 ], {
899 template: 'plotly_white',
900 xaxis: {
901 title: 'False Positive Rate',
902 gridcolor: '#e0e0e0',
903 showgrid: true,
904 zeroline: false,
905 range: [0, 1]
906 },
907 yaxis: {
908 title: 'True Positive Rate',
909 gridcolor: '#e0e0e0',
910 showgrid: true,
911 zeroline: false,
912 range: [0, 1]
913 },
914 legend: {
915 orientation: 'h',
916 yanchor: 'bottom',
917 y: 1.02,
918 xanchor: 'right',
919 x: 1.0
920 },
921 margin: { l: 60, r: 20, t: 40, b: 50 },
922 plot_bgcolor: 'white',
923 paper_bgcolor: 'white'
924 });
925 console.log('ROC curve created');
926 } catch(e) {
927 console.error('Error creating ROC curve:', e);
928 }
929
930 // Precision-Recall Curve (with fixed data)
931 const recall_pr = [0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.10,0.11,0.12,0.13,0.14,0.15,0.16,0.17,0.18,0.19,0.20,0.21,0.22,0.23,0.24,0.25,0.26,0.27,0.28,0.29,0.30,0.31,0.32,0.33,0.34,0.35,0.36,0.37,0.38,0.39,0.40,0.41,0.42,0.43,0.44,0.45,0.46,0.47,0.48,0.49,0.50,0.51,0.52,0.53,0.54,0.55,0.56,0.57,0.58,0.59,0.60,0.61,0.62,0.63,0.64,0.65,0.66,0.67,0.68,0.69,0.70,0.71,0.72,0.73,0.74,0.75,0.76,0.77,0.78,0.79,0.80,0.81,0.82,0.83,0.84,0.85,0.86,0.87,0.88,0.89,0.90,0.91,0.92,0.93,0.94,0.95,0.96,0.97,0.98,0.99,1.00];
932 const precision_pr = [0.95,0.949,0.948,0.947,0.946,0.945,0.944,0.943,0.942,0.941,0.940,0.939,0.938,0.937,0.936,0.935,0.934,0.933,0.932,0.931,0.930,0.929,0.928,0.927,0.926,0.925,0.924,0.923,0.922,0.921,0.920,0.919,0.918,0.917,0.916,0.915,0.914,0.913,0.912,0.911,0.910,0.909,0.908,0.907,0.906,0.905,0.904,0.903,0.902,0.901,0.900,0.899,0.898,0.897,0.896,0.895,0.894,0.893,0.892,0.891,0.890,0.889,0.888,0.887,0.886,0.885,0.884,0.883,0.882,0.881,0.880,0.879,0.878,0.877,0.876,0.875,0.874,0.873,0.872,0.871,0.870,0.869,0.868,0.867,0.866,0.865,0.864,0.863,0.862,0.861,0.860,0.859,0.858,0.857,0.856,0.855,0.854,0.853,0.852,0.851,0.850];
933 const ap_score = 0.9234;
934
935 try {
936 Plotly.newPlot('pr-curve', [
937 {
938 x: recall_pr,
939 y: precision_pr,
940 type: 'scatter',
941 mode: 'lines',
942 name: `Precision-Recall Curve (AP = ${ap_score.toFixed(4)})`,
943 line: { width: 3, color: '#1f77b4' }
944 }
945 ], {
946 template: 'plotly_white',
947 xaxis: {
948 title: 'Recall',
949 gridcolor: '#e0e0e0',
950 showgrid: true,
951 zeroline: false,
952 range: [0, 1]
953 },
954 yaxis: {
955 title: 'Precision',
956 gridcolor: '#e0e0e0',
957 showgrid: true,
958 zeroline: false,
959 range: [0.6, 1]
960 },
961 legend: {
962 orientation: 'h',
963 yanchor: 'bottom',
964 y: 1.02,
965 xanchor: 'right',
966 x: 1.0
967 },
968 margin: { l: 60, r: 20, t: 40, b: 50 },
969 plot_bgcolor: 'white',
970 paper_bgcolor: 'white'
971 });
972 console.log('PR curve created');
973 } catch(e) {
974 console.error('Error creating PR curve:', e);
975 }
976
977 console.log('All plots created successfully!');
978 }
979
980 // Start creating plots when DOM is ready
981 if (document.readyState === 'loading') {
982 document.addEventListener('DOMContentLoaded', createPlots);
983 } else {
984 createPlots();
985 }
986 </script>
987 </div>
988 </body>
989 </html>
990