| | <!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> |