How to make an axes occupy multiple subplots with pyplot - python

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.

Related

Make all data points of a matplotlib plot homogeneously transparent

I'd like to plot two scatter plots into the same Axes and turn the upper one's data points transparent such that the other plot shines through. However, I want the whole upper plot to have a homogeneous transparency level, such that superimposed markers of the upper plot do not add up their opacity as they would do if I simply set alpha=0.5.
In other words, I'd like both scatter plots to be rendered first and being set to one constant transparency level. Technically this should be possible for both raster and vector graphics (as SVG supports layer transparency, afaik), but either would be fine for me.
Here is some example code that displays what I do not want to achieve. ;)
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(1, figsize=(6,4), dpi=160)
ax = fig.gca()
s1 = ax.scatter(np.random.randn(1000), np.random.randn(1000), color="b", edgecolors="none")
s2 = ax.scatter(np.random.randn(1000), np.random.randn(1000), color="g", edgecolors="none")
s2.set_alpha(0.5) # sadly the same as setting `alpha=0.5`
fig.show() # or display(fig)
I'd like the green markers around (2,2) to not be darker where they superimpose, for example. Is this possible with matplotlib?
Thanks for your time! :)
After searching some more, I found related questions and two solutions, of which at least one kind of works for me:
As I hoped one can render one layer and then afterwards blend them together like this:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(1, figsize=(6,4), dpi=160)
ax1 = fig.gca()
s1 = ax1.scatter(np.random.randn(1000), np.random.randn(1000), color="#3355ff", edgecolors="none")
ax1.set_xlim(-3.5,3.5)
ax1.set_ylim(-3.5,3.5)
ax1.patch.set_facecolor("none")
ax1.patch.set_edgecolor("none")
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
img1 = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
ax1.clear()
s2 = ax1.scatter(np.random.randn(1000), np.random.randn(1000), color="#11aa44", edgecolors="none")
ax1.set_xlim(-3.5,3.5)
ax1.set_ylim(-3.5,3.5)
ax1.patch.set_facecolor("none")
ax1.patch.set_edgecolor("none")
fig.canvas.draw()
img2 = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
fig.clf()
plt.imshow(np.minimum(img1,img2))
plt.subplots_adjust(0, 0, 1, 1)
plt.axis("off")
plt.show()
I may have to come up with better methods than just taking the np.minimum of both layers to keep more color options (and probably save the axes and labels to a third layer).
I did not try mplcairo as suggested in the other linked answer as it has too many dependencies for my use case (my solution should be portable).
I am still happy for additional input. :)

How to draw BarPlot or Histogram using Subplot in MatplotLib?

I want to draw Grid of Bar graph/Histogram for my data.My Data contains 1 NUMERIC and 3 CATEGORICAL Column
PAIRGraph is not suitable for my purpose as my purpose as I have only 1 Numeric and 3 Categorical Column
Tried to Refer Documentation https://matplotlib.org/api/_as_gen/matplotlib.pyplot.subplots.html
However, I am unable to find exact way to fulfill my requirement.
Using Demo code I am able to draw only LineGraph. However, I am required to draw Bar Graph.
fig, axes = plt.subplots(1, 2, figsize=(10,4))
x = np.linspace(0, 5, 11)
axes[0].plot(x, x**2, x, np.exp(x),x,20*x)
axes[0].set_title("Normal scale")
axes[0].plot
axes[1].plot(x, x**2, x, np.exp(x))
axes[1].set_yscale("log")
axes[1].set_title("Logarithmic scale (y)");
Please feel free to correct my approach or guide me as I have just started learning.
If you specify exactly what you want to use for the bar and hist, I can modify, but generally it is simply changing the plot to the type of chart you need
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(1, 2, figsize=(10,4))
x = np.linspace(0, 5, 11)
axes[0].bar(x,x**2) # bar plot
axes[0].set_title("Normal scale")
axes[0].plot
axes[1].hist(x) # histogram
axes[1].set_yscale("log")
axes[1].set_title("Logarithmic scale (y)");
plt.show()
After going through the API documentation from Matplotlip Subplot Axes, I found ways to draw different graph not just Line graph.
https://matplotlib.org/api/axes_api.html
DEFAULT:-
axes[0].plot by-default draws line graph.
CUSTOM GRAPH:-
axes[0].bar can be used to draw BAR graph in selected Subplot
axes[0].scatter can be used to draw Scatter graph in selected Subplot
axes[0].hist can be used to draw a histogram. in selected Subplot
Like above example more graph can be drawn with below API:-

How to get two subplots to have the same height? [duplicate]

I am trying to plot an image (using matplotlib.imshow) and a scatter plot within the same figure. When trying this, the image appears smaller than the scatter plot. Small example code is shown below:
import matplotlib.pyplot as plt
import numpy as np
image = np.random.randint(100,200,(200,200))
x = np.arange(0,10,0.1)
y = np.sin(x)
fig, (ax1, ax2) = plt.subplots(1,2)
ax1.imshow(image)
ax2.scatter(x,y)
plt.show()
Which gives the following figure:
How can I get the two sublpots to have the same height? (and width I suppose)
I have tried using gridspec as shown in this answer:
fig=plt.figure()
gs=GridSpec(1,2)
ax1=fig.add_subplot(gs[0,0])
ax2=fig.add_subplot(gs[0,1])
ax1.imshow(image)
ax2.scatter(x,y)
But this gives the same result. I have also tried to adjust the subplot sizes manually by using:
fig = plt.figure()
ax1 = plt.axes([0.05,0.05,0.45,0.9])
ax2 = plt.axes([0.55,0.19,0.45,0.62])
ax1.imshow(image)
ax2.scatter(x,y)
By trial and error I can get the two subplots to the correct size, though any change in the overall figure size will mean that the subplots will no longer be the same size.
Is there a way to make imshow and a scatter plot appear the same size in a figure without manually changing the axes sizes?
I am using Python 2.7 and matplotlib 2.0.0
It's not perfectly clear what your desired outcome is.
You may use automatic aspect on the image
ax.imshow(z, aspect="auto")
Or you may set the aspect of the line plot depending on its axis limits such that it gets the same size as the image (in case the image has equal x and y sizes)
asp = np.diff(ax2.get_xlim())[0] / np.diff(ax2.get_ylim())[0]
ax2.set_aspect(asp)
Complete code:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0,10,20)
y = np.sin(x)
z = np.random.rand(100,100)
fig, (ax, ax2) = plt.subplots(ncols=2)
ax.imshow(z)
ax2.plot(x,y, marker=".")
asp = np.diff(ax2.get_xlim())[0] / np.diff(ax2.get_ylim())[0]
ax2.set_aspect(asp)
plt.show()
If the image does not have equal limits (is not square), one still needs to divide by the aspect of the image:
asp = np.diff(ax2.get_xlim())[0] / np.diff(ax2.get_ylim())[0]
asp /= np.abs(np.diff(ax1.get_xlim())[0] / np.diff(ax1.get_ylim())[0])
ax2.set_aspect(asp)
More sophisticated solutions:
This answer for using the subplot parameters to achieve a certain aspect.
If you want to use mpl_toolkits and make your hands dirty, this answer would be a good read.
I had the same problem and asked a very similar question in SO. The solution proposed by #ImportanceOfBeingErnest worked like a charm for me, but for completeness, I'd like to mention a pretty simple workaround I was suggested to apply (credit to #Yilun Zhang) before my question was marked as an exact duplicate of this one:
The problem is that the plot region height is too large and this is leaving empty place in the image.
If you change your code to:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
then you get the desired outcome:
Here's some code I use:
fig, axis_array = plt.subplots(1, 2, figsize=(chosen_value, 1.05 * chosen_value / 2),
subplot_kw={'aspect': 1})
I'm explicitly selecting that there will be 2 sub plots in my figure, and that the figure will be chosen_value tall and each subplot will be about half that size wide, and that the subplots will have an aspect ratio of 1 (i.e., they will both be square). The figure size is a specific ratio which forces the spacing.
For those sharing the y-axis across both plots, setting constrained_layout to True may help.

Matplotlib make subplot axes same size - imshow and plot [duplicate]

I am trying to plot an image (using matplotlib.imshow) and a scatter plot within the same figure. When trying this, the image appears smaller than the scatter plot. Small example code is shown below:
import matplotlib.pyplot as plt
import numpy as np
image = np.random.randint(100,200,(200,200))
x = np.arange(0,10,0.1)
y = np.sin(x)
fig, (ax1, ax2) = plt.subplots(1,2)
ax1.imshow(image)
ax2.scatter(x,y)
plt.show()
Which gives the following figure:
How can I get the two sublpots to have the same height? (and width I suppose)
I have tried using gridspec as shown in this answer:
fig=plt.figure()
gs=GridSpec(1,2)
ax1=fig.add_subplot(gs[0,0])
ax2=fig.add_subplot(gs[0,1])
ax1.imshow(image)
ax2.scatter(x,y)
But this gives the same result. I have also tried to adjust the subplot sizes manually by using:
fig = plt.figure()
ax1 = plt.axes([0.05,0.05,0.45,0.9])
ax2 = plt.axes([0.55,0.19,0.45,0.62])
ax1.imshow(image)
ax2.scatter(x,y)
By trial and error I can get the two subplots to the correct size, though any change in the overall figure size will mean that the subplots will no longer be the same size.
Is there a way to make imshow and a scatter plot appear the same size in a figure without manually changing the axes sizes?
I am using Python 2.7 and matplotlib 2.0.0
It's not perfectly clear what your desired outcome is.
You may use automatic aspect on the image
ax.imshow(z, aspect="auto")
Or you may set the aspect of the line plot depending on its axis limits such that it gets the same size as the image (in case the image has equal x and y sizes)
asp = np.diff(ax2.get_xlim())[0] / np.diff(ax2.get_ylim())[0]
ax2.set_aspect(asp)
Complete code:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0,10,20)
y = np.sin(x)
z = np.random.rand(100,100)
fig, (ax, ax2) = plt.subplots(ncols=2)
ax.imshow(z)
ax2.plot(x,y, marker=".")
asp = np.diff(ax2.get_xlim())[0] / np.diff(ax2.get_ylim())[0]
ax2.set_aspect(asp)
plt.show()
If the image does not have equal limits (is not square), one still needs to divide by the aspect of the image:
asp = np.diff(ax2.get_xlim())[0] / np.diff(ax2.get_ylim())[0]
asp /= np.abs(np.diff(ax1.get_xlim())[0] / np.diff(ax1.get_ylim())[0])
ax2.set_aspect(asp)
More sophisticated solutions:
This answer for using the subplot parameters to achieve a certain aspect.
If you want to use mpl_toolkits and make your hands dirty, this answer would be a good read.
I had the same problem and asked a very similar question in SO. The solution proposed by #ImportanceOfBeingErnest worked like a charm for me, but for completeness, I'd like to mention a pretty simple workaround I was suggested to apply (credit to #Yilun Zhang) before my question was marked as an exact duplicate of this one:
The problem is that the plot region height is too large and this is leaving empty place in the image.
If you change your code to:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
then you get the desired outcome:
Here's some code I use:
fig, axis_array = plt.subplots(1, 2, figsize=(chosen_value, 1.05 * chosen_value / 2),
subplot_kw={'aspect': 1})
I'm explicitly selecting that there will be 2 sub plots in my figure, and that the figure will be chosen_value tall and each subplot will be about half that size wide, and that the subplots will have an aspect ratio of 1 (i.e., they will both be square). The figure size is a specific ratio which forces the spacing.
For those sharing the y-axis across both plots, setting constrained_layout to True may help.

Python Subplot function parameters

I am having a hard time with putting in the parameters for the python subplot function.
What I want is to plot 4 graphs on a same image file with the following criteria
left
space
right
space
left
space
right
I have tried different ways of the 3 numbers but the output doesnt show up correctly.
Do you mean something like this?
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(4,2,1)
ax2 = fig.add_subplot(4,2,4)
ax3 = fig.add_subplot(4,2,5)
ax4 = fig.add_subplot(4,2,8)
fig.subplots_adjust(hspace=1)
plt.show()
Well, the not-so-easily-found documentation regarding the sublot function template is as follows:
subplot (number_of_graphs_horizontal, number of graphs_vertical, index)
Let us investigate the code from Joe Kington above:
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(4,2,1)
ax2 = fig.add_subplot(4,2,4)
ax3 = fig.add_subplot(4,2,5)
ax4 = fig.add_subplot(4,2,8)
fig.subplots_adjust(hspace=1)
plt.show()
You told matplotlib that you want a grid with 4 rows and 2 columns of graphs. ax1, ax2 and so on are the graphs that you add at the index positions which you can read as the third parameter. You count from left to right in a row-wise manner.
I hope that helped :)
Matplotlib provides several ways deal with the deliberate placement of plots on a single page; i think the best is gridspec, which i believe first appeared in the 1.0 release. The other two, by the way, are (i) directly indexing subplot and (ii) the new ImageGrid toolkit).
GridSpec works like grid-based packers in GUI toolkits used to placed widgets in a parent frame, so for that reason at least, it seems the easiest to use and the most configurable of the three placement techniques.
import numpy as NP
import matplotlib.pyplot as PLT
import matplotlib.gridspec as gridspec
import matplotlib.cm as CM
V = 10 * NP.random.rand(10, 10) # some data to plot
fig = PLT.figure(1, (5., 5.)) # create the top-level container
gs = gridspec.GridSpec(4, 4) # create a GridSpec object
# for the arguments to subplot that are identical across all four subplots,
# to avoid keying them in four times, put them in a dict
# and let subplot unpack them
kx = dict(frameon = False, xticks = [], yticks = [])
ax1 = PLT.subplot(gs[0, 0], **kx)
ax3 = PLT.subplot(gs[2, 0], **kx)
ax2 = PLT.subplot(gs[1, 1], **kx)
ax4 = PLT.subplot(gs[3, 1], **kx)
for itm in [ax1, ax2, ax3, ax4] :
itm.imshow(V, cmap=CM.jet, interpolation='nearest')
PLT.show()
Beyond just arranging the four plots in a 'checkerboard' configuration (per your Question), I have not tried to tune this configuration, but that's easy to do. E.g.,
# to change the space between the cells that hold the plots:
gs1.update(left=.1, right=,1, wspace=.1, hspace=.1)
# to create a grid comprised of varying cell sizes:
gs = gridspec.GridSpec(4, 4, width_ratios=[1, 2], height_ratios=[4, 1])

Categories

Resources