Lesson 27 · Data analytics zero to hero
Storytelling with Data: Present Like a Pro | Data Analytics #27
Video twenty-seven of the 30-part series: the difference between a chart and a real finding, and building an effective, shareable report entirely in Python.…
- CourseData analytics zero to hero
- Lesson27 of 30
- Video12 min
- FormatJupyter notebook · 8 code cells
- Data1 dataset
What you'll learn
Datasets used in this lesson
Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.
- superstore_sales.csv715.5 KB
📓 Full notebook
Download .ipynbData Analytics Zero to Hero, Video 27: Storytelling with Data#
- Video twenty-seven of the 30-part series: the difference between a chart and a real finding, and building an effective, shareable report entirely in Python.
- Still the real Sample Superstore dataset, now telling an actual business story with it.
- Let's jump straight in.
Before You Start#
- Open a new Jupyter Notebook in VS Code and select your Python interpreter as the kernel.
- Place superstore_sales.csv in the same folder as this notebook.
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('superstore_sales.csv')
print(df.shape)
Part 1: A Chart Is Not a Finding#
sub_profit = df.groupby('Sub-Category')['Profit'].sum().sort_values()
loss_making = sub_profit[sub_profit < 0]
print(loss_making)
fig, ax = plt.subplots(figsize=(8, 4))
colors = ['firebrick' if v < 0 else 'steelblue' for v in sub_profit.values]
ax.barh(sub_profit.index, sub_profit.values, color=colors)
ax.axvline(0, color='black', linewidth=0.8)
ax.set_title('Real Profit by Sub-Category: Two Categories Are Losing Money')
ax.set_xlabel('Total Profit ($)')
plt.tight_layout()
plt.show()
Part 2: Annotating the Number That Matters#
worst = sub_profit.idxmin()
worst_value = sub_profit.min()
fig, ax = plt.subplots(figsize=(8, 4))
ax.barh(sub_profit.index, sub_profit.values, color=colors)
ax.axvline(0, color='black', linewidth=0.8)
ax.annotate(
f'{worst}: -${abs(worst_value):,.0f}',
xy=(worst_value, worst), xytext=(worst_value - 8000, worst),
arrowprops=dict(arrowstyle='->', color='black'), fontsize=10, fontweight='bold'
)
ax.set_title('Real Profit by Sub-Category')
plt.tight_layout()
plt.show()
Part 3: A Generated Narrative Summary#
total_profit = df['Profit'].sum()
loss_total = loss_making.sum()
loss_share = abs(loss_total) / total_profit
summary = f'''
Across {len(df):,} real orders, the business generated ${total_profit:,.0f} in total profit.
However, {len(loss_making)} sub-categories are net unprofitable, led by {worst} at -${abs(worst_value):,.0f}.
Combined, these losses equal {loss_share:.1%} of total profit.
Recommendation: review pricing and discounting on {worst} before the next planning cycle.
'''
print(summary)
Part 4: A Multi-Panel Report Page#
fig, axes = plt.subplots(1, 2, figsize=(11, 4))
axes[0].barh(sub_profit.index, sub_profit.values, color=colors)
axes[0].axvline(0, color='black', linewidth=0.8)
axes[0].set_title('Profit by Sub-Category')
category_profit = df.groupby('Category')['Profit'].sum().sort_values()
axes[1].bar(category_profit.index, category_profit.values, color='steelblue')
axes[1].set_title('Profit by Category')
fig.suptitle('Real Superstore Profitability Review', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
Part 5: Exporting a Real PDF Report#
from matplotlib.backends.backend_pdf import PdfPages
with PdfPages('superstore_profitability_report.pdf') as pdf:
fig1, ax1 = plt.subplots(figsize=(8.5, 4))
ax1.text(0, 0.9, 'Superstore Profitability Review', fontsize=18, fontweight='bold')
ax1.text(0, 0.6, summary.strip(), fontsize=11, va='top', wrap=True)
ax1.axis('off')
pdf.savefig(fig1)
plt.close(fig1)
fig2, axes2 = plt.subplots(1, 2, figsize=(11, 4))
axes2[0].barh(sub_profit.index, sub_profit.values, color=colors)
axes2[0].axvline(0, color='black', linewidth=0.8)
axes2[0].set_title('Profit by Sub-Category')
axes2[1].bar(category_profit.index, category_profit.values, color='steelblue')
axes2[1].set_title('Profit by Category')
pdf.savefig(fig2)
plt.close(fig2)
print('Real PDF report saved')
import os
print(os.path.getsize('superstore_profitability_report.pdf'), 'bytes')
Wrap-Up: What You Learned#
- The core idea: a chart shows data, a finding tells someone what to do; every real chart should support one stated conclusion.
- Using color meaningfully, like flagging real losses in red, instead of decoratively.
- Annotating a chart with annotate, to point directly at the number that matters most.
- Generating a narrative summary, context, finding, and a recommendation, from real computed values with an f-string.
- Building a multi-panel report page with subplots and suptitle.
- Exporting a real, genuine multi-page PDF report with PdfPages, no separate tool required.
- All of it built on the real Sample Superstore dataset. Video twenty-eight kicks off three real-world capstone-style projects, starting with a full retail analytics deep dive. Subscribe so it lands automatically see you there.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



