⇦ Back

1 One Independent Variable, One Dependent Variable

1.1 Point-to-Point Curves

To create a simple line plot that connects points in a Cartesian plane:

  • Create an axes object with plt.axes()
    • It’s possible to create a plot without first creating axes, but there are certain pieces of functionality within Matplotlib that require axes to be explicitly defined in order to be used. All plots on this page will start by creating axes whether it’s necessary or not.
  • Plot points on those axes using the .plot() method

Here’s an example, showing a parabola (\(y = x^2\)):

import matplotlib.pyplot as plt

x = [1, 5, 9, 13]
y = [1, 25, 81, 169]

ax = plt.axes()
ax.plot(x, y)

1.2 Smooth Curves

When you only plot a few points as in the previous example, the resulting curve will look jagged. If you are plotting a continuous function like this it’s usually better to have it be a smooth curve. While it isn’t technically possible to plot a continuous curve using this method, you can at least make the line appear smooth by simply increasing the number of points you plot:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 13, 100)
y = x**2

ax = plt.axes()
ax.plot(x, y)

The above example is plotting 100 points and connecting them with straight lines. So the jagged edges still exist but they are now too small to be noticeable! Note the function that was used to create the x-data: Numpy’s linspace(). This function creates an array of numbers evenly spaced between a given start point and a given end point with the number of values created being the third input to the function. In this case the inputs were 0, 13 and 100, so 100 values were generated starting at 0 and ending at 13. The y-data was then created by taking the square of the x-data.

1.3 Vertical and Horizontal Lines

If you want to plot a straight line, you can simply plot two points and they will be connected as expected. In the special cases where you want to create horizontal or vertical lines that extend right to the ends of the graph in both directions, there are special functions ax.axhline() and ax.axvline():

ax = plt.axes()
# Plot a straight diagonal line
ax.plot([0, 9], [0, 8])
# Plot a horizontal line
ax.axhline(4)
# Plot a vertical line
ax.axvline(7)

1.4 Error Bars

Uncertainty can be communicated using the errorbar() function to plot error bars:

x = [1, 5, 9, 13]
y = [1, 25, 81, 169]
yerr = [1.5, 7.5, 15, 19]

ax = plt.axes()
ax.plot(x, y)
ax.errorbar(x, y, yerr=yerr, fmt='o', color='purple')

plt.show()

1.5 Formatting Options

Change the look of the plot with the following options:

  • Set the title with ax.set_title()
  • Set the axis labels with ax.set_ylabel() and ax.set_xlabel()
  • Change the axis limits with ax.set_ylim() and ax.set_xlim()
  • Change the line colour and width using the c and lw keyword arguments in the ax.plot() call (see this page for all the colour and line options)

Here’s an example using Manchester City’s log points after each matchweek of the 2018-19 English Premier League season:

import matplotlib.pyplot as plt

week = [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38
]
log_points = [
    3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98
]

ax = plt.axes()
ax.plot(week, log_points, c='#6caddf', lw=3)
ax.set_title('Manchester City in the 2018-19 Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)

plt.show()

Notice that the formatting options in the above example are all being changed by accessing the axes object, which is what the ax at the start of each line is indicating. It would also have been possible to produce this plot using functions directly from Matplotlib (and these would have started with plt instead of ax) although this page is specifically demonstrating how to produce plots using axes objects.

1.6 More Options

Some more options that can be tinkered with:

  • Transparency of the line: use the alpha keyword argument within ax.plot()
  • Colour of the graph area: use the ax.set_facecolor((R, G, B)) function where R, G and B are the red, green and blue colour proportions on a scale of 0 to 1
  • Gridlines: use the plt.grid() function in which you can set which gridlines to mark (major, minor or both) and the axis to apply the lines to (x, y or both), along with other keyword arguments related to line plots
    • If you want minor gridlines and axis ticks you will also need to use plt.minorticks_on()
    • Use ax.set_axisbelow(True) after adding gridlines to move them behind your data points
    • To colour the axis lines (eg if you want them to match your gridlines) you will need to use the .set_color() method of the spines (Matplotlib’s word for the axis lines). This can be done in a for loop and is demonstrated in the example below.
  • Alter the aspect ratio of the plotting area using ax.set_aspect(). To make this easier, it can be helpful to get the current limits of the axes using ax.get_ylim() and ax.get_xlim():
ax = plt.axes()
ax.plot(week, log_points, c='#6caddf', lw=3, alpha=0.5)
ax.set_title('Manchester City in the 2018-19 Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)
ax.set_facecolor((232 / 255, 232 / 255, 232 / 256))
# Gridlines
plt.grid(which='major', c='w', lw='0.5', linestyle='-')
ax.set_axisbelow(True)
for spine in ax.spines:
    ax.spines[spine].set_color('w')
# Make the axes square
y0, y1 = ax.get_ylim()
x0, x1 = ax.get_xlim()
ax.set_aspect(abs(x1 - x0) / abs(y1 - y0))

plt.show()

1.7 Text and Annotations

There is a difference between text and annotations on a graph:

  • Text labels are text only. They are added with the ax.text() function which allows you to specify the x- and y-coordinates of the label’s position along with the string that should appear. There are also additional options that can be edited, such as ha (the horizontal alignment of the text), size and color
  • Annotations are text with arrows. These can be added with ax.annotate() together with a number of arguments. These arguments can be specified by using their keywords or they can be given without keywords if they are in the following order:
    • text is the string that should appear
    • xy is a tuple containing the coordinates of the tip of the arrow. By default, these are interpreted as the Cartesian coordinates of the axes.
    • xytext is a tuple containing the coordinates of the text. Again, by default, these are Cartesian coordinates.
    • xycoords is the coordinate system that xy (the position of the tip of the arrow) is given in. There are a number of options but perhaps the most useful, besides Cartesian, is 'axes fraction' which allows you to specify the position as fractions of the width and height of the graph area. Unlike the default Cartesian coordinate system, this option allows you to add annotations outside of the actual graph area.
    • textcoords is similar to xycoords but is for the text, not the tip of the arrow. In addition to all the options available for xycoords you can use 'offset points' or 'offset pixels' which specifies the position of the text as an offset from the point it is annotating.
    • arrowprops is a dictionary of options for the arrow
  • Note that it is not compulsory for an annotation to have an arrow, which means that if you want to add text but also take advantage of the greater control that ax.annotate() gives you you can do so. Conversely, you can create arrows without text by simply not specifying a string:
ax = plt.axes()
ax.plot(week, log_points, c='#6caddf', lw=3, alpha=0.5)
ax.set_title('Manchester City in the 2018-19 Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlim(1, 38)
# Add text and annotations
ax.text(33, 62, 'Final 14 matches', ha='center')
ax.text(33, 58, 'were all victories', ha='center')
ax.annotate(
    'Moved clear at the top of the table', (11, 29), (30, -20), textcoords='offset points',
    arrowprops=dict(facecolor='black', width=0.5, headwidth=5, headlength=7)
)
ax.annotate(
    'Week 11', (11, 29), (-6, 6), textcoords='offset points', ha='right'
)
ax.annotate(
    '', (16 / 38, -0.07), (28 / 38, -0.07), 'axes fraction', 'axes fraction',
    arrowprops=dict(arrowstyle="<->", color='k')
)
ax.annotate(
    'Dropped from top spot', (22 / 38, -0.12), xycoords='axes fraction', ha='center'
)

plt.show()

Read the full documentation for text labels here and for annotations here.

1.8 Multiple Groups

To plot multiple data series on the same axes, simply use the ax.plot() function multiple times. When doing this, it’s usually best to include a legend. This is created via ax.legend() after having specified the label keyword argument in the ax.plot() calls:

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# Get the data
data = pd.read_csv('Week-by-Week Points.csv')

# Plot
ax = plt.axes()
for team in list(data):
    log_points = data[team]
    week = np.arange(1, 39)
    ax.plot(week, log_points, label=team)
ax.set_title('2018-19 English Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)
ax.legend(fontsize='xx-small')

Of course, for this particular example, it makes sense to individually set the team colours:

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# Get the data
data = pd.read_csv('Week-by-Week Points.csv')

# Create a dictionary of the colours
team_colours = {
    'AFC Bournemouth': '#8b0304',
    'Arsenal': '#ff0000',
    'Brighton': '#005daa',
    'Burnley': '#80bfff',
    'Cardiff': '#224781',
    'Chelsea': '#0000dd',
    'Crystal Palace': '#0a4af5',
    'Everton': '#274488',
    'Fulham': '#000000',
    'Huddersfield': '#176fc0',
    'Leicester': '#0101e8',
    'Liverpool': '#dd0000',
    'Man City': '#6caddf',
    'Man Utd': '#e80909',
    'Newcastle': '#000000',
    'Southampton': '#ed1a3b',
    'Spurs': '#132257',
    'Watford': '#fbee23',
    'West Ham': '#7f0000',
    'Wolves': '#fdbc02'
}

# Plot
ax = plt.axes()
for team in list(data):
    log_points = data[team]
    week = np.arange(1, 39)
    ax.plot(week, log_points, label=team, c=team_colours[team])
ax.set_title('2018-19 English Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)
ax.legend(fontsize='xx-small')

1.8.1 Positioning the Legend Outside the Axes

It often makes sense to place the legend outside of the axes to give it a bit more room. This requires some size adjustments using plt.subplots_adjust() and more complicated arguments to be passed to ax.legend():

ax = plt.axes()
for team in list(data):
    log_points = data[team]
    week = np.arange(1, 39)
    ax.plot(week, log_points, label=team, c=team_colours[team])
ax.set_title('2018-19 English Premier League Season')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)
plt.subplots_adjust(right=0.75)
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5), fontsize='small')

1.8.2 Manually Ordering the Legend

If you want the data series to appear in a particular order in the legend, you can assign each of them to a variable and then call those variables in the ax.legend() call. In this example, the data for Fulham, Man City and Wolves are plotted in that order, but the order they appear in the legend is manually determined:

week = np.arange(1, 39)
man_city = [
    3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98
]
wolves = [
    1, 1, 2, 5, 8, 9, 12, 15, 15, 15, 15, 16, 16, 16, 19, 22, 25, 25, 26, 29,
    29, 29, 32, 35, 38, 39, 40, 40, 43, 44, 44, 47, 47, 47, 51, 54, 57, 57
]
fulham = [
    0, 0, 3, 4, 4, 5, 5, 5, 5, 5, 5, 5, 8, 8, 9, 9, 9, 10, 11, 14, 14,
    14, 14, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 20, 23, 26, 26, 26
]

# Plot
ax = plt.axes()
# Notice the commas after the variable names
fulham, = ax.plot(week, fulham, c='#000000')
man_city, = ax.plot(week, man_city, c='#6caddf')
wolves, = ax.plot(week, wolves, c='#fdbc02')
ax.set_title('2018-19 English Premier League Season')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)
ax.legend(
    (man_city, wolves, fulham),
    ('Man City', 'Wolves', 'Fulham'),
    loc='center left'
)

1.9 Colour Within, Under or Between Graphs

There are two methods of doing this. The first - plt.fill() - will take the points you are plotting, draw straight lines between them (including from the last point back to the first point) and then fill in that enclosed area with colour. This means that, in order to get this method to work for our data, we need to add an extra point at the start and an extra point at the end. These will be at (1, 0) and (38, 0), respectively:

import matplotlib.pyplot as plt

week = [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38
]
log_points = [
    3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98
]

# Plot
ax = plt.axes()
ax.plot(week, log_points, c='#6caddf', lw=3)
ax.set_title('Manchester City in the 2018-19 Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)
# Add colour fill
week = [
    1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 38
]
log_points = [
    0, 3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98, 0
]
plt.fill(week, log_points, facecolor='#6caddf', alpha=0.5)

plt.show()

This is obviously quite obtuse, so a simpler option would be to use ax.fill_between() which fills in the area between lines with colour. If you only specify one line then it will default to assuming that the second line is the x-axis:

import matplotlib.pyplot as plt

week = [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38
]
log_points = [
    3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98
]

# Plot
ax = plt.axes()
ax.plot(week, log_points, c='#6caddf', lw=3)
ax.set_title('Manchester City in the 2018-19 Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)
ax.fill_between(week, log_points, facecolor='#6caddf', alpha=0.5)

plt.show()

And then, of course, you still have the option of specifying a second line in order to fill in the area between those two:

week = np.arange(1, 39)
man_city = [
    3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98
]
wolves = [
    1, 1, 2, 5, 8, 9, 12, 15, 15, 15, 15, 16, 16, 16, 19, 22, 25, 25, 26, 29,
    29, 29, 32, 35, 38, 39, 40, 40, 43, 44, 44, 47, 47, 47, 51, 54, 57, 57
]

# Plot
ax = plt.axes()
ax.plot(week, man_city, c='#6caddf', label='Man City', lw=3)
ax.plot(week, wolves, c='#fdbc02', label='Wolves', lw=3)
ax.set_title('2018-19 English Premier League Season')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)
ax.legend()
ax.fill_between(week, man_city, wolves, facecolor='gray', alpha=0.5)

plt.show()

  • Use ax.fill_betweenx(y, x) to do the same thing as above but between two vertical curves (or between a curve and the y-axis)
  • Use the hatch keyword argument to apply a pattern to the shaded area. To increase the density of the shading, increase the number of characters when setting the hatch pattern (see here for more examples):
week = np.arange(1, 39)
man_city = [
    3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98
]
wolves = [
    1, 1, 2, 5, 8, 9, 12, 15, 15, 15, 15, 16, 16, 16, 19, 22, 25, 25, 26, 29,
    29, 29, 32, 35, 38, 39, 40, 40, 43, 44, 44, 47, 47, 47, 51, 54, 57, 57
]

# Plot
ax = plt.axes()
ax.plot(week, man_city, c='#6caddf', label='Man City', lw=3)
ax.plot(week, wolves, c='#fdbc02', label='Wolves', lw=3)
ax.set_title('2018-19 English Premier League Season')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Week')
ax.set_xlim(1, 38)
ax.legend(framealpha=1)
ax.fill_betweenx(man_city, week, facecolor='#6caddf', alpha=0.5, hatch='++')
ax.fill_between(week, wolves, facecolor='#fdbc02', alpha=0.5, hatch='...')

plt.show()

1.10 Customising Axis Labels

1.10.1 Specifying Exact Tick Locations and Labels

Use ax.set_yticks() and ax.set_xticks() to define the locations of the axis ticks and ax.set_yticklabels() and ax.set_xticklabels() to list the exact strings to be used as labels:

import matplotlib.pyplot as plt

week = [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38
]
log_points = [
    3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98
]

ax = plt.axes()
ax.plot(week, log_points, c='#6caddf')
ax.set_title('Manchester City in the 2018-19 Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlim(1, 38)
ax.set_xticks([1, 4, 7, 11, 14, 21, 25, 29, 32, 37])
ax.set_xticklabels(['Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May'])

plt.show()

1.10.2 Plotting Text

In the previous example, the plot was created using numbers on the x-axis which were then replaced by text labels manually via the ax.set_xticklabels() function. In this example, however, we ‘cut out the middleman’ and use text strings as the x-data from the start:

plt.rc('text', usetex=True)

month = [
    'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
]
temp = [
    6.7, 6.8, 8.8, 11.9, 14.7, 18.2,
    19.5, 19.3, 17.3, 13.5, 9.9, 7.0
]

ax = plt.axes()
ax.plot(month, temp, 'k')
ax.set_title('Climate of London')
ax.set_ylabel(r'Mean Daily Temperature [\textdegree C]')
ax.set_xlabel('Month')

The above data comes from Wikipedia.

1.10.3 Scaling an Axis

If you want to change the scale on an axis (eg show the x-axis in units of days when the original data is in weeks) you can edit its format using the following:

  • The yaxis.set_major_formatter() or xaxis.set_major_formatter() method, depending on which axis you want to change the format of
  • The matplotlib.ticker library to provide access to the FuncFormatter() function. This can create format objects for the axis tick marks.

In this example, we want to scale up the x-axis by a factor of 7 to convert from weeks to days. We do this by:

  • Using the lambda statement to create a function ‘object’ (called a ‘lambda function’) which takes a dummy variable and multiplies it by 7
  • Turning this lambda function into a formatting object using FuncFormatter()
  • Applying this formatting object to the major tick marks on the x-axis with xaxis.set_major_formatter()
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

week = [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38
]
log_points = [
    3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98
]

ax = plt.axes()
ax.plot(week, log_points, c='#6caddf')
ax.set_title('Manchester City in the 2018-19 Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Day')
ax.set_xlim(1, 38)
fmt = ticker.FuncFormatter(lambda x, _: x * 7)
ax.xaxis.set_major_formatter(fmt)

plt.show()

The graph looks exactly the same as before except the values on the x-axis are now 7 times larger. Notice that we set the x-limit to be 38 (ax.set_xlim(1, 38)), but because we did that before changing the x-axis format this was interpreted as 38 weeks so it still looks correct even after converting to days. If you want to make the x-axis look cleaner by removing the “.0” from the tick labels you can change the lambda function to convert the values from floats to integers:

fmt = ticker.FuncFormatter(lambda x, _: int(x * 7))

1.10.4 Using Log Scale

If you want to use logarithmic scales, take a look at the ax.set_yscale('log') or the ax.set_xscale('log') function, depending on which axis you want to change. Again, use a lambda function to format the tick marker labels on the relevant axis. Here’s an example of one that will work for the y-axis:

ax.set_yscale('log')
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda y, _: f'{y:g}'))

This lambda function is simply creating a formatted string (an f-string that uses the g or general format). If we don’t do this it might make the labels look strange, depending on what exactly the values are.

1.10.5 Rotating Axis Labels

This can be done in one of two ways:

  • ax.xaxis.set_tick_params(rotation=30)
  • ax.set_xticklabels(label_names, rotation=90, ha='right')

One might be better than the other for a particular implementation. The first method is demonstrated in the example below and the second one is in the example after that (in the ‘Representing Time’ section):

ax = plt.axes()
ax.plot(week, log_points, c='#6caddf')
ax.set_title('Manchester City in the 2018-19 Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Day')
ax.set_xlim(1, 38)
fmt = ticker.FuncFormatter(lambda x, _: int(x * 7))
ax.xaxis.set_major_formatter(fmt)
ax.xaxis.set_tick_params(rotation=30)

plt.show()

1.10.6 Representing Time

This is covered more fully on a separate page, but here’s an example of what it looks like:

import matplotlib.pyplot as plt
import datetime
import matplotlib.dates as mdates

date_strings = [
    '12/08/2018', '20/08/2018', '27/08/2018', '02/09/2018', '17/09/2018', '23/09/2018',
    '01/10/2018', '07/10/2018', '22/10/2018', '29/10/2018', '05/11/2018', '11/11/2018',
    '26/11/2018', '02/12/2018', '05/12/2018', '10/12/2018', '16/12/2018', '23/12/2018',
    '27/12/2018', '30/12/2018', '03/01/2019', '14/01/2019', '20/01/2019', '30/01/2019',
    '06/02/2019', '11/02/2019', '24/02/2019', '27/02/2019', '03/03/2019', '10/03/2019',
    '17/03/2019', '03/04/2019', '08/04/2019', '16/04/2019', '24/04/2019', '28/04/2019',
    '06/05/2019', '12/05/2019'
]
datetimes = [datetime.datetime.strptime(s, '%d/%m/%Y') for s in date_strings]
log_points = [
    3, 6, 7, 10, 13, 16, 19, 20, 23, 26, 29, 32, 35, 38, 41, 41, 44, 44, 44,
    47, 50, 53, 56, 56, 62, 65, 65, 68, 71, 74, 74, 80, 80, 83, 89, 92, 95, 98
]

ax = plt.axes()
ax.plot(datetimes, log_points)
ax.set_title('Manchester City in the 2018-19 Premier League')
ax.set_ylabel('Log Points')
ax.set_ylim(0, 100)
ax.set_xlabel('Matchweek Date')
ax.set_xlim(datetimes[0], datetimes[-1])
# Re-format the x-axis
ax.set_xticks(datetimes)
ax.set_xticklabels(datetimes, rotation=90, ha='center', size='x-small')
fmt = mdates.DateFormatter('%d/%m')
ax.xaxis.set_major_formatter(fmt)
plt.subplots_adjust(bottom=0.15)

plt.show()

1.11 Sub-Plots

To create two (or more) completely separate plots in the same figure you still need to create axes objects, but now these objects need to be sub-plots as opposed to multiple sets of axes on the same plot:

  • plt.subplot(rcn) will create a sub-plot object where r is the total number of rows of plots you intend to make, c is the number of columns of plots you intend to make and n is the number that this plot will be within the grid of plots. For example, plt.subplot(321) will divide your figure into a grid with 3 rows and 2 columns (ie space for 6 plots) and then create an axes object for the first (top-left) of these plots. The plots are numbered using ‘reading order’ (left-to-right, top-to-bottom), ie plot 2 will be top-right, plot 3 will be middle-left and so on.
  • In order to create enough space for all of these plots, it’s a good idea to re-size your figure. This topic is discussed on it’s own separate page but, in short, your options are:
    • plt.figure(figsize=(w, h)) to set the width and height of the figure you are currently working on
    • plt.rc('figure', figsize=(w, h)) to set the same figsize parameter as above but for all the figures in your code
  • Because ax.set_title() creates the title for the individual plot referenced by ax, in order to create a title for the entire figure you need to use plt.suptitle()
  • By default, the layout of the plots in a grid of sub-plots doesn’t use up the available space particularly well. This can be improved by using plt.tight_layout()
import matplotlib.pyplot as plt
import numpy as np
import math

# Create data
x = np.linspace(0, math.tau, 100)

#
# Plot
#
plt.figure(figsize=(12, 5))
plt.suptitle('Trigonometric Functions')
# First sub-plot
ax1 = plt.subplot(121)
ax1.plot(x, np.sin(x))
ax1.axhline(0, c='gray', alpha=0.5)
ax1.set_title(r'$sin(\theta)$')
ax1.set_ylabel(r'Amplitude, $A$')
ax1.set_xlabel(r'Angle of Rotation, $\theta$ [radians]')
ax1.set_xlim(0, math.tau)
# Second sub-plot
ax2 = plt.subplot(122)
ax2.plot(x, np.cos(x))
ax2.axhline(0, c='gray', alpha=0.5)
ax2.set_title(r'$cos(\theta)$')
ax2.set_ylabel(r'Amplitude, $A$')
ax2.set_xlabel(r'Angle of Rotation, $\theta$ [radians]')
ax2.set_xlim(0, math.tau)
ax2.set_xticks([0, 0.25 * math.tau, 0.5 * math.tau, 0.75 * math.tau, math.tau])
ax2.set_xticklabels(
    ['$0$', r'$\frac{1}{4}\tau$', r'$\frac{1}{2}\tau$', r'$\frac{3}{4}\tau$', r'$\tau$']
)
plt.tight_layout()

plt.show()

1.12 Latex and Image Size

See here for more about using Latex formatting in the title and axes’ labels and see here for more about changing the image size.

# Make figures A5 in size
A = 5
plt.rc('figure', figsize=[46.82 * .5**(.5 * A), 33.11 * .5**(.5 * A)])
# Image quality
plt.rc('figure', dpi=141)
# Be able to add Latex
plt.rc('text', usetex=True)
plt.rc('font', family='serif')
plt.rc('text.latex', preamble=r'\usepackage{textgreek}')

# Plot
ax = plt.axes()
ax.plot(week, log_points, c='#6caddf')
ax.set_title(r'How to Include \LaTeX\ in Labels')
ax.set_ylabel(r'Output, $T$ [\textdegree C]')
ax.set_ylim(0, 100)
ax.set_xlabel(r'Input, $t$ [\textmu s]')
ax.set_xlim(1, 38)
ax.text(20, 30, r'\textAlpha\textBeta\textGamma\textDelta\textEpsilon\textZeta\textEta\textTheta\textIota\textKappa\textLambda\textMu\textNu\textXi\textOmikron\textPi\textRho\textSigma\textTau\textUpsilon\textPhi\textChi\textPsi\textOmega')
ax.text(20, 24, r'\textalpha\textbeta\textgamma\textdelta\textepsilon\textzeta\texteta\texttheta\textiota\textkappa\textlambda\textmu\textmugreek\textnu\textxi\textomikron\textpi\textrho\textsigma\texttau\textupsilon\textphi\textchi\textpsi\textomega')
ax.text(20, 18, r'\textvarsigma\straightphi\scripttheta\straighttheta\straightepsilon')
ax.text(20, 12, r'$$\lim_{n \to \infty} \left(1+\frac{1}{n}\right)^n$$')

1.13 Double Axes

As introduced in the ‘scaling an axis’ section above, sometimes it’s useful to display labels in two different sets of units. This can be done by creating a whole second set of axes and populating those with the alternate labels. Here’s an example that does that, using fake data, and a number of specific features:

  • The axes need to be of an ‘axis artist’ class. This can be imported from the mpl_toolkits library.
  • This class can only be created in a sub-plot of the the type in the mpl_toolkits library, via host_subplot()
  • The twinning of the complete axes can now be done using ax1.twin(), as opposed to ax1.twinx() which would only twin the x-axis
  • The ax.set_xticks() function can be used to specify exactly where to place tick markers
  • Similarly, ax.set_xticklabels() can be used to specify exactly what each tick label says
  • By default, only some of the axes’ tick labels are shown. These can be turned on and off with .major_ticklabels.set_visible()
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import numpy as np
import math

# Make images A5
A = 5
plt.rc('figure', figsize=[46.82 * .5**(.5 * A), 33.11 * .5**(.5 * A)])
# Use a serif font
plt.rc('font', family='serif')
# Use Latex
plt.rc('text', usetex=True)

# Fake up some data
x = np.linspace(0, math.tau, 100)
y = np.sin(x)

#
# Plot
#
ax1 = host_subplot(111, axes_class=AA.Axes)
ax1.plot(x, y)
ax1.axhline(0, c='gray', alpha=0.5)
ax1.set_ylabel(r'Voltage, $V$ [\textmu V]')
ax1.set_xlabel(r'Angle of Rotation, $\theta$ [radians]')
ax1.set_xlim(0, math.tau)
# Create a second set of axes as a twin of the first
ax2 = ax1.twin()
# Edit the second set of axes
ax2.set_xticks([0, 0.25 * math.tau, 0.5 * math.tau, 0.75 * math.tau, math.tau])
ax2.set_xticklabels(
    ['$0$', r'$\frac{1}{4}\tau$', r'$\frac{1}{2}\tau$', r'$\frac{3}{4}\tau$', r'$\tau$']
)
ax2.axis['right'].major_ticklabels.set_visible(False)
ax2.axis['top'].major_ticklabels.set_visible(True)

plt.show()

1.14 Finished?

Finally, save the plot as a PNG, JPG, PDF or other type of image with plt.savefig() or display it in a pop-up window with plt.show():

plt.savefig('Line Plot.png')

If you are plotting more than one figure in the same Python script use plt.figure() and plt.close() before and after each, respectively, in order to tell Python when one plot ends and the next one starts.

2 One Independent Variable, Two Dependent Variables

2.1 2D Plots

With two dependent variables you will often want two y-axes.

2.1.1 One Data Series

It’s not common to represent one series of data with two dependent variables on a 2D graph, but it can be useful when there is a constant relationship between the dependent variables. For example, if we take the force and pressure exerted on a piston, the relationship between the two (\(P = \frac{F}{A}\)) remains constant because the area of the piston head (\(A\)) doesn’t change. Here’s what the force vs time graph might look like:

import math
import matplotlib.pyplot as plt
import numpy as np

# Fake up some data
np.random.seed(20210319)
time = np.linspace(0, 10, 20)
force = time**2 + np.random.normal(size=20) * 2
area = (math.tau * 0.04**2) / 2

# Plot
ax1 = plt.axes()
ax1.plot(time, force)
ax1.set_title('Force vs Time')
ax1.set_ylabel(r'Force, $F$ [N]')
ax1.set_ylim(0, )
ax1.set_xlabel(r'Time, $t$ [s]')
ax1.set_xlim(0, 10)

plt.show()

Now, to add in the pressure, we need another whole set of axes. This can be achieved by ‘twinning’ the current x-axis (because the second y-axis will use the same x-axis as the first) via ax1.twinx() to create a new set of axes which we will call ‘ax2’. This set won’t have any data on it yet, so its y-axis will default to running from 0 to 1 while it’s x-axis will sit directly on top of the old x-axis (because it was created as a twin of this). We then have three options:

  • Calculate the pressure values and plot them on this new, second set of axes
  • Plot the same force values on the new set of axes and scale up the second y-axis
  • Don’t plot anything on the second set of axes and just scale it up using the limits of the first y-axis

The first option is probably the least complicated, so we will do that. It will also make sense to scale the second y-axis down by a factor of 1,000 to show the values in kilopascals instead of pascals:

pressure = [f / area for f in force]

# Plot
ax1 = plt.axes()
ax1.plot(time, force)
ax1.set_title('Force and Pressure vs Time')
ax1.set_ylabel(r'Force, $F$ [N]')
ax1.set_ylim(0, )
ax1.set_xlabel(r'Time, $t$ [s]')
ax1.set_xlim(0, 10)
ax2 = ax1.twinx()
ax2.plot(time, pressure)
ax2.set_ylabel(r'Pressure, $P$ [kPa]')
ax2.set_ylim(0, )
fmt = ticker.FuncFormatter(lambda x, _: x / 1000)
ax2.yaxis.set_major_formatter(fmt)

plt.show()

2.1.2 Two Data Series

In the previous example, the two axes both related to the same series of data. If, however, you want to plot two different but related series you will need to separate them more clearly:

  • Use different colours for the labels, the markers and the tick labels
    • The tick labels can be edited by accessing them through the .get_yticklabels() method and then iterating over them in a ‘for’ loop
import numpy as np
import math
import matplotlib.pyplot as plt

# Fake up some data
np.random.seed(20210316)
x = np.linspace(0, 0.4 * math.tau, 20)
y_1 = np.sin(x) + np.random.normal(size=20) * 0.08
y_2 = 5 * np.sin(x * 0.75) + np.random.normal(size=20) * 0.08

#
# Plot
#
ax1 = plt.axes()
ax1.set_title('Measurements vs Time')
ax1.set_xlabel(r'Time, $t$ [s]')
# First dependent variable
ax1.plot(x, y_1, c='b', marker='x')
for tl in ax1.get_yticklabels():
    # Change axis colours
    tl.set_color('b')
ax1.set_ylabel(r'Measurement 1, $m_1$ [m]', color='b')
ax1.set_ylim(0, )
ax1.set_xlim(0, )
# Create a second set of axes, twinned with the first x-axis
ax2 = ax1.twinx()
# Second dependent variable
ax2.plot(x, y_2, c='r', marker='x')
for tl in ax2.get_yticklabels():
    # Change axis colours
    tl.set_color('r')
ax2.set_ylabel(r'Measurement 2, $m_2$ [m]', color='r')
ax2.set_ylim(0, )

plt.show()

2.2 3D Plots

Perhaps the most logical way to represent three variables is by using three axes. This requires two new features:

  • The .add_subplot() method to add a subplot (we don’t actually want to add an additional plot, this just creates a new plot which we can edit the projection of)
  • The ax.set_zlabel() function which creates a label for the third axis
import numpy as np
import matplotlib.pyplot as plt

# Fake up some data
x = np.linspace(-2, 2, 100)
y = np.linspace(-1, 2, 100)
z = np.sin(x + y)

# Plot
ax = plt.figure().add_subplot(projection='3d')
ax.plot(x, y, z)
ax.set_title('Results for a Fictional Experiment')
ax.set_xlabel(r'Time, $t$ [s]')
ax.set_ylabel(r'Measurement 1, $m_1$ [m]')
ax.set_zlabel(r'Measurement 2, $m_2$ [m]')

plt.show()

Here’s an example using a parametric curve:

import numpy as np
import matplotlib.pyplot as plt
import math

# Fake up some data
theta = np.linspace(-2 * math.tau, 2 * math.tau, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)

# Plot
ax = plt.figure().add_subplot(projection='3d')
ax.plot(x, y, z, label='Parametric curve')
ax.legend()

plt.show()

2.2.1 Two Data Series

With two sets of data you will want to add a legend and marker colours:

import numpy as np
import matplotlib.pyplot as plt

# Fake up some data
np.random.seed(20210316)
x_1 = np.linspace(-1, 1, 100)
y_1 = np.linspace(1, 12, 100)
z_1 = np.sin(x_1 + y_1)
x_2 = np.linspace(-2, 2, 100)
y_2 = np.linspace(-1, 2, 100)
z_2 = np.sin(x_2 + y_2)

# Plot
ax = plt.figure().add_subplot(projection='3d')
ax.plot(x_1, y_1, z_1, c='r', label='Experimental Group')
ax.plot(x_2, y_2, z_2, c='b', label='Control Group')
ax.set_xlabel(r'Time, $t$ [s]')
ax.set_ylabel(r'Measurement 1, $m_1$ [m]')
ax.set_zlabel(r'Measurement 2, $m_2$ [m]')
ax.legend()

plt.show()

⇦ Back