PlaygroundOrganizer / Blog /binary_classification_playground.html
mnoorchenar's picture
Update 2026-01-30 15:32:07
e890e92
Raw
History Blame Contribute Delete
16.7 kB
<!DOCTYPE html>
<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>