Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent matplotlib statefulness

If I create an Axes object in matplotlib and mutate it (i.e. by plotting some data) and then I call a function without passing my Axes object to that function then that function can still mutate my Axes. For example:

import matplotlib.pyplot as plt
import numpy as np

def innocent_looking_function():
    #let's draw a red line on some unsuspecting Axes!
    plt.plot(100*np.random.rand(20), color='r')

fig, ax = plt.subplots()
ax.plot(100*np.random.rand(20), color='b') #draw blue line on ax
#ax now has a blue line, as expected

innocent_looking_function()
#ax now unexpectedly has a blue line and a red line!

My question is: can I prevent this global-variable behaviour in general? I know I can call plt.close() before calling any innocent_looking_function() but is there some way to make this the default?

like image 527
nicolaskruchten Avatar asked Feb 20 '15 14:02

nicolaskruchten


People also ask

Why is %Matplotlib inline?

“With this backend, the output of plotting commands is displayed inline within frontends like the Jupyter notebook, directly below the code cell that produced it. The resulting plots will then also be stored in the notebook document.”

What does PLT CLF () do?

plt. clf() clears the entire current figure with all its axes, but leaves the window opened, such that it may be reused for other plots. plt. close() closes a window, which will be the current window, if not specified otherwise.


1 Answers

Sure! What you need to do is bypass the pyplot state machine entirely when you make your figure.

It's more verbose, as you can't just call fig = plt.figure().


First off, let me explain how plt.gca() or plt.gcf() works. When using the pyplot interface, matplotlib stores all created-but-not-displayed figure managers. Figure managers are basically the gui wrapper for a figure.

plt._pylab_helpers.Gcf is the singleton object that stores the figure managers and keeps track of which one is currently active. plt.gcf() returns the active figure from _pylab_helpers.Gcf. Each Figure object keeps track of it's own axes, so plt.gca() is just plt.gcf().gca().

Normally, when you call plt.figure(), it:

  1. Creates the figure object that's returned
  2. Creates a FigureManager for that figure using the appropriate backend
  3. The figure manager creates a FigureCanvas, gui window (as needed), and NavigationToolbar2 (zoom buttons, etc)
  4. The figure manager instance is then added to _pylab_helpers.Gcf's list of figures.

It's this last step that we want to bypass.


Here's a quick example using a non-interactive backend. Note that because we're not worried about interacting with the plot, we can skip the entire figure manager and just create a Figure and FigureCanvas instance. (Technically we could skip the FigureCanvas, but it will be needed as soon as we want to save the plot to an image, etc.)

import matplotlib.backends.backend_agg as backend
from matplotlib.figure import Figure

# The pylab figure manager will be bypassed in this instance. `plt.gca()`
# can't access the axes created here.
fig = Figure()
canvas = backend.FigureCanvas(fig)
ax = fig.add_subplot(111)

Just to prove that gca can't see this axes:

import matplotlib.pyplot as plt
import matplotlib.backends.backend_agg as backend
from matplotlib.figure import Figure

# Independent figure/axes
fig = Figure()
canvas = backend.FigureCanvas(fig)
ax = fig.add_subplot(111)
ax.plot(range(10))

# gca() is completely unaware of this axes and will create a new one instead:
ax2 = plt.gca()
print 'Same axes?:', id(ax) == id(ax2)

# And `plt.show()` would show the blank axes of `ax2`

With an interactive backed, it's a touch more complicated. You can't call plt.show(), so you need to start the gui's mainloop yourself. You can do it all "from scratch" (see any of the "embedding matplotlib" examples), but the FigureManager abstracts the backed-specific parts away:

As an example using the TkAgg backend:

import matplotlib.backends.backend_tkagg as backend
from matplotlib.figure import Figure

fig = Figure()
ax = fig.add_subplot(111)

manager = backend.new_figure_manager_given_figure(1, fig)
manager.show()
backend.show.mainloop()

To use one of the other backends, just change the backend import. For example, for Qt4:

import matplotlib.backends.backend_qt4agg as backend
from matplotlib.figure import Figure

fig = Figure()
ax = fig.add_subplot(111)

manager = backend.new_figure_manager_given_figure(1, fig)
manager.show()
backend.show.mainloop()

This actually even works with the nbagg backend used in IPython notebooks. Just change the backend import to import matplotlib.backends.backend_nbagg as backend

like image 114
Joe Kington Avatar answered Sep 23 '22 11:09

Joe Kington