Dynamically add subplots in matplotlib with more than one column - python

How can I dynamically add a new plot to bunch of subplots if I'm using more than one column to display my subplots? This answers this question for one column, but I cant seem to modify the answers there to make it dynamically add to a subplot with x columns
I modified Sadarthrion's answer and attempted the following. Here, for sake of an example, I made number_of_subplots=11 and num_cols = 3.
import matplotlib.pyplot as plt
def plotSubplots(number_of_subplots,num_cols):
# Start with one
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1,2,3])
for j in range(number_of_subplots):
if j > 0:
# Now later you get a new subplot; change the geometry of the existing
n = len(fig.axes)
for i in range(n):
fig.axes[i].change_geometry(n+1, num_cols, i+1)
# Add the new
ax = fig.add_subplot(n+1, 1, n+1)
ax.plot([4,5,6])
plt.show()
plotSubplots(11,3)
As you can see this isn't giving me what I want. The first plot takes up all the columns and the additional plots are smaller than they should be
EDIT:
('2.7.6 | 64-bit | (default, Sep 15 2014, 17:36:35) [MSC v.1500 64 bit (AMD64)]'
Also I have matplotlib version 1.4.3:
import matplotlib as mpl
print mpl.__version__
1.4.3
I tried Paul's answer below and get the following error message:
import math
import matplotlib.pyplot as plt
from matplotlib import gridspec
def do_plot(ax):
ax.plot([1,2,3], [4,5,6], 'k.')
N = 11
cols = 3
rows = math.ceil(N / cols)
gs = gridspec.GridSpec(rows, cols)
fig = plt.figure()
for n in range(N):
ax = fig.add_subplot(gs[n])
do_plot(ax)
fig.tight_layout()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-1-f74203b1c1bf> in <module>()
15 fig = plt.figure()
16 for n in range(N):
---> 17 ax = fig.add_subplot(gs[n])
18 do_plot(ax)
19
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\figure.pyc in add_subplot(self, *args, **kwargs)
962 self._axstack.remove(ax)
963
--> 964 a = subplot_class_factory(projection_class)(self, *args, **kwargs)
965
966 self._axstack.add(key, a)
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\axes\_subplots.pyc in __init__(self, fig, *args, **kwargs)
73 raise ValueError('Illegal argument(s) to subplot: %s' % (args,))
74
---> 75 self.update_params()
76
77 # _axes_class is set in the subplot_class_factory
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\axes\_subplots.pyc in update_params(self)
113 self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = 114 self.get_subplotspec().get_position(self.figure,
--> 115 return_all=True)
116
117 def is_first_col(self):
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\gridspec.pyc in get_position(self, fig, return_all)
423
424 figBottoms, figTops, figLefts, figRights = --> 425 gridspec.get_grid_positions(fig)
426
427
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\gridspec.pyc in get_grid_positions(self, fig)
103 cellHeights = [netHeight*r/tr for r in self._row_height_ratios]
104 else:
--> 105 cellHeights = [cellH] * nrows
106
107 sepHeights = [0] + ([sepH] * (nrows-1))
TypeError: can't multiply sequence by non-int of type 'float'

Assuming at least 1 dimension of your grid and the total number of plots is known, I would use the gridspec module and a little bit of math.
import math
import matplotlib.pyplot as plt
from matplotlib import gridspec
def do_plot(ax):
ax.plot([1,2,3], [4,5,6], 'k.')
N = 11
cols = 3
rows = int(math.ceil(N / cols))
gs = gridspec.GridSpec(rows, cols)
fig = plt.figure()
for n in range(N):
ax = fig.add_subplot(gs[n])
do_plot(ax)
fig.tight_layout()

Here's the solution I ended up with. It lets you reference subplots by name, and adds a new subplot if that name has not been used yet, repositioning all previous subplots in the process.
Usage:
set_named_subplot('plot-a') # Create a new plot
plt.plot(np.sin(np.linspace(0, 10, 100))) # Plot a curve
set_named_subplot('plot-b') # Create a new plot
plt.imshow(np.random.randn(10, 10)) # Draw image
set_named_subplot('plot-a') # Set the first plot as the current one
plt.plot(np.cos(np.linspace(0, 10, 100))) # Plot another curve in the first plot
plt.show() # Will show two plots
The code:
import matplotlib.pyplot as plt
import numpy as np
def add_subplot(fig = None, layout = 'grid'):
"""
Add a subplot, and adjust the positions of the other subplots appropriately.
Lifted from this answer: http://stackoverflow.com/a/29962074/851699
:param fig: The figure, or None to select current figure
:param layout: 'h' for horizontal layout, 'v' for vertical layout, 'g' for approximately-square grid
:return: A new axes object
"""
if fig is None:
fig = plt.gcf()
n = len(fig.axes)
n_rows, n_cols = (1, n+1) if layout in ('h', 'horizontal') else (n+1, 1) if layout in ('v', 'vertical') else \
vector_length_to_tile_dims(n+1) if layout in ('g', 'grid') else bad_value(layout)
for i in range(n):
fig.axes[i].change_geometry(n_rows, n_cols, i+1)
ax = fig.add_subplot(n_rows, n_cols, n+1)
return ax
_subplots = {}
def set_named_subplot(name, fig=None, layout='grid'):
"""
Set the current axes. If "name" has been defined, just return that axes, otherwise make a new one.
:param name: The name of the subplot
:param fig: The figure, or None to select current figure
:param layout: 'h' for horizontal layout, 'v' for vertical layout, 'g' for approximately-square grid
:return: An axes object
"""
if name in _subplots:
plt.subplot(_subplots[name])
else:
_subplots[name] = add_subplot(fig=fig, layout=layout)
return _subplots[name]
def vector_length_to_tile_dims(vector_length):
"""
You have vector_length tiles to put in a 2-D grid. Find the size
of the grid that best matches the desired aspect ratio.
TODO: Actually do this with aspect ratio
:param vector_length:
:param desired_aspect_ratio:
:return: n_rows, n_cols
"""
n_cols = np.ceil(np.sqrt(vector_length))
n_rows = np.ceil(vector_length/n_cols)
grid_shape = int(n_rows), int(n_cols)
return grid_shape
def bad_value(value, explanation = None):
"""
:param value: Raise ValueError. Useful when doing conditional assignment.
e.g.
dutch_hand = 'links' if eng_hand=='left' else 'rechts' if eng_hand=='right' else bad_value(eng_hand)
"""
raise ValueError('Bad Value: %s%s' % (value, ': '+explanation if explanation is not None else ''))

from math import ceil
from PyQt5 import QtWidgets, QtCore
from matplotlib.gridspec import GridSpec
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
class MplCanvas(FigureCanvas):
"""
Frontend class. This is the FigureCanvas as well as plotting functionality.
Plotting use pyqt5.
"""
def __init__(self, parent=None):
self.figure = Figure()
gs = GridSpec(1,1)
self.figure.add_subplot(gs[0])
self.axes = self.figure.axes
super().__init__(self.figure)
self.canvas = self.figure.canvas
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.updateGeometry()
self.setParent(parent)
def add(self, cols=2):
N = len(self.axes) + 1
rows = int(ceil(N / cols))
grid = GridSpec(rows, cols)
for gs, ax in zip(grid, self.axes):
ax.set_position(gs.get_position(self.figure))
self.figure.add_subplot(grid[N-1])
self.axes = self.figure.axes
self.canvas.draw()
Was doing some PyQt5 work, but the add method shows how to dynamically add a new subplot. The set_position method of Axes is used to change the old position to new position. Then you add a new subplot with the new position.

I wrote a function that automatically formats all of the subplots into a compact, square-like shape.
To go off of Paul H's answer, we can use gridspec to dynamically add subplots to a figure. However, I then made an improvement. My code automatically arranges the subplots so that the entire figure will be compact and square-like. This ensures that there will be roughly the same amount of rows and columns in the subplot.
The number of columns equals the square root of n_plots rounded down and then enough rows are created so there will be enough spots for all of the subplots.
Check it out:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
def arrange_subplots(xs, ys, n_plots):
"""
---- Parameters ----
xs (n_plots, d): list with n_plot different lists of x values that we wish to plot
ys (n_plots, d): list with n_plot different lists of y values that we wish to plot
n_plots (int): the number of desired subplots
"""
# compute the number of rows and columns
n_cols = int(np.sqrt(n_plots))
n_rows = int(np.ceil(n_plots / n_cols))
# setup the plot
gs = gridspec.GridSpec(n_rows, n_cols)
scale = max(n_cols, n_rows)
fig = plt.figure(figsize=(5 * scale, 5 * scale))
# loop through each subplot and plot values there
for i in range(n_plots):
ax = fig.add_subplot(gs[i])
ax.plot(xs[i], ys[i])
Here are a couple of example images to compare
n_plots = 5
n_plots = 6
n_plots = 10
n_plots = 15

Related

gettting a colorbar programmatically from an axis object

Consider the following code in which data is being plotted within a loop. In order to not plot multiple colorbars, I remove them before plotting new ones. However, I am tracking these colorbars manually. Is there a way to get a reference to them using, say, something like ax[0].get_colorbar. That would make my code a lot simpler.
import matplotlib.pyplot as plt
import numpy as np
# How to get a colorbar from an axis?
nrows = 1
ncols = 2
nstep = 5
fig, ax = plt.subplots(nrows=nrows,ncols=ncols)
cb0 = None
cb1 = None
for istep in range(nstep):
data = np.random.random(size=(5,5))
imu0 = ax[0].pcolormesh(data)
imu1 = ax[1].pcolormesh(data)
# this code is for removing previously drawn colorbars
# I would like to get a reference to the colorbar cb0 from ax0
# and then remove it.
# I do not want to keep track of the colorbars manually
if cb0 is not None:
cb0.remove()
if cb1 is not None:
cb1.remove()
cb0 = plt.colorbar(imu0,ax=ax[0])
cb1 = plt.colorbar(imu1,ax=ax[1])
The following seems to do what I want:
import matplotlib.pyplot as plt
import numpy as np
# How to get a colorbar from an axis?
nrows = 1
ncols = 2
nstep = 10
fig, ax = plt.subplots(nrows=nrows,ncols=ncols)
cb0 = None
cb1 = None
for istep in range(nstep):
data = np.random.random(size=(5,5))+istep
imu0 = ax[0].pcolormesh(data)
imu1 = ax[1].pcolormesh(data)
if ax[0].collections[0].colorbar is None:
cb0 = plt.colorbar(imu0,ax=ax[0])
else:
ax[0].collections[0].colorbar.update_normal(imu0)
if ax[1].collections[0].colorbar is None:
cb1 = plt.colorbar(imu1,ax=ax[1])
else:
ax[1].collections[0].colorbar.update_normal(imu1)

Not able to use plt.subplots() for my data [duplicate]

This question already has answers here:
How to plot in multiple subplots
(12 answers)
Closed 1 year ago.
I am using the Lombscargle function to output the power spectrum for a signal I pass as input, I am able to get the plots one after another but the task at hand is to plot these graphs using subplots in a way that there are 5 rows, 4 cols.
An example for signal would be:
signal = [ '254.24', '254.32', '254.4', '254.84', '254.24', '254.28', '254.84', '253.56', '253.76', '253.32', '253.88', '253.72', '253.92', '251.56', '253.04', '244.72', '243.84', '246.08', '245.84', '249.0', '250.08', '248.2', '253.12', '253.2', '253.48', '253.88', '253.12', '253.4', '253.4']
from scipy.signal import lombscargle
def LSP_scipy(signal):
start_ang_freq = 2 * np.pi * (60/60)
end_ang_freq = 2 * np.pi * (240/60)
SAMPLES = 5000
SAMPLE_SPACING = 1/15
t = np.linspace(0,len(signal)*SAMPLE_SPACING,len(signal))
period_freq = np.linspace(start_ang_freq,end_ang_freq,SAMPLES)
modified_signal_axis = []
modified_time_axis = []
for count,value in enumerate(signal):
if value != 'None':
modified_signal_axis.append(float(value))
modified_time_axis.append(t[count])
prog = lombscargle(modified_time_axis, modified_signal_axis, period_freq, normalize=False, precenter = True)
fig, axes = plt.subplots()
ax.plot(period_freq,prog)
How do I plot these graphs in a matrix format?
Trying loop approach,
See inline comments to add and flatten the subplots.
This is an implementation of flattening the axes array from this answer of the duplicate.
from scipy.signal import lombscargle
from matplotlib.ticker import FormatStrFormatter
import numpy as np
import matplotlib.pyplot as plt
def LSP_scipy(signal):
start_ang_freq = 2 * np.pi * (60/60)
end_ang_freq = 2 * np.pi * (240/60)
SAMPLES = 5000
SAMPLE_SPACING = 1/15
t = np.linspace(0, len(signal)*SAMPLE_SPACING, len(signal))
period_freq = np.linspace(start_ang_freq, end_ang_freq, SAMPLES)
modified_signal_axis = []
modified_time_axis = []
# create the figure and subplots
fig, axes = plt.subplots(5, 6, figsize=(20, 9), sharex=False, sharey=False)
# flatten the array
axes = axes.ravel()
for count, value in enumerate(signal):
if value != 'None':
modified_signal_axis.append(float(value))
modified_time_axis.append(t[count])
prog = lombscargle(modified_time_axis, modified_signal_axis, period_freq, normalize=False, precenter=True)
# plot
axes[count].plot(period_freq, prog)
# format the axes
axes[count].set(title=value)
# some plot have an exponential offset on the yaxis, this turns it off
axes[count].ticklabel_format(useOffset=False)
# some yaxis values are long floats, this formats them to 3 decimal places
axes[count].yaxis.set_major_formatter(FormatStrFormatter('%.3f'))
# format the figure
fig.tight_layout()
signal = [ '254.24', '254.32', '254.4', '254.84', '254.24', '254.28', '254.84', '253.56', '253.76', '253.32', '253.88', '253.72', '253.92', '251.56', '253.04', '244.72', '243.84', '246.08', '245.84', '249.0', '250.08', '248.2', '253.12', '253.2', '253.48', '253.88', '253.12', '253.4', '253.4']
LSP_scipy(signal[:20]) # as per comment, only first 20
You can use for loop and iterate over subplots. A very simple example is shown below.The subplots method creates the figure along with the subplots and store in the ax array.
import matplotlib.pyplot as plt
x = np.linspace(0, 10)
y = range(10)
fig, ax = plt.subplots(nrows=2, ncols=2)
for row in ax:
for col in row:
col.plot(x, y)
plt.show()
# or you can also do
for in range(2): # row=0, col=0
for j in range(2): # row=0, col=1
ax[i, j].plot(x,y) # row=1, col=0
# row=1, col=1
Then one idea is to take the signals into an array of shape=(20,1), where each row corresponds to signal amplitude or some other measurable quantity. Then you could do as below (check the output keeping only the lines till plt.text you will get the idea).
for i in range(1, 21):
plt.subplot(5, 4, i)
plt.text(0.5, 0.5, str((5, 4, i)),
fontsize=18, ha='center')
# Call the function here...get the value of period_freq and prog
period_freq,prog = LSP_scipy(signal[i])
plt.plot(period_freq, prog)

How to plot a list of figures in a single subplot?

I have 2 lists of figures and their axes.
I need to plot each figure in a single subplot so that the figures become in one big subplot. How can I do that?
I tried for loop but it didn't work.
Here's what I have tried:
import ruptures as rpt
import matplotlib.pyplot as plt
# make random data with 100 samples and 9 columns
n_samples, n_dims, sigma = 100, 9, 2
n_bkps = 4
signal, bkps = rpt.pw_constant(n_samples, n_dims, n_bkps, noise_std=sigma)
figs, axs = [], []
for i in range(signal.shape[1]):
points = signal[:,i]
# detection of change points
algo = rpt.Dynp(model='l2').fit(points)
result = algo.predict(n_bkps=2)
fig, ax = rpt.display(points, bkps, result, figsize=(15,3))
figs.append(fig)
axs.append(ax)
plt.show()
I had a look at the source code of ruptures.display(), and it accepts **kwargs that are passed on to matplotlib. This allows us to redirect the output to a single figure, and with gridspec, we can position individual subplots within this figure:
import ruptures as rpt
import matplotlib.pyplot as plt
n_samples, n_dims, sigma = 100, 9, 2
n_bkps = 4
signal, bkps = rpt.pw_constant(n_samples, n_dims, n_bkps, noise_std=sigma)
#number of subplots
n_subpl = signal.shape[1]
#give figure a name to refer to it later
fig = plt.figure(num = "ruptures_figure", figsize=(8, 15))
#define grid of nrows x ncols
gs = fig.add_gridspec(n_subpl, 1)
for i in range(n_subpl):
points = signal[:,i]
algo = rpt.Dynp(model='l2').fit(points)
result = algo.predict(n_bkps=2)
#rpt.display(points, bkps, result)
#plot into predefined figure
_, curr_ax = rpt.display(points, bkps, result, num="ruptures_figure")
#position current subplot within grid
curr_ax[0].set_position(gs[i].get_position(fig))
curr_ax[0].set_subplotspec(gs[i])
plt.show()
Sample output:

Matplotlib animation function: plots points but no animation [duplicate]

I'm trying to do an animation of a scatter plot where colors and size of the points changes at different stage of the animation. For data I have two numpy ndarray with an x value and y value:
data.shape = (ntime, npoint)
x.shape = (npoint)
y.shape = (npoint)
Now I want to plot a scatter plot of the type
pylab.scatter(x,y,c=data[i,:])
and create an animation over the index i. How do I do this?
Suppose you have a scatter plot, scat = ax.scatter(...), then you can
change the positions
scat.set_offsets(array)
where array is a N x 2 shaped array of x and y coordinates.
change the sizes
scat.set_sizes(array)
where array is a 1D array of sizes in points.
change the color
scat.set_array(array)
where array is a 1D array of values which will be colormapped.
Here's a quick example using the animation module.
It's slightly more complex than it has to be, but this should give you a framework to do fancier things.
(Code edited April 2019 to be compatible with current versions. For the older code see revision history)
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
class AnimatedScatter(object):
"""An animated scatter plot using matplotlib.animations.FuncAnimation."""
def __init__(self, numpoints=50):
self.numpoints = numpoints
self.stream = self.data_stream()
# Setup the figure and axes...
self.fig, self.ax = plt.subplots()
# Then setup FuncAnimation.
self.ani = animation.FuncAnimation(self.fig, self.update, interval=5,
init_func=self.setup_plot, blit=True)
def setup_plot(self):
"""Initial drawing of the scatter plot."""
x, y, s, c = next(self.stream).T
self.scat = self.ax.scatter(x, y, c=c, s=s, vmin=0, vmax=1,
cmap="jet", edgecolor="k")
self.ax.axis([-10, 10, -10, 10])
# For FuncAnimation's sake, we need to return the artist we'll be using
# Note that it expects a sequence of artists, thus the trailing comma.
return self.scat,
def data_stream(self):
"""Generate a random walk (brownian motion). Data is scaled to produce
a soft "flickering" effect."""
xy = (np.random.random((self.numpoints, 2))-0.5)*10
s, c = np.random.random((self.numpoints, 2)).T
while True:
xy += 0.03 * (np.random.random((self.numpoints, 2)) - 0.5)
s += 0.05 * (np.random.random(self.numpoints) - 0.5)
c += 0.02 * (np.random.random(self.numpoints) - 0.5)
yield np.c_[xy[:,0], xy[:,1], s, c]
def update(self, i):
"""Update the scatter plot."""
data = next(self.stream)
# Set x and y data...
self.scat.set_offsets(data[:, :2])
# Set sizes...
self.scat.set_sizes(300 * abs(data[:, 2])**1.5 + 100)
# Set colors..
self.scat.set_array(data[:, 3])
# We need to return the updated artist for FuncAnimation to draw..
# Note that it expects a sequence of artists, thus the trailing comma.
return self.scat,
if __name__ == '__main__':
a = AnimatedScatter()
plt.show()
If you're on OSX and using the OSX backend, you'll need to change blit=True to blit=False in the FuncAnimation initialization below. The OSX backend doesn't fully support blitting. The performance will suffer, but the example should run correctly on OSX with blitting disabled.
For a simpler example, which just updates the colors, have a look at the following:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
def main():
numframes = 100
numpoints = 10
color_data = np.random.random((numframes, numpoints))
x, y, c = np.random.random((3, numpoints))
fig = plt.figure()
scat = plt.scatter(x, y, c=c, s=100)
ani = animation.FuncAnimation(fig, update_plot, frames=range(numframes),
fargs=(color_data, scat))
plt.show()
def update_plot(i, data, scat):
scat.set_array(data[i])
return scat,
main()
I wrote celluloid to make this easier. It's probably easiest to show by example:
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from celluloid import Camera
numpoints = 10
points = np.random.random((2, numpoints))
colors = cm.rainbow(np.linspace(0, 1, numpoints))
camera = Camera(plt.figure())
for _ in range(100):
points += 0.1 * (np.random.random((2, numpoints)) - .5)
plt.scatter(*points, c=colors, s=100)
camera.snap()
anim = camera.animate(blit=True)
anim.save('scatter.mp4')
It uses ArtistAnimation under the hood. camera.snap captures the current state of the figure which is used to create the frames in the animation.
Edit: To quantify how much memory this uses I ran it through memory_profiler.
Line # Mem usage Increment Line Contents
================================================
11 65.2 MiB 65.2 MiB #profile
12 def main():
13 65.2 MiB 0.0 MiB numpoints = 10
14 65.2 MiB 0.0 MiB points = np.random.random((2, numpoints))
15 65.2 MiB 0.1 MiB colors = cm.rainbow(np.linspace(0, 1, numpoints))
16 65.9 MiB 0.6 MiB fig = plt.figure()
17 65.9 MiB 0.0 MiB camera = Camera(fig)
18 67.8 MiB 0.0 MiB for _ in range(100):
19 67.8 MiB 0.0 MiB points += 0.1 * (np.random.random((2, numpoints)) - .5)
20 67.8 MiB 1.9 MiB plt.scatter(*points, c=colors, s=100)
21 67.8 MiB 0.0 MiB camera.snap()
22 70.1 MiB 2.3 MiB anim = camera.animate(blit=True)
23 72.1 MiB 1.9 MiB anim.save('scatter.mp4')
To summarize this:
Creating 100 plots used 1.9 MiB.
Making the animation used 2.3 MiB.
This method of making animations used 4.2 MiB of memory in sum.
TL/DR: If you are having trouble with the ax.set_... methods for animating your scatter plot, you can try to just clear the plot each frame (i.e., ax.clear()) and re-plot things as desired. This is slower, but might be useful when you want to change a lot of things in a small animation.
Here is an example demonstrating this "clear" approach:
import itertools
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
# set parameters
frames = 10
points = 20
np.random.seed(42)
# create data
data = np.random.rand(points, 2)
# set how the graph will change each frame
sizes = itertools.cycle([10, 50, 150])
colors = np.random.rand(frames, points)
colormaps = itertools.cycle(['Purples', 'Blues', 'Greens', 'Oranges', 'Reds'])
markers = itertools.cycle(['o', 'v', '^', 's', 'p'])
# init the figure
fig, ax = plt.subplots(figsize=(5,5))
def update(i):
# clear the axis each frame
ax.clear()
# replot things
ax.scatter(data[:, 0], data[:, 1],
s=next(sizes),
c=colors[i, :],
cmap=next(colormaps),
marker=next(markers))
# reformat things
ax.set_xlabel('world')
ax.set_ylabel('hello')
ani = animation.FuncAnimation(fig, update, frames=frames, interval=500)
ani.save('scatter.gif', writer='pillow')
The tutorials I have seen from matplotlib and other sources do not seem to use this approach, but I have seen others (as well as myself) suggest it on this site. I see some pros & cons, but I would appreciate anyone else's thoughts:
Pros
You can avoid using the set_... methods for the scatter plot (i.e. .set_offsets(), .set_sizes(), ...), which have more obscure and less-detailed documentation (though the leading answer will get you very far here!). Plus, there are different methods for different plot types (e.g. you use set_data for lines, but not for scatter points). By clearing the axis, you determine the plotted elements each frame like any other plot in matplotlib.
Even more so, it is unclear if some properties are set-able, such as the marker type (as commented) or the colormap. I wouldn't know how to create the above plot using ax.set_..., for example, because of the marker and colormap changes. But this is pretty basic with ax.scatter().
Cons
It can be much slower; i.e. clearing and redrawing everything appears to be more expensive than the set... methods. So for large animations, this approach can be kind of painful.
Clearing the axis also clears things like the axis labels, axis limits, other text, etc. So, those sorts of formatting things need to be included in update (else they will be gone). This can be annoying if you want some things to change, but others to stay the same.
Of course, the speed is a big con. Here is an example showing the difference. Given this data:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
np.random.seed(42)
frames = 40
x = np.arange(frames)
y = np.sin(x)
colors = itertools.cycle(['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])
data = [(np.random.uniform(-1, 1, 10) + x[i],
np.random.uniform(-1, 1, 10) + y[i])
for i in range(frames)]
You can plot using the set... method:
fig, ax = plt.subplots()
s = ax.scatter([], [])
ax.set_xlim(-2, frames+2)
ax.set_ylim(min(y) - 1, max(y) + 1)
def update(i):
s.set_offsets(np.column_stack([data[i][0], data[i][1]]))
s.set_facecolor(next(colors))
ani = animation.FuncAnimation(fig, update, frames=frames, interval=100)
ani.save('set.gif', writer='pillow')
Or the "clear" method:
fig, ax = plt.subplots()
def update(i):
ax.clear()
ax.scatter(data[i][0], data[i][1], c=next(colors))
ax.set_xlim(-2, frames+2)
ax.set_ylim(min(y) - 1, max(y) + 1)
ani = animation.FuncAnimation(fig, update, frames=frames, interval=100)
ani.save('clear.gif', writer='pillow')
To get this figure:
Using %%time, we can see that clearing and replotting takes (more than) twice as long:
for set...: Wall time: 1.33 s
for clear: Wall time: 2.73 s
Play with the frames parameter to test this at different scales. For smaller animations (less frames/data), the time difference between the two methods is inconsequential (and for me, sometimes causes me to prefer the clearing method). But for larger cases, using set_... can save significant time.
Here is the thing. I used to a user of Qt and Matlab and I am not quite familiar with the animation system on the matplotlib.
But I do have find a way that can make any kind of animation you want just like it is in matlab. It is really powerful. No need to check the module references and you are good to plot anything you want. So I hope it can help.
The basic idea is to use the time event inside PyQt( I am sure other Gui system on the Python like wxPython and TraitUi has the same inner mechanism to make an event response. But I just don't know how). Every time a PyQt's Timer event is called I refresh the whole canvas and redraw the whole picture, I know the speed and performance may be slowly influenced but it is not that much.
Here is a little example of it:
import sys
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
class Monitor(FigureCanvas):
def __init__(self):
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
self.x = np.linspace(0,5*np.pi,400)
self.p = 0.0
self.y = np.sin(self.x+self.p)
self.line = self.ax.scatter(self.x,self.y)
self.fig.canvas.draw()
self.timer = self.startTimer(100)
def timerEvent(self, evt):
# update the height of the bars, one liner is easier
self.p += 0.1
self.y = np.sin(self.x+self.p)
self.ax.cla()
self.line = self.ax.scatter(self.x,self.y)
self.fig.canvas.draw()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = Monitor()
w.setWindowTitle("Convergence")
w.show()
sys.exit(app.exec_())
You can adjust the refresh speed in the
self.timer = self.startTimer(100)
I am just like you who want to use the Animated scatter plot to make a sorting animation. But I just cannot find a so called "set" function. So I refreshed the whole canva.
Hope it helps..
Why Not try this
import numpy as np
import matplotlib.pyplot as plt
x=np.random.random()
y=np.random.random()
fig, ax = plt.subplots()
ax.scatter(x,y,color='teal')
ax.scatter(y,x,color='crimson')
ax.set_xlim([0,1])
ax.set_ylim([0,1])
for i in np.arange(50):
x=np.random.random()
y=np.random.random()
bha=ax.scatter(x,y)
plt.draw()
plt.pause(0.5)
bha.remove()
plt.show()

group multiple plot in one figure python

My function return 28 plots ( figure) but i need to group them on one figure this is my code for generating 28 plots
for cat in df.ASS_ASSIGNMENT.unique() :
a = df.loc[df['ASS_ASSIGNMENT'] == cat]
dates = a['DATE']
prediction = a['CSPL_RECEIVED_CALLS']
plt.plot(dates,prediction)
plt.ylabel("nmb_app")
plt.legend([cat.decode('utf-8')],loc='best')
plt.xlabel(cat.decode('utf-8'))
Use plt.subplots. For example,
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(ncols=7, nrows=4)
for i, ax in enumerate(axes.flatten()):
x = np.random.randint(-5, 5, 20)
y = np.random.randint(-5, 5, 20)
ax.scatter(x, y)
ax.set_title('Axis {}'.format(i))
plt.tight_layout()
Going a little deeper, as Mauve points out, it depends if you want 28 curves in a single plot in a single figure or 28 individual plots each with its own axis all in one figure.
Assuming you have a dataframe, df, with 28 columns you can put all 28 curves on a single plot in a single figure using plt.subplots like so,
fig1, ax1 = plt.subplots()
df.plot(color=colors, ax=ax1)
plt.legend(ncol=4, loc='best')
If instead you want 28 individual axes all in one figure you can use plt.subplots this way
fig2, axes = plt.subplots(nrows=4, ncols=7)
for i, ax in enumerate(axes.flatten()):
df[df.columns[i]].plot(color=colors[i], ax=ax)
ax.set_title(df.columns[i])
Here df looks like
In [114]: df.shape
Out[114]: (15, 28)
In [115]: df.head()
Out[115]:
IYU ZMK DRO UIC DOF ASG DLU \
0 0.970467 1.026171 -0.141261 1.719777 2.344803 2.956578 2.433358
1 7.982833 7.667973 7.907016 7.897172 6.659990 5.623201 6.818639
2 4.608682 4.494827 6.078604 5.634331 4.553364 5.418964 6.079736
3 1.299400 3.235654 3.317892 2.689927 2.575684 4.844506 4.368858
4 10.690242 10.375313 10.062212 9.150162 9.620630 9.164129 8.661847
BO1 JFN S9Q ... X4K ZQG 2TS \
0 2.798409 2.425745 3.563515 ... 7.623710 7.678988 7.044471
1 8.391905 7.242406 8.960973 ... 5.389336 5.083990 5.857414
2 7.631030 7.822071 5.657916 ... 2.884925 2.570883 2.550461
3 6.061272 4.224779 5.709211 ... 4.961713 5.803743 6.008319
4 10.240355 9.792029 8.438934 ... 6.451223 5.072552 6.894701
RS0 P6T FOU LN9 CFG C9D ZG2
0 9.380106 9.654287 8.065816 7.029103 7.701655 6.811254 7.315282
1 3.931037 3.206575 3.728755 2.972959 4.436053 4.906322 4.796217
2 3.784638 2.445668 1.423225 1.506143 0.786983 -0.666565 1.120315
3 5.749563 7.084335 7.992780 6.998563 7.253861 8.845475 9.592453
4 4.581062 5.807435 5.544668 5.249163 6.555792 8.299669 8.036408
and was created by
import pandas as pd
import numpy as np
import string
import random
m = 28
n = 15
def random_data(m, n):
return np.cumsum(np.random.randn(m*n)).reshape(m, n)
def id_generator(number, size=6, chars=string.ascii_uppercase + string.digits):
sequence = []
for n in range(number):
sequence.append(''.join(random.choice(chars) for _ in range(size)))
return sequence
df = pd.DataFrame(random_data(n, m), columns=id_generator(number=m, size=3))
Colors was defined as
import seaborn as sns
colors = sns.cubehelix_palette(28, rot=-0.4)

Categories

Resources