Matplotlib crashes after saving many plots - python

I am plotting and saving thousands of files for later animation in a loop like so:
import matplotlib.pyplot as plt
for result in results:
plt.figure()
plt.plot(result) # this changes
plt.xlabel('xlabel') # this doesn't change
plt.ylabel('ylabel') # this doesn't change
plt.title('title') # this changes
plt.ylim([0,1]) # this doesn't change
plt.grid(True) # this doesn't change
plt.savefig(location, bbox_inches=0) # this changes
When I run this with a lot of results, it crashes after several thousand plots are saved. I think what I want to do is reuse my axes like in this answer: https://stackoverflow.com/a/11688881/354979 but I don't understand how. How can I optimize it?

I would create a single figure and clear the figure each time (use .clf).
import matplotlib.pyplot as plt
fig = plt.figure()
for result in results:
fig.clf() # Clears the current figure
...
You are running out of memory since each call to plt.figure creates a new figure object. Per #tcaswell's comment, I think this would be faster than .close. The differences are explained in:
When to use cla(), clf() or close() for clearing a plot in matplotlib?

Although this question is old, the answer would be:
import matplotlib.pyplot as plt
fig = plt.figure()
plot = plt.plot(results[0])
title = plt.title('title')
plt.xlabel('xlabel')
plt.ylabel('ylabel')
plt.ylim([0,1])
plt.grid(True)
for i in range(1,len(results)):
plot.set_data(results[i])
title.set_text('new title')
plt.savefig(location[i], bbox_inches=0)
plt.close('all')

Related

Matplotlib: collecting lines onto the same axis

I'm just starting using Matplotlib the "right" way. I'm writing various programs that will each give me back a time series, and I'm looking to superimpose the graphs of the various time series, like this:
I think what I want is a single Axes instance defined in the main function, then I call each of my little functions, and they all return a Line2D instance, and then I'll put them all on the Axes object I created.
But I'm having trouble taking an existing Line2D object and adding it to an existing Axes object (like I'd want to do with the output of my function.) I thought of taking a Line2D called a and say ax.add_line(a).
import matplotlib.pyplot as plt
a, = plt.plot([1,2,3], [3,4,5], label = 'a')
fig, ax = plt.subplots()
ax.add_line(a)
Gives me a RuntimeError: "Can not put single artist in more than one figure."
I'm guessing that over time Matplotlib has stopped wanting users to be able to add a given line to any Axes they want. A similar thing is discussed in the comments of this answer, except there they're talking about an Axes object in two different Figure objects.
What's the best way to accomplish what I want? I'd rather keep my main script tidy, and not say ax.plot(some_data) over and over when I want to superimpose these lines.
Indeed, you cannot add the same artist to more than one axes or figure.
But for what I understand from your question, that isn't really necessary.
So let's just do as you propose;
"I thought of taking a Line2D called a and say ax.add_line(a)."
import numpy as np
import matplotlib.pyplot as plt
def get_line(label="a"):
return plt.Line2D(np.linspace(0,1,10), np.random.rand(10), label = label)
fig, ax = plt.subplots()
ax.add_line(get_line(label="a"))
ax.add_line(get_line(label="b"))
ax.add_line(get_line(label="z"))
ax.legend()
plt.show()
The way matplotlib would recommend is to create functions that take an axes as input and plot to that axes.
import numpy as np
import matplotlib.pyplot as plt
def plot_line(ax=None, label="a"):
ax = ax or plt.gca()
line, = ax.plot(np.linspace(0,1,10), np.random.rand(10), label = label)
return line
fig, ax = plt.subplots()
plot_line(ax, label="a")
plot_line(ax, label="b")
plot_line(ax, label="z")
ax.legend()
plt.show()
A possible work around for your problem:
import matplotlib.pyplot as plt
x = np.array([1,2,3])
y = np.array([3,4,5])
label = '1'
def plot(x,y,label):
a, = plt.plot(x,y, label = label)
return a
fig, ax = plt.subplots()
plot(x,y,label)
plot(x,1.5*y,label)
You can put your plot command now in a loop with changing labels. You can still use the ax handle to modify/define the plot parameters.

Matplotlib needs careful timing? (Or is there a flag to show plotting is done?)

The following code does not work as expected:
import matplotlib.pyplot as plt
plt.plot(range(100000), '.')
plt.draw()
ax = plt.gca()
lblx = ax.get_xticklabels()
lblx[1]._text = 'hello!'
ax.set_xticklabels(lblx)
plt.draw()
plt.show()
I'm getting the following figure:
I imagine that the reason is that the automatic xticklabels did not have time to be fully created when get_xticklabels() was called. And indeed, by adding plt.pause(1)
import matplotlib.pyplot as plt
plt.plot(range(100000), '.')
plt.draw()
plt.pause(1)
ax = plt.gca()
lblx = ax.get_xticklabels()
lblx[1]._text = 'hello!'
ax.set_xticklabels(lblx)
plt.draw()
plt.show()
would give the expected
I'm not very happy with this state (having to manually insert delays). And my main concern is: How can I know how much time I need to wait? Surely it depends on number of figure elements, machine strength, etc..
So my question is: Is there some flag to know that matplotlib has finished drawing all the elements? Or is there a better way to do what I'm doing?
First it should be noted that you may make an arbitrarily short pause
plt.pause(1e-18)
The issue here is that plt.draw() calls plt.gcf().canvas.draw_idle(). This means that the figure is drawn at some arbitrary point when there is time to do it - hence the _idle.
Instead you would probably want to draw the figure at the specific point in the code you need.
plt.gcf().canvas.draw()
Full code:
import matplotlib.pyplot as plt
plt.plot(range(100000), '.')
plt.gcf().canvas.draw()
ax = plt.gca()
lblx = ax.get_xticklabels()
lblx[1]._text = 'hello!'
ax.set_xticklabels(lblx)
plt.show()

Redrawing Seaborn Figures for Animations

Some seaborn methods like JointPlot create new figures on each call. This makes it impossible to create a simple animation like in matplotlib where iterative calls to plt.cla() or plt.clf() allow to update the contents of a figure without closing/opening the window each time.
The only solution I currently see is:
for t in range(iterations):
# .. update your data ..
if 'jp' in locals():
plt.close(jp.fig)
jp = sns.jointplot(x=data[0], y=data[1])
plt.pause(0.01)
This works because we close the previous window right before creating a new one. But of course, this is far from ideal.
Is there a better way? Can the plot somehow be done directly on a previously generated Figure object? Or is there a way to prevent these methods to generate new figures on each call?
sns.jointplot creates a figure by itself. In order to animate the jointplot, one might therefore reuse this created figure instead of recreating a new one in each iteration.
jointplot internally creates a JointGrid, so it makes sense to directly use this and plot the joint axes and the marginals individually. In each step of the animation one would then update the data, clear the axes and set them up just as during creation of the grid. Unfortunately, this last step involves a lot of code lines.
The final code may then look like:
import matplotlib.pyplot as plt
import matplotlib.animation
import seaborn as sns
import numpy as np
def get_data(i=0):
x,y = np.random.normal(loc=i,scale=3,size=(2, 260))
return x,y
x,y = get_data()
g = sns.JointGrid(x=x, y=y, size=4)
lim = (-10,10)
def prep_axes(g, xlim, ylim):
g.ax_joint.clear()
g.ax_joint.set_xlim(xlim)
g.ax_joint.set_ylim(ylim)
g.ax_marg_x.clear()
g.ax_marg_x.set_xlim(xlim)
g.ax_marg_y.clear()
g.ax_marg_y.set_ylim(ylim)
plt.setp(g.ax_marg_x.get_xticklabels(), visible=False)
plt.setp(g.ax_marg_y.get_yticklabels(), visible=False)
plt.setp(g.ax_marg_x.yaxis.get_majorticklines(), visible=False)
plt.setp(g.ax_marg_x.yaxis.get_minorticklines(), visible=False)
plt.setp(g.ax_marg_y.xaxis.get_majorticklines(), visible=False)
plt.setp(g.ax_marg_y.xaxis.get_minorticklines(), visible=False)
plt.setp(g.ax_marg_x.get_yticklabels(), visible=False)
plt.setp(g.ax_marg_y.get_xticklabels(), visible=False)
def animate(i):
g.x, g.y = get_data(i)
prep_axes(g, lim, lim)
g.plot_joint(sns.kdeplot, cmap="Purples_d")
g.plot_marginals(sns.kdeplot, color="m", shade=True)
frames=np.sin(np.linspace(0,2*np.pi,17))*5
ani = matplotlib.animation.FuncAnimation(g.fig, animate, frames=frames, repeat=True)
plt.show()
using the celluloid package (https://github.com/jwkvam/celluloid) I was able to animate seaborn plots without much hassle:
import numpy as np
from celluloid import Camera
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
fig = plt.figure()
camera = Camera(fig)
# animation draws one data point at a time
for i in range(0, data.shape[0]):
plot = sns.scatterplot(x=data.x[:i], y=data.y[:i])
camera.snap()
anim = camera.animate(blit=False)
anim.save('animation.mp4')
I'm sure similar code could be written for jointplots

Matplotlib: Saved files in a loop aren't the same as in show()

My program shows the correct graph in the plt.show() pop up but not in the fig.savefig one. I'm quite new to python so apologies if it is something simple.
I'm using python 2.7.10, windows (10).
import numpy as np
import matplotlib.pyplot as plt
data = np.genfromtxt('strike_details.txt') #, skip_header= 0
header= 3
information=10000
width = 5
files = 16
types = 4
length = information + header
frames = data[header:length,0]
fig= plt.figure()
plt.grid(True)
for i in range(0,int(files)):
density=data[(header+i*length):(length+i*length),4]
plt.plot(frames,density, label=data[i*length+1][2])
for j in range (0,files/types):
if i==(types*(j+1)-1):
plt.legend(loc='best')
plt.xlabel('$Frames$', fontsize=22)
plt.ylabel('$Density$', fontsize=22)
fig.savefig(str(data[j*length+1][0])+'_'+str(data[j*length+1][1])+'_'+str(data[j*length+1][2])+'.png',format='png', dpi=fig.dpi)
plt.show()
plt.clf()
The program produces four files with different file names but they're all of the first group you see in the plt.show pop up.
If I missed out anything important let me know.
Thanks,
Lio
I think this is due to mixing the API-style and interactive-styles of matplotlib. When you call plt.show() the link between the active figure and fig is broken, and so you continue to output the first figure you created. I can reproduce this problem with this minimal example:
import matplotlib.pyplot as plt
fig = plt.figure()
for n in range(0,10):
plt.plot(list(range(0,n)))
fig.savefig('test%d.png' % n)
plt.show()
plt.clf()
If you remove the show() the issue goes away.
The correct way to do this is to access the current interactive figure via plt.gcf():
plt.gcf().savefig(...)
Alternatively, you can workaround it by recreating the figure object on each loop:
for i in range(0,int(files)):
fig= plt.figure()
plt.grid(True)
...

"Reset original view" does not show the whole plot

I'm plotting a line and updating it in a loop. When I pan the plot at some point during the execution and then click "Reset original view" in the interactive matplotlib window, I am taken back to the plot state from the moment when I started zooming/panning it. Is there a way to see the full extents of the plot instead? Even better, is there a way to tell matplotlib to keep updating the view after this operation?
python 3.4.3, matplotlib 1.4.3
import matplotlib
matplotlib.use('Qt4Agg')
import matplotlib.pyplot as plt
import numpy as np
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
values_v = []
values_i = []
ln1, = ax1.plot(values_i, values_v, color='green')
plt.ion()
plt.show()
for i in range(40):
scopevals = [i, i+2+np.random.rand()]
values_v.append(scopevals[0])
values_i.append(scopevals[1])
ln1.set_data([values_i, values_v])
ax1.relim()
ax1.autoscale_view(True,True,True)
plt.pause(1)
I encountered the problem when I displayed different images in the same figure.
Clearing the old figure helped me:
plt.figure(1) #the figure you re working with
plt.clf() #clear figure
plt.imshow(self.sample_image) # show new picture
And the Original view should work again.
Cheers
Andy

Categories

Resources