| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Linear Regression β Statistical Machine Learning</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@400;500&family=Outfit:wght@300;400;500;600&display=swap" rel="stylesheet"> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script> |
| <style> |
| :root { |
| --bg:#EEF3F8; --bg-card:#FFFFFF; --bg-muted:#E2EAF3; |
| --teal:#1D9E75; --teal-light:#E1F5EE; --teal-dark:#0F6E56; |
| --blue:#378ADD; --blue-light:#E6F1FB; --blue-dark:#185FA5; |
| --orange:#E07A30; --orange-light:#FEF0E3; |
| --red:#C0392B; --red-light:#FDECEA; |
| --text:#1C2B3A; --text2:#4A5F74; --muted:#8298AE; |
| --border:rgba(28,43,58,0.10); --border-s:rgba(28,43,58,0.18); |
| --sh-sm:0 1px 3px rgba(28,43,58,0.07),0 1px 2px rgba(28,43,58,0.05); |
| --sh-md:0 4px 12px rgba(28,43,58,0.09),0 2px 4px rgba(28,43,58,0.06); |
| --sh-lg:0 12px 32px rgba(28,43,58,0.11),0 4px 8px rgba(28,43,58,0.07); |
| } |
| *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} |
| html{scroll-behavior:smooth} |
| body{font-family:'Outfit',sans-serif;background:var(--bg);color:var(--text);min-height:100vh} |
|
|
| /* NAV */ |
| nav{position:sticky;top:0;z-index:100;background:rgba(238,243,248,0.92);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 2.5rem;height:60px;display:flex;align-items:center;justify-content:space-between} |
| .nav-brand{display:flex;align-items:center;gap:10px;font-family:'DM Mono',monospace;font-size:13px;font-weight:500;color:var(--text2);letter-spacing:.04em;text-decoration:none} |
| .nav-brand span{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--teal)} |
| .breadcrumb{font-family:'DM Mono',monospace;font-size:12px;color:var(--muted);display:flex;align-items:center;gap:8px} |
| .breadcrumb a{color:var(--muted);text-decoration:none} |
| .breadcrumb a:hover{color:var(--teal-dark)} |
|
|
| /* PAGE HEADER */ |
| .ph{padding:3.5rem 2.5rem 2rem;max-width:1200px;margin:0 auto} |
| .ph-eye{font-family:'DM Mono',monospace;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--teal-dark);display:flex;align-items:center;gap:8px;margin-bottom:1rem} |
| .ph-eye::before{content:'';display:block;width:28px;height:1.5px;background:var(--teal)} |
| .ph h1{font-family:'DM Serif Display',serif;font-size:clamp(2.2rem,4vw,3rem);line-height:1.1;letter-spacing:-.02em;margin-bottom:.6rem} |
| .tags{display:flex;gap:8px;flex-wrap:wrap;margin-top:1rem} |
| .tag{font-family:'DM Mono',monospace;font-size:11px;font-weight:500;padding:4px 12px;border-radius:20px;letter-spacing:.06em;text-transform:uppercase} |
| .tag-sup{background:#E1F5EE;color:#0F6E56;border:1px solid #A8DFC8} |
| .tag-reg{background:#EEF3F8;color:#4A5F74;border:1px solid var(--border-s)} |
|
|
| /* MAIN */ |
| .main{max-width:1200px;margin:0 auto;padding:0 2.5rem 6rem} |
|
|
| /* CARDS */ |
| .card{background:var(--bg-card);border:1px solid var(--border);border-radius:16px;padding:2rem;box-shadow:var(--sh-sm);margin-bottom:1.5rem} |
| .card-title{font-family:'DM Mono',monospace;font-size:10.5px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:1.2rem} |
|
|
| /* THEORY */ |
| .theory-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;margin-bottom:1.5rem} |
| .tc{background:var(--bg-card);border:1px solid var(--border);border-radius:14px;padding:1.6rem;box-shadow:var(--sh-sm)} |
| .tc h3{font-family:'DM Mono',monospace;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--teal-dark);margin-bottom:.8rem} |
| .tc p{font-size:14px;line-height:1.75;color:var(--text2);font-weight:300} |
| .formula-card{background:var(--bg-card);border:1px solid var(--border);border-left:4px solid var(--teal);border-radius:14px;padding:1.6rem;box-shadow:var(--sh-sm);margin-bottom:1.5rem} |
| .formula-card h3{font-family:'DM Mono',monospace;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--teal-dark);margin-bottom:1rem} |
| .fl{font-family:'DM Mono',monospace;font-size:15px;color:var(--text);margin-bottom:.5rem} |
| .fl.lg{font-size:19px;margin-bottom:.8rem} |
| .fl.sub{font-size:12px;color:var(--muted);margin-top:.3rem} |
|
|
| /* SECTION LABEL */ |
| .sl{font-family:'DM Mono',monospace;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:1rem;display:flex;align-items:center;gap:8px} |
| .sl::after{content:'';flex:1;height:1px;background:var(--border)} |
| .sep{height:1px;background:var(--border);margin:2rem 0} |
|
|
| /* DATASET BUTTONS */ |
| .ds-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:1.2rem} |
| .ds-btn{border:1.5px solid var(--border-s);border-radius:10px;padding:.9rem .6rem;background:var(--bg-card);cursor:pointer;transition:all .2s;display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center} |
| .ds-btn:hover{border-color:var(--teal);background:var(--teal-light)} |
| .ds-btn.active{border-color:var(--teal);background:var(--teal-light);box-shadow:0 0 0 3px rgba(29,158,117,.12)} |
| .ds-name{font-family:'DM Mono',monospace;font-size:12px;font-weight:500;color:var(--text)} |
| .ds-dim{font-size:11px;color:var(--muted)} |
| .ds-type{font-size:10px;font-family:'DM Mono',monospace;color:var(--teal-dark);margin-top:2px} |
|
|
| /* PARAMS PANEL */ |
| .params-panel{background:var(--bg-muted);border:1px solid var(--border);border-radius:12px;padding:1.4rem 1.6rem;margin-bottom:1.2rem;animation:slideDown .25s ease} |
| @keyframes slideDown{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}} |
| .pg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:1rem} |
| .pg{display:flex;flex-direction:column;gap:4px} |
| .pg label{font-family:'DM Mono',monospace;font-size:10.5px;letter-spacing:.08em;color:var(--text2);display:flex;justify-content:space-between} |
| .pg label span{color:var(--teal-dark);font-weight:500} |
| .pg input[type=range]{width:100%;accent-color:var(--teal);cursor:pointer} |
| .pg select,.pg input[type=number]{padding:6px 10px;border:1px solid var(--border-s);border-radius:7px;background:var(--bg-card);font-family:'Outfit',sans-serif;font-size:13px;color:var(--text);outline:none;width:100%} |
| .pg select:focus,.pg input:focus{border-color:var(--teal)} |
|
|
| /* MODEL TABS */ |
| .train-row{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;margin-bottom:1.5rem;margin-top:1rem} |
| .model-tabs{display:flex;gap:8px} |
| .mt{padding:8px 18px;border:1.5px solid var(--border-s);border-radius:8px;background:var(--bg-card);font-family:'DM Mono',monospace;font-size:12px;cursor:pointer;transition:all .2s;color:var(--text2)} |
| .mt.active{border-color:var(--blue);background:var(--blue-light);color:var(--blue-dark)} |
| .alpha-wrap{display:flex;align-items:center;gap:8px;font-family:'DM Mono',monospace;font-size:12px;color:var(--text2)} |
| .btn-train{padding:11px 32px;background:var(--teal);color:#fff;border:none;border-radius:9px;font-family:'Outfit',sans-serif;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s;display:inline-flex;align-items:center;gap:8px;margin-left:auto} |
| .btn-train:hover{background:var(--teal-dark);transform:translateY(-1px)} |
| .btn-train:disabled{background:var(--muted);cursor:not-allowed;transform:none} |
|
|
| /* METRICS */ |
| .mbar{display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-bottom:1.5rem} |
| .mc{background:var(--bg-card);border:1px solid var(--border);border-radius:10px;padding:.9rem 1rem;box-shadow:var(--sh-sm)} |
| .mc .lbl{font-family:'DM Mono',monospace;font-size:10px;letter-spacing:.08em;color:var(--muted);text-transform:uppercase;margin-bottom:4px} |
| .mc .val{font-family:'DM Mono',monospace;font-size:17px;font-weight:500;color:var(--text)} |
| .mc .sub{font-size:11px;color:var(--muted);margin-top:2px} |
| .mc.good .val{color:var(--teal-dark)} |
| .mc.warn .val{color:var(--orange)} |
| .mc.bad .val{color:var(--red)} |
|
|
| /* PLOTS */ |
| .plots-2{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;margin-bottom:1.5rem} |
| .plots-1{display:grid;grid-template-columns:1fr;gap:1.5rem;margin-bottom:1.5rem} |
| .pc{background:var(--bg-card);border:1px solid var(--border);border-radius:14px;padding:1.4rem;box-shadow:var(--sh-sm)} |
| .pc-head{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem} |
| .pc-title{font-family:'DM Mono',monospace;font-size:10.5px;letter-spacing:.10em;text-transform:uppercase;color:var(--text2)} |
| .badge{font-size:10px;font-family:'DM Mono',monospace;padding:3px 9px;border-radius:20px} |
| .b-ok{background:var(--teal-light);color:var(--teal-dark)} |
| .b-warn{background:var(--orange-light);color:var(--orange)} |
| .b-bad{background:var(--red-light);color:var(--red)} |
| .cw{position:relative;height:240px} |
| .cw.tall{height:300px} |
| .pnote{font-size:11.5px;color:var(--muted);margin-top:.7rem;line-height:1.6;font-style:italic} |
|
|
| /* axis selectors */ |
| .ax-sel{display:flex;gap:10px;margin-bottom:.8rem;align-items:center;flex-wrap:wrap} |
| .ax-sel label{font-family:'DM Mono',monospace;font-size:10.5px;color:var(--muted)} |
| .ax-sel select{padding:4px 8px;border:1px solid var(--border-s);border-radius:6px;font-size:12px;font-family:'DM Mono',monospace;background:var(--bg-card);color:var(--text)} |
|
|
| /* COEF TABLE */ |
| .coef-table{width:100%;border-collapse:collapse;font-size:13px} |
| .coef-table th{font-family:'DM Mono',monospace;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);text-align:left;padding:6px 12px;border-bottom:1px solid var(--border)} |
| .coef-table td{padding:8px 12px;border-bottom:1px solid var(--border);color:var(--text2);font-family:'DM Mono',monospace;font-size:12.5px} |
| .coef-table tr:last-child td{border-bottom:none} |
| .cbar-w{width:100px;height:6px;background:var(--bg-muted);border-radius:3px;display:inline-block;vertical-align:middle} |
| .cbar{height:100%;border-radius:3px;background:var(--teal)} |
| .cbar.neg{background:var(--orange)} |
| .sig{font-size:11px;color:var(--teal-dark);margin-left:4px} |
| .nsig{font-size:11px;color:var(--muted);margin-left:4px} |
|
|
| /* DIAG SUMMARY */ |
| .diag-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem} |
| .dc{background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:1.2rem;box-shadow:var(--sh-sm)} |
| .dc h4{font-family:'DM Mono',monospace;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;margin-bottom:6px} |
| .dc p{font-size:12.5px;color:var(--muted);line-height:1.65;font-weight:300} |
|
|
| /* GD ANIMATION */ |
| .gd-controls{display:flex;align-items:center;gap:12px;margin-bottom:1rem;flex-wrap:wrap} |
| .gd-btn{padding:7px 16px;border:1.5px solid var(--border-s);border-radius:7px;background:var(--bg-card);font-family:'DM Mono',monospace;font-size:12px;cursor:pointer;transition:all .2s;color:var(--text2)} |
| .gd-btn.play{background:var(--teal);color:#fff;border-color:var(--teal)} |
| .gd-btn:hover:not(.play){border-color:var(--teal);color:var(--teal-dark)} |
| .gd-iter{font-family:'DM Mono',monospace;font-size:13px;color:var(--text2)} |
| .gd-speed{display:flex;align-items:center;gap:6px;font-family:'DM Mono',monospace;font-size:11px;color:var(--muted)} |
| .gd-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem} |
|
|
| /* SPINNER */ |
| .spinner{width:18px;height:18px;border:2.5px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:spin .7s linear infinite} |
| @keyframes spin{to{transform:rotate(360deg)}} |
|
|
| /* PERM IMPORTANCE */ |
| .perm-bars{display:flex;flex-direction:column;gap:8px;padding-top:.5rem} |
| .perm-row{display:flex;align-items:center;gap:10px} |
| .perm-feat{font-family:'DM Mono',monospace;font-size:11px;color:var(--text2);min-width:80px;text-align:right} |
| .perm-bar-w{flex:1;height:8px;background:var(--bg-muted);border-radius:4px;overflow:hidden} |
| .perm-bar-f{height:100%;border-radius:4px;background:var(--teal);transition:width .6s ease} |
| .perm-val{font-family:'DM Mono',monospace;font-size:11px;color:var(--muted);min-width:50px} |
|
|
| /* GROUPED METRICS */ |
| .metrics-2col{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px} |
| .metrics-group{background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:1rem;box-shadow:var(--sh-sm)} |
| .mg-label{font-family:'DM Mono',monospace;font-size:9px;letter-spacing:.14em;text-transform:uppercase;margin-bottom:.65rem;display:flex;align-items:center;gap:8px} |
| .mg-label::after{content:'';flex:1;height:1px;background:var(--border)} |
| .mrow-4{display:grid;grid-template-columns:repeat(4,1fr);gap:8px} |
| .mrow-3{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:1.5rem} |
|
|
| /* SIDEBAR */ |
| .sb{position:fixed;left:-172px;top:50%;transform:translateY(-50%);z-index:500;display:flex;align-items:stretch;transition:left .3s cubic-bezier(.4,0,.2,1)} |
| .sb:hover{left:0} |
| .sb-panel{width:172px;background:var(--bg-card);border:1px solid var(--border-s);border-right:none;border-radius:14px 0 0 14px;box-shadow:var(--sh-lg);padding:.8rem 0;display:flex;flex-direction:column;gap:1px;overflow:hidden} |
| .sb-head{font-family:'DM Mono',monospace;font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);padding:.3rem 1rem .35rem} |
| .sb-sep{height:1px;background:var(--border);margin:.35rem .7rem} |
| .sb-link{display:flex;align-items:center;gap:10px;padding:.55rem 1rem;text-decoration:none;color:var(--text2);font-family:'Outfit',sans-serif;font-size:12.5px;transition:background .15s,color .15s;white-space:nowrap} |
| .sb-link:hover{background:var(--bg-muted);color:var(--text)} |
| .sb-link.active-teal{background:var(--teal-light);color:var(--teal-dark);font-weight:500} |
| .sb-link.active-blue{background:var(--blue-light);color:var(--blue-dark);font-weight:500} |
| .sb-icon{width:18px;height:18px;flex-shrink:0;opacity:.65} |
| .sb-link:hover .sb-icon,.sb-link.active-teal .sb-icon,.sb-link.active-blue .sb-icon{opacity:1} |
| .sb-tab{width:26px;background:var(--bg-card);border:1px solid var(--border-s);border-left:none;border-radius:0 10px 10px 0;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:14px 0;cursor:default;box-shadow:3px 0 8px rgba(28,43,58,.07)} |
|
|
| /* RESPONSIVE */ |
| @media(max-width:900px){.theory-grid,.plots-2,.gd-grid,.diag-grid{grid-template-columns:1fr}.mbar{grid-template-columns:repeat(3,1fr)}.ds-grid{grid-template-columns:repeat(2,1fr)}.metrics-2col{grid-template-columns:1fr}.mrow-4{grid-template-columns:repeat(2,1fr)}} |
| @media(max-width:600px){nav{padding:0 1rem}.ph,.main{padding-left:1rem;padding-right:1rem}.mbar{grid-template-columns:repeat(2,1fr)}.mrow-4,.mrow-3{grid-template-columns:repeat(2,1fr)}} |
| </style> |
| </head> |
| <body> |
|
|
| |
| <div class="sb"> |
| <div class="sb-panel"> |
| <div class="sb-head">Navigation</div> |
| <a class="sb-link" href="/"> |
| <svg class="sb-icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 9L10 2l8 7v9h-5v-5H7v5H2z"/></svg> |
| <span>Home</span> |
| </a> |
| <div class="sb-sep"></div> |
| <div class="sb-head">Models</div> |
| <a class="sb-link active-teal" href="/linear-regression"> |
| <svg class="sb-icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"> |
| <circle cx="3" cy="15" r="1.4" fill="currentColor" stroke="none"/> |
| <circle cx="7" cy="11" r="1.4" fill="currentColor" stroke="none"/> |
| <circle cx="12" cy="8" r="1.4" fill="currentColor" stroke="none"/> |
| <circle cx="16" cy="4" r="1.4" fill="currentColor" stroke="none"/> |
| <line x1="1" y1="17" x2="18" y2="2"/> |
| </svg> |
| <span>Linear Regression</span> |
| </a> |
| <a class="sb-link" href="/logistic-regression"> |
| <svg class="sb-icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"> |
| <path d="M2 16C3 16 5 15 7 13C9 11 11 9 13 7C15 5 17 4 18 4"/> |
| <line x1="1" y1="10" x2="19" y2="10" stroke-dasharray="2.5 2.5" opacity="0.45"/> |
| </svg> |
| <span>Logistic Regression</span> |
| </a> |
| </div> |
| <div class="sb-tab"> |
| <svg width="8" height="14" viewBox="0 0 8 14" fill="none" stroke="var(--muted)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><polyline points="2,2 7,7 2,12"/></svg> |
| </div> |
| </div> |
|
|
| <nav> |
| <a class="nav-brand" href="/"><span></span> SML Β· UniPiraeus</a> |
| <div class="breadcrumb"><a href="/">Home</a> / Linear Regression</div> |
| </nav> |
|
|
| <div class="ph"> |
| <div class="ph-eye">Supervised Learning</div> |
| <h1>Linear Regression</h1> |
| <div class="tags"> |
| <span class="tag tag-sup">Supervised</span> |
| <span class="tag tag-reg">Regression</span> |
| </div> |
| </div> |
|
|
| <div class="main"> |
|
|
| |
| <div class="sl">Theory</div> |
| <div class="theory-grid"> |
| <div class="tc"><h3>What it does</h3><p>Estimates a continuous output by fitting the best-fit hyperplane through data, minimizing the sum of squared residuals (MSE). Each feature receives a coefficient Ξ² that quantifies its exact linear contribution to the prediction.</p></div> |
| <div class="tc"><h3>When to use</h3><p>Target is continuous (yield, price, log-survival). Relationship between features and target is roughly linear. Check residuals are normally distributed (Q-Q plot). Add regularization (Ridge/Lasso) when many features are correlated.</p></div> |
| <div class="tc"><h3>Key strength</h3><p>Fully interpretable β Ξ²<sub>i</sub> means "y changes by Ξ²<sub>i</sub> for every unit increase in x<sub>i</sub>". Fastest model to train. Best first baseline. <strong>Limitation:</strong> assumes linearity; fails on non-linear data and is sensitive to outliers.</p></div> |
| <div class="tc"><h3>Regularization</h3><p><strong>Ridge (L2)</strong> shrinks all coefficients toward zero β handles multicollinearity. <strong>Lasso (L1)</strong> zeroes out irrelevant features entirely β automatic feature selection. Both add a penalty λ·βΞ²β to the loss.</p></div> |
| </div> |
| <div class="formula-card"> |
| <h3>Hypothesis & Loss</h3> |
| <div class="fl lg">Ε· = Ξ²β + Ξ²βxβ + Ξ²βxβ + β¦ + Ξ²βxβ</div> |
| <div class="fl">Loss (MSE) = (1/n) Ξ£ (yα΅’ β Ε·α΅’)Β² Ξ² = (Xα΅X)β»ΒΉ Xα΅y [Normal Eq.]</div> |
| <div class="fl sub">Ridge: Loss + λ·Σβⱼ² | Lasso: Loss + λ·Σ|βⱼ|</div> |
| </div> |
|
|
| |
| <div class="sep"></div> |
| <div class="sl">Dataset Selection</div> |
| <div class="card"> |
| <div class="card-title">Synthetic Datasets β 2D</div> |
| <div class="ds-grid"> |
| <button class="ds-btn active" data-ds="linear" onclick="selectDS(this,'linear')"><div class="ds-name">Linear</div><div class="ds-dim">1 feature</div><div class="ds-type">synthetic</div></button> |
| <button class="ds-btn" data-ds="polynomial" onclick="selectDS(this,'polynomial')"><div class="ds-name">Polynomial</div><div class="ds-dim">1 feature</div><div class="ds-type">synthetic</div></button> |
| <button class="ds-btn" data-ds="sinusoidal" onclick="selectDS(this,'sinusoidal')"><div class="ds-name">Sinusoidal</div><div class="ds-dim">1 feature</div><div class="ds-type">synthetic</div></button> |
| <button class="ds-btn" data-ds="heteroscedastic" onclick="selectDS(this,'heteroscedastic')"><div class="ds-name">Heteroscedastic</div><div class="ds-dim">1 feature</div><div class="ds-type">synthetic</div></button> |
| </div> |
| <div class="card-title" style="margin-top:1.2rem">Real Datasets β Multi-feature</div> |
| <div class="ds-grid"> |
| <button class="ds-btn" data-ds="diabetes" onclick="selectDS(this,'diabetes')"><div class="ds-name">Diabetes</div><div class="ds-dim">10D Β· 442 rows</div><div class="ds-type">real</div></button> |
| <button class="ds-btn" data-ds="wine_quality" onclick="selectDS(this,'wine_quality')"><div class="ds-name">Wine</div><div class="ds-dim">13D Β· 178 rows</div><div class="ds-type">real</div></button> |
| <button class="ds-btn" data-ds="linnerud" onclick="selectDS(this,'linnerud')"><div class="ds-name">Linnerud</div><div class="ds-dim">3D Β· 20 rows</div><div class="ds-type">real</div></button> |
| </div> |
|
|
| <div id="paramsPanel"></div> |
|
|
| |
| <div class="params-panel" style="margin-bottom:.8rem"> |
| <div class="pg-grid"> |
| <div class="pg"> |
| <label>Train / Test split <span id="splitLbl">80 / 20 %</span></label> |
| <input type="range" id="splitSlider" min="50" max="90" step="5" value="80" oninput="updateSplit(this.value)"> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="train-row"> |
| <div> |
| <div style="font-family:'DM Mono',monospace;font-size:10px;letter-spacing:.10em;text-transform:uppercase;color:var(--muted);margin-bottom:6px">Model</div> |
| <div class="model-tabs"> |
| <button class="mt active" onclick="selectModel(this,'linear')">OLS</button> |
| <button class="mt" onclick="selectModel(this,'ridge')">Ridge</button> |
| <button class="mt" onclick="selectModel(this,'lasso')">Lasso</button> |
| </div> |
| </div> |
| <div id="alphaRow" style="display:none"> |
| <div style="font-family:'DM Mono',monospace;font-size:10px;letter-spacing:.10em;text-transform:uppercase;color:var(--muted);margin-bottom:6px">Ξ» (alpha)</div> |
| <div class="alpha-wrap"> |
| <input type="range" id="alphaSlider" min="-3" max="3" step="0.1" value="0" style="width:120px;accent-color:var(--blue)" oninput="updateAlpha(this.value)"> |
| <span id="alphaLbl" style="font-family:'DM Mono',monospace;font-size:13px;color:var(--blue-dark)">1.00</span> |
| </div> |
| </div> |
| <button class="btn-train" id="trainBtn" onclick="runTrain()"> |
| <svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24"><polygon points="5,3 19,12 5,21"/></svg> |
| Train Model |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="results" style="display:none"> |
|
|
| |
| <div class="sl">Performance Metrics</div> |
| <div id="metricsContainer"></div> |
|
|
| |
| <div class="sl">Model Fit</div> |
| <div class="plots-2"> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Scatter + Regression Line</div></div> |
| <div id="axSel" class="ax-sel" style="display:none"></div> |
| <div class="cw"><canvas id="cScatter"></canvas></div> |
| <div class="pnote" id="scatterNote"></div> |
| </div> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Actual vs Predicted</div></div> |
| <div class="cw"><canvas id="cAvp"></canvas></div> |
| <div class="pnote">Perfect predictions lie on the diagonal. Systematic deviation β model bias.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="sl">Coefficients & Confidence Intervals</div> |
| <div class="plots-2"> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Coefficient Values</div></div> |
| <div style="overflow-x:auto"> |
| <table class="coef-table"> |
| <thead><tr><th>Feature</th><th>Coef</th><th>95% CI</th><th>p-value</th><th>Magnitude</th></tr></thead> |
| <tbody id="coefBody"></tbody> |
| </table> |
| </div> |
| <div class="pnote">* p<0.05 significant. CI only available for OLS (not Ridge/Lasso).</div> |
| </div> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Coefficient Confidence Intervals (OLS)</div></div> |
| <div class="cw"><canvas id="cCI"></canvas></div> |
| <div class="pnote">Error bars = 95% CI. Bars crossing zero indicate non-significant features.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="sl">Model Understanding</div> |
| <div class="plots-2"> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Learning Curve</div></div> |
| <div class="cw"><canvas id="cLC"></canvas></div> |
| <div class="pnote">Train vs validation RΒ² as training set grows. Large gap β high variance (overfit). Both low β high bias (underfit).</div> |
| </div> |
| <div class="pc"> |
| <div class="pc-head"> |
| <div class="pc-title">Regularization Path</div> |
| <div style="display:flex;gap:6px"> |
| <button class="gd-btn" id="regPathRidgeBtn" onclick="showRegPath('ridge')" style="padding:4px 10px;font-size:10px">Ridge</button> |
| <button class="gd-btn" id="regPathLassoBtn" onclick="showRegPath('lasso')" style="padding:4px 10px;font-size:10px">Lasso</button> |
| </div> |
| </div> |
| <div class="cw"><canvas id="cRegPath"></canvas></div> |
| <div class="pnote">Coefficients vs logββ(Ξ»). Lasso zeroes features (feature selection); Ridge shrinks them gradually.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="plots-2"> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Permutation Feature Importance</div></div> |
| <div id="permBars" class="perm-bars"></div> |
| <div class="pnote">Drop in RΒ² when a feature is shuffled (avg over 20 repeats). Larger drop β more important feature.</div> |
| </div> |
| <div class="pc" id="partialRegCard" style="display:none"> |
| <div class="pc-head"> |
| <div class="pc-title">Partial Regression Plots</div> |
| <select id="prSel" onchange="showPartialReg(this.value)" style="padding:4px 8px;border:1px solid var(--border-s);border-radius:6px;font-size:12px;font-family:'DM Mono',monospace;background:var(--bg-card)"></select> |
| </div> |
| <div class="cw"><canvas id="cPR"></canvas></div> |
| <div class="pnote">Effect of selected feature after removing influence of all other features. Slope = partial regression coefficient.</div> |
| </div> |
| <div class="pc" id="permSyntheticCard" style="display:none"> |
| <div class="pc-head"><div class="pc-title">Residuals Distribution</div></div> |
| <div class="cw"><canvas id="cHist"></canvas></div> |
| <div class="pnote">Distribution of test-set residuals. A symmetric bell shape supports normality assumption.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="sl">Assumption Diagnostics</div> |
| <div class="plots-2"> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Residuals vs Fitted</div><div class="badge" id="bRvf">β</div></div> |
| <div class="cw"><canvas id="cRvf"></canvas></div> |
| <div class="pnote">Should be random around zero. A funnel or curve β heteroscedasticity or non-linearity.</div> |
| </div> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Q-Q Plot (Normality)</div><div class="badge" id="bQQ">β</div></div> |
| <div class="cw"><canvas id="cQQ"></canvas></div> |
| <div class="pnote" id="qqNote">Points near diagonal β residuals approximately normal.</div> |
| </div> |
| </div> |
| <div class="plots-2"> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Scale-Location (Homoscedasticity)</div><div class="badge" id="bSL">β</div></div> |
| <div class="cw"><canvas id="cSL"></canvas></div> |
| <div class="pnote">Flat red trend β equal variance. Rising trend β heteroscedastic errors.</div> |
| </div> |
| <div class="pc"> |
| <div class="pc-head"><div class="pc-title">Residuals vs Leverage (Cook's Distance)</div><div class="badge" id="bCooks">β</div></div> |
| <div class="cw"><canvas id="cCooks"></canvas></div> |
| <div class="pnote">Points beyond the dashed threshold are influential β they disproportionately affect the fit.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="sl">Interactive β Gradient Descent</div> |
| <div class="card"> |
| <div class="card-title">Gradient Descent Animation (1-D Regression)</div> |
| <div class="gd-controls"> |
| <button class="gd-btn play" id="gdPlayBtn" onclick="gdToggle()">βΆ Play</button> |
| <button class="gd-btn" onclick="gdReset()">βΊ Reset</button> |
| <div class="gd-iter">Iteration: <span id="gdIter">0</span> / <span id="gdTotal">β</span></div> |
| <div class="gd-speed"> |
| Speed: |
| <input type="range" id="gdSpeed" min="50" max="500" step="50" value="200" style="width:80px;accent-color:var(--teal)"> |
| </div> |
| <div style="font-family:'DM Mono',monospace;font-size:12px;color:var(--text2)"> |
| Ξ²β=<span id="gdB0">β</span> Ξ²β=<span id="gdB1">β</span> MSE=<span id="gdMSE">β</span> |
| </div> |
| </div> |
| <div class="gd-grid"> |
| <div class="pc" style="margin:0"><div class="pc-head"><div class="pc-title">Data + Regression Line</div></div><div class="cw"><canvas id="gdLine"></canvas></div></div> |
| <div class="pc" style="margin:0"><div class="pc-head"><div class="pc-title">MSE over Iterations</div></div><div class="cw"><canvas id="gdLoss"></canvas></div></div> |
| <div class="pc" style="margin:0"><div class="pc-head"><div class="pc-title">Loss Surface (Ξ²β, Ξ²β)</div></div><div class="cw"><canvas id="gdSurface"></canvas></div></div> |
| </div> |
| <div class="pnote" style="margin-top:1rem">Gradient descent updates Ξ²β, Ξ²β iteratively: Ξ² β Ξ² β Ξ·Β·βMSE. The path on the loss surface shows how quickly it converges to the minimum.</div> |
| </div> |
|
|
| |
| <div class="sl">Diagnostic Summary</div> |
| <div class="diag-grid" id="diagGrid"></div> |
|
|
| </div> |
| </div> |
|
|
| <script> |
| |
| |
| |
| const S = { |
| dataset:'linear', dsType:'synthetic', |
| modelType:'linear', alpha:1.0, testSize:.2, |
| featureNames:[], scatterFx:0, |
| result:null, regPathData:null, regPathMode:'ridge', |
| partialRegs:[], currentPR:0, |
| }; |
| const SYN = new Set(['linear','polynomial','sinusoidal','heteroscedastic']); |
| let CH = {}; |
| |
| function dc(id){ if(CH[id]){CH[id].destroy();delete CH[id];} } |
| |
| const COLORS = { |
| teal:'rgba(29,158,117,', blue:'rgba(55,138,221,', |
| orange:'rgba(224,122,48,', red:'rgba(192,57,43,', |
| text:'#1C2B3A', muted:'#8298AE', |
| }; |
| const axOpts = (xl,yl) => ({ |
| x:{title:{display:true,text:xl,font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'}}, |
| y:{title:{display:true,text:yl,font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'}}, |
| }); |
| const legOpts = { labels:{font:{family:"'DM Mono',monospace",size:11},padding:12} }; |
| |
| |
| |
| |
| function selectDS(btn, ds){ |
| document.querySelectorAll('.ds-btn').forEach(b=>b.classList.remove('active')); |
| btn.classList.add('active'); |
| S.dataset=ds; S.dsType=SYN.has(ds)?'synthetic':'real'; |
| renderParams(); |
| document.getElementById('results').style.display='none'; |
| } |
| |
| function renderParams(){ |
| const p=document.getElementById('paramsPanel'); |
| p.innerHTML = S.dsType==='synthetic' ? buildSynParams(S.dataset) |
| : `<div class="params-panel"><div class="pg-grid"> |
| <div class="pg"><label>Dataset</label> |
| <div style="font-size:13px;color:var(--text2);padding-top:4px">${S.dataset} β all features used for training</div></div> |
| </div></div>`; |
| if(S.dsType==='real') |
| fetch(`/api/dataset-info/${S.dataset}`).then(r=>r.json()).then(d=>{ S.featureNames=d.features||[]; }); |
| } |
| |
| function buildSynParams(ds){ |
| let ex=''; |
| if(ds==='polynomial') ex=` |
| <div class="pg"><label>Degree <span id="degLbl">2</span></label><input type="range" id="degSlider" min="2" max="6" step="1" value="2" oninput="document.getElementById('degLbl').textContent=this.value"></div> |
| <div class="pg"><label>Outlier fraction <span id="outLbl">0%</span></label><input type="range" id="outSlider" min="0" max="0.25" step="0.01" value="0" oninput="document.getElementById('outLbl').textContent=Math.round(this.value*100)+'%'"></div>`; |
| if(ds==='sinusoidal') ex=` |
| <div class="pg"><label>Frequency <span id="freqLbl">1.0</span></label><input type="range" id="freqSlider" min="0.2" max="4" step="0.1" value="1.0" oninput="document.getElementById('freqLbl').textContent=parseFloat(this.value).toFixed(1)"></div> |
| <div class="pg"><label>Amplitude <span id="ampLbl">1.0</span></label><input type="range" id="ampSlider" min="0.5" max="5" step="0.25" value="1.0" oninput="document.getElementById('ampLbl').textContent=parseFloat(this.value).toFixed(2)"></div> |
| <div class="pg"><label>Phase Ο <span id="phaseLbl">0.00</span></label><input type="range" id="phaseSlider" min="0" max="6.28" step="0.1" value="0" oninput="document.getElementById('phaseLbl').textContent=parseFloat(this.value).toFixed(2)"></div>`; |
| if(ds==='linear'||ds==='heteroscedastic') ex=` |
| <div class="pg"><label>Slope <span id="slopeLbl">2.0</span></label><input type="range" id="slopeSlider" min="-5" max="5" step="0.25" value="2.0" oninput="document.getElementById('slopeLbl').textContent=parseFloat(this.value).toFixed(2)"></div> |
| <div class="pg"><label>Intercept <span id="intLbl">1.0</span></label><input type="range" id="intSlider" min="-5" max="5" step="0.25" value="1.0" oninput="document.getElementById('intLbl').textContent=parseFloat(this.value).toFixed(2)"></div> |
| <div class="pg"><label>Outlier fraction <span id="outLbl">0%</span></label><input type="range" id="outSlider" min="0" max="0.25" step="0.01" value="0" oninput="document.getElementById('outLbl').textContent=Math.round(this.value*100)+'%'"></div>`; |
| return `<div class="params-panel"><div class="pg-grid"> |
| <div class="pg"><label>N samples <span id="nLbl">200</span></label><input type="range" id="nSlider" min="50" max="800" step="25" value="200" oninput="document.getElementById('nLbl').textContent=this.value"></div> |
| <div class="pg"><label>Noise Ο <span id="noiseLbl">0.30</span></label><input type="range" id="noiseSlider" min="0" max="2" step="0.05" value="0.30" oninput="document.getElementById('noiseLbl').textContent=parseFloat(this.value).toFixed(2)"></div> |
| ${ex} |
| </div></div>`; |
| } |
| |
| function updateSplit(v){ S.testSize=(100-parseInt(v))/100; document.getElementById('splitLbl').textContent=`${v} / ${100-parseInt(v)} %`; } |
| function selectModel(btn,mt){ document.querySelectorAll('.mt').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); S.modelType=mt; document.getElementById('alphaRow').style.display=mt==='linear'?'none':'flex'; } |
| function updateAlpha(v){ S.alpha=Math.pow(10,parseFloat(v)); document.getElementById('alphaLbl').textContent=S.alpha.toFixed(S.alpha<0.1?4:S.alpha<10?2:1); } |
| |
| |
| |
| |
| async function runTrain(){ |
| const btn=document.getElementById('trainBtn'); |
| btn.disabled=true; btn.innerHTML='<div class="spinner"></div> Trainingβ¦'; |
| |
| const body={ |
| dataset_type:S.dsType, test_size:S.testSize, |
| model_type:S.modelType, alpha:S.alpha, |
| feature_x:String(S.scatterFx), |
| }; |
| if(S.dsType==='synthetic'){ |
| body.synthetic_config={ |
| dataset_type:S.dataset, |
| n_samples:parseInt(document.getElementById('nSlider')?.value||200), |
| noise:parseFloat(document.getElementById('noiseSlider')?.value||.3), |
| outlier_fraction:parseFloat(document.getElementById('outSlider')?.value||0), |
| degree:parseInt(document.getElementById('degSlider')?.value||2), |
| frequency:parseFloat(document.getElementById('freqSlider')?.value||1), |
| amplitude:parseFloat(document.getElementById('ampSlider')?.value||1), |
| phase:parseFloat(document.getElementById('phaseSlider')?.value||0), |
| slope:parseFloat(document.getElementById('slopeSlider')?.value||2), |
| intercept:parseFloat(document.getElementById('intSlider')?.value||1), |
| }; |
| } else { |
| body.real_config={dataset_name:S.dataset}; |
| } |
| |
| try{ |
| const res=await fetch('/api/linear-regression/train',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}); |
| const d=await res.json(); |
| if(!d.ok){alert('Error: '+d.error);return;} |
| S.result=d; S.featureNames=d.feature_names||[]; |
| S.regPathData=d.reg_path; S.partialRegs=d.partial_regression||[]; |
| renderAll(d); |
| }catch(e){alert('Network error: '+e.message);} |
| finally{btn.disabled=false;btn.innerHTML='<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24"><polygon points="5,3 19,12 5,21"/></svg> Train Model';} |
| } |
| |
| |
| |
| |
| function renderAll(d){ |
| document.getElementById('results').style.display=''; |
| renderMetrics(d.metrics, d.shapiro); |
| renderScatter(d.scatter, d.feature_names); |
| renderAvP(d.avp); |
| renderCoefs(d.coefs, d.coef_ci, d.feature_names); |
| renderCI(d.coef_ci, d.feature_names); |
| renderLC(d.learning_curve); |
| showRegPath(S.regPathMode); |
| renderPermImportance(d.perm_importance); |
| renderPartialRegPanel(d.partial_regression, d.is_synthetic); |
| renderRvF(d.rvf); |
| renderQQ(d.qq, d.shapiro); |
| renderSL(d.sl); |
| renderCooks(d.cooks); |
| gdInit(d.gradient_descent); |
| renderDiag(d); |
| document.getElementById('results').scrollIntoView({behavior:'smooth',block:'start'}); |
| } |
| |
| |
| |
| |
| function renderMetrics(m, sw){ |
| const r2c_tr = m.r2_train>0.7?'good':m.r2_train>0.4?'':'warn'; |
| const r2c_te = m.r2_test >0.7?'good':m.r2_test >0.4?'':'warn'; |
| const mapeStr = v => isFinite(v) ? v.toFixed(1)+'%' : 'N/A'; |
| document.getElementById('metricsContainer').innerHTML=` |
| <div class="metrics-2col"> |
| <div class="metrics-group"> |
| <div class="mg-label" style="color:var(--teal-dark)">Training</div> |
| <div class="mrow-4"> |
| <div class="mc ${r2c_tr}"><div class="lbl">RΒ²</div><div class="val">${m.r2_train.toFixed(3)}</div></div> |
| <div class="mc"><div class="lbl">RMSE</div><div class="val">${m.rmse_train.toFixed(3)}</div></div> |
| <div class="mc"><div class="lbl">MAE</div><div class="val">${m.mae_train.toFixed(3)}</div></div> |
| <div class="mc"><div class="lbl">MAPE</div><div class="val">${mapeStr(m.mape_train)}</div></div> |
| </div> |
| </div> |
| <div class="metrics-group"> |
| <div class="mg-label" style="color:var(--blue-dark)">Testing</div> |
| <div class="mrow-4"> |
| <div class="mc ${r2c_te}"><div class="lbl">RΒ²</div><div class="val">${m.r2_test.toFixed(3)}</div></div> |
| <div class="mc"><div class="lbl">RMSE</div><div class="val">${m.rmse_test.toFixed(3)}</div></div> |
| <div class="mc"><div class="lbl">MAE</div><div class="val">${m.mae_test.toFixed(3)}</div></div> |
| <div class="mc"><div class="lbl">MAPE</div><div class="val">${mapeStr(m.mape_test)}</div></div> |
| </div> |
| </div> |
| </div> |
| <div class="mrow-3"> |
| <div class="mc"><div class="lbl">N Train</div><div class="val">${m.n_train}</div><div class="sub">samples</div></div> |
| <div class="mc"><div class="lbl">N Test</div><div class="val">${m.n_test}</div><div class="sub">samples</div></div> |
| <div class="mc ${sw.normal?'good':'warn'}"><div class="lbl">Shapiro p</div><div class="val">${sw.p.toFixed(4)}</div><div class="sub">${sw.normal?'Normal β':'Non-normal !'}</div></div> |
| </div>`; |
| } |
| |
| |
| |
| |
| function renderScatter(sc, fn){ |
| dc('scatter'); |
| const isSyn=!!sc.x_line; |
| const axDiv=document.getElementById('axSel'); |
| if(!isSyn && fn.length>1){ |
| axDiv.style.display='flex'; |
| axDiv.innerHTML=`<label>X-axis:</label><select id="fxSel" onchange="changeAxis(this.value)">${fn.map((f,i)=>`<option value="${i}" ${i===S.scatterFx?'selected':''}>${f}</option>`).join('')}</select>`; |
| } else { axDiv.style.display='none'; } |
| |
| const datasets=[ |
| {label:'Train',data:sc.x_train.map((x,i)=>({x,y:sc.y_train[i]})),backgroundColor:COLORS.teal+'0.5)',pointRadius:3.5}, |
| {label:'Test', data:sc.x_test.map((x,i)=>({x,y:sc.y_test[i]})), backgroundColor:COLORS.blue+'0.55)',pointRadius:3.5}, |
| ]; |
| if(isSyn) datasets.push({label:'Regression line',data:sc.x_line.map((x,i)=>({x,y:sc.y_line[i]})),type:'line',borderColor:'#E07A30',borderWidth:2.5,pointRadius:0,fill:false,tension:0}); |
| |
| CH.scatter=new Chart(document.getElementById('cScatter'),{ |
| type:'scatter',data:{datasets}, |
| options:{responsive:true,maintainAspectRatio:false,plugins:{legend:legOpts},scales:axOpts(isSyn?'x':(sc.fx_name||fn[0]),'y')} |
| }); |
| document.getElementById('scatterNote').textContent=isSyn?'Orange = fitted line. Green = train, Blue = test.': |
| `Showing "${sc.fx_name||fn[0]}" vs target. All features used in model.`; |
| } |
| function changeAxis(idx){ S.scatterFx=parseInt(idx); if(S.result) runTrain(); } |
| |
| |
| |
| |
| function renderAvP(avp){ |
| dc('avp'); |
| const mn=Math.min(...avp.actual,...avp.predicted), mx=Math.max(...avp.actual,...avp.predicted); |
| CH.avp=new Chart(document.getElementById('cAvp'),{ |
| type:'scatter', |
| data:{datasets:[ |
| {label:'Predictions',data:avp.actual.map((a,i)=>({x:a,y:avp.predicted[i]})),backgroundColor:COLORS.blue+'0.45)',pointRadius:3}, |
| {label:'Perfect fit',data:[{x:mn,y:mn},{x:mx,y:mx}],type:'line',borderColor:'rgba(28,43,58,0.3)',borderWidth:1.5,borderDash:[5,4],pointRadius:0}, |
| ]}, |
| options:{responsive:true,maintainAspectRatio:false,plugins:{legend:legOpts},scales:axOpts('Actual','Predicted')} |
| }); |
| } |
| |
| |
| |
| |
| function renderCoefs(coefs, ci, fn){ |
| const vals=Object.values(coefs); const maxA=Math.max(...vals.map(Math.abs)); |
| document.getElementById('coefBody').innerHTML=Object.entries(coefs).map(([name,val])=>{ |
| const pct=maxA>0?Math.abs(val)/maxA*100:0; const neg=val<0; |
| const info=ci[name]; const pStr=info?info.p_val.toFixed(3):'β'; |
| const ciStr=info?`[${info.ci_lo.toFixed(3)}, ${info.ci_hi.toFixed(3)}]`:'β'; |
| const sig=info&&info.p_val<.05; |
| return `<tr> |
| <td style="color:var(--text);font-weight:500">${name}</td> |
| <td style="color:${neg?'var(--orange)':'var(--teal-dark)'}">${val>=0?'+':''}${val.toFixed(4)}</td> |
| <td style="font-size:11px;color:var(--muted)">${ciStr}</td> |
| <td>${pStr}${info?(sig?'<span class="sig">*</span>':'<span class="nsig">ns</span>'):''}</td> |
| <td><span class="cbar-w"><span class="cbar${neg?' neg':''}" style="width:${pct.toFixed(1)}%"></span></span></td> |
| </tr>`; |
| }).join(''); |
| } |
| |
| function renderCI(ci, fn){ |
| dc('ci'); |
| if(!ci||Object.keys(ci).length===0){ |
| document.getElementById('cCI').parentElement.innerHTML='<div style="text-align:center;padding:3rem;color:var(--muted);font-size:13px">CI available for OLS only</div>'; |
| return; |
| } |
| const names=Object.keys(ci).filter(k=>k!=='intercept'); |
| const coefs=names.map(k=>ci[k].coef); |
| const ciLo=names.map(k=>ci[k].ci_lo); |
| const ciHi=names.map(k=>ci[k].ci_hi); |
| |
| CH.ci=new Chart(document.getElementById('cCI'),{ |
| type:'bar', |
| data:{labels:names,datasets:[ |
| {label:'Coefficient',data:coefs,backgroundColor:names.map((_,i)=>coefs[i]<0?COLORS.orange+'0.7)':COLORS.teal+'0.7)'),borderRadius:4, |
| errorBars:names.reduce((o,n,i)=>{o[i]={plus:ciHi[i]-coefs[i],minus:coefs[i]-ciLo[i]};return o;},{})}, |
| ]}, |
| options:{ |
| responsive:true,maintainAspectRatio:false, |
| plugins:{legend:{display:false}, |
| tooltip:{callbacks:{label:ctx=>{const n=names[ctx.dataIndex];return [`Coef: ${ci[n].coef.toFixed(4)}`,`95% CI: [${ci[n].ci_lo.toFixed(4)}, ${ci[n].ci_hi.toFixed(4)}]`,`p = ${ci[n].p_val.toFixed(4)}`];}}} |
| }, |
| scales:{ |
| x:{grid:{display:false},ticks:{font:{family:"'DM Mono',monospace",size:10}}}, |
| y:{title:{display:true,text:'Coefficient value',font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'}, |
| afterDataLimits:sc=>{const abs=Math.max(Math.abs(sc.min),Math.abs(sc.max));sc.min=-abs*1.2;sc.max=abs*1.2;}}, |
| } |
| } |
| }); |
| |
| const orig=CH.ci.draw.bind(CH.ci); |
| CH.ci.draw=function(){ |
| orig(); |
| const ctx2=CH.ci.ctx, meta=CH.ci.getDatasetMeta(0); |
| ctx2.save(); ctx2.strokeStyle='rgba(28,43,58,0.55)'; ctx2.lineWidth=2; |
| meta.data.forEach((bar,i)=>{ |
| const x=bar.x; |
| const yLo=CH.ci.scales.y.getPixelForValue(ciLo[i]); |
| const yHi=CH.ci.scales.y.getPixelForValue(ciHi[i]); |
| ctx2.beginPath(); ctx2.moveTo(x,yLo); ctx2.lineTo(x,yHi); ctx2.stroke(); |
| [yLo,yHi].forEach(yy=>{ctx2.beginPath();ctx2.moveTo(x-5,yy);ctx2.lineTo(x+5,yy);ctx2.stroke();}); |
| }); |
| ctx2.restore(); |
| }; |
| } |
| |
| |
| |
| |
| function renderLC(lc){ |
| dc('lc'); |
| CH.lc=new Chart(document.getElementById('cLC'),{ |
| type:'line', |
| data:{labels:lc.sizes,datasets:[ |
| {label:'Train RΒ²',data:lc.train,borderColor:COLORS.teal+'1)',backgroundColor:COLORS.teal+'0.1)',borderWidth:2.2,fill:true,tension:.4,pointRadius:4}, |
| {label:'Val RΒ²', data:lc.val, borderColor:COLORS.blue+'1)',backgroundColor:COLORS.blue+'0.1)',borderWidth:2.2,fill:true,tension:.4,pointRadius:4}, |
| ]}, |
| options:{responsive:true,maintainAspectRatio:false,plugins:{legend:legOpts}, |
| scales:{x:{title:{display:true,text:'Training set size',font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'}}, |
| y:{title:{display:true,text:'RΒ²',font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'},min:-0.1,max:1.05}}} |
| }); |
| } |
| |
| |
| |
| |
| const REG_COLORS=['#1D9E75','#378ADD','#E07A30','#C0392B','#8E44AD','#16A085','#D35400','#2980B9','#27AE60','#E74C3C','#F39C12','#2C3E50','#95A5A6']; |
| |
| function showRegPath(mode){ |
| S.regPathMode=mode; |
| ['Ridge','Lasso'].forEach(m=>{ |
| const b=document.getElementById(`regPath${m}Btn`); |
| b.style.background=mode===m.toLowerCase()?'var(--teal)':''; |
| b.style.color=mode===m.toLowerCase()?'#fff':''; |
| b.style.borderColor=mode===m.toLowerCase()?'var(--teal)':''; |
| }); |
| if(!S.regPathData) return; |
| dc('regPath'); |
| const rp=S.regPathData; |
| const coefs=mode==='ridge'?rp.ridge_coefs:rp.lasso_coefs; |
| const fn=rp.feature_names; |
| |
| const datasets=fn.slice(0,10).map((name,j)=>({ |
| label:name, |
| data:coefs.map(row=>row[j]), |
| borderColor:REG_COLORS[j%REG_COLORS.length], |
| borderWidth:1.8,pointRadius:0,fill:false,tension:.3, |
| })); |
| CH.regPath=new Chart(document.getElementById('cRegPath'),{ |
| type:'line', |
| data:{labels:rp.alphas.map(v=>v.toFixed(1)),datasets}, |
| options:{responsive:true,maintainAspectRatio:false, |
| plugins:{legend:{labels:{font:{family:"'DM Mono',monospace",size:9},padding:8,boxWidth:10}}}, |
| scales:{ |
| x:{title:{display:true,text:'logββ(Ξ»)',font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'},ticks:{maxTicksLimit:8}}, |
| y:{title:{display:true,text:'Coefficient value',font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'}}, |
| }} |
| }); |
| } |
| |
| |
| |
| |
| function renderPermImportance(imp){ |
| if(!imp||imp.length===0){document.getElementById('permBars').innerHTML='<div style="color:var(--muted);font-size:13px">Not available</div>';return;} |
| const max=Math.max(...imp.map(i=>Math.max(i.mean,0)))||1; |
| document.getElementById('permBars').innerHTML=imp.map(it=>` |
| <div class="perm-row"> |
| <div class="perm-feat">${it.feature}</div> |
| <div class="perm-bar-w"><div class="perm-bar-f" style="width:${Math.max(0,it.mean/max*100).toFixed(1)}%;background:${it.mean<0?'var(--orange)':'var(--teal)'}"></div></div> |
| <div class="perm-val">${it.mean.toFixed(3)}</div> |
| </div>`).join(''); |
| } |
| |
| |
| |
| |
| function renderPartialRegPanel(prs, isSynthetic){ |
| document.getElementById('partialRegCard').style.display = (!isSynthetic&&prs.length>0) ? '' : 'none'; |
| document.getElementById('permSyntheticCard').style.display = isSynthetic ? '' : 'none'; |
| if(isSynthetic){ renderHist(S.result.rvf.residuals); return; } |
| if(!prs||prs.length===0) return; |
| const sel=document.getElementById('prSel'); |
| sel.innerHTML=prs.map((p,i)=>`<option value="${i}">${p.feature}</option>`).join(''); |
| showPartialReg(0); |
| } |
| function showPartialReg(idx){ |
| S.currentPR=parseInt(idx); |
| const pr=S.partialRegs[S.currentPR]; if(!pr) return; |
| dc('pr'); |
| const mn=Math.min(...pr.ex), mx=Math.max(...pr.ex); |
| const yLine=[mn,mx].map(x=>pr.slope*x); |
| CH.pr=new Chart(document.getElementById('cPR'),{ |
| type:'scatter', |
| data:{datasets:[ |
| {label:'Observations',data:pr.ex.map((x,i)=>({x,y:pr.ey[i]})),backgroundColor:COLORS.teal+'0.5)',pointRadius:3}, |
| {label:`slope=${pr.slope.toFixed(3)}`,data:[{x:mn,y:yLine[0]},{x:mx,y:yLine[1]}],type:'line',borderColor:'#E07A30',borderWidth:2,pointRadius:0,fill:false}, |
| ]}, |
| options:{responsive:true,maintainAspectRatio:false,plugins:{legend:legOpts}, |
| scales:axOpts(`e(${pr.feature}|others)`,`e(y|others)`)} |
| }); |
| } |
| |
| |
| function renderHist(residuals){ |
| dc('hist'); |
| const bins=20, mn=Math.min(...residuals), mx=Math.max(...residuals), step=(mx-mn)/bins; |
| const counts=Array(bins).fill(0); |
| const labels=Array.from({length:bins},(_,i)=>(mn+i*step).toFixed(2)); |
| residuals.forEach(r=>{ let b=Math.floor((r-mn)/step); if(b>=bins)b=bins-1; counts[b]++; }); |
| CH.hist=new Chart(document.getElementById('cHist'),{ |
| type:'bar',data:{labels,datasets:[{label:'Count',data:counts,backgroundColor:COLORS.teal+'0.55)',borderColor:COLORS.teal+'0.9)',borderWidth:1,borderRadius:3}]}, |
| options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{title:{display:true,text:'Residual',font:{size:11}},grid:{display:false},ticks:{maxTicksLimit:8}},y:{title:{display:true,text:'Freq',font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'}}}} |
| }); |
| } |
| |
| |
| |
| |
| function corr(a,b){ const ma=a.reduce((s,v)=>s+v,0)/a.length, mb=b.reduce((s,v)=>s+v,0)/b.length; const num=a.reduce((s,v,i)=>s+(v-ma)*(b[i]-mb),0); const da=Math.sqrt(a.reduce((s,v)=>s+(v-ma)**2,0)), db=Math.sqrt(b.reduce((s,v)=>s+(v-mb)**2,0)); return num/(da*db+1e-12); } |
| |
| function renderRvF(rvf){ |
| dc('rvf'); |
| const c=Math.abs(corr(rvf.fitted,rvf.residuals.map(Math.abs))); |
| const b=document.getElementById('bRvf'); |
| b.textContent=c>0.25?'Pattern detected':'Random β'; b.className='badge '+(c>0.25?'b-warn':'b-ok'); |
| const mn=Math.min(...rvf.fitted), mx=Math.max(...rvf.fitted); |
| CH.rvf=new Chart(document.getElementById('cRvf'),{ |
| type:'scatter',data:{datasets:[ |
| {label:'Residual',data:rvf.fitted.map((f,i)=>({x:f,y:rvf.residuals[i]})),backgroundColor:COLORS.teal+'0.4)',pointRadius:2.5}, |
| {label:'Zero',data:[{x:mn,y:0},{x:mx,y:0}],type:'line',borderColor:COLORS.orange+'0.7)',borderWidth:1.5,borderDash:[4,4],pointRadius:0}, |
| ]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:legOpts},scales:axOpts('Fitted','Residuals')} |
| }); |
| } |
| |
| function renderQQ(qq,sw){ |
| dc('qq'); |
| const b=document.getElementById('bQQ'); |
| b.textContent=sw.normal?'Normal β':'Non-normal !'; b.className='badge '+(sw.normal?'b-ok':'b-warn'); |
| document.getElementById('qqNote').textContent=`Shapiro-Wilk: W=${sw.stat.toFixed(4)}, p=${sw.p.toFixed(4)}. `+(sw.normal?'Residuals approx. normal (p>0.05).':'Residuals deviate from normality (pβ€0.05).'); |
| const mn=Math.min(...qq.theoretical), mx=Math.max(...qq.theoretical); |
| const std=Math.sqrt(qq.sample.reduce((s,v)=>s+(v-(qq.sample.reduce((a,b)=>a+b,0)/qq.sample.length))**2,0)/qq.sample.length); |
| CH.qq=new Chart(document.getElementById('cQQ'),{ |
| type:'scatter',data:{datasets:[ |
| {label:'Q-Q',data:qq.theoretical.map((t,i)=>({x:t,y:qq.sample[i]})),backgroundColor:COLORS.blue+'0.5)',pointRadius:2.5}, |
| {label:'Normal',data:[{x:mn,y:mn*std},{x:mx,y:mx*std}],type:'line',borderColor:COLORS.orange+'0.8)',borderWidth:1.5,borderDash:[4,4],pointRadius:0}, |
| ]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:legOpts},scales:axOpts('Theoretical Quantiles','Sample Quantiles')} |
| }); |
| } |
| |
| function renderSL(sl){ |
| dc('sl'); |
| const c=Math.abs(corr(sl.fitted,sl.sqrt_abs_resid)); |
| const b=document.getElementById('bSL'); |
| b.textContent=c>0.25?'Heteroscedastic !':'Homoscedastic β'; b.className='badge '+(c>0.25?'b-warn':'b-ok'); |
| CH.sl=new Chart(document.getElementById('cSL'),{ |
| type:'scatter',data:{datasets:[{label:'β|Res|',data:sl.fitted.map((f,i)=>({x:f,y:sl.sqrt_abs_resid[i]})),backgroundColor:COLORS.teal+'0.4)',pointRadius:2.5}]}, |
| options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:axOpts('Fitted','β|Residuals|')} |
| }); |
| } |
| |
| function renderCooks(ck){ |
| dc('cooks'); |
| const n_inf=ck.influential.length; |
| const b=document.getElementById('bCooks'); |
| b.textContent=n_inf>0?`${n_inf} influential`:'None β'; b.className='badge '+(n_inf>0?'b-warn':'b-ok'); |
| const colors=ck.index.map(i=>ck.influential.includes(i)?COLORS.red+'0.8)':COLORS.blue+'0.45)'); |
| const mn=Math.min(...ck.index), mx=Math.max(...ck.index); |
| CH.cooks=new Chart(document.getElementById('cCooks'),{ |
| type:'scatter', |
| data:{datasets:[ |
| {label:"Cook's D",data:ck.index.map((i,ii)=>({x:i,y:ck.distance[ii]})),backgroundColor:colors,pointRadius:4}, |
| {label:'Threshold',data:[{x:mn,y:ck.threshold},{x:mx,y:ck.threshold}],type:'line',borderColor:COLORS.red+'0.7)',borderWidth:1.5,borderDash:[4,4],pointRadius:0}, |
| ]}, |
| options:{responsive:true,maintainAspectRatio:false,plugins:{legend:legOpts},scales:axOpts('Observation index',"Cook's Distance")} |
| }); |
| } |
| |
| |
| |
| |
| let GD = { data:null, step:0, timer:null, playing:false }; |
| |
| function gdInit(gd){ |
| GD.data=gd; GD.step=0; GD.playing=false; |
| if(GD.timer){clearInterval(GD.timer);GD.timer=null;} |
| document.getElementById('gdPlayBtn').textContent='βΆ Play'; |
| document.getElementById('gdTotal').textContent=gd.path.length; |
| gdDrawAll(0); |
| } |
| |
| function gdToggle(){ |
| if(!GD.data) return; |
| GD.playing=!GD.playing; |
| document.getElementById('gdPlayBtn').textContent=GD.playing?'βΈ Pause':'βΆ Play'; |
| if(GD.playing){ |
| GD.timer=setInterval(()=>{ |
| if(GD.step>=GD.data.path.length-1){GD.playing=false;document.getElementById('gdPlayBtn').textContent='βΆ Play';clearInterval(GD.timer);return;} |
| GD.step++; gdDrawAll(GD.step); |
| }, 550 - parseInt(document.getElementById('gdSpeed').value)); |
| } else { |
| clearInterval(GD.timer); |
| } |
| } |
| |
| function gdReset(){ if(GD.timer){clearInterval(GD.timer);GD.timer=null;} GD.playing=false; GD.step=0; document.getElementById('gdPlayBtn').textContent='βΆ Play'; if(GD.data) gdDrawAll(0); } |
| |
| function gdDrawAll(step){ |
| if(!GD.data) return; |
| const p=GD.data.path[step]; |
| document.getElementById('gdIter').textContent=step; |
| document.getElementById('gdB0').textContent=p.b0.toFixed(4); |
| document.getElementById('gdB1').textContent=p.b1.toFixed(4); |
| document.getElementById('gdMSE').textContent=p.mse.toFixed(4); |
| gdDrawLine(step); gdDrawLoss(step); gdDrawSurface(step); |
| } |
| |
| function gdDrawLine(step){ |
| dc('gdLine'); |
| const gd=GD.data, p=gd.path[step]; |
| const xr=[Math.min(...gd.x_data), Math.max(...gd.x_data)]; |
| const yLine=xr.map(x=>p.b0+p.b1*x); |
| CH.gdLine=new Chart(document.getElementById('gdLine'),{ |
| type:'scatter', |
| data:{datasets:[ |
| {label:'Data',data:gd.x_data.map((x,i)=>({x,y:gd.y_data[i]})),backgroundColor:COLORS.teal+'0.4)',pointRadius:3}, |
| {label:`Ε· = ${p.b0.toFixed(2)} + ${p.b1.toFixed(2)}x`,data:xr.map((x,i)=>({x,y:yLine[i]})),type:'line',borderColor:'#E07A30',borderWidth:2.5,pointRadius:0,fill:false,tension:0}, |
| ]}, |
| options:{animation:false,responsive:true,maintainAspectRatio:false,plugins:{legend:legOpts},scales:axOpts('x','y')} |
| }); |
| } |
| |
| function gdDrawLoss(step){ |
| dc('gdLoss'); |
| const gd=GD.data; |
| const allMSE=gd.path.map(p=>p.mse); |
| const shown=allMSE.slice(0,step+1); |
| CH.gdLoss=new Chart(document.getElementById('gdLoss'),{ |
| type:'line', |
| data:{labels:Array.from({length:shown.length},(_,i)=>i),datasets:[{label:'MSE',data:shown,borderColor:COLORS.orange+'1)',backgroundColor:COLORS.orange+'0.1)',borderWidth:2,fill:true,tension:.3,pointRadius:0}]}, |
| options:{animation:false,responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}}, |
| scales:{x:{title:{display:true,text:'Iteration',font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'},ticks:{maxTicksLimit:8}}, |
| y:{title:{display:true,text:'MSE',font:{size:11}},grid:{color:'rgba(28,43,58,0.05)'},min:0,max:Math.max(...allMSE)*1.05}}} |
| }); |
| } |
| |
| function gdDrawSurface(step){ |
| dc('gdSurf'); |
| const gd=GD.data; |
| |
| const pts=[], cols=[]; |
| let zMin=Infinity, zMax=-Infinity; |
| gd.Z.forEach(row=>row.forEach(v=>{zMin=Math.min(zMin,v);zMax=Math.max(zMax,v);})); |
| gd.b0_grid.forEach((b0,i)=>gd.b1_grid.forEach((b1,j)=>{ |
| const z=gd.Z[i][j]; |
| const t=(z-zMin)/(zMax-zMin+1e-12); |
| const r=Math.round(29+(192-29)*t), g=Math.round(158+(57-158)*t), b=Math.round(117+(43-117)*t); |
| pts.push({x:b0,y:b1}); cols.push(`rgba(${r},${g},${b},0.7)`); |
| })); |
| const path=gd.path.slice(0,step+1); |
| CH.gdSurf=new Chart(document.getElementById('gdSurface'),{ |
| type:'scatter', |
| data:{datasets:[ |
| {label:'Loss surface',data:pts,backgroundColor:cols,pointRadius:5,pointStyle:'rect'}, |
| {label:'GD path',data:path.map(p=>({x:p.b0,y:p.b1})),type:'line',borderColor:'#fff',borderWidth:2,pointRadius:3,pointBackgroundColor:'#fff',fill:false,tension:.3}, |
| {label:'Current',data:[{x:path[path.length-1].b0,y:path[path.length-1].b1}],backgroundColor:'#FFD700',pointRadius:8,pointStyle:'star'}, |
| ]}, |
| options:{animation:false,responsive:true,maintainAspectRatio:false, |
| plugins:{legend:{display:false},tooltip:{filter:d=>d.datasetIndex===2}}, |
| scales:axOpts('Ξ²β','Ξ²β')} |
| }); |
| } |
| |
| |
| |
| |
| function renderDiag(d){ |
| const sw=d.shapiro; |
| const hasPatRvF=Math.abs(corr(d.rvf.fitted,d.rvf.residuals.map(Math.abs)))>0.25; |
| const hasTrend=Math.abs(corr(d.sl.fitted,d.sl.sqrt_abs_resid))>0.25; |
| const overfit=(d.metrics.r2_train-d.metrics.r2_test)>0.15; |
| const nInfl=d.cooks.influential.length; |
| const items=[ |
| {ok:sw.normal,title:'Normality',text:sw.normal?`Shapiro-Wilk p=${sw.p.toFixed(4)}>0.05. Residuals approximately normal β OLS inference valid.`:`p=${sw.p.toFixed(4)}β€0.05. Residuals non-normal. Consider log(y) or robust regression.`}, |
| {ok:!hasTrend,title:'Homoscedasticity',text:hasTrend?'Scale-Location shows increasing spread. Consider log(y) or WLS.':'Residual spread constant across fitted values. β'}, |
| {ok:!hasPatRvF,title:'Linearity',text:hasPatRvF?'Non-random pattern in residuals vs fitted. Consider polynomial features.':'No systematic pattern. Linearity assumption satisfied. β'}, |
| {ok:!overfit,title:'Overfitting',text:overfit?`Train RΒ²=${d.metrics.r2_train.toFixed(3)} >> Test RΒ²=${d.metrics.r2_test.toFixed(3)}. Try Ridge/Lasso.`:`Train RΒ²βTest RΒ² (${d.metrics.r2_train.toFixed(3)} vs ${d.metrics.r2_test.toFixed(3)}). Good generalization. β`}, |
| {ok:nInfl===0,title:"Influential Points",text:nInfl>0?`${nInfl} influential observations (Cook's D > threshold). Check for data errors.`:'No influential observations detected. β'}, |
| {ok:d.metrics.r2_test>0.5,title:'Overall Fit',text:`RΒ²=${d.metrics.r2_test.toFixed(3)}, RMSE=${d.metrics.rmse_test.toFixed(3)}, MAE=${d.metrics.mae_test.toFixed(3)}. `+(d.metrics.r2_test>0.7?'Good fit.':d.metrics.r2_test>0.4?'Moderate fit.':'Poor fit β linear model may be insufficient.')}, |
| ]; |
| document.getElementById('diagGrid').innerHTML=items.map(it=>` |
| <div class="dc" style="border-left:3px solid ${it.ok?'var(--teal)':'var(--orange)'}"> |
| <h4 style="color:${it.ok?'var(--teal-dark)':'var(--orange)'}">${it.ok?'β':'!'} ${it.title}</h4> |
| <p>${it.text}</p> |
| </div>`).join(''); |
| } |
| |
| |
| |
| |
| renderParams(); |
| </script> |
| </body> |
| </html> |
|
|