Data Visualization in Python Using Matplotlib: Complete Tutorial
Imagine you have a spreadsheet with ten thousand rows of sales, marks or sensor readings. You could scroll through it for hours and still miss the one pattern that matters. Draw a chart and you see it in seconds. Data visualization is the habit of turning numbers into pictures, and in Python the library that does most of the work is Matplotlib.
Matplotlib is the oldest and most widely used plotting library in Python. It draws 2D and 3D charts, it works with NumPy arrays, and many other tools, such as Seaborn and the plotting methods in pandas, are built on top of it. If you learn how Matplotlib thinks, with its figures, axes and plot functions, every other Python charting tool becomes easier.
This tutorial is a complete working reference. We list the main plot types and functions, explain the Figure and Axes classes, and then go through each plot one by one: line, multiplot, subplot2grid, grid, limits, ticks, bar, histogram, pie, scatter, contour, quiver, box, violin, and the 3D line, contour, wireframe and surface plots. Every program has been run. We also add a guide to picking the right chart, the two ways of writing Matplotlib code, and the mistakes beginners make most often.
Introduction to Data Visualization in Python Using Matplotlib
Here is what Matplotlib gives you.
- 2D and 3D plots. Line, bar, pie, scatter, contour and many more, plus 3D surfaces through the mplot3d toolkit.
- NumPy friendly. It draws NumPy arrays directly.
- Pyplot. A module of simple commands that works like MATLAB, where each command changes the current figure.
- Many output formats. Save to PNG, JPG, PDF, SVG and more.
- Works in many places. Jupyter Notebook, plain Python scripts, and desktop windows built with Tkinter, PyQt or wxPython.
Install Matplotlib
Python 2 reached its end of life in 2020, so use Python 3. Current Matplotlib needs Python 3.11 or newer, along with these packages, which pip installs for you: contourpy, cycler, fonttools, kiwisolver, numpy, packaging, pillow, pyparsing and python-dateutil.
terminal
pip install matplotlib numpy
This tutorial was tested on Python 3.13 with Matplotlib 3.11.2 and NumPy 2.5.3.
Two Ways to Write Matplotlib Code
This is the idea that confuses most beginners, so it is worth a minute. Matplotlib has two styles, and the examples below use both.
Pyplot style and object oriented style
| Style |
How it looks |
When to use it |
| Pyplot style |
plt.plot(x, y) then plt.title(“Sales”) |
Quick, single plots and learning |
| Object oriented style |
fig, ax = plt.subplots() then ax.plot(x, y) and ax.set_title(“Sales”) |
Several plots, full control, real projects |
In the object oriented style, a Figure is the whole window or page, and an Axes is one plot inside it. You will see the methods on an Axes start with set_, such as set_title and set_xlabel.
Types of Plots in Matplotlib
These are the main plot functions.
Main plot functions
| Function |
What it draws |
| plot |
Lines or markers, the default choice for trends |
| bar |
Vertical bar chart |
| barh |
Horizontal bar chart |
| boxplot |
Box and whisker plot |
| hist |
Histogram |
| hist2d |
2D histogram, counting points in a grid of cells |
| pie |
Pie chart |
| polar |
Plot on polar coordinates |
| scatter |
Scatter plot of two variables |
| stackplot |
Stacked area plot |
| stem |
Stem plot, lines from a baseline to each value |
| step |
Step plot |
| quiver |
Arrows showing direction and size |
Image Functions in Matplotlib
These three functions read, save and show images. Their names are all lowercase.
Image functions
| Function |
What it does |
| imread |
Reads an image file into an array |
| imsave |
Saves an array as an image file |
| imshow |
Shows an array as an image |
python
import matplotlib.pyplot as plt
import numpy as np
pixels = np.array([[0.0, 0.5, 1.0], [1.0, 0.5, 0.0], [0.0, 1.0, 0.0]])
plt.imsave("tiny.png", pixels, cmap="gray")
img = plt.imread("tiny.png")
print(img.shape)
plt.imshow(img)
plt.show()
output
(3, 3, 4)
The shape means 3 rows, 3 columns and 4 values per pixel, which are red, green, blue and transparency.
Axes Functions in Matplotlib
Axes functions
| Function |
What it does |
| axes |
Adds an axes to the figure |
| text |
Adds text at a position on the axes |
| title |
Sets the title of the axes |
| xlabel, ylabel |
Sets the label of the x axis and the y axis |
| xlim, ylim |
Gets or sets the range of the x axis and the y axis |
| xticks, yticks |
Gets or sets the tick positions and labels of the x axis and the y axis |
Do not mix up the last two rows. The functions xlim and ylim set the range. The functions xticks and yticks control where the marks sit and what they say.
Figure Functions in Matplotlib
Figure functions
| Function |
What it does |
| figtext |
Adds text to the figure |
| figure |
Creates a new figure |
| show |
Shows the figure |
| savefig |
Saves the figure to a file |
| close |
Closes a figure window |
Figure Class in Matplotlib
The Figure is the top level container. Everything you draw sits inside one. You create it with plt.figure.
You can pass these optional parameters:
Figure parameters
| Parameter |
What it sets |
| figsize |
Width and height in inches, as a pair such as (6, 3) |
| dpi |
Dots per inch, which is the sharpness of the image |
| facecolor |
The background colour of the figure |
| edgecolor |
The colour of the border around the figure |
| linewidth |
The width of that border |
python
import matplotlib.pyplot as plt
import numpy as np
g1 = plt.figure(figsize=(6, 3), dpi=100, facecolor="lightgrey", edgecolor="navy", linewidth=2)
print(g1.get_size_inches(), g1.dpi)
output
[6. 3.] 100
Axes Class in Matplotlib
An Axes is the actual plot area, with its own x axis, y axis, title and data. A figure can hold many. You add one by calling add_axes with four numbers, which are the left, bottom, width and height as fractions of the figure. So [0.1, 0.1, 0.8, 0.8] means a plot that starts 10 percent in from the left and bottom, and takes 80 percent of the width and height.
python
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
The legend method labels the lines or shapes, and its loc argument says where to put it. Here are the location codes.
Legend location codes
| Location |
Code |
Location |
Code |
| best |
0 |
center left |
6 |
| upper right |
1 |
center right |
7 |
| upper left |
2 |
lower center |
8 |
| lower left |
3 |
upper center |
9 |
| lower right |
4 |
center |
10 |
| right |
5 |
|
|
axes.plot() in Matplotlib
The plot method draws lines or markers. You can add a format string as the third argument, which packs the colour, marker and line style into a short code. For example, rs- means red, square markers, solid line. Here is a working example.
python
import matplotlib.pyplot as plt
import numpy as np
y = [1, 5, 10, 17, 25, 36, 49, 64]
x1 = [1, 16, 30, 42, 55, 68, 77, 88]
x2 = [1, 6, 12, 18, 28, 40, 52, 65]
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
l1 = ax.plot(x1, y, "rs-") # red, square markers, solid line
l2 = ax.plot(x2, y, "mo--") # magenta, circle markers, dashed line
ax.legend(labels=("Hadoop", "Data science"), loc="lower right")
ax.set_title("Learners enrolled over time")
ax.set_xlabel("Learners enrolled")
ax.set_ylabel("Weeks since launch")
plt.show()

You will see two rising lines and a legend in the lower right corner. These are the colour codes:
Colour codes
| Code |
Colour |
Code |
Colour |
| b |
Blue |
m |
Magenta |
| g |
Green |
y |
Yellow |
| r |
Red |
k |
Black |
| c |
Cyan |
w |
White |
Marker Codes in Matplotlib
Markers are the shapes drawn on each data point. Note that the D marker is a diamond.
Marker codes
| Code |
Marker |
| “.” |
Point |
| “o” |
Circle |
| “x” |
X mark |
| “D” |
Diamond |
| “H” |
Hexagon |
| “s” |
Square |
| “+” |
Plus |
Line Styles in Matplotlib
The dashed style is two hyphens typed together.
Line styles
| Code |
Line style |
| “-“ |
Solid |
| “–“ |
Dashed |
| “-.” |
Dash dot |
| “:” |
Dotted |
Multiplot in Matplotlib
To draw several plots in one figure, use plt.subplots with the number of rows and columns. It returns the figure and an array of axes, and you pick each axes by its row and column.
python
plt.subplots(nrows, ncols)
python
import matplotlib.pyplot as plt
import numpy as np
fig, a = plt.subplots(2, 2)
x = np.arange(2, 8)
a[0][0].plot(x, x ** 2)
a[0][0].set_title("square")
a[0][1].plot(x, np.sqrt(x))
a[0][1].set_title("square root")
a[1][0].plot(x, np.exp(x))
a[1][0].set_title("exp")
a[1][1].plot(x, np.log10(x))
a[1][1].set_title("log")
fig.tight_layout()
plt.show()

You will see four small plots, with square and exp rising fast, and square root and log rising slowly. The call tight_layout stops the titles from overlapping.
The subplot2grid Function
With subplots, every cell is the same size. The function subplot2grid lets you place axes at chosen positions in a grid, and let one axes span several rows or columns. The call is plt.subplot2grid(shape, location, rowspan, colspan).
python
import matplotlib.pyplot as plt
import numpy as np
a1 = plt.subplot2grid((4, 4), (0, 0), colspan=2)
a2 = plt.subplot2grid((4, 4), (0, 2), rowspan=3)
a3 = plt.subplot2grid((4, 4), (1, 0), rowspan=2, colspan=2)
x = np.arange(1, 10)
a2.plot(x, x ** 3)
a2.set_title("cube")
a1.plot(x, np.exp(x))
a1.set_title("exp")
a3.plot(x, np.log(x))
a3.set_title("log")
plt.tight_layout()
plt.show()

The grid is 4 by 4. The first plot takes the top two cells, the second takes three rows on the right, and the third takes a 2 by 2 block on the left.
Grid in Matplotlib
The grid method turns gridlines on or off, and lets you change their colour, line style and width. The short names are ls for linestyle and lw for linewidth.
python
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
x = np.arange(2, 22)
axes[0].plot(x, x ** 4, "g", lw=2)
axes[0].grid(True)
axes[0].set_title("default grid")
axes[1].plot(x, np.exp(x), "r")
axes[1].grid(color="b", ls="-.", lw=0.25)
axes[1].set_title("custom grid")
axes[2].plot(x, x)
axes[2].set_title("no grid")
fig.tight_layout()
plt.show()

Setting Limits in Matplotlib
By default Matplotlib chooses the range of each axis from your data. The methods set_xlim and set_ylim let you fix it, which is useful when a few huge values squash everything else. Note that these are two separate methods.
python
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
a1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
x = np.arange(1, 90)
a1.plot(x, np.exp(x), "r")
a1.set_title("exp")
a1.set_ylim(0, 80000)
a1.set_xlim(0, 10)
plt.show()

Without the limits, the curve would reach a number with 39 digits and the early part would look flat. With the limits, you see the rise clearly.
Setting Ticks and Tick Labels
Ticks are the small marks along an axis. Tick labels are the text next to them. Use set_xticks and set_yticks to choose the positions, and set_xticklabels and set_yticklabels to choose the text.
python
import matplotlib.pyplot as plt
import numpy as np
import math
x = np.arange(0, math.pi * 4, 0.04)
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax.plot(x, np.sin(x))
ax.set_xlabel("angle")
ax.set_title("sine")
ax.set_xticks([0, 3, 6, 9])
ax.set_xticklabels(["a", "b", "c", "d"])
ax.set_yticks([-1, 0, 1])
plt.show()

The x axis now shows the letters a to d at positions 0, 3, 6 and 9, and the y axis shows only minus 1, 0 and 1.
Bar Plot in Matplotlib
A bar plot compares values across categories. Use bar for vertical bars and barh for horizontal ones. These are its main parameters.
Bar plot parameters
| Parameter |
What it sets |
| x |
The positions of the bars on the x axis |
| height |
The height of each bar |
| width |
The width of each bar, 0.8 by default |
| bottom |
Where each bar starts on the y axis, used for stacking |
| align |
Whether bars are centred on the position or start at it |
The first example stacks the girls on top of the boys using bottom. The stacked totals reach about 267, so the y axis here goes to 300. If you stopped it at 80, the bars would run off the chart.
python
import matplotlib.pyplot as plt
import numpy as np
N = 5
boysMeans = (120, 135, 130, 135, 127)
girlsMeans = (125, 132, 134, 120, 125)
ind = np.arange(N) # the x locations for the groups
width = 0.35
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax.bar(ind, boysMeans, width, color="r")
ax.bar(ind, girlsMeans, width, bottom=boysMeans, color="b")
ax.set_ylabel("Scores")
ax.set_title("Scores by group and gender (stacked)")
ax.set_xticks(ind, ("G1", "G2", "G3", "G4", "G5"))
ax.set_yticks(np.arange(0, 301, 50))
ax.legend(labels=["boys", "girls"])
plt.show()

Stacked bars hide the comparison, because only the bottom bar starts at zero. To compare the two means, put the bars side by side. Shift the boys bars left by half the width, and the girls bars right by half the width.
python
import matplotlib.pyplot as plt
import numpy as np
ind = np.arange(5)
width = 0.35
fig, ax = plt.subplots()
ax.bar(ind - width / 2, (120, 135, 130, 135, 127), width, color="r", label="boys")
ax.bar(ind + width / 2, (125, 132, 134, 120, 125), width, color="b", label="girls")
ax.set_ylabel("Scores")
ax.set_title("Scores by group and gender (side by side)")
ax.set_xticks(ind, ("G1", "G2", "G3", "G4", "G5"))
ax.legend()
plt.show()
Now the two bars in each group start at zero, and you can see at a glance that the girls beat the boys only in G1 and G3. The call to set_xticks with two arguments needs Matplotlib 3.5 or newer.
Histogram in Matplotlib
A histogram groups numbers into ranges, called bins, and counts how many values fall in each. It shows the shape of a distribution, for example how marks are spread across a class. It is an estimate of the distribution, because the picture changes with the bin choice.
Histogram parameters
| Parameter |
What it sets |
| x |
The data, as an array or a list |
| bins |
The number of bins, or a list of bin edges |
| range |
The lowest and highest values to include |
| density |
If True, shows the share of values and not the count |
python
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(1, 1)
a = np.array([22, 87, 5, 43, 56, 73, 55, 54, 11, 20, 51, 5, 79, 31, 27])
ax.hist(a, bins=[0, 25, 50, 75, 100])
ax.set_title("histogram of result")
ax.set_xticks([0, 25, 50, 75, 100])
ax.set_xlabel("marks")
ax.set_ylabel("no. of students")
plt.show()
The four bars have heights 5, 3, 5 and 2. That means 5 students scored below 25, 3 scored from 25 to 50, 5 from 50 to 75, and 2 from 75 up.
Pie Chart in Matplotlib
A pie chart shows each value as a share of the whole. It works best with five or fewer slices. These are its main parameters.
Pie chart parameters
| Parameter |
What it sets |
| x |
The values, one per slice |
| labels |
The text for each slice |
| colors |
The colour of each slice |
| autopct |
A format string that prints the percentage on each slice |
python
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
langs = ["Statistics", "Python", "Machine Learning", "SQL", "Big Data"]
students = [53, 27, 25, 19, 12]
ax.pie(students, labels=langs, autopct="%1.2f%%")
plt.show()

Statistics gets 38.97 percent, Python 19.85, Machine Learning 18.38, SQL 13.97 and Big Data 8.82. The pie function already draws a circle, so you do not need an extra axis equal line.
Scatter Plot in Matplotlib
A scatter plot draws one dot for each pair of values. It shows whether two variables move together, and where the odd points sit. A third variable can set the colour or the size of the dots. Keep in mind that a pattern shows a link, not a cause.
python
import matplotlib.pyplot as plt
import numpy as np
girls_marks = [19, 30, 10, 69, 80, 60, 14, 30, 80, 34]
boys_marks = [90, 29, 49, 48, 100, 48, 38, 45, 20, 30]
grades_range = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax.scatter(grades_range, girls_marks, color="r", label="girls")
ax.scatter(grades_range, boys_marks, color="b", label="boys")
ax.set_xlabel("Grades Range")
ax.set_ylabel("Grades Scored")
ax.set_title("scatter plot")
ax.legend()
plt.show()

The label and legend lines tell the reader which colour is which. Without them, red and blue would be a guess.
Contour Plot in Matplotlib
A contour plot shows a surface on a flat page. It is sometimes called a level plot. You give it a grid of X and Y values, and a height Z for each point. X goes on the x axis and Y on the y axis, and Z is shown by lines or colours. The function contour draws lines, and contourf fills the bands with colour.
python
import matplotlib.pyplot as plt
import numpy as np
xlist = np.linspace(-8.0, 10.0, 100)
ylist = np.linspace(-8.0, 10.0, 100)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X ** 4 + Y ** 4)
fig, ax = plt.subplots(1, 1)
cp = ax.contourf(X, Y, Z)
fig.colorbar(cp) # add a colorbar to the plot
ax.set_title("Filled Contours Plot")
ax.set_xlabel("x (cm)")
ax.set_ylabel("y (cm)")
plt.show()

Quiver Plot in Matplotlib
A quiver plot draws arrows. Each arrow sits at a point and has a direction and a length. It is used for wind, water flow, forces and slopes. These are its parameters.
Quiver plot parameters
| Parameter |
What it sets |
| x, y |
The position of each arrow |
| u, v |
The horizontal and vertical size of each arrow |
| c |
An optional colour for each arrow |
In the code below, np.gradient gives the slope of the surface. It takes the spacing along y first, then x. The y grid steps by 0.25 and the x grid by 0.2, so the call is np.gradient(z, .25, .2).
python
import matplotlib.pyplot as plt
import numpy as np
x, y = np.meshgrid(np.arange(-2, 2, .2), np.arange(-2, 2, .25))
z = x * np.exp(-x ** 2 - y ** 2)
v, u = np.gradient(z, .25, .2) # spacing along y first, then x
fig, ax = plt.subplots()
q = ax.quiver(x, y, u, v)
plt.show()

The arrows point uphill, toward the peak on the right and away from the dip on the left. They are longest where the slope is steepest.
Box Plot in Matplotlib
A box plot summarises a set of numbers with five values: the lowest value in range, the first quartile, the median, the third quartile and the highest value in range. The box runs from the first to the third quartile, and the line inside it is the median. By default the whiskers reach 1.5 times the interquartile range, and any point beyond that is drawn as a separate circle and called an outlier.
python
import matplotlib.pyplot as plt
import numpy as np
value1 = [82, 76, 24, 40, 67, 62, 71, 79, 81, 22, 98, 89, 78, 67, 72, 82, 87, 66, 56, 52]
value2 = [12, 25, 11, 35, 36, 32, 96, 95, 3, 90, 95, 32, 27, 55, 100, 12, 1, 451, 37, 21]
value3 = [23, 89, 12, 78, 72, 89, 25, 69, 68, 86, 19, 49, 15, 16, 16, 75, 65, 31, 25, 52]
value4 = [99, 33, 75, 66, 83, 61, 82, 98, 10, 87, 29, 72, 26, 23, 72, 88, 78, 99, 75, 30]
box_plot_data = [value1, value2, value3, value4]
plt.boxplot(box_plot_data)
plt.show()

You will see four boxes. The second data set has a circle far above the others at 451. It is an outlier, and it also squashes the other boxes into the bottom of the chart. The first data set shows two outliers near 22 and 24, drawn so close that they look like one.
Violin Plot in Matplotlib
A violin plot has the same summary idea as a box plot, and adds the shape of the density on each side. The wide parts of the shape show where most values sit. It shows more than a box plot, but it is also harder to read for a new audience, so the choice depends on who reads it. Remember that a box plot shows the median, and not the mean.
python
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(10)
collectn_1 = np.random.normal(300, 30, 300)
collectn_2 = np.random.normal(60, 10, 300)
collectn_3 = np.random.normal(60, 40, 300)
collectn_4 = np.random.normal(20, 25, 300)
# combine these different collections into a list
data_to_plot = [collectn_1, collectn_2, collectn_3, collectn_4]
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
vp = ax.violinplot(data_to_plot, showmedians=True)
plt.show()

The first violin sits high, around 300. The second is short, because its values vary the least. The third is the tallest, because its values vary the most. The showmedians argument adds a line at the median of each one.
Three Dimensional Plotting in Matplotlib
Although Matplotlib began as a 2D library, it can draw 3D plots through the mplot3d toolkit. To create a 3D axes, set projection to the text "3d". In recent versions the import of mplot3d is no longer needed, but it does no harm. The first 3D example draws a line that spirals upward.
python
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = plt.axes(projection="3d")
z = np.linspace(0, 2, 200)
x = z * np.sin(40 * z)
y = z * np.cos(40 * z)
ax.plot3D(x, y, z, "red")
ax.set_title("3D line plot")
plt.show()

3D Contour Plot
The contour3D method draws contour lines on a 3D surface, given a grid of X and Y values and the height Z. The number 50 is how many levels to draw, and cmap sets the colours.
python
import matplotlib.pyplot as plt
import numpy as np
def f(x, y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
x = np.linspace(-4, 4, 40)
y = np.linspace(-4, 4, 40)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig = plt.figure()
ax = plt.axes(projection="3d")
ax.contour3D(X, Y, Z, 50, cmap="binary")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
ax.set_title("3D contour")
plt.show()

3D Wireframe Plot
The plot_wireframe method places a mesh of lines over the surface, so you see the shape without a solid fill. It uses the same X, Y and Z grids as the contour above. They are repeated here so the code runs on its own, and only the last calls change.
python
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-4, 4, 40)
y = np.linspace(-4, 4, 40)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X ** 2 + Y ** 2))
fig = plt.figure()
ax = plt.axes(projection="3d")
ax.plot_wireframe(X, Y, Z, color="green")
ax.set_title("wireframe")
plt.show()

3D Surface Plot in Matplotlib
The plot_surface method draws a filled 3D surface. It takes the X, Y and Z grids, and the surface is coloured by height when you pass a colour map. In this example Z is the dependent value, calculated from X and Y.
python
import matplotlib.pyplot as plt
import numpy as np
x = np.outer(np.linspace(-4, 4, 40), np.ones(40))
y = x.copy().T # transpose
z = np.cos(x ** 2 + y ** 2)
fig = plt.figure()
ax = plt.axes(projection="3d")
ax.plot_surface(x, y, z, cmap="viridis", edgecolor="none")
ax.set_title("Surface plot")
plt.show()

The np.outer call builds a grid where every row is the same, and the transpose makes a matching grid in the other direction. Together they work like meshgrid.
Which Plot Should You Use?
Pick the plot by the question you want the chart to answer.
Choosing a plot
| If you want to show |
Use |
Function |
| A trend over time |
Line plot |
plot |
| A comparison across categories |
Bar plot |
bar, barh |
| How one set of numbers is spread |
Histogram, box plot or violin plot |
hist, boxplot, violinplot |
| Parts of a whole, five or fewer |
Pie chart |
pie |
| The link between two numbers |
Scatter plot |
scatter |
| A surface on a flat page |
Contour plot |
contour, contourf |
| Direction and size at many points |
Quiver plot |
quiver |
| A surface in three dimensions |
Wireframe or surface plot |
plot_wireframe, plot_surface |
Common Mistakes With Matplotlib
- Curly quotes in code. Text copied from a web page or a Word file can turn straight quotes into curly ones, and Python rejects them.
- A stray character before import. One minus sign at the start of a line stops the whole program.
- Misspelt method names. The correct names are
set_xticklabels and not set_xlabels, and xlabel and not xlable.
- An axis that cuts off the data. Check the axis range after you set ticks, as in the stacked bar example.
- Overlapping titles. Call
tight_layout when you use more than one plot.
- No labels or legend. If a chart has two colours and no legend, the reader has to guess.
- Reading too much into a scatter plot. A pattern shows a link, not a cause.
Complete Script to Run Yourself
Here is the full script used to test every plot on this page. It saves each plot as an image in a folder called viz_out, so create that folder first. The line matplotlib.use("Agg") lets it run without opening windows. Remove it if you want to see the plots on screen.
python
import math
import matplotlib
matplotlib.use("Agg") # lets the script run without a window; remove this line in Jupyter
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits import mplot3d # not needed in recent versions, kept for older ones
print(matplotlib.__version__, np.__version__)
# Image functions: imsave, imread, imshow
pixels = np.array([[0.0, 0.5, 1.0], [1.0, 0.5, 0.0], [0.0, 1.0, 0.0]])
plt.imsave("viz_out/tiny.png", pixels, cmap="gray")
img = plt.imread("viz_out/tiny.png")
print(img.shape)
fig, ax = plt.subplots()
ax.imshow(img)
fig.savefig("viz_out/00_imshow.png")
plt.close(fig)
# Figure class
g1 = plt.figure(figsize=(6, 3), dpi=100, facecolor="lightgrey", edgecolor="navy", linewidth=2)
print(g1.get_size_inches(), g1.dpi)
plt.close(g1)
# axes.plot with format strings
y = [1, 5, 10, 17, 25, 36, 49, 64]
x1 = [1, 16, 30, 42, 55, 68, 77, 88]
x2 = [1, 6, 12, 18, 28, 40, 52, 65]
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
l1 = ax.plot(x1, y, "rs-") # red, square markers, solid line
l2 = ax.plot(x2, y, "mo--") # magenta, circle markers, dashed line
ax.legend(labels=("Hadoop", "Data science"), loc="lower right")
ax.set_title("Learners enrolled over time")
ax.set_xlabel("Learners enrolled")
ax.set_ylabel("Weeks since launch")
fig.savefig("viz_out/01_plot.png")
plt.close(fig)
# Multiplot: subplots(2, 2)
fig, a = plt.subplots(2, 2)
x = np.arange(2, 8)
a[0][0].plot(x, x ** 2)
a[0][0].set_title("square")
a[0][1].plot(x, np.sqrt(x))
a[0][1].set_title("square root")
a[1][0].plot(x, np.exp(x))
a[1][0].set_title("exp")
a[1][1].plot(x, np.log10(x))
a[1][1].set_title("log")
fig.tight_layout()
fig.savefig("viz_out/02_subplots.png")
plt.close(fig)
# subplot2grid
a1 = plt.subplot2grid((4, 4), (0, 0), colspan=2)
a2 = plt.subplot2grid((4, 4), (0, 2), rowspan=3)
a3 = plt.subplot2grid((4, 4), (1, 0), rowspan=2, colspan=2)
x = np.arange(1, 10)
a2.plot(x, x ** 3)
a2.set_title("cube")
a1.plot(x, np.exp(x))
a1.set_title("exp")
a3.plot(x, np.log(x))
a3.set_title("log")
plt.tight_layout()
plt.savefig("viz_out/03_grid2.png")
plt.close()
# grid()
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
x = np.arange(2, 22)
axes[0].plot(x, x ** 4, "g", lw=2)
axes[0].grid(True)
axes[0].set_title("default grid")
axes[1].plot(x, np.exp(x), "r")
axes[1].grid(color="b", ls="-.", lw=0.25)
axes[1].set_title("custom grid")
axes[2].plot(x, x)
axes[2].set_title("no grid")
fig.tight_layout()
fig.savefig("viz_out/04_grid.png")
plt.close(fig)
# Limits
fig = plt.figure()
a1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
x = np.arange(1, 90)
a1.plot(x, np.exp(x), "r")
a1.set_title("exp")
a1.set_ylim(0, 80000)
a1.set_xlim(0, 10)
fig.savefig("viz_out/05_limits.png")
plt.close(fig)
# Ticks and tick labels
x = np.arange(0, math.pi * 4, 0.04)
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax.plot(x, np.sin(x))
ax.set_xlabel("angle")
ax.set_title("sine")
ax.set_xticks([0, 3, 6, 9])
ax.set_xticklabels(["a", "b", "c", "d"])
ax.set_yticks([-1, 0, 1])
fig.savefig("viz_out/06_ticks.png")
plt.close(fig)
# Bar plot, stacked and side by side
N = 5
boysMeans = (120, 135, 130, 135, 127)
girlsMeans = (125, 132, 134, 120, 125)
ind = np.arange(N)
width = 0.35
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax.bar(ind, boysMeans, width, color="r")
ax.bar(ind, girlsMeans, width, bottom=boysMeans, color="b")
ax.set_ylabel("Scores")
ax.set_title("Scores by group and gender (stacked)")
ax.set_xticks(ind, ("G1", "G2", "G3", "G4", "G5"))
ax.set_yticks(np.arange(0, 301, 50))
ax.legend(labels=["boys", "girls"])
fig.savefig("viz_out/07_bar_stacked.png")
plt.close(fig)
fig, ax = plt.subplots()
ax.bar(ind - width / 2, boysMeans, width, color="r", label="boys")
ax.bar(ind + width / 2, girlsMeans, width, color="b", label="girls")
ax.set_ylabel("Scores")
ax.set_title("Scores by group and gender (side by side)")
ax.set_xticks(ind, ("G1", "G2", "G3", "G4", "G5"))
ax.legend()
fig.savefig("viz_out/07_bar_side.png")
plt.close(fig)
# Histogram
fig, ax = plt.subplots(1, 1)
a = np.array([22, 87, 5, 43, 56, 73, 55, 54, 11, 20, 51, 5, 79, 31, 27])
counts, edges, patches = ax.hist(a, bins=[0, 25, 50, 75, 100])
print(counts, edges)
ax.set_title("histogram of result")
ax.set_xticks([0, 25, 50, 75, 100])
ax.set_xlabel("marks")
ax.set_ylabel("no. of students")
fig.savefig("viz_out/08_hist.png")
plt.close(fig)
# Pie chart
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
langs = ["Statistics", "Python", "Machine Learning", "SQL", "Big Data"]
students = [53, 27, 25, 19, 12]
ax.pie(students, labels=langs, autopct="%1.2f%%")
fig.savefig("viz_out/09_pie.png")
plt.close(fig)
# Scatter plot
girls_marks = [19, 30, 10, 69, 80, 60, 14, 30, 80, 34]
boys_marks = [90, 29, 49, 48, 100, 48, 38, 45, 20, 30]
grades_range = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax.scatter(grades_range, girls_marks, color="r", label="girls")
ax.scatter(grades_range, boys_marks, color="b", label="boys")
ax.set_xlabel("Grades Range")
ax.set_ylabel("Grades Scored")
ax.set_title("scatter plot")
ax.legend()
fig.savefig("viz_out/10_scatter.png")
plt.close(fig)
# Contour plot
xlist = np.linspace(-8.0, 10.0, 100)
ylist = np.linspace(-8.0, 10.0, 100)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X ** 4 + Y ** 4)
fig, ax = plt.subplots(1, 1)
cp = ax.contourf(X, Y, Z)
fig.colorbar(cp) # add a colorbar to the plot
ax.set_title("Filled Contours Plot")
ax.set_xlabel("x (cm)")
ax.set_ylabel("y (cm)")
fig.savefig("viz_out/11_contour.png")
plt.close(fig)
# Quiver plot
x, y = np.meshgrid(np.arange(-2, 2, .2), np.arange(-2, 2, .25))
z = x * np.exp(-x ** 2 - y ** 2)
v, u = np.gradient(z, .25, .2) # spacing along y first, then x
fig, ax = plt.subplots()
q = ax.quiver(x, y, u, v)
fig.savefig("viz_out/12_quiver.png")
plt.close(fig)
# Box plot
value1 = [82, 76, 24, 40, 67, 62, 71, 79, 81, 22, 98, 89, 78, 67, 72, 82, 87, 66, 56, 52]
value2 = [12, 25, 11, 35, 36, 32, 96, 95, 3, 90, 95, 32, 27, 55, 100, 12, 1, 451, 37, 21]
value3 = [23, 89, 12, 78, 72, 89, 25, 69, 68, 86, 19, 49, 15, 16, 16, 75, 65, 31, 25, 52]
value4 = [99, 33, 75, 66, 83, 61, 82, 98, 10, 87, 29, 72, 26, 23, 72, 88, 78, 99, 75, 30]
box_plot_data = [value1, value2, value3, value4]
fig, ax = plt.subplots()
bp = ax.boxplot(box_plot_data)
print("outliers per box:", [len(f.get_ydata()) for f in bp["fliers"]])
fig.savefig("viz_out/13_box.png")
plt.close(fig)
# Violin plot
np.random.seed(10)
collectn_1 = np.random.normal(300, 30, 300)
collectn_2 = np.random.normal(60, 10, 300)
collectn_3 = np.random.normal(60, 40, 300)
collectn_4 = np.random.normal(20, 25, 300)
data_to_plot = [collectn_1, collectn_2, collectn_3, collectn_4]
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
vp = ax.violinplot(data_to_plot, showmedians=True)
fig.savefig("viz_out/14_violin.png")
plt.close(fig)
# 3D line plot
fig = plt.figure()
ax = plt.axes(projection="3d")
z = np.linspace(0, 2, 200)
x = z * np.sin(40 * z)
y = z * np.cos(40 * z)
ax.plot3D(x, y, z, "red")
ax.set_title("3D line plot")
fig.savefig("viz_out/15_3dline.png")
plt.close(fig)
# 3D contour, wireframe and surface share one function
def f(x, y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
x = np.linspace(-4, 4, 40)
y = np.linspace(-4, 4, 40)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig = plt.figure()
ax = plt.axes(projection="3d")
ax.contour3D(X, Y, Z, 50, cmap="binary")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
ax.set_title("3D contour")
fig.savefig("viz_out/16_contour3d.png")
plt.close(fig)
fig = plt.figure()
ax = plt.axes(projection="3d")
ax.plot_wireframe(X, Y, Z, color="green")
ax.set_title("wireframe")
fig.savefig("viz_out/17_wireframe.png")
plt.close(fig)
x = np.outer(np.linspace(-4, 4, 40), np.ones(40))
y = x.copy().T # transpose
z = np.cos(x ** 2 + y ** 2)
fig = plt.figure()
ax = plt.axes(projection="3d")
ax.plot_surface(x, y, z, cmap="viridis", edgecolor="none")
ax.set_title("Surface plot")
fig.savefig("viz_out/18_surface.png")
plt.close(fig)
print("all plots saved")
Matplotlib has a lot of functions, but the ideas repeat. You make a figure, you add axes, you call one plot method, and you label it. Once you see that pattern, the 3D plots and the unusual charts become variations on the same steps. Run the script above, change the data, and break things on purpose. Then try a real dataset with pandas, and look at Seaborn when you want statistical charts with less code.