Related
I would like to have three plots in a single figure. The figure should have a subplot layout of two by two, where the first plot should occupy the first two subplot cells (i.e. the whole first row of plot cells) and the other plots should be positioned underneath the first one in cells 3 and 4.
I know that MATLAB allows this by using the subplot command like so:
subplot(2,2,[1,2]) % the plot will span subplots 1 and 2
Is it also possible in pyplot to have a single axes occupy more than one subplot?
The docstring of pyplot.subplot doesn't talk about it.
Anyone got an easy solution?
You can simply do:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 7, 0.01)
plt.subplot(2, 1, 1)
plt.plot(x, np.sin(x))
plt.subplot(2, 2, 3)
plt.plot(x, np.cos(x))
plt.subplot(2, 2, 4)
plt.plot(x, np.sin(x)*np.cos(x))
i.e., the first plot is really a plot in the upper half (the figure is only divided into 2x1 = 2 cells), and the following two smaller plots are done in a 2x2=4 cell grid.
The third argument to subplot() is the position of the plot inside the grid (in the direction of reading in English, with cell 1 being in the top-left corner):
for example in the second subplot (subplot(2, 2, 3)), the axes will go to the third section of the 2x2 matrix i.e, to the bottom-left corner.
The Using Gridspec to make multi-column/row subplot layouts shows a way to do this with GridSpec. A simplified version of the example with 3 subplots would look like
import matplotlib.pyplot as plt
fig = plt.figure()
gs = fig.add_gridspec(2,2)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[1, :])
plt.show()
To have multiple subplots with an axis occupy, you can simply do:
from matplotlib import pyplot as plt
import numpy as np
b=np.linspace(-np.pi, np.pi, 100)
a1=np.sin(b)
a2=np.cos(b)
a3=a1*a2
plt.subplot(221)
plt.plot(b, a1)
plt.title('sin(x)')
plt.subplot(222)
plt.plot(b, a2)
plt.title('cos(x)')
plt.subplot(212)
plt.plot(b, a3)
plt.title('sin(x)*cos(x)')
plt.show()
Another way is
plt.subplot(222)
plt.plot(b, a1)
plt.title('sin(x)')
plt.subplot(224)
plt.plot(b, a2)
plt.title('cos(x)')
plt.subplot(121)
plt.plot(b, a3)
plt.title('sin(x)*cos(x)')
plt.show()
For finer-grained control you might want to use the subplot2grid module of matplotlib.pyplot.
http://matplotlib.org/users/gridspec.html
A more modern answer would be: Simplest is probably to use subplots_mosaic:
https://matplotlib.org/stable/tutorials/provisional/mosaic.html
import matplotlib.pyplot as plt
import numpy as np
# Some example data to display
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
fig, axd = plt.subplot_mosaic([['left', 'right'],['bottom', 'bottom']],
constrained_layout=True)
axd['left'].plot(x, y, 'C0')
axd['right'].plot(x, y, 'C1')
axd['bottom'].plot(x, y, 'C2')
plt.show()
There are three main options in matplotlib to make separate plots within a figure:
subplot: access the axes array and add subplots
gridspec: control the geometric properties of the underlying figure (demo)
subplots: wraps the first two in a convenient api (demo)
The posts so far have addressed the first two options, but they have not mentioned the third, which is the more modern approach and is based on the first two options. See the specific docs Combining two subplots using subplots and GridSpec.
Update
A much nicer improvement may be the provisional subplot_mosaic method mentioned in #Jody Klymak's post. It uses a structural, visual approach to mapping out subplots instead of confusing array indices. However it is still based on the latter options mentioned above.
I can think of 2 more flexible solutions.
The most flexible way: using subplot_mosaic.
f, axes = plt.subplot_mosaic('AAB;CDD;EEE')
# axes = {'A': ..., 'B': ..., ...}
Effect:
Using gridspec_kw of subplots. Although it is also inconvenient when different rows need different width ratios.
f, (a0, a1) = plt.subplots(1, 2, gridspec_kw={'width_ratios': [2, 1]})
Effect:
The subplot method of other answers is kind of rigid, IMO. For example, you cannot create two rows with width ratios being 1:2 and 2:1 easily. However, it can help when you need to overwrite some layout of subplots, for example.
I'm trying to share two subplots axes, but I need to share the x axis after the figure was created. E.g. I create this figure:
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(1000)/100.
x = np.sin(2*np.pi*10*t)
y = np.cos(2*np.pi*10*t)
fig = plt.figure()
ax1 = plt.subplot(211)
plt.plot(t,x)
ax2 = plt.subplot(212)
plt.plot(t,y)
# some code to share both x axes
plt.show()
Instead of the comment I want to insert some code to share both x axes.
How do I do this? There are some relevant sounding attributes
_shared_x_axes and _shared_x_axes when I check to figure axis (fig.get_axes()) but I don't know how to link them.
The usual way to share axes is to create the shared properties at creation. Either
fig=plt.figure()
ax1 = plt.subplot(211)
ax2 = plt.subplot(212, sharex = ax1)
or
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
Sharing the axes after they have been created should therefore not be necessary.
However if for any reason, you need to share axes after they have been created (actually, using a different library which creates some subplots, like here might be a reason), there would still be a solution:
Using
ax1.get_shared_x_axes().join(ax1, ax2)
creates a link between the two axes, ax1 and ax2. In contrast to the sharing at creation time, you will have to set the xticklabels off manually for one of the axes (in case that is wanted).
A complete example:
import numpy as np
import matplotlib.pyplot as plt
t= np.arange(1000)/100.
x = np.sin(2*np.pi*10*t)
y = np.cos(2*np.pi*10*t)
fig=plt.figure()
ax1 = plt.subplot(211)
ax2 = plt.subplot(212)
ax1.plot(t,x)
ax2.plot(t,y)
ax1.get_shared_x_axes().join(ax1, ax2)
ax1.set_xticklabels([])
# ax2.autoscale() ## call autoscale if needed
plt.show()
The other answer has code for dealing with a list of axes:
axes[0].get_shared_x_axes().join(axes[0], *axes[1:])
As of Matplotlib v3.3 there now exist Axes.sharex, Axes.sharey methods:
ax1.sharex(ax2)
ax1.sharey(ax3)
Just to add to ImportanceOfBeingErnest's answer above:
If you have an entire list of axes objects, you can pass them all at once and have their axes shared by unpacking the list like so:
ax_list = [ax1, ax2, ... axn] #< your axes objects
ax_list[0].get_shared_x_axes().join(ax_list[0], *ax_list)
The above will link all of them together. Of course, you can get creative and sub-set your list to link only some of them.
Note:
In order to have all axes linked together, you do have to include the first element of the axes_list in the call, despite the fact that you are invoking .get_shared_x_axes() on the first element to start with!
So doing this, which would certainly appear logical:
ax_list[0].get_shared_x_axes().join(ax_list[0], *ax_list[1:])
... will result in linking all axes objects together except the first one, which will remain entirely independent from the others.
I'm creating a plot using python 3.5.1 and matplotlib 1.5.1 that has two subplots (side by side) with a shared Y axis. A sample output image is shown below:
Notice the extra white space at the top and bottom of each set of axes. Try as I might I can't seem to get rid of it. The overall goal of the figure is to have a waterfall type plot on the left with a shared Y axes with the plot on the right.
Here's some sample code to reproduce the image above.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline
# create some X values
periods = np.linspace(1/1440, 1, 1000)
# create some Y values (will be datetimes, not necessarily evenly spaced
# like they are in this example)
day_ints = np.linspace(1, 100, 100)
days = pd.to_timedelta(day_ints, 'D') + pd.to_datetime('2016-01-01')
# create some fake data for the number of points
points = np.random.random(len(day_ints))
# create some fake data for the color mesh
Sxx = np.random.random((len(days), len(periods)))
# Create the plots
fig = plt.figure(figsize=(8, 6))
# create first plot
ax1 = plt.subplot2grid((1,5), (0,0), colspan=4)
im = ax1.pcolormesh(periods, days, Sxx, cmap='viridis', vmin=0, vmax=1)
ax1.invert_yaxis()
ax1.autoscale(enable=True, axis='Y', tight=True)
# create second plot and use the same y axis as the first one
ax2 = plt.subplot2grid((1,5), (0,4), sharey=ax1)
ax2.scatter(points, days)
ax2.autoscale(enable=True, axis='Y', tight=True)
# Hide the Y axis scale on the second plot
plt.setp(ax2.get_yticklabels(), visible=False)
#ax1.set_adjustable('box-forced')
#ax2.set_adjustable('box-forced')
fig.colorbar(im, ax=ax1)
As you can see in the commented out code I've tried a number of approaches, as suggested by posts like https://github.com/matplotlib/matplotlib/issues/1789/ and Matplotlib: set axis tight only to x or y axis.
As soon as I remove the sharey=ax1 part of the second subplot2grid call the problem goes away, but then I also don't have a common Y axis.
Autoscale tends to add a buffer to the data so that all of the data points are easily visible and not part-way cut off by the axes.
Change:
ax1.autoscale(enable=True, axis='Y', tight=True)
to:
ax1.set_ylim(days.min(),days.max())
and
ax2.autoscale(enable=True, axis='Y', tight=True)
to:
ax2.set_ylim(days.min(),days.max())
To get:
I saw this example on how to create a parallel coordinate plot: Parallel Coordinates:
This creates a nice Parallel Coordinates figure, but I would like to add this plot to an already existing figure in a subplot (there should be another plot next to it in the same plot).
For the already existing figure, the figure and axes are defined as:
fig = plt.figure(figsize=plt.figaspect(2.))
ax = fig.add_subplot(1,2,1)
For the Parallel Coordinates, they suggest:
fig, axes = plt.subplots(1, dims-1, sharey=False)
How can I reconcile both initializations of the figure and the ax(es)?
One option is to create all the axes using subplots then just shift the location of the one that you don't want to have wspace=0 as is done for the Parallel Coordinate plots:
import matplotlib.pylab as plt
dims = 4
fig, axes = plt.subplots(1, dims-1 + 1, sharey=False)
plt.subplots_adjust(wspace=0)
ax1 = axes[0]
pos = ax1.get_position()
ax1.set_position(pos.translated(tx = -0.1,ty=0))
I have added 1 to the number of columns creates (leaving it explicitly -1+1) and set wspace=0 which draws all the plots adjacent to one another with no space inbetween. Take the left most axes and get the position which is a Bbox. This is nice as it gives you the ability to translate it by tx=-0.1 separating your existing figure.
How can the plot lines from .plot be reused in subsequent plots?
I'd like to make plots on 4 axes, first three individual plot on each axes, and the last all 3 plots on last axes.
Here is the code:
from numpy import *
from matplotlib.pyplot import *
fig=figure()
data=arange(0,10,0.01)
ax1=fig.add_subplot(2,2,1)
ax2=fig.add_subplot(2,2,2)
ax3=fig.add_subplot(2,2,3)
ax4=fig.add_subplot(2,2,4)
line1=ax1.plot(data,data)
line2=ax2.plot(data, data**2/10, ls='--', color='green')
line3=ax3.plot(data, np.sin(data), color='red')
#could I somehow use previous plots, instead recreating them all?
line4=ax4.plot(data,data)
line4=ax4.plot(data, data**2/10, ls='--', color='green')
line4=ax4.plot(data, np.sin(data), color='red')
show()
The resulting picture is:
Is there a way to define plots first and then add them to axes, and then plot them? Here is the logic I had in mind:
#this is just an example, implementation can be different
line1=plot(data, data)
line2=plot(data, data**2/10, ls='--', color='green')
line3=plot(data, np.sin(data), color='red')
line4=[line1, line2, line3]
Now plot line1 on ax1, line2 on ax2, line3 on ax3 and line4 on ax4.
The requested implementation in the OP doesn't work because the Line2D plot Artist returned by plt.plot can't be reused. Trying to do so, will result in a RuntimeError as per def set_figure(self, fig):
line1 in the OP, is not the same as line1 created directly with the Line2D method, because a plotted Artist has different properties.
In regards to seaborn, and API for matplotlib, axes-level plots like seaborn.lineplot return an axes:
p = sns.lineplot(...) then p.get_children() to get the Artist objects.
Plot artists can be created directly, with methods like matplotlib.lines.Line2D, and reused in multiple plots.
Updated code using standard importing practices, subplots, and not using a list-comprehension for a side-effect (a python anti-pattern).
Tested in python 3.8.11, matplotlib 3.4.3
import numpy as np
from copy import copy
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
# crate the figure and subplots
fig, axes = plt.subplots(2, 2)
# flatten axes into 1-D for easy indexing and iteration
axes = axes.ravel()
# test data
data=np.arange(0, 10, 0.01)
# create test lines
line1 = Line2D(data, data)
line2 = Line2D(data, data**2/10, ls='--', color='green')
line3 = Line2D(data, np.sin(data), color='red')
lines = [line1, line2, line3]
# add the copies of the lines to the first 3 subplots
for ax, line in zip(axes[0:-1], lines):
ax.add_line(copy(line))
# add 3 lines to the 4th subplot
for line in lines:
axes[3].add_line(line)
# autoscale all the subplots if needed
for _a in axes:
_a.autoscale()
plt.show()
Original Answer
Here is one possible solution. I'm not sure that it's very pretty, but at least it does not require code duplication.
import numpy as np, copy
import matplotlib.pyplot as plt, matplotlib.lines as ml
fig=plt.figure(1)
data=np.arange(0,10,0.01)
ax1=fig.add_subplot(2,2,1)
ax2=fig.add_subplot(2,2,2)
ax3=fig.add_subplot(2,2,3)
ax4=fig.add_subplot(2,2,4)
#create the lines
line1=ml.Line2D(data,data)
line2=ml.Line2D(data,data**2/10,ls='--',color='green')
line3=ml.Line2D(data,np.sin(data),color='red')
#add the copies of the lines to the first 3 panels
ax1.add_line(copy.copy(line1))
ax2.add_line(copy.copy(line2))
ax3.add_line(copy.copy(line3))
[ax4.add_line(_l) for _l in [line1,line2,line3]] # add 3 lines to the 4th panel
[_a.autoscale() for _a in [ax1,ax2,ax3,ax4]] # autoscale if needed
plt.draw()
I think your usage is fine, but you can pass all of the x,y data pairs to plot like this (although it makes it very horrible to read!):
ax4.plot(data, data, data, data**2 / 10, data, np.sin(data))
An amusing different way to do it is like this:
graph_data = [(data, data), (data, data**2 / 10), (data, np.sin(data))]
[ax4.plot(i,j) for i,j in graph_data]
I had a simpler use case in jupyter notebooks. Given that you have stored a figure object somewhere, how can you replot it.
eg:
Cell 1:
f = plt.figure(figsize=(18, 6))
f.suptitle("Hierarchical Clustring", fontsize=20)
dendrogram(Z, color_threshold=cut_off,
truncate_mode='lastp',
p=20)
Cell 2:
#plot f again, the answer is really simple
f
plt.show()
That's it. The benefit of that is you can store figures in objects and later use them when necessary.
Also this question has a good example of referencing to previous axes using:
fix, ax = plt.subplots(2, 2)
ax[0,1].plot(data, data**2 / 10, ls='--', color='g')
but also explains how to insert a title on each subplot using:
ax[0,1].set_title('Simple plot')
the dimension of ax depends on subplot parameters: if they are just tiled orizontally or vertically, ax will only need one index.