matplotlib: The Python Plotting Library

Introduction

Spyder is a Python Integrating Development Environment (IDE) which has a similar user interface to Matrix Laboratory (MATLAB) which is a Commercial product commonly used in the physical sciences and engineering fields. Python and the Spyder IDE are open-source and can be freely downloaded using the Anaconda Python Distribution.

In this guide we will look at creating a figure in python using the Spyder 4 IDE and matplotlib module pyplot. pyplot is highly based on the commercial matrix laboratory matlab which is widely used for array manipulation and plotting in scientific disciplines.

 Python with the numeric python library (numpy), the plotting library (matplotlib) and the Python and data analysis library (pandas) can be used to replace the commercial product MATLAB for most applications.

Installation

If you haven't already installed Python and Spyder 4 using the Anaconda Installation see.

Perquisites

It is not recommended to jump straight into matplotlib (like I did when first learning Python) unless you familiar with the Core Python programming language and the Numeric Python Library NumPy. It is also useful to have a background on the Python and Data Analysis Library (PANDAS). I have guides on these here:

video

inline plotting vs automatic plotting

We can create a very basic script to test out the numpy library and the matplotlib plotting libraries. We will go through this code in more detail later but right now just want to use it to demonstrate how matplotlib plots in Spyder:

import numpy as np
import matplotlib.pyplot as plt
x=np.arange(start=0,stop=11,step=1)
y=np.arange(start=0,stop=22,step=2)
plt.figure()
plt.clf()
plt.plot(x,y)
plt.show()

By default plots will show up inline embedded within the plots pane.

This behavior can be modified for a single session to plot each figure in a dedicated window by typing in:

%matplotlib auto

in the console.

It can be reverted by typing in:

%matplotlib inline

The default behavior can be changed by going to tools and preferences:

To the left hand side selecting iPython Console, then to the top select Graphics. Change the backend to either automatic or inline as desired:

importing the matplotlib library

To get started we will import the numpy and library and the module pyplot from the matplotlib library. The numpy library is commonly imported as the two letter abbreviation np as it is routinely referenced, we will use it to create two numpy vector arrays t and v of equal length for plotting. In order to perform a 2 dimensional plot we need to import the submodule pyplot from the matplotlib library. Additional submodules are required for 3 dimensional plots and some of these will be discussed later. pyplot is also routinely imported using the 3 letter abbreviation plt as it is also commonly referenced.

# %% Perquisites
import numpy as np
import matplotlib.pyplot as plt
# %% Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])

The two numpy vectors display on the console and are shown to have equal size (6,) which is necessary for plotting.

Once pyplot is imported as plt, functions within the module can be accessed by typing in plt followed by a dot . and then tab .

It is worth grouping these functions together into three columns. Now lets take an analogy of a piece of paper.

  • If we go to the shop we can buy a blank piece of paper. The figure gives the properties of the blank piece of paper and the functions listed in the first column relate to the figure.
  • We can also buy a piece of paper with horizontal lines or a square grid. In our case the grid is there so we can write on straight lines. These lines are on a set of axes. The properties in the second column are related to the axes.
  • Finally we can buy a picture. The picture (plot, legend, scatter) can be thought of as contained within a grid (axes) and printed on the piece of paper (figure).
figuregcaplot
showaxisscatter
imshowsubplotlegend
savefigsubplots
closexlabel
ylabel
title
xlim
ylim
xticks
yticks

creating a figure

The pyplot module of matplotlib imported as plt is highly based on matlab and as a result many of the commands mimic matlab's style. In matlab, a new figure is created by use of the function figure(). In python we of course need to call the function figure() from the imported module plt because figure() is a function it ends in parenthesis which enclose input arguments.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure() # No Figures Exist so this will be assigned the Figure Number 1

If no input arguments are supplied, the keyword input arguments take on their default values. If the input arguments are left blank then the figure is assigned the lowest free positive integer. When Spyder is initially launched as has a blank kernal, the first free positive integer is 1 and this is why we see figure 1 in the plot above. If we rerun the code, without restarting the kernal we will create a new figure however as figure 1 already exists we will create figure 2.

Alternatively the user may explicitly specify the number of the figure, for example:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(10)

If the figure number already exists, for example in the case of figure(1) instead of creating a new figure, it will simply reselect the existing figure.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Reselect Figure(2)
plt.figure(2)

We can close a figure by using the command close and either inputting the figure number as an input argument:

plt.close(2)

Or to close all figures, inputting the string 'all' as the input argument:

plt.close('all')

We can close all figures and remove all variables by restarting the Kernal. To do this go to Consoles and Restart Kernal.

Notice all figures are closed and all variables are removed from the variable explorer.

Let's now use the plot command to plot the independent variable t on the x-axis and the dependent variable v on the y-axis. We will look at the plot() function in more detail later on.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.plot(t,v)

Let's run the code above again without closing anything or restarting the Kernal.

And again.

You may have noticed that the line appeared to change color. However what has happened here during the second run is that line 8 re-selected figure(1) and plotted the same data above the original line. We therefore have three lines on this graph. Let's restart the Kernal and repeat this however before rerunning the code, we can add an offset of 5 to v.

In other words if we restart the Kernal, what we have done is this:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.plot(t,v)
plt.plot(t,v+10)
plt.plot(t,v+20)

When we reran the script unchanged three times, we never meant to plot the same line three separate times. To prevent this it is common to clear the figure one it is created in a script. This is done by the function clear figure clf():

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.plot(t,v)

Now even when the code above is ran multiple times, the line remains blue as it is a new canvas every time the script is run:

The function close with the input argument string 'all' is also routinely run at the top of scripts.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Close All Figures
plt.close('all')
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.plot(t,v)

We are using the Spyder IDE and by default the figure will display as soon as it is created. In other IDEs one may have to explicitly instruct the IDE to show the figure and therefore it is common to type in the function once a figure has been created show().

Let's now type in figure with open parenthesis.

plt.figure(

With open parenthesis to view the keyword input arguments.

By default num=None, which means the figure number will be automatically supplied taking the lowest unused integer value. It is also possible to specify the figure size as a tuple giving the width and height in inches and the dots per inch dpi respectively. The default value figsize=None corresponds to figsize=(6,4) and dpi=None corresponds to 72.0 which are the default values. These can be viewed in Tools → Preferences:

Then selecting IPython console and then Graphics.

We can supply these keyword input arguments to override the default values for a single figure.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(num=1,figsize=(7,3),dpi=125)
plt.clf()
plt.plot(t,v)
plt.show()

We can save the plot to a file using the function savefig.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(num=1,figsize=(7,3),dpi=125)
plt.clf()
plt.plot(t,v)
plt.show()
plt.savefig(r'C:\Users\Phili\Documents\plotting\fig1.png')

The input argument is the file name of the output file as a string. If no file path is specified, the default folder is used. Recall that in order for Python to recognize a \ we need to use \\.

'C:\\Users\\Phili\\Documents\\plotting\\fig1.png'

However if copying and pasting this is inefficient so we can use a relative string instead by typing in r before the string quotations.

r'C:\Users\Phili\Documents\plotting\fig1.png'

creating axes

Notice in the case above the last three commands are in matlab style, the figure is selected and the operations from any of the other functions called directly from the module plt will act on the existing figure. When we used the command plot, axes were created for us by default taking up the full canvas of the graph. It is possible to explicitly create axes using the function subplot. If we type the command:

plt.subplot(

With open parenthesis we will see details about the arguments. These are the number of rows nrows, number of columns ncols and the index specified using 1st order indexing.

The default values of these are nrows=1 and ncols=1 and index=1 respectively.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.subplot()
plt.plot(t,v)
plt.show()
# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.subplot(nrows=1,ncols=1,index=1)
plt.plot(t,v)
plt.show()

Unfortunately these arguments only work as positional arguments and an attribute error occurs if they are assigned as keyword arguments (hopefully this will be addressed in an update).

If nrows, ncols and index are just assigned as positional arguments:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.subplot(1,1,1)
plt.plot(t,v)
plt.show()

We can create a subplot that has 2 rows and 1 column. The indexes will start at 1 and go to 2 the number of plots (1st order indexing):

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.subplot(2,1,1)
plt.plot(t,v,color='r')
plt.subplot(2,1,2)
plt.plot(t,v,color='g')
plt.show()

We can create a subplot that has 2 rows and 2 columns. In this case the indexes go along rows and then down columns:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.subplot(2,2,1)
plt.plot(t,v,color='r')
plt.subplot(2,2,2)
plt.plot(t,v,color='g')
plt.subplot(2,2,3)
plt.plot(t,v,color='b')
plt.subplot(2,2,4)
plt.plot(t,v,color='k')
plt.show()

We seen earlier a figure can be reselected by its number. Once the figure is reselected, a subplot within the figure can then be then reselected:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.subplot(2,2,1)
plt.plot(t,v,color='r')
plt.subplot(2,2,2)
plt.plot(t,v,color='g')
plt.subplot(2,2,3)
plt.plot(t,v,color='b')
plt.subplot(2,2,4)
plt.plot(t,v,color='k')
plt.show()
plt.figure(2)
plt.clf()
plt.show()
plt.figure(1) # reselect figure 1
plt.subplot(2,2,3) # reselect subplot index 3
plt.plot(t,2*v,color='c')

plot

We have used the command plot to plot data, let's examine it in more detail:

The documentation shown is fairly limited here however we can get far more detailed information in the console by typing in:

? plt.plot

Let's create a basic plot with a number of keyword input arguments.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.plot(t,v,color='r',linestyle=':',marker='8',
         linewidth=3.5,markeredgewidth=3,
         fillstyle='left',markeredgecolor='g',
         markerfacecolor='m',markerfacecoloralt='c',
         label='Rocket')
plt.show()

The keyword arguments, color, markeredgecolor, markerfacecolor, markerfacecoloralt all expect a color. The primary, secondary, black and white all have full strings and 1 letter strings. All other colors can be made by color mixing the primary colors. We therefore have 3 channels, corresponding to r, g and b. We can typically adjust the intensity of each primary color by 8 bit levels (0-255). In matplotlib we can express these as [r,g,b,a] floats where the last channel alpha a is the transparency (0 is fully transparent and 1 is non-transparent). We can also express the 0-255 by two hexadecimal values. There are 16 hexadecimal digits 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e and f and two hexadecimal digits can be used to represent 256 levels (16×16=256) '#ffffff'.

1 Letter String String   hexadecimal string[r,g,b,a]
float
'r' 'red' '#ff0000'[255/255,0/255,0/255,1]
'g' 'green' '#00ff00' [0/255,255/255,0/255,1]
'b' 'blue' '#0000ff' [0/255,0/255,255/255,1]
'c' 'cyan' '#00ffff' [0/255,255/255,255/255,1]
'y' 'yellow' '#ffff00' [255/255,255/255,0/255,1]
'm' 'magenta' '#ff00ff' [255/255,0/255,255/255,1]
'k' ('b' is taken) 'black' –––'#000000' [0/255,0/255,0/255,1]
'w' 'white' '#ffffff' [255/255,255/255,255/255,1]

The line style is also represented by a string as well as a 1-2 string, the default is a solid line.

1-2 Letter String String
'-' 'solid'
'–' 'dashed'
'-.' 'dashdot'
':' 'dotted'

The marker is represented by a string or number. Note some of the strings are strings of numbers (try to avoid confusion with these). The markers that are strings are more commonly used. The default is None (no marker).

marker Description
None None (default)
'.' point
',' pixel
'o' circle
'v' triangle_down
'^' triangle_up
'<' triangle_left
'>' triangle_right
'1' tri_down
'2' tri_up
'3' tri_left
'4' tri_right
'8' octagon
's' square
'p' pentagon
'P' plus
'*' star
'h' hexagon1
'H' hexagon2
'+' plus
'x' x
'X' X
'D' diamond
'd' thin_diamond
'|' vline
'_' hline
0 tickleft
1 tickright
2 tickup
3 tickdown
4 caretleft
5 caretright
6 caretup
7 caretdown
8 caretleft
9 caretright
10 caretup
11 caretdown

The markerfillstyle is also a string and some of the marker fill styles will have an alternative color as shown in the plot above.

fillstyle has marker face alternative color
'full' no (default)
None no
'top' yes
'bottom' yes
'left' yes
'right' yes

The keyword argument label assigns a label for the line as a string and can be used with the function legend.

Not all of the keyword arguments need to be used. The most commonly ones are the color, linestyle and marker which all have abbreviated 1-2 letter strings. For convenience the short hand strings of these can be combined and used as a positional argument.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.plot(t,v,'r:8',t,2*v,'g--*',t,3*v,'c-.^')
plt.show()

In the above the three were combined using color, line style and then marker style. The order these three are combined doesn't matter:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.plot(t,v,'r:8',t,2*v,'--*g',t,3*v,'^c-.')
plt.show()

If any keyword arguments are used in this form, they will automatically be applied to all lines. Note we cannot use the keyword argument label in this scenario as it will apply the same label to all the lines.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.plot(t,v,'r:8',t,2*v,'g--*',t,3*v,'c-.^',
         linewidth=3,markersize=15)
plt.show()

We can however feed labels as a list of strings into the function legend and while we are at it we can use the functions xlabel, ylabel and title with string input arguments.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.plot(t,v,'r:8',t,2*v,'g--*',t,3*v,'c-.^',
         linewidth=3,markersize=15)
plt.xlabel('time (s)')
plt.ylabel('velocity (m/s)')
plt.title('velocity of a rocket')
plt.legend(labels=['rocket 1','rocket 2','rocket 3'])
plt.show()

By default the axes will rescale to match the upper and lower bounds of the x and y data respectively and matplotlib will assign the legend to what it thinks is the best location on the plot, in this case the upper left hand corner when there is no data. We can assign a location to the legend as a string or an integer (the integers unfortunately don't map to the arrows in a number square which would have made more sense).

string int
'best' (default) 0
'upper right' 1
'upper left' 2
'lower left' 3
'lower right' 4
'right' 5
'center left' 6
'center right' 7
'lower centre' 8
'upper center' 9

We can also use the function ylim with a vector containing the lower bound and the upper bound of the y axes respectively.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.plot(t,v,'r:8',t,2*v,'g--*',t,3*v,'c-.^',
         linewidth=3,markersize=15)
plt.xlabel('time (s)')
plt.ylabel('velocity (m/s)')
plt.title('velocity of a rocket')
plt.legend(labels=['rocket 1','rocket 2','rocket 3'],
           loc='lower left')
plt.ylim([-1000,3000])
plt.show()

scatter

The function scatter a lot of commonalities with plot but some slight differences. In the place of plot above, let's type in scatter with open parenthesis:

plt.scatter(

Once again we have the x and y positional arguments. We can supply t as the independent x vector and v as the dependent y vector respectively. The keyword argument marker takes the same string and numeric arguments as the keyword argument marker in plot. The next argument is the keyword input argument s which stands for sizes of the marker and c or color which stands for the face colors of the marker. The keyword arguments linewidths in this case refers to the line width of the markers and the edgecolors refer to the marker edge colors. In the simplest case each of these can be assigned a single value and all the markers will be formatted the same:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.scatter(t,v,s=150,marker='8',linewidths=2,
            color='r',edgecolors='b',alpha=1)
plt.xlabel('time (s)')
plt.ylabel('velocity (m/s)')
plt.title('velocity of a rocket')
plt.legend(labels=['rocket 1'])
plt.show()

The following keyword input arguments s, linewidths, color and edgecolors are plural and as a result each marker can be uniquely specified by use of vectors opposed to scalars. These vectors have to have the same dimensions as the x and y data supplied as the positional input arguments. Note that there is some inconsistency in the naming here; color and s should be colors and sizes denoting that these are plural)

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
sizes=np.array([150,175,200,225,250,275])
widths=[0.1,0.3,0.5,1.0,2.0,3.0]
colors=['r','g','b','m','c','y']
colors2=['m','c','y','r','g','b']
# Create Figure
plt.figure(1)
plt.clf()
plt.scatter(t,v,s=sizes,marker='8',
            linewidths=widths,
            color=colors,edgecolors=colors2,
            alpha=1)
plt.xlabel('time (s)')
plt.ylabel('velocity (m/s)')
plt.title('velocity of a rocket')
plt.legend(labels=['rocket 1'])
plt.show()

It is not possible to feed two or more datasets into the function scatter in a single line like they can in plot. This will become significant when we look at the concept of object orientated programming.

grid

We can supply a grid to the axes by use of the function grid. Let's type this in with open parenthesis

plt.grid(

Here we can see the keyword arguments and their default values. b is a bool with a default value of None meaning the grid is not shown by default, although the major tick marks on the axes are present by default. The keyword argument which allows one to select either the 'major', 'minor' or 'both' major and minor grid lines. The default value is 'major'. By default minor grid ticks are not present on the axes and these need to be enabled before the function grid can be used to show minor gird lines. The keyword input argument axis can be used to select 'x', 'y' or 'both' and the default value is 'both'. Additional keyword arguments color, linewidth and linestyle act in an identical manner to those within the function plot().

Let's just change b from None to True and the default grid will display.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
sizes=np.array([150,175,200,225,250,275])
widths=[0.1,0.3,0.5,1.0,2.0,3.0]
colors=['r','g','b','m','c','y']
colors2=['m','c','y','r','g','b']
# Create Figure
plt.figure(1)
plt.clf()
plt.scatter(t,v,s=sizes,marker='8',
            linewidths=widths,
            color=colors,edgecolors=colors2,
            alpha=1)
plt.xlabel('time (s)')
plt.ylabel('velocity (m/s)')
plt.title('velocity of a rocket')
plt.legend(labels=['rocket 1'])
plt.grid(b=True)
plt.show()

Now let's supply our own keyword arguments:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
sizes=np.array([150,175,200,225,250,275])
widths=[0.1,0.3,0.5,1.0,2.0,3.0]
colors=['r','g','b','m','c','y']
colors2=['m','c','y','r','g','b']
# Create Figure
plt.figure(1)
plt.clf()
plt.scatter(t,v,s=sizes,marker='8',
            linewidths=widths,
            color=colors,edgecolors=colors2,
            alpha=1)
plt.xlabel('time (s)')
plt.ylabel('velocity (m/s)')
plt.title('velocity of a rocket')
plt.legend(labels=['rocket 1'])
plt.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
plt.show()

We can now turn on/off the minor ticks by using the functions minorticks_on() and minorticks_off().

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
sizes=np.array([150,175,200,225,250,275])
widths=[0.1,0.3,0.5,1.0,2.0,3.0]
colors=['r','g','b','m','c','y']
colors2=['m','c','y','r','g','b']
# Create Figure
plt.figure(1)
plt.clf()
plt.scatter(t,v,s=sizes,marker='8',
            linewidths=widths,
            color=colors,edgecolors=colors2,
            alpha=1)
plt.xlabel('time (s)')
plt.ylabel('velocity (m/s)')
plt.title('velocity of a rocket')
plt.legend(labels=['rocket 1'])
plt.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
plt.minorticks_on()
plt.show()

You can see the minor tick marks if you look closely between 0 and 5.

Now that these are enabled, we can enable the minor grid lines.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
sizes=np.array([150,175,200,225,250,275])
widths=[0.1,0.3,0.5,1.0,2.0,3.0]
colors=['r','g','b','m','c','y']
colors2=['m','c','y','r','g','b']
# Create Figure
plt.figure(1)
plt.clf()
plt.scatter(t,v,s=sizes,marker='8',
            linewidths=widths,
            color=colors,edgecolors=colors2,
            alpha=1)
plt.xlabel('time (s)')
plt.ylabel('velocity (m/s)')
plt.title('velocity of a rocket')
plt.legend(labels=['rocket 1'])
plt.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
plt.minorticks_on()
plt.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
plt.show()

object orientated programming

So we far we have only used the matlab notation for plotting with matplotlib pyplot which works well for the basic plot and scatter above. However matplotlib is a python library and can also be interfaced using object orientated programming. The matlab notation doesn't work properly with matplotlib for more complicated plots which involve 3D axes and most the documentation uses a mixture of the matlab interface and object orientated reprogramming which can be confusing when first encountered.

Recall the difference between:

[1,2,3]

And:

l=[1,2,3]

[1,2,3] creates an object but it is only printed in the console and because this object is not assigned a variable name it cannot be accessed. On the other hand l=[1,2,3] is assigned to the variable name l and can now be re-accessed by use of its variable name. Recall that this has the form:

instance=list([1,2,3])

Which in terms of an instance and a class have the more general form:

instance=Class([1,2,3])

instance attributes (these can be thought of variables that belong to the instance) can be accessed using . indexing and instance methods can also be accessed using . indexing. Methods have input arguments so must be followed by parenthesis. Recall that we can type in the object's name followed by a dot . and a tab to view available attributes and methods.

instance.attribute
instance.method()

We can compare the matlab style and object orientated programming approach below.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])

# Create Figure matlab style
plt.figure(1) # creates figure 1 and selects figure 1
plt.clf() # referenced from module plt, applies to selected figure (figure 1)
plt.show() # referenced from module plt, applies to selected figure (figure 1)
plt.figure(2) # creates figure 2 and selects figure 2
plt.clf() # referenced from module plt, applies to selected figure (figure 1)
plt.show() # referenced from module plt, applies to selected figure (figure 1)
plt.figure(1) # re-selects figure 1
plt.clf() # referenced from module plt, applies to selected figure (figure 1)
plt.show() # referenced from module plt, applies to selected figure (figure 1)

# Create Figure object-orientated programming style
fig3=plt.figure(3) # saved to an object name
fig3.clf() # called as a method of the instance fig3
fig3.show() # called as a method of the instance fig3
fig4=plt.figure(4) # saved to an object name
fig4.clf() # called as a method of the instance fig4
fig4.show() # called as a method of the instance fig4
fig3.clf() # called as a method of the instance fig3
fig3.show() # called as a method of the instance fig3

Let's return to our scatter plot typed using the matlab interface:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
plt.figure(1)
plt.clf()
plt.subplot(1,1,1)
plt.scatter(t,v,s=50,marker='8',
            linewidths=2,
            color='r',edgecolors='b',
            alpha=1)
plt.xlabel('time (s)')
plt.ylabel('velocity (m/s)')
plt.title('velocity of a rocket')
plt.legend(labels=['rocket 1'])
plt.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
plt.minorticks_on()
plt.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
plt.show()

Now let's modify it for object orientated programming.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.scatter(t,v,s=50,marker='8',
                  linewidths=2,
                  color='r',edgecolors='b',alpha=1)
ax1.set_xlabel('time (s)')
ax1.set_ylabel('velocity (m/s)')
ax1.set_title('velocity of a rocket')
ax1.legend(labels=['rocket 1'])
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
fig1.show()

Let's go through the subtle changes. In line 8, the function plt.figure() from the plt module is assigned to an output fig1 and line 9 is modified to call clf() as a method of fig1 opposed to a function of plt. In line 10, the method subplots is assigned from fig1 opposed to using the function subplot from the module plt. There is a slight difference in the input arguments between subplots() and subplot(). In subplots() the keyword nrows and ncols work as keyword arguments (ideally as mentioned earlier this should work also within subplot() and hence be consistent across both). The main difference however is the lack of the keyword argument index which in the case of subplot() is used to select the subplot(). The method subplots() is instead assigned to an output or variable name ax1 and ax1 attributes and methods can be accessed using object orientated programming. In contrast the function subplot() has no output. In line 11 scatter() is then called as a method from the object ax1 opposed to be called as a function from the plt module and is once again assigned to a variable or object name plot1. In object orientated programming it is a common convention to have paired methods, so called "getters and setters" which get and set values respectively. In lines 14, 15 and 16 xlabel(), ylabel() and title() were called as functions from the plt submodule. Instead we use their setters, set_xlabel(), set_ylabel() and set_title(). In lines 17-22 we use the method legend(), minorticks_on() and grid() from the axes ax1 and finally in line 23 we use the method show() from fig1.

This gives us the same figure as shown before. Notice however the presence of fig1, ax1 and plot1 on the variable explorer.

If we type in fig1 followed by a dot . and then tab , we will get a list of the methods and attributes available.

We can have a look at the more commonly useful methods below. We have seen many of these already as functions within the plt module. We then have the methods to change the color of the canvas face and edge as well as the transparency. There are methods to add axes. For subplots there are additional methods to include a super title and for alignment of labels within each subplot. Finally there are methods to change the size of the figure dimensions.

clf()
clear()
show()
imshow()
savefig()
close()
get_alpha()set_alpha()
get_edgecolor()set_edgecolor()
get_facecolor()set_facecolor()
axes()
add_axes()
subplots()
suptitle()
align_labels()
align_xlabels()
align_ylabels()
get_size_inches()set_size_inches()
get_figheight()set_figheight()
get_figwidth()set_figwidth()
get_dpi()set_dpi()

Now if we type in ax1 followed by a dot . and then tab , we will get a list of the methods and attributes available.

Once again we have seen many of these already as functions within the plt module. There are methods to clear the axes, to turn them on or off. Then there are methods to set the axes labels, to invert the axes (highest x-value is at the start and lowest x-value is at the end), to get and set the ticks and to replace the numeric ticks with ticklabels. There are methods to change the facecolor of the grid as well as methods to set the major and minor grid lines and tick parameters. There is a method to turn off axis auto-scaling (which is done by default when new data is added). Then there are methods for a wide variety of plots, we have seen plot and scatter already. There is the attribute spines which can be used to access the axes spines (which will be looked at) and there are methods to create additional axes that are linked in x or y.

clear()
set_axis_off()set_axis_off()
get_title()set_title()
get_xlabel()get_ylabel()set_xlabel()set_ylabel()
get_xlim()get_ylim()set_xlim()set_ylim()
xaxis_inverted()invert_xaxis()yaxis_inverted()inverty_axis()
get_xticks()get_yticks()set_xticks()set_yticks()
get_xticklabels()get_yticklabels()set_xticklabels()set_yticklabels()
get_xscale()get_yscale()set_xscale()set_yscale()
facecolor()fc()
grid()
majorticks_off()majorticks_on()minorticks_off()minorticks_on()
tick_params()
get_autoscale_on()set_autoscale_on()
get_autoscalex_on()get_autoscaley_onset_autoscalex_on()set_autoscaley_on()
axvline()axhline()
plot()
semilogx()
semilogy()
loglog()
step()
scatter()
bar()
hist()
hist2d()
pie()
box()
violinplot()
contour()
contourf()
tricontour()
tricontourf()
stem()
streamplot()
pcolor()
pcolorfast()
pcolormesh()
get_gridspec()set_gridspec()
twinx()twiny()

If we type in plot1 followed by a dot . and then tab , we will get a list of the methods and attributes available.

get_offsets()set_offsets()
get_sizes()set_sizes()
get_lw()get_linewidth()get_linewidths()set_lw()set_linewidth()set_linewidths()
get_ec()get_edgecolor()get_edgecolors()set_ec()set_edgecolor()set_edgecolors()
get_fc()get_facecolor()get_facecolors()set_fc()set_facecolor()set_facecolors()
get_alpha()set_alpha()
get_label()set_label()

Note that all the methods above correspond to each of the positional and keyword arguments below.

plot1=ax1.scatter(t,v,s=150,marker='8',
                  linewidths=2,
                  color='r',edgecolors='b',alpha=1,
                  label='rocket 1')

There are some minor inconsistencies, get_offsets() and set_offsets() differ from the more logical get_data() and set_data() used in other plot types. There are alias for plurals to singular values, for example get_linewidths(), get_lw() are alias for get_linewidth(). There also seems to be a getter and setter missing for the marker type. We can demonstrate using these setters to modify an already created figure (opposed to deleting the figure and recreating it from scratch which we would have to do with the matlab user interface). For some reason, the method set_sizes() cannot accept an integer input that is not enclosed in a list.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.scatter(t,v,s=50,marker='8',
                  linewidths=2,
                  color='r',edgecolors='b',alpha=1,
                  label='rocket 1')
ax1.set_xlabel('time (s)')
ax1.set_ylabel('velocity (m/s)')
ax1.set_title('velocity of a rocket')
ax1.legend(loc='lower left')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
fig1.show()
plot1.set_sizes([150])
plot1.set_linewidth(2.5)
plot1.set_edgecolor('g')
plot1.set_facecolor('c')
plot1.set_alpha(0.8)
plot1.set_label('ROCKET 1')
ax1.legend()

Let's now use subplots and create subplots with 2 rows and 2 columns.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
axes=fig1.subplots(nrows=2,ncols=2)
fig1.show()

Note that the output variable we assign it to is a matrix of axes.

We can access each axes type from it by indexing into this matrix. For example:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
axes=fig1.subplots(nrows=2,ncols=2)
axes[0,0].scatter(t,v,s=150,color='r')
axes[0,1].scatter(t,v,s=150,color='g')
axes[1,0].scatter(t,v,s=150,color='b')
axes[1,1].scatter(t,v,s=150,color='k')
fig1.show()

It is common when assigning the subplots to assign them to an output matrix of variable names for example:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
((ax1,ax2),(ax3,ax4))=fig1.subplots(nrows=2,ncols=2)
ax1.scatter(t,v,s=150,color='r')
ax2.scatter(t,v,s=150,color='g')
ax3.scatter(t,v,s=150,color='b')
ax4.scatter(t,v,s=150,color='k')
fig1.show()

Now let's return to our line plot using the matlab notation.

We want to switch this to object orientated programming notation. Most of the changes are identical to the changes we made when looking at the scatter plot however in line 11 we assign each of the three lines to a new variable name within a tuple.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
(plot1,plot2,plot3)=ax1.plot(t,v,'r:8',
                             t,2*v,'g--*',
                             t,3*v,'c-.^',
                             linewidth=3,markersize=15)
ax1.set_xlabel('time (s)')
ax1.set_ylabel('velocity (m/s)')
ax1.set_title('velocity of a rocket')
ax1.legend(labels=['rocket 1','rocket 2','rocket 3'])
fig1.show()

As we seen above, we have the ability to feed multiple x,y datasets to the function plot and when doing so we can assign the output to a tuple of variable names corresponding to each plot. As a consequence of this behavior, we have to supply a tuple even if we only have a single plot. Recall that (scalar) is taken as scalar because () are taken as parenthesis. In order to make a tuple of 1 element we need to add a comma at the end (scalar,).

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
(plot1,)=ax1.plot(t,v,'r:8',linewidth=3,markersize=15)
ax1.set_xlabel('time (s)')
ax1.set_ylabel('velocity (m/s)')
ax1.set_title('velocity of a rocket')
ax1.legend(labels=['rocket 1','rocket 2','rocket 3'])
fig1.show()

Failure to supply a tuple here will create an output that is a element list. One will have to index into this list to access the plot and the plot attributes and methods. We can open the list in variable explorer and then open the object nested in the list.

Select the blue button on the list to show all callable attributes and methods. We can have a look through the list of attributes and methods. The attributes and methods beginning with an underscore are hidden attributes and methods and you can scroll through these.

In theory we should be able to type in plot1[0] followed by a dot . and tab to view the callable methods and attributes but this doesn't seem to work correctly.

This will work as intended if we either directly assign the output of the method plot to a tuple as before or if we unpack the 0th element of a list to another variable name.

get_data()set_data()
get_color()get_c()set_color()set_c()
get_linestyle()get_ls()set_linestyle()set_ls()
get_marker()set_marker()
get_linewidth()get_lw()set_linewidth()set_lw()
get_markersize()get_ms()set_markersize()set_ms()
get_markeredgewidth()get_mew()set_markeredgewidth()set_mew()
get_fillstyle()set_fillstyle()
get_markeredgecolor()get_mec()set_markeredgecolor()set_mec()
get_markerfacecolor()get_mfc()set_markerfacecolor()set_mfc()
get_markerfacecoloralt()get_mfcalt()set_markerfacecoloralt()set_mfcalt()
get_label()set_label()

Note once again that all the methods above correspond to each of the positional and keyword arguments below.

plt.plot(t,v,color='b',linestyle='-.',marker='p',
         linewidth=3.5,markersize=20,markeredgewidth=3,
         fillstyle='left',markeredgecolor='g',
         markerfacecolor='m',markerfacecoloralt='c',
         label='Rocket')

We can once again use the methods above to post-modify the figure.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
(plot1,)=ax1.plot(t,v,'r:8')
ax1.set_xlabel('time (s)')
ax1.set_ylabel('velocity (m/s)')
ax1.set_title('velocity of a rocket')
ax1.legend(labels=['rocket 1'])
fig1.show()
plot1.set_color('b')
plot1.set_linestyle('-.')
plot1.set_marker('p')
plot1.set_linewidth('3.5')
plot1.set_markersize(20)
plot1.set_markeredgewidth(3)
plot1.set_fillstyle('left')
plot1.set_markeredgecolor('m')
plot1.set_markerfacecoloralt('c')
plot1.set_label('ROCKET 1')
ax1.legend()

tick parameters and spines

It is common convention for the y-axis to be on the right hand side and the x-axis to be at the bottom. We can get more fine control of an axis by selecting the axis as an attribute of the axes. Supposing we have the basic plot:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
f=np.array([100,99,98,97,94,92])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
(plot1,)=ax1.plot(t,v,'r:8')
ax1.set_xlabel('time (s)')
ax1.set_ylabel('velocity (m/s)')
ax1.set_title('velocity of a rocket')
ax1.legend(labels=['rocket 1'])
fig1.show()

We can modify the tick parameters using the method tick_params. Let's call it with open parenthesis:

Here we can get more details about the keyword input arguments. In this case we will change the x-axis to have ticks at both the top and bottom that are facing in that are red colored that are of length 10 and width 3. The label color can also be assigned to blue and increased to fontsize 12. Similar modifications can be made to the y-axis with different colors.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
f=np.array([100,99,98,97,94,92])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
(plot1,)=ax1.plot(t,v,'r:8')
ax1.set_xlabel('time (s)',color='y',fontsize=12)
ax1.tick_params(axis='x',which='major',
                direction='in',length=10,width=3,
                color='r',labelcolor='b',
                labelsize=12,top=True,bottom=True,
                labeltop=True,labelbottom=True)
ax1.set_ylabel('velocity (m/s)',color='g',fontsize=12)
ax1.tick_params(axis='y',which='major',
                direction='in',length=10,width=3,
                color='c',labelcolor='m',
                labelsize=12,left=True,right=True,
                labelleft=True,labelright=True)
ax1.set_title('velocity of a rocket',color='k',
              fontsize=18)
ax1.legend(labels=['rocket 1'])
fig1.show()

The set_xlabel() and set_ylabel() methods are far more limited than tick_params only allowing one to change the label text, font size and color. For some reson the position isn't added as a keyword argument. We can however access each axis as an attribute of the axes ax1.xaxis. We can then see what available methods we have by using a dot . and tab :

There are many methods additional methods here, however the one we are interested in is the:

get_label_position()set_label_position()

We can move the xlabel to the top and the ylabel to the right:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
f=np.array([100,99,98,97,94,92])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
(plot1,)=ax1.plot(t,v,'r:8')
ax1.set_xlabel('time (s)',color='y',fontsize=12)
ax1.tick_params(axis='x',which='major',
                direction='in',length=10,width=3,
                color='r',labelcolor='b',
                labelsize=12,top=True,bottom=True,
                labeltop=True,labelbottom=True)
ax1.xaxis.set_label_position('top')
ax1.set_ylabel('velocity (m/s)',color='g',fontsize=12)
ax1.tick_params(axis='y',which='major',
                direction='in',length=10,width=3,
                color='c',labelcolor='m',
                labelsize=12,left=True,right=True,
                labelleft=True,labelright=True)
ax1.yaxis.set_label_position('right')
ax1.set_title('velocity of a rocket',color='k',
              fontsize=18)
ax1.legend(labels=['rocket 1'])
fig1.show()

Notice that there is a wider gap to the left hand side and bottom where the labels used to be and the labels are crushed in their new positions. To resolve this we can select a tight layout.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
f=np.array([100,99,98,97,94,92])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
(plot1,)=ax1.plot(t,v,'r:8')
ax1.set_xlabel('time (s)',color='y',fontsize=12)
ax1.tick_params(axis='x',which='major',
                direction='in',length=10,width=3,
                color='r',labelcolor='b',
                labelsize=12,top=True,bottom=True,
                labeltop=True,labelbottom=True)
ax1.xaxis.set_label_position('top')
ax1.set_ylabel('velocity (m/s)',color='g',fontsize=12)
ax1.tick_params(axis='y',which='major',
                direction='in',length=10,width=3,
                color='c',labelcolor='m',
                labelsize=12,left=True,right=True,
                labelleft=True,labelright=True)
ax1.yaxis.set_label_position('right')
ax1.set_title('velocity of a rocket',color='k',
              fontsize=18)
ax1.legend(labels=['rocket 1'])
fig1.show()
fig1.tight_layout()

It is also possible to change the properties of the four spines using the attribute spines. This gives a dictionary with a key corresponding to each spine position.

s=ax1.spines

It is recommended to unpack these individually:

bottom=ax1.spines['bottom']
left=ax1.spines['left']
right=ax1.spines['right']
top=ax1.spines['top']

This will enable you to easily access the methods by typing in the unpacked spine followed by a dot . and then tab :

The most useful are:

get_edgecolor()	get_ec()	set_edgecolor()	set_ec()
get_linestyle()	get_ls()	set_linestyle()	set_ls()
get_linewidth()	get_lw()	set_linewidth()	set_lw()
get_visible()		set_visible()	

For example, supposing we want to make the bottom and left spines invisible and want the top and right spines to be green dotted lines of line width 3:

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
f=np.array([100,99,98,97,94,92])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
(plot1,)=ax1.plot(t,v,'r:8')
ax1.set_xlabel('time (s)',color='y',fontsize=12)
ax1.tick_params(axis='x',which='major',
                direction='in',length=10,width=3,
                color='r',labelcolor='b',
                labelsize=12,top=True,bottom=True,
                labeltop=True,labelbottom=True)
ax1.xaxis.set_label_position('top')
ax1.set_ylabel('velocity (m/s)',color='g',fontsize=12)
ax1.tick_params(axis='y',which='major',
                direction='in',length=10,width=3,
                color='c',labelcolor='m',
                labelsize=12,left=True,right=True,
                labelleft=True,labelright=True)
ax1.yaxis.set_label_position('right')
ax1.set_title('velocity of a rocket',color='k',
              fontsize=18)
ax1.legend(labels=['rocket 1'])
fig1.show()
fig1.tight_layout()
bottom=ax1.spines['bottom']
left=ax1.spines['left']
right=ax1.spines['right']
top=ax1.spines['top']
bottom.set_visible(False)
left.set_visible(False)
top.set_edgecolor('g')
top.set_linewidth(3)
top.set_linestyle(':')
right.set_edgecolor('g')
right.set_linewidth(3)
right.set_linestyle(':')

bar and barh

let's create a dataframe that contains the British quarterly rainfall over the last 4 years.

# perquisites
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)

We want to create a bar graph of the 2019 values. First let's get more details about the input arguments by typing in the method ax1.bar with open parenthesis:

ax1.bar(

To get more details in the console we can type in:

? ax1.bar

This gives us the full documentation for the method bar() which unsurprisingly is similar to the other plot methods plot() and scatter().

We can supply the data from the data dataframe by selecting each column as an attribute and then we can change the bar properties.

# perquisites
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.bar(data.index,data.y2019,
              color=[0/255,176/255,80/255,0.8],
              width=0.6,hatch='O',edgecolor='k',
              linewidth=1.2,label='2019')
ax1.set_xlabel('quarter')
ax1.set_ylabel('rainfall (mm)')
ax1.set_title('British rainfall')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
ax1.legend()
plt.show()

The keyword argument width is a float between 0 and 1. If the value is 1 the bars will touch and there will be no white spacing between the bars, if it is 0 there will be only be white spacing.

We can change this to a horizontal bar graph by using barh in place of bar and replacing the keyword argument width by height. All other code remains the same.

# perquisites
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
ax1.barh(data.index,data.y2019,
         color=[0/255,176/255,80/255,0.8],
         height=0.6,hatch='O',edgecolor='k',
         linewidth=1.2,label='2019')
ax1.set_xlabel('quarter')
ax1.set_ylabel('rainfall (mm)')
ax1.set_title('British rainfall')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
ax1.legend()
plt.show()

We can create a stacked bar graph of the past two years by using the keyword input argument bottom and setting it to the 2019 data. For clarity we can also use the method set_ylim() and instead of inputting a vector with a bottom and top bound, we can use the keyword argument bottom to fix the bottom bound to 0 while retaining an automatic top bound. To see what's going on we can comment out the plot of the 2019 data (by selecting lines 14-17 and pressing [Ctrl] +[ 1 ]).

# perquisites
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
# plot1=ax1.bar(data.index,data.y2019,
#               color=[0/255,176/255,80/255,0.8],
#               width=0.6,hatch='O',edgecolor='k',
#               linewidth=1.2,label='2019')
plot2=ax1.bar(data.index,data.y2018,
              color=[0/255,112/255,192/255,0.8],
              width=0.6,hatch='x',edgecolor='k',
              linewidth=1.2,label='2018',
              bottom=data.y2019)
ax1.set_xlabel('quarter')
ax1.set_ylabel('rainfall (mm)')
ax1.set_ylim(bottom=0)
ax1.set_title('British rainfall')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
ax1.legend()
plt.show()

If we uncomment out the 2019 data (once again by selecting lines 14-17 and pressing [Ctrl] +[ 1 ]).

We can now add the other two years.

# perquisites
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.bar(data.index,data.y2019,
              color=[0/255,176/255,80/255,0.8],
              width=0.6,hatch='O',edgecolor='k',
              linewidth=1.2,label='2019')
plot2=ax1.bar(data.index,data.y2018,
              color=[0/255,112/255,192/255,0.8],
              width=0.6,hatch='x',edgecolor='k',
              linewidth=1.2,label='2018',
              bottom=data.y2019)
plot3=ax1.bar(data.index,data.y2017,
              color=[255/255,0/255,0/255,0.8],
              width=0.6,hatch='-',edgecolor='k',
              linewidth=1.2,label='2017',
              bottom=data.y2019+data.y2018)
plot4=ax1.bar(data.index,data.y2016,
              color=[255/255,192/255,0/255,0.8],
              width=0.6,hatch='//',edgecolor='k',
              linewidth=1.2,label='2016',
              bottom=data.y2019+data.y2018+data.y2017)
ax1.set_xlabel('quarter')
ax1.set_ylabel('rainfall (mm)')
ax1.set_ylim(bottom=0)
ax1.set_title('British rainfall')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
ax1.legend()
plt.show()

It is slightly harder to create a side by side bar graph as the only keyword argument for stacking is bottom. However recall that the width is a float that specifies the thickness of the bars, we can set the width to be 1/4 because there are four bars. In order to apply this as an offset we will need numeric x-data so we can create a vector np.array([0,1,2,3]).

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
x=np.array([0,1,2,3])
bw=1/4
plot1=ax1.bar(x,data.y2019,
              color=[0/255,176/255,80/255,0.8],
              width=bw,hatch='O',edgecolor='k',
              linewidth=1.2,label='2019')
plot2=ax1.bar(x+bw,data.y2018,
              color=[0/255,112/255,192/255,0.8],
              width=bw,hatch='x',edgecolor='k',
              linewidth=1.2,label='2018')
plot3=ax1.bar(x+2*bw,data.y2017,
              color=[255/255,0/255,0/255,0.8],
              width=bw,hatch='-',edgecolor='k',
              linewidth=1.2,label='2017')
plot4=ax1.bar(x+3*bw,data.y2016,
              color=[255/255,192/255,0/255,0.8],
              width=bw,hatch='//',edgecolor='k',
              linewidth=1.2,label='2016')
ax1.set_xlabel('quarter')
ax1.set_ylabel('rainfall (mm)')
ax1.set_ylim(bottom=0)
ax1.set_title('British rainfall')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
ax1.legend()
plt.show()

We can change lower the side of the width to 1/5 give additional white space. The axes methods set_xlim(), set_xticks() and set_xticklabels() can then be used to set the new x ticks and then replace them with text labels.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
x=np.array([0,1,2,3])
bw=1/5
plot1=ax1.bar(x,data.y2019,
              color=[0/255,176/255,80/255,0.8],
              width=bw,hatch='O',edgecolor='k',
              linewidth=1.2,label='2019')
plot2=ax1.bar(x+bw,data.y2018,
              color=[0/255,112/255,192/255,0.8],
              width=bw,hatch='x',edgecolor='k',
              linewidth=1.2,label='2018')
plot3=ax1.bar(x+2*bw,data.y2017,
              color=[255/255,0/255,0/255,0.8],
              width=bw,hatch='-',edgecolor='k',
              linewidth=1.2,label='2017')
plot4=ax1.bar(x+3*bw,data.y2016,
              color=[255/255,192/255,0/255,0.8],
              width=bw,hatch='//',edgecolor='k',
              linewidth=1.2,label='2016')
ax1.set_xlabel('quarter')
ax1.set_ylabel('rainfall (mm)')
ax1.set_ylim(bottom=0)
ax1.set_title('British rainfall')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
ax1.legend()
plt.show()
ax1.set_xlim([-0.5,4])
ax1.set_xticks([0.3,1.3,2.3,3.3])
ax1.set_xticklabels(['win','spr','sum','aut'],rotation=45)
fig1.tight_layout()

pie

We can move onto another plot method pie(), which once again has some commonalities with plot(), scatter(), bar() and barh(). Once again we can type in the method with open parenthesis to get more details about the input arguments:

ax1.pie(

To get more information we can type in the console:

? plt.pie

We see a number of input arguments, the data x is the only positional argument and all the rest are keyword arguments. Let's look at the 2019 rainfall data from earlier and just provide the labels. We will also provide a vectors of normalised floats to explode autumn by 0.1 (0 not exploded, 1 all the way out and off the page) to place a slight enthesis on this wedge as it is the quarter with the most rain.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.pie(data.y2019,
              labels=['win','spr','sum','aut'],
              explode=[0,0,0,0.1])
ax1.set_title('British rainfall 2019')
plt.show()
fig1.tight_layout()

The pie chart is a good example of the advantages of object orientated programming. We can see that we get a tuple and element 0 is a list of the wedges while element 1 is a list of the text corresponding to each wedge.

Let's first index into the tuple to get to the list of wedges and then select the 0th wedge which will be the winter wedge and assign it to a variable name:

winwed=plot1[0][0]

Once we have assigned it to a variable name we can type in the variable name followed by a dot . and tab :

Once again we can see a list of all the attributes and methods for the wedge. The most commonly used ones are:

get_edgecolor()get_ec()set_edgecolor()set_ec()
get_facecolor()get_fc()set_facecolor()set_fc()
get_hatch()set_hatch()
get_linestyle()get_ls()set_linestyle()set_ls()
get_linewidth()get_lw()set_linewidth()set_lw()
get_visible()set_visible()
get_label()set_label()
get_radius()set_radius()
get_width()set_width()

Let's apply custom formatting to each wedge:

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.pie(data.y2019,
              labels=['win','spr','sum','aut'],
              explode=[0,0,0,0.1])
ax1.set_title('British rainfall 2019')
plt.show()
fig1.tight_layout()
winwed=plot1[0][0]
winwed.set_edgecolor('k')
winwed.set_facecolor([0/255,176/255,80/255,0.8])
winwed.set_hatch('O')
winwed.set_linestyle('-')
winwed.set_linewidth(3)
sprwed=plot1[0][1]
sprwed.set_edgecolor('k')
sprwed.set_facecolor([0/255,112/255,192/255,0.8])
sprwed.set_hatch('x')
sprwed.set_linestyle('-')
sprwed.set_linewidth(3)
sumwed=plot1[0][2]
sumwed.set_edgecolor('k')
sumwed.set_facecolor([255/255,0/255,0/255,0.8])
sumwed.set_hatch('-')
sumwed.set_linestyle('-')
sumwed.set_linewidth(3)
autwed=plot1[0][3]
autwed.set_edgecolor('k')
autwed.set_facecolor([255/255,192/255,0/255,0.8])
autwed.set_hatch('//')
autwed.set_linestyle(':')
autwed.set_linewidth(3)

We can use the radius and the width which are normalized floats to make hollow pie charts otherwise known as donut charts. We can also use this to nest two pie charts together. The radius is the length from the center of the pie chart (1 is the edge, 0 is the center), the width starts from the radius and heads towards the center. We can demonstrate how this works by making all the pie segments an equal width but differing radius.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
data=pd.DataFrame([[506.7,247.1,322.2,254.6],
                   [222.2,188.6,235.6,241.5],
                   [260.2,314.7,172.5,333.0],
                   [248.7,327.4,328.5,378.9]],
                  columns=['y2016','y2017','y2018','y2019'],
                  index=['win','spr','sum','aut'])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.pie(data.y2019,
              labels=['win','spr','sum','aut'],
              explode=[0,0,0,0])
ax1.set_title('British rainfall 2019')
plt.show()
fig1.tight_layout()
winwed=plot1[0][0]
winwed.set_edgecolor('k')
winwed.set_facecolor([0/255,176/255,80/255,0.8])
winwed.set_hatch('O')
winwed.set_linestyle('-')
winwed.set_linewidth(3)
winwed.set_radius(1)
winwed.set_width(0.25)
sprwed=plot1[0][1]
sprwed.set_edgecolor('k')
sprwed.set_facecolor([0/255,112/255,192/255,0.8])
sprwed.set_hatch('x')
sprwed.set_linestyle('-')
sprwed.set_linewidth(3)
sprwed.set_radius(0.75)
sprwed.set_width(0.25)
sumwed=plot1[0][2]
sumwed.set_edgecolor('k')
sumwed.set_facecolor([255/255,0/255,0/255,0.8])
sumwed.set_hatch('-')
sumwed.set_linestyle('-')
sumwed.set_linewidth(3)
sumwed.set_radius(0.5)
sumwed.set_width(0.25)
autwed=plot1[0][3]
autwed.set_edgecolor('k')
autwed.set_facecolor([255/255,192/255,0/255,0.8])
autwed.set_hatch('//')
autwed.set_linestyle(':')
autwed.set_linewidth(3)
autwed.set_radius(0.25)
autwed.set_width(0.25)

linked x or y axes

Supposing we have two related datasets, for example the fuel left in a rocket's fuel tank with respect to its projectile, we could plot these as subplots as we have seen earlier or alternatively in some cases it is appropriate to them on the same chart. In this case they would both have the common x-values of t but they would have differing y-values of v and f respectively. We can therefore create a plot with linked x-axes but separate y-axes which we can plot on the left and right respectively. To do this we use the axes method twinx() or twiny() for the shared x or shared y axes respectively. In this case we will plot a line plot and then a bar graph. The bars will be made transparent so the line plot can be observed. In this type of chart it is appropriate to use color to distinguish what data set belongs to what axis.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Create Data
t=np.array([0,10,15,20,22.5,30])
v=np.array([0,227.04,362.78,517.35,602.97,901.67])
f=np.array([100,50,25,20,15,10])
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
(plot1,)=ax1.plot(t,v,'r:8')
ax1.set_xlabel('time (s)')
ax1.set_ylabel('velocity (m/s)',color='r')
ax1.set_title('rocket projectile')
ax1.legend(labels=['velocity'],loc='upper left')
ax1.tick_params(axis='y',color='r',labelcolor='r')
ax2=ax1.twinx()
plot2=ax2.bar(t,f,
              color=[0/255,0/255,255/255,0.5],
              width=2,hatch='O',edgecolor='k',
              linewidth=1.2,label='fuel')
ax2.set_ylabel('fuel remaining (%)',color='b')
ax2.tick_params(axis='y',color='b',labelcolor='b')
ax2.yaxis.set_label_position('right')
ax2.legend(labels=['fuel'],loc='upper right')
fig1.show()

meshgrid

So far we have graphed only 2 dimensional data, we can now consider how we visualize 3 dimensional data. A matrix can be considered to be 3 dimensional data. If we create a very basic matrix and open it up in the variable explorer:

# perquisites
import numpy as np
import matplotlib.pyplot as plt
# Generate Data
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])

We see that each element in the matrix (z value) has a row (y value), column (x value). We can also see that the variable explorer in Spyder has automatically applied a color map to each element so we can visualize the data.

Assume we've got some data z then each element in z has a row (x-value) and a column (y-value). If these are undefined we will use the row indexes in this case [0,1,2,3] and column indexes in this case [0,1,2,3,4].

It is possible to plot z using these indexes however in some applications it may be more insightful to plot z with respect to more meaningful x and y values.

For convenience, assume x and y start at position 1 mm and 5 mm respectively on a translation stage and we perform a measurement every 1 mm on a detector. Then we can explicitly represent the x-values and the y-values as a row vector and column vector respectively.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,x.size))
y=np.array([1,2,3,4])
y=np.reshape(y,(y.size,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])

We can open x, y and z in the variable explorer and overlay them so we can see what the x and y values are for each z value. For example z=0 at x=5 and y=1.

Some of the plotting functions don't accept x as a row vector, y as a column vector and z as a matrix but instead require x, y and z data of equal dimensions. To get these we can use the meshgrid() function from the numpy library.

# Perquisites
import numpy as np
import matplotlib.pyplot as plt
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)

Now we can look at x, y and z. We see that all three are matrices of the same dimensions:

Finally we can flatten these to get three columns:

import numpy as np
import matplotlib.pyplot as plt
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()

contour and contourf plot

We can now look at plotting a contour. Once again we can type in the method using open parenthesis to see the input arguments.

ax1.contour(

And for more details we can type the following in the console.

? ax1.contour

Let's create a contour plot of our x,y and z data.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
# Generate Data
import numpy as np
import matplotlib.pyplot as plt
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.contour(x,y,z)
ax1.set_xlabel('x (µm)')
ax1.set_ylabel('y (µm)')
fig1.show()

In a contour plot, the third axes the z-axis is referred to as the color axis caxis or as it is represented by color. It is worthwhile representing the values of the z-data via a colorbar, we can do this via the method fig1.colorbar(). Unfortunately neither the matlab like user interface or object orientated programming user interface is fully polished especially when it comes to the colorbar. Note that colorbar() is a method of the figure fig1 and not a method of ax1 or plot1. We need to define the plot as a positional input argument.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
# Generate Data
import numpy as np
import matplotlib.pyplot as plt
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.contour(x,y,z)
ax1.set_xlabel('x (µm)')
ax1.set_ylabel('y (µm)')
cbar1=fig1.colorbar(plot1)
fig1.show()

We can look at filled contour plot by using the method ax1.contourf() instead of ax1.contour().

# perquisites
import numpy as np
import matplotlib.pyplot as plt
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
plot1=ax1.contourf(x,y,z)
ax1.set_xlabel('x (µm)')
ax1.set_ylabel('y (µm)')
cbar1=fig1.colorbar(plot1)
fig1.show()

We can type in plot1 followed by a dot . and tab to access the methods and attributes. The most common are:

get_cmap()set_cmap()
get_clim()set_clim()

The color map uses pseudo-colors in order to clearly distinguish features, the default color is 'viridis', however other popular maps are available such as 'jet', 'hot' and 'bone' and the reversed variants 'viridis_r', 'jet_r', 'hot_r' and 'bone_r'. We can change the colormap used with the method plot1.set_cmap() where the input argument is the string.

We can set the upper and lower limits using the method:

plot1.get_clim()
plot1.set_clim((0,100))

We could for example set it to the upper bound 100, assuming that's the regime of z-values we would expect for a signal. Rescaling the color map limits to these values makes the contourf look completely blue, but this could be expected for a low level of noise counts.

Unfortunately at present this doesn't modify the lower and upper bound of the colorbar (as seen the lower bound is still 0.0 and the upper bound is still 3.2). The entire colorbar is colored in blue because the plot1 climits have been changed. We can type in the variable name we assigned to the colorbar cbar1 followed by a dot . and then tab :

There are a number of cbar1 methods and attributes:

get_ticks()set_ticks()
get_ticklabels()set_ticklabels()
get_cbar() set_cbar()
vmin
vmax
boundaries
values
update_normal()
update_ticks()
update_bruteforce()

The method set_clim() does not amend the limits on the colorbar. It appears to just duplicate plot1.set_clim() and is being phased out. My guess is this function should either be updated to change the plot as well as the colorbar or a separate method should be made available to change the lower and upper bound of the colorbar.

cbar1.get_clim()
cbar1.set_clim((0,100))

The attributes vmin and vmax appear to give the upper and lower bound of the colorbar and while they can be reassigned it doesn't change the colorbar.

There are additional attributes cbar1.boundaries and cbar1.values and these give arrays corresponding to the lines however again cannot be updated.

The method cbar1.get_ticks() and cbar1.set_ticks() works and can be used to modify the ticks and although the ticks change the colorbar lower and upper bound itself doesn't update.

cbar1.get_ticks()
cbar1.set_ticks(np.array([0,1.5,2,3,5,10]))

The fact that there are attributes that can be accessed and changed without appropriate methods, in addition to methods that are obsolete and methods with names such as update_bruteforce() suggests that accessing and changing the colorbar properties is far from polished and hopefully will be addressed in new versions of matplotlib. Ideally there should be a method cbar1.get_boundaries() and cbar1.set_boundaries() which would accept lower and upper boundaries acting quite similar to ax1.ylim().

In the meantime it is recommended to set the levels and colormap when creating the contour plot (line 23 and 24) and set the orientation of the colorbar when creating it (line 29), in this case we will set the keyword argument orientation to 'horizontal'. Another limitation is that we need to index into the attribute cbar1.ax of the colorbar to use the method cbar1.ax.set_title() opposed to having a method set_clabel() to label the caxis directly. Since we are placing the colorbar horizontally, it is recommended to use the keyword argument figsize within the function plt.figure() to make the plot taller (line 21) and to use the method fig1.tight_layout() (line 31).

# perquisites
import numpy as np
import matplotlib.pyplot as plt
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
fig1=plt.figure(num=1,figsize=(6,6.25))
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
cmap_levels=np.arange(start=0,stop=100,step=1)
plot1=ax1.contourf(x,y,z,levels=cmap_levels,cmap='jet')
ax1.set_xlabel('x (µm)')
ax1.set_ylabel('y (µm)')
ax1.set_title('cmap jet')
cbar1=fig1.colorbar(plot1,orientation='horizontal')
cbar1.ax.set_title('z (counts)')
fig1.tight_layout()
fig1.show()

contour3d, contourf3d, plot_surface and plot_wireframe

Unfortunately 3D plots are natively included with the module pyplot. In order to get 3D plots, an additional perquisite is required:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d

To get a 3d axes we need to use the method fig1.add_subplot() which is an intermediate between the methods fig1.subplots() which has an output but no index and fig1.subplot() which has no output but an index. The positional arguments are the number of rows, number of columns and index to be added to a subplot. In this case we will set all these to 1.

ax1=fig1.add_subplot(1,1,1)

Recall that we can look up available attributes and methods by typing in the ax1 name followed by a dot . and then tab :

The method add_subplot() has an additional keyword argument that subplot() and subplots() don't have, projection. We can assign projection='3d' (lower case d) which allows for additional 3d plots. Once again we can look up available attributes and methods by typing in the ax1 name followed by a dot . and then tab and we will see additional contour3D() and contourf3D() (upper case d) methods:

We can use the method contour3D() on the same data as earlier:

# perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.add_subplot(1,1,1,projection='3d')
plot1=ax1.contour3D(x,y,z)
ax1.set_xlabel('x (µm)')
ax1.set_ylabel('y (µm)')
ax1.set_zlabel('z (counts)')
fig1.show()

Note you can ignore the warning at line 4 that mpl_toolkits.mplot.3d.axes3d is imported and is unused because it is untrue. The assignment of the keyword argument projection='3d' in line 24 uses this toolkit.

You'll notice that I have used the object orientated programming interface throughout here for consistency. Other guides use a hybrid matlab and object orientated programming interface, merely because the matlab user interface doesn't work correctly as 3D plots aren't incorporated natively into pyplot. We can see this if we try to use a function plt.zlabel() which doesn't exist within pyplot() and gives AttributeError. This makes 3D plotting a bit more cumbersome in matplotlib than in matlab.

We can change the method from ax1.contour3D() to ax1.contourf3D().

# perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.add_subplot(1,1,1,projection='3d')
plot1=ax1.contourf3D(x,y,z)
ax1.set_xlabel('x (µm)')
ax1.set_ylabel('y (µm)')
ax1.set_zlabel('z (counts)')
fig1.show()

This gives contour data filled in the x and y plane but it is not filled in on the z plane.

To get a solid structure in all planes, we can change the method to ax1.plot_surface().

# perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.add_subplot(1,1,1,projection='3d')
plot1=ax1.plot_surface(x,y,z)
ax1.set_xlabel('x (µm)')
ax1.set_ylabel('y (µm)')
ax1.set_zlabel('z (counts)')
fig1.show()

We can also change the method to ax1.plot_wireframe().

# perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.add_subplot(1,1,1,projection='3d')
plot1=ax1.plot_wireframe(x,y,z)
ax1.set_xlabel('x (µm)')
ax1.set_ylabel('y (µm)')
ax1.set_zlabel('z (counts)')
fig1.show()

By default wireframes and surfaces are monochrome and the color can be changed using a keyword argument color in the methods ax1.plot_surface() and ax1.plot_wireframe(). Contours will use a color map and this can be changed the keyword argument cmap in the methods ax1.contour3D() and ax1.contourf3D(). The list of available colormaps and the procedure to add a colorbar is identical to their 2D counterparts. It is also possible to use the keyword argument cmap in the methods ax1.plot_surface() and ax1.plot_wireframe() to get a color map opposed to a monochrome color.

We can click on a 3D plot with a mouse and the x,y,z position will display to the bottom left hand side.

The figure is displayed with a default azimuth of -60 degrees and elevation of 30 degrees. If however left click on the figure (and hold down the left click), then we can rotate the figure using the mouse shown, the azimuth and elevation are shown on the bottom left while we are holding down the left click.

We can think of the azim as the angle rotated around the z-axis and the elev as the angle tilted with the object coming out of the page.

Both ax1.azim and ax1.elev are attributes of ax1 (unfortunately there is no getter and setter method for these).

ax1.elev
ax1.azim

Once we move the the figure, we can re-examine these attributes.

Although we can reassign them in the console:

ax1.elev=45
ax1.azim=45

The figure does not update, this is likely as we are directly accessing attributes and because we don't have a getter and setter which changes the attribute and then updates the figure. We can however change these attributes within our code before we create the plot to get an updated figure.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.add_subplot(1,1,1,projection='3d')
ax1.azim=45
ax1.elev=45
plot1=ax1.plot_wireframe(x,y,z,color='r')
ax1.set_xlabel('x (µm)')
ax1.set_ylabel('y (µm)')
ax1.set_zlabel('z (counts)')
fig1.show()

Instead of reassigning the attributes of ax1.elev and ax1.axin there is a method ax1.view_init() which has two input arguments, the elev and axim respectively. This prepares the axes for a new plot with the new axim and elev values. This can be typed into the console however we don't see any changes until a the method creates a fresh instance of ax1.plot_wireframe() that reassigned to the name plot1.

ax1.view_init(45,45)
plot1=ax1.plot_wireframe(x,y,z,color='r')

animated 3d rotation plot

The matplotlib library is very clunky when it comes to 3D plots compared to commercial products like matrix laboratory. This is because as mentioned 3D plots are not incorporated into pyplot and as we have seen the methods available to to rotate the axes require, the axes to be reinitialized and then for the plot to be remade which is quite inefficient. Never the less we can use the above alongside the function plt.pause() which has an input of time in seconds and the method fig1.savefig() in a very basic for loop to create a crude animated plot. When saving we can use the function str() to convert the loop variable to a string so we can concatenate it in the figures file name. In this case we can make a crude animation showing the plot by incrementing the axim every 15 degrees updating every 1 second until a full rotation is completed.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
# Generate Data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
             [1,2,3,2,1],
             [1,2,3,2,1],
             [0,1,1,1,0]])
# Use MeshGrid to create x and y of equal dimensions to z
(x,y)=np.meshgrid(x,y)
# Create a matrix where cols=x,y,z
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
# Create Figure
every15=np.arange(start=0,stop=375,step=15)
fig1=plt.figure(1)
fig1.show()
for i in every15:
    print(i)
    fig1.clf()
    ax1=fig1.add_subplot(1,1,1,projection='3d')
    ax1.view_init(45,i)
    plot1=ax1.plot_wireframe(x,y,z,color='r')
    ax1.set_xlabel('x (µm)')
    ax1.set_ylabel('y (µm)')
    ax1.set_zlabel('z (counts)')
    plt.pause(1)
    fig1.savefig('3dfig'+str(i)+'.png')

should we want this to continue this loop forever we could nest this in a while True: loop and remove the method plt1.savefig().

animated plot and scatter

Let's have a look at creating an animated line plot. For this we need another module of the matplotlib library animation which is imported using the four letter abbreviation anim

import matplotlib.animation as anim

We will create a vector of xdata and ydata and then create a plot. In this case we will use the methods of the axes ax1.set_xlim() and ax1.set_ylim() to set the upper and lower bounds of the axes using the 0th and last values of the vectors xdata and ydata respectively (line 16 and 17) and we will create a plot of empty lists (line 25).

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# Generate Data
xdata=np.arange(start=0,stop=11,step=1)
ydata=np.arange(start=0,stop=11,step=1)
# Time Interval
delay=1
# Create Figure with Axes
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_xlim((xdata[0],xdata[-1]))
ax1.set_ylim((ydata[0],ydata[-1]))
ax1.set_title('Animated Line Plot')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
# Create Plot with No Data
(plot1,)=ax1.plot([],[],lw=3,color='r',
                 label='animated line')
ax1.legend(loc='upper left')
fig1.show()

This gives a blank plot. We can then use the method plot1.set_data() to set the data to include up to (but not including the 2nd data point, which gives us the 0th and 1st data points which we can use to draw a line).

plot1.set_data(xdata[:2],ydata[:2])

We can then use this in a for loop alongside the function plt.pause() and the method fig1.savefig() to get our animated plot.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# Generate Data
xdata=np.arange(start=0,stop=11,step=1)
ydata=np.arange(start=0,stop=11,step=1)
# Time Interval
delay=1
# Create Figure with Axes
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_xlim((xdata[0],xdata[-1]))
ax1.set_ylim((ydata[0],ydata[-1]))
ax1.set_title('Animated Line Plot')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
# Create Plot with No Data
(plot1,)=ax1.plot([],[],lw=3,color='r',
                 label='animated line')
ax1.legend(loc='upper left')
fig1.show()
for i in range(len(xdata)+1):
    print(i)
    plot1.set_data(xdata[:i],ydata[:i])
    plt.pause(1)
    fig1.savefig('frame'+str(i)+'.png')

Note the requirement to use the len(xdata)+1 in the for loop because we are indexing to a value but not including a value in line 31. This gives the following figures:

Which create an animated plot.

We could remove the method fig1.savefig() and nest the for loop in a while True loop to have it loop forever.

We can instead write two functions. One function to initialise the plot with no data which we will call init(), this has no data and hence requires no input arguments and the second function animate() which requires the index of the data to set up to (but not including) and hence has a single input argument which we will assign i. We can call the function init(), without any input arguments and this time we can call the function animate() within the for loop using the loop to provide the index in this case the frame f as an input argument.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# Generate Data
xdata=np.arange(start=0,stop=11,step=1)
ydata=np.arange(start=0,stop=11,step=1)
# Time Interval
delay=1
# Create Figure with Axes
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_xlim((xdata[0],xdata[-1]))
ax1.set_ylim((ydata[0],ydata[-1]))
ax1.set_title('Animated Line Plot')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
# Create Plot with No Data
(plot1,)=ax1.plot([],[],lw=3,color='r',
                 label='animated line')

# Create functions
def init():
    plot1.set_data([],[])
    plt.pause(delay)
    return((plot1,))

def animate(i):
    plot1.set_data(xdata[:i],ydata[:i])
    plt.pause(delay)
    return((plot1,))

# Call functions
init()

for f in range(len(xdata)+1):
    animate(f)

This works the same as before. Now instead of calling the two custom functions, we can supply them in a new class. If we type in anim followed by a dot . and tab we see that there is the class FuncAnimation() and we can can create a new instance of it assigning it to a new variable name.

If we type in this class with open parenthesis we will see the details about the positional and input arguments:

animatedfig1=anim.FuncAnimation(

We can provide the two positional arguments and the keyword arguments init_func. Note when providing the functions as inputs to this class don't include parenthesis as this class will populate the input arguments directly. The keyword argument frames will be used as the 0th positional input of the function animate.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# Generate Data
xdata=np.arange(start=0,stop=11,step=1)
ydata=np.arange(start=0,stop=11,step=1)
# Time Interval
delay=1
# Create Figure with Axes
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_xlim((xdata[0],xdata[-1]))
ax1.set_ylim((ydata[0],ydata[-1]))
ax1.set_title('Animated Line Plot')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
# Create Plot with No Data
(plot1,)=ax1.plot([],[],lw=3,color='r',
                 label='animated line')

f=np.arange(start=0,stop=12,step=1)

# Create functions
def init():
    plot1.set_data([],[])
    plt.pause(delay)
    return((plot1,))

def animate(i):
    plot1.set_data(xdata[:i],ydata[:i])
    plt.pause(delay)
    return((plot1,))

animatedfig1=anim.FuncAnimation(fig1,
                               animate,frames=f,
                               init_func=init)

The animation will occur indefinately until the figure is closed or the Kernal restarted.

There is the additional keyword input argument interval which is the time between frames measured in ms which has a default of 200 ms, we can change this to 1000 ms to update every second. In order for this change to be successful, we need to remove the pause from the init() and animation() functions.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# Generate Data
xdata=np.arange(start=0,stop=11,step=1)
ydata=np.arange(start=0,stop=11,step=1)
# Time Interval
delay=1
# Create Figure with Axes
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_xlim((xdata[0],xdata[-1]))
ax1.set_ylim((ydata[0],ydata[-1]))
ax1.set_title('Animated Line Plot')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
# Create Plot with No Data
(plot1,)=ax1.plot([],[],lw=3,color='r',
                 label='animated line')

f=np.arange(start=0,stop=12,step=1)

# Create functions
def init():
    plot1.set_data([],[]
    return((plot1,))

def animate(i):
    plot1.set_data(xdata[:i],ydata[:i])
    return((plot1,))

animatedfig1=anim.FuncAnimation(fig1,
                               animate,frames=f,
                               init_func=init,
                               interval=1000)

We can now type in the name of the animated figure animatedfig1 followed by a dot . and tab . This will give us the methods available:

And we can use the method animatefig1.save() to save the file to a video format where the input argument is the video's file name.

However in order to use save we need to specify the writer. To do this we can create an instance of a video writer class we can use PillowWriter to save the file to a gif.

Note we supply the keyword input argument fps to the video writer and in this case we will set this to 1 meaning our gif will display 1 frame per second. The speed the animation plays in Spyder is dictated by the interval keyword input argument of the FuncAnimation class (provided there are no additional pause statements) and the speed of the video is dictated by the fps keyword input argument of the video writer. These are independent.

videowriter=anim.PillowWriter(fps=1)

Now that we have the videowriter we can save the file to a gif by supplying the file name with the appropriate extension. The keyword input argument fps for the animatedfig1.save() method becomes redundant.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# Generate Data
xdata=np.arange(start=0,stop=11,step=1)
ydata=np.arange(start=0,stop=11,step=1)
# Time Interval
delay=1
# Create Figure with Axes
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_xlim((xdata[0],xdata[-1]))
ax1.set_ylim((ydata[0],ydata[-1]))
ax1.set_title('Animated Line Plot')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
# Create Plot with No Data
(plot1,)=ax1.plot([],[],lw=3,color='r',
                 label='animated line')

f=np.arange(start=0,stop=12,step=1)

# Create functions
def init():
    plot1.set_data([],[])
    return((plot1,))

def animate(i):
    plot1.set_data(xdata[:i],ydata[:i])
    return((plot1,))

animatedfig1=anim.FuncAnimation(fig1,
                               animate,frames=f,
                               init_func=init, interval=1000
                               save_count=50)

videowriter=anim.PillowWriter(fps=1)

animatedfig1.save('animatedline.gif',
                  writer=videowriter)

It is also possible to use the FFMpegWriter (Fast Forward "Motion Pictures Expert Group") to save it to a mp4 but note that this writer is not installed by default with Anaconda and needs to be installed from the conda forge. If you get the FileNotFoundError: [WinError 2]. The system cannot find the file specified then the video writer isn't installed. Close down Spyder and open the terminal in Linux or Anaconda PowerShell Prompt in Windows. Type in:

conda install -c conda-forge ffmpeg

Then select y to proceed with the installation:

Once finished you should see (base) followed by the prompt. You can now close down the Anaconda PowerShell Prompt and launch Spyder.

We can now use the writer class FFMpegWriter and save our animated graph to a mp4 file extension.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# Generate Data
xdata=np.arange(start=0,stop=11,step=1)
ydata=np.arange(start=0,stop=11,step=1)
# Time Interval
delay=1
# Create Figure with Axes
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_xlim((xdata[0],xdata[-1]))
ax1.set_ylim((ydata[0],ydata[-1]))
ax1.set_title('Animated Line Plot')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
# Create Plot with No Data
(plot1,)=ax1.plot([],[],lw=3,color='r',
                 label='animated line')

f=np.arange(start=0,stop=12,step=1)

# Create functions
def init():
    plot1.set_data([],[])
    return((plot1,))

def animate(i):
    plot1.set_data(xdata[:i],ydata[:i])
    return((plot1,))

animatedfig1=anim.FuncAnimation(fig1,
                               animate,frames=f,
                               init_func=init,interval=1000
                               save_count=50)

videowriter2=anim.FFMpegWriter(fps=1)
animatedfig1.save('animatedline.mp4',
                  writer=videowriter2)

Note that the duration of the video is 12 seconds as expected given each frame displays for 1 second and we have 12 frames.

We can modify the code above to make an animated scatter plot. Note the differences in line 25, assignment is direct to the variable name and not a tuple of 1 with the element name, we seen the reason for this early with the multiform ax1.plot() and otherwise updated to be a scatter plot. Unfortunately we need to use the method plot1.set_offsets() in a scatter plot which differs from the method plot1.set_data() in a line plot. This method expects a matrix as an input where the 0th column is the x values and the 1st column is the y values which is achieved with np.c_[[],[]] otherwise the code is more or less identical.

# perquisites
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# Generate Data
xdata=np.arange(start=0,stop=11,step=1)
ydata=np.arange(start=0,stop=11,step=1)
# Time Interval
delay=1
# Create Figure with Axes
fig1=plt.figure(1)
fig1.clf()
ax1=fig1.subplots(nrows=1,ncols=1)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_xlim((xdata[0],xdata[-1]))
ax1.set_ylim((ydata[0],ydata[-1]))
ax1.set_title('Animated Scatter Plot')
ax1.grid(b=True,which='major',axis='both',
         color='#a6a6a6',linestyle='-',linewidth=1)
ax1.minorticks_on()
ax1.grid(b=True,which='minor',axis='both',
         color='#a6a6a6',linestyle=':',linewidth=0.5)
# Create Plot with No Data
plot1=ax1.scatter([],[],s=30,color='r',
                  label='animated scatter')

f=np.arange(start=0,stop=12,step=1)

# Create functions
def init():
    plot1.set_offsets(np.c_[[],[]])
    return(plot1)

def animate(i):
    plot1.set_offsets(np.c_[xdata[:i],ydata[:i]])
    return(plot1)

animatedfig1=anim.FuncAnimation(fig1,
                               animate,
                               init_func=init,
                               interval=1000,
                               save_count=50)

animatedfig1=anim.FuncAnimation(fig1,
                               animate,frames=f,
                               init_func=init,
                               save_count=50)

videowriter2=anim.FFMpegWriter(fps=1)
animatedfig1.save('animatedscatter.mp4',
                  writer=videowriter2)

animated 3d plot continued

It is possible to make a video of a rotating animated 3D plot using a similar procedure to the above. Here we create the figure and display the figure as normal however we create, the 3D axes and initialize the view of the axes within the function animate and create the plot within the function which we return. In this case we are also going to print the input of the function to the console so we can see what is going on. We will create a vector every_step which which can use for the frames. Once again we can create an instance of the anim.FuncAnimation() class and supply the figure, function and frames.

# %% perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import pandas as pd
import matplotlib.animation as anim
# %% create data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
            [1,2,3,2,1],
            [1,2,3,2,1],
            [0,1,1,1,0]])
(x,y)=np.meshgrid(x,y)
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
data=pd.DataFrame(xyz,columns=['x','y','z'])
# %% plot data

my_step=15
every_step=np.arange(start=0,stop=360+my_step,
                     step=my_step)
fig1=plt.figure(1)
def animate(i):
    print(i)
    fig1.clf()
    ax1=fig1.add_subplot(1,1,1,projection='3d')
    ax1.view_init(45,i)
    plot1=ax1.plot_wireframe(x,y,z,color='r')
    ax1.set_xlabel('x (μm)')
    ax1.set_ylabel('y (μm)')
    ax1.set_zlabel('z (counts)')
    return(plot1)

animatedfig1=anim.FuncAnimation(fig1,animate,
                                frames=every_step,
                                interval=10)

We can see the figure rotate and the console will report the angle of the axim, note that this figure will continue to rotate indefinitely until it is closed. I can now save this as a video with a frame rate of 1 frame per second using an instance of the FFMpegWriter() class and the save method of animatedfig1.save().

# %% perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import pandas as pd
import matplotlib.animation as anim
# %% create data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
            [1,2,3,2,1],
            [1,2,3,2,1],
            [0,1,1,1,0]])
(x,y)=np.meshgrid(x,y)
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
data=pd.DataFrame(xyz,columns=['x','y','z'])
# %% plot data

my_step=15
every_step=np.arange(start=0,stop=360+my_step,
                     step=my_step)
fig1=plt.figure(1)
def animate(i):
    print(i)
    fig1.clf()
    ax1=fig1.add_subplot(1,1,1,projection='3d')
    ax1.view_init(45,i)
    plot1=ax1.plot_wireframe(x,y,z,color='r')
    ax1.set_xlabel('x (μm)')
    ax1.set_ylabel('y (μm)')
    ax1.set_zlabel('z (counts)')
    return(plot1)

animatedfig1=anim.FuncAnimation(fig1,animate,
                                frames=every_step,
                                interval=10)
videowriter2=anim.FFMpegWriter(fps=1)
animatedfig1.save('animated3daximrotate.mp4',
                  writer=videowriter2)

Note during the first run the animated 3d plot may display more slowly and in my case the figure said not responding. This is because it is saving the animation to the video file. Once the first rotation is completed, it is no longer saving to the video file and should display as normal. The duration of this video is 25 s as expected (360/15)+1=25 frames and each frame is 1 s. The text on the axes is still unfortunately slightly messed up in my case.

We can now update this to give the axim in steps of 1 and give a 15 frames per seconds video. This will substantially longer to run.

# %% perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import pandas as pd
import matplotlib.animation as anim
# %% create data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
            [1,2,3,2,1],
            [1,2,3,2,1],
            [0,1,1,1,0]])
(x,y)=np.meshgrid(x,y)
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
data=pd.DataFrame(xyz,columns=['x','y','z'])
# %% plot data

my_step=1
every_step=np.arange(start=0,stop=360+my_step,
                     step=my_step)
fig1=plt.figure(1)
def animate(i):
    print(i)
    fig1.clf()
    ax1=fig1.add_subplot(1,1,1,projection='3d')
    ax1.view_init(45,i)
    plot1=ax1.plot_wireframe(x,y,z,color='r')
    ax1.set_xlabel('x (μm)')
    ax1.set_ylabel('y (μm)')
    ax1.set_zlabel('z (counts)')
    return(plot1)

animatedfig1=anim.FuncAnimation(fig1,animate,
                                frames=every_step,
                                interval=10)
videowriter2=anim.FFMpegWriter(fps=15)
animatedfig1.save('animated3daximrotate15fps.mp4',
                  writer=videowriter2)

We can modify the code to look at the change in elevation instead.

# %% perquisites
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import pandas as pd
import matplotlib.animation as anim
# %% create data
x=np.array([5,6,7,8,9])
x=np.reshape(x,(1,-1))
y=np.array([1,2,3,4])
y=np.reshape(y,(-1,1))
z=np.array([[0,1,1,1,0],
            [1,2,3,2,1],
            [1,2,3,2,1],
            [0,1,1,1,0]])
(x,y)=np.meshgrid(x,y)
xyz=np.zeros((z.size,3))
xyz[:,0]=x.flatten()
xyz[:,1]=y.flatten()
xyz[:,2]=z.flatten()
data=pd.DataFrame(xyz,columns=['x','y','z'])
# %% plot data
cmap_levels=np.arange(start=0,stop=3.5,step=0.5)

my_step=1
every_step=np.arange(start=0,stop=360+my_step,
                     step=my_step)
fig1=plt.figure(1)
def animate(i):
    print(i)
    fig1.clf()
    ax1=fig1.add_subplot(1,1,1,projection='3d')
    ax1.view_init(i,45)
    plot1=ax1.plot_wireframe(x,y,z,color='r')
    ax1.set_xlabel('x (μm)')
    ax1.set_ylabel('y (μm)')
    ax1.set_zlabel('z (counts)')
    return(plot1)

animatedfig1=anim.FuncAnimation(fig1,animate,
                                frames=every_step,
                                interval=10)
videowriter2=anim.FFMpegWriter(fps=15)
animatedfig1.save('animated3delevrotate15fps.mp4',
                  writer=videowriter2)

issues

  • nrows and ncols should work as keyword arguments in plt.subplots.
  • get_marker and set_marker methods should be added to scatter plots.
  • linewidths and edgecolors should be singular linewidth and edgecolor in plt.scatter function.
  • the method set_sizes for a scatter plot should accept a scalar input not only a single element list [].
  • get_offsets should be renamed to get_data for a scatter plot, alternatively the method get_data should be made an alias for get_offsets.
  • colorbar method to set lower and upper bound of colorbar.
  • 3D axes integrated into pyplot so plt.zlabel() etc can work.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.