How does the indexing of subplots work - python

I have the following:
import matplotlib.pyplot as plt
fig = plt.figure()
for i in range(10):
ax = fig.add_subplot(551 + i)
ax.plot([1,2,3,4,5], [10,5,10,5,10], 'r-')
I was imagining that the 55 means that it is creating a grid that is 5 subplots wide and 5 subplots deep - so can cater for 25 subplots?
The for loop will just iterate 10 times - so I thought (obviously wrongly) that 25 possible plots would accomodate those iterations ok but I get the following:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-118-5775a5ea6c46> in <module>()
10
11 for i in range(10):
---> 12 ax = fig.add_subplot(551 + i)
13 ax.plot([1,2,3,4,5], [10,5,10,5,10], 'r-')
14
/home/blah/anaconda/lib/python2.7/site-packages/matplotlib/figure.pyc in add_subplot(self, *args, **kwargs)
1003 self._axstack.remove(ax)
1004
-> 1005 a = subplot_class_factory(projection_class)(self, *args, **kwargs)
1006
1007 self._axstack.add(key, a)
/home/blah/anaconda/lib/python2.7/site-packages/matplotlib/axes/_subplots.pyc in __init__(self, fig, *args, **kwargs)
62 raise ValueError(
63 "num must be 1 <= num <= {maxn}, not {num}".format(
---> 64 maxn=rows*cols, num=num))
65 self._subplotspec = GridSpec(rows, cols)[int(num) - 1]
66 # num - 1 for converting from MATLAB to python indexing
ValueError: num must be 1 <= num <= 30, not 0

In the convience shorthand notation, the 55 does mean there are 5 rows and 5 columns. However, the shorthand notation only works for single-digit integers (i.e. for nrows, ncols and plot_number all less than 10).
You can expand it to full notation (i.e. use commas: add_subplot(nrows, ncols, plot_number)) and then all will work fine for you:
for i in range(10):
ax = fig.add_subplot(5, 5, 1 + i)
ax.plot([1,2,3,4,5], [10,5,10,5,10], 'r-')
From the docs for plt.subplot (which uses the same args as fig.add_subplot) :
Typical call signature:
subplot(nrows, ncols, plot_number)
Where nrows and ncols are used to notionally split the figure into nrows * ncols sub-axes, and
plot_number is used to identify the particular subplot that this
function is to create within the notional grid. plot_number starts at
1, increments across rows first and has a maximum of nrows * ncols.
In the case when nrows, ncols and plot_number are all less than 10, a convenience exists, such that the a 3 digit number can be given
instead, where the hundreds represent nrows, the tens represent ncols
and the units represent plot_number.

Although tom answered your question, in this sort of situation you should be using fig, axs = plt.subplots(n, m). This will create a new figure with the n rows and m columns of subplots. fig is the figure created. axs is a 2D numpy array where each element in the array is the subplot in the corresponding location in the figure. So the top-right element axs is the top-right subplot in the figure. You can access the subplots through normal indexing, or loop over them.
So in your case you can do
import matplotlib.pyplot as plt
# axs is a 5x5 numpy array of axes objects
fig, axs = plt.subplots(5, 5)
# "ravel" flattens the numpy array without making a copy
for ax in axs.ravel():
ax.plot([1,2,3,4,5], [10,5,10,5,10], 'r-')

Related

Automatically generate subplots based on length of a list

I want to generate colorplot subplots based on a numpy array's length which I call dets. I want to use the array length to determine the right number of columns and rows for the subplots. If square, plot a square matrix of subplots, if not square, add another row. For starters, I have written some code to check if the array's length would create a square matrix of subplots with the following:
data_f = np.random.rand(len(dets),2,5)
dets = np.arange(-5,-0.75,0.25)
x = np.array([1,5,6,3,8,9,2,3,10,12,3])
v = np.linspace(0,10,len(x))
square = np.sqrt(len(dets))
check_square = len(dets)%square
non_square = 1
print(len(data_f))
if check_square == 0:
nrows = int(np.sqrt(len(dets)))
ncols = int(np.sqrt(len(dets)))
else:
nrows = int(np.sqrt(len(dets)))+non_square
ncols = int(np.sqrt(len(dets)))
fig, ax = plt.subplots(nrows, ncols, sharex='col', sharey='row')
for i in range(nrows):
for j in range(ncols):
if i==0:
im = ax[i,j].imshow(data_f[j],extent=(x.min(), x.max(), v.min(), v.max()),origin='lower',aspect='auto')
else:
im = ax[i,j].imshow(data_f[j+ncols*i],extent=(x.min(), x.max(), v.min(), v.max()),origin='lower',aspect='auto')
The output plot:
This plots 17 plots but the resulting plots I cannot adjust because of the error
This plots everything I want, except it always smushes the plots together in a weird way because of the following error:
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
~\AppData\Local\Temp\1/ipykernel_4560/3817292743.py in <module>
6 im = ax[i,j].imshow(data_f[j],extent=(x.min(), x.max(), v.min(), v.max()),origin='lower',aspect='auto')
7 else:
----> 8 im = ax[i,j].imshow(data_f[j+ncols*i],extent=(x.min(), x.max(), v.min(), v.max()),origin='lower',aspect='auto')
9
IndexError: index 17 is out of bounds for axis 0 with size 17
It is because you have a total of 20 axes, and when you loop through ncols and nrows, you will get 20 iterations. But len(data_f) is only 17.
at the start of your iteration, add
if(j + ncols*i) == len(data_f):
break
I did this and it stopped the error

Subplots in columns and rows

I would like my sybplots to be generated in 2x columns and 5x rows.
I've also tried adding ncols=2, nrows=5 to the code. didn't work.
And when I change the subplots to plt.subplots(5,2) instead of plt.subplots(10,1) it says (see added picture of code+error message):
AttributeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_9844/709244097.py in
13
14 for ax, afstand, tid in zip(ax, afstande, tider):
---> 15 ax.plot(tid, afstand)
16 ax.set_title("x(t)", fontsize=12)
17 ax.set_xlabel("tid (s)", fontsize=12)
AttributeError: 'numpy.ndarray' object has no attribute 'plot'
My code:
from scipy.optimize import fmin
a = -75.64766759
b = 68.02691163
f = lambda x: a * x + b
afstand1, afstand2, afstand3, afstand4, afstand5, afstand6, afstand7, afstand8, afstand9, afstand10 = f(U1), f(U2), f(U3), f(U4), f(U5),f(U6), f(U7), f(U8), f(U9), f(U10)
afstande = [afstand1, afstand2, afstand3, afstand4, afstand5, afstand6, afstand7, afstand8, afstand9, afstand10]
tider = [tid1, tid2, tid3, tid4, tid5, tid6, tid7, tid8, tid9, tid10]
fig, ax = plt.subplots(10,1, figsize=(7,25))
plt.subplots_adjust(hspace=0.55)
#loop
for ax, afstand, tid in zip(ax, afstande, tider):
ax.plot(tid, afstand)
ax.set_title("x(t)", fontsize=12)
ax.set_xlabel("tid (s)", fontsize=12)
ax.set_ylabel("Position", fontsize=12)
enter image description here
First of all, you're using the same variable name for the array of axis and in the loop, you should change that. Subplot-axes are stored in numpy arrays. If you only have 1 row, then looping over the array gives you the elements, but in a x*y pattern of subplots, you loop over a two-dimensional array of axis, which yields the rows. You can solve that by using .flat to get a one-dimensional view.
fig, axs = plt.subplots(ncols=5, nrows=2)
for ax in axs.flat:
ax.plot(...)

python saving multiple subplot figures to pdf

I am new with python I am trying to save a huge bunch of data into a pdf with figures using PdfPages of matplotlib and subplots. Problem is that I found a blottleneck I dont know how to solve, the code goes something like:
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
with PdfPages('myfigures.pdf') as pdf:
for i in range(1000):
f,axarr = plt.subplots(2, 3)
plt.subplots(2, 3)
axarr[0, 0].plot(x1, y1)
axarr[1, 0].plot(x2, y2)
pdf.savefig(f)
plt.close('all')
Creating a figure each loop it is highly time consuming, but if I put that outside the loop it doesnt clear each plot. Other options I tried like clear() or clf() didnt work either or ended in creating multiple different figures, anyone as an idea on how to put this in a different way so that it goes faster?
Multipage PDF appending w/ matplotlib
Create 𝑚-rows × 𝑛-cols matrices of subplot axes arrays per pdf page & save (append) as each page's matrix of subplots becomes completely full → then create new page, repeat, 𝐞𝐭𝐜.
To contain large numbers of subplots as multipage output inside a single pdf, immediately start filling the first page with your plot(s), then you'll need to create a new page after detecting that the latest subplot addition in your iteration of plot generation has maxed out the available space in the current page's 𝑚-rows × 𝑛-cols subplot-array layout [i.e., an 𝑚 × 𝑛 matrix of subplots], as applicable.
Here's a way to do it where the dimensions (𝑚 × 𝑛) controlling the number of subplots per page can easily be changed:
import sys
import matplotlib
from matplotlib.backends.backend_pdf import PdfPages
import matplotlib.pyplot as plt
import numpy as np
matplotlib.rcParams.update({"font.size": 6})
# Dimensions for any m-rows × n-cols array of subplots / pg.
m, n = 4, 5
# Don't forget to indent after the with statement
with PdfPages("auto_subplotting.pdf") as pdf:
"""Before beginning the iteration through all the data,
initialize the layout for the plots and create a
representation of the subplots that can be easily
iterated over for knowing when to create the next page
(and also for custom settings like partial axes labels)"""
f, axarr = plt.subplots(m, n, sharex="col", sharey="row")
arr_ij = [(x, y) for x, y in np.ndindex(axarr.shape)]
subplots = [axarr[index] for index in arr_ij]
# To conserve needed plotting real estate,
# only label the bottom row and leftmost subplots
# as determined automatically using m and n
splot_index = 0
for s, splot in enumerate(subplots):
splot.set_ylim(0, 0.15)
splot.set_xlim(0, 50)
last_row = m * n - s < n + 1
first_in_row = s % n == 0
if last_row:
splot.set_xlabel("X-axis label")
if first_in_row:
splot.set_ylabel("Y-axis label")
# Iterate through each sample in the data
for sample in range(33):
# As a stand-in for real data, let's just make numpy take 100 random draws
# from a poisson distribution centered around say ~25 and then display
# the outcome as a histogram
scaled_y = np.random.randint(20, 30)
random_data = np.random.poisson(scaled_y, 100)
subplots[splot_index].hist(
random_data,
bins=12,
normed=True,
fc=(0, 0, 0, 0),
lw=0.75,
ec="b",
)
# Keep collecting subplots (into the mpl-created array;
# see: [1]) through the samples in the data and increment
# a counter each time. The page will be full once the count is equal
# to the product of the user-set dimensions (i.e. m * n)
splot_index += 1
"""Once an mxn number of subplots have been collected
you now have a full page's worth, and it's time to
close and save to pdf that page and re-initialize for a
new page possibly. We can basically repeat the same
exact code block used for the first layout
initialization, but with the addition of 3 new lines:
+2 for creating & saving the just-finished pdf page,
+1 more to reset the subplot index (back to zero)"""
if splot_index == m * n:
pdf.savefig()
plt.close(f)
f, axarr = plt.subplots(m, n, sharex="col", sharey="row")
arr_ij = [(x, y) for x, y in np.ndindex(axarr.shape)]
subplots = [axarr[index] for index in arr_ij]
splot_index = 0
for s, splot in enumerate(subplots):
splot.set_ylim(0, 0.15)
splot.set_xlim(0, 50)
last_row = (m * n) - s < n + 1
first_in_row = s % n == 0
if last_row:
splot.set_xlabel("X-axis label")
if first_in_row:
splot.set_ylabel("Y-axis label")
# Done!
# But don't forget to save to pdf after the last page
pdf.savefig()
plt.close(f)
For any m×n layout, just change the declarations for the values of m and n, respectively. From the code above (where "m, n = 4, 5"), a 4x5 matrix of subplots with a total 33 samples is produced as a two-page pdf output file:
References
Link to matplotlib subplots official docs.
Note:
There will be, on the final page of the multipage PDF, a number of blank subplots equal to the remainder from the the product of your chosen subplots 𝑚 × 𝑛 layout dimension numbers and your total number of samples/data to plot. E.g., say m=3, and n=4, thus you get 3 rows of 4 subplots each equals 12 per page, and if you had say 20 samples, then there would be a two-page pdf auto-created with a total of 24 subplots with the last 4 (so full bottom-most row in this hypothetical example) of subplots on the second page empty.
Using seaborn
For a more advanced (& more "pythonic"*) extension of the implementation above, see below:
The multipage handling should probably be simplified by creating a new_page function; it's better to not repeat code verbatim*, especially if you start customizing the plots in which case you won't want to have to mirror every change and type the same thing twice. A more customized aesthetic based off of seaborn and utilizing the available matplotlib parameters like shown below might be preferable too.
Add a new_page function & some customizations for the subplot style:
import matplotlib.pyplot as plt
import numpy as np
import random
import seaborn as sns
from matplotlib.backends.backend_pdf import PdfPages
# this erases labels for any blank plots on the last page
sns.set(font_scale=0.0)
m, n = 4, 6
datasize = 37
# 37 % (m*n) = 13, (m*n) - 13 = 24 - 13 = 11. Thus 11 blank subplots on final page
# custom colors scheme / palette
ctheme = [
"k", "gray", "magenta", "fuchsia", "#be03fd", "#1e488f",
(0.44313725490196076, 0.44313725490196076, 0.88627450980392153), "#75bbfd",
"teal", "lime", "g", (0.6666674, 0.6666663, 0.29078014184397138), "y",
"#f1da7a", "tan", "orange", "maroon", "r", ] # pick whatever colors you wish
colors = sns.blend_palette(ctheme, datasize)
fz = 7 # labels fontsize
def new_page(m, n):
global splot_index
splot_index = 0
fig, axarr = plt.subplots(m, n, sharey="row")
plt.subplots_adjust(hspace=0.5, wspace=0.15)
arr_ij = [(x, y) for x, y in np.ndindex(axarr.shape)]
subplots = [axarr[index] for index in arr_ij]
for s, splot in enumerate(subplots):
splot.grid(
b=True,
which="major",
color="gray",
linestyle="-",
alpha=0.25,
zorder=1,
lw=0.5,
)
splot.set_ylim(0, 0.15)
splot.set_xlim(0, 50)
last_row = m * n - s < n + 1
first_in_row = s % n == 0
if last_row:
splot.set_xlabel("X-axis label", labelpad=8, fontsize=fz)
if first_in_row:
splot.set_ylabel("Y-axis label", labelpad=8, fontsize=fz)
return (fig, subplots)
with PdfPages("auto_subplotting_colors.pdf") as pdf:
fig, subplots = new_page(m, n)
for sample in xrange(datasize):
splot = subplots[splot_index]
splot_index += 1
scaled_y = np.random.randint(20, 30)
random_data = np.random.poisson(scaled_y, 100)
splot.hist(
random_data,
bins=12,
normed=True,
zorder=2,
alpha=0.99,
fc="white",
lw=0.75,
ec=colors.pop(),
)
splot.set_title("Sample {}".format(sample + 1), fontsize=fz)
# tick fontsize & spacing
splot.xaxis.set_tick_params(pad=4, labelsize=6)
splot.yaxis.set_tick_params(pad=4, labelsize=6)
# make new page:
if splot_index == m * n:
pdf.savefig()
plt.close(fig)
fig, subplots = new_page(m, n)
if splot_index > 0:
pdf.savefig()
plt.close(f)

Multiple subplots in triangular form using matplotlib

I have 6 lists and I want to create scatterplots for all possible combinations. This means that I want to create n(n-1)/2 combinations, so 15 plots. I have done this correctly based on the following
script.
for i in d:
for j in d:
if(j>i):
plt.cla() # Clear axis
plt.clf() # Clear figure
correlation_coefficient = str(np.corrcoef(d[i], d[j])[0][1])
plt.scatter(d[i],d[j])
plt.xlabel(names[i])
plt.ylabel(names[j])
plt.title('Correlation Coefficient: '+correlation_coefficient)
plt.grid()
plt.savefig(names[i]+"_"+names[j]+".png")
I want to save all these plots in one figure using subplot, where the first row will have the combinations (0,1) (0,2) (0,3) (0,4) (0,5) the second row (1,2) (1,3) (1,4) (1,5) the third row (2,3) (2,4) (2,5) etc.
So the final outcome will be a figure containing subplots in triangular form.
Update:
If I use subplots (code below) I was able to get somehow the result, but it is not optimal as I create a 6x6 frame whereas you can do it with 5x5.
fig = plt.figure()
cnt = 0
# Create scatterplots for all pairs
for i in d:
for j in d:
if(i>=j):
cnt=cnt+1
if(j>i):
cnt += 1
fig.add_subplot(6,6,cnt) #top left
correlation_coefficient = str(np.corrcoef(d[i], d[j])[0][1])
plt.scatter(np.log(d[i]),np.log(d[j]))
fig.savefig('test.png')
With gridspec:
from matplotlib import pyplot as plt
fig = plt.figure()
data = [(1,2,3),(8,2,3),(0,5,2),(4,7,1),(9,5,2),(8,8,8)]
plotz = len(data)
for i in range(plotz-1):
for j in range(plotz):
if(j>i) :
print(i,j)
ax = plt.subplot2grid((plotz-1, plotz-1), (i,j-1))
ax.xaxis.set_ticklabels([])
ax.yaxis.set_ticklabels([])
plt.scatter(data[i],data[j]) # might be nice with shared axis limits
fig.show()
With add_subplot, you've hit an oddity inherited from MATLAB, which 1-indexes the subplot count. (Also you have some counting errors.) Here's an example that just keeps track of the various indices:
from matplotlib import pyplot as plt
fig = plt.figure()
count = 0
data = [(1,2,3),(8,2,3),(0,5,2),(4,7,1),(9,5,2),(8,8,8)]
plotz = len(data)
for i in range(plotz-1):
for j in range(plotz):
if(j>i):
print(count, i,j, count -i)
ax = fig.add_subplot(plotz-1, plotz-1, count-i)
ax.xaxis.set_ticklabels([])
ax.yaxis.set_ticklabels([])
plt.text(.15, .5,'i %d, j %d, c %d'%(i,j,count))
count += 1
fig.show()
N.b.: the error from doing the obvious (your original code with add_subplot(5,5,cnt)) was a good hint:
...User/lib/python2.7/site-packages/matplotlib/axes.pyc in
init(self, fig, *args, **kwargs)
9249 self._subplotspec = GridSpec(rows,
cols)[num[0] - 1:num1]
9250 else:
-> 9251 self._subplotspec = GridSpec(rows, cols)[int(num) - 1]
9252 # num - 1 for converting from MATLAB to
python indexing

Multiple figures in a single window

I want to create a function which plot on screen a set of figures in a single window. By now I write this code:
import pylab as pl
def plot_figures(figures):
"""Plot a dictionary of figures.
Parameters
----------
figures : <title, figure> dictionary
"""
for title in figures:
pl.figure()
pl.imshow(figures[title])
pl.gray()
pl.title(title)
pl.axis('off')
It works perfectly but I would like to have the option for plotting all the figures in single window. And this code doesn't. I read something about subplot but it looks quite tricky.
You can define a function based on the subplots command (note the s at the end, different from the subplot command pointed by urinieto) of matplotlib.pyplot.
Below is an example of such a function, based on yours, allowing to plot multiples axes in a figure. You can define the number of rows and columns you want in the figure layout.
def plot_figures(figures, nrows = 1, ncols=1):
"""Plot a dictionary of figures.
Parameters
----------
figures : <title, figure> dictionary
ncols : number of columns of subplots wanted in the display
nrows : number of rows of subplots wanted in the figure
"""
fig, axeslist = plt.subplots(ncols=ncols, nrows=nrows)
for ind,title in enumerate(figures):
axeslist.ravel()[ind].imshow(figures[title], cmap=plt.gray())
axeslist.ravel()[ind].set_title(title)
axeslist.ravel()[ind].set_axis_off()
plt.tight_layout() # optional
Basically, the function creates a number of axes in the figures, according to the number of rows (nrows) and columns (ncols) you want, and then iterates over the list of axis to plot your images and adds the title for each of them.
Note that if you only have one image in your dictionary, your previous syntax plot_figures(figures) will work since nrows and ncols are set to 1 by default.
An example of what you can obtain:
import matplotlib.pyplot as plt
import numpy as np
# generation of a dictionary of (title, images)
number_of_im = 6
figures = {'im'+str(i): np.random.randn(100, 100) for i in range(number_of_im)}
# plot of the images in a figure, with 2 rows and 3 columns
plot_figures(figures, 2, 3)
You should use subplot.
In your case, it would be something like this (if you want them one on top of the other):
fig = pl.figure(1)
k = 1
for title in figures:
ax = fig.add_subplot(len(figures),1,k)
ax.imshow(figures[title])
ax.gray()
ax.title(title)
ax.axis('off')
k += 1
Check out the documentation for other options.
If you want to group multiple figures in one window you can do smth. like this:
import matplotlib.pyplot as plt
import numpy as np
img = plt.imread('C:/.../Download.jpg') # Path to image
img = img[0:150,50:200,0] # Define image size to be square --> Or what ever shape you want
fig = plt.figure()
nrows = 10 # Define number of columns
ncols = 10 # Define number of rows
image_heigt = 150 # Height of the image
image_width = 150 # Width of the image
pixels = np.zeros((nrows*image_heigt,ncols*image_width)) # Create
for a in range(nrows):
for b in range(ncols):
pixels[a*image_heigt:a*image_heigt+image_heigt,b*image_heigt:b*image_heigt+image_heigt] = img
plt.imshow(pixels,cmap='jet')
plt.axis('off')
plt.show()
As result you receive:
Building on the answer from: How to display multiple images in one figure correctly?, here is another method:
import math
import numpy as np
import matplotlib.pyplot as plt
def plot_images(np_images, titles = [], columns = 5, figure_size = (24, 18)):
count = np_images.shape[0]
rows = math.ceil(count / columns)
fig = plt.figure(figsize=figure_size)
subplots = []
for index in range(count):
subplots.append(fig.add_subplot(rows, columns, index + 1))
if len(titles):
subplots[-1].set_title(str(titles[index]))
plt.imshow(np_images[index])
plt.show()
You can also do this:
import matplotlib.pyplot as plt
f, axarr = plt.subplots(1, len(imgs))
for i, img in enumerate(imgs):
axarr[i].imshow(img)
plt.suptitle("Your title!")
plt.show()
def plot_figures(figures, nrows=None, ncols=None):
if not nrows or not ncols:
# Plot figures in a single row if grid not specified
nrows = 1
ncols = len(figures)
else:
# check minimum grid configured
if len(figures) > nrows * ncols:
raise ValueError(f"Too few subplots ({nrows*ncols}) specified for ({len(figures)}) figures.")
fig = plt.figure()
# optional spacing between figures
fig.subplots_adjust(hspace=0.4, wspace=0.4)
for index, title in enumerate(figures):
plt.subplot(nrows, ncols, index + 1)
plt.title(title)
plt.imshow(figures[title])
plt.show()
Any grid configuration (or none) can be specified as long as the product of the number of rows and the number of columns is equal to or greater than the number of figures.
For example, for len(figures) == 10, these are acceptable
plot_figures(figures)
plot_figures(figures, 2, 5)
plot_figures(figures, 3, 4)
plot_figures(figures, 4, 3)
plot_figures(figures, 5, 2)
import numpy as np
def save_image(data, ws=0.1, hs=0.1, sn='save_name'):
import matplotlib.pyplot as plt
m = n = int(np.sqrt(data.shape[0])) # (36, 1, 32, 32)
fig, ax = plt.subplots(m,n, figsize=(m*6,n*6))
ax = ax.ravel()
for i in range(data.shape[0]):
ax[i].matshow(data[i,0,:,:])
ax[i].set_xticks([])
ax[i].set_yticks([])
plt.subplots_adjust(left=0.1, bottom=0.1, right=0.9,
top=0.9, wspace=ws, hspace=hs)
plt.tight_layout()
plt.savefig('{}.png'.format(sn))
data = np.load('img_test.npy')
save_image(data, ws=0.1, hs=0.1, sn='multiple_plot')

Categories