Breaking a plot into subplots [duplicate] - python

I need to add two subplots to a figure. One subplot needs to be about three times as wide as the second (same height). I accomplished this using GridSpec and the colspan argument but I would like to do this using figure so I can save to PDF. I can adjust the first figure using the figsize argument in the constructor, but how do I change the size of the second plot?

As of matplotlib 3.6.0, width_ratios and height_ratios can now be passed directly as keyword arguments to plt.subplots and subplot_mosaic, as per What's new in Matplotlib 3.6.0 (Sep 15, 2022).
f, (a0, a1) = plt.subplots(1, 2, width_ratios=[3, 1])
f, (a0, a1, a2) = plt.subplots(3, 1, height_ratios=[1, 1, 3])
Another way is to use the subplots function and pass the width ratio with gridspec_kw
matplotlib Tutorial: Customizing Figure Layouts Using GridSpec and Other Functions
matplotlib.gridspec.GridSpec has available gridspect_kw options
import numpy as np
import matplotlib.pyplot as plt
# generate some data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot it
f, (a0, a1) = plt.subplots(1, 2, gridspec_kw={'width_ratios': [3, 1]})
a0.plot(x, y)
a1.plot(y, x)
f.tight_layout()
f.savefig('grid_figure.pdf')
Because the question is canonical, here is an example with vertical subplots.
# plot it
f, (a0, a1, a2) = plt.subplots(3, 1, gridspec_kw={'height_ratios': [1, 1, 3]})
a0.plot(x, y)
a1.plot(x, y)
a2.plot(x, y)
f.tight_layout()

You can use gridspec and figure:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
# generate some data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot it
fig = plt.figure(figsize=(8, 6))
gs = gridspec.GridSpec(1, 2, width_ratios=[3, 1])
ax0 = plt.subplot(gs[0])
ax0.plot(x, y)
ax1 = plt.subplot(gs[1])
ax1.plot(y, x)
plt.tight_layout()
plt.savefig('grid_figure.pdf')

I used pyplot's axes object to manually adjust the sizes without using GridSpec:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# definitions for the axes
left, width = 0.07, 0.65
bottom, height = 0.1, .8
bottom_h = left_h = left+width+0.02
rect_cones = [left, bottom, width, height]
rect_box = [left_h, bottom, 0.17, height]
fig = plt.figure()
cones = plt.axes(rect_cones)
box = plt.axes(rect_box)
cones.plot(x, y)
box.plot(y, x)
plt.show()

Probably the simplest way is using subplot2grid, described in Customizing Location of Subplot Using GridSpec.
ax = plt.subplot2grid((2, 2), (0, 0))
is equal to
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 2)
ax = plt.subplot(gs[0, 0])
so bmu's example becomes:
import numpy as np
import matplotlib.pyplot as plt
# generate some data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot it
fig = plt.figure(figsize=(8, 6))
ax0 = plt.subplot2grid((1, 3), (0, 0), colspan=2)
ax0.plot(x, y)
ax1 = plt.subplot2grid((1, 3), (0, 2))
ax1.plot(y, x)
plt.tight_layout()
plt.savefig('grid_figure.pdf')

In a simple way, different size sub plotting can also be done without gridspec:
plt.figure(figsize=(12, 6))
ax1 = plt.subplot(2,3,1)
ax2 = plt.subplot(2,3,2)
ax3 = plt.subplot(2,3,3)
ax4 = plt.subplot(2,1,2)
axes = [ax1, ax2, ax3, ax4]

A nice way of doing this was added in matplotlib 3.3.0, subplot_mosaic.
You can make a nice layout using an "ASCII art" style.
For example
fig, axes = plt.subplot_mosaic("ABC;DDD")
will give you three axes on the top row and one spanning the full width on the bottom row like below
A nice thing about this method is that the axes returned from the function is a dictionary with the names you define, making it easier to keep track of what is what e.g.
axes["A"].plot([1, 2, 3], [1, 2, 3])
You can also pass a list of lists to subplot_mosaic if you want to use longer names
fig, axes = plt.subplot_mosaic(
[["top left", "top centre", "top right"],
["bottom row", "bottom row", "bottom row"]]
)
axes["top left"].plot([1, 2, 3], [1, 2, 3])
will produce the same figure

Related

How to get aligments of subplots and subfigures right with constrained_layout in matplotlib?

I have the following sub figures and sub plots in matplotlib"
Sub figure 1 > ax1
Sub figure 2 > Sub plot 1 > ax2
> Sub plot 2 > ax3
The MWE is given below. The problem with the present MWE is that for numbers of different magnitudes on y-axes; the alignment between ax1, ax2, and ax3 are broken as shown in the green box of the figure.
Setting the contstrained_layout to False I can get the alignments right, but messes the spacings. So I need the constrained_layout set to True, but need to get the alignments of ax1, ax2, ax3 right. Are there any methods that I am missing to fix this alignment?
MWE
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 100, 100)
y = x ** 2
y2 = x ** 10
figure = plt.figure(figsize=(10, 8), constrained_layout=True)
figure.clf()
subfigs = figure.subfigures(2, 1, height_ratios=[1, 1], hspace=0.05, wspace=0.05)
plots = subfigs[0].subplots()
ax1 = plt.gca()
ax1.plot(x, y2)
sub_plot = subfigs[1].subplots(2,1)
ax2 = sub_plot[0]
ax2.plot(x, y)
ax3 = sub_plot[1]
ax3.plot(x, y)
plt.show()
The point of a subfigure is to make the subfigures independent. If you want axes spines to line up, then you need to keep the axes in the same figure:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 100, 100)
y = x ** 2
y2 = x ** 10
figure = plt.figure(figsize=(5, 4), constrained_layout=True)
ax1, ax2, ax3 = figure.subplots(3, 1, gridspec_kw={'height_ratios': [2, 1, 1]})
ax1.plot(x, y2)
ax2.plot(x, y)
ax3.plot(x, y)
plt.show()
Note that you can set the height_ratios and the width_ratios using the gridspec_kw argument.
For more explanation, you can see: https://matplotlib.org/stable/tutorials/intermediate/arranging_axes.html

Putting one color bar for several subplots from different dataframes

I looked everywhere and nothing really helped.
Here is my code:
fig = plt.figure(figsize=(12, 6))
marker_colors = pca_data2['Frame']
fig.suptitle('PCA')
plt.subplot(1, 2, 1)
x = pca_data2.PC_1
y = pca_data2.PC_2
plt.scatter(x, y, c = marker_colors, cmap = "inferno")
plt.colorbar()
plt.subplot(1, 2, 2)
x1 = pca_data.PC_1
y1 = pca_data.PC_2
plt.scatter(x1, y1, c = marker_colors, cmap = "inferno")
plt.colorbar()
plt.show()
pca_data and pca_data2 are two completely different dataframes from to completele different things. But I need them side by side with the 1 color bar being on the right side for all.
Thats how the figure looks like
When I try to remove the first plt.colorbar() then the two subplots look uneven.
I would really appreciate the help.
... since none of the answers seems to mention the fact that you can tell the colorbar the axes on which it should be drawn... here's a simple example how I would do it:
The benefits of this are:
it's much clearer to read
you have complete control over the size of the colorbar
you can extend this easily to any grid of subplots and any position of the colorbar
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
# generate some data
data, data1 = np.random.rand(10,10), np.random.rand(10,10)
x, y = np.meshgrid(np.linspace(0,1,10), np.linspace(0,1,10))
# initialize a plot-grid with 3 axes (2 plots and 1 colorbar)
gs = GridSpec(1, 3, width_ratios=[.48,.48,.04])
# set vmin and vmax explicitly to ensure that both colorbars have the same range!
vmin = np.min([np.min(data), np.min(data1)])
vmax = np.max([np.max(data), np.max(data1)])
plot_kwargs = dict(cmap = "inferno", vmin=vmin, vmax=vmax)
fig = plt.figure(figsize=(12, 6))
ax_0 = fig.add_subplot(gs[0], aspect='equal')
ax_1 = fig.add_subplot(gs[1], aspect='equal')
ax_cb = fig.add_subplot(gs[2])
s1 = ax_0.scatter(x, y, c = data, **plot_kwargs)
s2 = ax_1.scatter(x, y, c = data1, **plot_kwargs)
plt.colorbar(s1, cax=ax_cb)
You can use aspect to set a fixed aspect ratio on the subplots. Then append the colorbars to the right side of each axis and discard the first colorbar, to get an even layout:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure(figsize=(12, 6))
marker_colors = range(0,10)
x = x1 = np.random.randint(0,10,10)
y = y1 = np.random.randint(0,10,10)
ax1 = fig.add_subplot(1, 2, 1, aspect="equal") # or e.g. aspect=0.9
g1 = ax1.scatter(x, y, c = marker_colors, cmap = "inferno", )
ax2 = fig.add_subplot(1, 2, 2, aspect="equal") # or e.g. aspect=0.9
g2 = ax2.scatter(x1, y1, c = marker_colors, cmap = "inferno")
# put colorbars right next to axes
divider1 = make_axes_locatable(ax1)
cax1 = divider1.append_axes("right", size="5%", pad=0.05)
divider2 = make_axes_locatable(ax2)
cax2 = divider2.append_axes("right", size="5%", pad=0.05)
# reserve space for 1st colorbar, then remove
cbar1 = fig.colorbar(g1, cax=cax1)
fig.delaxes(fig.axes[2])
# 2nd colorbar
cbar2 = fig.colorbar(g2, cax=cax2)
plt.tight_layout()
plt.show()
If you want a different aspect ratio, you can modify aspect, e.g. to aspect=0.9. The result will have locked aspect ratios for the subplots, even if you resize the figure box:
use following code:
Hope it will match your problem statment.
fig = plt.figure(figsize=(12, 6))
marker_colors = range(0,10)
x=x1=np.random.randint(0,10,10)
y=y1=np.random.randint(0,10,10)
plt.subplot(1, 2, 1)
g1=plt.scatter(x, y, c = marker_colors, cmap = "inferno")
plt.subplot(1, 2, 2)
g2=plt.scatter(x1, y1, c = marker_colors, cmap = "inferno")
g11=plt.colorbar(g1)
g12=plt.colorbar(g2)
g11.ax.set_title('g1')
g12.ax.set_title('g2')

Specify the height of a subplot in a multiple subplot matplotlib in Jupyter [duplicate]

I need to add two subplots to a figure. One subplot needs to be about three times as wide as the second (same height). I accomplished this using GridSpec and the colspan argument but I would like to do this using figure so I can save to PDF. I can adjust the first figure using the figsize argument in the constructor, but how do I change the size of the second plot?
As of matplotlib 3.6.0, width_ratios and height_ratios can now be passed directly as keyword arguments to plt.subplots and subplot_mosaic, as per What's new in Matplotlib 3.6.0 (Sep 15, 2022).
f, (a0, a1) = plt.subplots(1, 2, width_ratios=[3, 1])
f, (a0, a1, a2) = plt.subplots(3, 1, height_ratios=[1, 1, 3])
Another way is to use the subplots function and pass the width ratio with gridspec_kw
matplotlib Tutorial: Customizing Figure Layouts Using GridSpec and Other Functions
matplotlib.gridspec.GridSpec has available gridspect_kw options
import numpy as np
import matplotlib.pyplot as plt
# generate some data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot it
f, (a0, a1) = plt.subplots(1, 2, gridspec_kw={'width_ratios': [3, 1]})
a0.plot(x, y)
a1.plot(y, x)
f.tight_layout()
f.savefig('grid_figure.pdf')
Because the question is canonical, here is an example with vertical subplots.
# plot it
f, (a0, a1, a2) = plt.subplots(3, 1, gridspec_kw={'height_ratios': [1, 1, 3]})
a0.plot(x, y)
a1.plot(x, y)
a2.plot(x, y)
f.tight_layout()
You can use gridspec and figure:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
# generate some data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot it
fig = plt.figure(figsize=(8, 6))
gs = gridspec.GridSpec(1, 2, width_ratios=[3, 1])
ax0 = plt.subplot(gs[0])
ax0.plot(x, y)
ax1 = plt.subplot(gs[1])
ax1.plot(y, x)
plt.tight_layout()
plt.savefig('grid_figure.pdf')
I used pyplot's axes object to manually adjust the sizes without using GridSpec:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# definitions for the axes
left, width = 0.07, 0.65
bottom, height = 0.1, .8
bottom_h = left_h = left+width+0.02
rect_cones = [left, bottom, width, height]
rect_box = [left_h, bottom, 0.17, height]
fig = plt.figure()
cones = plt.axes(rect_cones)
box = plt.axes(rect_box)
cones.plot(x, y)
box.plot(y, x)
plt.show()
Probably the simplest way is using subplot2grid, described in Customizing Location of Subplot Using GridSpec.
ax = plt.subplot2grid((2, 2), (0, 0))
is equal to
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 2)
ax = plt.subplot(gs[0, 0])
so bmu's example becomes:
import numpy as np
import matplotlib.pyplot as plt
# generate some data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot it
fig = plt.figure(figsize=(8, 6))
ax0 = plt.subplot2grid((1, 3), (0, 0), colspan=2)
ax0.plot(x, y)
ax1 = plt.subplot2grid((1, 3), (0, 2))
ax1.plot(y, x)
plt.tight_layout()
plt.savefig('grid_figure.pdf')
In a simple way, different size sub plotting can also be done without gridspec:
plt.figure(figsize=(12, 6))
ax1 = plt.subplot(2,3,1)
ax2 = plt.subplot(2,3,2)
ax3 = plt.subplot(2,3,3)
ax4 = plt.subplot(2,1,2)
axes = [ax1, ax2, ax3, ax4]
A nice way of doing this was added in matplotlib 3.3.0, subplot_mosaic.
You can make a nice layout using an "ASCII art" style.
For example
fig, axes = plt.subplot_mosaic("ABC;DDD")
will give you three axes on the top row and one spanning the full width on the bottom row like below
A nice thing about this method is that the axes returned from the function is a dictionary with the names you define, making it easier to keep track of what is what e.g.
axes["A"].plot([1, 2, 3], [1, 2, 3])
You can also pass a list of lists to subplot_mosaic if you want to use longer names
fig, axes = plt.subplot_mosaic(
[["top left", "top centre", "top right"],
["bottom row", "bottom row", "bottom row"]]
)
axes["top left"].plot([1, 2, 3], [1, 2, 3])
will produce the same figure

Colorbar makes subplot smaller in size than rest

I'm trying to make a subplot with three plots next to each other, and then a colorbar on the right side of the last plot (see figure).
I'm doing it with this code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
from mpl_toolkits.axes_grid1 import make_axes_locatable
x = np.linspace(1, 100, 100)
y = np.linspace(0.1, 10, 100)
z = x[:, np.newaxis] + y[np.newaxis, :]
fig, ax = plt.subplots(1, 3, figsize=(12, 4))
ax[0].contourf(x, y, z)
ax[0].set_xlabel('x')
ax[0].set_ylabel('y')
ax[1].contourf(x, y, z)
ax[1].set_xlabel('x')
ax[1].set_ylabel('y')
plt.contourf(x, y, z)
ax[2].set_xlabel('x')
ax[2].set_ylabel('y')
divider = make_axes_locatable(plt.gca())
cax = divider.append_axes("right", "10%", pad="3%")
plt.colorbar(cax=cax)
plt.tight_layout()
plt.show()
My problem is that 1) I don't think the first two plots are completely square (which I would like them to be), 2) the last plot that includes the colorbar is smaller in width than the two others. Is there some easy trick to fix this, or do I manually have to go in and give one a little more padding than the other an so on.
If you don't want the subplot to eat into the third axes, already create an extra axes for it when you make the subplots.
To make the plots square, you need to set the aspect ratio: axes.set_aspect(10).
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1, 100, 100)
y = np.linspace(0.1, 10, 100)
z = x[:, np.newaxis] + y[np.newaxis, :]
gridspec = {'width_ratios': [1, 1, 1, 0.1]}
fig, ax = plt.subplots(1, 4, figsize=(12, 4), gridspec_kw=gridspec)
ax[0].contourf(x, y, z)
ax[0].set_xlabel('x')
ax[0].set_ylabel('y')
ax[1].contourf(x, y, z)
ax[1].set_xlabel('x')
ax[1].set_ylabel('y')
plt.sca(ax[2])
plt.contourf(x, y, z)
ax[2].set_xlabel('x')
ax[2].set_ylabel('y')
for axes in ax[:3]:
axes.set_aspect(10)
cax = ax[3]
plt.colorbar(cax=cax)
plt.tight_layout()
plt.show()

Merge matplotlib subplots with shared x-axis

I have two graphs to where both have the same x-axis, but with different y-axis scalings.
The plot with regular axes is the data with a trend line depicting a decay while the y semi-log scaling depicts the accuracy of the fit.
fig1 = plt.figure(figsize=(15,6))
ax1 = fig1.add_subplot(111)
# Plot of the decay model
ax1.plot(FreqTime1,DecayCount1, '.', color='mediumaquamarine')
# Plot of the optimized fit
ax1.plot(x1, y1M, '-k', label='Fitting Function: $f(t) = %.3f e^{%.3f\t} \
%+.3f$' % (aR1,kR1,bR1))
ax1.set_xlabel('Time (sec)')
ax1.set_ylabel('Count')
ax1.set_title('Run 1 of Cesium-137 Decay')
# Allows me to change scales
# ax1.set_yscale('log')
ax1.legend(bbox_to_anchor=(1.0, 1.0), prop={'size':15}, fancybox=True, shadow=True)
Now, i'm trying to figure out to implement both close together like the examples supplied by this link
http://matplotlib.org/examples/pylab_examples/subplots_demo.html
In particular, this one
When looking at the code for the example, i'm a bit confused on how to implant 3 things:
1) Scaling the axes differently
2) Keeping the figure size the same for the exponential decay graph but having a the line graph have a smaller y size and same x size.
For example:
3) Keeping the label of the function to appear in just only the decay graph.
Any help would be most appreciated.
Look at the code and comments in it:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import gridspec
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
fig = plt.figure()
# set height ratios for subplots
gs = gridspec.GridSpec(2, 1, height_ratios=[2, 1])
# the first subplot
ax0 = plt.subplot(gs[0])
# log scale for axis Y of the first subplot
ax0.set_yscale("log")
line0, = ax0.plot(x, y, color='r')
# the second subplot
# shared axis X
ax1 = plt.subplot(gs[1], sharex = ax0)
line1, = ax1.plot(x, y, color='b', linestyle='--')
plt.setp(ax0.get_xticklabels(), visible=False)
# remove last tick label for the second subplot
yticks = ax1.yaxis.get_major_ticks()
yticks[-1].label1.set_visible(False)
# put legend on first subplot
ax0.legend((line0, line1), ('red line', 'blue line'), loc='lower left')
# remove vertical gap between subplots
plt.subplots_adjust(hspace=.0)
plt.show()
Here is my solution:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
fig, (ax1,ax2) = plt.subplots(nrows=2, sharex=True, subplot_kw=dict(frameon=False)) # frameon=False removes frames
plt.subplots_adjust(hspace=.0)
ax1.grid()
ax2.grid()
ax1.plot(x, y, color='r')
ax2.plot(x, y, color='b', linestyle='--')
One more option is seaborn.FacetGrid but this requires Seaborn and Pandas libraries.
Here are some adaptions to show how the code could work to add a combined legend when plotting a pandas dataframe. ax=ax0 can be used to plot on a given ax and ax0.get_legend_handles_labels() gets the information for the legend.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
dates = pd.date_range('20210101', periods=100, freq='D')
df0 = pd.DataFrame({'x': np.random.normal(0.1, 1, 100).cumsum(),
'y': np.random.normal(0.3, 1, 100).cumsum()}, index=dates)
df1 = pd.DataFrame({'z': np.random.normal(0.2, 1, 100).cumsum()}, index=dates)
fig, (ax0, ax1) = plt.subplots(nrows=2, sharex=True, gridspec_kw={'height_ratios': [2, 1], 'hspace': 0})
df0.plot(ax=ax0, color=['dodgerblue', 'crimson'], legend=False)
df1.plot(ax=ax1, color='limegreen', legend=False)
# put legend on first subplot
handles0, labels0 = ax0.get_legend_handles_labels()
handles1, labels1 = ax1.get_legend_handles_labels()
ax0.legend(handles=handles0 + handles1, labels=labels0 + labels1)
# remove last tick label for the second subplot
yticks = ax1.get_yticklabels()
yticks[-1].set_visible(False)
plt.tight_layout()
plt.show()

Categories