Preview: Data Analysis Agent Template
AI-powered data analysis agent that can analyze CSV files, create visualizations, and provide insights using pandas, numpy, and matplotlib.
Preview Mode
This is a preview with sample data. The template uses placeholders like
which will be replaced with actual agent data.
Template Preview
Template Metadata
- Slug
- data-analysis-agent-template
- Created By
- ozzo
- Created
- Oct 27, 2025
- Usage Count
- 12
Tags
data-analysis
pandas
visualization
csv
statistics
Code Statistics
- HTML Lines
- 140
- CSS Lines
- 312
- JS Lines
- 322
- Python Lines
- 199
Source Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>{{ agent_name }} â AI-Powered Data Analysis</title>
<style>
{{ css_code }}
</style>
<!-- Conditional Script Imports -->
{% if needs_pyodide %}
<script src="https://cdn.jsdelivr.net/pyodide/v{{ pyodide_version }}/full/pyodide.js"></script>
{% endif %}
<script>
const PROVIDER = "{{ embedded_provider }}";
const API_KEY = "{{ embedded_api_key }}";
const AGENT_CONFIG = {{ default_config|safe }};
const NEEDS_PYODIDE = {{ needs_pyodide|lower }};
const PYODIDE_VERSION = "{{ pyodide_version }}";
</script>
</head>
<body>
<div class="dashboard">
<div class="sidebar">
<div class="agent-header">
<div class="agent-icon">đ</div>
<div>
<h2 style="margin: 0; font-size: 1.1rem;">Data Analysis Agent</h2>
<p style="margin: 0; color: #64748b; font-size: 0.875rem;">Data Analysis</p>
</div>
</div>
<h3>Data Upload</h3>
<div class="input-section" style="margin-bottom: 2rem;">
<div class="upload-area" id="file-upload-area"
ondragenter="handleDragOver(event)"
ondragover="handleDragOver(event)"
ondragleave="handleDragLeave(event)"
ondrop="handleDrop(event)"
onclick="document.getElementById('csv-file-input').click()">
<div style="color: var(--text-muted);">
<div style="font-size: 2rem; margin-bottom: 0.5rem;">đ</div>
<p style="margin: 0; font-weight: 500;">Drop CSV file here</p>
<p style="margin: 0.25rem 0 0 0; font-size: 0.75rem;">or click to browse</p>
</div>
<input type="file" id="csv-file-input" accept=".csv,.txt" style="display: none;" onchange="handleFileSelect(event)">
</div>
<div id="file-info" class="file-info" style="display: none;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-weight: 500;" id="fileName">data.csv</div>
<div style="font-size: 0.75rem; color: #6b7280;" id="fileSize">1.2 KB</div>
</div>
<button onclick="clearFile()" class="btn" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;">Clear</button>
</div>
</div>
</div>
<!-- Data Preview Section -->
<div id="data-preview" style="display: none; margin-top: 1.5rem;">
<h3>Data Preview</h3>
<div id="preview-content" style="
border: 1px solid var(--surface-border);
border-radius: 8px;
padding: 1rem;
background: var(--surface-bg);
overflow-x: auto;
max-height: 300px;
overflow-y: auto;
">
<div id="preview-table"></div>
</div>
</div>
<h3>Analysis Query</h3>
<div class="input-section">
<textarea id="queryInput"
class="query-input"
placeholder="Examples:
âĸ Plot a histogram of sales values
âĸ Show correlation between variables
âĸ What are the summary statistics?
âĸ Create a chart for the region column"
rows="4">
</textarea>
<button class="analyze-button" id="analyzeBtn" onclick="processQuery()">
<span>đ</span>
<span>Analyze Data</span>
</button>
</div>
<div class="sample-queries">
<h4 style="margin-bottom: 0.75rem; font-size: 0.9rem; color: var(--text-heading);">Sample Queries</h4>
<div class="sample-query" onclick="fillSampleQuery('Show me a summary of the data')">đ Show me a summary of the data</div>
<div class="sample-query" onclick="fillSampleQuery('Create a histogram of sales')">đ Create a histogram of sales</div>
<div class="sample-query" onclick="fillSampleQuery('What are the correlations between numeric columns?')">đ What are the correlations between numeric columns?</div>
<div class="sample-query" onclick="fillSampleQuery('Show value counts for the region column')">đ Show value counts for the region column</div>
</div>
</div>
<div class="main-content">
<div class="agent-header">
<h1 style="margin: 0; font-size: 1.5rem; color: var(--text-heading);">Data Analysis Results</h1>
<div style="margin-left: auto; font-size: 0.875rem; color: #64748b;">Powered by AI</div>
</div>
<div class="result-area">
<div id="results-container" class="result-content">
<div style="text-align: center; color: var(--text-muted); padding: 3rem 0;">
<div style="font-size: 3rem; margin-bottom: 1rem;">đ</div>
<h3 style="margin: 0 0 0.5rem 0;">Ready for Analysis</h3>
<p style="margin: 0;">Upload a CSV file and ask questions about your data</p>
</div>
</div>
<div class="loading-indicator" id="loading-indicator">
<div class="spinner"></div>
<p>Analyzing your data...</p>
</div>
</div>
</div>
</div>
<script>
{{ js_code|safe }}
</script>
<!-- Hidden Python code -->
<script type="text/python" id="python-code">
{{ python_code|safe }}
</script>
</body>
</html>
:root {
--primary: #059669;
--primary-hover: #047857;
--secondary: #0ea5e9;
--surface-bg: #f8fafc;
--surface-border: #e2e8f0;
--text-heading: #1e293b;
--text-body: #475569;
--text-muted: #64748b;
--radius-lg: 12px;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f0f9ff 0%, #f8fafc 100%);
color: var(--text-body);
line-height: 1.6;
}
.dashboard {
max-width: 1400px;
margin: 0 auto;
padding: 1.5rem;
display: grid;
grid-template-columns: 300px 1fr;
gap: 1.5rem;
min-height: 100vh;
}
.sidebar {
background: white;
border-radius: var(--radius-lg);
padding: 1.5rem;
height: fit-content;
border: 1px solid var(--surface-border);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.main-content {
background: white;
border-radius: var(--radius-lg);
padding: 2rem;
border: 1px solid var(--surface-border);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
position: relative;
}
.agent-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--surface-border);
}
.agent-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 1.25rem;
}
.sidebar h3 {
margin: 0 0 1rem 0;
color: var(--text-heading);
font-size: 1.1rem;
}
.upload-area {
border: 2px dashed var(--surface-border);
border-radius: 8px;
padding: 2rem;
text-align: center;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 1rem;
}
.upload-area:hover, .upload-area.drag-over {
border-color: var(--primary);
background: rgba(5, 150, 105, 0.05);
}
.file-info {
display: none;
padding: 0.75rem;
background: #f0f9ff;
border: 1px solid #bfdbfe;
border-radius: 6px;
margin-bottom: 0.75rem;
}
.query-input {
width: 100%;
min-height: 100px;
max-height: 200px;
padding: 0.75rem;
border: 1px solid var(--surface-border);
border-radius: 8px;
font-family: inherit;
font-size: 0.875rem;
resize: vertical;
margin-bottom: 0.75rem;
outline: none;
transition: border-color 0.2s;
box-sizing: border-box;
}
.query-input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.1);
}
.analyze-button {
width: 100%;
background: var(--primary);
color: white;
padding: 0.75rem 1rem;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.analyze-button:hover:not(:disabled) {
background: var(--primary-hover);
}
.analyze-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
background: var(--primary);
color: white;
}
.btn:hover { background: var(--primary-hover); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.sample-queries {
margin-top: 1.5rem;
}
.sample-query {
background: #f8fafc;
border: 1px solid var(--surface-border);
border-radius: 6px;
padding: 0.5rem 0.75rem;
margin-bottom: 0.5rem;
font-size: 0.875rem;
cursor: pointer;
transition: background-color 0.2s;
}
.sample-query:hover {
background: #e2e8f0;
}
.result-area {
min-height: 500px;
background: var(--surface-bg);
border-radius: 8px;
padding: 1.5rem;
margin-top: 1rem;
border: 1px solid var(--surface-border);
position: relative;
}
.result-content {
line-height: 1.6;
}
.message {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1rem;
border-radius: 8px;
background: white;
border: 1px solid var(--surface-border);
}
.message-user {
background: #eff6ff;
border-color: #bfdbfe;
}
.message-assistant {
background: #f0fdf4;
border-color: #bbf7d0;
}
.message-error {
background: #fef2f2;
border-color: #fecaca;
}
.message-system, .message-info {
background: #fefce8;
border-color: #fde047;
}
.message-icon {
font-size: 1.5rem;
flex-shrink: 0;
}
.message-content {
flex: 1;
overflow-wrap: break-word;
}
.result-content h1, .result-content h2, .result-content h3 {
color: var(--text-heading);
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.result-content img {
max-width: 100%;
height: auto;
margin: 1rem 0;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.loading-indicator {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: #64748b;
}
.loading-indicator.show {
display: block;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #e2e8f0;
border-top: 3px solid var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
table {
border-collapse: collapse;
width: 100%;
margin: 1rem 0;
font-family: monospace;
font-size: 0.9rem;
background: white;
border-radius: 8px;
overflow: hidden;
}
th, td {
border: 1px solid var(--surface-border);
padding: 0.5rem;
text-align: left;
}
th {
background: var(--surface-bg);
font-weight: 600;
color: var(--text-heading);
}
tr:nth-child(even) {
background: #f8fafc;
}
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
gap: 1rem;
padding: 1rem;
}
}
// File Upload Handlers
function handleDragOver(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
document.getElementById('file-upload-area').classList.add('drag-over');
}
function handleDragLeave(event) {
event.preventDefault();
document.getElementById('file-upload-area').classList.remove('drag-over');
}
function handleDrop(event) {
event.preventDefault();
document.getElementById('file-upload-area').classList.remove('drag-over');
const files = event.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
}
function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
handleFile(file);
}
}
async function handleFile(file) {
const fileInfo = document.getElementById('file-info');
const fileName = document.getElementById('fileName') || document.querySelector('#file-info .file-name');
const fileSize = document.getElementById('fileSize') || document.querySelector('#file-info .file-size');
const dataPreview = document.getElementById('data-preview');
if (fileName) fileName.textContent = file.name;
if (fileSize) fileSize.textContent = formatFileSize(file.size);
if (fileInfo) fileInfo.style.display = 'block';
// Wait for Pyodide to be ready
let attempts = 0;
const maxAttempts = 150; // 30 seconds (150 * 200ms)
while (!window.pyodide && attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 200));
attempts++;
}
if (!window.pyodide) {
console.error('Pyodide not initialized after 30 seconds');
return;
}
// Read and preview the file
const reader = new FileReader();
reader.onload = async function(e) {
const csvContent = e.target.result;
displayDataPreview(csvContent);
if (dataPreview) dataPreview.style.display = 'block';
// Load data into Python for analysis
try {
window.pyodide.globals.set('csv_content', csvContent);
window.pyodide.globals.set('filename', file.name);
const result = await window.pyodide.runPythonAsync('load_csv_data(csv_content, filename)');
console.log('â
CSV data loaded into Python:', result);
} catch (error) {
console.error('â Failed to load CSV into Python:', error);
}
};
reader.readAsText(file);
}
function displayDataPreview(csvContent) {
const lines = csvContent.split('\n').filter(line => line.trim());
const previewTable = document.getElementById('preview-table');
if (!previewTable || lines.length === 0) return;
const maxRows = Math.min(lines.length, 10); // Show first 10 rows
let tableHTML = '<table style="width: 100%; border-collapse: collapse; font-size: 0.875rem;">';
for (let i = 0; i < maxRows; i++) {
const cells = lines[i].split(',');
const isHeader = i === 0;
const tag = isHeader ? 'th' : 'td';
const style = isHeader ?
'border: 1px solid var(--surface-border); padding: 0.5rem; background: var(--surface-bg); font-weight: 600;' :
'border: 1px solid var(--surface-border); padding: 0.5rem;';
tableHTML += '<tr>';
cells.forEach(cell => {
tableHTML += `<${tag} style="${style}">${cell.trim()}</${tag}>`;
});
tableHTML += '</tr>';
}
if (lines.length > 10) {
tableHTML += '<tr><td colspan="100%" style="border: 1px solid var(--surface-border); padding: 0.5rem; text-align: center; font-style: italic; color: var(--text-muted);">... and ' + (lines.length - 10) + ' more rows</td></tr>';
}
tableHTML += '</table>';
previewTable.innerHTML = tableHTML;
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function clearFile() {
const fileInput = document.getElementById('csv-file-input');
const fileInfo = document.getElementById('file-info');
const dataPreview = document.getElementById('data-preview');
if (fileInput) fileInput.value = '';
if (fileInfo) fileInfo.style.display = 'none';
if (dataPreview) dataPreview.style.display = 'none';
}
// Global variables
let pyodide = null;
let current_api_key = '';
// Load dataset into Python
async function loadDataset(csvContent, filename) {
if (!window.pyodide) {
addMessage('error', 'Python environment not ready. Please wait...');
return;
}
try {
addMessage('system', `Loading ${filename}...`);
// Call Python function to load data
window.pyodide.globals.set('csv_content', csvContent);
window.pyodide.globals.set('filename', filename);
const result = await window.pyodide.runPythonAsync(`
load_csv_data(csv_content, filename)
`);
addMessage('system', result);
// Show quick info about the dataset
const info = await window.pyodide.runPython(`
f"Dataset shape: {current_data.shape if current_data is not None else 'No data'}"
`);
addMessage('info', info);
} catch (error) {
addMessage('error', `Error loading dataset: ${error.message}`);
}
}
// Query processing
async function processQuery() {
const input = document.getElementById('queryInput');
const query = input.value.trim();
if (!query) return;
console.log('[QUERY START]', query, new Date().toLocaleTimeString());
const startTime = Date.now();
if (!window.pyodide) {
addMessage('error', 'Python environment not ready. Please wait...');
return;
}
// Clear input and add user message
input.value = '';
addMessage('user', query);
// Show loading indicator and disable button
const loadingEl = document.getElementById('loading-indicator');
const analyzeBtn = document.getElementById('analyzeBtn');
if (loadingEl) loadingEl.classList.add('show');
if (analyzeBtn) {
analyzeBtn.disabled = true;
analyzeBtn.innerHTML = '<span>âŗ</span><span>Processing...</span>';
}
try {
console.log('[PYODIDE] Setting up query execution');
// Set API key for this query
if (window.API_KEY) {
window.pyodide.globals.set('current_api_key', window.API_KEY);
console.log('[PYODIDE] API key set, length:', window.API_KEY.length);
}
// Process query in Python
window.pyodide.globals.set('user_query', query);
console.log('[PYODIDE] Calling process_user_query()');
const result = await window.pyodide.runPythonAsync(`process_user_query(user_query)`);
console.log('[PYODIDE] Query completed, result length:', result ? result.length : 0);
addMessage('assistant', result);
} catch (error) {
console.error('[ERROR] Query processing failed:', error);
addMessage('error', `Error processing query: ${error.message}`);
} finally {
const duration = Date.now() - startTime;
console.log('[QUERY END] Duration:', duration + 'ms');
if (loadingEl) loadingEl.classList.remove('show');
if (analyzeBtn) {
analyzeBtn.disabled = false;
analyzeBtn.innerHTML = '<span>đ</span><span>Analyze Data</span>';
}
}
}
// Sample query functions
function fillSampleQuery(query) {
const textarea = document.getElementById('queryInput');
textarea.value = query;
textarea.focus();
// Don't auto-submit - let user review and click button
}
// Message display
function addMessage(type, content) {
const resultsContainer = document.getElementById('results-container');
const messageEl = document.createElement('div');
messageEl.className = `message message-${type}`;
// Preserve image tags by replacing them with placeholders
const imgPlaceholders = [];
let contentWithPlaceholders = content.replace(/<img[^>]*>/g, (match) => {
const placeholder = `___IMG_PLACEHOLDER_${imgPlaceholders.length}___`;
imgPlaceholders.push(match);
return placeholder;
});
// Handle markdown-like formatting
let formattedContent = contentWithPlaceholders
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/### (.*?)(\n|$)/g, '<h3>$1</h3>')
.replace(/## (.*?)(\n|$)/g, '<h2>$1</h2>')
.replace(/# (.*?)(\n|$)/g, '<h1>$1</h1>')
.replace(/^- (.*?)$/gm, '<li>$1</li>')
.replace(/^âĸ (.*?)$/gm, '<li>$1</li>')
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
// Convert markdown tables to HTML
formattedContent = formattedContent.replace(
/(\|[^\n]+\|\n)(\|[-:\s|]+\|\n)((?:\|[^\n]+\|\n?)+)/g,
(match, headerRow, separatorRow, bodyRows) => {
// Parse header
const headers = headerRow.split('|').filter(h => h.trim()).map(h => h.trim());
// Parse body rows
const rows = bodyRows.trim().split('\n').map(row =>
row.split('|').filter(c => c.trim()).map(c => c.trim())
);
// Build HTML table
let tableHtml = '<table style="width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.875rem;">';
// Header
tableHtml += '<thead><tr>';
headers.forEach(h => {
tableHtml += `<th style="border: 1px solid var(--surface-border); padding: 0.5rem; background: var(--surface-bg); font-weight: 600; text-align: left;">${h}</th>`;
});
tableHtml += '</tr></thead>';
// Body
tableHtml += '<tbody>';
rows.forEach((row, idx) => {
const bgStyle = idx % 2 === 0 ? 'background: white;' : 'background: #f8fafc;';
tableHtml += `<tr style="${bgStyle}">`;
row.forEach(cell => {
tableHtml += `<td style="border: 1px solid var(--surface-border); padding: 0.5rem;">${cell}</td>`;
});
tableHtml += '</tr>';
});
tableHtml += '</tbody></table>';
return tableHtml;
}
);
// Replace newlines with <br> (after table processing)
formattedContent = formattedContent.replace(/\n/g, '<br>');
// Restore image tags
imgPlaceholders.forEach((img, index) => {
formattedContent = formattedContent.replace(`___IMG_PLACEHOLDER_${index}___`, img);
});
messageEl.innerHTML = `
<div class="message-icon">
${type === 'user' ? 'đ¤' : type === 'assistant' ? 'đ¤' : type === 'error' ? 'â' : type === 'system' ? 'đ§' : 'âšī¸'}
</div>
<div class="message-content">${formattedContent}</div>
`;
resultsContainer.appendChild(messageEl);
resultsContainer.scrollTop = resultsContainer.scrollHeight;
}
// Enter key handling
document.addEventListener('DOMContentLoaded', function() {
// Enter key for query
const queryInput = document.getElementById('queryInput');
if (queryInput) {
queryInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
processQuery();
}
});
}
});
import pandas as pd
import numpy as np
# Template Variables - Users can customize these
max_rows = [[[MAX_ROWS|1000]]]
precision = [[[PRECISION|2]]]
chart_type = "[[[CHART_TYPE|bar]]]"
include_summary = [[[INCLUDE_SUMMARY|True]]]
# Global variables to store loaded data
current_data = None
current_filename = None
def dataframe_to_markdown(df, max_rows=10):
"""Convert pandas DataFrame to markdown table format."""
if len(df) > max_rows:
df = df.head(max_rows)
truncated = True
else:
truncated = False
lines = []
headers = [''] + list(df.columns)
lines.append('| ' + ' | '.join(str(h) for h in headers) + ' |')
lines.append('|' + '|'.join([' --- ' for _ in range(len(headers))]) + '|')
for idx, row in df.iterrows():
row_values = [str(idx)] + [str(v) for v in row]
lines.append('| ' + ' | '.join(row_values) + ' |')
if truncated:
lines.append(f'\n*Showing first {max_rows} rows of {len(df)} total*')
return '\n'.join(lines)
def load_csv_data(csv_content: str, filename: str = "data.csv"):
"""Load CSV data into global variable."""
global current_data, current_filename
try:
from io import StringIO
current_data = pd.read_csv(StringIO(csv_content))
current_filename = filename
return f"â
Loaded {current_data.shape[0]} rows and {current_data.shape[1]} columns from {filename}"
except Exception as e:
return f"â Error loading CSV: {str(e)}"
def get_data_summary() -> str:
"""Get dataset summary: shape, columns, data types, statistics."""
global current_data
if current_data is None:
return "â No data loaded. Please upload a CSV file first."
result = []
result.append(f"## Dataset Overview")
result.append(f"Shape: {current_data.shape[0]} rows à {current_data.shape[1]} columns")
result.append(f"\nColumns: {', '.join(current_data.columns.tolist())}")
result.append("\n### Data Types:")
for col in current_data.columns:
dtype = str(current_data[col].dtype)
result.append(f"- **{col}**: {dtype}")
result.append("\n### Missing Values:")
missing = current_data.isnull().sum()
has_missing = False
for col in current_data.columns:
if missing[col] > 0:
pct = (missing[col] / len(current_data)) * 100
result.append(f"- **{col}**: {missing[col]} missing ({pct:.1f}%)")
has_missing = True
if not has_missing:
result.append("- â
No missing values found")
if current_data.select_dtypes(include=[np.number]).shape[1] > 0:
result.append("\n### Summary Statistics:")
stats_df = current_data.describe()
result.append(dataframe_to_markdown(stats_df))
return "\n".join(result)
def get_column_info() -> str:
"""Get column info: data types and missing values."""
global current_data
if current_data is None:
return "â No data loaded. Please upload a CSV file first."
result = [f"## Column Information\n"]
result.append(f"Dataset has **{len(current_data.columns)} columns** and **{len(current_data)} rows**:\n")
result.append("| Column | Type | Non-Null | Missing | % Missing |")
result.append("| --- | --- | --- | --- | --- |")
for col in current_data.columns:
dtype = str(current_data[col].dtype)
non_null = current_data[col].count()
total = len(current_data)
missing = total - non_null
pct_missing = (missing / total) * 100
result.append(f"| {col} | {dtype} | {non_null} | {missing} | {pct_missing:.1f}% |")
return "\n".join(result)
def get_value_counts(column: str) -> str:
"""Get value counts for a specific column."""
global current_data
if current_data is None:
return "â No data loaded. Please upload a CSV file first."
if column not in current_data.columns:
return f"â Column '{column}' not found. Available columns: {', '.join(current_data.columns)}"
result = [f"## Value counts for '{column}'\n"]
value_counts = current_data[column].value_counts().head(15)
total = len(current_data)
result.append("| Value | Count | Percentage |")
result.append("| --- | --- | --- |")
for value, count in value_counts.items():
pct = (count / total) * 100
result.append(f"| {value} | {count} | {pct:.1f}% |")
unique_count = current_data[column].nunique()
if unique_count > 15:
result.append(f"\n*Showing top 15 of {unique_count} unique values*")
else:
result.append(f"\n*Total unique values: {unique_count}*")
return "\n".join(result)
def create_chart(column: str, chart_type: str = "histogram") -> str:
"""Create a chart for a specific column."""
global current_data
if current_data is None:
return "â No data loaded. Please upload a CSV file first."
if column not in current_data.columns:
return f"â Column '{column}' not found. Available columns: {', '.join(current_data.columns)}"
try:
try:
import matplotlib
matplotlib.use('Agg') # CRITICAL: Use Agg backend for Pyodide
import matplotlib.pyplot as plt
import base64
from io import BytesIO
plt.ioff()
except ImportError as e:
return f"â Chart creation unavailable: {str(e)}"
fig, ax = plt.subplots(figsize=(10, 6))
if chart_type.lower() == "bar":
value_counts = current_data[column].value_counts().head(10)
ax.bar(range(len(value_counts)), value_counts.values, color='#059669')
ax.set_xticks(range(len(value_counts)))
ax.set_xticklabels(value_counts.index, rotation=45, ha='right')
ax.set_ylabel('Count')
ax.set_title(f'Bar Chart: {column}', fontsize=14, fontweight='bold')
ax.grid(axis='y', alpha=0.3)
elif chart_type.lower() == "histogram":
if pd.api.types.is_numeric_dtype(current_data[column]):
ax.hist(current_data[column].dropna(), bins=20, alpha=0.7, color='#059669', edgecolor='white')
ax.set_xlabel(column)
ax.set_ylabel('Frequency')
ax.set_title(f'Histogram: {column}', fontsize=14, fontweight='bold')
ax.grid(axis='y', alpha=0.3)
else:
plt.close(fig)
return f"â Cannot create histogram for non-numeric column '{column}'. Try 'bar' chart instead."
else:
plt.close(fig)
return f"â Unsupported chart type '{chart_type}'. Use: bar or histogram"
plt.tight_layout()
# Save to BytesIO buffer and encode to base64
buffer = BytesIO()
plt.savefig(buffer, format='png', dpi=100, bbox_inches='tight')
buffer.seek(0)
image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
plt.close(fig)
# Return HTML with embedded base64 image
return f"""â
Chart created successfully for '{column}' ({chart_type} chart).
<img src="data:image/png;base64,{image_base64}" alt="{chart_type.title()} chart for {column}" style="max-width: 100%; height: auto; margin: 10px 0; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
Chart shows the distribution of values in the '{column}' column."""
except Exception as e:
import traceback
error_details = traceback.format_exc()
return f"â Error creating chart: {str(e)}\n\nDetails:\n{error_details}"
def get_correlation_analysis() -> str:
"""Get correlation analysis for numeric columns."""
global current_data
if current_data is None:
return "â No data loaded. Please upload a CSV file first."
numeric_cols = current_data.select_dtypes(include=[np.number]).columns
if len(numeric_cols) < 2:
return "â Need at least 2 numerical columns to calculate correlations."
corr_matrix = current_data[numeric_cols].corr()
result = ["## Correlation Analysis\n"]
result.append("### Correlation Matrix:\n")
result.append(dataframe_to_markdown(corr_matrix, max_rows=20))
result.append("\n### Key Insights:")
strong_corr = []
for i in range(len(numeric_cols)):
for j in range(i+1, len(numeric_cols)):
corr_val = corr_matrix.iloc[i, j]
if abs(corr_val) > 0.7:
strength = "strong positive" if corr_val > 0 else "strong negative"
emoji = "đ" if corr_val > 0 else "đ"
strong_corr.append(f"- {emoji} **{numeric_cols[i]}** and **{numeric_cols[j]}**: {strength} correlation ({corr_val:.3f})")
if strong_corr:
result.extend(strong_corr)
else:
result.append("- âšī¸ No strong correlations found (|r| > 0.7)")
return "\n".join(result)