Python usage notes - Matplotlib, pylab
Syntaxish: syntax and language · changes and py2/3 · decorators · importing, modules, packages · iterable stuff · concurrency · exceptions, warnings
IO: networking and web · filesystem Data: Numpy, scipy · pandas, dask · struct, buffer, array, bytes, memoryview · Python database notes Image, Visualization: PIL · Matplotlib, pylab · seaborn · bokeh · plotly
Stringy: strings, unicode, encodings · regexp · command line argument parsing · XML speed, memory, debugging, profiling · Python extensions · semi-sorted |
Intro
Matplotlib is a graphing package that creates various types of graphs relatively easily.
It is modelled after Matlab's plotting, hence the name.
To get an idea of what kind of plots you can make:
To get an idea of how code looks like - the simplest may be something like
from matplotlib import pyplot
fig, axis = pyplot.subplots(1)
axis.plot([1,5,2,3,3,4,2,1,2,3,5,3,4])
pyplot.show()
...but look below for slightly more realistic tweaking you will often do.
It's also specifically integrated a bit into jupyter notebooks, which is nice for interactive work.
Other software you may prefer in other contexts
- seaborn - easier to visualize analyses (so useful if you e.g. use pandas or similar), in more interesting graphs than just lines and bars, in particular making it easier to show uncertainties in statistical analysies
- Mayavi - if you want interactive 3D plots, this is often much faster than matplotlib, and with a similar interface.
GUI side
API choice
There are, broadly, two distinct ways to call into matplotlib:
- the object-oriented interface
- keeps "current state of your graph" in objects.
- which is cleaner and isolated when embedding, around threading
- the more complex the tweaking, the more controlled/cleaner it tends to be
- avoids problems when generating distinct plots at the same time (which as a user you almost never o)
- This page uses only this
- pylab
- a more direct imitation of matlab, so if you're a matlab person this will be obvious for you.
- simple graphs can be done in fewer lines
- the "current state of your graph" is stored in the module itself
- also means
- that you can't make more than one plot at a time
- threading/subinterpreters will bite you
If you're new and want to learn just one style, I strongly recommend the OO interface.
- Yes, pylab is a little shorter or the same (see below) for simple graphs.
- Yet once you want fancier things you'll probably want the OO style.
Examples (minus the imports, and 'actually show me it' command.)
OO style:
fig, ax = pyplot.subplots() # actually a shorthand, see below
ax.plot([1,2,3,4], [5,1,3,2]) # plot some data (that's an x and y array)
ax.set_title('some data')
Pylab equivalent:
pylab.figure() # omittable, the first plot() implies its creation. Later plots should clear it.
pylab.plot([1,2,3,4], [5,1,3,2])
pylab.title('some data')
The difference becomes more pronounced when you have multiple axes, or produce more graphs on a screen and/or over time, because the "I am currently drawing on this" is an evolving part of the module state, not the calls or something you can refer to.
See also:
- http://matplotlib.sourceforge.net/
- scipy wiki's matplotlib pages.
- http://matplotlib.sourceforge.net/api/index.html]
- http://matplotlib.sourceforge.net/api/pyplot_api.html]
Layout
Just one plot
Probably shortest:
import matplotlib.pyplot as plt
fig, axis = plt.subplots() # its default is 1 row, 1 column.
# If either is >1, the second thing returned will be a _list_ of axes rather than a single one
Adding same-sized figures on a grid
Use figure() and figure.add_subplot() if you want to create and use axes objects as you go:
import numpy, matplotlib, matplotlib.pyplot as plt
fig = plt.figure(figsize=(12, 6)) # figsize in inches (with the default dpi)
axis2 = fig.add_subplot(2,3, 2) # same as 232 (matlab style): Lay out 2-row, 3-column grid,
# create and select the 2nd one in that grid, i.e. top middle
axis4 = fig.add_subplot(2,3, 4) # ...4th plot in the same grid, i.e. bottom left
axis6 = fig.add_subplot(2,3, 6) # ...6th, bottom right
There is also pyplot.subplots(), which creates axes objects for you, which can save typing if your lotting commands vary very little within a grid. To steal a fragment from here, the same data with a different parameter:
# where interpolation_methods is a list of 18 different methods by name
fig, axes = plt.subplots(3, 6, figsize=(12, 6)) # 3 rows, 6 cols
# axes is an array (except if rows==cols==1, the default)
# This makes it short to make plots that are variations on a theme, e.g.:
for ax, interp_method in zip(axes.flat, interpolation_methods):
ax.imshow(grid, interpolation=interp_method)
ax.set_title(interp_method)
Notes:
- Follows matlab, so most grid addressing like this works the same way
- add_subplot does little more than using those numbers to calculate position and size to end up drawing in
- which each call does independently, based only on the given parameters - if you mix grid sizes can easily make a mess (rather than give errors)
- The object representing a subplot is Axes and so many examples call their subplots 'axis' or such.
- (it can get confusing Axes object in a variable named axis has an Axis attribute (the last as in the label-and-tick thing) -- so you may prefer your own naming.
Spanning cells
There are a few different ways of doing this.
I prefer the gridspec style, which seems the more flexible while still being fairly short.
# the plan below is to have 3x3 for granularity, and span them like:
#
# 1-1-1
# 2-2 3
# 4 5 3
import matplotlib.pyplot as plt, matplotlib.gridspec
fig = plt.figure(figsize=(8,7))
gs = matplotlib.gridspec.GridSpec(3, 3)
ax1 = fig.add_subplot( gs[0, : ] ) # first row, all columns
ax2 = fig.add_subplot( gs[1, 0:2] ) # second row, left and middle cells
ax3 = fig.add_subplot( gs[1:, 2 ] ) # second and third row, last-column
ax4 = fig.add_subplot( gs[2, 0 ] ) # third row, first column
ax5 = fig.add_subplot( gs[2, 1 ] ) # third row, second column
plt.show()
Others include pyplot.subplot2grid(), which which works something like:
fig = plt.figure()
ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=3)
ax2 = plt.subplot2grid((3, 3), (1, 0), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
ax4 = plt.subplot2grid((3, 3), (2, 0))
ax5 = plt.subplot2grid((3, 3), (2, 1))
free-style axes
Figure.add_axes( (left,bottom, width,height) ) will let you anchor axis edges at arbitrary fractions of the overall figure width/height.
I've seen this used mainly for inlaid plots.
Spacing
Subplots are added with spacing.
You can control spacing around plots and between subplots (but little else) via subplots_adjust.
The default spacing is apparently equivalent to:
fig.subplots_adjust(left=0.125, bottom=0.1, right=0.9, top=0.9, wspace=0.2, hspace=0.2)
...which is spacious for single plots, but may be a little cramped for grids, so you may care to use subplots_adjust yourself.
tight_layout
You might also first try fig.tight_layout(), which tries to reduce whitespace while being aware of specific components (artists(verify)).
It's a convenient call when it does what you want - though also also a bit of a hack that you may not want to rely on too much. Say, it considers ticklabels, axis labels, and titles overlap - but no other components that may be there.
See also:
- http://matplotlib.org/users/tight_layout_guide.html
- https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.tight_layout.html
Various backends lack a direct implementation of tight_layout, which is why if you ask for it you may see:
tight_layout : falling back to Agg renderer
It seems this isn't a warning or a change - it seems to mean matplotlib is redoing the plot in the Agg renderer purely to calculate the new positions (it needs to call get_renderer(), which not all backends provide), then and uses those positions in your actual requested backend(verify). So you can ignore this warning if you don't care about a little extra CPUwork(verify)
...and sometimes you want no space, e.g. I abuse imshow to visualize matrices, where axes are irrelevant.
Different kinds of plots
Often, it pays to search for galleries of examples and find someone who has tweaked something nicely for a specific purpose.
But to start listing things....
Basics
pyplot.plot - line plots, mostly example
pyplot.step - stepped lines example
pyplot.stem - stem/impulse-like example
pyplot.bar - bar graph example
barh vertial and horizontal bar graphs example
Statistics:
Dense data or images:
angle_spectrum, magnitude_spectrum, phase_spectrum example
specgram - STFT of a signal(verify) example
More specialized:
contour and contourf (filled) example example
streamplot streaming vector fields example
eventplot (a.k.a. spike raster, dot raster, or raster plot) example
Logarithmic
The often-more-flexible variant is to, after axis creation, is to use set_xscale and/or set_yscale, e.g.
import matplotlib, matplotlib.pyplot as plt
fig = plt.figure(figsize=(12, 6))
axis1 = fig.add_subplot(2,2, 1)
axis2 = fig.add_subplot(2,2, 2)
axis2.set_xscale('log')
axis3 = fig.add_subplot(2,2, 3)
axis3.set_yscale('log')
axis4 = fig.add_subplot(2,2, 4)
axis4.set_xscale('log')
axis4.set_yscale('log')
Various other plots allow this, but may have their own footnotes, like a polar allows it on the y axis (outwards) but sensibly refuses on the x axis (what would that even mean?)
There are also semilogx, semilogy, and loglog, but they mostly are log variants specifically of line plots - in fact they just seem to be thin wrappers around plot() that call set_xscale and set_yscale (verify)
Polar
polar plot [1]
polar scatter [2]
Polar areas (bar/pie-like) [3]
3D plots
...or other slightly atypical projections will probably also need this somewhat longer style, e.g.
fig = plt.figure()
from mpl_toolkits.mplot3d import Axes3D # without this, '3d' below will be an unknown projection
ax = fig.add_subplot(111, projection='3d')
Some plots (e.g. scatterplot, quiver) translate well to 3D without much extra thought.
Others need care, or specific alternatives, or are 3D-only in the first place
scatter https://matplotlib.org/stable/gallery/mplot3d/scatter3d.html
quiver https://matplotlib.org/stable/gallery/mplot3d/quiver3d.html
wireframe: https://matplotlib.org/stable/gallery/mplot3d/wire3d.html
surface: https://matplotlib.org/stable/gallery/mplot3d/surface3d.html
voxels: https://matplotlib.org/stable/gallery/mplot3d/voxels.html
Or wider, more creatively:
See also: https://matplotlib.org/2.0.2/mpl_toolkits/mplot3d/tutorial.html#d-plots-in-3d
Returning questions
How do I set Y range?
ax.set_ylim(minval, maxval)
e.g.
ax.set_ylim( min(ary), 1.01*max(ary) )
In general you may have use for:
- ax.set_xlim
- ax.set_ylim
- ax.set_zlim (for 3D plots)
Masking out data, missing data
Style notes
Drawing more
Legend
A legend will be added if you do
ax.legend()
Notes:
- legend names come from the name= parameter on the command that settles what kind of plot the axis contains (e.g. plot())
- legend() has a parameter called loc, to force the legend to a corner I figure is least likely to contain data.
- default seems to be 'best' for axes, 'upper right' for figures(verify)
- 'best' tries to avoid drawing over data but isn't always clever, and is slow on things with lots of data
- you can also use 'upper right', 'upper left', 'lower left', 'lower right', 'right', 'center left', 'center right', 'lower center', 'upper center', 'center'
- You can also control how legend rows are formatted.
- This picks up names from the label argument handed to functions like plot(),
though you can override this if you wish.
See https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
Grid
Showninga grid at all:
ax.grid(True)
Optional keyword arguments:
- which: 'major', 'minor', or 'both'
- various style, e.g.
- color=’r’
- linestyle=’-‘
- linewidth=2
- zorder=0
You can style major and minor differently like:
ax.grid(b=True, which='major', color='b', linestyle='-') # solid blue ax.grid(b=True, which='minor', color='r', linestyle='--') # dashed red
You could also style the lines later, by fetching the objects via calls like
ax.get_xgridlines() ax.get_ygridlines()
To change the interval of the grid, see the next section (tick labels)
See also:
for more control, see http://matplotlib.org/examples/axes_grid/demo_axes_grid.html
Custom tick labels
- Amount, interval, and content of ticklabels
You can tell matplotlib the amount of ticks you want to see (minimum, maximum) and have it figure out how much exactly that means for your particular x and y range.
You can change that behaviour.
For example, if only integer labels make sense, you may care to do:
ax.xaxis.set_major_locator( matplotlib.ticker.MaxNLocator(integer=True) )
You can also change the minor tick behaviour
See also:
You can also force them at specific points.
Note that this is only useful if the ranges are basically fixed.
ax.set_xticks( range(0, 100, 20) )
ax.set_xticks( range(0, 100, 5), minor=True )
You can also change the way they are formatted.
For most types of data it's smart enough and you don't need to.
For one example where I used it: I e.g. made a data collection report PDF thing that by nature was never longer than a few days, so the default date formatting is overly verbose, and I prefer the still-flexible:
adf = matplotlib.dates.AutoDateFormatter(
matplotlib.dates.AutoDateLocator(minticks=5, maxticks=15), defaultfmt='%a %d\n%k:%M' )
adf.scaled = { # if major tick interval is more than <key> days, call strftime with <value>
1.0 : '%a %d\n%k:%M', # > 1 day
1./24. : '%a %d\n%k:%M', # > 1 hour
1. / (24. * 60.): '%H:%M:%S.%f', # > 1 minute
}
axis.get_xaxis().set_major_formatter( adf )
# the day/hour one which prints something like: Thu 17
# 22:30
- Size of tick labels
To set the default for all axes, see #Font_size
To alter for just one axis, try fontsize= on the axis call (often simpler), OR:
for tick in ax.xaxis.get_major_ticks(): tick.label.set_fontsize(14) # possibly make that mybasesize-2 or such
- Rotation of tick labels
When you have a bunch of labels, or long labels, you may care to rotate them.
- rotation='vertical' may be simplest
- you can also use arbitrary rotation, like rotation=45
- with tightly packed labels (and particularly with arbitrary rotation) you may also care about where the label/rotation is anchored. Basically:
For example, compare the following (and try swapping these ha values to see what goes wrong):
rotation=-45, rotation_mode='anchor', ha='left', va='center' rotation= 45, rotation_mode='anchor', ha='right', va='center'
Lines for indicating
Sometimes you want to draw on a plot, to point out thresholds, boundaries, specific values, etc.
e.g.
...and various others, since a lot of drawing can work in data coordinates.
http://stackoverflow.com/questions/16930328/vertical-horizontal-lines-in-matplotlib
Note that for more arbitrary lines, you could do:
plot((x1, x2), (y1, y2), 'k-')
...but this starts to fall under #Annotations
Plot markers (to distinguish series)
http://matplotlib.org/examples/lines_bars_and_markers/marker_reference.html
Annotations
In its basic form, annotations are placed text, optionally using arrows.
You can specify coordinates as
- 'data': same as the data
- 'figure fraction': fraction within the figure
- 'axes fraction': fraction within the axis
- axisobj.transData: a fraction in another axis
- 'offset points': absolute offset in (verify)
- figure points, figure pixels, axes points, axes pixels
- polar
- a 2-tuple, specifying x and y coordinate systems differently, e.g. ("data", "axes fraction")
- matplotlib.patches.ConnectionPatch is sometimes easier to use, though
- matplotlib.text.OffsetFrom(obj, (x,y))
See also things like [6]
Boxes around text happen when you specify bbox. The value to bbox is a dict that specifies how to draw it, a dict including things like:
- boxstyle:"rarrow,pad=0.3"
- fc:"cyan"
- ec:"b"
- lw:2
Note that one alternative is instantiating AnchoredText, which is how legends are made. You can get anchored things more generic than that.
Arrows (other than a bbox with larrow/rarrow/darrow for its shape) happen when you have an xy and an xytext: an arrow will be drawn from the latter to the former (and the text that is the first, non-keyword argument will also be drawn at the latter).
You can have them in different coordinate systems, even:
annotate('look at that', xy=(x1, y1), xycoords='data', xytext=(x2, y2), textcoords='offset points' )
See also:
Second axis
hostaxis.twinx() gives you a new axis based on the host.
If you want a single legend, it seems better to have two derived axes and not use the host.
http://stackoverflow.com/questions/5484922/secondary-axis-with-twinx-how-to-add-to-legend
Drawing less
Removing a plot's text and tickmarks
Sometimes the values on an an axis are more distracting than useful, and you can
ax.set_xticks([]) ax.set_yticks([])
to remove just the ticks and labels, or
ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False)
to also remove an axis label even if it was set
Removing the plot's box, background
To remove only the lines of the main plot box: these are called the spines, so you can e.g.:
axisobj.spines['top'].set_visible(False)
axisobj.spines['right'].set_visible(False)
The background color is the frame.
- You can remove it with frameon=False to the figure
- save() settings can have its own overriding defaults, so sometimes you may need to force e.g. transparent=True on that call
Lines and markers
https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html
https://matplotlib.org/stable/gallery/lines_bars_and_markers/marker_reference.html
Colors
Can be one of:
- Single letter:
- 'b' blue
- 'g' green
- 'r' red
- 'c' cyan
- 'm' magenta
- 'y' yellow
- 'k' black
- 'w' white
- float-as-string: gray shades / colormap, e.g '0.4'
- CSS style: e.g. '#eeefff'
- RGB tuple, e.g. (0.5, 0.5, 0.8)
- RGBA tuple, e.g. (0.5, 0.5, 0.8, 0.2)
Changing line color
Basics:
- Hand it into plot (c= or color=)
- You can also alter the Line2D object returned by plt.plot()
Multi-colored lines:
Most solutions come down to plotting separate parts of the line, each with their own color.
There's:
- not very readable, but more flexible in theory
- figure out start and end indices in the data, and do a series of plot() calls, like:
plot(x[start:end], y[start:end], lw=2, c=segment_color[i])
- numpy mask arrays, separate plot() calls
- more readable if it's a single split based on x or y value.
- The below example colors differently based on a threshold - so two segments. You'ld need to generalize it to be more flexibe
from matplotlib import pyplot
import numpy
x = numpy.array(range(50))
y = numpy.sin(x)
threshold=20
if threshold:
before = (x <= threshold) # note: assumes numpy, won't work when they're python arrays
after = (x >= threshold)
else:
before = numpy.zeros(x.shape[0], dtype=bool)
after = numpy.ones(x.shape[0], dtype=bool)
fig, ax = pyplot.subplots()
ax.plot(x[before],y[before], lw=2, color=(0.0,1.0,0, 1.0), label='data (avrot)')
ax.plot(x[after], y[after], lw=1, color=(0.4,1.0,0, 0.5))
pyplot.show()
Font
Font size
Overall, and per type of thing
You can change de default (which defaults to 12) like:
ourfontsize = 10
matplotlib.pyplot.rcParams["font.size"] = ourfontsize
# The following are by default relative to font.size, but can be set to a fixed size
matplotlib.pyplot.rcParams['axes.titlesize'] = 'large'
matplotlib.pyplot.rcParams['axes.labelsize'] = 'medium'
matplotlib.pyplot.rcParams['figure.titlesize'] = 'medium'
matplotlib.pyplot.rcParams['legend.fontsize'] = 'large'
matplotlib.pyplot.rcParams['xtick.labelsize'] = 0.6*ourfontsize # if you want more control
matplotlib.pyplot.rcParams['ytick.labelsize'] = 0.6*ourfontsize
# (the last because you cannot hand in a scale, only a specific size)
These are factor scales. For reference (taken from font_manager.py):
'xx-small' : 0.579, 'x-small' : 0.694, 'small' : 0.833, 'smaller' : 0.833, 'medium' : 1.0, 'large' : 1.200, 'larger' : 1.2, 'x-large' : 1.440, 'xx-large' : 1.728,
For more structural reuse, see styles: http://matplotlib.org/users/style_sheets.html#style-sheets
For the finest-grained (and fully-OO) control, either
- use the fontsize argument on axis.set_title(), axis.set_xlabel(), axis.legend(), etc.
- use setters, like
for tick in axis.get_xaxis().get_major_ticks():
tick.label.set_fontsize(5)
http://matplotlib.org/examples/pylab_examples/fonts_demo.html
Custom font
See also:
- the font section in this example matplotlibrc file
- http://matplotlib.org/api/font_manager_api.html
Interaction
Changing the status bar
This is updated by the current axis's format_coord function. (The default function formats these vaues via the axis formatters)
It gets x and y, which are coordinates within the actual plot area, and should return a string.
def format_coord(x, y):
centx,centy = 100,100 # assuming you know them for your data
rad = math.sqrt( (centx-x)**2 + (centy-y)**2 )
return ' radius=%.3f x=%4d y=%4d'%( rad, x,y )
axis.format_coord( show_txt )
I regularly find I want to use state beyond x,y -- e.g. fetching a value from the data. You can hardcode a reference to a global -- or put your plotting into classes to cleanly keep the state.
Performance
Picking
Interactive backends let you do things like
def onclick(event):
print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
(event.button, event.x, event.y, event.xdata, event.ydata))
cid = fig.canvas.mpl_connect('button_press_event', onclick)
You can make arbitrary artist objects report events when you click near enough.
See also:
https://matplotlib.org/stable/users/explain/event_handling.html
On figure/axis reuse
If you create a figure, then display it, then create a figure, etc., you'll probably have run into:
RuntimeWarning: More than 20 figures have been opened.
If you went the pyplot route, you can use:
pyplot.close([figure]) # the current figure (default), by reference, index, name, or 'all'
However, you can be faster and more resource-efficient:
- figure.clf() to clear the figure and redo it
- (pyplot.clf clears the current figure)
- axis.cla() to clear the axis and redo it
- (pyplot.cla clears the current axis)
- set_data() on an axis (faster - often fast enough for realtime animation)
Blocking and non-blocking plot windows
The following is irrelevant for the non-interactive backends, because they just create a file and move on.
The sensible cases
- Use from an ipython/jupyter notebook
Entering %matplotlib inline within a notebook means from then on it will pick up on created plot objects, and render them as an inline image (static image in interactive context)
To get interaction with the plot, use:
- %matplotlib nbagg (ipython 2)
- %matplotlib notebook (since ipython 3)
- From an interactive python prompt
Use ipython rather than plain python.
After a %matplotlib [7] it will notice when you produce a figure object(verify) and fires off a window for it. (Without an argument to the magic function it will choose a GUI backend for you. You can specify a specific one if you want.)
- from one-off CLI scripts
If you have a script of the "gather data, show the results, done" sort, then it is actually useful to block program flow while the plots are shown. (in that it means you don't have to ensure some sort of threading, only to have to listen for the figure close to postpone the program's exit)
...which is more or less the default when you use just pyplot.show() with non-embedded interactive GUI backends: They'll create their own window with their own event loop, which is the thing that blocks until the plot window(s) it created are closed.
- Plots with their own interaction
Everything you want to happen can be hooked into interactive events (or a timer), and thereby just the figure's mainloop.
See e.g. some examples here: http://matplotlib.org/examples/widgets/
You There are inbetweens with more control, if you don't mind coding for a specific backend, see e.g. the example here
- Preprogrammed animations
Matplotlib has some built-in, on-a-timer style animation.
These are not interactive (although you can combine this, see the previous point).
See the examples: http://matplotlib.org/examples/animation/
- Embedded in an existing GUI window
That is, you already created a GUI, and happen to want to put a plot somewhere in a window.
You would want to integrate it into your GUI program's event loop. There are examples, see http://matplotlib.org/examples/user_interfaces/
The interesting cases
- from mostly-CLI programs (or the basic python shell)
I frequently write a script that gathers some data, shows it, and updates it later (Be it iterations done within the same program, or watching files, a database, or whatnot)
This means a few different details
- leaving the originating program interactive
- leaving the plot window interactive
- ensuring draws-to-screen happen
- having the originating program wait for the window to close before quitting itself
The exact solution depends on what exactly you are doing. (TODO: examples)
Interactive mode is a good part of the answer (but also a bit confusing, details vary a bit with backend, and parts are a bit experimental). Interactive mode refer more to how it leaves your shell than to the plot itself.(verify)
- means a show() will not block
- means things will not draw() on every state change (verify)
It does not directly imply that a GUI window will be independent and drawing. Says http://matplotlib.org/faq/usage_faq.html#what-is-interactive-mode
Use of an interactive backend (see What is a backend?) permits–but does not by itself require or ensure–plotting to the screen. Whether and when plotting to the screen occurs, and whether a script or shell session continues after a plot is drawn on the screen, depends on the functions and methods that are called, and on a state variable that determines whether matplotlib is in “interactive mode”. The default Boolean value is set by the matplotlibrc file, and may be customized like any other configuration parameter (see Customizing matplotlib). It may also be set via matplotlib.interactive(), and its value may be queried via matplotlib.is_interactive().
pyplot.pause:
If there is an active figure it will be updated and displayed, and the GUI event loop will run during the pause. If there is no active figure, or if a non-interactive backend is in use, this executes time.sleep(interval). This can be used for crude animation.
Notes so far:
- setting interactive mode:
- matplotlib.interactive(True) and/or pyplot.ion()
- matplotlib.interactive(False) and/or pyplot.ioff()
- toggling it in the middle of your program is probably more confusing than useful
- in interactive mode, OO-style (or altering plot state manually) will not imply a draw() (pylab-style calls still do)
- (seems to be for performance(verify))
- so do an explicit pause() or draw()
- calling draw() not actually force a draw on-screen (when and because the backend is separate from us), so...
- pyplot.pause(0.0001) roughtly makes sure the redraw actually happens nowish
- (apparently can be used instead of draw())
- was meant to pace animations that are non-interactive, it just turns out we can abuse it.
- is basically draw(), show(block=False), and runs a matplotlib-side event loop so that our interaction can make it to the GUI-side event loop(verify) (see also canvas.start_event_loop())
- to avoid script-exit meaning closing everything
- in quick and dirty plotting it's close enough to do pyplot.pause(10000000) (three months. Add more zeroes if you care)
- show(block=True) will probably effectively be the same (verify)
- (there are many footnotes that I don't necessarily mention here)
The problem cases
- GUI window from GUI shells
That is, from IDEs like IDLE and such.
Short answer: Works around IPython because it specifically cooperates with matplotlib. But for most other things, the fact you now have two GUI-related event loops in the same interpreter(verify), which is messy at best.
Says http://matplotlib.org/users/shell.html#other-python-interpreters
Gui shells are at best problematic, because they have to run a mainloop, but interactive plotting also involves a mainloop. Ipython has sorted all this out for the primary matplotlib backends. There may be other shells and IDEs that also work with matplotlib in interactive mode, but one obvious candidate does not: the python IDLE IDE is a Tkinter gui app that does not support pylab interactive mode, regardless of backend.
matplotlib in browsers
mpld3 notes
Digging deeper, necessary hacks, lower level notes, etc.
Dates
The easiest way to deal with dates is to hand in datetime objects.
- Matplotlib will do everything for you, primarily choosing an appropriate axis ticker formatter (AutoDateFormatter) (also convert the dates to its interal format).
When you want more manual or lower-level control (e.g. update the plot via set_data), then you probably have to deal with its internal format, possibly set a AutoDateFormatter yourself. There's a basic example above
(You can hand in numbers representing dates and e.g. use plot_date() over plot(), but there are no such alternatives for various other plot functions.)
There is:
- matplotlib's internal format - floating point, days since 0001-01-01 00:00:00 (plus one)
- you tend to not have to care about the actual values, until you care about interactive plots or such
- datetime
- matplotlib.dates.date2num takes a datetime and converts it to such a number
- matplotlib.dates.num2date takes such a number and returns a datetime
- (will handle a single value or a sequence)
- unix timestamp
- matplotlib.dates.epoch2num takes unix time
- matplotlib.dates.num2epoch produces unix time
- (will handle a single value or a sequence)
- mx2num and num2mx
As always, keep on the watch for timezone stuff (e.g. when converting from datetime or mx). Google around.
See also:
Running from minimal environments (like a web server)
matplotlib will like to default to an interactive backend
To run without X, choose a backend that doesn't use one, before a call that implicitly chooses a backend for you (pylab, also pyplot).
Choices include Cairo (usually looks nicest), Agg, or things like ps or pdf, and more. For example:
import matplotlib
matplotlib.use('Cairo')
matplotlib wants a HOME directory, and it should be writeable.
This usually only matters when embedding in web environments (apache, mod_wsgi, CGI) where HOME is typically not set. You should alter this before you import matplotlib, like:
import os
env=os.environ
if 'HOME' not in env:
env['HOME']='/tmp/' #a unique, empty subdir of /tmp would probably be slightly safer
import matplotlib
Writing to images
Backends will generally implement
matplotlib.pyplot.savefig
This takes
- a path
- a Python file-like object
- various backends accept StringIO/BytesIO, some not though this may have changed by now
- something backend-specific - e.g. [8] lets you write multi-page PDFs
Writing to images (memory-only)
This will be somewhat specific to the backend you prefer.
Cairo
Cairo can savefig() to a (c)StringIO objects, which is easier and more memory-efficient than going via an additional uncompressed raster.
By default it writes PNGs, and you can make it write other formats by handing along a format parameter to savefig, such as savefig(sio,format='PNG'), or one of 'PDF', 'PS', 'EPS', 'SVG'.
For example, we might wrap our plot-generating code like:
import matplotlib
from matplotlib.backends.backend_cairo import FigureCanvasCairo
fig = matplotlib.figure.Figure( figsize=(4,3), dpi=100 )
canvas = FigureCanvasCairo(fig)
# plot code
ax = fig.add_subplot(111)
X = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Y = [ 0., 0.84, 0.90, 0.14, -0.75, -0.95, -0.27 , 0.65 , 0.985, 0.41 ]
ax.bar( X,Y )
output = StringIO.StringIO()
figure.savefig(output, format='PNG')
pngbytes = output.getvalue()
output.close()
Agg
You can ask Agg's figure/canvas for RGB pixel data (note: easily quite large).
Can be useful when you want to send it straight to PIL without an intermediate compressed form.
#If you do things at the figure level (i.e. pylab), get the underlying canvas,
# and make sure the figure is drawn on the canvas (you may well get an empty image if it isn't)
canv = figure.canvas
canv.draw()
# Get the canvas' pixel size as a (x,y) tuple; you'll need to hand this to PIL along with the raw data.
# (You could probably do this at canvas level, but I prefer to use the underlying renderer so that you don't have to remember the dpi)
rend = canv.get_renderer()
pixelsize = rend.get_canvas_width_height()
pizelsize = list(int(v) for v in pixelsize) # apparently this became floats at some point,
# which the below does not like.
# Export raw image (also possible at canvas level)
rgbImg = rend.tostring_rgb()
# then e.g. read it into a PIL image.
im=Image.fromstring('RGB', pixelsize, rgbImg)
# You can use transparency, but ARGB is larger and takes a little more code to explain to PIL
pngdata = StringIO() #Make PIL save the image and store it into a string:
im.save(pngdata, 'PNG')
Backend list
These lists will change over time
Image backends:
- Cairo
- Agg
- PS
- SVG
- EMF
- pgf (referring to pgfplots(verify))
UI/Interactive backends:
- TkAgg (probably the most portable)
- WX
- WXAgg
- QtAgg
- Qt4Agg
- Qt5Agg
- gdk
- GTK
- GTKCairo
- GTKAgg
- CocoaAgg
- MacOSX (verify)
- FltkAgg
Special/unsorted cases:
User-made:
- mpld3 [9]
- matascii
Notes:
- Most backends can savefig() to a file.
- The UI backends also react to show() and are often interactive.
- Producing raster images for web use may be easiest with Cairo, because:
- It allows StringIO-based saving, which most others do not
- You do not have to switch backends to produce PNG, PDF, EPS, SVG -- which is useful because you cannot switch backends (an imposed restioction because not all backends react well to this) when you use persisting interpreters, which might apply to WSGI, mod_python and more.
See also:
On memory leaks
If you have long-running processes, it matters that state not cleaned up ends up being a memory leak.
As far as I can see, pylab doesn't even try to garbage collect.
The OO interface can, but there seems to be a known reference counting bug[10], and the basic solution is to close the figure:
matplotlib.pyplot.close(fig)
This isn't ideal when you intended to endlessly reuse the figure (e.g. use clf()).
See also:
- https://matplotlib.org/api/_as_gen/matplotlib.pyplot.close.html
- https://stackoverflow.com/questions/8213522/when-to-use-cla-clf-or-close-for-clearing-a-plot-in-matplotlib
Issues
I get an extra blank window
Matplotlib tries to defer drawing work, but some functions work on the current state (either fundamentally and/or because of the way it's called).
For those functions, if a figure does not yet exist, it will make one.
For example, if
- you create a figure,
- then do a fig.tight_layout(),
- then do a pyplot.show()
you will end up with an empty window, because tight_layout made one for you
- you create a figure,
- then do a pyplot.show()
- then do a fig.tight_layout(),
it's fine.