|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>ML Prediction Probability Visualizer</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.dropzone { |
|
|
border: 2px dashed #3b82f6; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.dropzone.active { |
|
|
border-color: #10b981; |
|
|
background-color: #f0f9ff; |
|
|
} |
|
|
.probability-bar { |
|
|
transition: width 0.5s ease; |
|
|
} |
|
|
.glow { |
|
|
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); |
|
|
} |
|
|
.card-hover:hover { |
|
|
transform: translateY(-5px); |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.fade-in { |
|
|
animation: fadeIn 0.5s ease-in; |
|
|
} |
|
|
@keyframes fadeIn { |
|
|
from { opacity: 0; } |
|
|
to { opacity: 1; } |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
<div class="container mx-auto px-4 py-8"> |
|
|
|
|
|
<header class="text-center mb-12"> |
|
|
<h1 class="text-4xl font-bold text-blue-600 mb-2">ML Prediction Probability Visualizer</h1> |
|
|
<p class="text-gray-600 max-w-2xl mx-auto">Upload your prediction results and visualize the probability distributions with interactive charts and insights.</p> |
|
|
</header> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> |
|
|
|
|
|
<div class="bg-white rounded-xl shadow-md p-6 lg:col-span-1 card-hover"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> |
|
|
<i class="fas fa-upload mr-2 text-blue-500"></i> Upload Data |
|
|
</h2> |
|
|
|
|
|
<div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-4"> |
|
|
<i class="fas fa-file-csv text-4xl text-blue-400 mb-3"></i> |
|
|
<p class="text-gray-600 mb-2">Drag & drop your CSV/JSON file here</p> |
|
|
<p class="text-sm text-gray-500">or</p> |
|
|
<input type="file" id="fileInput" class="hidden" accept=".csv,.json,.txt"> |
|
|
<button id="selectFileBtn" class="mt-3 bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition"> |
|
|
Select File |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="mt-4"> |
|
|
<label for="fileType" class="block text-sm font-medium text-gray-700 mb-1">File Type</label> |
|
|
<select id="fileType" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
<option value="csv">CSV</option> |
|
|
<option value="json">JSON</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div class="mt-4"> |
|
|
<label for="probabilityColumn" class="block text-sm font-medium text-gray-700 mb-1">Probability Column (CSV)</label> |
|
|
<input type="text" id="probabilityColumn" placeholder="e.g., coppaRisk" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
|
|
|
<button id="visualizeBtn" class="mt-6 w-full bg-green-500 hover:bg-green-600 text-white px-4 py-3 rounded-lg font-medium transition flex items-center justify-center disabled:opacity-50" disabled> |
|
|
<i class="fas fa-chart-bar mr-2"></i> Visualize Probabilities |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-md p-6 lg:col-span-2 card-hover"> |
|
|
<div class="flex justify-between items-center mb-6"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> |
|
|
<i class="fas fa-chart-pie mr-2 text-blue-500"></i> Probability Distribution |
|
|
</h2> |
|
|
<div class="flex space-x-2"> |
|
|
<button id="barChartBtn" class="px-3 py-1 bg-blue-100 text-blue-600 rounded-md text-sm"> |
|
|
<i class="fas fa-chart-bar mr-1"></i> Bar |
|
|
</button> |
|
|
<button id="pieChartBtn" class="px-3 py-1 bg-gray-100 text-gray-600 rounded-md text-sm"> |
|
|
<i class="fas fa-chart-pie mr-1"></i> Pie |
|
|
</button> |
|
|
<button id="histogramBtn" class="px-3 py-1 bg-gray-100 text-gray-600 rounded-md text-sm"> |
|
|
<i class="fas fa-chart-line mr-1"></i> Histogram |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="chartContainer" class="h-80 flex items-center justify-center bg-gray-50 rounded-lg mb-6"> |
|
|
<p class="text-gray-400">Upload a file to visualize prediction probabilities</p> |
|
|
</div> |
|
|
|
|
|
<div id="statsContainer" class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6"> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div id="probabilityBars" class="space-y-3"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="dataTableSection" class="mt-8 bg-white rounded-xl shadow-md p-6 hidden fade-in"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> |
|
|
<i class="fas fa-table mr-2 text-blue-500"></i> Raw Data Preview |
|
|
</h2> |
|
|
<button id="toggleTableBtn" class="text-blue-500 hover:text-blue-700 text-sm"> |
|
|
<i class="fas fa-eye-slash mr-1"></i> Hide Table |
|
|
</button> |
|
|
</div> |
|
|
<div class="overflow-x-auto"> |
|
|
<table id="dataTable" class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
|
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="insightsSection" class="mt-8 bg-white rounded-xl shadow-md p-6 hidden fade-in"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> |
|
|
<i class="fas fa-lightbulb mr-2 text-yellow-500"></i> Model Insights |
|
|
</h2> |
|
|
<div id="insightsContent" class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const dropzone = document.getElementById('dropzone'); |
|
|
const fileInput = document.getElementById('fileInput'); |
|
|
const selectFileBtn = document.getElementById('selectFileBtn'); |
|
|
const visualizeBtn = document.getElementById('visualizeBtn'); |
|
|
const chartContainer = document.getElementById('chartContainer'); |
|
|
const statsContainer = document.getElementById('statsContainer'); |
|
|
const probabilityBars = document.getElementById('probabilityBars'); |
|
|
const dataTableSection = document.getElementById('dataTableSection'); |
|
|
const dataTable = document.getElementById('dataTable'); |
|
|
const insightsSection = document.getElementById('insightsSection'); |
|
|
const toggleTableBtn = document.getElementById('toggleTableBtn'); |
|
|
const barChartBtn = document.getElementById('barChartBtn'); |
|
|
const pieChartBtn = document.getElementById('pieChartBtn'); |
|
|
const histogramBtn = document.getElementById('histogramBtn'); |
|
|
|
|
|
|
|
|
let uploadedData = null; |
|
|
let chart = null; |
|
|
let currentChartType = 'bar'; |
|
|
|
|
|
|
|
|
selectFileBtn.addEventListener('click', () => fileInput.click()); |
|
|
|
|
|
fileInput.addEventListener('change', handleFileSelect); |
|
|
|
|
|
dropzone.addEventListener('dragover', (e) => { |
|
|
e.preventDefault(); |
|
|
dropzone.classList.add('active'); |
|
|
}); |
|
|
|
|
|
dropzone.addEventListener('dragleave', () => { |
|
|
dropzone.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
dropzone.addEventListener('drop', (e) => { |
|
|
e.preventDefault(); |
|
|
dropzone.classList.remove('active'); |
|
|
if (e.dataTransfer.files.length) { |
|
|
fileInput.files = e.dataTransfer.files; |
|
|
handleFileSelect({ target: fileInput }); |
|
|
} |
|
|
}); |
|
|
|
|
|
visualizeBtn.addEventListener('click', visualizeData); |
|
|
|
|
|
toggleTableBtn.addEventListener('click', () => { |
|
|
dataTableSection.classList.toggle('hidden'); |
|
|
toggleTableBtn.innerHTML = dataTableSection.classList.contains('hidden') ? |
|
|
'<i class="fas fa-eye mr-1"></i> Show Table' : |
|
|
'<i class="fas fa-eye-slash mr-1"></i> Hide Table'; |
|
|
}); |
|
|
|
|
|
barChartBtn.addEventListener('click', () => switchChartType('bar')); |
|
|
pieChartBtn.addEventListener('click', () => switchChartType('pie')); |
|
|
histogramBtn.addEventListener('click', () => switchChartType('histogram')); |
|
|
|
|
|
|
|
|
function handleFileSelect(event) { |
|
|
const file = event.target.files[0]; |
|
|
if (!file) return; |
|
|
|
|
|
const fileType = document.getElementById('fileType').value; |
|
|
const reader = new FileReader(); |
|
|
|
|
|
reader.onload = function(e) { |
|
|
try { |
|
|
if (fileType === 'csv') { |
|
|
Papa.parse(e.target.result, { |
|
|
header: true, |
|
|
skipEmptyLines: true, |
|
|
complete: function(results) { |
|
|
uploadedData = results.data; |
|
|
visualizeBtn.disabled = false; |
|
|
showSuccess('File uploaded successfully!'); |
|
|
}, |
|
|
error: function(error) { |
|
|
showError('Error parsing CSV file: ' + error.message); |
|
|
} |
|
|
}); |
|
|
} else if (fileType === 'json') { |
|
|
uploadedData = JSON.parse(e.target.result); |
|
|
visualizeBtn.disabled = false; |
|
|
showSuccess('File uploaded successfully!'); |
|
|
} |
|
|
} catch (error) { |
|
|
showError('Error parsing file: ' + error.message); |
|
|
} |
|
|
}; |
|
|
|
|
|
reader.onerror = function() { |
|
|
showError('Error reading file'); |
|
|
}; |
|
|
|
|
|
reader.readAsText(file); |
|
|
} |
|
|
|
|
|
function visualizeData() { |
|
|
if (!uploadedData || uploadedData.length === 0) { |
|
|
showError('No data to visualize'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const probabilityColumn = document.getElementById('probabilityColumn').value; |
|
|
|
|
|
|
|
|
let probabilities = []; |
|
|
let labels = []; |
|
|
|
|
|
if (document.getElementById('fileType').value === 'csv') { |
|
|
if (!probabilityColumn) { |
|
|
showError('Please specify the probability column name for CSV files'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
uploadedData.forEach((row, index) => { |
|
|
if (row[probabilityColumn]) { |
|
|
probabilities.push(parseFloat(row[probabilityColumn])); |
|
|
labels.push(`Prediction ${index + 1}`); |
|
|
} |
|
|
}); |
|
|
} else { |
|
|
|
|
|
if (Array.isArray(uploadedData)) { |
|
|
if (typeof uploadedData[0] === 'number') { |
|
|
probabilities = uploadedData; |
|
|
labels = uploadedData.map((_, index) => `Prediction ${index + 1}`); |
|
|
} else if (typeof uploadedData[0] === 'object') { |
|
|
probabilities = uploadedData.map(item => item.probability || item.prediction || item.value || 0); |
|
|
labels = uploadedData.map((item, index) => item.label || `Prediction ${index + 1}`); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (probabilities.length === 0) { |
|
|
showError('No valid probability data found'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
updateStats(probabilities); |
|
|
updateProbabilityBars(probabilities, labels); |
|
|
renderChart(probabilities, labels, currentChartType); |
|
|
renderDataTable(uploadedData); |
|
|
generateInsights(probabilities); |
|
|
|
|
|
|
|
|
dataTableSection.classList.remove('hidden'); |
|
|
insightsSection.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function updateStats(probabilities) { |
|
|
const mean = probabilities.reduce((a, b) => a + b, 0) / probabilities.length; |
|
|
const sorted = [...probabilities].sort((a, b) => a - b); |
|
|
const median = sorted[Math.floor(sorted.length / 2)]; |
|
|
const min = Math.min(...probabilities); |
|
|
const max = Math.max(...probabilities); |
|
|
const stdDev = Math.sqrt(probabilities.reduce((sq, n) => sq + Math.pow(n - mean, 2), 0) / probabilities.length); |
|
|
|
|
|
statsContainer.innerHTML = ` |
|
|
<div class="bg-blue-50 p-4 rounded-lg"> |
|
|
<p class="text-sm text-blue-500 font-medium">Mean Probability</p> |
|
|
<p class="text-2xl font-bold text-blue-600">${mean.toFixed(4)}</p> |
|
|
</div> |
|
|
<div class="bg-green-50 p-4 rounded-lg"> |
|
|
<p class="text-sm text-green-500 font-medium">Median Probability</p> |
|
|
<p class="text-2xl font-bold text-green-600">${median.toFixed(4)}</p> |
|
|
</div> |
|
|
<div class="bg-purple-50 p-4 rounded-lg"> |
|
|
<p class="text-sm text-purple-500 font-medium">Standard Deviation</p> |
|
|
<p class="text-2xl font-bold text-purple-600">${stdDev.toFixed(4)}</p> |
|
|
</div> |
|
|
<div class="bg-yellow-50 p-4 rounded-lg"> |
|
|
<p class="text-sm text-yellow-500 font-medium">Range</p> |
|
|
<p class="text-2xl font-bold text-yellow-600">${min.toFixed(4)} - ${max.toFixed(4)}</p> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
function updateProbabilityBars(probabilities, labels) { |
|
|
probabilityBars.innerHTML = ''; |
|
|
|
|
|
probabilities.forEach((prob, index) => { |
|
|
const percentage = Math.round(prob * 100); |
|
|
const barColor = prob > 0.7 ? 'bg-green-500' : prob > 0.3 ? 'bg-yellow-500' : 'bg-red-500'; |
|
|
|
|
|
const bar = document.createElement('div'); |
|
|
bar.className = 'probability-bar'; |
|
|
bar.innerHTML = ` |
|
|
<div class="flex justify-between mb-1"> |
|
|
<span class="text-sm font-medium text-gray-700">${labels[index] || `Prediction ${index + 1}`}</span> |
|
|
<span class="text-sm font-medium ${prob > 0.7 ? 'text-green-600' : prob > 0.3 ? 'text-yellow-600' : 'text-red-600'}">${prob.toFixed(4)}</span> |
|
|
</div> |
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
|
<div class="h-2.5 rounded-full ${barColor}" style="width: ${percentage}%"></div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
probabilityBars.appendChild(bar); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
bar.querySelector(`.${barColor}`).classList.add('glow'); |
|
|
}, index * 100); |
|
|
}); |
|
|
} |
|
|
|
|
|
function renderChart(probabilities, labels, type = 'bar') { |
|
|
|
|
|
if (chart) { |
|
|
chart.destroy(); |
|
|
} |
|
|
|
|
|
const ctx = document.createElement('canvas'); |
|
|
chartContainer.innerHTML = ''; |
|
|
chartContainer.appendChild(ctx); |
|
|
|
|
|
const backgroundColors = probabilities.map(prob => { |
|
|
const opacity = 0.7; |
|
|
return prob > 0.7 ? `rgba(16, 185, 129, ${opacity})` : |
|
|
prob > 0.3 ? `rgba(234, 179, 8, ${opacity})` : |
|
|
`rgba(239, 68, 68, ${opacity})`; |
|
|
}); |
|
|
|
|
|
const borderColors = probabilities.map(prob => { |
|
|
return prob > 0.7 ? 'rgba(16, 185, 129, 1)' : |
|
|
prob > 0.3 ? 'rgba(234, 179, 8, 1)' : |
|
|
'rgba(239, 68, 68, 1)'; |
|
|
}); |
|
|
|
|
|
const chartData = { |
|
|
labels: labels, |
|
|
datasets: [{ |
|
|
label: 'Prediction Probability', |
|
|
data: probabilities, |
|
|
backgroundColor: backgroundColors, |
|
|
borderColor: borderColors, |
|
|
borderWidth: 1 |
|
|
}] |
|
|
}; |
|
|
|
|
|
const chartOptions = { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
scales: { |
|
|
y: { |
|
|
beginAtZero: true, |
|
|
max: 1, |
|
|
ticks: { |
|
|
callback: function(value) { |
|
|
return value.toFixed(2); |
|
|
} |
|
|
} |
|
|
} |
|
|
}, |
|
|
plugins: { |
|
|
tooltip: { |
|
|
callbacks: { |
|
|
label: function(context) { |
|
|
return `Probability: ${context.raw.toFixed(4)}`; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
switch (type) { |
|
|
case 'bar': |
|
|
chart = new Chart(ctx, { |
|
|
type: 'bar', |
|
|
data: chartData, |
|
|
options: chartOptions |
|
|
}); |
|
|
barChartBtn.className = 'px-3 py-1 bg-blue-100 text-blue-600 rounded-md text-sm'; |
|
|
pieChartBtn.className = 'px-3 py-1 bg-gray-100 text-gray-600 rounded-md text-sm'; |
|
|
histogramBtn.className = 'px-3 py-1 bg-gray-100 text-gray-600 rounded-md text-sm'; |
|
|
break; |
|
|
|
|
|
case 'pie': |
|
|
chart = new Chart(ctx, { |
|
|
type: 'pie', |
|
|
data: chartData, |
|
|
options: { |
|
|
...chartOptions, |
|
|
plugins: { |
|
|
legend: { |
|
|
position: 'right', |
|
|
}, |
|
|
tooltip: { |
|
|
callbacks: { |
|
|
label: function(context) { |
|
|
const label = context.label || ''; |
|
|
const value = context.raw || 0; |
|
|
const percentage = Math.round(value * 100); |
|
|
return `${label}: ${value.toFixed(4)} (${percentage}%)`; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
barChartBtn.className = 'px-3 py-1 bg-gray-100 text-gray-600 rounded-md text-sm'; |
|
|
pieChartBtn.className = 'px-3 py-1 bg-blue-100 text-blue-600 rounded-md text-sm'; |
|
|
histogramBtn.className = 'px-3 py-1 bg-gray-100 text-gray-600 rounded-md text-sm'; |
|
|
break; |
|
|
|
|
|
case 'histogram': |
|
|
|
|
|
const binCount = Math.min(10, Math.floor(probabilities.length / 5)); |
|
|
const histogramData = Array(binCount).fill(0); |
|
|
const step = 1 / binCount; |
|
|
|
|
|
probabilities.forEach(prob => { |
|
|
const binIndex = Math.min(Math.floor(prob / step), binCount - 1); |
|
|
histogramData[binIndex]++; |
|
|
}); |
|
|
|
|
|
const histogramLabels = Array.from({length: binCount}, (_, i) => { |
|
|
const start = (i * step).toFixed(2); |
|
|
const end = ((i + 1) * step).toFixed(2); |
|
|
return `${start}-${end}`; |
|
|
}); |
|
|
|
|
|
chart = new Chart(ctx, { |
|
|
type: 'bar', |
|
|
data: { |
|
|
labels: histogramLabels, |
|
|
datasets: [{ |
|
|
label: 'Count of Predictions', |
|
|
data: histogramData, |
|
|
backgroundColor: 'rgba(59, 130, 246, 0.7)', |
|
|
borderColor: 'rgba(59, 130, 246, 1)', |
|
|
borderWidth: 1 |
|
|
}] |
|
|
}, |
|
|
options: { |
|
|
...chartOptions, |
|
|
scales: { |
|
|
y: { |
|
|
beginAtZero: true, |
|
|
title: { |
|
|
display: true, |
|
|
text: 'Count' |
|
|
} |
|
|
}, |
|
|
x: { |
|
|
title: { |
|
|
display: true, |
|
|
text: 'Probability Range' |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
barChartBtn.className = 'px-3 py-1 bg-gray-100 text-gray-600 rounded-md text-sm'; |
|
|
pieChartBtn.className = 'px-3 py-1 bg-gray-100 text-gray-600 rounded-md text-sm'; |
|
|
histogramBtn.className = 'px-3 py-1 bg-blue-100 text-blue-600 rounded-md text-sm'; |
|
|
break; |
|
|
} |
|
|
|
|
|
currentChartType = type; |
|
|
} |
|
|
|
|
|
function renderDataTable(data) { |
|
|
if (!data || data.length === 0) return; |
|
|
|
|
|
|
|
|
dataTable.querySelector('thead tr').innerHTML = ''; |
|
|
dataTable.querySelector('tbody').innerHTML = ''; |
|
|
|
|
|
|
|
|
const headers = Object.keys(data[0]); |
|
|
|
|
|
|
|
|
headers.forEach(header => { |
|
|
const th = document.createElement('th'); |
|
|
th.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider'; |
|
|
th.textContent = header; |
|
|
dataTable.querySelector('thead tr').appendChild(th); |
|
|
}); |
|
|
|
|
|
|
|
|
const rowsToShow = Math.min(10, data.length); |
|
|
for (let i = 0; i < rowsToShow; i++) { |
|
|
const row = document.createElement('tr'); |
|
|
row.className = i % 2 === 0 ? 'bg-white' : 'bg-gray-50'; |
|
|
|
|
|
headers.forEach(header => { |
|
|
const td = document.createElement('td'); |
|
|
td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500'; |
|
|
td.textContent = data[i][header] !== undefined ? data[i][header] : ''; |
|
|
row.appendChild(td); |
|
|
}); |
|
|
|
|
|
dataTable.querySelector('tbody').appendChild(row); |
|
|
} |
|
|
} |
|
|
|
|
|
function generateInsights(probabilities) { |
|
|
const highConfidence = probabilities.filter(p => p > 0.7).length; |
|
|
const lowConfidence = probabilities.filter(p => p < 0.3).length; |
|
|
const midConfidence = probabilities.length - highConfidence - lowConfidence; |
|
|
|
|
|
const mean = probabilities.reduce((a, b) => a + b, 0) / probabilities.length; |
|
|
const sorted = [...probabilities].sort((a, b) => a - b); |
|
|
const median = sorted[Math.floor(sorted.length / 2)]; |
|
|
|
|
|
insightsSection.innerHTML = ` |
|
|
<div class="bg-white p-4 rounded-lg border border-gray-200"> |
|
|
<h3 class="font-medium text-gray-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-chart-line mr-2 text-blue-500"></i> Confidence Distribution |
|
|
</h3> |
|
|
<div class="space-y-2"> |
|
|
<div class="flex justify-between"> |
|
|
<span class="text-sm text-green-600">High Confidence (>70%)</span> |
|
|
<span class="text-sm font-medium">${highConfidence} (${Math.round(highConfidence/probabilities.length*100)}%)</span> |
|
|
</div> |
|
|
<div class="flex justify-between"> |
|
|
<span class="text-sm text-yellow-600">Medium Confidence (30-70%)</span> |
|
|
<span class="text-sm font-medium">${midConfidence} (${Math.round(midConfidence/probabilities.length*100)}%)</span> |
|
|
</div> |
|
|
<div class="flex justify-between"> |
|
|
<span class="text-sm text-red-600">Low Confidence (<30%)</span> |
|
|
<span class="text-sm font-medium">${lowConfidence} (${Math.round(lowConfidence/probabilities.length*100)}%)</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white p-4 rounded-lg border border-gray-200"> |
|
|
<h3 class="font-medium text-gray-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-balance-scale mr-2 text-purple-500"></i> Central Tendency |
|
|
</h3> |
|
|
<div class="space-y-2"> |
|
|
<div class="flex justify-between"> |
|
|
<span class="text-sm">Mean Probability</span> |
|
|
<span class="text-sm font-medium">${mean.toFixed(4)}</span> |
|
|
</div> |
|
|
<div class="flex justify-between"> |
|
|
<span class="text-sm">Median Probability</span> |
|
|
<span class="text-sm font-medium">${median.toFixed(4)}</span> |
|
|
</div> |
|
|
<div class="flex justify-between"> |
|
|
<span class="text-sm">Difference</span> |
|
|
<span class="text-sm font-medium">${Math.abs(mean - median).toFixed(4)}</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white p-4 rounded-lg border border-gray-200"> |
|
|
<h3 class="font-medium text-gray-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-exclamation-triangle mr-2 text-red-500"></i> Potential Issues |
|
|
</h3> |
|
|
<div class="space-y-2 text-sm"> |
|
|
${lowConfidence > probabilities.length * 0.5 ? |
|
|
'<p class="text-red-500"><i class="fas fa-exclamation-circle mr-1"></i> Over 50% of predictions have low confidence (<30%)</p>' : |
|
|
'<p class="text-green-500"><i class="fas fa-check-circle mr-1"></i> Low confidence predictions are in acceptable range</p>'} |
|
|
|
|
|
${Math.abs(mean - median) > 0.2 ? |
|
|
'<p class="text-yellow-500"><i class="fas fa-exclamation-circle mr-1"></i> Significant difference between mean and median probabilities</p>' : |
|
|
'<p class="fas fa-check-circle mr-1"></i> Mean and median probabilities are close</p>'} |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white p-4 rounded-lg border border-gray-200"> |
|
|
<h3 class="font-medium text-gray-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-lightbulb mr-2 text-yellow-500"></i> Recommendations |
|
|
</h3> |
|
|
<div class="space-y-2 text-sm"> |
|
|
<p><i class="fas fa-check-circle text-blue-500 mr-1"></i> Review low confidence predictions for potential errors</p> |
|
|
<p><i class="fas fa-check-circle text-blue-500 mr-1"></i> Consider model retraining if confidence is consistently low</p> |
|
|
<p><i class="fas fa-check-circle text-blue-500 mr-1"></i> Examine features influencing high confidence predictions</p> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
function switchChartType(type) { |
|
|
if (!uploadedData) return; |
|
|
|
|
|
const probabilityColumn = document.getElementById('probabilityColumn').value; |
|
|
let probabilities = []; |
|
|
let labels = []; |
|
|
|
|
|
if (document.getElementById('fileType').value === 'csv') { |
|
|
uploadedData.forEach((row, index) => { |
|
|
if (row[probabilityColumn]) { |
|
|
probabilities.push(parseFloat(row[probabilityColumn])); |
|
|
labels.push(`Prediction ${index + 1}`); |
|
|
} |
|
|
}); |
|
|
} else { |
|
|
if (Array.isArray(uploadedData)) { |
|
|
if (typeof uploadedData[0] === 'number') { |
|
|
probabilities = uploadedData; |
|
|
labels = uploadedData.map((_, index) => `Prediction ${index + 1}`); |
|
|
} else if (typeof uploadedData[0] === 'object') { |
|
|
probabilities = uploadedData.map(item => item.probability || item.prediction || item.value || 0); |
|
|
labels = uploadedData.map((item, index) => item.label || `Prediction ${index + 1}`); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
renderChart(probabilities, labels, type); |
|
|
} |
|
|
|
|
|
function showSuccess(message) { |
|
|
const alert = document.createElement('div'); |
|
|
alert.className = 'fixed top-4 right-4 bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded shadow-lg z-50 flex items-center'; |
|
|
alert.innerHTML = ` |
|
|
<i class="fas fa-check-circle mr-2"></i> |
|
|
<span>${message}</span> |
|
|
`; |
|
|
document.body.appendChild(alert); |
|
|
|
|
|
setTimeout(() => { |
|
|
alert.classList.add('opacity-0', 'transition-opacity', 'duration-500'); |
|
|
setTimeout(() => alert.remove(), 500); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
function showError(message) { |
|
|
const alert = document.createElement('div'); |
|
|
alert.className = 'fixed top-4 right-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded shadow-lg z-50 flex items-center'; |
|
|
alert.innerHTML = ` |
|
|
<i class="fas fa-exclamation-circle mr-2"></i> |
|
|
<span>${message}</span> |
|
|
`; |
|
|
document.body.appendChild(alert); |
|
|
|
|
|
setTimeout(() => { |
|
|
alert.classList.add('opacity-0', 'transition-opacity', 'duration-500'); |
|
|
setTimeout(() => alert.remove(), 500); |
|
|
}, 3000); |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=farwew/proba-le" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |