I search on internet how using a slider with 3D data and I find this algorithm which plot 3D data in 2D with a slider, so I copy-paste it and I tried to run it in order to adapt it (for solving my real problem : plotting 3D+time data and using a slider to interact with the time).
This is my complete code :
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
import scipy.ndimage as ndi
data = np.zeros((10, 10, 10))
data[5, 5, 5] = 10.
data = ndi.filters.gaussian_filter(data, sigma=1)
print(data.max())
def cube_show_slider(cube, axis=0, **kwargs):
"""
Display a 3d ndarray with a slider to move along the third dimension.
Extra keyword arguments are passed to imshow
"""
# check dim
if not cube.ndim == 3:
raise ValueError("cube should be an ndarray with ndim == 3")
# generate figure
fig = plt.figure()
ax = plt.subplot(111)
fig.subplots_adjust(left=0.25, bottom=0.25)
# select first image
s = [slice(0, 1) if i == axis else slice(None) for i in range(3)]
im = cube[s].squeeze()
# display image
l = ax.matshow(im, **kwargs)
cb = plt.colorbar(l)
cb.set_clim(vmin=data.min(), vmax=data.max())
cb.draw_all()
# define slider
axcolor = 'lightgoldenrodyellow'
ax = fig.add_axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
slideryo = Slider(ax, 'Axis %i index' % axis, 0, cube.shape[axis] - 1, valinit=0, valfmt='%i')
slideryo.on_changed(update)
plt.show()
def update(val):
ind = int(slider.val)
s = [slice(ind, ind + 1) if i == axis else slice(None) for i in range(3)]
im = cube[s].squeeze()
l.set_data(im, **kwargs)
cb.set_clim(vmin=data.min(), vmax=data.max())
cb.formatter.set_powerlimits((0, 0))
cb.update_ticks()
cb.draw_all()
fig.canvas.draw()
cube_show_slider(data)
A window with the axis and the slider are on my screen but no data is plotted. The plot is just a big blue square and when I interact with the slider I have this error :
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/backend_bases.py", line 1952, in motion_notify_event
self.callbacks.process(s, event)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/cbook.py", line 563, in process
proxy(*args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/cbook.py", line 430, in __call__
return mtd(*args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/widgets.py", line 434, in _update
self.set_val(val)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/widgets.py", line 448, in set_val
func(val)
File "<stdin>", line 2, in update
NameError: global name 'slider' is not defined
I don't understand why it doesn't work. All the functions and files that the console cite were added by the importation. And I know that the code written by mmensing is ok, so I missed something but what? I'm sure that I did a stupid error, but I don't know where.
To check if the data I created are ok, I write this code to see the 3d plot in 3D without slider :
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
import scipy.ndimage as ndi
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
data = np.zeros((10, 10, 10))
data[5, 5, 5] = 10.
data = ndi.filters.gaussian_filter(data, sigma=1)
ax.plot(data[0,:,:], data[1,:,:], data[2,:,:], label='my data')
ax.legend()
plt.show()
But it returns this error :
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/mpl_toolkits/mplot3d/axes3d.py", line 1541, in plot
lines = Axes.plot(self, xs, ys, *args[argsi:], **kwargs)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/__init__.py", line 1812, in inner
return func(ax, *args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/axes/_axes.py", line 1424, in plot
for line in self._get_lines(*args, **kwargs):
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/axes/_base.py", line 386, in _grab_next_args
for seg in self._plot_args(remaining, kwargs):
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/axes/_base.py", line 339, in _plot_args
raise ValueError('third arg must be a format string')
ValueError: third arg must be a format string
/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/axes/_axes.py:519: UserWarning: No labelled objects found. Use label='...' kwarg on individual plots.
warnings.warn("No labelled objects found. ")
What can I do ?
I have corrected your code, you had some errors which you can find by comparing:
update function needs to be defined in subprogram so that it is accessible there, indentation wrong
your slider had two different names at different positions
refer to update function only after having defined the function.
Hope it works now.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
import scipy.ndimage as ndi
data = np.zeros((10, 10, 10))
data[5, 5, 5] = 10.
data = ndi.filters.gaussian_filter(data, sigma=1)
print(data.max())
print data.shape
def cube_show_slider(cube, axis=0, **kwargs):
"""
Display a 3d ndarray with a slider to move along the third dimension.
Extra keyword arguments are passed to imshow
"""
# check dim
if not cube.ndim == 3:
raise ValueError("cube should be an ndarray with ndim == 3")
# generate figure
fig = plt.figure()
ax = plt.subplot(111)
fig.subplots_adjust(left=0.25, bottom=0.25)
# select first image
s = [slice(0, 1) if i == axis else slice(None) for i in range(3)]
im = cube[s].squeeze()
# display image
l = ax.matshow(im, **kwargs)
cb = plt.colorbar(l)
cb.set_clim(vmin=data.min(), vmax=data.max())
cb.draw_all()
# define slider
axcolor = 'lightgoldenrodyellow'
ax = fig.add_axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
slideryo = Slider(ax, 'Axis %i index' % axis, 0, cube.shape[axis] - 1, valinit=0, valfmt='%i')
def update(val):
ind = int(slideryo.val)
s = [slice(ind, ind + 1) if i == axis else slice(None) for i in range(3)]
im = cube[s].squeeze()
l.set_data(im, **kwargs)
cb.set_clim(vmin=data.min(), vmax=data.max())
cb.formatter.set_powerlimits((0, 0))
cb.update_ticks()
cb.draw_all()
fig.canvas.draw()
slideryo.on_changed(update)
plt.show()
cube_show_slider(data)
Related
I'm trying to animate a particle collision animation in one axes object while simultaneously plotting some data in another axis object (using matplotlib.gridspec).
I set up a test environment with test data, simplifying the collision to just appearing circles in axes 1 (matplotlib.patches.Circle objects) and plotting a sine function in axes 2 (matplotlib.lines.Line2D object).
The animation works for one axes at a time, but I can't run both axes animations simultaneously.
I think the problem is related to the return objects in the init() and animate(i) function. The "matplotlib.patches" and "matplotlib.lines" object have problems in combination with the animation, but I can't figure out why.
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib.animation import FuncAnimation
from matplotlib.gridspec import GridSpec
#%% setup test data
xy = (0.5, 0.5)
r = 0.02
i = np.arange(10,110)
#%% setup figure
fig = plt.figure(figsize = (8,8))
gs = GridSpec(6,4, figure=fig, wspace=0, hspace=0)
ax0 = fig.add_subplot(gs[0:4,0:4])
ax1 = fig.add_subplot(gs[5:6,0:4])
ax1.set_xlim(0,100)
# assigning labels
circles = []
ln1, = ax1.plot([],[], 'r')
def init():
ln1.set_data([],[])
circles = []
return circles, ln1
def animate(i):
xy = (np.random.random(), np.random.random())
circles.append(ax0.add_patch(Circle(xy,r)))
x = np.linspace(0,100,100)
y = np.sin(x)
mask1 = x < i
x1 = x[mask1]
y1 = y[mask1]
ln1.set_data(x1, y1)
return circles, ln1
ani = FuncAnimation(fig, animate, init_func=init, frames = i, blit=True, interval = 1)
plt.show()
That is the error message I receive:
Traceback (most recent call last):
File "C:\Users\flofr\anaconda3\lib\site-packages\matplotlib\cbook\__init__.py", line 216, in process
func(*args, **kwargs)
File "C:\Users\flofr\anaconda3\lib\site-packages\matplotlib\animation.py", line 953, in _start
self._init_draw()
File "C:\Users\flofr\anaconda3\lib\site-packages\matplotlib\animation.py", line 1741, in _init_draw
a.set_animated(self._blit)
AttributeError: 'list' object has no attribute 'set_animated'
Traceback (most recent call last):
File "C:\Users\flofr\anaconda3\lib\site-packages\matplotlib\cbook\__init__.py", line 216, in process
func(*args, **kwargs)
File "C:\Users\flofr\anaconda3\lib\site-packages\matplotlib\animation.py", line 1269, in _handle_resize
self._init_draw()
File "C:\Users\flofr\anaconda3\lib\site-packages\matplotlib\animation.py", line 1741, in _init_draw
a.set_animated(self._blit)
AttributeError: 'list' object has no attribute 'set_animated'
I tried to test if the return values are correct for init() and it seems to be the case.
Input:
def init():
ln1.set_data([],[])
circles = []
circles.append(ax0.add_patch(Circle(xy,r)))
return ln1, circles
a, b = init()
print(a)
print(b)
Output:
Line2D(_line0)
[<matplotlib.patches.Circle object at 0x000001E7DE16F548>]
Here are the three states of the simulation. Imgage one shows the layout, image two the axes 1 simulation (circles) and image three the axes 2 simulation (sine).
I have the following data and labels I am transforming through PCA.
The labels are only 0 or 1.
from mpl_toolkits.mplot3d import Axes3D
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import seaborn as sns
import numpy as np
fields = ["Occupancy", "Temperature", "Humidity", "Light", "CO2", "HumidityRatio", "NSM", "WeekStatus"]
df = pd.read_csv('datatraining-updated.csv', skipinitialspace=True, usecols=fields, sep=',')
#Get the output from pandas as a numpy matrix
final_data=df.values
#Data
X = final_data[:,1:8]
#Labels
y = final_data[:,0]
#Normalize features
X_scaled = StandardScaler().fit_transform(X)
#Apply PCA on them
pca = PCA(n_components=7).fit(X_scaled)
#Transform them with PCA
X_reduced = pca.transform(X_scaled)
Then, I just want to show, in a 3D graph, the 3 PCA features with highest variance, I can find them as follows
#Show variable importance
importance = pca.explained_variance_ratio_
print('Explained variation per principal component:
{}'.format(importance))
After that, I want to plot only the top-3 highest variance features. So, I previously select them in the code below
X_reduced=X_reduced[:, [0, 4, 5]]
Ok, here is my problem: I can plot them without the legend. When I try to plot them using the following code
# Create plot
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax = fig.gca(projection='3d')
colors = ("red", "gray")
for data, color, group in zip(X_reduced, colors, y):
dim1,dim2,dim3=data
ax.scatter(dim1, dim2, dim3, c=color, edgecolors='none',
label=group)
plt.title('Matplot 3d scatter plot')
plt.legend(y)
plt.show()
I get the following error:
plot_data-3d-pca.py:56: UserWarning: Requested projection is different from current axis projection, creating new axis with requested projection.
ax = fig.gca(projection='3d')
plot_data-3d-pca.py:56: MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance. In a future version, a new instance will always be created and returned. Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance.
ax = fig.gca(projection='3d')
Traceback (most recent call last):
File "/home/unica-server/.local/lib/python3.6/site-packages/matplotlib/backends/backend_gtk3.py", line 307, in idle_draw
self.draw()
File "/home/unica-server/.local/lib/python3.6/site-packages/matplotlib/backends/backend_gtk3agg.py", line 76, in draw
self._render_figure(allocation.width, allocation.height)
File "/home/unica-server/.local/lib/python3.6/site-packages/matplotlib/backends/backend_gtk3agg.py", line 20, in _render_figure
backend_agg.FigureCanvasAgg.draw(self)
File "/home/unica-server/.local/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py", line 388, in draw
self.figure.draw(self.renderer)
File "/home/unica-server/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/unica-server/.local/lib/python3.6/site-packages/matplotlib/figure.py", line 1709, in draw
renderer, self, artists, self.suppressComposite)
File "/home/unica-server/.local/lib/python3.6/site-packages/matplotlib/image.py", line 135, in _draw_list_compositing_images
a.draw(renderer)
File "/home/unica-server/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/unica-server/.local/lib/python3.6/site-packages/mpl_toolkits/mplot3d/axes3d.py", line 292, in draw
reverse=True)):
File "/home/unica-server/.local/lib/python3.6/site-packages/mpl_toolkits/mplot3d/axes3d.py", line 291, in <lambda>
key=lambda col: col.do_3d_projection(renderer),
File "/home/unica-server/.local/lib/python3.6/site-packages/mpl_toolkits/mplot3d/art3d.py", line 545, in do_3d_projection
ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else
File "/home/unica-server/.local/lib/python3.6/site-packages/mpl_toolkits/mplot3d/art3d.py", line 847, in _zalpha
rgba = np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs), 4))
File "<__array_function__ internals>", line 6, in broadcast_to
File "/home/unica-server/.local/lib/python3.6/site-packages/numpy/lib/stride_tricks.py", line 182, in broadcast_to
return _broadcast_to(array, shape, subok=subok, readonly=True)
File "/home/unica-server/.local/lib/python3.6/site-packages/numpy/lib/stride_tricks.py", line 127, in _broadcast_to
op_flags=['readonly'], itershape=shape, order='C')
ValueError: operands could not be broadcast together with remapped shapes [original->remapped]: (0,4) and requested shape (1,4)
My y's shape is (8143,) and my X_reduced's shape is (8143,3)
What is my mistake?
EDIT: The data I am using can be found here
The first warning Requested projection is different from current axis projection
is because you are trying to change the projection of an axis after its creation with ax = fig.gca(projection='3d') but you cannot. Set the projection at creation instead.
To fix the second error, replace edgecolors='none' by edgecolors=None.
The following corrected code works for me.
# Create plot
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d') # set projection at creation of axis
# ax = fig.gca(projection='3d') # you cannot change the projection after creation
colors = ("red", "gray")
for data, color, group in zip(X_reduced, colors, y):
dim1,dim2,dim3=data
# replace 'none' by None
ax.scatter(dim1, dim2, dim3, c=color, edgecolors=None, label=group)
plt.title('Matplot 3d scatter plot')
plt.legend(y)
plt.show()
EDIT : Above is my answer to what I understood of the original question. Below is a looped version of mad's own answer.
class_values = [0, 1]
labels = ['Empty', 'Full']
n_class = len(class_values)
# allocate lists
index_class = [None] * n_class
X_reduced_class = [None] * n_class
for i, class_i in enumerate(class_values) :
# get where are the 0s and 1s labels
index_class[i] = np.where(np.isin(y, class_i))
# get reduced PCA for each label
X_reduced_class[i] = X_reduced[index_class[i]]
colors = ['blue', 'red']
# To getter a better understanding of interaction of the dimensions
# plot the first three PCA dimensions
fig = plt.figure(1, figsize=(8, 6))
ax = Axes3D(fig, elev=-150, azim=110)
ids_plot = [0, 4, 5]
for i in range(n_class) :
# get the three interesting columns
data = X_reduced_class[i][:, ids_plot]
ax.scatter(data[:,0], data[:,1], data[:,2], c=colors[i], edgecolor='k', s=40, label=labels[i])
ax.set_title("Data Visualization with 3 highest variance dimensions with PCA")
ax.set_xlabel("1st eigenvector")
ax.w_xaxis.set_ticklabels([])
ax.set_ylabel("2nd eigenvector")
ax.w_yaxis.set_ticklabels([])
ax.set_zlabel("3rd eigenvector")
ax.w_zaxis.set_ticklabels([])
ax.legend()
plt.show()
I solved the error in a different way.
I did not know that, for each label, I had to do a different scatterplot. Thanks to this post I found the answer.
My solution was first to separate the labels and data from one class, and then do the same for the other class. Finally, I plot them separately with different scatterplots. So, firstly I identify the different labels (I have only two labels, 0 or 1) and their data (their corresponding Xs).
#Get where are the 0s and 1s labels
index_class1 = np.where(np.isin(y, 0))
index_class2 = np.where(np.isin(y, 1))
#Get reduced PCA for each label
X_reducedclass1=X_reduced[index_class1][:]
X_reducedclass2=X_reduced[index_class2][:]
Then, I will plot each PCA reduced vectors from each class in different scatterplots
colors = ['blue', 'red']
# To getter a better understanding of interaction of the dimensions
# plot the first three PCA dimensions
fig = plt.figure(1, figsize=(8, 6))
ax = Axes3D(fig, elev=-150, azim=110)
scatter1=ax.scatter(X_reducedclass1[:, 0], X_reducedclass1[:, 4], X_reducedclass1[:, 5], c=colors[0], cmap=plt.cm.Set1, edgecolor='k', s=40)
scatter2=ax.scatter(X_reducedclass2[:, 0], X_reducedclass2[:, 4], X_reducedclass2[:, 5], c=colors[1], cmap=plt.cm.Set1, edgecolor='k', s=40)
ax.set_title("Data Visualization with 3 highest variance dimensions with PCA")
ax.set_xlabel("1st eigenvector")
ax.w_xaxis.set_ticklabels([])
ax.set_ylabel("2nd eigenvector")
ax.w_yaxis.set_ticklabels([])
ax.set_zlabel("3rd eigenvector")
ax.w_zaxis.set_ticklabels([])
#ax.legend(np.unique(y))
ax.legend([scatter1, scatter2], ['Empty', 'Full'], loc="upper right")
plt.show()
Which gives me this beautiful image
Of course, such a code can be simplified with a for loop too (altough I have no idea how to do that).
My question is similar to this question which I want to plot my spectroscopic data to 3D-plot but
1) My data is matrix in np.ndarray
2) It has a large dimension of 1201*5001 (result.shape = (1201,5001)), therefore the hard code labeling manually is not suitable.
3) The data is not continuous and sparse. The final plot may look like mplot3d bar3d.
Can I use 3D bar plot from Matplotlib in this case? If possible, how to define the different length for every axes?
This is my code in progress (3rd update)
if __name__ == '__main__':
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib
%matplotlib inline
# from array, x is time, y is mz, z is intensity
# in graph x is mz, y is time, z is intensity
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
zs = np.arange(0, 50.01, 0.01)
for z in zs:
xs = np.arange(300, 1500.01, 1)
ys = result
ax.bar(xs,ys,zs=z,zdir='y')
plt.show()
Error (3rd)
Traceback (most recent call last):
File "prelimnmf_importcsv3.py", line 70, in <module>
ax.bar(xs,ys,zs=z,zdir='y')
File "/Users/pp/anaconda/lib/python2.7/site-packages/mpl_toolkits/mplot3d/axes3d.py", line 2394, in bar
patches = Axes.bar(self, left, height, *args, **kwargs)
File "/Users/pp/anaconda/lib/python2.7/site-packages/matplotlib/__init__.py", line 1892, in inner
return func(ax, *args, **kwargs)
File "/Users/pp/anaconda/lib/python2.7/site-packages/matplotlib/axes/_axes.py", line 2115, in bar
if h < 0:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
While I doubt that a bar plot with 1200*5000 can give any visual insight into the data, it should still be possible to use it.
So here is an example
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
#Assume you have arrays like this
x = np.arange(300,1500,100)
y = np.arange(4)*10
Z = np.random.rand(len(y), len(x))*33
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for i in range(len(y))[::-1]:
c = plt.cm.jet(i/float(len(y)))
ax.bar(x, Z[i,:], zs=y[i], zdir='y', width=80,alpha=1 )
ax.set_xlabel('time')
ax.set_ylabel('mz')
ax.set_zlabel('intensity')
plt.show()
There are a number of questions on SO about creating "thumbnail" plots with matplotlib (i.e. smaller versions of a larger plot, where the thumbnail plot is overlaid onto the original).
However, I cannot find a way to do this with GridSpec plots. I understand that this is because axes from a GridSpec cannot be transformed (i.e. resized and translated).
Here is a complete script which reproduces the problem:
import matplotlib
from matplotlib import gridspec, pyplot
from matplotlib.backends.backend_pdf import PdfPages
def add_inset_to_axis(figure, axis, rect):
left, bottom, width, height = rect
def transform(coord):
return figure.transFigure.inverted().transform(
axis.transAxes.transform(coord))
fig_left, fig_bottom = transform((left, bottom))
fig_width, fig_height = transform([width, height]) - transform([0, 0])
return figure.add_axes([fig_left, fig_bottom, fig_width, fig_height])
def main():
pdf = PdfPages('example.pdf')
fig = pyplot.figure()
n_rows, n_cols = 2, 2
x_range = (-100, 100)
outer_grid = gridspec.GridSpec(n_rows, n_cols)
index, row, col = 0, 0, 0
while index < n_rows * n_cols:
data = [x for x in xrange(*x_range)]
grid_cell = outer_grid[row, col]
axis = pyplot.subplot(grid_cell)
axis.plot(range(*x_range), data)
inset = add_inset_to_axis(fig, grid_cell, (0.675, 0.82, 0.3, 0.15))
inset.plot(range(0, 10), data[0:10])
col += 1
if col == 2:
col = 0
row += 1
index = row * 2 + col
pdf.savefig(fig)
pdf.close()
if __name__ == '__main__':
print('Using matplotlib version %s' % matplotlib.__version__)
main()
Output:
Using matplotlib version 1.5.1
Traceback (most recent call last):
File "stackoverflow_inset.py", line 38, in <module>
main()
File "stackoverflow_inset.py", line 26, in main
inset = add_inset_to_axis(fig, grid_cell, (0.675, 0.82, 0.3, 0.15))
File "stackoverflow_inset.py", line 10, in add_inset_to_axis
fig_left, fig_bottom = transform((left, bottom))
File "stackoverflow_inset.py", line 9, in transform
axis.transAxes.transform(coord))
AttributeError: 'SubplotSpec' object has no attribute 'transAxes'
Is there a way around this?
From the function definition add_inset_to_axis(figure, axis, rect) it seems that the second argument is actually meant to be a matplotlib.axes instance.
So instead of giving grid_cell as an argument, one should probably use axis
inset = add_inset_to_axis(fig, axis, (0.675, 0.82, 0.3, 0.15))
I have a 3D array of data (2 spatial dimensions and 1 time dimension) and I'm trying to produce an animated contour plot using matplotlib.animate. I'm using this link as a basis:
http://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/
And here's my attempt:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from numpy import array, zeros, linspace, meshgrid
from boutdata import collect
# First collect data from files
n = collect("n") # This is a routine to collect data
Nx = n.shape[1]
Nz = n.shape[2]
Ny = n.shape[3]
Nt = n.shape[0]
fig = plt.figure()
ax = plt.axes(xlim=(0, 200), ylim=(0, 100))
cont, = ax.contourf([], [], [], 500)
# initialisation function
def init():
cont.set_data([],[],[])
return cont,
# animation function
def animate(i):
x = linspace(0, 200, Nx)
y = linspace(0, 100, Ny)
x,y = meshgrid(x,y)
z = n[i,:,0,:].T
cont.set_data(x,y,z)
return cont,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=True)
plt.show()
But when I do this, I get the following error:
Traceback (most recent call last):
File "showdata.py", line 16, in <module>
cont, = ax.contourf([], [], [], 500)
File "/usr/lib/pymodules/python2.7/matplotlib/axes.py", line 7387, in contourf
return mcontour.QuadContourSet(self, *args, **kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1112, in __init__
ContourSet.__init__(self, ax, *args, **kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 703, in __init__
self._process_args(*args, **kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1125, in _process_args
x, y, z = self._contour_args(args, kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1172, in _contour_args
x,y,z = self._check_xyz(args[:3], kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1204, in _check_xyz
raise TypeError("Input z must be a 2D array.")
TypeError: Input z must be a 2D array.
So I've tried replacing all the [] by [[],[]] but this then produces:
Traceback (most recent call last):
File "showdata.py", line 16, in <module>
cont, = ax.contourf([[],[]], [[],[]], [[],[]],500)
File "/usr/lib/pymodules/python2.7/matplotlib/axes.py", line 7387, in contourf
return mcontour.QuadContourSet(self, *args, **kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1112, in __init__
ContourSet.__init__(self, ax, *args, **kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 703, in __init__
self._process_args(*args, **kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1125, in _process_args
x, y, z = self._contour_args(args, kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1177, in _contour_args
self.zmax = ma.maximum(z)
File "/usr/lib/python2.7/dist-packages/numpy/ma/core.py", line 5806, in __call__
return self.reduce(a)
File "/usr/lib/python2.7/dist-packages/numpy/ma/core.py", line 5824, in reduce
t = self.ufunc.reduce(target, **kargs)
ValueError: zero-size array to maximum.reduce without identity
Thanks in advance!
Felix Schneider is correct about the animation becoming very slow. His solution of setting ax.collections = [] removes all old (and superseded) "artist"s. A more surgical approach is to only remove the artists involved in the drawing the contours:
for c in cont.collections:
c.remove()
which is useful in more complicated cases, in lieu of reconstructing the entire figure for each frame. This also works in Rehman Ali's example; instead of clearing the entire figure with clf() the value returned by contourf() is saved and used in the next iteration. Here is an example code similar to Luke's from Jun 7 '13, demonstrating removing the contours only:
import pylab as plt
import numpy
import matplotlib.animation as animation
#plt.rcParams['animation.ffmpeg_path'] = r"C:\some_path\ffmpeg.exe" # if necessary
# Generate data for plotting
Lx = Ly = 3
Nx = Ny = 11
Nt = 20
x = numpy.linspace(0, Lx, Nx)
y = numpy.linspace(0, Ly, Ny)
x,y = numpy.meshgrid(x,y)
z0 = numpy.exp(-(x-Lx/2)**2-(y-Ly/2)**2) # 2 dimensional Gaussian
def some_data(i): # function returns a 2D data array
return z0 * (i/Nt)
fig = plt.figure()
ax = plt.axes(xlim=(0, Lx), ylim=(0, Ly), xlabel='x', ylabel='y')
cvals = numpy.linspace(0,1,Nt+1) # set contour values
cont = plt.contourf(x, y, some_data(0), cvals) # first image on screen
plt.colorbar()
# animation function
def animate(i):
global cont
z = some_data(i)
for c in cont.collections:
c.remove() # removes only the contours, leaves the rest intact
cont = plt.contourf(x, y, z, cvals)
plt.title('t = %i: %.2f' % (i,z[5,5]))
return cont
anim = animation.FuncAnimation(fig, animate, frames=Nt, repeat=False)
anim.save('animation.mp4', writer=animation.FFMpegWriter())
This is what I got to work:
# Generate grid for plotting
x = linspace(0, Lx, Nx)
y = linspace(0, Ly, Ny)
x,y = meshgrid(x,y)
fig = plt.figure()
ax = plt.axes(xlim=(0, Lx), ylim=(0, Ly))
plt.xlabel(r'x')
plt.ylabel(r'y')
# animation function
def animate(i):
z = var[i,:,0,:].T
cont = plt.contourf(x, y, z, 25)
if (tslice == 0):
plt.title(r't = %1.2e' % t[i] )
else:
plt.title(r't = %i' % i)
return cont
anim = animation.FuncAnimation(fig, animate, frames=Nt)
anim.save('animation.mp4')
I found that removing the blit=0 argument in the FuncAnimation call also helped...
This is the line:
cont, = ax.contourf([], [], [], 500)
change to:
x = linspace(0, 200, Nx)
y = linspace(0, 100, Ny)
x, y = meshgrid(x, y)
z = n[i,:,0,:].T
cont, = ax.contourf(x, y, z, 500)
You need to intilize with sized arrays.
Here is another way of doing the same thing if matplotlib.animation don't work for you. If you want to continuously update the colorbar and everything else in the figure, use plt.ion() at the very beginning to enable interactive plotting and use a combo of plt.draw() and plt.clf() to continuously update the plot.
import matplotlib.pyplot as plt
import numpy as np
plt.ion(); plt.figure(1);
for k in range(10):
plt.clf(); plt.subplot(121);
plt.contourf(np.random.randn(10,10)); plt.colorbar();
plt.subplot(122,polar=True)
plt.contourf(np.random.randn(10,10)); plt.colorbar();
plt.draw();
Note that this works with figures containing different subplots and various types of plots (i.e. polar or cartesian)
I used Lukes approach (from Jun 7 '13 at 8:08 ), but added
ax.collections = []
right before
cont = plt.contourf(x, y, z, 25).
Otherwise I experienced that creating the animation will become very slow for large frame numbers.
I have been looking at this a while ago. I my situation I had a few subplots with contours which I wanted to animate. I did not want to use the plt.clf() solution as Rehman ali suggest as I used some special setup of my axis (with pi symbols etc) which would be cleaned as well, so I preferred the 'remove()' approach suggest be Felix. The thing is that only using 'remove' does not clean up memory and will clog your computer eventually, so you need to explicitly delete of the contours by setting it to an empty list as well.
In order to have a generic remove routine which is able to take away contours as well as text, I wrote the routine 'clean_up_artists' which you should use on every time step on all the axis.
This routine cleans up the artists which are passed in a list called 'artist_list' in a given axis 'axis'. This means that for animating multiple subplots, we need to store the lists of artists for each axis which we need to clean every time step.
Below the full code to animate a number of subplots of random data. It is pretty self-explanatory, so hopefully it becomes clear what happens. Anyhow, I just thought to post it, as it combines several ideas I found on stack overflow which I just to come up with this working example.
Anybody with suggestions to improve the code, please shoot-)
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.animation as animation
import string
import numpy as np
def clean_up_artists(axis, artist_list):
"""
try to remove the artists stored in the artist list belonging to the 'axis'.
:param axis: clean artists belonging to these axis
:param artist_list: list of artist to remove
:return: nothing
"""
for artist in artist_list:
try:
# fist attempt: try to remove collection of contours for instance
while artist.collections:
for col in artist.collections:
artist.collections.remove(col)
try:
axis.collections.remove(col)
except ValueError:
pass
artist.collections = []
axis.collections = []
except AttributeError:
pass
# second attempt, try to remove the text
try:
artist.remove()
except (AttributeError, ValueError):
pass
def update_plot(frame_index, data_list, fig, axis, n_cols, n_rows, number_of_contour_levels, v_min, v_max,
changed_artists):
"""
Update the the contour plots of the time step 'frame_index'
:param frame_index: integer required by animation running from 0 to n_frames -1. For initialisation of the plot,
call 'update_plot' with frame_index = -1
:param data_list: list with the 3D data (time x 2D data) per subplot
:param fig: reference to the figure
:param axis: reference to the list of axis with the axes per subplot
:param n_cols: number of subplot in horizontal direction
:param n_rows: number of subplot in vertical direction
:param number_of_contour_levels: number of contour levels
:param v_min: minimum global data value. If None, take the smallest data value in the 2d data set
:param v_max: maximum global data value. If None, take the largest value in the 2d data set
:param changed_artists: list of lists of artists which need to be updated between the time steps
:return: the changed_artists list
"""
nr_subplot = 0 # keep the index of the current subplot (nr_subplot = 0,1, n_cols x n_rows -1)
# loop over the subplots
for j_col in range(n_cols):
for i_row in range(n_rows):
# set a short reference to the current axis
ax = axis[i_row][j_col]
# for the first setup call, add and empty list which can hold the artists belonging to the current axis
if frame_index < 0:
# initialise the changed artist list
changed_artists.append(list())
else:
# for the next calls of update_plot, remove all artists in the list stored in changed_artists[nr_subplot]
clean_up_artists(ax, changed_artists[nr_subplot])
# get a reference to 2d data of the current time and subplot
data_2d = data_list[nr_subplot][frame_index]
# manually set the levels for better contour range control
if v_min is None:
data_min = np.nanmin(data_2d)
else:
data_min = v_min
if v_max is None:
data_max = np.nanmax(data_2d)
else:
data_max = v_max
# set the contour levels belonging to this subplot
levels = np.linspace(data_min, data_max, number_of_contour_levels + 1, endpoint=True)
# create the contour plot
cs = ax.contourf(data_2d, levels=levels, cmap=cm.rainbow, zorder=0)
cs.cmap.set_under("k")
cs.cmap.set_over("k")
cs.set_clim(v_min, v_max)
# store the contours artists to the list of artists belonging to the current axis
changed_artists[nr_subplot].append(cs)
# set some grid lines on top of the contours
ax.xaxis.grid(True, zorder=0, color="black", linewidth=0.5, linestyle='--')
ax.yaxis.grid(True, zorder=0, color="black", linewidth=0.5, linestyle='--')
# set the x and y label on the bottom row and left column respectively
if i_row == n_rows - 1:
ax.set_xlabel(r"Index i ")
if j_col == 0:
ax.set_ylabel(r"Index j")
# set the changing time counter in the top left subplot
if i_row == 0 and j_col == 1:
# set a label to show the current time
time_text = ax.text(0.6, 1.15, "{}".format("Time index : {:4d}".format(frame_index)),
transform=ax.transAxes, fontdict=dict(color="black", size=14))
# store the artist of this label in the changed artist list
changed_artists[nr_subplot].append(time_text)
# for the initialisation call only, set of a contour bar
if frame_index < 0:
# the first time we add this (make sure to pass -1 for the frame_index
cbar = fig.colorbar(cs, ax=ax)
cbar.ax.set_ylabel("Random number {}".format(nr_subplot))
ax.text(0.0, 1.02, "{}) {}".format(string.ascii_lowercase[nr_subplot],
"Random noise {}/{}".format(i_row, j_col)),
transform=ax.transAxes, fontdict=dict(color="blue", size=12))
nr_subplot += 1
return changed_artists
def main():
n_pixels_x = 50
n_pixels_y = 30
number_of_time_steps = 100
number_of_contour_levels = 10
delay_of_frames = 1000
n_rows = 3 # number of subplot rows
n_cols = 2 # number of subplot columns
min_data_value = 0.0
max_data_value = 1.0
# list containing the random plot per sub plot. Insert you own data here
data_list = list()
for j_col in range(n_cols):
for i_row in range(n_rows):
data_list.append(np.random.random_sample((number_of_time_steps, n_pixels_x, n_pixels_y)))
# set up the figure with the axis
fig, axis = plt.subplots(nrows=n_rows, ncols=n_cols, sharex=True, sharey=True, figsize=(12,8))
fig.subplots_adjust(wspace=0.05, left=0.08, right=0.98)
# a list used to store the reference to the axis of each subplot with a list of artists which belong to this subplot
# this list will be returned and will be updated every time plot which new artists
changed_artists = list()
# create first image by calling update_plot with frame_index = -1
changed_artists = update_plot(-1, data_list, fig, axis, n_cols, n_rows, number_of_contour_levels,
min_data_value, max_data_value, changed_artists)
# call the animation function. The fargs argument equals the parameter list of update_plot, except the
# 'frame_index' parameter.
ani = animation.FuncAnimation(fig, update_plot, frames=number_of_time_steps,
fargs=(data_list, fig, axis, n_cols, n_rows, number_of_contour_levels, min_data_value,
max_data_value, changed_artists),
interval=delay_of_frames, blit=False, repeat=True)
plt.show()
if __name__ == "__main__":
main()
Removing the blit=0 or blit = True argument in the FuncAnimation call also helped
is important!!!