Add figures in new subplot in Python [duplicate] - python

Looking at the matplotlib documentation, it seems the standard way to add an AxesSubplot to a Figure is to use Figure.add_subplot:
from matplotlib import pyplot
fig = pyplot.figure()
ax = fig.add_subplot(1,1,1)
ax.hist( some params .... )
I would like to be able to create AxesSubPlot-like objects independently of the figure, so I can use them in different figures. Something like
fig = pyplot.figure()
histoA = some_axes_subplot_maker.hist( some params ..... )
histoA = some_axes_subplot_maker.hist( some other params ..... )
# make one figure with both plots
fig.add_subaxes(histo1, 211)
fig.add_subaxes(histo1, 212)
fig2 = pyplot.figure()
# make a figure with the first plot only
fig2.add_subaxes(histo1, 111)
Is this possible in matplotlib and if so, how can I do this?
Update: I have not managed to decouple creation of Axes and Figures, but following examples in the answers below, can easily re-use previously created axes in new or olf Figure instances. This can be illustrated with a simple function:
def plot_axes(ax, fig=None, geometry=(1,1,1)):
if fig is None:
fig = plt.figure()
if ax.get_geometry() != geometry :
ax.change_geometry(*geometry)
ax = fig.axes.append(ax)
return fig

Typically, you just pass the axes instance to a function.
For example:
import matplotlib.pyplot as plt
import numpy as np
def main():
x = np.linspace(0, 6 * np.pi, 100)
fig1, (ax1, ax2) = plt.subplots(nrows=2)
plot(x, np.sin(x), ax1)
plot(x, np.random.random(100), ax2)
fig2 = plt.figure()
plot(x, np.cos(x))
plt.show()
def plot(x, y, ax=None):
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y, 'go')
ax.set_ylabel('Yabba dabba do!')
return line
if __name__ == '__main__':
main()
To respond to your question, you could always do something like this:
def subplot(data, fig=None, index=111):
if fig is None:
fig = plt.figure()
ax = fig.add_subplot(index)
ax.plot(data)
Also, you can simply add an axes instance to another figure:
import matplotlib.pyplot as plt
fig1, ax = plt.subplots()
ax.plot(range(10))
fig2 = plt.figure()
fig2.axes.append(ax)
plt.show()
Resizing it to match other subplot "shapes" is also possible, but it's going to quickly become more trouble than it's worth. The approach of just passing around a figure or axes instance (or list of instances) is much simpler for complex cases, in my experience...

The following shows how to "move" an axes from one figure to another. This is the intended functionality of #JoeKington's last example, which in newer matplotlib versions is not working anymore, because axes cannot live in several figures at once.
You would first need to remove the axes from the first figure, then append it to the next figure and give it some position to live in.
import matplotlib.pyplot as plt
fig1, ax = plt.subplots()
ax.plot(range(10))
ax.remove()
fig2 = plt.figure()
ax.figure=fig2
fig2.axes.append(ax)
fig2.add_axes(ax)
dummy = fig2.add_subplot(111)
ax.set_position(dummy.get_position())
dummy.remove()
plt.close(fig1)
plt.show()

For line plots, you can deal with the Line2D objects themselves:
fig1 = pylab.figure()
ax1 = fig1.add_subplot(111)
lines = ax1.plot(scipy.randn(10))
fig2 = pylab.figure()
ax2 = fig2.add_subplot(111)
ax2.add_line(lines[0])

TL;DR based partly on Joe nice answer.
Opt.1: fig.add_subplot()
def fcn_return_plot():
return plt.plot(np.random.random((10,)))
n = 4
fig = plt.figure(figsize=(n*3,2))
#fig, ax = plt.subplots(1, n, sharey=True, figsize=(n*3,2)) # also works
for index in list(range(n)):
fig.add_subplot(1, n, index + 1)
fcn_return_plot()
plt.title(f"plot: {index}", fontsize=20)
Opt.2: pass ax[index] to a function that returns ax[index].plot()
def fcn_return_plot_input_ax(ax=None):
if ax is None:
ax = plt.gca()
return ax.plot(np.random.random((10,)))
n = 4
fig, ax = plt.subplots(1, n, sharey=True, figsize=(n*3,2))
for index in list(range(n)):
fcn_return_plot_input_ax(ax[index])
ax[index].set_title(f"plot: {index}", fontsize=20)
Outputs respect.
Note: Opt.1 plt.title() changed in opt.2 to ax[index].set_title(). Find more Matplotlib Gotchas in Van der Plas book.

To go deeper in the rabbit hole. Extending my previous answer, one could return a whole ax, and not ax.plot() only. E.g.
If dataframe had 100 tests of 20 types (here id):
dfA = pd.DataFrame(np.random.random((100,3)), columns = ['y1', 'y2', 'y3'])
dfB = pd.DataFrame(np.repeat(list(range(20)),5), columns = ['id'])
dfC = dfA.join(dfB)
And the plot function (this is the key of this whole answer):
def plot_feature_each_id(df, feature, id_range=[], ax=None, legend_bool=False):
feature = df[feature]
if not len(id_range): id_range=set(df['id'])
legend_arr = []
for k in id_range:
pass
mask = (df['id'] == k)
ax.plot(feature[mask])
legend_arr.append(f"id: {k}")
if legend_bool: ax.legend(legend_arr)
return ax
We can achieve:
feature_arr = dfC.drop('id',1).columns
id_range= np.random.randint(len(set(dfC.id)), size=(10,))
n = len(feature_arr)
fig, ax = plt.subplots(1, n, figsize=(n*6,4));
for i,k in enumerate(feature_arr):
plot_feature_each_id(dfC, k, np.sort(id_range), ax[i], legend_bool=(i+1==n))
ax[i].set_title(k, fontsize=20)
ax[i].set_xlabel("test nr. (id)", fontsize=20)

Related

How to plot using a prewritten library function into a subplot?

I've been stumbling around this issue for a while, but today I really want to figure out if it can be done.
Say you have a function from a library that does some plotting, such as:
def plotting_function():
fig, ax = plt.subplots()
ax.plot([1,2,3], [2,4,10])
return fig
If I want to add this single plot multiple times to my own subplots, how could I do this?
I'm not able to change the plotting_function, as it's from a library, so what I've tried is:
fig, axs = plt.subplots(1,3)
for i in range(3):
plt.sca(axs[i])
plotting_function()
plt.show()
This results in an empty subplot with the line graphs plotting separate.
Is there any simple answer to this problem? Thanks in advance.
I think you might be better off to monkey patch plt.subplots(), but it is possible to move a subplot from one figure to another. Here's an example, based on this post:
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
def plotting_function1():
fig, ax = plt.subplots()
ax.plot([1,2,3], [2,4,10])
return fig
def plotting_function2():
fig, ax = plt.subplots()
ax.plot([10,20,30], [20,40,100])
return fig
def main():
f1 = plotting_function1()
ax1 = plt.gca()
ax1.remove()
f2 = plotting_function2()
ax2 = plt.gca()
ax1.figure = f2
f2.axes.append(ax1)
f2.add_axes(ax1)
# These positions are copied from a call to subplots().
ax1.set_position(Bbox([[0.125, 0.11], [0.477, 0.88]]))
ax2.set_position(Bbox([[0.55, 0.11], [0.9, 0.88]]))
plt.show()
main()

How to plot sublots when creating plots in a for loop in python?

I'm new to programming and currently stuck on this: I create 4 different plots using a for loop and I want to assign each plot to a different subplot. Basically, I want 4 plots in a 2x2 grid so I can compare my data. Anyone knows how I can achieve this? My approach was to create a list of subplots and then assign every plot to a subplot using a nested plot:
import matplotlib.pyplot as plt
import numpy as np
def load_file(filename):
return np.loadtxt(filename, delimiter=',', usecols=(0, 1), unpack=True, skiprows=1)
fig = plt.figure()
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222)
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224)
ax_list=[ax1, ax2, ax3, ax4]
for i,filename in enumerate(file_list):
for p in ax_list:
x,y = load_file(filename)
plt.plot(x,y,
label=l, #I assign labels from a list beforehand, as well as colors
color=c,
linewidth=0.5,
ls='-',
)
p.plot()
The problem is, all plots are assigned to only one subplot and I don't know how to correct this. I'd appreciate any help!
I guess what you want is to show different data on all 4 plots, hence use a single loop. Make sure to use the axes plotting method, not plt.plot as the latter would always plot in the last subplot.
import matplotlib.pyplot as plt
import numpy as np
def load_file(filename):
return np.loadtxt(filename, delimiter=',', usecols=(0, 1), unpack=True, skiprows=1)
fig, ax_list = plt.subplots(2,2)
for i,(filename,ax) in enumerate(zip(file_list, ax_list.flatten())):
x,y = load_file(filename)
ax.plot(x,y, linewidth=0.5,ls='-' )
plt.show()
You don't need to loop over filenames and plots, only need to select the next plot in the list.
for i, filename in enumerate(file_list):
p = ax_list[i]:
x,y = load_file(filename)
p.plot(x, y,
label=l, #I assign labels from a list beforehand, as well as colors
color=c,
linewidth=0.5,
ls='-')
plt.plot()
You can also replace
fig = plt.figure()
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222)
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224)
ax_list=[ax1, ax2, ax3, ax4]
with just
fig, ax_list = plt.subplots(2, 2)
ax_list = ax_list.flatten()
To get a simple 2x2 grid.

Move or copy patches between figures

How can I move (or copy) patches between figures in matplotlib?
I'm working with a set of pickled figures, and would like to combine them to one plot.
This is no problem when working with line plots, as I can access the data through ax.get_lines.
However, when working with histograms, ax.get_lines returns <a list of 0 Line2D objects>. As far as I can see, the only way to access the plotted data is through ax.patches.
If I try to set a patch from one figure to another with ax.add_patch, I get RuntimeError: Can not put single artist in more than one figure.
Edit
I'm using matplotlib2.0.0.
The following example illustrates the problem
import numpy as np
import matplotlib.pylab as plt
import copy
# Creating the two figures
x = np.random.rand(20)
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
nr = 0
for color, ax in zip(("red", "blue"), (ax1, ax2)):
x = np.random.rand(20) + nr
ax.hist(x, color=color)
nr += 0.5
# Copying from ax1 to ax2
for patch in ax1.patches:
patch_cpy = copy.copy(patch)
# del patch # Uncommenting seems this makes no difference
ax2.add_patch(patch_cpy)
# RuntimeError: Can not put single artist in more than one figure
I would like to copy the red patches to the figure with the blue patches.
Edit 2
Although #ImportanceOfBeingErnest's answer worked for the case above, it did not work in the real-life problem I had.
I ended up making a new axis, and manually created new patches like so:
import numpy as np
import matplotlib.pylab as plt
from matplotlib import patches
# Creating the two figures
x = np.random.rand(20)
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
nr = 0
for color, ax in zip(("red", "blue"), (ax1, ax2)):
x = np.random.rand(20) + nr
ax.hist(x, color=color)
nr += 0.5
# Create another axis
fig3, ax3 = plt.subplots()
# Copy the properties of the patches to the new axis
for p in ax1.patches:
ax3.add_patch(patches.Rectangle(p.get_xy(),\
p.get_width(),\
p.get_height(),\
color = "red"))
for p in ax2.patches:
ax3.add_patch(patches.Rectangle(p.get_xy(),\
p.get_width(),\
p.get_height(),\
color = "blue"))
ax3.autoscale()
plt.show()
Apparently, the old solution of just deleting the artist doesn't work any more in matplotlib 2.0.
The patch_cpy will still be connected to the same axis as the original. You can see this by print patch_cpy.axes == ax1 which prints True.
So the solution can be to just set the axes and figure attribute of patch_cpy to None. I have to admit that I'm not sure if this hasn't got any side effects, but, at least the example below works.
Additionally, the copied patch wil still have the data transform of the old axes incorporated. This needs to be updated using patch_cpy.set_transform(ax2.transData).
Finally, to make sure the plot limits cover both the old and newly copied artists, use ax2.autoscale().
import numpy as np
import matplotlib.pylab as plt
import copy
# Creating the two figures
x = np.random.rand(20)
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
nr = 0
for color, ax in zip(("red", "blue"), (ax1, ax2)):
x = np.random.rand(20) + nr
ax.hist(x, color=color)
nr += 0.5
# Copying from ax1 to ax2
for patch in ax1.patches:
patch_cpy = copy.copy(patch)
# cut the umbilical cord the hard way
patch_cpy.axes = None
patch_cpy.figure = None
patch_cpy.set_transform(ax2.transData)
ax2.add_patch(patch_cpy)
ax2.autoscale()
plt.show()
You can make a copy of each patch. Here is an example where all the pathces are copied from one axes to another:
import copy
x = np.random.rand(20)
fig, ax = plt.subplots()
for color in ("red", "blue"):
x = np.random.rand(20)
ax.hist(x, color=color)
fig2, ax2 = plt.subplots()
for patch in ax.patches:
patch_cpy = copy.copy(patch)
ax2.add_patch(patch_cpy)
If you want to remove the patches from the first axes you can use del to do that, for example deleting every other patch:
del ax.patches[::2]
Remember to redraw the figure afterward with:
fig.canvas.draw()

Can I tell python to put an existing figure in a new figure?

Creating a certain plot is a lot of work, so I would like to automate this by create a function f() that returns a figure.
I would like to call this function so that I can put the result in a subplot. Is there anyway I can do this? Below is some psuedo code explaining what I mean
figure_of_interest = f()
fig,ax = plt.subplots(nrows = 4,cols = 1)
ax[1].replace_with(figure_of_interest)
This was asked before here and here.
Short answer: It is not possible.
But you can always modify the axes instance or use a function to create/modify the current axes:
import matplotlib.pyplot as plt
import numpy as np
def test():
x = np.linspace(0, 2, 100)
# With subplots
fig1, (ax1, ax2) = plt.subplots(2)
plot(x, x, ax1)
plot(x, x*x, ax2)
# Another Figure without using axes
fig2 = plt.figure()
plot(x, np.exp(x))
plt.show()
def plot(x, y, ax=None):
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y)
return line
test()

Matplotlib: Plotting the same graph in two different figures without writting the "plot(x,y)" line twice

I have this simple code that plots exactly the same thing in two different figures (fig1 and fig2). However, I have to write the line ax?.plot(x, y) twice, once for ax1 and once for ax2. How can I have only one plot expression (having multiple redondant ones could be a source of troubles for my more complex code). Something like ax1,ax2.plot(x, y) ... ?
import numpy as np
import matplotlib.pyplot as plt
#Prepares the data
x = np.arange(5)
y = np.exp(x)
#plot fig1
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
#plot fig2
fig2 = plt.figure()
ax2 = fig2.add_subplot(111)
#adds the same fig2 plot on fig1
ax1.plot(x, y)
ax2.plot(x, y)
plt.show()
You can either add each axes to a list, like this:
import numpy as np
import matplotlib.pyplot as plt
axes_lst = []
#Prepares the data
x = np.arange(5)
y = np.exp(x)
#plot fig1
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
axes_lst.append(ax1)
#plot fig2
fig2 = plt.figure()
ax2 = fig2.add_subplot(111)
axes_lst.append(ax2)
for ax in axes_lst:
ax.plot(x, y)
plt.show()
or you can use this unsupported feature to pull all of the figures in pyplot. Taken from https://stackoverflow.com/a/3783303/1269969
figures=[manager.canvas.figure
for manager in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
for figure in figures:
figure.gca().plot(x,y)
Without knowing about matplotlib, you could add all your axes (?) to a list:
to_plot = []
to_plot.append(ax1)
...
to_plot.append(ax2)
...
# apply the same action to each ax
for ax in to_plot:
ax.plot(x, y)
You could then add as many as you like, and the same thing will happen to each.

Categories