I've got a series of functions that return three plot objects (figure, axis and plot) and I would like to combine them into a single figure as subplots. I've put together example code:
import matplotlib.pyplot as plt
import numpy as np
def main():
line_fig,line_axes,line_plot=line_grapher()
cont_fig,cont_axes,cont_plot=cont_grapher()
compound_fig=plot_compounder(line_fig,cont_fig)#which arguments?
plt.show()
def line_grapher():
x=np.linspace(0,2*np.pi)
y=np.sin(x)/(x+1)
line_fig=plt.figure()
line_axes=line_fig.add_axes([0.1,0.1,0.8,0.8])
line_plot=line_axes.plot(x,y)
return line_fig,line_axes,line_plot
def cont_grapher():
z=np.random.rand(10,10)
cont_fig=plt.figure()
cont_axes=cont_fig.add_axes([0.1,0.1,0.8,0.8])
cont_plot=cont_axes.contourf(z)
return cont_fig,cont_axes,cont_plot
def plot_compounder(fig1,fig2):
#... lines that will compound the two figures that
#... were passed to the function and return a single
#... figure
fig3=None#provisional, so that the code runs
return fig3
if __name__=='__main__':
main()
It would be really useful to combine a set of graphs into one with a function. Has anybody done this before?
If you're going to be plotting your graphs on the same figure anyway, there's no need to create a figure for each plot. Changing your plotting functions to just return the axes, you can instantiate a figure with two subplots and add an axes to each subplot:
def line_grapher(ax):
x=np.linspace(0,2*np.pi)
y=np.sin(x)/(x+1)
ax.plot(x,y)
def cont_grapher(ax):
z=np.random.rand(10,10)
cont_plot = ax.contourf(z)
def main():
fig3, axarr = plt.subplots(2)
line_grapher(axarr[0])
cont_grapher(axarr[1])
plt.show()
if __name__=='__main__':
main()
Look into the plt.subplots function and the add_subplot figure method for plotting multiple plots on one figure.
Related
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)
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:
In the MWE below, the y-label and ticks disappear. It seems to happen only with a few specific combinations of twiny and pandas for the other plot. The MWE is easily solved by changing the order in which the two subplots are created, but in my full script this is not as straightforward.
Any ideas on why this is happening and how it can be solved?
df = pd.DataFrame(np.random.randn(100))
def plot_twin(ax):
ax.plot([0,1])
ax.set_ylabel('test')
# Add return time axis to plot
other_ax = ax.twiny()
return
def plot_df(df,ax):
df.plot(kind='box',ax=ax)
return
fig,(ax1,ax2) = plt.subplots(1,2)
plot_twin(ax2)
plot_df(df,ax1)
I need to draw subplots of a figure through loop iterations; each iteration calls a function defined in another module (=another py file), which draws a pair of subplots. Here is what I tried -- and alas does not work:
1) Before the loop, create a figure with the adequate number of rows, and 2 columns:
import matplotlib.pyplot as plt
fig, axarr = plt.subplots(nber_rows,2)
2) Inside the loop, at iteration number iter_nber, call on the function drawing each subplot:
fig, axarr = module.graph_function(fig,axarr,iter_nber,some_parameters, some_data)
3) The function in question is basically like this; each iteration creates a pair of subplots on the same row:
def graph_function(fig,axarr,iter_nber,some_parameters, some_data):
axarr[iter_nber,1].plot(--some plotting 1--)
axarr[iter_nber,2].plot(--some plotting 2--)
return fig,axarr
This does not work. I end up with an empty figure at the end of the loop.
I have tried various combinations of the above, like leaving only axarr in the function's return argument, to no avail. Obviously I do not understand the logic of this figure and its subplots.
Any suggestions much appreciated.
The code you've posted seems largely correct. Other than the indexing, as #hitzg mentioned, nothing you're doing looks terribly out of the ordinary.
However, it doesn't make much sense to return the figure and axes array from your plotting function. (If you need access to the figure object, you can always get it through ax.figure.) It won't change anything to pass them in and return them, though.
Here's a quick example of the type of thing it sounds like you're trying to do. Maybe it helps clear some confusion?
import numpy as np
import matplotlib.pyplot as plt
def main():
nrows = 3
fig, axes = plt.subplots(nrows, 2)
for row in axes:
x = np.random.normal(0, 1, 100).cumsum()
y = np.random.normal(0, 0.5, 100).cumsum()
plot(row, x, y)
plt.show()
def plot(axrow, x, y):
axrow[0].plot(x, color='red')
axrow[1].plot(y, color='green')
main()
I have some graphs created with NetworkX and show them on screen using Matplotlib. Specifically, since I don't know in advance how many graphs I need to show, I create a subplot on the figure on fly. That works fine. However, at some point in the script, some subplots are removed from the figure and the figure is shown with some empty subplots. I would like to avoid it, but I was not able to retrieve the subplots that are empty in the figure. Here is my code:
#instantiate a figure with size 12x12
fig = plt.figure(figsize=(12,12))
#when a graph is created, also a subplot is created:
ax = plt.subplot(3,4,count+1)
#and the graph is drawn inside it: N.B.: pe is the graph to be shown
nx.draw(pe, positions, labels=positions, font_size=8, font_weight='bold', node_color='yellow', alpha=0.5)
#many of them are created..
#under some conditions a subplot needs to be deleted, and so..
#condition here....and then retrieve the subplot to deleted. The graph contains the id of the ax in which it is shown.
for ax in fig.axes:
if id(ax) == G.node[shape]['idax']:
fig.delaxes(ax)
until here works fine, but when I show the figure, the result looks like this:
you can notice that there are two empty subplots there.. at the second position and at the fifth. How can I avoid it? Or.. how can I re-organize the subplots in such a way that there are no more blanks in the figure?
Any help is apreciated! Thanks in advance.
So to do this I would keep a list of axes and when I delete the contents of one I would swap it out with a full one. I think the example below solved the problem (or at least gives an idea of how to solve it):
import matplotlib.pyplot as plt
# this is just a helper class to keep things clean
class MyAxis(object):
def __init__(self,ax,fig):
# this flag tells me if there is a plot in these axes
self.empty = False
self.ax = ax
self.fig = fig
self.pos = self.ax.get_position()
def del_ax(self):
# delete the axes
self.empty = True
self.fig.delaxes(self.ax)
def swap(self,other):
# swap the positions of two axes
#
# THIS IS THE IMPORTANT BIT!
#
new_pos = other.ax.get_position()
self.ax.set_position(new_pos)
other.ax.set_position(self.pos)
self.pos = new_pos
def main():
# generate a figure and 10 subplots in a grid
fig, axes = plt.subplots(ncols=5,nrows=2)
# get these as a list of MyAxis objects
my_axes = [MyAxis(ax,fig) for ax in axes.ravel()]
for ax in my_axes:
# plot some random stuff
ax.ax.plot(range(10))
# delete a couple of axes
my_axes[0].del_ax()
my_axes[6].del_ax()
# count how many axes are dead
dead = sum([ax.empty for ax in my_axes])
# swap the dead plots for full plots in a row wise fashion
for kk in range(dead):
for ii,ax1 in enumerate(my_axes[kk:]):
if ax1.empty:
print ii,"dead"
for jj,ax2 in enumerate(my_axes[::-1][kk:]):
if not ax2.empty:
print "replace with",jj
ax1.swap(ax2)
break
break
plt.draw()
plt.show()
if __name__ == "__main__":
main()
The extremely ugly for loop construct is really just a placeholder to give an example of how the axes can be swapped.