Matplotlib Tips and Demos

When I first learned Python three years ago, I was often finding myself having to lookup the same thing again and again for Matplotlib. This was back pre-2.0 when there was even more issues (e.g. plotting NaNs with masked arrays).

Some of this is super simple, some of it is more advanced. It is just my go-to reference

In [1]:
import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt
#%matplotlib inline # Display inline -- May not be needed on newer platforms

import sys

print('Matplotlib Version: ' + mpl.__version__)
print('NumPy Version: ' + np.version.full_version)
print('Python Version: ' + sys.version)

import datetime
now  = datetime.datetime.now().isoformat()
print('Ran on ' + now)
Matplotlib Version: 2.2.3
NumPy Version: 1.15.1
Python Version: 3.7.0 (default, Jun 28 2018, 07:39:16) 
[Clang 4.0.1 (tags/RELEASE_401/final)]
Ran on 2018-09-18T11:15:45.456807

The basics

With few exception, all examples use the object-oriented interface via plt.subplots(). In general, there is full parity between pyplot and the object-oriented interface. My personal opinion is plt.(...) for quick-and-dirty and then use ax.(...) when I want more control.

Basic Plot

With labels, etc. The code exPlot builds the basics with the set configurations

In [2]:
x = np.linspace(0,2*np.pi,10**3)
y = np.sin(x) + np.cos(x)
In [3]:
def exPlot(isTeX=False):
    fig,ax = plt.subplots(1,1,figsize=(3,3),dpi=100,num=1)
    ax.plot(x,y,'-k')
    ax.set_xlabel(r'x label with math (ex: $x^2$)')
    if not isTeX:
        ax.set_ylabel(r'y label with non-math math (ex: $\mathregular{{x^2}}$)') # Doesn't work for LaTeX render
    

Defaults

In [4]:
plt.rc('font', family='sans-serif') # These are the defaults
plt.rc('text', usetex=False)

exPlot()

Serif Only

In [5]:
plt.rc('font', family='serif')
exPlot()

LaTeX

... then reset

In [6]:
plt.rc('font', family='serif')
plt.rc('text', usetex=True)
exPlot(isTeX=True)
In [7]:
plt.rc('font', family='sans-serif')
plt.rc('text', usetex=False)

Scientific Notation

In [8]:
fig,(ax1,ax2) = plt.subplots(1,2,figsize=(7,3),dpi=100,num=1)

# Regular
ax1.plot(x,1e4*y)

# Scientific
ax2.plot(x,1e4*y)

from matplotlib import ticker
formatter = ticker.ScalarFormatter(useMathText=True)
formatter.set_scientific(True) 
formatter.set_powerlimits((-1,1))

ax2.yaxis.set_major_formatter(formatter)

Standard Subplots

Adjustments

In [9]:
def subdemo(axes):
    for ii,ax in enumerate(axes.ravel()):
        ax.plot(x,10**(ii)*y,'-k')
    ax.yaxis.set_major_formatter(formatter) # Will always be the last one

Default

In [10]:
fig,axes = plt.subplots(2,2,figsize=(7,3),dpi=100,num=1)
subdemo(axes)

Automatic

This is the preffered way to do it

In [11]:
fig,axes = plt.subplots(2,2,figsize=(7,3),dpi=100,num=1)
subdemo(axes)
fig.tight_layout()

Manual

This example is not designed to look good. It is to show the results.

This comes from http://stackoverflow.com/a/6541482

In [12]:
fig,axes = plt.subplots(2,2,figsize=(7,3),dpi=100,num=1)
subdemo(axes)

fig.subplots_adjust(hspace=0.45,wspace=0.35)

## All Options w/ examples
# left  = 0.125  # the left side of the subplots of the figure
# right = 0.9    # the right side of the subplots of the figure
# bottom = 0.1   # the bottom of the subplots of the figure
# top = 0.9      # the top of the subplots of the figure
# wspace = 0.2   # the amount of width reserved for blank space between subplots
# hspace = 0.2   # the amount of height reserved for white space between subplots

Shared Axes

In [13]:
fig,axes = plt.subplots(2,2,figsize=(7,3),dpi=100,
                        sharex=True)
subdemo(axes)
fig.tight_layout()

You can also share the y axis, but it won't look good for this since they are different scales so this won't look good

In [14]:
fig,axes = plt.subplots(2,2,figsize=(7,3),dpi=100,
                        sharex=True,sharey=True)
subdemo(axes)
fig.tight_layout()

Fancy Subplots

There are a few ways to do this.

  • gridspec -- General purpose
  • manually
  • add subplots where you want -- doesn't have spans
    • Regular subplots and then "clear" axis

gridspec

Based on http://matplotlib.org/users/gridspec.html. You seem to have to rely on the plt tools to make all of the axes

In [15]:
fig = plt.figure()

ax = [None for _ in range(6)]

ax[0] = plt.subplot2grid((3,4), (0,0), colspan=4)

ax[1] = plt.subplot2grid((3,4), (1,0), colspan=1)
ax[2] = plt.subplot2grid((3,4), (1,1), colspan=1)
ax[3] = plt.subplot2grid((3,4), (1,2), colspan=1)
ax[4] = plt.subplot2grid((3,4), (1,3), colspan=1,rowspan=2)

ax[5] = plt.subplot2grid((3,4), (2,0), colspan=3)

for ix in range(6):
    ax[ix].set_title('ax[{}]'.format(ix))

fig.tight_layout()

Manually

This example will be less complex to make life easier... In this case, you create the axes from the parent fig

In [16]:
fig = plt.figure()

ax = [None for _ in range(3)]

ax[0] = fig.add_axes([0.1,0.1,0.9,0.4]) # Bottom
ax[1] = fig.add_axes([0.15,0.6,0.25,0.6]) # They do not *need* to be in a grid
ax[2] = fig.add_axes([0.5,0.6,0.4,0.3])

for ix in range(3):
    ax[ix].set_title('ax[{}]'.format(ix))
    
# fig.tight_layout() # does not work with this method

Add subplots

Can also do grids but harder (though not impossible) to do spanning

In [17]:
fig = plt.figure()

ax = [None for _ in range(3)]

ax[0] = fig.add_subplot(2,2,1)
ax[1] = fig.add_subplot(2,2,2)
ax[2] = fig.add_subplot(2,2,3)

for ix in range(3):
    ax[ix].set_title('ax[{}]'.format(ix))
    
fig.tight_layout()

Regular plots with "cleared" axis

In [18]:
fig,axes = plt.subplots(2,2)
ax = axes[1,1]
ax.set_frame_on(False)
ax.set_xticks([])
ax.set_yticks([])

fig.tight_layout()

No Spacing Demo

In [19]:
fig,axes = plt.subplots(3,3,sharex=True,sharey=True)

np.random.seed(282)
X = np.random.normal(size=(30,3))

import itertools
for ix,iy in itertools.product(range(3),range(3)):
    ax = axes[ix,iy]
    ax.plot(X[:,ix],X[:,iy],'ko')

for ax in axes[-1,:]:
    ax.set_xlabel('x')
for ax in axes[:,0]:
    ax.set_ylabel('y')

#fig.tight_layout(h_pad=0,w_pad=0)
fig.subplots_adjust(hspace=0,wspace=0)
        
In [20]:
fig = plt.figure()

axes = []

np.random.seed(282)
X = np.random.normal(size=(30,3))

import itertools
for ii,(ix,iy) in enumerate(itertools.combinations([0,1,2],2)):
    ax = fig.add_subplot(2,2,2*ix+iy)
    ax.plot(X[:,ix],X[:,iy],'ko')
    axes.append(ax)


#fig.tight_layout(h_pad=0,w_pad=0)
fig.subplots_adjust(hspace=0,wspace=0)

Pcolor(mesh) & Colorbar (and nice colormap)

Consider the following for making a nice pcolor-type plots with a colorbar. The colormaps are set with cmap=plt.cm.Spectral_r which is the nice one from ColorBrewer

Setup & Defaults

In [21]:
np.random.seed(362423)
A = np.random.uniform(size=(6,8))
In [22]:
fig,(ax1,ax2,ax3,ax4) = plt.subplots(1,4,figsize=(9,3),dpi=100)
def plotEX(ax,**kw):
    ax.pcolormesh(A,**kw)

plotEX(ax1)
plotEX(ax2,cmap=plt.cm.Spectral_r)
plotEX(ax3,cmap=plt.cm.Spectral_r,edgecolor='k')
plotEX(ax4,cmap=plt.cm.Spectral_r,shading='gouraud')
fig.tight_layout()

Colorbars

Standard colorbar

The size of the figure was selected to show the problem with scale

Also, since this invokes fig, it doesn’t play nice with subplots

In [23]:
fig,ax = plt.subplots(1,1,figsize=(3,5),dpi=100)
pl = ax.pcolormesh(A,cmap = plt.cm.Spectral_r,edgecolor='k')
ax.axis('image')
fig.colorbar(pl)
Out[23]:
<matplotlib.colorbar.Colorbar at 0x110eac828>

Scaled Colorbar

This example scaled the colorbar. It also plays nicely with subplots (not demoed)

In [24]:
fig,ax = plt.subplots(1,1,figsize=(3,5),dpi=100)
pl = ax.pcolormesh(A,cmap = plt.cm.Spectral_r,edgecolor='k')
ax.axis('image')

from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cbar = fig.colorbar(pl,cax=cax)

Set ranges

In [25]:
fig,ax = plt.subplots(1,1,figsize=(3,5),dpi=100)
pl = ax.pcolormesh(A,cmap = plt.cm.Spectral_r,edgecolor='k',vmin=-1,vmax=2.2)
ax.axis('image')

from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cbar = fig.colorbar(pl,cax=cax)
cbar.set_ticks(np.linspace(-1,2.2,6))

Dealing with nan -- DEPRECATED

This may be deprecated in Python 3 and/or later versions of matplotlib (not sure) but here is how to do it for this

In [26]:
B = A.copy()
B[2,3] = np.nan

fig,(ax1,ax2) = plt.subplots(1,2,figsize=(7,5),dpi=100)

ax1.pcolormesh(B,cmap = plt.cm.Spectral_r,edgecolor='k')
ax1.axis('image')

B = np.ma.masked_array(B,np.isnan(B))
ax2.pcolormesh(B,cmap = plt.cm.Spectral_r,edgecolor='k')
ax2.axis('image')
Out[26]:
(0.0, 8.0, 0.0, 6.0)

Ticks

Position

The following shows some examples of setting the tick locations

In [27]:
fig,axes = plt.subplots(2,2,figsize=(7,5),dpi=100)

X = np.linspace(0,2*np.pi,1000)
Y = np.sin(X)

for ax in axes.ravel(): #plot
    ax.plot(X,Y,'-k')

ax = axes[0,0]

ax = axes[0,1]
ax.tick_params(labeltop=True,labelbottom=False)

ax = axes[1,0]
ax.tick_params(labelright=True,labelbottom=False,labelleft=False)

ax = axes[1,1]
ax.tick_params(labeltop=True,labelbottom=True,labelright=True,labelleft=True)

# Also add ticks
ax.tick_params(axis='both',bottom=True,top=True,left=True,right=True)

fig.tight_layout()

All sides + inside

In [28]:
x = np.logspace(0,3,600)
y = 1.2**(0.04*x)
fig,ax = plt.subplots(dpi=100)
ax.plot(x,y)
ax.set(xscale='log',yscale='log')
ax.tick_params(axis='both',bottom=True,top=True,left=True,right=True,direction='in',which='both')

Grids

Note that this is the same with and without a log scale but the log scale shows it better. The zorder makes sure the plot is in front of the grid lines

In [29]:
fig,(ax1,ax2) = plt.subplots(1,2,sharey=True,figsize=(10,5))
x = np.logspace(0,3,600)
y = 1.2**(0.04*x)

for ax in (ax1,ax2):
    ax.plot(x,y,'-k')
    ax.set(xscale='log',yscale='log')
    ax.tick_params(axis='both',bottom=True,top=True,left=True,right=True,direction='in',which='both',zorder=10)
ax1.grid(which='major')
ax2.grid(which='both')

Labels

Rotated text and ha (horizontal alignment)

In [30]:
fig,axes = plt.subplots(2,2,figsize=(5,3),dpi=100)

x = np.linspace(-10,10,100)
y = np.sin(x)/(x + np.spacing(1))

labs = ['neg ten','neg five','zero','pos five','pos ten']

for ax in axes.ravel():
    ax.plot(x,y)
    ax.set_xticks([-10,-5,0,5,10])

axes[0,0].set_xticklabels(labs)

axes[0,1].set_xticklabels(labs,rotation=-45)
axes[0,1].set_title('rotated')

axes[1,0].set_xticklabels(labs,rotation=-45,ha='left')
axes[1,0].set_title('rotated, left')

axes[1,1].set_xticklabels(labs,rotation=-45,ha='right')
axes[1,1].set_title('rotated, right')


fig.tight_layout()

Label Formats

Also included are prettier x-labels

In [31]:
X = np.linspace(0,2*np.pi,1000)
Y = np.cos(X)**2

fig,(ax1,ax2,ax3) = plt.subplots(1,3,figsize=(8,3),dpi=100,num=1)
for ax in (ax1,ax2,ax3):
    ax.plot(X,Y,'k-')
    ax.set_yticks([0, 0.25, 0.5,0.75,1])
    ax.set_xticks(np.linspace(0,2*np.pi,5))
    ax.set_xticklabels([r'$0$',r'$\frac{\pi}{2}$',r'$\pi$',r'$\frac{3\pi}{2}$',r'$2\pi$'])

ax1.set_title('Default')

ax2.set_title('g formatting')
ax2.set_yticklabels( ['{:0.2g}'.format(l) for l in ax2.get_yticks()] )

ax3.set_title('g formatting, force decimal')
ax3.set_yticklabels( ['{:0.2g}'.format(l) if int(l)!=float(l) else '{:0.1f}'.format(l) 
                      for l in ax3.get_yticks()] )


fig.tight_layout()

Legends

There are a few ways to work a legend. And there is a lot more that can be found on the web.

The main takeaway is to have label= in the respective plot

Directly specify

In [32]:
fig,(ax1,ax2) = plt.subplots(1,2,figsize=(7,3),dpi=100,num=1,sharey=True)

X = np.linspace(0,2*np.pi,1000)

def plotEX(ax):
    pl = [None for i in range(3)]
    
    pl[0] = ax.plot(X,np.sin(X),label='One')
    pl[1] = ax.plot(X,np.cos(X),label='Two')
    pl[2] = ax.plot(X,np.sin(np.cos(X)),label='Three')
    return [p[0] for p in pl] # makes it just the objects
    
pl1 = plotEX(ax1) 
ax1.legend()

pl2 = plotEX(ax2) 
ax2.legend([pl2[0],pl2[2]],['One','Three'])
Out[32]:
<matplotlib.legend.Legend at 0x11263ca90>

Use label

In [33]:
fig,(ax1,ax2) = plt.subplots(1,2,figsize=(7,3),dpi=100,num=1,sharey=True)

X = np.linspace(0,2*np.pi,1000)

ax1.plot(X,np.sin(X),label='One')
ax1.plot(X,np.cos(X),label='Two')
ax1.plot(X,np.sin(np.cos(X)),label='Three')
ax1.legend()

ax2.plot(X,np.sin(X),label='One')
ax2.plot(X,np.cos(X)) # NO LABEL
ax2.plot(X,np.sin(np.cos(X)),label='Three')
ax2.legend()
Out[33]:
<matplotlib.legend.Legend at 0x110e932e8>

Number of points

Thankfully, the default was changed to one.

In [34]:
fig,axes = plt.subplots(1,4,figsize=(8,3),dpi=100,num=1,sharey=True)

X = np.linspace(0,2*np.pi,8)


for ii,ax in enumerate(axes):
    ax.plot(X,np.sin(X),'-o',label=r'Lab 1: $\sin(x)$')
    ax.plot(X,np.cos(X),'-s',label=r'Lab 1: $\cos(x)$')
    ax.legend(numpoints=(ii+1))
    
    
fig.tight_layout()

Dummy Legends

This is useful if you want certain entries that are not to be plotted

In [35]:
fig,ax = plt.subplots(1,1,figsize=(5,3),dpi=100,num=1,sharey=True)

X = np.linspace(0,2*np.pi,1000)

# Real Lines
ax.plot(X,np.sin(X),'-')
ax.plot(X,np.cos(X),'-')
ax.plot(X,np.sin(np.cos(X)),'-')

# Dummy lines
ax.plot([],'-rs',label='Dummy1')
ax.plot([],'k',label='Dummy2',LineWidth=8)
ax.legend()
Out[35]:
<matplotlib.legend.Legend at 0x1110e6278>

Legend in its own plot

In [36]:
fig,ax = plt.subplots(1,1,figsize=(1,1))
ax.plot([],[],'-s',label='a',linewidth=2,ms=8)
ax.plot([],[],'-*',label='b',linewidth=2,ms=8)
ax.plot([],[],'-o',label='c',linewidth=2,ms=8)
ax.legend(loc='center',numpoints=1)
ax.set_frame_on(False)
ax.set_xticks([])
ax.set_yticks([])
fig.tight_layout()

Multiple y-axes

The first example is mine. The second is taken nearly verbatim from http://stackoverflow.com/a/7734614

I will experiment a little with combining scientific notation as well as log scales but I won't go too crazy (until I have to do it at which point, I will update this)

Double y-axis

NOTE: The keyword for tick_params is colors (with an s)

In [37]:
fig, ax1 = plt.subplots(1,1,figsize=(5,3),dpi=100)

X = np.linspace(-3*np.pi,3*np.pi,1000)
Y1 = np.sin(X)/(X+0.0001)
Y2 = 1e3 * np.cos(X)
Y3 =  np.exp(np.abs(X))

# Twin the axis
ax2 = ax1.twinx()

# Plotting
ax1.plot(X,Y1,'-r')
ax2.plot(X,Y2,':b')

# Color the axis and add labels
ax1.set_ylabel('Y1',color='r')
ax2.set_ylabel('Y2',color='b')

ax1.tick_params(axis='y', colors='r')
ax2.tick_params(axis='y', colors='b')

# Set the spine colors. Really only need to do ax2 since that is on top
# but this just makes 100% sure
for ax in (ax1,ax2):
    ax.spines['left'].set_color('r')
    ax.spines['right'].set_color('b')

from matplotlib import ticker
formatter = ticker.ScalarFormatter(useMathText=True)
formatter.set_scientific(True) 
formatter.set_powerlimits((-1,1)) 
ax2.yaxis.set_major_formatter(formatter)
ax2.yaxis.get_offset_text().set_color('b') # Set the color of the power

# Better X-Ticks
ax1.set_xlim((X.min(),X.max()))
ax1.set_xticks(np.arange(-3,4)*np.pi)
xticklabs = [r'$\mathregular{{{0:d}\pi}}$'.format(i) for i in np.arange(-3,4)]; xticklabs[3]='0'
ax1.set_xticklabels(xticklabs) 

fig.tight_layout()

Triple y-axis

Again, this is inspired by http://stackoverflow.com/a/7734614 but I make a few changes

In [38]:
fig, ax1 = plt.subplots(1,1,figsize=(6,4),dpi=100)

# Twin the x-axis twice to make independent y-axes.
ax2 = ax1.twinx()
ax3 = ax1.twinx()

# Make some space on the right side for the extra y-axis.
fig.subplots_adjust(right=0.75)

# Move the last y-axis spine over to the right by 20% of the width of the axes
ax3.spines['right'].set_position(('axes', 1.2))

# To make the border of the right-most axis visible, we need to turn the frame
# on. This hides the other plots, however, so we need to turn its fill off.
ax3.set_frame_on(True)
ax3.patch.set_visible(False)

# Plot
ax1.plot(X,Y1,'-r')
ax2.plot(X,Y2,'-b')
ax3.plot(X,Y3,'-g')

colors = ['r','b','g']
axes = [ax1,ax2,ax3]
names = ['Y1','Y2','Y3']

for ax in (ax1,ax2,ax3):
    ax.spines['left'].set_color(colors[0])
    ax.spines['right'].set_color(colors[1])
ax3.spines['right'].set_color('g') # reset
    
for ax,color,name in zip(axes,colors,names):
    ax.set_ylabel(name,color=color)
    ax.tick_params(axis='y', colors=color)

# Nicer ax2 y axis
from matplotlib import ticker
formatter = ticker.ScalarFormatter(useMathText=True)
formatter.set_scientific(True) 
formatter.set_powerlimits((-1,1)) 
ax2.yaxis.set_major_formatter(formatter)
ax2.yaxis.get_offset_text().set_color('b') # Set the color of the power

# Set ax3 to log
ax3.set_yscale('log')

# Better X-Ticks
ax1.set_xlim((X.min(),X.max()))
ax1.set_xticks(np.arange(-3,4)*np.pi)
xticklabs = [r'$\mathregular{{{0:d}\pi}}$'.format(i) for i in np.arange(-3,4)]; xticklabs[3]='0'
ax1.set_xticklabels(xticklabs)

fig.tight_layout()

3D Data

In [39]:
X1,X2 = np.meshgrid(*[np.linspace(-1,1,100)]*2)
F = (1.0 + (1.0/3.0)/(2.0 * X1 + X2 + 7.0/2.0) ) * np.exp(- (0.5 * (X2-1.0/5.0) * (X1 + 1.0))**2)

def _set_axis(ax,z=True):
    r =0.01
    if z:
        ax.set_zlim([0.25, 1.67])
    ax.set_xlim([-1-2*r, 1+2*r])
    ax.set_ylim([-1-2*r, 1+2*r])
    ax.set_xlabel('x1');ax.set_ylabel('x2')
    ax.set_aspect('equal')

Countour

contourf does not include the lines so it is helpful to set them yourself.

In [40]:
fig,axes = plt.subplots(2,2,figsize=(9,7))

axes[0,0].contour(X1,X2,F,35)
axes[0,0].set_title('Defaults + N=35')

axes[0,1].contourf(X1,X2,F,35)
axes[0,1].set_title('Defaults filled + N=35')

axes[1,0].contourf(X1,X2,F,35)
axes[1,0].contour(X1,X2,F,35,colors='k',linewidths =0.5 )
axes[1,0].set_title('Filled + lines + N=35')

axes[1,1].contourf(X1,X2,F,35,cmap=plt.cm.Greys)
axes[1,1].contour(X1,X2,F,35,colors='k',linewidths =0.5 )
axes[1,1].set_title('Filled + lines + N=35, greys')

for ax in axes.ravel():
    _set_axis(ax,z=False)

fig.tight_layout()