Matplotlib, matshow not aligned with gridspec when fig size bi - python

I tried to use gridspec to plot multiple types of plots together. I use it with Jupyter Notebook, I realise that when the figure width is bigger than the cell width. The matshow shrunk and no longer aligned with others.
For example, when figsize's width is smaller than the cell width, everything is fine. .
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as grd
duration = 1
data1 = np.sin(2*np.pi*np.linspace(0, duration, 10000))
data2 = np.random.random((100,12))
fig = plt.figure(figsize=[15, 5], constrained_layout=True)
grid = grd.GridSpec(2, 2, figure=fig, height_ratios=[1, 1], width_ratios=[40, 1])
ax = plt.subplot(grid[0])
ax.plot(data1)
ax = plt.subplot(grid[2])
im = ax.matshow(data2.T, cmap=plt.get_cmap('inferno'), origin='lower')
ax = plt.subplot(grid[3])
cb = plt.colorbar(im, cax=ax)
Then when the width is bigger than the cell. .
fig = plt.figure(figsize=[20, 5], constrained_layout=True)
grid = grd.GridSpec(2, 2, figure=fig, height_ratios=[1, 1], width_ratios=[40, 1])
ax = plt.subplot(grid[0])
ax.plot(data1)
ax = plt.subplot(grid[2])
im = ax.matshow(data2.T, cmap=plt.get_cmap('inferno'), origin='lower')
ax = plt.subplot(grid[3])
cb = plt.colorbar(im, cax=ax)
What is causing the matshow() to shrink and how can I fix it? I am on Python 3.7 with Matplotlib 3.1.3
Thanks

One of the whole points of constrained_layout is colorbars are dealt with more gracefully. i.e. you don't need the width_ratios=[40, 1] hack.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(2, 1, constrained_layout=True)
pc = ax[0].matshow(np.random.rand(20, 20), aspect='auto')
fig.colorbar(pc, ax=ax[0])
ax[1].plot(np.random.rand(20))
plt.show()

i'm tried your code.
If you use plt.show() and maximizes the window:enter image description here

Related

How to force aspect ratio of subplot while keeping the width identical?

I'm trying to create a plot with two subplots. 1 column, 2 rows.
Similar to the image but without the subplot to the right
How can I enforce one subplot to be square and the other one to have the same width without being square?
I tried gridspec without success:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig = plt.figure(constrained_layout=True)
gs = GridSpec(nrows=4, ncols=3, height_ratios=[1, 1, 1, 1], figure=fig)
ax1 = fig.add_subplot(gs[:-1,:])
ax2 = fig.add_subplot(gs[-1,:])
I also tried setting the aspect ratio for both subplots resulting in different widths the subplots:
fig, axs = plt.subplots(2)
axs[0].set_aspect(1)
axs[1].set_aspect(2)
I also tried... but this fixes the x range of the subplots to the same value.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
F = plt.figure()
grid = ImageGrid(F, 111,
nrows_ncols=(2, 1),
axes_pad=0.1,
)
grid[1].set_aspect(.4)
Thanks for any suggestions...
One working solution I could come up with following the suggestions of ImportanceOfBeingErnest:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
fig = plt.figure(figsize=(10,12))
axScatter = plt.subplot(111)
axScatter.set_aspect(1.)
divider = make_axes_locatable(axScatter)
axHistx = divider.append_axes("bottom", size=.8, pad=0.2)
Thank you!

In python, how to correctly use `colorbar` and `pcolormesh`?

Here is my code,
from mpl_toolkits.axes_grid1 import make_axes_locatable # colorbar
from matplotlib import pyplot as plt
from matplotlib import cm # 3D surface color
import numpy as np
data1 = np.random.rand(10, 12)
data2 = np.random.rand(10, 12)
data3 = data1 - data2
vmin = min([data1.min(), data2.min(), data3.min()])
vmax = max([data1.max(), data2.max(), data2.max()])
fig, (ax_1, ax_2, ax_error) = plt.subplots(nrows=3, ncols=1, figsize=(6, 6))
ax_1.set_ylabel('x')
mesh_1 = ax_1.pcolormesh(data1.T, cmap = cm.coolwarm)
ax_2.set_ylabel('x')
mesh_2 = ax_2.pcolormesh(data2.T, cmap = cm.coolwarm)
mesh_error = ax_error.pcolormesh(data3.T, cmap = cm.coolwarm)
ax_error.set_ylabel('x')
ax_error.set_xlabel('t')
divider = make_axes_locatable(ax_2)
cax_val = divider.append_axes("right", size="2%", pad=.1)
fig.colorbar(mesh_2, ax=[ax_1, ax_2, ax_error], cax=cax_val)
fig.tight_layout()
plt.show()
and it produces an image
However, what I expect is that it produces the picture below
Can anyone help me with this problem? Thanks in advance for any helpful suggestion!
tight_layout doesn't help with this problem, unfortunately. No tight_layout and no axes_grid works fine:
from matplotlib import pyplot as plt
from matplotlib import cm # 3D surface color
import numpy as np
data1 = np.random.rand(10, 12)
data2 = np.random.rand(10, 12)
data3 = data1 - data2
fig, (ax_1, ax_2, ax_error) = plt.subplots(nrows=3, ncols=1, figsize=(6, 6))
mesh_1 = ax_1.pcolormesh(data1.T, cmap = cm.coolwarm)
mesh_2 = ax_2.pcolormesh(data2.T, cmap = cm.coolwarm)
mesh_error = ax_error.pcolormesh(data3.T, cmap = cm.coolwarm)
fig.colorbar(mesh_2, ax=[ax_1, ax_2, ax_error])
plt.show()
If you want better spacing you can try constrained_layout:
fig, (ax_1, ax_2, ax_error) = plt.subplots(nrows=3, ncols=1, figsize=(6, 6),
constrained_layout=True)
Constrained layout will also work for just one axes:
fig.colorbar(mesh_2, ax=ax_2)
With the help from #JodyKlymak, I finally solved the problem. The keypoint lies in using shrink, i.e. fig.colorbar(mesh_2, ax=[ax_1, ax_2, ax_error], shrink=0.3). Here is the solution
from matplotlib import pyplot as plt
from matplotlib import cm # 3D surface color
import numpy as np
data1 = np.random.rand(10, 12)
data2 = np.random.rand(10, 12)
data3 = data1 - data2
fig, (ax_1, ax_2, ax_error) = plt.subplots(nrows=3, ncols=1, figsize=(6, 6))
mesh_1 = ax_1.pcolormesh(data1.T, cmap = cm.coolwarm)
mesh_2 = ax_2.pcolormesh(data2.T, cmap = cm.coolwarm)
mesh_error = ax_error.pcolormesh(data3.T, cmap = cm.coolwarm)
fig.colorbar(mesh_2, ax=[ax_1, ax_2, ax_error], shrink=0.3)
plt.show()
and it produces

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

Breaking a plot into subplots [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

imshow and plot side by side

I'm trying to put side-by-side numpy array displayed as image and seaborn distplot of the same array. I've came up with the following function:
def visualize(arr):
f, (ax1, ax2) = plt.subplots(1, 2, gridspec_kw = {'width_ratios': [1, 3]})
ax1.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
plt.show()
which produces:
As you can see, the image has smaller height than the plot. How can I modify my function in order to have the same height for the plot and the imshow?
I want the following placement of the image and the plot:
There are just so many ways to tackle this. All of the following will give more or less the same image
A. Reduce the available space
You may reduce the available space such that both plots are constrained to the same vertical margins. This can be done by
reducing figure height
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(6,2.3), ...)
using subplots_adjust to limit the margins
fig.subplots_adjust(top=0.7, bottom=0.3)
B. Use InsetPosition
You may use mpl_toolkits.axes_grid1.inset_locator.InsetPosition to adjust the coordinates of the second axes to match those of the first one.
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
def visualize(arr):
fig, (ax1, ax2) = plt.subplots(1, 2,
gridspec_kw = {'width_ratios': [1, 3]})
ax1.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
ip = InsetPosition(ax1, [1.5,0,3,1])
ax2.set_axes_locator(ip)
plt.show()
arr = np.random.randn(200,120)
visualize(arr)
C. Use an axes divider
You may create only the axes for the image and then use mpl_toolkits.axes_grid1.make_axes_locatable to create a new axes next to it.
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
def visualize(arr):
fig, ax = plt.subplots()
divider = make_axes_locatable(ax)
ax2 = divider.new_horizontal(size="300%", pad=0.5)
fig.add_axes(ax2)
ax.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
plt.show()
arr = np.random.randn(200,120)
visualize(arr)
D. calculate the desired aspect ratio
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
def visualize(arr):
gkw = {'width_ratios':[1, 3] }
fig, (ax1, ax2) = plt.subplots(1, 2, gridspec_kw = gkw )
ax1.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
ya = np.diff(np.array(ax2.get_ylim()))[0]
xa = np.diff(np.array(ax2.get_xlim()))[0]
wa = gkw['width_ratios'][0]/float(gkw['width_ratios'][1])
ia = arr.shape[0]/float(arr.shape[1])
ax2.set_aspect(float(wa*ia/(ya/xa)))
plt.show()
arr = np.random.randn(200,120)
visualize(arr)
E. Dynamically copy positions
You may get the position of the left plot and copy its y-coordinates to the right subplot's position. This is a nice add-on to existing code. The drawback is necessary because subsequent changes to the figure size require to recalculate the positions.
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
def visualize(arr):
gkw = {'width_ratios':[1, 3] }
fig, (ax1, ax2) = plt.subplots(1, 2, gridspec_kw = gkw )
ax1.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
def on_resize(evt=None):
ax1.apply_aspect()
bb1 = ax1.get_position()
bb2 = ax2.get_position()
bb2.y0 = bb1.y0; bb2.y1 = bb1.y1
ax2.set_position(bb2)
fig.canvas.mpl_connect("resize_event", on_resize)
on_resize()
plt.show()
arr = np.random.randn(200,120)
visualize(arr)

Categories