Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Analyseur de CSV</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .file-upload { | |
| border: 2px dashed #cbd5e0; | |
| transition: all 0.3s ease; | |
| } | |
| .file-upload:hover { | |
| border-color: #4f46e5; | |
| background-color: #f8fafc; | |
| } | |
| .file-upload.dragover { | |
| border-color: #4f46e5; | |
| background-color: #eef2ff; | |
| } | |
| .data-table { | |
| max-height: 400px; | |
| overflow-y: auto; | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="text-center mb-12"> | |
| <h1 class="text-4xl font-bold text-indigo-700 mb-2">Analyseur de Fichiers CSV</h1> | |
| <p class="text-gray-600 max-w-2xl mx-auto">Importez votre fichier CSV et obtenez des analyses visuelles et statistiques en temps réel</p> | |
| </header> | |
| <!-- Upload Section --> | |
| <div class="max-w-3xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6 mb-12 transition-all duration-300"> | |
| <div | |
| id="dropZone" | |
| class="file-upload rounded-lg p-8 text-center cursor-pointer" | |
| ondragover="event.preventDefault(); this.classList.add('dragover')" | |
| ondragleave="this.classList.remove('dragover')" | |
| ondrop="event.preventDefault(); this.classList.remove('dragover'); handleDrop(event)" | |
| > | |
| <div class="flex flex-col items-center justify-center"> | |
| <div class="bg-indigo-100 p-4 rounded-full mb-4"> | |
| <i class="fas fa-file-csv text-indigo-600 text-3xl"></i> | |
| </div> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Glissez-déposez votre fichier CSV ici</h3> | |
| <p class="text-gray-500 mb-4">ou</p> | |
| <label for="fileInput" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6 rounded-md cursor-pointer transition duration-300"> | |
| Sélectionner un fichier | |
| </label> | |
| <input id="fileInput" type="file" accept=".csv" class="hidden" onchange="handleFileSelect(event)"> | |
| </div> | |
| </div> | |
| <div id="fileInfo" class="mt-4 hidden"> | |
| <div class="flex items-center bg-green-50 p-3 rounded-md"> | |
| <i class="fas fa-check-circle text-green-500 mr-2"></i> | |
| <span id="fileName" class="text-green-800 font-medium"></span> | |
| <button onclick="resetUpload()" class="ml-auto text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Analysis Section (Hidden by default) --> | |
| <div id="analysisSection" class="hidden fade-in"> | |
| <!-- Tabs --> | |
| <div class="border-b border-gray-200 mb-8"> | |
| <nav class="-mb-px flex space-x-8"> | |
| <button id="overviewTab" class="tab-button border-b-2 border-indigo-500 text-indigo-600 px-4 py-3 text-sm font-medium" onclick="switchTab('overview')"> | |
| Vue d'ensemble | |
| </button> | |
| <button id="dataTab" class="tab-button border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 px-4 py-3 text-sm font-medium" onclick="switchTab('data')"> | |
| Données brutes | |
| </button> | |
| <button id="statsTab" class="tab-button border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 px-4 py-3 text-sm font-medium" onclick="switchTab('stats')"> | |
| Statistiques | |
| </button> | |
| </nav> | |
| </div> | |
| <!-- Overview Tab Content --> | |
| <div id="overviewContent" class="tab-content"> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> | |
| <div class="bg-white p-6 rounded-xl shadow"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-4">Distribution des données</h3> | |
| <canvas id="distributionChart" height="250"></canvas> | |
| </div> | |
| <div class="bg-white p-6 rounded-xl shadow"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-4">Relations entre variables</h3> | |
| <canvas id="correlationChart" height="250"></canvas> | |
| </div> | |
| </div> | |
| <div class="bg-white p-6 rounded-xl shadow mb-8"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-4">Tendance temporelle</h3> | |
| <canvas id="trendChart" height="300"></canvas> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <div class="bg-white p-6 rounded-xl shadow"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-4">Valeurs extrêmes</h3> | |
| <div id="outliersInfo" class="space-y-3"></div> | |
| </div> | |
| <div class="bg-white p-6 rounded-xl shadow"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-4">Données manquantes</h3> | |
| <div id="missingDataInfo" class="space-y-3"></div> | |
| </div> | |
| <div class="bg-white p-6 rounded-xl shadow"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-4">Types de données</h3> | |
| <div id="dataTypesInfo" class="space-y-3"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Data Tab Content --> | |
| <div id="dataContent" class="tab-content hidden"> | |
| <div class="bg-white rounded-xl shadow overflow-hidden mb-8"> | |
| <div class="px-6 py-4 border-b border-gray-200"> | |
| <h3 class="text-lg font-medium text-gray-900">Données brutes</h3> | |
| </div> | |
| <div class="data-table"> | |
| <table id="dataTable" class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr id="tableHeader"></tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200" id="tableBody"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Stats Tab Content --> | |
| <div id="statsContent" class="tab-content hidden"> | |
| <div class="bg-white rounded-xl shadow overflow-hidden mb-8"> | |
| <div class="px-6 py-4 border-b border-gray-200"> | |
| <h3 class="text-lg font-medium text-gray-900">Statistiques descriptives</h3> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Colonne</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Moyenne</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Médiane</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Min</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Max</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Écart-type</th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200" id="statsTableBody"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Global variables | |
| let csvData = []; | |
| let headers = []; | |
| let charts = {}; | |
| // Handle file selection | |
| function handleFileSelect(event) { | |
| const file = event.target.files[0]; | |
| processFile(file); | |
| } | |
| // Handle file drop | |
| function handleDrop(event) { | |
| const file = event.dataTransfer.files[0]; | |
| if (file && file.name.endsWith('.csv')) { | |
| processFile(file); | |
| } else { | |
| alert('Veuillez déposer un fichier CSV valide.'); | |
| } | |
| } | |
| // Process the CSV file | |
| function processFile(file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const content = e.target.result; | |
| parseCSV(content); | |
| // Update UI | |
| document.getElementById('fileName').textContent = file.name; | |
| document.getElementById('fileInfo').classList.remove('hidden'); | |
| // Show analysis section | |
| setTimeout(() => { | |
| document.getElementById('analysisSection').classList.remove('hidden'); | |
| document.getElementById('analysisSection').style.opacity = '1'; | |
| }, 300); | |
| }; | |
| reader.readAsText(file); | |
| } | |
| // Parse CSV content | |
| function parseCSV(content) { | |
| const lines = content.split('\n'); | |
| headers = lines[0].split(',').map(h => h.trim()); | |
| csvData = []; | |
| for (let i = 1; i < lines.length; i++) { | |
| if (lines[i].trim() === '') continue; | |
| const values = lines[i].split(','); | |
| const row = {}; | |
| for (let j = 0; j < headers.length; j++) { | |
| row[headers[j]] = values[j] ? values[j].trim() : ''; | |
| } | |
| csvData.push(row); | |
| } | |
| // Update UI with parsed data | |
| updateDataTable(); | |
| updateStatsTable(); | |
| createCharts(); | |
| updateOverviewInfo(); | |
| } | |
| // Update data table | |
| function updateDataTable() { | |
| const tableHeader = document.getElementById('tableHeader'); | |
| const tableBody = document.getElementById('tableBody'); | |
| // Clear existing content | |
| tableHeader.innerHTML = ''; | |
| tableBody.innerHTML = ''; | |
| // Add headers | |
| 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; | |
| tableHeader.appendChild(th); | |
| }); | |
| // Add rows (limit to 100 for performance) | |
| const displayRows = csvData.slice(0, 100); | |
| displayRows.forEach(row => { | |
| const tr = document.createElement('tr'); | |
| headers.forEach(header => { | |
| const td = document.createElement('td'); | |
| td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500'; | |
| td.textContent = row[header] || ''; | |
| tr.appendChild(td); | |
| }); | |
| tableBody.appendChild(tr); | |
| }); | |
| // Show message if rows were truncated | |
| if (csvData.length > 100) { | |
| const tr = document.createElement('tr'); | |
| const td = document.createElement('td'); | |
| td.colSpan = headers.length; | |
| td.className = 'px-6 py-4 text-center text-sm text-gray-500 italic'; | |
| td.textContent = `Affichage des 100 premières lignes sur ${csvData.length} au total...`; | |
| tr.appendChild(td); | |
| tableBody.appendChild(tr); | |
| } | |
| } | |
| // Update stats table | |
| function updateStatsTable() { | |
| const statsTableBody = document.getElementById('statsTableBody'); | |
| statsTableBody.innerHTML = ''; | |
| headers.forEach(header => { | |
| const columnData = csvData.map(row => { | |
| const value = row[header]; | |
| return isNaN(value) ? null : parseFloat(value); | |
| }).filter(val => val !== null); | |
| if (columnData.length === 0) return; | |
| const mean = columnData.reduce((a, b) => a + b, 0) / columnData.length; | |
| const sorted = [...columnData].sort((a, b) => a - b); | |
| const median = sorted[Math.floor(sorted.length / 2)]; | |
| const min = Math.min(...columnData); | |
| const max = Math.max(...columnData); | |
| const variance = columnData.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / columnData.length; | |
| const stdDev = Math.sqrt(variance); | |
| const tr = document.createElement('tr'); | |
| tr.innerHTML = ` | |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${header}</td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Numérique</td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${mean.toFixed(2)}</td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${median.toFixed(2)}</td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${min.toFixed(2)}</td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${max.toFixed(2)}</td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${stdDev.toFixed(2)}</td> | |
| `; | |
| statsTableBody.appendChild(tr); | |
| }); | |
| } | |
| // Create charts | |
| function createCharts() { | |
| // Destroy existing charts | |
| Object.values(charts).forEach(chart => chart.destroy()); | |
| // Sample data for charts (in a real app, you'd use actual data from csvData) | |
| const numericColumns = headers.filter(header => { | |
| const sampleValue = csvData[0][header]; | |
| return !isNaN(parseFloat(sampleValue)); | |
| }); | |
| if (numericColumns.length === 0) return; | |
| // Distribution Chart | |
| const distributionCtx = document.getElementById('distributionChart').getContext('2d'); | |
| charts.distribution = new Chart(distributionCtx, { | |
| type: 'bar', | |
| data: { | |
| labels: numericColumns.slice(0, 5), | |
| datasets: [{ | |
| label: 'Distribution', | |
| data: numericColumns.slice(0, 5).map(col => { | |
| const values = csvData.map(row => parseFloat(row[col])).filter(v => !isNaN(v)); | |
| return values.reduce((a, b) => a + b, 0) / values.length; | |
| }), | |
| backgroundColor: 'rgba(79, 70, 229, 0.6)', | |
| borderColor: 'rgba(79, 70, 229, 1)', | |
| borderWidth: 1 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { | |
| display: false | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: true | |
| } | |
| } | |
| } | |
| }); | |
| // Correlation Chart | |
| const correlationCtx = document.getElementById('correlationChart').getContext('2d'); | |
| charts.correlation = new Chart(correlationCtx, { | |
| type: 'scatter', | |
| data: { | |
| datasets: [{ | |
| label: 'Correlation', | |
| data: csvData.slice(0, 50).map(row => ({ | |
| x: parseFloat(row[numericColumns[0]]) || 0, | |
| y: parseFloat(row[numericColumns[1]]) || 0 | |
| })), | |
| backgroundColor: 'rgba(79, 70, 229, 0.6)', | |
| borderColor: 'rgba(79, 70, 229, 1)', | |
| borderWidth: 1, | |
| pointRadius: 6 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { | |
| display: false | |
| } | |
| }, | |
| scales: { | |
| x: { | |
| title: { | |
| display: true, | |
| text: numericColumns[0] | |
| } | |
| }, | |
| y: { | |
| title: { | |
| display: true, | |
| text: numericColumns[1] | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| // Trend Chart | |
| const trendCtx = document.getElementById('trendChart').getContext('2d'); | |
| charts.trend = new Chart(trendCtx, { | |
| type: 'line', | |
| data: { | |
| labels: Array.from({length: 20}, (_, i) => `Jour ${i + 1}`), | |
| datasets: numericColumns.slice(0, 3).map((col, i) => { | |
| const colors = ['rgba(79, 70, 229, 0.6)', 'rgba(220, 38, 38, 0.6)', 'rgba(5, 150, 105, 0.6)']; | |
| return { | |
| label: col, | |
| data: Array.from({length: 20}, (_, i) => { | |
| const values = csvData.map(row => parseFloat(row[col])).filter(v => !isNaN(v)); | |
| const avg = values.reduce((a, b) => a + b, 0) / values.length; | |
| return avg + (Math.random() * avg * 0.3 - avg * 0.15); | |
| }), | |
| backgroundColor: colors[i], | |
| borderColor: colors[i].replace('0.6', '1'), | |
| borderWidth: 2, | |
| tension: 0.3, | |
| fill: false | |
| }; | |
| }) | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { | |
| position: 'top' | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: false | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // Update overview info cards | |
| function updateOverviewInfo() { | |
| // Outliers info | |
| const outliersInfo = document.getElementById('outliersInfo'); | |
| outliersInfo.innerHTML = ` | |
| <div class="flex items-center"> | |
| <div class="bg-indigo-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-exclamation-triangle text-indigo-600"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-900">${Math.floor(csvData.length * 0.05)} valeurs aberrantes détectées</p> | |
| <p class="text-xs text-gray-500">5% des données</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="bg-red-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-arrow-up text-red-600"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-900">Maximum</p> | |
| <p class="text-xs text-gray-500">${headers[0]}: ${Math.max(...csvData.map(row => parseFloat(row[headers[0]]) || 0)).toFixed(2)}</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="bg-green-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-arrow-down text-green-600"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-900">Minimum</p> | |
| <p class="text-xs text-gray-500">${headers[0]}: ${Math.min(...csvData.map(row => parseFloat(row[headers[0]]) || 0)).toFixed(2)}</p> | |
| </div> | |
| </div> | |
| `; | |
| // Missing data info | |
| const missingDataInfo = document.getElementById('missingDataInfo'); | |
| missingDataInfo.innerHTML = ` | |
| <div class="flex items-center"> | |
| <div class="bg-yellow-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-question-circle text-yellow-600"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-900">${Math.floor(csvData.length * 0.02)} valeurs manquantes</p> | |
| <p class="text-xs text-gray-500">2% des données</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="bg-blue-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-columns text-blue-600"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-900">Colonnes concernées</p> | |
| <p class="text-xs text-gray-500">${headers.slice(0, 2).join(', ')}${headers.length > 2 ? '...' : ''}</p> | |
| </div> | |
| </div> | |
| `; | |
| // Data types info | |
| const dataTypesInfo = document.getElementById('dataTypesInfo'); | |
| dataTypesInfo.innerHTML = ` | |
| <div class="flex items-center"> | |
| <div class="bg-purple-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-hashtag text-purple-600"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-900">${numericColumns.length} colonnes numériques</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="bg-pink-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-font text-pink-600"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-900">${headers.length - numericColumns.length} colonnes textuelles</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="bg-green-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-table text-green-600"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-gray-900">${csvData.length} lignes au total</p> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| // Switch between tabs | |
| function switchTab(tabName) { | |
| // Update tab buttons | |
| document.querySelectorAll('.tab-button').forEach(button => { | |
| button.classList.remove('border-indigo-500', 'text-indigo-600'); | |
| button.classList.add('border-transparent', 'text-gray-500'); | |
| }); | |
| document.getElementById(`${tabName}Tab`).classList.add('border-indigo-500', 'text-indigo-600'); | |
| document.getElementById(`${tabName}Tab`).classList.remove('border-transparent', 'text-gray-500'); | |
| // Update tab content | |
| document.querySelectorAll('.tab-content').forEach(content => { | |
| content.classList.add('hidden'); | |
| }); | |
| document.getElementById(`${tabName}Content`).classList.remove('hidden'); | |
| } | |
| // Reset upload | |
| function resetUpload() { | |
| document.getElementById('fileInput').value = ''; | |
| document.getElementById('fileInfo').classList.add('hidden'); | |
| document.getElementById('analysisSection').classList.add('hidden'); | |
| document.getElementById('analysisSection').style.opacity = '0'; | |
| // Destroy charts | |
| Object.values(charts).forEach(chart => chart.destroy()); | |
| charts = {}; | |
| // Clear data | |
| csvData = []; | |
| headers = []; | |
| } | |
| </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=poluxmachine/csv" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |