I would like to create an animation of two axes. In the simple example illustrated below, I would like to plot two matrices using imshow as a function of time.
Coming from python, I would create an animation using matplotlib.animation similar to this:
using PyCall
#pyimport matplotlib.animation as anim
using PyPlot
import IJulia
A = randn(20,20,20,2)
fig, axes = PyPlot.subplots(nrows=1, ncols=2, figsize=(7, 2.5))
ax1, ax2 = axes
function make_frame(i)
ax1.clear()
ax2.clear()
ax1.imshow(A[:,:,i+1, 1])
ax2.imshow(A[:,:,i+1, 2])
end
withfig(fig) do
myanim = anim.FuncAnimation(fig, make_frame, frames=size(A,3), interval=20, blit=false)
myanim[:save]("test.mp4", bitrate=-1, extra_args=["-vcodec", "libx264", "-pix_fmt", "yuv420p"])
end
This however just creates a blank animation.
Do I need to use an init_func in the FuncAnimation? Do I need to enable blitting? Or can I update the artist using a set_data attribute?
Don't use IJulia for this routine. If you plan to include the routine in a notebook, just run the code and then view the file you create, without using withfig. withfig is causing your animation creation to abort for some reason, probably because it expects something within an IJulia environment to be set differently.
This works:
using PyCall
#pyimport matplotlib.animation as anim
using PyPlot
A = randn(20,20,20,2)
fig, axes = PyPlot.subplots(nrows=1, ncols=2, figsize=(7, 2.5))
ax1, ax2 = axes
function make_frame(i)
ax1.clear()
ax2.clear()
ax1.imshow(A[:,:,i+1, 1])
ax2.imshow(A[:,:,i+1, 2])
end
myanim = anim.FuncAnimation(fig, make_frame, frames=size(A,3), interval=20, blit=false)
myanim[:save]("test.mp4", bitrate=-1, extra_args=["-vcodec", "libx264", "-pix_fmt", "yuv420p"])
# now you can call your video viewer on "test.mp4"
Related
I have been banging my head to draw correct plot using the below code. The below code is a piece from a large code. At some point, I am calling this function with reg as the data input (which is very big) to plot it. When I try the below code on Jupyter it works well. But when I try to it in my pipeline, it does not work. It simply gives me a normal plot not the seaborn-paper style based. Any help please
import matplotlib.pyplot as plt
from matplotlib import rc
f = plt.figure()
f.clear()
plt.clf()
plt.close(f)
fig, ax = plt.subplots(frameon=False)
rc('mathtext',default='regular')
rc('text', usetex=True)
plt.style.use('seaborn-paper')
col = {'sar':'b', 'r':'r'}
pslon_ix = ['sar', 'r']
reg = {'sar':{1:[1,2,3],2:[3,4,5]}, 'r':{1:[1,1,4],2:[1,3,6]}}
labels = {'sar':'sar', 'r':'r'}
for pslon in pslon_ix:
cum_reg = [sum(x)/3 for x in zip(*reg[pslon].values())]
ax.plot(range(3), cum_reg, c=col[pslon], ls='-', label=labels[pslon])
ax.set_xlabel(r'pslon')
ax.set_ylabel('cumreg')
ax.legend()
fig.savefig('sim.pdf',format='pdf')
plt.close()
f = plt.figure()
f.clear()
plt.close(f)
Managed to solve it by using
with plt.style.context(("seaborn-paper",)):
I am using a package called shap which has a integrated plot function. However i want to adjust some things like the labels, legend, coloring, size etc.
apparently due to the developer thats possible via using plt.gcf().
I call the plot like this, this will give a figure object but i am not sure how to use it:
fig = shap.summary_plot(shap_values_DT, data_train,color=plt.get_cmap("tab10"), show=False)
ax = plt.subplot()
UPDATE / SOLUTION
Finally i got everything adjusted as i wanted it by doing the following:
shap.summary_plot(shap_values_DT, data_train, color=plt.get_cmap("tab10"), show=False)
fig = plt.gcf()
fig.set_figheight(12)
fig.set_figwidth(14)
ax = plt.gca()
ax.set_xlabel(r'durchschnittliche SHAP Werte $\vert\sigma_{ij}\vert$', fontsize=16)
ax.set_ylabel('Inputparameter', fontsize=16)
ylabels = string_latexer([tick.get_text() for tick in ax.get_yticklabels()])
ax.set_yticklabels(ylabels)
leg = ax.legend()
for l in leg.get_texts(): l.set_text(l.get_text().replace('Class', 'Klasse'))
plt.show()
Finally i got everything adjusted as i wanted it by doing the following:
shap.summary_plot(shap_values_DT, data_train, color=plt.get_cmap("tab10"), show=False)
fig = plt.gcf()
fig.set_figheight(12)
fig.set_figwidth(14)
ax = plt.gca()
ax.set_xlabel(r'durchschnittliche SHAP Werte $\vert\sigma_{ij}\vert$', fontsize=16)
ax.set_ylabel('Inputparameter', fontsize=16)
ylabels = string_latexer([tick.get_text() for tick in ax.get_yticklabels()])
ax.set_yticklabels(ylabels)
leg = ax.legend()
for l in leg.get_texts(): l.set_text(l.get_text().replace('Class', 'Klasse'))
plt.show()
I have not used shap yet, but maybe you can modify in the following way:
shap.summary_plot(shap_values_DT, data_train,color=plt.get_cmap("tab10"), show=False)
plt.title('my custom title')
plt.savefig('test.png')
Update
From the official documentation, I read
import xgboost
import shap
# load JS visualization code to notebook
shap.initjs()
# train XGBoost model
X,y = shap.datasets.boston()
model = xgboost.train({"learning_rate": 0.01}, xgboost.DMatrix(X, label=y), 100)
# explain the model's predictions using SHAP values
# (same syntax works for LightGBM, CatBoost, and scikit-learn models)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X)
# visualize the first prediction's explanation (use matplotlib=True to avoid Javascript)
shap.force_plot(explainer.expected_value, shap_values[0,:], X.iloc[0,:])
I quickly tried the example and it seems to work, if you add the matplotlib=True option. Nevertheless, not all functions seem to support it...
I would like to generate a centered figure legend for subplot(s), for which there is a single label. For my actual use case, the number of subplot(s) is greater than or equal to one; it's possible to have a 2x2 grid of subplots and I would like to use the figure-legend instead of using ax.legend(...) since the same single label entry will apply to each/every subplot.
As a brief and simplified example, consider the code just below:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y, color='orange', label='$f(x) = sin(x)$')
fig.subplots_adjust(bottom=0.15)
fig.legend(mode='expand', loc='lower center')
plt.show()
plt.close(fig)
This code will generate the figure seen below:
I would like to use the mode='expand' kwarg to make the legend span the entire width of the subplot(s); however, doing so prevents the label from being centered. As an example, removing this kwarg from the code outputs the following figure.
Is there a way to use both mode='expand' and also have the label be centered (since there is only one label)?
EDIT:
I've tried using the bbox_to_anchor kwargs (as suggested in the docs) as an alternative to mode='expand', but this doesn't work either. One can switch out the fig.legend(...) line for the line below to test for yourself.
fig.legend(loc='lower center', bbox_to_anchor=(0, 0, 1, 0.5))
The handles and labels are flush against the left side of the legend. There is no mechanism to allow for aligning them.
A workaround could be to use 3 columns of legend handles and fill the first and third with a transparent handle.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
y = np.sin(x)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.15)
line, = ax.plot(x, y, color='orange', label='$f(x) = sin(x)$')
proxy = plt.Rectangle((0,0),1,1, alpha=0)
fig.legend(handles=[proxy, line, proxy], mode='expand', loc='lower center', ncol=3)
plt.show()
Here is a toy piece of code that illustrates my problem:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], '-o', animated=True)
def init():
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)
return ln,
def update(frame):
xdata.append(frame)
ydata.append(np.sin(frame))
ln.set_data(xdata, ydata)
ax.set_xlim(np.amin(xdata), np.amax(xdata))
return ln,
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init, blit=True)
plt.show()
If I set blit=True then the data points are plotted just how I want them. However, the x-axis labels/ticks remain static.
If I set blit=False then the x-axis labels and ticks update just how I want them. However, none of the data points are ever plotted.
How can I get both the plotted data (sine curve) and the x-asis data to update"?
First concerning blitting: Blitting is only applied to the content of the axes. It will affect the inner part of the axes, but not the outer axes decorators. Hence if using blit=True the axes decorators are not updated. Or inversely put, if you want the scale to update, you need to use blit=False.
Now, in the case from the question this leads to the line not being drawn. The reason is that the line has its animated attribute set to True. However, "animated" artists are not drawn by default. This property is actually meant to be used for blitting; but if no blitting is performed it will result in the artist neither be drawn nor blitted. It might have been a good idea to call this property blit_include or something similar to avoid confusion from its name.
Unfortunately, it looks like it's also not well documented. You find however a comment in the source code saying
# if the artist is animated it does not take normal part in the
# draw stack and is not expected to be drawn as part of the normal
# draw loop (when not saving) so do not propagate this change
So in total, one can ignore the presence of this argument, unless you use blitting. Even when using blitting, it can be ignored in most cases, because that property is set internally anyways.
To conclude the solution here is to not use animated and to not use blit.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], '-o')
def init():
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)
def update(frame):
xdata.append(frame)
ydata.append(np.sin(frame))
ln.set_data(xdata, ydata)
ax.set_xlim(np.amin(xdata), np.amax(xdata))
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init)
plt.show()
I'm having issues with redrawing the figure here. I allow the user to specify the units in the time scale (x-axis) and then I recalculate and call this function plots(). I want the plot to simply update, not append another plot to the figure.
def plots():
global vlgaBuffSorted
cntr()
result = collections.defaultdict(list)
for d in vlgaBuffSorted:
result[d['event']].append(d)
result_list = result.values()
f = Figure()
graph1 = f.add_subplot(211)
graph2 = f.add_subplot(212,sharex=graph1)
for item in result_list:
tL = []
vgsL = []
vdsL = []
isubL = []
for dict in item:
tL.append(dict['time'])
vgsL.append(dict['vgs'])
vdsL.append(dict['vds'])
isubL.append(dict['isub'])
graph1.plot(tL,vdsL,'bo',label='a')
graph1.plot(tL,vgsL,'rp',label='b')
graph2.plot(tL,isubL,'b-',label='c')
plotCanvas = FigureCanvasTkAgg(f, pltFrame)
toolbar = NavigationToolbar2TkAgg(plotCanvas, pltFrame)
toolbar.pack(side=BOTTOM)
plotCanvas.get_tk_widget().pack(side=TOP)
You essentially have two options:
Do exactly what you're currently doing, but call graph1.clear() and graph2.clear() before replotting the data. This is the slowest, but most simplest and most robust option.
Instead of replotting, you can just update the data of the plot objects. You'll need to make some changes in your code, but this should be much, much faster than replotting things every time. However, the shape of the data that you're plotting can't change, and if the range of your data is changing, you'll need to manually reset the x and y axis limits.
To give an example of the second option:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 6*np.pi, 100)
y = np.sin(x)
# You probably won't need this if you're embedding things in a tkinter plot...
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(x, y, 'r-') # Returns a tuple of line objects, thus the comma
for phase in np.linspace(0, 10*np.pi, 500):
line1.set_ydata(np.sin(x + phase))
fig.canvas.draw()
fig.canvas.flush_events()
You can also do like the following:
This will draw a 10x1 random matrix data on the plot for 50 cycles of the for loop.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
for i in range(50):
y = np.random.random([10,1])
plt.plot(y)
plt.draw()
plt.pause(0.0001)
plt.clf()
This worked for me. Repeatedly calls a function updating the graph every time.
import matplotlib.pyplot as plt
import matplotlib.animation as anim
def plot_cont(fun, xmax):
y = []
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
def update(i):
yi = fun()
y.append(yi)
x = range(len(y))
ax.clear()
ax.plot(x, y)
print i, ': ', yi
a = anim.FuncAnimation(fig, update, frames=xmax, repeat=False)
plt.show()
"fun" is a function that returns an integer.
FuncAnimation will repeatedly call "update", it will do that "xmax" times.
This worked for me:
from matplotlib import pyplot as plt
from IPython.display import clear_output
import numpy as np
for i in range(50):
clear_output(wait=True)
y = np.random.random([10,1])
plt.plot(y)
plt.show()
I have released a package called python-drawnow that provides functionality to let a figure update, typically called within a for loop, similar to Matlab's drawnow.
An example usage:
from pylab import figure, plot, ion, linspace, arange, sin, pi
def draw_fig():
# can be arbitrarily complex; just to draw a figure
#figure() # don't call!
plot(t, x)
#show() # don't call!
N = 1e3
figure() # call here instead!
ion() # enable interactivity
t = linspace(0, 2*pi, num=N)
for i in arange(100):
x = sin(2 * pi * i**2 * t / 100.0)
drawnow(draw_fig)
This package works with any matplotlib figure and provides options to wait after each figure update or drop into the debugger.
In case anyone comes across this article looking for what I was looking for, I found examples at
How to visualize scalar 2D data with Matplotlib?
and
http://mri.brechmos.org/2009/07/automatically-update-a-figure-in-a-loop
(on web.archive.org)
then modified them to use imshow with an input stack of frames, instead of generating and using contours on the fly.
Starting with a 3D array of images of shape (nBins, nBins, nBins), called frames.
def animate_frames(frames):
nBins = frames.shape[0]
frame = frames[0]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
for k in range(nBins):
frame = frames[k]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
del tempCS1
fig.canvas.draw()
#time.sleep(1e-2) #unnecessary, but useful
fig.clf()
fig = plt.figure()
ax = fig.add_subplot(111)
win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate_frames, frames)
I also found a much simpler way to go about this whole process, albeit less robust:
fig = plt.figure()
for k in range(nBins):
plt.clf()
plt.imshow(frames[k],cmap=plt.cm.gray)
fig.canvas.draw()
time.sleep(1e-6) #unnecessary, but useful
Note that both of these only seem to work with ipython --pylab=tk, a.k.a.backend = TkAgg
Thank you for the help with everything.
All of the above might be true, however for me "online-updating" of figures only works with some backends, specifically wx. You just might try to change to this, e.g. by starting ipython/pylab by ipython --pylab=wx! Good luck!
Based on the other answers, I wrapped the figure's update in a python decorator to separate the plot's update mechanism from the actual plot. This way, it is much easier to update any plot.
def plotlive(func):
plt.ion()
#functools.wraps(func)
def new_func(*args, **kwargs):
# Clear all axes in the current figure.
axes = plt.gcf().get_axes()
for axis in axes:
axis.cla()
# Call func to plot something
result = func(*args, **kwargs)
# Draw the plot
plt.draw()
plt.pause(0.01)
return result
return new_func
Usage example
And then you can use it like any other decorator.
#plotlive
def plot_something_live(ax, x, y):
ax.plot(x, y)
ax.set_ylim([0, 100])
The only constraint is that you have to create the figure before the loop:
fig, ax = plt.subplots()
for i in range(100):
x = np.arange(100)
y = np.full([100], fill_value=i)
plot_something_live(ax, x, y)