Mathew K Analytics

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.…

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.

📓 Full notebook

Download .ipynb

Data 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)
(9994, 9)

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)
Sub-Category
Tables      -17725.4811
Bookcases    -3472.5560
Supplies     -1189.0995
Name: Profit, dtype: float64
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()
No description has been provided for this image

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()
No description has been provided for this image

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)
Across 9,994 real orders, the business generated $286,397 in total profit.
However, 3 sub-categories are net unprofitable, led by Tables at -$17,725.
Combined, these losses equal 7.8% of total profit.
Recommendation: review pricing and discounting on Tables before the next planning cycle.

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()
No description has been provided for this image

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')
Real PDF report saved
import os
print(os.path.getsize('superstore_profitability_report.pdf'), 'bytes')
28644 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.