Chart Interpretation: Use AI to Add Narrative Insights to Charts
Build A Personal AI Agent – Episode 14
A Practical Guide for Evaluators and M&E Professionals
Why AI Chart Interpretation Matters
Charts alone don’t tell the full story. AI-powered narrative insights transform visualizations into actionable intelligence. This tutorial shows you how to build an AI agent that automatically generates insightful narratives from your M&E charts.
Part 1: Setting Up the System
Step 1: Install Required Libraries
pip install anthropic pandas numpyStep 2: Chart Data Extractor
# chart_data_extractor.py
import pandas as pd
import numpy as np
class ChartDataExtractor:
"""Extract key data points from chart data."""
def __init__(self, df):
self.df = df
def extract_target_actual(self):
"""Extract target vs actual data insights."""
if 'target' not in self.df.columns or 'actual' not in self.df.columns:
return None
data = {
'indicators': self.df['indicator_name'].tolist(),
'targets': self.df['target'].tolist(),
'actuals': self.df['actual'].tolist(),
'achievements': self.df['achievement'].tolist() if 'achievement' in self.df.columns else [],
'statuses': self.df['status'].tolist() if 'status' in self.df.columns else []
}
if data['achievements']:
data['avg_achievement'] = np.mean(data['achievements'])
data['max_achievement'] = np.max(data['achievements'])
data['min_achievement'] = np.min(data['achievements'])
data['on_track_count'] = sum(1 for s in data['statuses'] if s == 'on_track')
data['off_track_count'] = sum(1 for s in data['statuses'] if s == 'off_track')
data['at_risk_count'] = sum(1 for s in data['statuses'] if s == 'at_risk')
return data
def extract_trend_data(self):
"""Extract trend insights from time-series data."""
if 'date' not in self.df.columns or 'achievement' not in self.df.columns:
return None
df_trend = self.df.groupby('date')['achievement'].mean().reset_index()
df_trend = df_trend.sort_values('date')
data = {
'dates': df_trend['date'].dt.strftime('%Y-%m-%d').tolist(),
'achievements': df_trend['achievement'].tolist()
}
if len(data['achievements']) > 1:
data['change'] = data['achievements'][-1] - data['achievements'][0]
data['trend_direction'] = 'improving' if data['achievements'][-1] > data['achievements'][0] else 'declining'
data['current'] = data['achievements'][-1]
data['target'] = 80
return data
def extract_status_data(self):
"""Extract status distribution data."""
if 'status' not in self.df.columns:
return None
status_counts = self.df['status'].value_counts()
data = {
'statuses': status_counts.index.tolist(),
'counts': status_counts.values.tolist(),
'total': len(self.df)
}
data['percentages'] = [(c / data['total'] * 100) for c in data['counts']]
return dataPart 2: AI Narrative Generator
import anthropic
import os
from dotenv import load_dotenv
load_dotenv()
class ChartInterpreter:
"""Generate narrative insights from chart data using AI."""
def __init__(self, api_key=None):
self.client = anthropic.Anthropic(
api_key=api_key or os.getenv('ANTHROPIC_API_KEY')
)
def interpret_target_actual(self, data):
"""Generate narrative for target vs actual chart."""
if not data or not data.get('achievements'):
return "No data available for analysis."
prompt = f"""
You are an M&E expert. Analyze this performance data:
Average Achievement: {data['avg_achievement']:.1f}%
On Track: {data['on_track_count']}
At Risk: {data['at_risk_count']}
Off Track: {data['off_track_count']}
Provide a concise narrative that:
1. Summarizes overall performance
2. Highlights best and worst performers
3. Suggests priority actions
"""
response = self.client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=400,
temperature=0.3,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
def interpret_trend(self, data):
"""Generate narrative for trend chart."""
if not data:
return "No trend data available."
prompt = f"""
You are an M&E expert. Analyze this trend data:
Current Achievement: {data['current']:.1f}%
Change: {data['change']:.1f}%
Direction: {data['trend_direction']}
Provide a narrative that:
1. Describes the overall trend
2. Identifies if corrective action is needed
3. Suggests timeline to reach target
"""
response = self.client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=350,
temperature=0.3,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
def interpret_status_distribution(self, data):
"""Generate narrative for status distribution chart."""
if not data:
return "No status data available."
on_track = data['percentages'][data['statuses'].index('on_track')] if 'on_track' in data['statuses'] else 0
at_risk = data['percentages'][data['statuses'].index('at_risk')] if 'at_risk' in data['statuses'] else 0
off_track = data['percentages'][data['statuses'].index('off_track')] if 'off_track' in data['statuses'] else 0
prompt = f"""
You are an M&E expert. Analyze this status distribution:
On Track: {on_track:.1f}%
At Risk: {at_risk:.1f}%
Off Track: {off_track:.1f}%
Provide a narrative that:
1. Summarizes program health
2. Identifies areas of concern
3. Recommends actions for off-track indicators
"""
response = self.client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=350,
temperature=0.3,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].textPart 3: Complete Chart Interpretation System
class ChartInterpretationSystem:
"""Complete chart interpretation and narrative generation."""
def __init__(self, df):
self.df = df
self.extractor = ChartDataExtractor(df)
self.interpreter = ChartInterpreter()
self.narratives = {}
def generate_all_narratives(self):
"""Generate narratives for all chart types."""
# Target vs Actual
ta_data = self.extractor.extract_target_actual()
if ta_data:
self.narratives['target_actual'] = self.interpreter.interpret_target_actual(ta_data)
# Trend
trend_data = self.extractor.extract_trend_data()
if trend_data:
self.narratives['trend'] = self.interpreter.interpret_trend(trend_data)
# Status Distribution
status_data = self.extractor.extract_status_data()
if status_data:
self.narratives['status'] = self.interpreter.interpret_status_distribution(status_data)
return self.narratives
def get_narrative_html(self, chart_type='all'):
"""Get narratives formatted as HTML."""
if not self.narratives:
self.generate_all_narratives()
html = '<div style="font-family: Arial, sans-serif; padding: 15px;">'
if chart_type == 'all':
for key, narrative in self.narratives.items():
html += f'<div style="background: #f8f9fa; padding: 15px; margin: 10px 0; border-radius: 6px; border-left: 4px solid #4a9eff;">'
html += f'<h4 style="color: #1a1a2e; margin: 0 0 10px 0;">{key.replace("_", " ").title()}</h4>'
html += f'<p style="color: #333; line-height: 1.6; margin: 0;">{narrative.replace(chr(10), "<br>")}</p>'
html += '</div>'
else:
if chart_type in self.narratives:
html += f'<p style="color: #333; line-height: 1.6;">{self.narratives[chart_type].replace(chr(10), "<br>")}</p>'
html += '</div>'
return htmlPart 4: Complete Usage Example
import pandas as pd
import numpy as np
def create_sample_data():
"""Create sample M&E data for demonstration."""
indicators = ['Vaccination', 'Education', 'Health', 'WASH', 'Nutrition']
dates = ['2024-01-15', '2024-02-15', '2024-03-15', '2024-04-15']
data = []
for date in dates:
for indicator in indicators:
achievement = np.random.uniform(60, 98)
target = np.random.randint(100, 500)
actual = int(target * achievement / 100)
if achievement >= 90:
status = 'on_track'
elif achievement >= 70:
status = 'at_risk'
else:
status = 'off_track'
data.append({
'indicator_name': indicator,
'target': target,
'actual': actual,
'achievement': round(achievement, 1),
'status': status,
'category': np.random.choice(['Health', 'Education', 'WASH']),
'date': date
})
return pd.DataFrame(data)
def main():
# Create sample data
df = create_sample_data()
print(f"Created {len(df)} records")
# Initialize interpretation system
system = ChartInterpretationSystem(df)
# Generate narratives
print("\nGenerating chart narratives...")
narratives = system.generate_all_narratives()
# Display narratives
print("\n=== TARGET VS ACTUAL NARRATIVE ===")
print(narratives.get('target_actual', 'Not available'))
print("\n=== TREND NARRATIVE ===")
print(narratives.get('trend', 'Not available'))
print("\n=== STATUS DISTRIBUTION NARRATIVE ===")
print(narratives.get('status', 'Not available'))
# Get HTML formatted narratives
html = system.get_narrative_html('all')
with open('chart_narratives.html', 'w') as f:
f.write(html)
print("\nHTML narratives saved to chart_narratives.html")
if __name__ == "__main__":
main()
Example Output
Target vs Actual Narrative
The overall achievement across all indicators is 82.3% with 3 out of 5 indicators on track. The education indicator leads with 94.2% achievement, while the nutrition indicator is of concern at 68.5%. Priority action should focus on addressing the 1 off-track indicator (Nutrition) and providing additional support to the 1 at-risk indicator (Health).
Trend Narrative
Performance has been improving steadily over the last 3 months, with overall achievement rising from 76.2% to 84.1%. At this rate, the program is on track to reach its 90% target within the next 2-3 months. Continue current interventions and maintain monitoring.
Status Distribution Narrative
The program is in good health with 60% of indicators on track. However, 20% are at risk and 20% are off track. Immediate attention is needed for the off-track indicators, which are primarily in the health sector. Consider a targeted intervention plan for these priority areas.
Troubleshooting Chart Interpretation
| Issue | Solution |
|---|---|
| No narrative generated | Check that data has required columns |
| API key error | Set ANTHROPIC_API_KEY in .env file |
| Incomplete narrative | Ensure data has achievement and status columns |
Best Practices for AI Chart Narratives
- Provide Context: Include program background in prompts
- Be Specific: Use exact numbers and percentages in narratives
- Actionable Insights: Always include concrete recommendations
- Human Review: Always review AI-generated narratives for accuracy
- Consistent Tone: Maintain professional M&E tone
Next Steps: Advanced Features
- Custom Prompts: Tailor narratives for different audiences
- Multi-Language: Generate narratives in multiple languages
- Sentiment Analysis: Add sentiment scoring to insights
- Automated Reporting: Include narratives in automated reports
Master AI-Powered Chart Interpretation
The AI Agents for Evaluators Certificate teaches you to build complete M&E solutions including AI-powered chart narratives, automated reporting, and stakeholder management.
What you will learn:
- Build AI-powered chart interpretation
- Generate narrative insights automatically
- Create actionable recommendations
- Integrate narratives into reports
Course Features: 32 lectures · Lifetime access · Certificate included
Enroll Now – $249 Lifetime Access
Bundle with AI in M&E course and save 30%
This guide is part of the AI Agents for Evaluators series. Continue your learning journey with the full certificate course above.
