Reusing patch objects in matplotlib without them moving position - python

I want to automatically generate a series of plots which are clipped to patches. If I try and reuse a patch object, it moves position across the canvas.
This script (based on an answer to a previous question by Yann) demonstrates what is happening.
import pylab as plt
import scipy as sp
import matplotlib.patches as patches
sp.random.seed(100)
x = sp.random.random(100)
y = sp.random.random(100)
patch = patches.Circle((.75,.75),radius=.25,fc='none')
def doplot(x,y,patch,count):
fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.scatter(x,y)
ax.add_patch(patch)
im.set_clip_path(patch)
plt.savefig(str(count) + '.png')
for count in xrange(4):
doplot(x,y,patch,count)
The first plot looks like this:
But in the second '1.png', the patch has moved..
However replotting again doesn't move the patch. '2.png' and '3.png' look exactly the same as '1.png'.
Could anyone point me in the right direction of what I'm doing wrong??
In reality, the patches I'm using are relatively complex and take some time to generate - I'd prefer to not have to remake them every frame if possible.

The problem can be avoided by using the same axes for each plot, with ax.cla() called to clear the plot after each iteration.
import pylab as plt
import scipy as sp
import matplotlib.patches as patches
sp.random.seed(100)
patch = patches.Circle((.75,.75),radius=.25,fc='none')
fig = plt.figure()
ax = fig.add_subplot(111)
def doplot(x,y,patch,count):
ax.set_xlim(-0.2,1.2)
ax.set_ylim(-0.2,1.2)
x = sp.random.random(100)
y = sp.random.random(100)
im = ax.scatter(x,y)
ax.add_patch(patch)
im.set_clip_path(patch)
plt.savefig(str(count) + '.png')
ax.cla()
for count in xrange(4):
doplot(x,y,patch,count)

An alternative to unutbu's answer, is to use the copy package, which can copy objects. It is very hard to see how things are changing after one calls add_patch, but they are. The axes, figure, extents,clip_box,transform and window_extent properties of the patch are changed. Unfortantely the superficial printing of each of these properties results in the same string, so it looks like they are not changing. But the underlying attributes of some or all of these properties, eg extents is a Bbox, are probably changed.
The copy call will allow you to get a unique patch for each figure you make, without know what kind of patch it is. This still does not answer why this happens, but as I wrote above it's an alternative solution to the problem:
import copy
def doplot(x,y,patch,count):
newPatch = copy.copy(patch)
fig = plt.figure(dpi=50)
ax = fig.add_subplot(111)
im = ax.scatter(x,y)
ax.add_patch(newPatch)
im.set_clip_path(newPatch)
plt.savefig(str(count) + '.png')
Also you can use fig.savefig(str(count) + '.png'). This explicitly saves the figure fig where as the plt.savefig call saves the current figure, which happens to be the one you want.

Related

Returning matplotlib plot/figure from a function and saving it later

As the title states I want to return a plt or figure (still not sure what the difference between the two things are) using matplotlib. The main idea behind it is so I can save the plt/figure later.
import seaborn as sns
from matplotlib import pyplot as plt
def graph(df, id):
# size of the graph
xlims = (-180, 180)
ylims = (-180, 180)
# dictate the colors of the scatter plot based on the grouping of hot or cold
color_dict = {'COLD': 'blue',
'HOT': 'red'}
title_name = f"{id}"
ax = sns.scatterplot(data=df, hue='GRP', x='X_GRID', y='Y_GRID',
legend=False, palette=color_dict)
ax.set_title(title_name)
ax.set(xlim=xlims)
ax.set(ylim=ylims)
if show_grid:
# pass in the prev graph so I can overlay grid
ax = self.__get_grid( ax)
circle1 = plt.Circle(xy=(0, 0), radius=150, color='black', fill=False, zorder=3)
ax.add_patch(circle1)
ax.set_aspect('equal')
plt.axis('off')
plt.savefig(title_name + '_in_ftn.png')
fig = plt.figure()
plt.clf()
return (fig, title_name + '.png')
plots = []
# dfs is just a tuple of df, id for example purposes
for df, id in dfs:
plots.append(graph(df, id))
for plot, file_name in plots:
plot.savefig(file_name)
plot.clf()
When using plot.savefig(filename) it saves, but the saved file is blank which is wrong. Am I not properly returning the object I want to save? If not what should I return to be able to save it?
I kind of having it work, but not really. I am currently saving two figures for testing purposes. For some reason when I use the fig=plt.figure() and saving it outside the function the title of the figure and the filename are different (even though they should be the same since the only difference is .png)
However, when saving it inside the function the title name of the figure and the filename name are the same.
You code has multiple issues that I'll try to discuss here:
Your confusion around plt
First of all, there is no such thing as "a plt". plt is the custom name you are giving to the matplotlib.pyplot module when you are importing it with the line import matplotlib.pyplot as plt. You are basically just renaming the module with an easy to type abbreviation. If you had just written import matplotlib, you would have to write matplotlib.pyplot.axis('off') instead of plt.axis('off').
Mix of procedural and object oriented approach
You are using a mix of the procedural and object oriented approach for matplotlib.
Either you call your methods on the axis object (ax) or you can call functions that implicitly handle the axis and figure. For example you could either create and axis and then call ax.plot(...) or instead use plt.plot(...), which implicitly creates the figure and axis. In your case, you mainly use the object oriented approach on the axis object that is returned by the seaborn function. However, you should use ax.axis('off') instead of plt.axis('off').
You create a new blank figure
When you are calling the seaborn function sns.scatterplot, you are implicitly creating a matplotlib figure and axis object. You catch that axis object in the variable ax. You then use plt.savefig to save your image in the function, which works by implicitly getting the figure corresponding to the currently used axis object. However, you are then creating a new figure by calling fig = plt.figure(), which is of course blank, and then returning it. What you should do, is getting the figure currently used by the axis object you are working with. You can get it by calling fig = plt.gcf() (which stands for "get current figure") and would be the procedural approach, or better use fig = ax.get_figure()
What you should do instead is something like this:
import seaborn as sns
from matplotlib import pyplot as plt
def graph(df, id):
# size of the graph
xlims = (-180, 180)
ylims = (-180, 180)
# dictate the colors of the scatter plot based on the grouping of hot or cold
color_dict = {'COLD': 'blue',
'HOT': 'red'}
title_name = f"{id}"
ax = sns.scatterplot(data=df, hue='GRP', x='X_GRID', y='Y_GRID',
legend=False, palette=color_dict)
ax.set_title(title_name)
ax.set(xlim=xlims)
ax.set(ylim=ylims)
if show_grid:
# pass in the prev graph so I can overlay grid
ax = self.__get_grid( ax)
circle1 = plt.Circle(xy=(0, 0), radius=150, color='black', fill=False, zorder=3)
ax.add_patch(circle1)
ax.set_aspect('equal')
ax.axis('off')
fig = ax.get_figure()
fig.savefig(title_name + '_in_ftn.png')
return (fig, title_name + '.png')

Animating with matplotlib without animation function

Is there a way to animate a graph in matplotlib without resorting to the built in animation functions? I find them extremely awkward to use and feel it would be much simpler to just plot a point, wipe the graph, then plot the next point.
I envision something such as:
def f():
# do stuff here
return x, y, t
where each t would be a different frame.
I mean, I've tried stuff like using plt.clf(), plt.close() etc. but nothing seems to work.
It is sure possible to animate without FuncAnimation. The purpose of "the enivisioned function", however, is not really clear. In an animation, the time is the independent variable, i.e. for each time step you produce some new data to plot or similar. Therefore the function would take t as an input and give some data back.
import matplotlib.pyplot as plt
import numpy as np
def f(t):
x=np.random.rand(1)
y=np.random.rand(1)
return x,y
fig, ax = plt.subplots()
ax.set_xlim(0,1)
ax.set_ylim(0,1)
for t in range(100):
x,y = f(t)
# optionally clear axes and reset limits
#plt.gca().cla()
#ax.set_xlim(0,1)
#ax.set_ylim(0,1)
ax.plot(x, y, marker="s")
ax.set_title(str(t))
fig.canvas.draw()
plt.pause(0.1)
plt.show()
Also, it is not clear why you would want to avoid FuncAnimation. The same animation as above can be produced with FuncAnimation as follows:
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
def f(t):
x=np.random.rand(1)
y=np.random.rand(1)
return x,y
fig, ax = plt.subplots()
ax.set_xlim(0,1)
ax.set_ylim(0,1)
def update(t):
x,y = f(t)
# optionally clear axes and reset limits
#plt.gca().cla()
#ax.set_xlim(0,1)
#ax.set_ylim(0,1)
ax.plot(x, y, marker="s")
ax.set_title(str(t))
ani = matplotlib.animation.FuncAnimation(fig, update, frames=100)
plt.show()
There is not much changed, you have the same number of lines, nothing really awkward to see here.
Plus you have all the benefits from FuncAnimation when the animation gets more complex, when you want to repeat the animation, when you want to use blitting, or when you want to export it to a file.
it is not clear why you would want to avoid FuncAnimation.
For very simple tests, where you want to check a situation deep inside a loop, it is not easy to set up an animation function.
For instance, I wanted to visualize what happens with this strange sort algorithm: https://arxiv.org/pdf/2110.01111.pdf. To my opinion, the simplest way to do it is:
import numpy as np
import matplotlib.pyplot as plt
def sort(table):
n = len(table)
for i in range (n):
for j in range (n):
if table[i] < table[j]:
tmp = table[i]
table[i] = table[j]
table[j] = tmp
plt.plot(table, 'ro')
plt.title(f"i {i} j {j}")
plt.pause(0.001)
plt.clf() # clear figure
return table
n = 50
table = np.random.randint(1,101,n)
sort(table)
```python
I agree that FuncAnimation is awkward to use (not pythonic at all). Actually I believe this function doesn't make too much sense. What is the advantage to have it?
Yes, it introduces an implicit loop that you do not have to write yourself. But the reader cannot fully control this loop and -unless he knows the syntax of the function in advance- he cannot even understand it. Personally I avoid FuncAnimation for reasons of clarity and versatility. Here's a minimal pseudocode example to do that:
fig=plt.figure("animation")
M=zeros((sizeX,sizeY)) # initialize the data (your image)
im=plt.imshow(M) # make an initial plot
########### RUN THE "ANIMATION" ###########################
while {some condition}:
M=yourfunction() # updates your image
im.set_array(M) # prepare the new image
fig.canvas.draw() # draw the image
plt.pause(0.1) # slow down the "animation"
Very simple and you can see what is happening in your code.

Matplotlib multiple lines on graph

I've been having an issue with saving matplotlib graphs as images.
The images are saving differently from what shows up when I call the .show() method on the graph.
An example is here:
http://s1.postimg.org/lbyei5cfz/blue5.png
I'm not sure what else to do. I've spent the past hours trying to figure out what's causing it, but I can't figure it out.
Here is my code in it's entirety.
import matplotlib.pyplot as plt
import random
turn = 1 #for the x values
class Graph():
def __init__(self, name, color):
self.currentValue = 5 #for the y values
self.x = [turn]
self.y = [self.currentValue]
self.name = name
self.color = color
def update(self):
if random.randint(0,1): #just to show if the graph's value goes up or down
self.currentValue += random.randint(0,10)
self.y.append(self.currentValue)
else:
self.currentValue -= random.randint(0,10)
self.y.append(self.currentValue)
self.x.append(turn)
def plot(self):
lines = plt.plot(self.x,self.y)
plt.setp(lines, 'color',self.color)
plt.savefig(self.name + str(turn))
#plt.show() will have a different result from plt.savefig(args)
graphs = [Graph("red",'r'),Graph("blue",'b'),Graph("green",'g')]
for i in range(5):
for i in graphs:
i.update() #changes the x and y value
i.plot() #saves the picture of the graph
turn += 1
Sorry if this is a stupid mistake I'm making, I just find it peculiar how plt.show() and plt.savefig are different.
Thanks for the help.
As stated correctly by David, plt.show() resets current figure. plt.savefig(), however, does not, so you need to reset it explicitly. plt.clf() or plt.figure() are two functions that can do it dor you. Just insert the call right after plt.savefig:
plt.savefig(self.name + str(turn))
plt.clf()
If you want to save the figure after displaying it, you'll need to hold on to the figure instance. The reason that plt.savefig doesn't work after calling show is that the current figure has been reset.
pyplot keeps track of which figures, axes, etc are "current" (i.e. have not yet been displayed with show) behind-the-scenes. gcf and gca get the current figure and current axes instances, respectively. plt.savefig (and essentially any other pyplot method) just does plt.gcf().savefig(...). In other words, get the current figure instance and call its savefig method. Similarly plt.plot basically does plt.gca().plot(...).
After show is called, the list of "current" figures and axes is empty.
In general, you're better off directly using the figure and axes instances to plot/save/show/etc, rather than using plt.plot, etc, to implicitly get the current figure/axes and plot on it. There's nothing wrong with using pyplot for everything (especially interactively), but it makes it easier to shoot yourself in the foot.
Use pyplot for plt.show() and to generate a figure and an axes object(s), but then use the figure or axes methods directly. (e.g. ax.plot(x, y) instead of plt.plot(x, y), etc) The main advantage of this is that it's explicit. You know what objects you're plotting on, and don't have to reason about what the pyplot state-machine does (though it's not that hard to understand the state-machine interface, either).
As an example of the "recommended" way of doing things, do something like:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y)
fig.savefig('fig1.pdf')
plt.show()
fig.savefig('fig2.pdf')
If you'd rather use the pyplot interface for everything, then just grab the figure instance before you call show. For example:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y = x**2
plt.plot(x, y)
fig = plt.gcf()
fig.savefig('fig1.pdf')
plt.show()
fig.savefig('fig2.pdf')
source

Matplotlib pyplot in real time

I have a while function that generates two lists of numbers and at the end I plot them using matplotlib.pyplot.
I'm doing
while True:
#....
plt.plot(list1)
plt.plot(list2)
plt.show()
But in order to see the progression I have to close the plot window.
Is there a way to refresh it with the new data every x seconds?
The most robust way to do what you want is to use matplotlib.animation. Here's an example of animating two lines, one representing sine and one representing cosine.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
sin_l, = ax.plot(np.sin(0))
cos_l, = ax.plot(np.cos(0))
ax.set_ylim(-1, 1)
ax.set_xlim(0, 5)
dx = 0.1
def update(i):
# i is a counter for each frame.
# We'll increment x by dx each frame.
x = np.arange(0, i) * dx
sin_l.set_data(x, np.sin(x))
cos_l.set_data(x, np.cos(x))
return sin_l, cos_l
ani = animation.FuncAnimation(fig, update, frames=51, interval=50)
plt.show()
For your particular example, you would get rid of the while True and put the logic inside that while loop in the update function. Then, you just have to make sure to do set_data instead of making a whole new plt.plot call.
More details can be found in this nice blog post, the animation API, or the animation examples.
I think what you're looking for is the "animation" feature.
Here is an example
This example is a second one.

Accessing axis or figure in python ggplot

python ggplot is great, but still new, and I find the need to fallback on traditional matplotlib techniques to modify my plots. But I'm not sure how to either pass an axis instance to ggplot, or get one back from it.
So let's say I build a plot like so:
import ggplot as gp
(explicit import)
p = gp.ggplot(gp.aes(x='basesalary', y='compensation'), data = df)
p + gp.geom_histogram(binwidth = 10000)
No problems so far. But now let's say I want the y-axis in log scale. I'd like to be able to do this:
plt.gca().set_yscale('log')
Unfortunately, plt.gca() doesn't access the axis created by ggplot. I end up with two figures: the histogram from ggplot in linear scale, and an empty figure with a log-scale y axis.
I've tried a few variations with both gca() and gcf() without success.
There might have been some changes since 2013 when this question was asked. The way to produce a matplotlib figure from a ggplot is
g.make()
after that, figure and axes can be obtained via
fig = plt.gcf()
ax = plt.gca()
or, if there are more axes, axes = fig.axes.
Then, additional features can be added in matplotlib, like also shown in this question's answer.
Finally the plot can be saved using the usual savefig command.
Complete example:
import ggplot as gp
import matplotlib.pyplot as plt
# produce ggplot
g = gp.ggplot(gp.aes(x='carat', y='price'), data=gp.diamonds)
g = g + gp.geom_point()
g = g + gp.ylab(' ')+ gp.xlab(' ')
# Make
g.make()
# obtain figure from ggplot
fig = plt.gcf()
ax = plt.gca()
# adjust some of the ggplot axes' parameters
ax.set_title("ggplot plot")
ax.set_xlabel("Some x label")
plt.savefig(__file__+".png")
plt.show()
[This is outdated with current ggpy]
There is now a scale_y_log(). If you want to do something in matplotlib, you can get the current figure/axis with
g = ggplot(...)
fig = g.draw()
#or
g.draw() # or print(g)
fig = plt.gcf()
ax = plt.gca()
Your version fails because ggplots draws the plot on print(g) in the ggplot.__repr__() method (which calls ggplot.draw()), so there is simple no matplotlib figure right after constructing the ggplot object but only after print (or g.draw()). g.draw() also returns the figure, so you don't need to use plt.gcf()
Did you try:
p = gp.ggplot(gp.aes(x='basesalary', y='compensation'), data = df)
p + gp.geom_histogram(binwidth = 10000) + gp.scale_y_log()
Not sure if it works just like that though, just guessing from looking at the code...

Categories