Data Analysis with Python (pandas, polars, DuckDB) Module 10 – Visualising data with matplotlib
Plotting with matplotlib and pandas
Draw line, bar, scatter and histogram charts with matplotlib's Figure and Axes and with pandas .plot, then label, format and save them as SVG or PNG.
What you will learn
- Build a Figure and named Axes with the object-oriented API
- Choose line, bar, scatter and histogram methods for DataFrame columns
- Label axes and distinguish tick formatting from bar-value formatting
- Choose SVG or PNG and calculate a figure output size
Before you start
What you’ll learn · Before you start
What you will learn
- Build a Figure and named Axes with the object-oriented API
- Choose line, bar, scatter and histogram methods for DataFrame columns
- Label axes and distinguish tick formatting from bar-value formatting
- Choose SVG or PNG and calculate a figure output size
Before you start
On this page
One shop needs a monthly revenue line and a category ranking in the same report. Start with a matplotlib Figure: The complete matplotlib image that owns one or more plotting areas and can be saved to a file., the whole image, and one or more matplotlib Axes: One matplotlib plotting area with its own data artists, title, scales, axis labels and ticks., its plotting areas. fig, ax = plt.subplots() creates both. Draw a line with ax.plot, bars with ax.bar or ax.barh, points with ax.scatter, and a histogram with ax.hist.
pandas draws from a DataFrame using matplotlib underneath. Pass ax=ax so it draws into your named Axes, then refine the result with titles, labels and formatters. fig.savefig("chart.svg") writes the Figure to a file. The examples use twelve monthly rows, six categories and 160 invented orders to show the objects each chart creates.
Named Axes make the destination of every plotting call explicit
What a matplotlib chart is made of
Text description of the diagram
The Figure is the whole image. plt.subplots() returns it together with one or more Axes, and fig.savefig writes it to a file.
Each Axes is one plotting area, and fig.axes lists them. An Axes holds:
- its title, set with ax.set_title();
- two Axis objects, ax.xaxis and ax.yaxis, each with its ticks, tick labels and label (ax.set_xlabel(), ax.set_ylabel());
- the artists that draw the data: ax.plot adds a Line2D, ax.bar and ax.hist add Rectangles, ax.scatter adds a PathCollection;
- its spines, grid and legend.
pandas draws into the same structure: df.plot(ax=ax) adds its artists to the Axes you pass and returns that Axes, so you can keep customising it with matplotlib.
matplotlib can be called in two styles. In the object-oriented style you create the Figure and the Axes and call
their methods. In the pyplot style, functions such as plt.plot and plt.title draw on a “current” Figure and Axes
that pyplot keeps for you. Both draw the same chart:
import matplotlib.pyplot as plt
import pandas as pd
monthly = pd.read_csv("monthly.csv")
# Object-oriented style: make the Figure and the Axes, then call methods on the Axes.
fig, ax = plt.subplots()
ax.plot(monthly["month"], monthly["revenue"])
ax.set_title("Revenue by month")
# pyplot style: pyplot keeps a "current" Figure and Axes and draws on them.
plt.figure()
plt.plot(monthly["month"], monthly["revenue"])
plt.title("Revenue by month")
current = plt.gca()
for name, a in [("object-oriented", ax), ("pyplot", current)]:
line = a.get_lines()[0]
print(f"{name:>15}: {type(a.figure).__name__} with {len(a.figure.axes)} Axes, "
f"{len(a.get_lines())} line of {len(line.get_xdata())} points, title {a.get_title()!r}")
same = list(ax.get_lines()[0].get_ydata()) == list(current.get_lines()[0].get_ydata())
print("same data in both:", same)
print("open figures:", plt.get_fignums()) Output
object-oriented: Figure with 1 Axes, 1 line of 12 points, title 'Revenue by month'
pyplot: Figure with 1 Axes, 1 line of 12 points, title 'Revenue by month'
same data in both: True
open figures: [1, 2]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 two_styles.py
The matplotlib documentation suggests the object-oriented style for complicated charts and for code you will reuse,
and pyplot for quick interactive looks. Plotting with matplotlib in a script or a report is the reusable kind, so
this track writes fig, ax = plt.subplots() and calls Axes methods. Note the last line: pyplot keeps every figure
it made, here two, until they are closed.
Follow the ownership chain used by the shop’s monthly chart.
The drawing shows the whole scene; its 3 steps are listed below.
Every step as text
- Create a Figure. subplots creates the image and returns its plotting area.
- Name the Axes. Each method names the plotting area it changes.
- Save the Figure. Saving the owning Figure writes the complete image rather than one individual artist.
Naming the objects keeps a second chart from silently becoming the destination.
Key idea
Checkpoint
Check: Figure and named Axes
Four plotting methods create four inspectable kinds of marks
The shop’s sales live in two small tables, monthly.csv (orders and revenue for 12 months) and categories.csv
(revenue per category for the year), plus 160 invented orders made from a fixed random seed. One figure holds a
line, a bar chart, a scatter plot and a histogram. Two are drawn with Axes methods and two with pandas, into the
Axes the figure already has:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
plt.style.use("house.mplstyle")
monthly = pd.read_csv("monthly.csv")
categories = pd.read_csv("categories.csv")
rng = np.random.default_rng(42) # 160 invented orders, the same on every run
items = rng.integers(1, 9, size=160)
orders = pd.DataFrame({"items": items, "value": (items * rng.normal(420, 90, size=160)).round()})
fig, axs = plt.subplots(2, 2, figsize=(8.4, 6.0), layout="constrained")
line, bars, points, spread = axs.flat
# Axes methods: you pass the values.
line.plot(monthly["month"], monthly["revenue"] / 1e6)
line.set(title="Line: revenue by month", ylabel="Million", ylim=(0, 2.2))
line.set_xticks(monthly["month"][::3])
# pandas .plot: you pass column names and the Axes to draw on.
categories.sort_values("revenue").plot.barh(x="category", y="revenue", ax=bars, legend=False)
bars.set(title="Bar: revenue by category", xlabel="", ylabel="")
bars.xaxis.set_major_formatter(lambda v, pos: f"{v / 1e6:.0f}M")
orders.plot.scatter(x="items", y="value", ax=points, s=14, alpha=0.7, color="#1d5fcb")
points.set(title="Scatter: items and order value", xlabel="Items in the order", ylabel="Order value")
spread.hist(orders["value"], bins=20)
spread.set(title="Histogram: order values", xlabel="Order value")
fig.savefig("four-kinds.svg", metadata={"Date": None})
print("line:", len(line.get_lines()), "Line2D with", len(line.get_lines()[0].get_xdata()), "points")
print("bars:", len(bars.patches), "rectangles, longest at the top:", bars.get_yticklabels()[-1].get_text())
print("scatter:", len(points.collections[0].get_offsets()), "points in one collection")
print("histogram:", len(spread.patches), "bins, tallest holds", int(max(p.get_height() for p in spread.patches)), "orders") Output
line: 1 Line2D with 12 points bars: 6 rectangles, longest at the top: Books scatter: 160 points in one collection histogram: 20 bins, tallest holds 18 orders
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 four_kinds.py
The figure four_kinds.py draws
Text description of the diagram
A figure with four Axes in two rows, each drawn with a different method:
- Top left, a line chart of revenue by month, in millions, on an axis from 0 to 2.2: it starts at 1.18 in January, dips to 1.09 in February, climbs to a peak of 1.93 in November and ends at 1.65 in December.
- Top right, horizontal bars of revenue by category for the year, sorted with the largest at the top: Books, Stationery, Electronics, Toys, Kitchen and Garden, from about 4.4 million down to 1.2 million.
- Bottom left, a scatter plot of 160 orders: the number of items, from 1 to 8, against the order value, whose median rises from 428 for one item to 3,249 for eight, with a wide spread at every count.
- Bottom right, a histogram of the same 160 order values in 20 bins, spread from 235 to 4,798, with 85 % of the orders below 3,000 and a median of about 1,860.
Each chart type adds its own kind of object to its Axes, and the run counts them: one Line2D with 12 points, 6
bar rectangles, one collection of 160 points and 20 histogram rectangles. Knowing these objects is what lets a
check, or a test, inspect a chart without looking at its pixels. Two smaller habits show in the code as well:
ax.set(title=..., ylabel=...) sets several properties in one call, and layout="constrained" makes matplotlib
leave room for every title and label, so the four charts do not overlap.
| Criterion | Axes methods (ax.plot, ax.bar …) | pandas .plot (df.plot.bar …) |
|---|---|---|
| You pass | Arrays or Series of values | Column names of the DataFrame |
| The x values | Whatever you pass | The index, unless you name x= |
| Labels and legend | Only what you add | Column names as axis labels, and a legend |
| Returns | The artists it drew (lines, bars) | The Axes it drew on |
| Chart names | plot, bar, barh, scatter, hist and more | line, bar, barh, hist, box, kde, area, pie, scatter, hexbin |
| When to choose | Full control, or data that is not in a DataFrame | A quick chart straight from a table, refined afterwards |
pandas fills in some decisions for you, which is convenient until it is not:
import matplotlib.pyplot as plt
import pandas as pd
monthly = pd.read_csv("monthly.csv")
fig, ax = plt.subplots()
returned = monthly.plot(x="month", y=["orders"], ax=ax)
print("returns the Axes it drew on:", returned is ax)
print("x label from the column name:", repr(ax.get_xlabel()))
print("legend added:", ax.get_legend() is not None, [t.get_text() for t in ax.get_legend().get_texts()])
monthly.set_index("month")["revenue"].plot(ax=ax, secondary_y=False, legend=False)
print("lines after a second call:", [line.get_label() for line in ax.get_lines()])
fig2, ax2 = plt.subplots()
monthly.plot.bar(x="month", y="orders", ax=ax2, legend=False, rot=0)
print("bar chart x tick labels:", [t.get_text() for t in ax2.get_xticklabels()][:4], "...") Output
returns the Axes it drew on: True x label from the column name: 'month' legend added: True ['orders'] lines after a second call: ['orders', 'revenue'] bar chart x tick labels: ['Jan', 'Feb', 'Mar', 'Apr'] ...
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pandas_defaults.py
It returned the very Axes it was given, labelled the x-axis with the column name and added a legend for one line.
A second call on the same Axes added a second line, which is how pandas charts are layered. Pass legend=False,
or set the labels yourself, when the defaults do not fit the report.
Follow the tables into the four kinds of artists in four_kinds.py.
The drawing shows the whole scene; its 3 steps are listed below.
Every step as text
- Read the tables. Choose the monthly columns for the line and category values for the ranking.
- Draw named marks. The example uses a line, six bar rectangles, 160 paired points and twenty histogram rectangles.
- Inspect the artists. Read the corresponding artist collections instead of inferring their data from pixels.
The object counts describe this input and these calls; they are not universal counts for each chart kind.
Key idea
Checkpoint
Check: Methods and chart artists
Labels and a chosen file format make a chart usable in a report
A report’s twelve monthly points need a title, units and a suitable output file. The reader should be able to read the chart without its code. Three Axes methods do most of that work:
ax.set_title, ax.set_xlabel and ax.set_ylabel. For the numbers along an axis, matplotlib uses formatters.
StrMethodFormatter formats every tick with one format string, such as "{x:,.0f}" for thousands separators.
FuncFormatter turns each tick value into text with a function you write. ax.bar_label writes each bar’s value
next to the bar.
fig.savefig writes the figure to a file and picks the format from the extension: .svg for vector graphics that
stay sharp at any size, .png for a picture made of pixels. For a PNG, dpi sets how many pixels the file gets for
each inch of figure size, and bbox_inches="tight" trims the empty margin around the chart. With these few calls,
plotting with matplotlib covers the everyday charts of a report.
Follow the decisions that come after drawing the shop’s values.
The drawing shows the whole scene; its 3 steps are listed below.
Every step as text
- Name the measure. A title and axis labels explain what the twelve monthly points mean.
- Format the values. Formatters change tick text; bar_label changes text beside individual bars.
- Choose the file. Save the Figure in the format required by the report destination.
A file extension chooses an output format; it does not repair missing units or ambiguous labels.
Key idea
Checkpoint
Check: Units and output files
Tick labels and value labels that read like a report Pro: opens with a Pro, Premium or Ultimate pass
A reader should never have to count zeros or decode 1e6. This part compares tick formatters on one axis, fixes value labels that come out in scientific notation, and builds the Indian digit grouping that Python’s format strings cannot produce.
In this part:
- One chosen unit must govern ticks and bar-value labels
- Digit grouping and the rupee prefix are separate operations
3 diagrams · 4 runnable examples (Python) · about 6 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Saving figures at a fixed size, with identical files on every run Pro: opens with a Pro, Premium or Ultimate pass
A chart that goes into a report, a slide deck and a repository should come out the same size and, where possible, the same bytes every time. This part measures what figure size, dpi and the file format change, makes repeated SVG saves identical, and keeps the number of open figures at zero when a loop draws hundreds of charts.
In this part:
- Fixed inches and dpi determine pixels until cropping changes the bounds
- Dates and salted identifiers can change an unchanged SVG
- Saving a pyplot Figure does not remove its registry reference
- Your turn: the monthly report in one Figure
3 diagrams · 5 runnable examples (Python) · about 8 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Practice
Test your Figure and Axes choices on a shop report: select the destination, put the largest horizontal bar at the top and calculate an export size.
Practice that adapts to you
Practise this lesson
6 questions about 8 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
-
Fresher · Warm-up
What is the difference between a Figure and an Axes in matplotlib, and why do many analysts start every chart with fig, ax = plt.subplots()?
A Figure is the whole image: the canvas that is saved to a file, with its size and background. An Axes is one plotting area inside it, with its own title, x- and y-axis, ticks, legend and the objects that draw the data, so one Figure can hold several Axes side by side. plt.subplots() creates the Figure and its Axes in one call and returns both. Every later call then names the object it changes, ax.set_title or fig.savefig, instead of relying on whatever pyplot considers current, which keeps code with several charts unambiguous and easy to reuse in functions. pandas fits in too, because df.plot(ax=ax) draws into the Axes you pass and returns it. Do not confuse Axes with Axis: an Axes has two Axis objects, ax.xaxis and ax.yaxis.
-
Fresher to mid
The value labels on a revenue bar chart read 4.41e+06, 3.72e+06 and so on, and the y-axis runs from 0.5 to 2.0 with a small 1e6 above it. Explain both effects and fix the chart so that nobody has to count zeros.
-
Mid to senior
A nightly job saves 40 charts as SVG files into a Git repository. Every night all 40 files show up as changed, even on nights when the data did not change. What is going on, and how do you make unchanged charts produce unchanged files?
-
Mid to senior
A script draws one chart per store for 3,000 stores. It gets slower and slower and is killed for using too much memory after a few hundred charts. What is happening, and how do you restructure it?
Key takeaways
fig, ax = plt.subplots()gives you the Figure and its Axes; draw withax.plot,ax.bar,ax.barh,ax.scatterandax.hist.- pandas
df.plot(..., ax=ax)draws into your Axes and returns it, so pandas charts can be refined with matplotlib. - Each chart type adds its own objects to the Axes, which is what checks and tests can inspect.
- Label every axis, format the ticks for the reader, and save with
fig.savefig, choosing SVG or PNG by where the chart will be shown.
Terms in this lesson
- matplotlib Axes
- One matplotlib plotting area with its own data artists, title, scales, axis labels and ticks.
- matplotlib Figure
- The complete matplotlib image that owns one or more plotting areas and can be saved to a file.
Corrections
- Added object and export scenes, checkpoints and adaptive practice; corrected the Indian-grouping description while retaining all chart programs and the Figure worksheet.
References
- Quick start guide (Matplotlib) (Matplotlib development team)
- matplotlib.axes (the Axes API) (Matplotlib development team)
- matplotlib.ticker (tick locating and formatting) (Matplotlib development team)
- matplotlib.axes.Axes.bar_label (Matplotlib development team)
- matplotlib.pyplot.savefig (Matplotlib development team)
- Embedding in a web application server (Matplotlib) (Matplotlib development team)
- Chart visualization (pandas user guide) (pandas development team (NumFOCUS))
- Format specification mini-language (Python 3.14) (Python Software Foundation)
- SOURCE_DATE_EPOCH specification (Reproducible Builds)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress