EduFlow

Section 5 · Answering Real Questions

Charts with pandas and matplotlib

16 min3 min readChoose wisely

Lesson at a glance

Choose the right chart for a question, draw it from a DataFrame with pandas .plot and matplotlib’s figure and axes, and label it so the chart states its finding on its own.

  1. 1Choose the chart from the question
  2. 2Prepare the data, then plot
  3. 3Draw it with pandas and matplotlib
  4. 4Check the chart before you share it

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

You have numbers that answer the head of growth’s question, and soon you will present them. A table of 24 monthly figures makes people squint; a line chart of the same figures shows the November spike before anyone reads an axis. Charts are not decoration added at the end. They are how most people will consume your analysis, and a good one is often the whole message.

This lesson’s chart code is for your own Jupyter notebook: the Run blocks in this course show printed text, not images. The data preparation runs here as usual.

Choose the chart from the question

Four panels. How does it change over time: a line chart. Which group is bigger: sorted horizontal bars. How are values spread: a histogram. Do two things move together: a scatter plot. change over time line compare groups sorted bars spread of values histogram relationship scatter
Four questions, four chart types

Most business charts are one of these four. Pie charts compare poorly beyond two or three slices; a sorted bar chart says the same thing more clearly. Charts with two different y-axes invite the reader to compare lines that are on unrelated scales, so prefer two charts stacked one above the other.

Prepare the data, then plot

A chart wants a small table: one row per point or bar, already sorted and labelled. Building it is ordinary pandas, and it runs here:

Python
import pandas as pd

items = pd.read_csv("order_items.csv")
orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])
orders = orders.merge(items.groupby("order_id", as_index=False)["revenue"].sum(), on="order_id", validate="one_to_one")
orders = orders[orders["status"] != "cancelled"]

monthly = orders.set_index("order_date").resample("ME")["revenue"].sum()
by_year = monthly.groupby([monthly.index.year, monthly.index.month]).sum().unstack(level=0)
by_year.index.name = "month"
print(by_year.round(0))

unstack(level=0) moves the year into the columns, giving one column per year and one row per month: exactly the shape for a chart with one line per year. Comparing years on the same January-to-December axis is how you see seasonality: if both lines rise in November, the November spike is a pattern, not an accident.

Draw it with pandas and matplotlib

pandas’ .plot methods call matplotlib for you. Create the figure and axes yourself, so you can finish the chart properly:

Python
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 4))
by_year.plot(ax=ax, marker="o")
ax.set_title("November is the peak in both years; 2025 runs about 2.7x 2024")
ax.set_xlabel("Month")
ax.set_ylabel("Revenue (USD)")
ax.yaxis.set_major_formatter("${x:,.0f}")
ax.set_xticks(range(1, 13))
ax.legend(title="Year")
fig.tight_layout()
fig.savefig("monthly_revenue.png", dpi=150, bbox_inches="tight")

plt.subplots() returns a figure (the whole image) and an axes (one plotting area with its x and y axes). by_year.plot(ax=ax) draws one line per column onto that axes, and every ax.set_... call refines it. A format string passed to set_major_formatter turns tick values into dollars with thousands separators. savefig writes the file you paste into a slide; bbox_inches="tight" trims empty margins.

For comparing categories, sort first and use horizontal bars so long labels stay readable:

Python
# sales: the line-level table you built in Lesson 4.2
revenue_by_country = sales.groupby("country")["revenue"].sum().sort_values()

fig, ax = plt.subplots(figsize=(7, 4))
bars = ax.barh(revenue_by_country.index, revenue_by_country.values)
ax.bar_label(bars, fmt="${:,.0f}", padding=3)
ax.set_title("UAE and UK lead revenue; Jordan trails")
ax.set_xlabel("Revenue 2024-2025 (USD)")
fig.tight_layout()

And for a distribution, a histogram of order values with the median marked:

Python
fig, ax = plt.subplots(figsize=(7, 4))
orders["revenue"].plot.hist(bins=40, ax=ax)
ax.axvline(orders["revenue"].median(), linestyle="--", label="median $147")
ax.set_xlabel("Order value (USD)")
ax.legend()

For relationships, a scatter plot puts one numeric column on each axis. With thousands of points they pile up into a blob, so make them small and partly transparent, and it is often clearer to plot a summary instead, such as the purchase rate per number of pages viewed:

Python
# assumes fig, ax = plt.subplots() and churn.csv / web_sessions.csv loaded as churn / sessions
churn.plot.scatter(x="tenure_days", y="total_spent", s=8, alpha=0.3, ax=ax)

rate = sessions.groupby("pages_viewed")["purchased"].mean()
rate.loc[:15].plot(ax=ax, marker="o")   # one point per page count, up to 15

The first line is the pattern for any two numeric columns, here each customer’s tenure against their total spend from churn.csv; the other two turn 6,000 sessions into fifteen readable points, each a conversion rate.

Check the chart before you share it

Charts hide mistakes well. Before a chart leaves your notebook, compare it with the table it came from: the number of bars or points, the first and last period (a partial month at the end looks like a collapse, as Lesson 4.4 warned), and one or two values you can read off and check against a printed number. Then look at it once as a stranger would: can someone who has never seen the data say what it shows in one sentence? If not, change the title, the sort order or the chart type, not the reader.

You will also meet seaborn, a library built on matplotlib with good statistical defaults. Everything here carries over: it draws onto the same ax, and the same rules about baselines and titles apply.

In this lesson’s exercise you pick the right charts for a set of questions. Next you put everything together and answer the Q4 question end to end.

Key takeaways

  • Pick the chart from the question - line for change over time, sorted bars for comparing categories, histogram for a distribution, scatter for a relationship.
  • fig, ax = plt.subplots() then df.plot(ax=ax) gives you pandas’ convenience with matplotlib’s full control over titles, axes and saving.
  • Prepare a small, tidy table first (one row per bar or point), then plot it; most charting problems are data-shaping problems.
  • Bars start at zero, axes carry units, and the title states the finding rather than naming the variables.
  • fig.savefig("chart.png", dpi=150, bbox_inches="tight") saves the chart for a report; charts are not shown in this course’s Run blocks.

Further reading

Check your understanding

3 questions

Question 1 of 3

You want to show that revenue per channel grew from 2024 to 2025. Which chart reads best?

Question 2 of 3

What is the advantage of fig, ax = plt.subplots() followed by monthly.plot(ax=ax), over calling monthly.plot() alone?

Question 3 of 3

A bar chart of revenue by country has a y-axis starting at 60,000. What is the problem?

Practice

Choose wisely

Pick the charts for the Q4 briefing

The head of growth asks: “Is Q4 2025 unusual, or is it the normal seasonal peak on a growing business?” You have monthly and quarterly revenue for 2024 and 2025. Select every chart that would help answer that question honestly.

Pick the charts for the Q4 briefing