Update plot in a loop in Jupyter Notebook - python

I am making multiple plots on the same canvas using data from dataframe. I want to update the plot in a loop based on newly filtered data.
The code I am using is:
from IPython import display
fig = plt.figure(figsize = (10,13))
ax.set_xlim(-0.5,2.5)
ax.set_ylim(-0.5,3.5)
# d_a is a list of dataframes created using different filters
for data_filtered in d_a:
for index,row in data_filtered.iterrows():
x_values = [row['x'] - xy_offset[row['direction']][0]/2.1,
row['x']+xy_offset[row['direction']][0]/2.1]
y_values = [row['y']-xy_offset[row['direction']][1]/2.1,
row['y']+xy_offset[row['direction']][1]/2.1]
# for each row in the dataframe a plot is drawn
plt.plot(x_values,y_values, linewidth=20,color= 'green',
alpha = 0.1
)
t.sleep(0.5)
display.display(plt.gcf())
display.clear_output(wait =True)
Output:(Dynamic and changes with the iteration)
Now the idea is to use a varying value of 'alpha' in the plot based on a certain value in the row of the dataframe.
When I plot this, the opacity just keeps on increasing even when alpha is kept constant as in the code snipped shown.
Shouldn't the display be cleared entirely and a new plot made instead?

You need to clear either the matplotlib axis or figure also, with plt.cla() or plt.clf() respectively. Otherwise the lines will be drawn onto the same matplotlib axis object in memory, and redrawn at each iteration.
from IPython import display
import numpy as np
import time as t
fig = plt.figure(figsize = (10,13))
ax = fig.subplots()
shifts = [1, 3, 4, 1, 2, 5, 2]
for shift in shifts:
ax.plot([0, 1], [0 + shift/10, 1 - shift/10], linewidth=20, color= 'green', alpha = 0.1)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
display.display(plt.gcf())
t.sleep(0.5)
plt.cla()
display.clear_output(wait =True)

Related

Add a single colorbar after generating subplots in a loop

My problem lies within the colorbar after generating subplots based on a data within certain gridpoints.
If I generate a single Snapshot, the colorbar will appear:
fig1, ax = plt.subplots()
im = ax.imshow(data[0])
fig1.colorbar(im, ax=ax,label='blublub')
ax.set_title = ('blabla')
But when I generate a loop for several subplots like, I cannot implement showing the colorbar at least once at the bottom of the figure.
fig = plt.figure(figsize = (15, 15))
for t in range(17):
plt.subplot(5, 5, t + 1)
plt.pcolor(x1grid, x2grid, data[t])
txt = "t = {t:.1f}"
plt.title(txt.format(t = time[t]))
I reviewed all questions on this platform, but could not implement a single one within my code. If my question is covered by already existing one, pls excuse me. I will review it and delete my post.
Thanks in advance
Maria
If you want to add a colorbar to each subplot in your loop, you can use the plt.colorbar() function in the same way as you did for the single subplot. However, instead of passing the im object as the first argument, you should pass the mappable object returned by plt.pcolor(). For example:
fig = plt.figure(figsize = (15, 15))
for t in range(17):
plt.subplot(5, 5, t + 1)
pc = plt.pcolor(x1grid, x2grid, data[t])
plt.colorbar(pc, label='blublub')
txt = "t = {t:.1f}"
plt.title(txt.format(t = time[t]))
I hope that you understand that, if you want to use a single colormap, you should use a single normalization for all of your plots. That said, here it is your figure with your colormap
And here it's the code
import numpy as np
import matplotlib.pyplot as plt
# Let's fake the data…
Nt, Nxy = 17, 201
x = y = np.linspace(0, 10, Nxy)
x, y = np.meshgrid(x,y)
data = np.empty((Nt, Nxy, Nxy))
for t in range(Nt):
t4 = 1+t/4
data[t] = t4*(1+np.sin( t4*x+y/t4))
# as I said, we need a single normalize object for all the data
norm = plt.Normalize(round(data.min()), round(data.max()))
# now we plot the data's elements
fig = plt.figure(figsize = (8, 8), layout='constrained')
# because we'll later need a list of all axes …
axes = []
for t in range(17):
axes.append(plt.subplot(5, 5, t + 1))
axes[-1].pcolor(x, y, data[t], norm=norm)
axes[-1].set_title('t='+str(t))
# decorate the figure and place the colormap
fig.suptitle('A single colormap, a single normalization scale\n',
size='xx-large')
fig.colorbar(plt.cm.ScalarMappable(norm=norm),
orientation='horizontal',
# ax = axes instructs the colormap to extend over all axes
ax=axes,
# but it's to much, so we shrink it to 75%
shrink=0.75,
# and make it a little slimmer
aspect=30,
)
# I'm satisfied, hence
plt.show()
ps if you want squarish plots, you could a) reduce the figure width or b) play with the aspect of the plots.

matplotlib subplots: how to freeze x and y axis?

Good evening
matplotlib changes the scaling of the diagram when drawing with e.g. hist() or plot(), which is usually great.
Is it possible to freeze the x and y axes in a subplot after drawing, so that further drawing commands do not change them anymore? For example:
fig, (plt1, plt2) = plt.subplots(2, 1, figsize=(20, 10))
plt1.hist(…)
plt1.plot(…)
# How can this get done?:
plt1.Freeze X- and Y-Axis
# Those commands no longer changes the x- and y-axis
plt1.plot(…)
plt1.plot(…)
Thanks a lot, kind regards,
Thomas
Matplotlib has an autoscale() function that you can turn on or off for individual axis objects and their individual x- and y-axes:
from matplotlib import pyplot as plt
fig, (ax1, ax2) = plt.subplots(2)
#standard is that both x- and y-axis are autoscaled
ax1.plot([1, 3, 5], [2, 5, 1], label="autoscale on")
#rendering the current output
fig.draw_without_rendering()
#turning off autoscale for the x-axis of the upper panel
#the y-axis will still be autoscaled for all following artists
ax1.autoscale(False, axis="x")
ax1.plot([-1, 7], [-2, 4], label="autoscale off")
ax1.legend()
#other axis objects are not influenced
ax2.plot([-2, 4], [3, 1])
plt.show()
Sample output:
Use plt.xlim and plt.ylim to get the current limits after plotting the initial plots, then use those values to set the limits after plotting the additional plots:
import matplotlib.pyplot as plt
# initial data
x = [1, 2, 3, 4, 5]
y = [2, 4, 8, 16, 32]
plt.plot(x, y)
# Save the current limits here
xlims = plt.xlim()
ylims = plt.ylim()
# additional data (will change the limits)
new_x = [-10, 100]
new_y = [2, 2]
plt.plot(new_x, new_y)
# Then set the old limits as the current limits here
plt.xlim(xlims)
plt.ylim(ylims)
plt.show()
Output figure (note how the x-axis limits are ~ [1, 5] even though the orange line is defined in the range [-10, 100]) :
To freeze x-axis specify the domain on the plot function:
import matplotlib.pyplot as plt
fig, (plt1, plt2) = plt.subplots(2, 1, figsize=(20, 10))
# range(min, max, step)
n = range(0, 10, 1) # domain [min, max] = [0, 9]
# make sure your functions has equal length
f = [i * 2 for i in n]
g = [i ** 2 for i in n]
# keep x-axis scale the same by specifying x-axis on the plot function.
plt1.plot(n, f) # funtion (f) range depends on it's value [min, max]
plt1.plot(n, g) # funtion (g) range depends on it's value [min, max]
# range of (f) and (g) impacts the scaling of y-axis
See matplotlib.pyplot for hist function parameters.
The answer of #jfaccioni is almost perfect (thanks a lot!), but it does not work with matplotlib subplots (as asked) because Python, as unfortunately so often, does not have uniform attributes and methods (not even in the same module), and so the matplotlib interface to a plot and a subplot is different.
In this example, this code works with a plot but not with a subplot:
# this works for plots:
xlims = plt.xlim()
# and this must be used for subplots :-(
xlims = plt1.get_xlim()
therefore, this code works with subplots:
import matplotlib.pyplot as plt
fig, (plt1, plt2) = plt.subplots(2, 1, figsize=(20, 10))
# initial data
x = [1, 2, 3, 4, 5]
y = [2, 4, 8, 16, 32]
plt1.plot(x, y)
# Save the current limits here
xlims = plt1.get_xlim()
ylims = plt1.get_ylim()
# additional data (will change the limits)
new_x = [-10, 100]
new_y = [2, 2]
plt1.plot(new_x, new_y)
# Then set the old limits as the current limits here
plt1.set_xlim(xlims)
plt1.set_ylim(ylims)
plt.show()
btw: Freezing the x- and y axes can even be done by 2 lines because once again, python unfortunately has inconsistent attributes:
# Freeze the x- and y axes:
plt1.set_xlim(plt1.get_xlim())
plt1.set_ylim(plt1.get_ylim())
It does not make sense at all to set xlim to the value it already has.
But because Python matplotlib misuses the xlim/ylim attribute and sets the current plot size (and not the limits!), therefore this code works not as expected.
It helps to solve the task in question, but those concepts makes using matplotlib hard and reading matplotlib code is annoying because one must know hidden / unexpected internal behaviors.

Broken y-axis from 0 to value-start with matplotlib and pandas data

My sample data start with values beginning with 700 so that there is nothing between 0 and 700. I want to cut that range out of the line plot but I also want to point the reader to that cut by visualizing it like this.
This example picture is manipulated via a drawing software just to explain what I want.
Something like this is explained in the matplotlib documentation. But I do not understand what happens there in the example code. And I am not sure if it fits to my case because the example cutting data. And I just want to cut the area where is no data present.
Here is a minimal working example producing line figure where the y-axis starts at 700 not 0. Can we start from here?
#!/usr/bin/env python3
import random as rd
import matplotlib.pyplot as plt
import pandas as pd
# version info
print(pd.__version__) # 1.2.5
print(matplotlib.__version__) # 3.5.1
print(plt.get_backend()) # TkAgg
# sample data
rd.seed(0)
data = [rd.sample(range(700, 850), 12) for i in range(3)]
df = pd.DataFrame(data)
# not sure if this is a usual maptlotlib/pyplot approach to
# simply plot three lines
fig, (ax) = plt.subplots(1, 1)
for row in range(3):
ax.plot(df.iloc[row])
# limit range of y-axis to the data only
ax.set_ylim(bottom=650,
top=900)
plt.show()
That is the result.
By the way: I am also often confused by all the matplotlib based approaches how to draw figures; there are submodules (pyplot, ...) and pandas itself have an API (pandas.DataFrame.plot()). It is hard bringing this together and to decide where to start.
The matplotlib example you provided the link for shows how to plot data at different parts of the scale using two different subplots. You can use the same technique in your case and modify the height ratio of the bottom subplot to get the result you want.
See code below:
import random as rd
import matplotlib.pyplot as plt
import pandas as pd
# sample data
rd.seed(0)
data = [rd.sample(range(700, 850), 12) for i in range(3)]
df = pd.DataFrame(data)
fig, (ax1, ax2) = plt.subplots(2, 1,
sharex=True,
gridspec_kw={
'height_ratios': [1, 0.1]
})
fig.subplots_adjust(hspace=0.05)
for row in range(3):
ax1.plot(df.iloc[row])
ax2.plot(df.iloc[row])
ax1.set_ylim(700, 900) # outliers only
ax2.set_ylim(0, 100) # most of the data
# limit range of y-axis to the data only
# remove x-axis line's between the two sub-plots
ax1.spines['bottom'].set_visible(False) # 1st subplot bottom x-axis
ax2.spines['top'].set_visible(False) # 2nd subplot top x-axis
# 1st x-axis: move ticks from bottom to top
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # no labels
# 2nd x-axis: ticks on the bottom
ax2.xaxis.tick_bottom()
# 1st subplot y-axis: remove first tick
ax1.set_yticks(ax1.get_yticks()[1:])
# 2nd subplot y-axis: remove the last
ax2.set_yticks(ax2.get_yticks()[:-1])
# now draw the cut
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(
marker=[(-1, -d), (1, d)],
markersize=12, # "length" of cut-line
linestyle='none',
color='k', # ?
mec='k', # ?
mew=1, # line thickness
clip_on=False
)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
plt.show()
And the output gives:

How to remove the space between subplots in matplotlib.pyplot?

I am working on a project in which I need to put together a plot grid of 10 rows and 3 columns. Although I have been able to make the plots and arrange the subplots, I was not able to produce a nice plot without white space such as this one below from gridspec documentatation..
I tried the following posts, but still not able to completely remove the white space as in the example image. Can someone please give me some guidance? Thanks!
Matplotlib different size subplots
how to remove “empty” space
between subplots?
Here's my image:
Below is my code. The full script is here on GitHub.
Note: images_2 and images_fool are both numpy arrays of flattened images with shape (1032, 10), while delta is an image array of shape (28, 28).
def plot_im(array=None, ind=0):
"""A function to plot the image given a images matrix, type of the matrix: \
either original or fool, and the order of images in the matrix"""
img_reshaped = array[ind, :].reshape((28, 28))
imgplot = plt.imshow(img_reshaped)
# Output as a grid of 10 rows and 3 cols with first column being original, second being
# delta and third column being adversaril
nrow = 10
ncol = 3
n = 0
from matplotlib import gridspec
fig = plt.figure(figsize=(30, 30))
gs = gridspec.GridSpec(nrow, ncol, width_ratios=[1, 1, 1])
for row in range(nrow):
for col in range(ncol):
plt.subplot(gs[n])
if col == 0:
#plt.subplot(nrow, ncol, n)
plot_im(array=images_2, ind=row)
elif col == 1:
#plt.subplot(nrow, ncol, n)
plt.imshow(w_delta)
else:
#plt.subplot(nrow, ncol, n)
plot_im(array=images_fool, ind=row)
n += 1
plt.tight_layout()
#plt.show()
plt.savefig('grid_figure.pdf')
A note at the beginning: If you want to have full control over spacing, avoid using plt.tight_layout() as it will try to arange the plots in your figure to be equally and nicely distributed. This is mostly fine and produces pleasant results, but adjusts the spacing at its will.
The reason the GridSpec example you're quoting from the Matplotlib example gallery works so well is because the subplots' aspect is not predefined. That is, the subplots will simply expand on the grid and leave the set spacing (in this case wspace=0.0, hspace=0.0) independent of the figure size.
In contrast to that you are plotting images with imshow and the image's aspect is set equal by default (equivalent to ax.set_aspect("equal")). That said, you could of course put set_aspect("auto") to every plot (and additionally add wspace=0.0, hspace=0.0 as arguments to GridSpec as in the gallery example), which would produce a plot without spacings.
However when using images it makes a lot of sense to keep an equal aspect ratio such that every pixel is as wide as high and a square array is shown as a square image.
What you will need to do then is to play with the image size and the figure margins to obtain the expected result. The figsize argument to figure is the figure (width, height) in inch and here the ratio of the two numbers can be played with. And the subplot parameters wspace, hspace, top, bottom, left can be manually adjusted to give the desired result.
Below is an example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
nrow = 10
ncol = 3
fig = plt.figure(figsize=(4, 10))
gs = gridspec.GridSpec(nrow, ncol, width_ratios=[1, 1, 1],
wspace=0.0, hspace=0.0, top=0.95, bottom=0.05, left=0.17, right=0.845)
for i in range(10):
for j in range(3):
im = np.random.rand(28,28)
ax= plt.subplot(gs[i,j])
ax.imshow(im)
ax.set_xticklabels([])
ax.set_yticklabels([])
#plt.tight_layout() # do not use this!!
plt.show()
Edit:
It is of course desireable not having to tweak the parameters manually. So one could calculate some optimal ones according to the number of rows and columns.
nrow = 7
ncol = 7
fig = plt.figure(figsize=(ncol+1, nrow+1))
gs = gridspec.GridSpec(nrow, ncol,
wspace=0.0, hspace=0.0,
top=1.-0.5/(nrow+1), bottom=0.5/(nrow+1),
left=0.5/(ncol+1), right=1-0.5/(ncol+1))
for i in range(nrow):
for j in range(ncol):
im = np.random.rand(28,28)
ax= plt.subplot(gs[i,j])
ax.imshow(im)
ax.set_xticklabels([])
ax.set_yticklabels([])
plt.show()
Try to add to your code this line:
fig.subplots_adjust(wspace=0, hspace=0)
And for every an axis object set:
ax.set_xticklabels([])
ax.set_yticklabels([])
Following the answer by ImportanceOfBeingErnest, but if you want to use plt.subplots and its features:
fig, axes = plt.subplots(
nrow, ncol,
gridspec_kw=dict(wspace=0.0, hspace=0.0,
top=1. - 0.5 / (nrow + 1), bottom=0.5 / (nrow + 1),
left=0.5 / (ncol + 1), right=1 - 0.5 / (ncol + 1)),
figsize=(ncol + 1, nrow + 1),
sharey='row', sharex='col', # optionally
)
If you are using matplotlib.pyplot.subplots you can display as many images as you want using Axes arrays. You can remove the spaces between images by making some adjustments to the matplotlib.pyplot.subplots configuration.
import matplotlib.pyplot as plt
def show_dataset_overview(self, img_list):
"""show each image in img_list without space"""
img_number = len(img_list)
img_number_at_a_row = 3
row_number = int(img_number /img_number_at_a_row)
fig_size = (15*(img_number_at_a_row/row_number), 15)
_, axs = plt.subplots(row_number,
img_number_at_a_row,
figsize=fig_size ,
gridspec_kw=dict(
top = 1, bottom = 0, right = 1, left = 0,
hspace = 0, wspace = 0
)
)
axs = axs.flatten()
for i in range(img_number):
axs[i].imshow(img_list[i])
axs[i].set_xticks([])
axs[i].set_yticks([])
Since we create subplots here first, we can give some parameters for grid_spec using the gridspec_kw parameter(source).
Among these parameters are the "top = 1, bottom = 0, right = 1, left = 0, hspace = 0, wspace = 0" parameters that will prevent inter-image spacing. To see other parameters, please visit here.
I usually use a figure size like (30,15) when setting the figure_size above. I generalized this a bit and added it to the code. If you wish, you can enter a manual size here.
Here's another simple approach using the ImageGrid class (adapted from this answer).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
nrow = 5
ncol = 3
fig = plt.figure(figsize=(4, 10))
grid = ImageGrid(fig,
111, # as in plt.subplot(111)
nrows_ncols=(nrow,ncol),
axes_pad=0,
share_all=True,)
for row in grid.axes_column:
for ax in row:
im = np.random.rand(28,28)
ax.imshow(im)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)

Matplotlib: updating multiple scatter plots in a loop

I have two data sets that I would like to produce scatterplots for, with different colors.
Following the advice in MatPlotLib: Multiple datasets on the same scatter plot
I managed to plot them. However, I would like to be able to update the scatter plots inside of a loop that will affect both sets of data. I looked at the matplotlib animation package but it doesn't seem to fit the bill.
I cannot get the plot to update from within a loop.
The structure of the code looks like this:
fig = plt.figure()
ax1 = fig.add_subplot(111)
for g in range(gen):
# some simulation work that affects the data sets
peng_x, peng_y, bear_x, bear_y = generate_plot(population)
ax1.scatter(peng_x, peng_y, color = 'green')
ax1.scatter(bear_x, bear_y, color = 'red')
# this doesn't refresh the plots
Where generate_plot() extracts the relevant plotting information (x,y) coords from a numpy array with additional info and assigns them to the correct data set so they can be colored differently.
I've tried clearing and redrawing but I can't seem to get it to work.
Edit: Slight clarification. What I'm looking to do basically is to animate two scatter plots on the same plot.
Here's a code that might fit your description:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Create new Figure and an Axes which fills it.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_axes([0, 0, 1, 1], frameon=False)
ax.set_xlim(-1, 1), ax.set_xticks([])
ax.set_ylim(-1, 1), ax.set_yticks([])
# Create data
ndata = 50
data = np.zeros(ndata, dtype=[('peng', float, 2), ('bear', float, 2)])
# Initialize the position of data
data['peng'] = np.random.randn(ndata, 2)
data['bear'] = np.random.randn(ndata, 2)
# Construct the scatter which we will update during animation
scat1 = ax.scatter(data['peng'][:, 0], data['peng'][:, 1], color='green')
scat2 = ax.scatter(data['bear'][:, 0], data['bear'][:, 1], color='red')
def update(frame_number):
# insert results from generate_plot(population) here
data['peng'] = np.random.randn(ndata, 2)
data['bear'] = np.random.randn(ndata, 2)
# Update the scatter collection with the new positions.
scat1.set_offsets(data['peng'])
scat2.set_offsets(data['bear'])
# Construct the animation, using the update function as the animation
# director.
animation = FuncAnimation(fig, update, interval=10)
plt.show()
You might also want to take a look at http://matplotlib.org/examples/animation/rain.html. You can learn more tweaks in animating a scatter plot there.

Categories