imshow and plot side by side - python

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)

Related

How to add a color bar for vspans created with variable alpha

I would like to use varying degrees of red color to represent the different importance of each time element and fill in that region.
The example code is shown below.
import matplotlib.pyplot as plt
X_example = np.random.rand(400)
importance_values = np.random.rand(400)
plt.figure(figsize=(13,7))
plt.plot(X_example)
for j in range(len(X_example)):
plt.axvspan(xmin=j, xmax=j+1,facecolor="r",alpha=importance_values[j])
It generates a graph like:
Now I would like to add a colormap in this figure to show that, e.g. the light red means low importance and the dark red means high importance, just like this:
How could I achieve that in my case?
One solution would be to create a LinearSegmentedColormap which takes a list of colors and turns it into a matplotlib colorbar object. Then you can set the "alpha channel":
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap, ListedColormap
from matplotlib.colorbar import ColorbarBase
X_example = np.random.rand(400)
importance_values = np.random.rand(400)
fig, (ax, cax) = plt.subplots(ncols=2, figsize=(8,5), gridspec_kw={'width_ratios': [1, 0.05]})
ax.plot(X_example, color='b')
for j in range(len(X_example)):
ax.axvspan(xmin=j, xmax=j+1,facecolor="r",alpha=importance_values[j])
N = 20 # the number of colors/alpha-values in the colorbar
cmap = LinearSegmentedColormap.from_list(None, ['r' for i in range(N)], N=N)
alpha_cmap = cmap(np.arange(N))
alpha_cmap[:,-1] = np.linspace(0, 1, N)
alpha_cmap = ListedColormap(alpha_cmap, N=N)
cbar = ColorbarBase(cax, cmap=alpha_cmap, ticks=[0., 1],)
cbar.ax.set_yticklabels(["low importance", "high importance"])
This gives the following plot, where the two colors of the colorbar have custom labels:
You could create a colormap mixing the red color with a range of alpha values:
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, to_rgba
from matplotlib.cm import ScalarMappable
import numpy as np
X_example = np.random.rand(400)
importance_values = np.random.rand(400)
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(X_example)
for j in range(len(X_example)):
ax.axvspan(xmin=j, xmax=j + 1, facecolor="r", alpha=importance_values[j])
ax.margins(x=0)
cmap = LinearSegmentedColormap.from_list(None, [to_rgba('r', 0), 'r'])
cbar = plt.colorbar(ScalarMappable(cmap=cmap), ticks=[0, 1], pad=0.02)
cbar.ax.set_yticklabels(["low", "high"], fontsize=20)
cbar.ax.set_ylabel("importance", labelpad=-30, fontsize=20)
plt.tight_layout()
plt.show()
An example of a horizontal colorbar:
cbar = plt.colorbar(ScalarMappable(cmap=cmap), ticks=[0, 1], orientation='horizontal')
cbar.ax.set_xticklabels(["low", "high"], fontsize=20)
cbar.ax.set_xlabel("importance", labelpad=-15, fontsize=20)

Matplotlib, matshow not aligned with gridspec when fig size bi

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

Share X axis between line and bar plot in Python's Matplotlib

I have the following script for generating a figure with two subplots: one line plot, and one bar plot.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
plt.close('all')
np.random.seed(42)
n = 1000
idx = pd.date_range(end='2020-02-27', periods=n)
df = pd.Series(np.random.randint(-5, 5, n),
index=idx)
curve = df.cumsum()
bars = df.resample('M').sum()
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
curve.plot(ax=ax1)
bars.plot(kind='bar', ax=ax2)
fig.set_tight_layout(True)
I would like to share the x axis between the two subplots, however the command ax2 = fig.add_subplot(212, sharex=ax1) will result in an empty graph for the line plot like the following figure.
Here is my version based on Matplotlib (without pandas api for plotting), may be it would be helpful.
I explicitly set the width of bars.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
%matplotlib inline
plt.close('all')
np.random.seed(42)
n = 1000
idx = pd.date_range(end='2020-02-27', periods=n)
df = pd.Series(np.random.randint(-5, 5, n), index=idx)
curve = df.cumsum()
bars = df.resample('M').sum()
#fig = plt.figure()
#ax1 = fig.add_subplot(211)
#ax2 = fig.add_subplot(212)
#curve.plot(ax=ax1)
#bars.plot(kind='bar', ax=ax2)
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, gridspec_kw={'hspace': 0})
ax1.plot(curve.index, curve.values)
ax2.bar(bars.index, bars.values, width = (bars.index[0] - bars.index[1])/2)
fig.set_tight_layout(True)
_ = plt.xticks(bars.index, bars.index, rotation=90)

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!

plot two seaborn heatmap graphs side by side

I'm attempting to plot two seaborn graphs side by side as other graphs (successfully) have done in previous questions, only difference I can see is that heatmaps seems to be throwing an issue. The code to produce the error is:
import numpy as np; np.random.seed(0)
import seaborn as sns
uniform_data = np.random.rand(10, 12)
uniform_data2 = np.random.rand(100, 120)
fig, ax =plt.subplots(1,2)
ax = sns.heatmap(uniform_data)
ax = sns.heatmap(uniform_data2)
Which produces the below
You just have to use the ax parameter
fig, (ax1, ax2) = plt.subplots(1,2)
sns.heatmap(uniform_data, ax=ax1)
sns.heatmap(uniform_data2, ax=ax2)
plt.show()
You have created an array of axes using fig, ax = plt.subplots(1,2). You are then overwriting that array with the result of sns.heatmap. Instead, you want to specify which axes you want to plot to using the ax= argument of sns.heatmap:
import numpy as np; np.random.seed(0)
import seaborn as sns
uniform_data = np.random.rand(10, 12)
uniform_data2 = np.random.rand(100, 120)
fig, ax =plt.subplots(1,2)
sns.heatmap(uniform_data, ax=ax[0])
sns.heatmap(uniform_data2, ax=ax[1])
plt.show()
Which gives:

Categories