RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Fredoline Eruo
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /Data Analysis with Local AI
  6. /Ch. 15
Data Analysis with Local AI

15. Data Storytelling

Chapter 15 of 18 · 20 min
KEY INSIGHT

Structure precedes visualization. Build the narrative arc before selecting charts—each visualization should serve a specific point in the story.

Data storytelling transforms raw analysis into compelling narratives that drive decisions. Effective stories combine data with context, emotion, and clear visual communication.

Building a Narrative Structure

Every data story needs a structure:

def generate_narrative(analysis_results, audience='executives'):
    """Generate narrative from analysis results."""
    
    prompt = f"""Create a data story narrative from these analysis results.
    Audience: {audience}
    
    Key findings:
    {analysis_results['key_metrics']}
    
    Trends identified:
    {analysis_results['trends']}
    
    Anomalies detected:
    {analysis_results['anomalies']}
    
    Structure: Hook → Context → Discovery → Implication → Call to action
    Tone: Professional but engaging"""

    response = ollama.chat(model='llama3.2', messages=[
        {'role': 'user', 'content': prompt}
    ])
    return response['message']['content']

Choosing the Right Visualization

Different stories need different charts:

# Comparison: Bar chart
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Bar for category comparison
df.groupby('region')['revenue'].sum().sort_values().plot(kind='barh', ax=axes[0])
axes[0].set_title('Revenue by Region')
axes[0].set_xlabel('Revenue ($)')

# Composition: Stacked bar
pivot = df.groupby(['region', 'product_line'])['revenue'].sum().unstack()
pivot.plot(kind='bar', stacked=True, ax=axes[1])
axes[1].set_title('Revenue Composition by Region')
axes[1].legend(title='Product Line', bbox_to_anchor=(1.05, 1))

Annotating Key Points

fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(df.index, df['revenue'], linewidth=2)

# Annotate significant events
annotations = [
    ('2024-02-15', 'Product Launch', (30, 50)),
    ('2024-06-01', 'Market Shift', (20, -30)),
    ('2024-09-20', 'Recovery', (-50, 20))
]

for date, label, offset in annotations:
    ax.annotate(label, xy=(pd.Timestamp(date), df.loc[date, 'revenue']),
                xytext=offset, textcoords='offset points',
                arrowprops=dict(arrowstyle='->', color='gray'),
                fontsize=10, color='darkred')

ax.set_title('Revenue Trajectory with Key Events')

Dashboard Composition

import matplotlib.gridspec as gridspec

fig = plt.figure(figsize=(16, 10))
gs = gridspec.GridSpec(3, 3, figure=fig, hspace=0.3, wspace=0.3)

# KPI cards at top
ax1 = fig.add_subplot(gs[0, 0])
ax1.text(0.5, 0.7, f"${df['revenue'].sum()/1e6:.1f}M", fontsize=24, ha='center', fontweight='bold')
ax1.text(0.5, 0.3, 'Total Revenue', fontsize=12, ha='center', color='gray')
ax1.axis('off')

ax2 = fig.add_subplot(gs[0, 1])
ax2.text(0.5, 0.7, f"{len(df[df['anomaly']])}", fontsize=24, ha='center', fontweight='bold')
ax2.text(0.5, 0.3, 'Anomalies', fontsize=12, ha='center', color='gray')
ax2.axis('off')

# Main chart
ax3 = fig.add_subplot(gs[1, :])
ax3.plot(df.index, df['revenue'])
ax3.fill_between(df.index, df['revenue'], alpha=0.3)
ax3.set_title('Revenue Over Time')

Exporting Story for Sharing

def export_story_to_html(data, narrative, visualizations):
    html = f"""
    <html>
    <head><title>Data Analysis Report</title></head>
    <body>
        <h1>Executive Summary</h1>
        <div class='narrative'>{narrative}</div>
        <h2>Key Charts</h2>
        {''.join([f"<img src='{v}'/>" for v in visualizations])}
    </body>
    </html>"""
    
    with open('report.html', 'w') as f:
        f.write(html)
EXERCISE

Create a 4-slide data story from your analysis: (1) hook with KPI card, (2) trend line with annotations, (3) anomaly callout, (4) call to action summary. Export as HTML.

← Chapter 14
Natural Language Insights
Chapter 16 →
Report Generation