I am righteously confused with the coding paradigms offered by matplotlib. I am using code like this below to plot some data:
fig=plt.figure(figsize=fig_size) # plt=pyplot defined above
axes1 = fig.add_subplot(111)
axes1.plot(temp, depth, 'k-')
axes1.set_ylim(-600,0)
axes1.set_ylabel(r'Depth $(m)$')
axes1.set_xlim(0,80)
axes1.set_xlabel(r'Temperature (\textcelsius)')
axes1.set_xticks(np.arange(0,100,20))
axes1.grid(True)
plt.savefig(savedir + 'plot.svg', transparent=True)
I'd rather use mpl's object-oriented style than the pylab convenience functions. So question is if I only want to plot one curve, non-interactively, am I using the right figure creation style? (lines 1 & 2). It seems like a lot of separate calls are needed to format the axis labels and so on.
What you're doing looks fine. (And I agree, it's much cleaner to only use pyplot for figure creation and use the OO API for everything else.)
If you'd prefer to make the figure and axes in one call, use plt.subplots.
Also, I find it's a bit cleaner to use fig.savefig instead of plt.savefig. It won't matter in this case, but that way you avoid having to worry about which figure is "active" in the state-machine interface.
For one last thing, you could set the x and y limits with a single call to axes1.axis(...). This is purely a matter of preference. set_xlim and set_ylim are arguably a more readable way of doing it.
The "setters" and "getters" are annoying, but date from when python didn't have properties, if I recall correctly. They've been kept as the main methods partly for backwards compatibility, and partly so that "matlab-isms" like plt.setp are easier to write. In fact, if you wanted you could do
plt.setp(ax, xlabel='Xlabel', ylabel='Ylabel', xticks=range(0, 100, 20))
This avoids having to do three separate calls to set the xlabel, ylabel, and xticks. However, I personally tend to avoid it. I find it's better to be slightly more verbose in most cases. If you find it cleaner or more convenient, though, there's nothing wrong with using setp.
As an example of how I'd write it:
import matplotlib.pyplot as plt
import numpy as np
depth = np.linspace(-600, 0, 30)
temp = (4 * np.random.random(depth.size)).cumsum()
fig, ax = plt.subplots()
ax.plot(temp, depth, 'k-')
ax.axis([0, 80, -600, 0])
ax.set_ylabel(r'Depth $(m)$')
ax.set_xlabel(r'Temperature $(^{\circ}C)$')
ax.set_xticks(np.arange(0, 100, 20))
ax.grid(True)
fig.savefig('plot.svg', transparent=True)
plt.show()
Related
Matplotlib offers these functions:
cla() # Clear axis
clf() # Clear figure
close() # Close a figure window
When should I use each function and what exactly does it do?
They all do different things, since matplotlib uses a hierarchical order in which a figure window contains a figure which may consist of many axes. Additionally, there are functions from the pyplot interface and there are methods on the Figure class. I will discuss both cases below.
pyplot interface
pyplot is a module that collects a couple of functions that allow matplotlib to be used in a functional manner. I here assume that pyplot has been imported as import matplotlib.pyplot as plt.
In this case, there are three different commands that remove stuff:
See matplotlib.pyplot Functions:
plt.cla() clears an axis, i.e. the currently active axis in the current figure. It leaves the other axes untouched.
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.
Which functions suits you best depends thus on your use-case.
The close() function furthermore allows one to specify which window should be closed. The argument can either be a number or name given to a window when it was created using figure(number_or_name) or it can be a figure instance fig obtained, i.e., usingfig = figure(). If no argument is given to close(), the currently active window will be closed. Furthermore, there is the syntax close('all'), which closes all figures.
methods of the Figure class
Additionally, the Figure class provides methods for clearing figures.
I'll assume in the following that fig is an instance of a Figure:
fig.clf() clears the entire figure. This call is equivalent to plt.clf() only if fig is the current figure.
fig.clear() is a synonym for fig.clf()
Note that even del fig will not close the associated figure window. As far as I know the only way to close a figure window is using plt.close(fig) as described above.
There is just a caveat that I discovered today.
If you have a function that is calling a plot a lot of times you better use plt.close(fig) instead of fig.clf() somehow the first does not accumulate in memory. In short if memory is a concern use plt.close(fig) (Although it seems that there are better ways, go to the end of this comment for relevant links).
So the the following script will produce an empty list:
for i in range(5):
fig = plot_figure()
plt.close(fig)
# This returns a list with all figure numbers available
print(plt.get_fignums())
Whereas this one will produce a list with five figures on it.
for i in range(5):
fig = plot_figure()
fig.clf()
# This returns a list with all figure numbers available
print(plt.get_fignums())
From the documentation above is not clear to me what is the difference between closing a figure and closing a window. Maybe that will clarify.
If you want to try a complete script there you have:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(1000)
y = np.sin(x)
for i in range(5):
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(x, y)
plt.close(fig)
print(plt.get_fignums())
for i in range(5):
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(x, y)
fig.clf()
print(plt.get_fignums())
If memory is a concern somebody already posted a work-around in SO see:
Create a figure that is reference counted
plt.cla() means clear current axis
plt.clf() means clear current figure
also, there's plt.gca() (get current axis) and plt.gcf() (get current figure)
Read more here: Matplotlib, Pyplot, Pylab etc: What's the difference between these and when to use each?
The matplotlib documentation
https://matplotlib.org/gallery/lines_bars_and_markers/simple_plot.html
provides the following example code
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
# Data for plotting
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='About as simple as it gets, folks')
ax.grid()
fig.savefig("test.png")
plt.show()
to produce the following plot
The exact same plot can be produced with the following code
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
plt.plot(t,s)
plt.grid(True)
plt.xlabel('time (s)')
plt.ylabel('voltage (mV)')
plt.title('About as simple as it gets, folks')
plt.savefig("test.png")
plt.show()
which makes no use of subplot, ax, or fig.
I learned how to plot using the second approach. I've stumbled upon the first approach only recently
and although I can kind of imagine what it's doing, it does seem far less intuitive than the second approach.
Can somebody explain the rational behind the first approach as it seems to complicate something very simple?
The pyplot approach may look more intuitive at first sight indeed. And of course that is to a large extent subjective. So feel free to use it if it suits your needs.
pyplot is a so-called state machine. It will operate on the "current figure" and "current axes". However, it will then, like in the first case, call the respective methods of the current objects. E.g. plt.plot(...) will just call ax = plt.gca(); ax.plot(...) internally anyways. So one might decide to directly work with the objects in question, instead of relying on pyplot to find those.
Also, pyplot does not expose matplotlib's complete functionality. E.g. if you want to create a formatter for your ticklabels, you will need to use the object oriented interface anyways,
plt.gca().xaxis.set_major_formatter(...)
in which case one might also be tempted to directly have an axes ax at one's disposal to work on
ax.xaxis.set_major_formatter(...)
Equally if you want to write a function to plot something to an axes, you might let this function take the axes object as input
def myplot(ax, data, **kwargs):
ax.plot(data, label = "My plot", **kwargs)
ax.set_title("My plot")
ax.legend()
Then reusing such function on any axes is pretty handy. Maybe you have a single figure with an axes to plot to,
fig, ax = plt.subplots()
myplot(ax, data)
or you have several axes in different figures
fig1, (ax1, ax2) = plt.subplots(ncols=2)
fig2, (ax3, ax4) = plt.subplots(ncols=2)
myplot(ax2, data2)
myplot(ax4, data3)
There are also cases, where you don't want to use pyplot at all, e.g. when embedding your plot into a GUI, in which case you'd need to use a structure like
fig = matplotlib.figure.Figure()
canvas = FigureCanvasQT(fig)
axes = fig.subplots(2,3)
axes[0,1].plot(...)
Finally, it should be noted that a lot of issues that people ask about here on Stackoverflow directly result from pyplot being used instead of the object-oriented approach. That's not to say, you shouldn't use it, but in overall terms it seems like pyplot increases the chances for shooting yourself in the foot if you don't pay maximum attention to what the "current axes" is while working with pyplot.
Due to all of the above, matplotlib came to the conclusion that it's best to advocate using the object-oriented approach, even though it might, for very simple examples like the ones from the question, be less straight forward to use. Even if not using the object oriented style, understanding it is somehow a prerequisite of using matplotlib even for midly complex plots.
I am creating a large array of subplots and I want to turn off axes for all the subplots.
Currently I am achieving this by
fig, ax = plt.subplots(7, len(clusters))
fig.subplots_adjust(wspace=0, top=1.0, bottom=0.5, left=0, right=1.0)
for x in ax.ravel():
x.axis("off")
but looping over the subplots to turn of the axes individually is ugly.
Is there a way to tell subplots to turn od axes at creation time
or some setting on Figure or pyplot that turns axes off globally.
pyplot.axis('off') turns off axes just on the last subplot.
I agree with #tcaswell that you should probably just use what you're already using. Another option to use it as a function is to use numpy.vectorize():
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(7, len(clusters))
np.vectorize(lambda ax:ax.axis('off'))(ax)
or, if you need to invoke it multiple times, by assigning the vectorized function to a variable:
axoff_fun = np.vectorize(lambda ax:ax.axis('off'))
# ... stuff here ...
fig, ax = plt.subplots(7, len(clusters))
axoff_fun(ax)
Again, note that this is the same thing that #tcaswell suggested, in a fancier setting (only slower, probably). And it's essentially the same thing you're using now.
However, if you insist on doing it some other way (i.e. you are a special kind of lazy), you can set matplotlib.rcParams once, and then every subsequent axes will automatically be off. There's probably an easier way to emulate axis('off'), but here's how I've succeeded:
import matplotlib as mpl
# before
mpl.pyplot.figure()
mpl.pyplot.plot([1,3,5],[4,6,5])
# kill axis in rcParams
mpl.rc('axes.spines',top=False,bottom=False,left=False,right=False);
mpl.rc('axes',facecolor=(1,1,1,0),edgecolor=(1,1,1,0));
mpl.rc(('xtick','ytick'),color=(1,1,1,0));
# after
mpl.pyplot.figure()
mpl.pyplot.plot([1,3,5],[4,6,5])
Result before/after:
Hopefully there aren't any surprises which I forgot to override, but that would become clear quite quickly in an actual application anyway.
A fellow programmer alerted me to a problem where matplotlib.pyplot and Tkinter don't behave well together, as demonstrated by this question Tkinter/Matplotlib backend conflict causes infinite mainloop
We changed our code to prevent potential problems as mentioned in the linked question, as follows:
Old
import matplotlib.pyplot as plt
self.fig = plt.figure(figsize=(8,6))
if os.path.isfile('./UI.png'):
image = plt.imread('./UI.png')
plt.axis('off')
plt.tight_layout()
im = plt.imshow(image)
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master = master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH,expand=YES)
self.canvas.draw()
Intermediate (UI.png not being shown)
import matplotlib.pyplot as plt
import matplotlib
self.fig = matplotlib.figure.Figure(figsize=(8, 6))
if os.path.isfile('./UI.png'):
image = matplotlib.image.imread('./UI.png')
plt.axis('off')
plt.tight_layout()
plt.imshow(image)
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH, expand=YES)
self.canvas.draw()
The changed code did not display the 'background' image anymore and I have been mostly just trying random things (as I am quite lost in the difference between the two options) to get the figure displaying again. The changes involved switching from tight_layout to set_tight_layout to avoid a warning, as mentioned on https://github.com/matplotlib/matplotlib/issues/1852 . The resulting code is as follows:
Potential Fix
import matplotlib.pyplot as plt
import matplotlib
self.fig = matplotlib.figure.Figure(figsize=(8, 6))
background_image = self.fig.add_subplot(111)
if os.path.isfile('./UI.png'):
image = matplotlib.image.imread('./UI.png')
background_image.axis('off')
#self.fig.tight_layout() # This throws a warning and falls back to Agg renderer, 'avoided' by using the line below this one.
self.fig.set_tight_layout(True)
background_image.imshow(image)
# The Canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=master)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, root)
self.canvas.get_tk_widget().pack(fill=BOTH, expand=YES)
self.canvas.draw()
The question therefore is, why do we need to use a subplot now (using matplotlib.figure.Figure) while before we did not (using matplotlib.pyplot)?
PS: I am sorry if this is a silly question but almost everything that I can find on the subject seems to use the matplotlib.pyplot variant. Therefore, I am having trouble finding any good documentation for the matplotlib.figure.Figure variant.
TL;DR
The question therefore is, why do we need to use a subplot now (using matplotlib.figure.Figure) while before we did not (using matplotlib.pyplot)?
subplot creates an Axes object. You did have one before, but the pyplot API "hid" it from you under the covers so you didn't realise it. You are now trying to use the objects directly, so have to handle it yourself.
More detailed reason
The reason you see this behaviour is because of how matplotlib.pyplot works. To quote the tutorial a little:
matplotlib.pyplot is a collection of command style functions that make matplotlib work like MATLAB.... matplotlib.pyplot is stateful, in that it keeps track of the current figure and plotting area, and the plotting functions are directed to the current axes
The crucial bit is that pyplot is stateful. It is keeping track of state "under the covers" and hiding the object model from you to some extent. It also does some implicit things. So - if you simply call, e.g., plt.axis(), under the covers pyplot calls plt.gca() and that in turn calls gcf() which will return a new figure, because you haven't set up a figure via pyplot yet. This is true for most calls to plt.some_function() - if pyplot doesn't have a figure object in it's own state yet, it will create one.
So, in your intermediate example, you've created your own Figure object - fiven it a name self.fig (I'm not sure what your class structure is, so I don't know what self is, but I'm guessing it's your tk.Frame object or something similar).
The punchline
pyplot doesn't know anything about self.fig. So in your intermediate code, you're calling imshow() on the Figure object in pyplot state, but displaying a different figure (self.fig) on your canvas.
The problem is not that you need to use subplot as such, but that you need to change the background image on the correct Figure object. The way you've used subplot in your potential fix code will do that - although I suggest an alternative below which maybe makes the intent clearer.
How to fix
Change
plt.axis('off')
plt.tight_layout()
plt.imshow(image)
to
self.fig.set_tight_layout(True)
ax = self.fig.gca() # You could use subplot here to get an Axes object instead
ax.axis('off')
ax.imshow(image)
A note on root cause: pyplot API vs direct use of objects
This a bit of an opinion, but might help. I tend to use the pyplot interface when I need to quickly get things prototyped and want to use one of the fairly standard cases. Often, that is enough.
As soon as I need to do more complicated things, I start to use the object model directly - maintaining my own named Figure and Axes objects etc.
Mixing the two is possible, but often confusing. You've found this with your intermediate solution. So I recommend doing one or the other.
I'm using matplotlib to interactively view a large dataset. I'd like to zoom/pan the view without redrawing it for performance reasons. It's easy to create the new x view range and apply it with set_xlim(left,right). But there does not seem to be an automatic way to show the new view with the y axis appropriately value limited based on the new x range.
autoscale(enable=True, axis='y') finds the y max and min from all the data drawn, and not restricted to the y data based on the x view limit. Is there any such mode or function, or must set_ylim() be used with a manually calculated range?
Here is an example:
#! /usr/bin/env python
import matplotlib.pyplot as plt
import numpy as np
plt.ioff()
fig = plt.figure(1)
ax = plt.subplot(111, axisbg='black')
tick = np.arange(10)
ax.grid(b=True, color='white')
ax.autoscale(enable=True, axis='x', tight=False)
ax.autoscale(enable=True, axis='y', tight=True)
ax.plot(tick, color='#ff3333', linestyle='-')
fig.canvas.draw()
raw_input('enter:')
ax.set_xlim(2, 5)
fig.canvas.draw()
raw_input('enter:')
ax.set_xlim(1, 6)
fig.canvas.draw()
raw_input('enter:')
I'm sorry, I submitted an answer to the wrong question!
But I should think that it is not that hard to manually calculate the range. Or you can use plt.ion() and zoom in appropriately with the magnifying glass.
I never did find a way through matplotlib to determine the rendered y limits based on x.
However, two factors seem to obviate the need for this.
matplotlib 1.2.1 has big performance gains over the previous version I was using.
For OS X, changing the backend from 'MacOSX' to 'TkAgg' saw big performance improvements for the line styles being used.
In sum, the gains were on the order of 10-20x, which made the interactive browsing of the dataset real-time, so no additional hacks were needed.