Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Binary Classification Playground</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 20px; | |
| min-height: 100vh; | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| background: white; | |
| border-radius: 20px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.3); | |
| overflow: hidden; | |
| } | |
| .header { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 30px; | |
| text-align: center; | |
| } | |
| .header h1 { | |
| font-size: 2.5em; | |
| margin-bottom: 10px; | |
| } | |
| .content { | |
| padding: 30px; | |
| } | |
| .controls { | |
| background: #f8f9fa; | |
| padding: 25px; | |
| border-radius: 15px; | |
| margin-bottom: 30px; | |
| } | |
| .control-row { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 20px; | |
| margin-bottom: 20px; | |
| } | |
| .control-group label { | |
| display: block; | |
| font-weight: 600; | |
| margin-bottom: 8px; | |
| color: #333; | |
| } | |
| .control-group input[type="number"] { | |
| width: 100%; | |
| padding: 10px; | |
| border: 2px solid #ddd; | |
| border-radius: 8px; | |
| font-size: 1.1em; | |
| } | |
| .btn-generate { | |
| background: #667eea; | |
| color: white; | |
| border: none; | |
| padding: 12px 30px; | |
| border-radius: 8px; | |
| font-size: 1.1em; | |
| font-weight: 600; | |
| cursor: pointer; | |
| width: 100%; | |
| margin-top: 10px; | |
| } | |
| .btn-generate:hover { | |
| background: #5568d3; | |
| } | |
| .data-section { | |
| background: #f8f9fa; | |
| padding: 25px; | |
| border-radius: 15px; | |
| margin-bottom: 30px; | |
| } | |
| .data-section h3 { | |
| margin-bottom: 15px; | |
| color: #333; | |
| } | |
| .data-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| background: white; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| } | |
| .data-table th, | |
| .data-table td { | |
| padding: 12px; | |
| text-align: center; | |
| border-bottom: 1px solid #ddd; | |
| } | |
| .data-table th { | |
| background: #667eea; | |
| color: white; | |
| font-weight: 600; | |
| } | |
| .data-table tr:hover { | |
| background: #f8f9fa; | |
| } | |
| .class-0 { | |
| color: #3498db; | |
| font-weight: 600; | |
| } | |
| .class-1 { | |
| color: #e74c3c; | |
| font-weight: 600; | |
| } | |
| .correct { | |
| background: #d4edda; | |
| } | |
| .incorrect { | |
| background: #f8d7da; | |
| } | |
| .confusion-matrix { | |
| background: #f8f9fa; | |
| padding: 25px; | |
| border-radius: 15px; | |
| margin-bottom: 30px; | |
| } | |
| .confusion-matrix h3 { | |
| text-align: center; | |
| margin-bottom: 20px; | |
| color: #333; | |
| } | |
| .matrix-grid { | |
| display: grid; | |
| grid-template-columns: auto 1fr 1fr; | |
| grid-template-rows: auto 1fr 1fr; | |
| gap: 10px; | |
| max-width: 500px; | |
| margin: 0 auto; | |
| } | |
| .matrix-label { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: 600; | |
| color: #555; | |
| } | |
| .matrix-cell { | |
| background: white; | |
| border: 2px solid #ddd; | |
| border-radius: 10px; | |
| padding: 20px; | |
| text-align: center; | |
| min-height: 100px; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| } | |
| .matrix-cell.tp { background: #d4edda; border-color: #28a745; } | |
| .matrix-cell.tn { background: #d4edda; border-color: #28a745; } | |
| .matrix-cell.fp { background: #f8d7da; border-color: #dc3545; } | |
| .matrix-cell.fn { background: #f8d7da; border-color: #dc3545; } | |
| .matrix-cell .label { | |
| font-size: 0.9em; | |
| color: #666; | |
| margin-bottom: 5px; | |
| } | |
| .matrix-cell .value { | |
| font-size: 2em; | |
| font-weight: 700; | |
| } | |
| .metrics { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 20px; | |
| } | |
| .metric-card { | |
| background: #f8f9fa; | |
| padding: 25px; | |
| border-radius: 15px; | |
| border-left: 5px solid #667eea; | |
| } | |
| .metric-card h4 { | |
| color: #333; | |
| margin-bottom: 15px; | |
| font-size: 1.3em; | |
| } | |
| .metric-value { | |
| font-size: 2.5em; | |
| font-weight: 700; | |
| color: #667eea; | |
| margin-bottom: 15px; | |
| } | |
| .formula { | |
| background: white; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-bottom: 15px; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.95em; | |
| color: #555; | |
| border: 1px solid #ddd; | |
| } | |
| .explanation { | |
| color: #666; | |
| line-height: 1.6; | |
| font-size: 0.95em; | |
| } | |
| @media (max-width: 768px) { | |
| .control-row { | |
| grid-template-columns: 1fr; | |
| } | |
| .metrics { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>🎯 Binary Classification Playground</h1> | |
| <p>Set sample counts and see all classification metrics</p> | |
| </div> | |
| <div class="content"> | |
| <div class="controls"> | |
| <div class="control-row"> | |
| <div class="control-group"> | |
| <label>Number of Class 0 (Negative) Samples:</label> | |
| <input type="number" id="class0Count" min="1" max="50" value="5"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Number of Class 1 (Positive) Samples:</label> | |
| <input type="number" id="class1Count" min="1" max="50" value="5"> | |
| </div> | |
| </div> | |
| <button class="btn-generate" onclick="generateData()">Generate Data</button> | |
| </div> | |
| <div class="data-section"> | |
| <h3>📊 Generated Data & Predictions</h3> | |
| <div style="overflow-x: auto;"> | |
| <table class="data-table" id="dataTable"> | |
| <thead> | |
| <tr> | |
| <th>#</th> | |
| <th>Actual Class</th> | |
| <th>Predicted Class</th> | |
| <th>Result</th> | |
| </tr> | |
| </thead> | |
| <tbody id="dataTableBody"> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="confusion-matrix"> | |
| <h3>Confusion Matrix</h3> | |
| <div class="matrix-grid"> | |
| <div></div> | |
| <div class="matrix-label">Predicted 0</div> | |
| <div class="matrix-label">Predicted 1</div> | |
| <div class="matrix-label">Actual 0</div> | |
| <div class="matrix-cell tn"> | |
| <div class="label">True Negative (TN)</div> | |
| <div class="value" id="tnValue">0</div> | |
| </div> | |
| <div class="matrix-cell fp"> | |
| <div class="label">False Positive (FP)</div> | |
| <div class="value" id="fpValue">0</div> | |
| </div> | |
| <div class="matrix-label">Actual 1</div> | |
| <div class="matrix-cell fn"> | |
| <div class="label">False Negative (FN)</div> | |
| <div class="value" id="fnValue">0</div> | |
| </div> | |
| <div class="matrix-cell tp"> | |
| <div class="label">True Positive (TP)</div> | |
| <div class="value" id="tpValue">0</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="metrics"> | |
| <div class="metric-card"> | |
| <h4>Accuracy</h4> | |
| <div class="metric-value" id="accuracyValue">0%</div> | |
| <div class="formula">Accuracy = (TP + TN) / (TP + TN + FP + FN)</div> | |
| <div class="explanation"> | |
| <strong>What it measures:</strong> The proportion of correct predictions (both positive and negative) among all predictions made. | |
| <br><br> | |
| <strong>When to use:</strong> Best for balanced datasets where both classes are equally important. Can be misleading with imbalanced data. | |
| </div> | |
| </div> | |
| <div class="metric-card"> | |
| <h4>Precision</h4> | |
| <div class="metric-value" id="precisionValue">0%</div> | |
| <div class="formula">Precision = TP / (TP + FP)</div> | |
| <div class="explanation"> | |
| <strong>What it measures:</strong> Of all instances predicted as positive, how many were actually positive. Answers "How reliable are positive predictions?" | |
| <br><br> | |
| <strong>When to use:</strong> When false positives are costly (e.g., spam detection, where marking legitimate emails as spam is problematic). | |
| </div> | |
| </div> | |
| <div class="metric-card"> | |
| <h4>Recall (Sensitivity)</h4> | |
| <div class="metric-value" id="recallValue">0%</div> | |
| <div class="formula">Recall = TP / (TP + FN)</div> | |
| <div class="explanation"> | |
| <strong>What it measures:</strong> Of all actual positive instances, how many were correctly identified. Answers "How many positives did we catch?" | |
| <br><br> | |
| <strong>When to use:</strong> When false negatives are costly (e.g., disease detection, where missing a positive case is dangerous). | |
| </div> | |
| </div> | |
| <div class="metric-card"> | |
| <h4>F1 Score</h4> | |
| <div class="metric-value" id="f1Value">0%</div> | |
| <div class="formula">F1 = 2 × (Precision × Recall) / (Precision + Recall)</div> | |
| <div class="explanation"> | |
| <strong>What it measures:</strong> The harmonic mean of precision and recall. Balances both metrics into a single score. | |
| <br><br> | |
| <strong>When to use:</strong> When you need a balance between precision and recall, especially with imbalanced datasets. Higher F1 means better overall performance. | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let samples = []; | |
| function generateData() { | |
| const class0Count = parseInt(document.getElementById('class0Count').value); | |
| const class1Count = parseInt(document.getElementById('class1Count').value); | |
| samples = []; | |
| // Generate Class 0 samples | |
| for (let i = 0; i < class0Count; i++) { | |
| samples.push({ | |
| actualClass: 0, | |
| predictedClass: Math.random() < 0.8 ? 0 : 1 // 80% accuracy | |
| }); | |
| } | |
| // Generate Class 1 samples | |
| for (let i = 0; i < class1Count; i++) { | |
| samples.push({ | |
| actualClass: 1, | |
| predictedClass: Math.random() < 0.8 ? 1 : 0 // 80% accuracy | |
| }); | |
| } | |
| displayData(); | |
| calculateMetrics(); | |
| } | |
| function displayData() { | |
| const tbody = document.getElementById('dataTableBody'); | |
| tbody.innerHTML = ''; | |
| samples.forEach((sample, index) => { | |
| const row = tbody.insertRow(); | |
| let result = ''; | |
| let rowClass = ''; | |
| if (sample.actualClass === 1 && sample.predictedClass === 1) { | |
| result = 'TP'; | |
| rowClass = 'correct'; | |
| } else if (sample.actualClass === 0 && sample.predictedClass === 0) { | |
| result = 'TN'; | |
| rowClass = 'correct'; | |
| } else if (sample.actualClass === 0 && sample.predictedClass === 1) { | |
| result = 'FP'; | |
| rowClass = 'incorrect'; | |
| } else if (sample.actualClass === 1 && sample.predictedClass === 0) { | |
| result = 'FN'; | |
| rowClass = 'incorrect'; | |
| } | |
| row.className = rowClass; | |
| row.insertCell(0).textContent = index + 1; | |
| const actualCell = row.insertCell(1); | |
| actualCell.textContent = sample.actualClass; | |
| actualCell.className = sample.actualClass === 0 ? 'class-0' : 'class-1'; | |
| const predCell = row.insertCell(2); | |
| predCell.textContent = sample.predictedClass; | |
| predCell.className = sample.predictedClass === 0 ? 'class-0' : 'class-1'; | |
| row.insertCell(3).textContent = result; | |
| }); | |
| } | |
| function calculateMetrics() { | |
| let tp = 0, tn = 0, fp = 0, fn = 0; | |
| samples.forEach(sample => { | |
| if (sample.actualClass === 1 && sample.predictedClass === 1) tp++; | |
| else if (sample.actualClass === 0 && sample.predictedClass === 0) tn++; | |
| else if (sample.actualClass === 0 && sample.predictedClass === 1) fp++; | |
| else if (sample.actualClass === 1 && sample.predictedClass === 0) fn++; | |
| }); | |
| document.getElementById('tpValue').textContent = tp; | |
| document.getElementById('tnValue').textContent = tn; | |
| document.getElementById('fpValue').textContent = fp; | |
| document.getElementById('fnValue').textContent = fn; | |
| const total = tp + tn + fp + fn; | |
| const accuracy = total > 0 ? (tp + tn) / total : 0; | |
| const precision = (tp + fp) > 0 ? tp / (tp + fp) : 0; | |
| const recall = (tp + fn) > 0 ? tp / (tp + fn) : 0; | |
| const f1 = (precision + recall) > 0 ? 2 * (precision * recall) / (precision + recall) : 0; | |
| document.getElementById('accuracyValue').textContent = (accuracy * 100).toFixed(1) + '%'; | |
| document.getElementById('precisionValue').textContent = (precision * 100).toFixed(1) + '%'; | |
| document.getElementById('recallValue').textContent = (recall * 100).toFixed(1) + '%'; | |
| document.getElementById('f1Value').textContent = (f1 * 100).toFixed(1) + '%'; | |
| } | |
| // Generate initial data | |
| generateData(); | |
| </script> | |
| </body> | |
| </html> | |