Building a Data Analysis Agent
Create an end-to-end data analysis agent that accepts a CSV file and a natural language question, writes pandas and matplotlib code iteratively, and produces a polished report.
Building a Data Analysis Agent is a free AI Engineering Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Engineering Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Data Analysis Agent Vision
A data analysis agent accepts a CSV file and a natural language question, then autonomously explores the data, cleans it, computes statistics, generates visualizations, and produces a polished report — all through iterative code generation and execution. This is one of the most practical applications of code agents and directly replicates the workflow of a data analyst, minus the human effort.
The Agent's Overall Architecture
The data analysis agent has four conceptual phases: Exploration (understand the data's shape, types, and quality), Cleaning (handle missing values, outliers, type conversions), Analysis (compute the metrics and statistics that answer the question), and Reporting (generate charts and summarize findings in prose). Each phase corresponds to 1-5 code execution iterations.
DATA_AGENT_SYSTEM_PROMPT = '''
You are a data analysis agent. Given a CSV file and a question, answer it through iterative Python code.
Phases to follow:
1. EXPLORE: Load the data, print shape, dtypes, head(), describe(), and check for nulls.
2. CLEAN: Handle missing values, fix dtypes, remove outliers.
3. ANALYZE: Compute statistics, groupbys, correlations - whatever answers the question.
4. REPORT: Generate a matplotlib chart saved to /workspace/chart.png and write a text summary.
Rules:
- Write code in ```python ... ``` blocks.
- Save intermediate results to /workspace/ for use in later steps.
- Print key findings after each computation so you can observe them.
- When all phases are done, say TASK COMPLETE and summarize the answer.
'''Phase 1: Data Exploration Code
The exploration phase loads the data and immediately prints all the information needed to plan the analysis: shape, column names and types, sample rows, summary statistics, and null counts. The LLM reads this output and uses it to make informed decisions about cleaning and analysis in subsequent iterations.
import pandas as pd
import numpy as np
# Load dataset
df = pd.read_csv('/workspace/data.csv')
# Basic exploration
print('=== SHAPE ===' )
print(df.shape)
print('\n=== DTYPES ===')
print(df.dtypes)
print('\n=== HEAD ===')
print(df.head())
print('\n=== DESCRIBE ===')
print(df.describe(include='all'))
print('\n=== NULL COUNTS ===')
print(df.isnull().sum())
print('\n=== UNIQUE VALUES (categorical) ===')
for col in df.select_dtypes(include='object').columns:
print(f'{col}: {df[col].nunique()} unique values: {df[col].unique()[:5]}')
# Save for next iteration
df.to_parquet('/workspace/raw.parquet')
print('\nData saved to workspace.')Phase 2: Data Cleaning
After exploration, the agent writes targeted cleaning code based on what it observed. The cleaning strategy is dynamic — if the LLM observed 15% nulls in a column, it decides whether to drop or impute. If it saw negative values in a column that should be non-negative, it filters them. This adaptive, observation-driven cleaning is what makes the agent genuinely useful rather than just running a fixed pipeline.
import pandas as pd
import numpy as np
# Load from previous iteration
df = pd.read_parquet('/workspace/raw.parquet')
# Fill numeric nulls with median (robust to outliers)
for col in df.select_dtypes(include='number').columns:
if df[col].isnull().sum() > 0:
median_val = df[col].median()
df[col] = df[col].fillna(median_val)
print(f'Filled nulls in {col} with median {median_val:.2f}')
# Drop rows where categorical key columns are null
df = df.dropna(subset=['category', 'date'])
# Fix date column type
df['date'] = pd.to_datetime(df['date'], errors='coerce')
df = df.dropna(subset=['date']) # drop unparseable dates
# Remove clear outliers: revenue > 3 std deviations from mean
if 'revenue' in df.columns:
z_scores = (df['revenue'] - df['revenue'].mean()) / df['revenue'].std()
df = df[z_scores.abs() < 3]
print(f'Removed outliers, rows remaining: {len(df)}')
df.to_parquet('/workspace/cleaned.parquet')
print('Cleaning complete:', df.shape)Phase 3: Answering the Question
In the analysis phase, the agent focuses on computing the specific answer to the user's question. If the question is 'which product category had the highest revenue last quarter?', the agent writes code to filter by date, group by category, sum revenue, and sort. The analysis code is generated fresh based on both the question and what the agent observed during exploration — it is not a generic template.
import pandas as pd
import matplotlib
matplotlib.use('Agg') # non-interactive backend for server use
import matplotlib.pyplot as plt
df = pd.read_parquet('/workspace/cleaned.parquet')
# Analysis: revenue by category for last quarter
df['date'] = pd.to_datetime(df['date'])
last_quarter = df[df['date'] >= '2024-10-01']
revenue_by_category = (
last_quarter
.groupby('category')['revenue']
.sum()
.sort_values(ascending=False)
)
print('Revenue by category (last quarter):')
print(revenue_by_category)
print(f'\nTop category: {revenue_by_category.index[0]} (${revenue_by_category.iloc[0]:,.0f})')
# Save analysis result
revenue_by_category.to_json('/workspace/analysis_result.json')Phase 4: Generating Charts
Visualizations make analytical findings clear and compelling. The data analysis agent generates matplotlib charts and saves them as PNG files in the workspace. Key chart design decisions — chart type, color palette, axis labels, title, annotations — are made by the LLM based on the nature of the data. The agent always saves charts to /workspace/ so they can be included in the final report.
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import json
with open('/workspace/analysis_result.json') as f:
data = json.load(f)
categories = list(data.keys())
values = [data[k] / 1e6 for k in categories] # convert to millions
fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.barh(categories, values, color='steelblue', edgecolor='white')
# Add value labels on bars
for bar, val in zip(bars, values):
ax.text(bar.get_width() + 0.05, bar.get_y() + bar.get_height()/2,
f'${val:.1f}M', va='center', fontsize=11)
ax.set_xlabel('Revenue ($ Millions)', fontsize=12)
ax.set_title('Revenue by Product Category - Q4 2024', fontsize=14, fontweight='bold')
ax.invert_yaxis() # highest on top
plt.tight_layout()
plt.savefig('/workspace/chart.png', dpi=150, bbox_inches='tight')
print('Chart saved to /workspace/chart.png')Generating the Written Report
The final iteration produces the written summary report. The LLM reads the analysis results and chart description, then writes a concise, accurate narrative that answers the original question with specific numbers. The report references the chart and includes the key insight, supporting data, and a recommendation or implication for the business.
import json
with open('/workspace/analysis_result.json') as f:
revenue_data = json.load(f)
top_category = max(revenue_data, key=revenue_data.get)
top_revenue = revenue_data[top_category]
total_revenue = sum(revenue_data.values())
share = top_revenue / total_revenue * 100
report = f'''# Q4 2024 Revenue Analysis
## Answer
The **{top_category}** category generated the highest revenue in Q4 2024:
**${top_revenue:,.0f}** ({share:.1f}% of total Q4 revenue).
## Key Findings
- Total Q4 revenue: ${total_revenue:,.0f}
- Top 3 categories: {list(revenue_data.items())[:3]}
- The top category outperformed the average by {top_revenue / (total_revenue / len(revenue_data)):.1f}x
## Chart
See chart.png for the full breakdown by category.
## Recommendation
Consider reallocating marketing budget toward {top_category} to
capitalize on its demonstrated strong performance.
'''
with open('/workspace/report.md', 'w') as f:
f.write(report)
print('TASK COMPLETE')
print(report)Assembling the Agent Orchestrator
The orchestrator ties all phases together: it initializes the workspace, copies the input CSV, runs the code execution loop, monitors for the TASK COMPLETE signal, and then collects the output files (report.md, chart.png). The orchestrator also handles errors, retries, and the final cleanup of intermediate files.
import shutil
from pathlib import Path
def run_data_analysis_agent(csv_path: str, question: str) -> dict:
workspace = setup_workspace()
shutil.copy(csv_path, workspace / 'data.csv')
messages = [
{'role': 'system', 'content': DATA_AGENT_SYSTEM_PROMPT},
{'role': 'user', 'content': f'File: /workspace/data.csv\nQuestion: {question}'}
]
state = ExecutionState(task=question)
for iteration in range(15): # max 15 iterations
response = llm.complete(messages)
code = extract_code_block(response)
if not code or 'TASK COMPLETE' in response:
break
stdout, stderr = execute_in_docker(code, workspace=workspace)
observation = format_observation(stdout, stderr)
messages += [
{'role': 'assistant', 'content': response},
{'role': 'user', 'content': observation}
]
state.after_execution(stdout, f'iteration_{iteration}')
return {
'report': (workspace / 'report.md').read_text() if (workspace / 'report.md').exists() else '',
'chart_path': str(workspace / 'chart.png'),
'iterations': state.iteration
}Handling Ambiguous Questions
Users often ask ambiguous questions like 'which products are performing well?'. A robust data analysis agent clarifies ambiguity before starting: it first explores the data, identifies what metrics are available, and then either infers the most sensible interpretation or asks the user for clarification. This reflection step prevents the agent from producing a technically correct but practically useless answer.
def handle_ambiguous_question(question: str, df_summary: dict) -> str:
clarification_prompt = f'''The user asked: '{question}'
Available data:
- Columns: {df_summary['columns']}
- Date range: {df_summary['date_range']}
- Metrics available: {df_summary['numeric_columns']}
Is the question clear enough to answer definitively?
If yes, restate the specific interpretation you will use.
If no, list the 2-3 clarifications needed to proceed.'''
response = llm.complete([{'role': 'user', 'content': clarification_prompt}])
return responseQuality Checks on Agent Output
After the agent completes, validate the quality of its output. Check that the report file was actually created, the chart PNG is a valid image file, the numbers in the report match the data in analysis_result.json, and the report actually answers the original question. An automated QA step using a second LLM call can catch cases where the agent completed the loop but produced a poor answer.
def validate_analysis_output(workspace: str, question: str, report: str) -> dict:
validation_prompt = f'''Original question: {question}
Agent report:
{report[:2000]}
Rate this analysis on a scale of 1-5 for:
1. Does it directly answer the question? (1=No, 5=Yes)
2. Are specific numbers cited? (1=No, 5=Yes)
3. Is the conclusion clearly stated? (1=No, 5=Yes)
Return JSON: {{"question_answered": N, "numbers_cited": N, "clear_conclusion": N, "overall": N}}'''
result = llm.complete([{'role': 'user', 'content': validation_prompt}],
response_format={'type': 'json_object'})
scores = json.loads(result)
passed = scores['overall'] >= 4
return {'scores': scores, 'passed': passed}Extending the Agent with Domain Tools
The basic code agent becomes even more powerful when extended with domain-specific tools. For a financial data agent, add functions that fetch live stock prices. For a marketing data agent, add Google Analytics API access. For a sales agent, add Salesforce queries. These tools become available to the LLM through function definitions in the system prompt or through LangChain's @tool decorator.
Quick Check
Test your understanding of the data analysis agent from this lesson.
Lesson Recap
In this lesson you learned: a data analysis agent follows four phases — explore, clean, analyze, report — each implemented through iterative code generation and execution, chart generation with matplotlib and file-based state persistence tie the phases together into a coherent workflow, and output validation ensures the agent's final answer actually addresses the original question. Next up we explore LLM observability and tracing.
Frequently asked questions
Is the “Building a Data Analysis Agent” lesson free?
Yes — the full text of “Building a Data Analysis Agent” is free to read here on the web, and the AI Engineering Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Engineering Academy course, upgrade to CoddyKit PRO.
What will I learn in “Building a Data Analysis Agent”?
Create an end-to-end data analysis agent that accepts a CSV file and a natural language question, writes pandas and matplotlib code iteratively, and produces a polished report. You practise AI Engineering Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AI Engineering Academy?
No prior experience is required. AI Engineering Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Building a Data Analysis Agent” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AI Engineering Academy lesson?
Yes. Every AI Engineering Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The Code Execution Loop
- Sandboxing with Docker and RestrictedPython
- State Management Across Execution Steps
- Building a Data Analysis Agent