How can I add multiple pre-generated subplots into a figure? - python

I'm using this library called FMSkill.
One of the method in this library is called .plot_timeseries
This method returns an Axes.Subplot object from matplotlib.
I'm trying to use that method to build a Multiplot Figure. I have a list called comparison that contains items upon which I can call the .plot_timeseries() method.
I've tried something like:
import math
import matplotlib as mpl
import numpy as np
import fmskill as fms
#Code to determine how many subplots in the figure
fig = plt.figure()
if len(comparison) % 2 == 0:
col, row = (int(math.ceil(np.sqrt(len(comparison)))),int(math.ceil(np.sqrt(len(comparison)))))
if len(comparison) % 2 == 1:
col, row = (int(math.ceil(np.sqrt(len(comparison)+1))),int(math.ceil(np.sqrt(len(comparison)+1))))
#Code where I try to iterate on the axes in my figures and set them using the .plot_timeseries() method
for graphs in range(len(comparison)):
ax = comparison[graphs].plot_timeseries()
fig.add_subplot(col,row,graphs+1)
This particular codes outputs a figure with the appropriate number of subplots. However the subplots are all empty. Also, it outputs every graphs generated by the .plot_timeseries() method separately.
I would like them to be put inside the subplots into one Figure.
Any ideas?
Thanks

The last portion of code is backwards.
# original
for graphs in range(len(comparison)):
ax = comparison[graphs].plot_timeseries()
fig.add_subplot(col,row,graphs+1)
Generate the axes object first, the pass it to the plot_timeseries function:
for graphs in range(len(comparison)):
ax = fig.add_subplot(col, row, graphs+1)
comparison[graphs].plot_timeseries(ax=ax)

Related

How do I stack multiple matplotlib figures in one figure and plot it?

I have a function that plots a graph with 5 columns, and I call this function
# Seaborn prints warnings if histogram has small values. We can ignore them for now
import warnings
warnings.filterwarnings('ignore')
import numpy as np
ax_arr = []
fig_arr = []
## Create a plot for every activation function
# enumerate enumerates between index and dictionary key
i = 0
for k, act_fns in act_fn_by_name.items():
act_fn = act_fns()
set_seed(42) # Setting the seed ensures that we have the same weight initialization for each activation function
net_actfn = BaseNetwork(act_fn=act_fn).to(device)
fig, ax = visualize_gradients(net_actfn, color=f"C{i}") # plotting function
ax_arr.append(ax)
fig_arr.append(fig)
i+=1
Instead of plotting them all individually within the function, I want to plot all subplots in a 6 by 5 figure.
I thought of collecting a list of figures and axes from the function and using the
matplotlib.figure.Figure and array of AxesSubplot to reconstruct the plots.
Does matplotlib work in that way at all?

Display images with matplotlib

I wanna display in a list of lists the images inside it using matplotlib. So for example I wanna have in the first row, the images of the first list, the second row, the images of the second list and so on. I tried this, but I obtain the images in each row, maybe because it will call over and over again subplot. How can I fix it?
index_plot=0
for query in list_plot:
for qm_images in query:
plt.subplot(3,5,index_plot+1)
plt.imshow(np.array(Image.open(qm_images)))
plt.show()
index_plot += 1
Instead of creating many subplots initially create a nested list of subplots with plt.subplots(), call imshow on each axis
import matplotlib.pyplot as plt
fig, axs = plt.subplots(3, 6)
for i, query in enumerate(list_plot):
for j, qm_images in enumerate(query:
axs[i][j].imshow(np.array(Image.open(qm_images)))
plt.show()

Save multiple seaborn plots into one pdf file

At the moment I'm learning how to work with matplotlib and seaborn and the concept behind it seems quite strange to me. One would expect the sns.countplot function to return an object that has a .plot() and .save() fuction so one could work with the plot in a different function.
Instead it seems that every call to sns.countplot overwrites the previous object (see MWE).
So one the one hand It would be grate if someone could provide a explanation of the matplotlib and seaborn interface (or have some good doku linked). Since all the doku I read wasn't of any great help.
On the other hand I have a function that returns some plots, which I want to save as an .pdf file with one plot per page. I found this similar question but can't copy the code over in a way to make my MWE work.
from matplotlib.backends.backend_pdf import PdfPages
import seaborn as sns
def generate_plots():
penguins = sns.load_dataset("penguins")
countplot_sex = sns.countplot(y='sex', data=penguins)
countplot_species = sns.countplot(y='species', data=penguins)
countplot_island = sns.countplot(y='island', data=penguins)
# As showes
# print(countplot_sex) -> AxesSubplot(0.125,0.11;0.775x0.77)
# print(countplot_species) -> AxesSubplot(0.125,0.11;0.775x0.77)
# print(countplot_island) -> AxesSubplot(0.125,0.11;0.775x0.77)
# All three variables contain the same object
return(countplot_sex, countplot_species, countplot_island)
def plots2pdf(plots, fname): # from: https://stackoverflow.com/a/21489936
pp = PdfPages('multipage.pdf')
for plot in plots:
pass
# TODO save plot
# Does not work: plot.savefig(pp, format='pdf')
pp.savefig()
pp.close()
def main():
plots2pdf(generate_plots(), 'multipage.pdf')
if __name__ == '__main__':
main()
My Idea is to have a somewhat decent software architecture with one function generating plots and another function saving them.
The problem is that by default, sns.countplot will do its plotting on the current matplotlib Axes instance. From the docs:
ax matplotlib Axes, optional
Axes object to draw the plot onto, otherwise uses the current Axes.
One solution would be to define a small function that creates a new figure and Axes instance, then passes that to sns.countplot, to ensure it is plotted on a new figure and does not overwrite the previous one. This is what I have shown in the example below. An alternative would be to just create 3 figures and axes, and then pass each one to the sns.countplot function yourself.
Then in your plots2pdf function, you can iterate over the Axes, and pass their figure instances to the PdfPages instance when you save. (Note: Since you create the figures in the generate_plots function, an alternative would be to return the figure instances from that function, then you have them ready to pass into the pp.savefig function, but I did it this way so the output from your function remained the same).
from matplotlib.backends.backend_pdf import PdfPages
import seaborn as sns
import matplotlib.pyplot as plt
def generate_plots():
penguins = sns.load_dataset("penguins")
def my_countplot(y, data):
fig, ax = plt.subplots()
sns.countplot(y=y, data=data)
return ax
countplot_sex = my_countplot(y='sex', data=penguins)
countplot_species = my_countplot(y='species', data=penguins)
countplot_island = my_countplot(y='island', data=penguins)
return(countplot_sex, countplot_species, countplot_island)
def plots2pdf(plots, fname):
with PdfPages(fname) as pp:
for plot in plots:
pp.savefig(plot.figure)
def main():
plots2pdf(generate_plots(), 'multipage.pdf')
if __name__ == '__main__':
main()
A screenshot of the multipage pdf produced:

Matplotlib: Plotting multiple histograms in plt.subplots

Background of the problem:
I'm working on a class that takes an Axes object as constructor parameter and produces a (m,n) dimension figure with a histogram in each cell, kind of like the figure below:
There are two important things to note here, that I'm not allowed to modified in any way:
The Figure object is not passed as a constructor parameter; only the Axes object is. So the subplots object cannot be modified in any way.
The Axes parameter is set to that of a (1,1) figure, by default (as below). All the modification required to make it an (m,n) figure are performed within the class (inside its methods)
_, ax = plt.subplots() # By default takes (1,1) dimension
cm = ClassName(model, ax=ax, histogram=True) # calling my class
What I'm stuck on:
Since I want to plot histograms within each cell, I decided to approach it by looping over each cell and creating a histogram for each.
results[col].hist(ax=self.ax[y,x], bins=bins)
However, I'm not able to specify the axes of the histogram in any way. This is because the Axes parameter passed is of default dimension (1,1) and hence not index-able. When I try this I get a TypeError saying.
TypeError: 'AxesSubplot' object is not subscriptable
With all this considered, I would like to know of any possible ways I can add my histogram to the parent Axes object. Thanks for taking a look.
The requirement is pretty strict and maybe not the best design choice. Because you later want to plot several subplots at the position of a single subplot, this single subplot is only created for the sole purpose of dying and being replaced a few moments later.
So what you can do is obtain the position of the axes you pass in and create a new gridspec at that position. Then remove the original axes and create a new set of axes at within that newly created gridspec.
The following would be an example. Note that it currently requires that the axes to be passed in is a Subplot (as opposed to any axes).
It also hardcodes the number of plots to be 2*2. In the real use case you would probably derive that number from the model you pass in.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import gridspec
class ClassName():
def __init__(self, model, ax=None, **kwargs):
ax = ax or plt.gca()
if not hasattr(ax, "get_gridspec"):
raise ValueError("Axes needs to be a subplot")
parentgs = ax.get_gridspec()
q = ax.get_geometry()[-1]
# Geometry of subplots
m, n = 2, 2
gs = gridspec.GridSpecFromSubplotSpec(m,n, subplot_spec=parentgs[q-1])
fig = ax.figure
ax.remove()
self.axes = np.empty((m,n), dtype=object)
for i in range(m):
for j in range(n):
self.axes[i,j] = fig.add_subplot(gs[i,j], label=f"{i}{j}")
def plot(self, data):
for ax,d in zip(self.axes.flat, data):
ax.plot(d)
_, (ax,ax2) = plt.subplots(ncols=2)
cm = ClassName("mymodel", ax=ax2) # calling my class
cm.plot(np.random.rand(4,10))
plt.show()

How to subplot multiple graphs when calling a function that plots the graph?

I have a function that plots a graph. I can call this graph with different variables to alter the graph. I'd like to call this function multiple times and plot the graphs along side each other but not sure how to do so
def plt_graph(x, graph_title, horiz_label):
df[x].plot(kind='barh')
plt.title(graph_title)
plt.ylabel("")
plt.xlabel(horiz_label)
plt_graph('gross','Total value','Gross (in millions)')
In case you know the number of plots you want to produce beforehands, you can first create as many subplots as you need
fig, axes = plt.subplots(nrows=1, ncols=5)
(in this case 5) and then provide the axes to the function
def plt_graph(x, graph_title, horiz_label, ax):
df[x].plot(kind='barh', ax=ax)
Finally, call every plot like this
plt_graph("framekey", "Some title", "some label", axes[4])
(where 4 stands for the fifth and last plot)
I have created a tool to do this really easily. I use it all the time in jupyter notebooks and find it so much neater than a big column of charts. Copy the Gridplot class from this file:
https://github.com/simonm3/analysis/blob/master/analysis/plot.py
Usage:
gridplot = Gridplot()
plt.plot(x)
plt.plot(y)
It shows each new plot in a grid with 4 plots to a row. You can change the size of the charts or the number per row. It works for plt.plot, plt.bar, plt.hist and plt.scatter. However it does require you use matplot directly rather than pandas.
If you want to turn it off:
gridplot.off()
If you want to reset the grid to position 1:
gridplot.on()
Here is a way that you can do it. First you create the figure which will contain the axes object. With those axes you have something like a canvas, which is where every graph will be drawn.
fig, ax = plt.subplots(1,2)
Here I have created one figure with two axes. This is a one row and two columns figure. If you inspect the ax variable you will see two objects. This is what we'll use for all the plotting. Now, going back to the function, let's start with a simple dataset and define the function.
df = pd.DataFrame({"a": np.random.random(10), "b": np.random.random(10)})
def plt_graph(x, graph_title, horiz_label, ax):
df[x].plot(kind = 'barh', ax = ax)
ax.set_xlabel(horiz_label)
ax.set_title(graph_title)
Then, to call the function you will simply do this:
plt_graph("a", "a", "a", ax=ax[0])
plt_graph("b", "b", "b", ax=ax[1])
Note that you pass each graph that you want to create to any of the axes you have. In this case, as you have two, you pass the first to the first axes and so on. Note that if you include seaborn in your import (import seaborn as sns), automatically the seaborn style will be applied to your graphs.
This is how your graphs will look like.
When you are creating plotting functions, you want to look at matplotlib's object oriented interface.

Categories