AI EngineeringZero to ProductionHome·About·Contact
Data & App Building · Chapter B2

Data Visualization with Matplotlib and Seaborn

A number in a table hides; a picture reveals. This chapter is practical plotting: Matplotlib's figure/axes model (the foundation everything sits on), Seaborn's statistical charts straight from a DataFrame, and — crucially — how to choose the right chart and not lie with it.

⏱️ ~55 min📈 Hands-on🎯 Beginner→Intermediate

Learning objectives

  • Explain Matplotlib's Figure/Axes model and the two API styles.
  • Make the core chart types and know which question each answers.
  • Use Seaborn to plot statistical charts directly from a DataFrame.
  • Choose the right chart for the data — and avoid misleading ones.
  • Save/return charts for reports, apps, and LLM pipelines.
Builds on B1Visualization consumes the clean DataFrames you produced in B1 — you plot the result of a groupby, not raw mess. Matplotlib underlies almost all Python plotting (including Seaborn and pandas' own .plot()), and it's pre-installed in the code-execution sandbox (C2/tool-use), so agents use it to generate charts too.

The Matplotlib mental model: Figure & Axes essential

Almost all Matplotlib confusion comes from missing one idea. A Figure is the whole canvas; an Axes is a single plot on it (with its own x/y axis). You draw on Axes. A figure can hold several Axes (subplots).

Figure (the canvas) Axes 1 Axes 2 you call methods on an Axes: ax.plot(...), ax.set_title(...) Figure = canvas, Axes = one plot. You almost always create both with fig, ax = plt.subplots() and then call methods on axax.plot(), ax.bar(), ax.set_title(). Grasp this and Matplotlib stops feeling random; miss it and you'll fight the API forever.
🗺️ How to read this diagram

This is the one idea that makes Matplotlib click. A Figure is the whole canvas (the sheet of paper); an Axes is a single plot drawn on it, with its own x-axis and y-axis. "Axes" is confusing at first — it does not mean the x/y lines; it means one whole chart.

  • The big outer box is the Figure — the container. One figure can hold several plots.
  • Each inner box is an Axes — one individual chart. Here the figure holds two: Axes 1 (drawn with a line) and Axes 2 (drawn with bars).
  • The little axis lines and the line/bars inside each box are what you actually draw. You put them there by calling methods on that Axes — the caption shows ax.plot(...) and ax.set_title(...).
  • The normal way to get both at once is fig, ax = plt.subplots(): fig is the canvas, ax is the one plot on it.

In short: Figure = the page, Axes = one chart on the page. You spend almost all your time calling ax.something(...) to draw on a single Axes.

Two API styles — pick the explicit one essential

pyplot (state machine)Object-oriented (explicit)
Looks likeplt.plot(x, y); plt.title(...)fig, ax = plt.subplots(); ax.plot(x, y)
Draws on"current" axes (implicit)the ax you hold (explicit)
Fine forquick one-off plots in a notebookeverything real — subplots, apps, functions
Prefer fig, ax = plt.subplots()The plt.-everything style relies on a hidden "current axes" and breaks down the moment you have multiple plots or return a figure from a function. The explicit object-oriented style — hold fig and ax, call methods on them — is unambiguous and scales. Learn it first; it's the same effort and saves confusion later.

Lab B2.1 · Core charts in Matplotlib essential

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Lab B2.1

Requires: pip install matplotlib

charts.pyimport matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(months, revenue, marker="o")     # line: trend over time
ax.set_title("Monthly revenue")
ax.set_xlabel("month"); ax.set_ylabel("$")
fig.savefig("revenue.png", dpi=150, bbox_inches="tight")

# subplots: two Axes on one Figure
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.bar(regions, totals)          # bar: compare categories
ax2.hist(latencies, bins=30)      # histogram: see a distribution
▶ How this works

This is the core Matplotlib pattern you'll reuse everywhere: make a Figure and Axes, draw on the Axes, label it, then save it. The second half shows how to put two plots side by side on one figure.

  1. fig, ax = plt.subplots(figsize=(7, 4)) creates the canvas (fig) and one plot (ax) on it. figsize is the size in inches (width, height).
  2. ax.plot(months, revenue, marker="o") draws a line chart — x-values months against y-values revenue, with a dot at each point. A line is the right choice for a trend over time.
  3. ax.set_title(...), ax.set_xlabel(...), and ax.set_ylabel(...) add the title and axis labels. Always label — an unlabeled chart can mean anything.
  4. fig.savefig("revenue.png", dpi=150, bbox_inches="tight") writes the picture to a file. dpi sets sharpness; bbox_inches="tight" trims extra whitespace around the edges.
  5. The second block asks for a grid of plots: plt.subplots(1, 2, ...) means 1 row, 2 columns, so you get back two Axes — ax1 gets a bar chart (ax1.bar, to compare categories) and ax2 gets a histogram (ax2.hist(..., bins=30), to see a distribution).

What the output means: Two PNG-able figures: a labelled revenue line chart saved as revenue.png, and a second figure showing a bar chart and a histogram side by side.

Try this: Change marker="o" to marker="s" (squares) or drop it entirely, and change bins=30 to bins=5 on the histogram — fewer bins means chunkier bars and a coarser view of the distribution.

pandas plots tooA DataFrame has .plot() built in (it uses Matplotlib): by_region.plot.bar(ax=ax). That's the fastest path from a B1 groupby to a chart — pass it the ax you made so you keep control of the figure.

Lab B2.2 · Seaborn: statistical charts from a DataFrame intermediate

Seaborn sits on Matplotlib and is built for DataFrames: you pass the frame plus column names, and it handles the statistics and styling. It's the fast path for the charts analysts actually make.

Lab B2.2

Requires: pip install seaborn

seaborn_charts.pyimport seaborn as sns

sns.barplot(data=df, x="region", y="total")     # aggregates + error bars for you
sns.histplot(data=df, x="latency", bins=30)      # distribution
sns.scatterplot(data=df, x="price", y="qty", hue="region")  # relationship, colored by group
sns.boxplot(data=df, x="region", y="total")     # compare distributions across groups
sns.heatmap(df.corr(numeric_only=True), annot=True)  # correlations between numeric columns
▶ How this works

Seaborn is built for DataFrames: instead of handing it arrays, you hand it the whole frame and the names of the columns to use. It then does the grouping, the math, and the styling for you. Every line here is a different chart type from the same df.

  1. The shared shape is sns.SOMECHART(data=df, x="col", y="col") — pass the DataFrame, then name the columns for the x and y axes.
  2. sns.barplot(..., x="region", y="total") groups by region and shows the average total per region, with little error bars for the spread — all computed automatically.
  3. sns.histplot(..., x="latency", bins=30) shows the distribution of one column; sns.scatterplot(..., x="price", y="qty") plots each row as a dot to reveal a relationship between two numbers.
  4. hue="region" is the superpower: it splits the chart by category using color, turning one plot into a comparison across groups.
  5. sns.boxplot(...) compares the shape of a distribution across groups, and sns.heatmap(df.corr(numeric_only=True), annot=True) draws a colored grid of how strongly the numeric columns correlate (annot=True prints the number in each cell).

What the output means: Five statistical charts drawn straight from df — bar, histogram, scatter, box, and correlation heatmap — with grouping and aggregation handled for you.

Try this: Add hue="region" to the histplot line and watch it split into one colored distribution per region. Then remove annot=True from the heatmap to see the grid without the printed numbers.

Seaborn = "pass the DataFrame, name the columns"Where Matplotlib wants arrays, Seaborn wants data=df, x="col", y="col" — and it does the grouping, aggregation, and error bars for you. The hue="group" parameter is a superpower: it splits any chart by a category with color, turning one plot into a comparison. Reach for Seaborn for statistical/exploratory charts; drop to Matplotlib when you need pixel control.

Choosing the right chart intermediate

The hardest part isn't the code — it's picking a chart that answers your question honestly. Match the chart to the question.

Your questionChartWhy
How does X change over time?LineConnects ordered points; shows trend
How do categories compare?BarLength is easy to compare across groups
What's the distribution of X?Histogram / box / violinShows spread, skew, outliers
Is X related to Y?ScatterReveals correlation/clusters
How do many variables correlate?HeatmapMatrix of pairwise relationships
Parts of a whole (few, sum to 100%)Bar (usually) not pieHumans compare lengths better than angles
Averages hide the story — show the distributionA bar of group means can look identical for wildly different data. If the shape matters — outliers, skew, bimodality — reach for a histogram, box, or violin plot instead of a single bar. "The average is 50" is very different from "half are 0 and half are 100." The chart you choose decides which truth the reader sees.

Lab B2.3 · Not lying with charts intermediate

A chart is an argument. Small choices can mislead — sometimes accidentally. Professional plotting means making the honest version.

✗ truncated axis y starts at 90 ✓ zero-based axis y starts at 0 Same data, opposite impression. A y-axis that starts at 90 makes a 2% difference look enormous; a zero-based axis shows it's tiny. Truncating axes, cherry-picking ranges, or using dual axes to force a correlation are the classic ways charts mislead. Default to honest scales and label everything.
🗺️ How to read this diagram

This picture shows the single most common way a chart lies: where the y-axis starts. Both sides plot the exact same two numbers — only the axis differs — yet they tell opposite stories.

  • The left chart (✗) has a y-axis that starts at 90, not 0. That chops off the bottom of the bars, so a tiny 2% difference in the numbers looks like a huge gap.
  • The right chart (✓) has a y-axis that starts at 0. Now you see the full height of each bar, and the real difference — small — is obvious.
  • The lesson: for bar charts, the bar's length is how the reader judges the value, so starting anywhere but zero exaggerates. Truncated axes, cherry-picked ranges, and forced dual axes are the classic tricks.

In short: Same data, opposite impression. For bars, start the y-axis at zero and label everything — that's the honest default.

Honest-charting ruleWhy
Start bar-chart y-axes at zeroBar length encodes value; a truncated axis exaggerates
Label axes, units, and the titleAn unlabeled chart can mean anything
Don't over-plot / rainbow everythingClutter hides the signal; use color with purpose
Show the data's real shapeDistribution over a lone average when spread matters
One clear message per chartA chart trying to say five things says nothing

Lab B2.4 · Charts for reports, apps & LLM pipelines advanced

Where visualization plugs into the rest of the course: charts get saved for reports, returned to a web app (B4), or generated by an agent in the code sandbox.

Lab B2.4

Illustrative fragment — defines demo values / files are needed before this runs standalone.

deliver.py# save to a file (reports, email, artifacts)
fig.savefig("report.png", dpi=150, bbox_inches="tight")

# return the figure object to a web framework (Streamlit/Gradio, B4)
def make_chart(df) -> "plt.Figure":
    fig, ax = plt.subplots()
    sns.barplot(data=df, x="region", y="total", ax=ax)
    return fig          # st.pyplot(fig) / Gradio Plot component renders it

# in an agent's code-execution tool, the generated PNG comes back as a file (C2)
▶ How this works

The point of a chart is to deliver it somewhere: a saved image for a report, or a live figure handed to a web app or an agent. This fragment shows both — and why returning a figure (not calling plt.show()) is the shape the rest of the course needs.

  1. fig.savefig("report.png", dpi=150, bbox_inches="tight") writes the figure to a file — the way you attach a chart to a report or an email.
  2. def make_chart(df) -> "plt.Figure": is a function that builds a chart and hands it back. Inside it makes its own fig, ax, draws a Seaborn bar plot onto that ax, and then return fig — it returns the figure object instead of showing it.
  3. Passing ax=ax to sns.barplot tells Seaborn to draw on the exact Axes you created, so the function stays in control of its figure.
  4. Returning the figure lets a caller decide what to do with it: a web framework shows it (st.pyplot(fig) in Streamlit, a Plot component in Gradio, B4), or an agent's code sandbox turns it into a PNG file (C2).

What the output means: A reusable make_chart(df) that returns a Figure you can either save to disk or hand to an app/agent to display — no plt.show() needed.

Try this: Never put plt.show() inside a function like this — it tries to pop up a window and blocks apps and agents. Build the figure, return fig, and let the caller display or save it.

Agents make charts tooThe code-execution tool (tool-use in C2, and the sandbox in Claude Skills) ships with Matplotlib/Seaborn pre-installed. So when the Data Analyst Agent (P5) answers "plot revenue by region", it writes exactly this Seaborn code, runs it in the sandbox, and returns the PNG. The visualization skill you're learning is the same one the agent uses — which is why "return a Figure" matters as much as "show a plot."

Common pitfalls advanced

PitfallFix
Fighting the plt. state machineUse fig, ax = plt.subplots() and call methods on ax
Truncated bar-chart axesStart at zero; don't exaggerate differences
Unlabeled axes/title/unitsLabel everything — a chart must stand alone
Bar of means hiding the distributionUse histogram/box/violin when shape matters
Pie chart with many slicesPrefer a bar chart; lengths beat angles
Passing raw messy data to a plotClean/aggregate in Pandas first (B1)
Reaching for Matplotlib for a stat chartSeaborn from the DataFrame is faster & cleaner

Exercises advanced

Exercise B2.1 — Chart a groupby

Context: A group-by result from the data lesson is only an answer once someone can see it. Three views of the same summary — a comparison, a trend, and a statistical chart — cover most of what a report needs.

Your task: Take a group-by result and make three charts of it with the explicit fig, ax style — a bar, a line, and one Seaborn statistical chart — labelling every axis and title, and save one as a PNG.

Requirements:

  • Reuse a real group-by result as the data
  • Make three charts with the explicit Figure/Axes API: a bar, a line, and a Seaborn statistical chart
  • Give every chart a clear title and label both axes
  • Save at least one chart as a PNG file

💡 Hint: Match each chart to its job — bar to compare groups, line for a trend, Seaborn for the statistical view — and drive them all through ax.

Exercise B2.2 — Fix a misleading chart

Context: The judgment that separates a chart from a lie is seeing how the same data can read two ways. Building the dishonest version on purpose, then the honest one, makes the difference impossible to un-see.

Your task: Deliberately make a misleading chart (a truncated y-axis, or a bar of means over bimodal data), then make the honest version and write one sentence on how the reader's takeaway changed.

Requirements:

  • Build the misleading chart first (truncated axis, or a mean-bar over split data)
  • Build the honest counterpart from the same data
  • Only the framing changes — the underlying numbers are identical
  • Write one sentence on what the reader now correctly sees
  • Note which specific choice (zero baseline, histogram) fixed the distortion

💡 Hint: For a truncated axis the fix is a zero baseline; for a mean-bar over bimodal data the fix is a histogram that reveals the two clusters.

Show what to look for

The truncated-axis version makes a trivial difference look dramatic; the zero-based version shows it's small. The mean-bar hides that the data is split; the histogram reveals two clusters. In both, the data is identical — only the honest chart lets the reader see what's true.

Exercise B2.3 — Return a Figure

Context: Apps and agents don't want a chart splashed to the screen — they want a Figure object they can save or hand to a display function. Returning the figure instead of calling show() is the shape the rest of the module depends on.

Your task: Write a function make_chart(df) -> Figure that builds and returns a matplotlib Figure without calling plt.show() inside it, then confirm you can both save it and pass the returned figure to a display function.

Requirements:

  • The function returns a Figure object
  • It does not call plt.show() internally
  • The returned figure can be saved with savefig
  • The same returned figure can be handed to a display function (e.g. st.pyplot)

💡 Hint: Build the figure with fig, ax = plt.subplots() and simply return fig — let the caller decide whether to save or display it.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · A labeled line chart the explicit wayBeginner

Context: The plt.plot global-state style works until you need two charts in one figure, then it fights you. The explicit Figure/Axes API scales cleanly and never depends on hidden state — it's the habit worth building first.

Your task: Using the explicit fig, ax = plt.subplots() API, plot monthly values as a line chart with a title and labels on both axes.

Requirements:

  • Create the figure and axes explicitly with plt.subplots(), not plt.plot global state
  • Plot the monthly series on the axes (a marker helps read the points)
  • Set a title and label both the x and y axes
  • Render or save the figure (e.g. fig.savefig(...) or plt.show())

💡 Hint: Everything hangs off the ax object — ax.plot, ax.set_title, ax.set_xlabel — so the chart never relies on which plot was 'current'.

Show solution

Needs matplotlib + a display/file. Prefer the explicit fig, ax API — it scales to subplots and never depends on hidden global state:

import matplotlib.pyplot as plt

months = ["Jan", "Feb", "Mar", "Apr"]
values = [10, 14, 9, 18]

fig, ax = plt.subplots()
ax.plot(months, values, marker="o")
ax.set_title("Monthly active users")
ax.set_xlabel("Month")
ax.set_ylabel("Users (thousands)")
fig.savefig("mau.png", dpi=120, bbox_inches="tight")   # or plt.show()

Always label axes and title — an unlabeled chart isn’t evidence, it’s decoration.

Exercise 2 · Pick the right chart for the dataIntermediate

Context: Choosing the wrong chart type quietly misleads even with correct data. The chart should be dictated by the question you're answering, not by what looks nice.

Your task: For three situations — revenue share across five product lines, a distribution of request latencies, and a year of daily signups — name the right chart for each and say why, then show sample code for one.

Requirements:

  • Revenue share across categories → a bar chart, because lengths are easy to compare (not a pie)
  • Distribution of latencies → a histogram (or box/violin) to show spread and outliers a mean hides
  • Trend over a year → a line chart with time on the x-axis
  • Give a one-line justification tying each choice to comparison / distribution / trend
  • Provide runnable sample code for at least one (e.g. the bar chart)

💡 Hint: Map the question to the chart: comparison wants bars, spread wants a histogram, change-over-time wants a line.

Show solution
  • (a) Share across categories: a bar chart (not a pie) — bars let the eye compare lengths precisely; pies are hard to read past 3–4 slices.
  • (b) Distribution: a histogram (or box/violin) — you want to see spread, skew, and outliers, which a single mean hides.
  • (c) Trend over time: a line chart — time on x, value on y, continuity shows the trend.

Needs matplotlib + display for the bar example:

import matplotlib.pyplot as plt
lines = ["A", "B", "C", "D", "E"]; rev = [30, 22, 18, 15, 5]
fig, ax = plt.subplots()
ax.bar(lines, rev)
ax.set_ylabel("Revenue share (%)"); ax.set_title("Revenue by product line")
plt.show()

Match the chart to the question: comparison → bars, distribution → histogram, trend → line.

Exercise 3 · A statistical chart from a DataFrame with SeabornAdvanced

Context: Seaborn maps DataFrame columns straight onto statistical charts, so you can see a distribution per category in a couple of lines — and a box plot exposes the outlier that a bar-of-means would bury.

Your task: Use Seaborn to draw a box plot of a numeric metric across a category directly from a tidy DataFrame — for example latency by endpoint.

Requirements:

  • Feed a tidy DataFrame to Seaborn, mapping the category to x and the metric to y
  • Use a box plot (sns.boxplot) so spread and outliers are visible
  • Give the chart a descriptive title
  • Render the figure (needs seaborn + matplotlib + a display)
  • Confirm the box plot surfaces an outlier that a mean would hide

💡 Hint: Pass data=df, x=..., y=... straight to sns.boxplot — Seaborn handles the aggregation and drawing from the column names.

Show solution

Needs seaborn + matplotlib + a display. Seaborn maps DataFrame columns straight to a statistical chart:

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

df = pd.DataFrame({
    "endpoint": ["/a", "/a", "/a", "/b", "/b", "/b"],
    "latency":  [120, 130, 400, 90, 95, 88],
})

ax = sns.boxplot(data=df, x="endpoint", y="latency")
ax.set_title("Latency distribution by endpoint")
plt.show()

The box plot exposes the /a outlier (400ms) that a bar-of-means would hide — the point of a distributional view.

Exercise 4 · Don’t lie with chartsExpert

Context: Two of the most common ways a chart misleads are a truncated y-axis that exaggerates tiny differences and dual axes that imply a correlation that isn't there. Knowing the honest fix is the judgment that separates a chart from a lie.

Your task: Explain the truncated-y-axis and dual-axis pitfalls, then show the honest fix in code — a bar chart whose y-axis starts at zero so a small gap looks small.

Requirements:

  • Explain why a truncated y-axis exaggerates small differences on a bar chart
  • Explain why dual y-axes can imply a false correlation between unrelated series
  • Show a bar chart with the y-axis baseline at 0 (ax.set_ylim(0, ...))
  • Contrast it with the misleading truncated version in a comment
  • State the rule: bar length encodes value, so the axis must start at 0

💡 Hint: For bars the length is the value, so setting a zero baseline is the fix; note what the misleading set_ylim would have been.

Show solution

Truncated y-axis exaggerates small differences; start bar-chart axes at 0. Dual y-axes can make two unrelated series look correlated — avoid unless the reader clearly understands both scales.

Needs matplotlib + a display. Honest bar chart with a zero baseline:

import matplotlib.pyplot as plt

labels = ["Q1", "Q2", "Q3"]; vals = [98, 99, 100]

fig, ax = plt.subplots()
ax.bar(labels, vals)
ax.set_ylim(0, 110)      # honest: baseline at 0, so 98 vs 100 looks like ~2%
ax.set_title("Quarterly score (true scale)")
plt.show()
# Misleading version would be: ax.set_ylim(97, 100)  -> a 2% gap looks huge

The rule: for bars, the length encodes the value, so the axis must start at 0 or you distort the comparison.

Exercise 5 · One figure, saved for a reportProfessional

Context: A figure destined for a report needs more than a plot — it needs multiple panels in one image, a shared title, and print-resolution output that doesn't clip its own labels. That polish is the line between a draft and something you paste in.

Your task: Produce a report-ready figure with two subplots — a trend and a distribution — sharing one overall title, saved to a file at print resolution.

Requirements:

  • Create a single figure with two subplots via plt.subplots(1, 2, ...)
  • Configure each axes independently (one line/trend, one histogram)
  • Add a shared overall title with fig.suptitle(...)
  • Save to a file at print resolution (a higher dpi) with bbox_inches="tight"
  • Use tight_layout() so labels don't clip

💡 Hint: subplots(1, 2) hands back an array of axes you configure separately, then tight_layout plus bbox_inches="tight" keep the saved image clean.

Show solution

Needs matplotlib + file output. subplots returns an array of axes you configure independently, then save the whole figure:

import matplotlib.pyplot as plt

months = ["Jan", "Feb", "Mar", "Apr"]; signups = [10, 14, 9, 18]
latencies = [120, 130, 400, 90, 95, 88, 110, 100]

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot(months, signups, marker="o"); ax1.set_title("Signups"); ax1.set_xlabel("Month")
ax2.hist(latencies, bins=6); ax2.set_title("Latency distribution"); ax2.set_xlabel("ms")
fig.suptitle("Weekly report", fontweight="bold")
fig.tight_layout()
fig.savefig("report.png", dpi=150, bbox_inches="tight")

tight_layout() + bbox_inches="tight" stop labels from clipping — the difference between a draft and something you paste into a doc.

Exercise 6 · A reusable chart helper for a pipelineIndustry scenario

Context: An automated report pipeline needs a chart function it can call repeatedly in a headless job without a display — and without leaking a figure into memory on every run. That means the Agg backend and disciplined cleanup.

Your task: Wrap chart creation in a function that takes a DataFrame plus column names, writes a PNG, and depends on no global pyplot state so an automated report pipeline can call it in a loop.

Requirements:

  • Select a headless backend (matplotlib.use("Agg")) so no display is needed
  • The function takes the DataFrame, the category and value column names, and an output path
  • Create a fresh fig, ax per call — no reliance on global pyplot state
  • Label the axes and write the PNG to the given path
  • Close the figure (plt.close(fig)) so a long-running job doesn't leak memory, and return the path

💡 Hint: The Agg backend plus a per-call figure and plt.close is exactly what makes matplotlib safe inside a repeated, headless report job.

Show solution

Needs matplotlib. Create a fresh Figure per call (no global state), and close it so a long-running pipeline doesn’t leak figures:

import matplotlib
matplotlib.use("Agg")            # headless backend: no display needed in a pipeline
import matplotlib.pyplot as plt

def save_bar(df, cat_col, val_col, out_path, title=""):
    # Write a bar chart of val_col by cat_col to out_path (PNG). Returns out_path.
    fig, ax = plt.subplots()
    ax.bar(df[cat_col].astype(str), df[val_col])
    ax.set_xlabel(cat_col); ax.set_ylabel(val_col); ax.set_title(title)
    fig.tight_layout()
    fig.savefig(out_path, dpi=120, bbox_inches="tight")
    plt.close(fig)               # release memory in a batch job
    return out_path

# usage (in a pipeline):
# import pandas as pd
# df = pd.DataFrame({"region": ["US","EU"], "rev": [250,120]})
# save_bar(df, "region", "rev", "rev.png", "Revenue by region")

Agg backend + plt.close are what make matplotlib safe inside a headless, repeated report job.

✓ Checkpoint — you can move on when you can…

  • Explain Figure vs Axes and use the explicit fig, ax style.
  • Make line, bar, histogram, and scatter charts.
  • Use Seaborn to plot statistical charts straight from a DataFrame.
  • Pick the right chart for a question and avoid misleading ones.
  • Save a chart and return a Figure for an app or agent.
🏗️ Toward the capstone & projectsThe Data Analyst Agent (P5) ends its answers with a chart — generated by exactly the Seaborn code here, run in the code sandbox, returned as an image. And an O4 monitoring dashboard for the capstone is just these charts over the four signals (cost, latency, quality, safety). Clean it in B1, chart it in B2, then B3/B4 serve it. Next: serve it over an API →

Knowledge check check yourself

✓ Knowledge check

In Matplotlib, what is the difference between a Figure and an Axes, and which do you draw on?

Show answer
A Figure is the whole canvas (which can hold several plots); an Axes is a single plot on it with its own x/y axis. You draw on an Axes — calling methods like ax.plot() and ax.set_title(), usually obtained via fig, ax = plt.subplots().
✓ Knowledge check

Why should bar-chart y-axes start at zero, and what does a truncated axis do to the reader?

Show answer
Because a bar's length encodes its value, so starting the axis anywhere but zero exaggerates differences — a y-axis starting at 90 can make a tiny 2% difference look enormous, misleading the reader.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in