在 imshow 的边缘添加条形图,使条形图与单元格对齐
Adding bar plots at the margins of imshow, keeping bars aligned to cells
我正在尝试在 imshow 图的顶部添加一个条形图,在右侧添加另一个条形图,条形图与 imshow“单元格”对齐。
我已经尝试过使用本例 adding histograms at the margins of a scatterplot) 中使用的方法和 make_axes_locatable
.
我得到的结果如图所示。有两个问题我无法解决:
imshow
图的实际大小小于我绘制它的轴的大小,因为我想保持矩阵纵横比,所以实际图将严格包含在轴
- 即使这不是问题(见上图),条形图也不会与
imshow
单元格对齐。
这是我的代码
# from mpl_toolkits.axes_grid1 import make_axes_locatable
plt.style.use('dark_background')
m = np.random.rand(25, 200)
# definitions for the axes
left, width = 0.1, 0.65
bottom, height = 0.1, 0.65
spacing = 0.005
rect0 = [left, bottom, width, height]
rect1 = [left, bottom + height + spacing, width, 0.2]
rect2 = [left + width + spacing, bottom, 0.2, height]
# start with a rectangular Figure
fig = plt.figure(figsize=(20, 8))
ax0 = plt.axes(rect0)
ax0.tick_params(direction='in', top=True, right=True)
ax1 = plt.axes(rect1)
ax1.tick_params(direction='in', labelbottom=False)
ax2 = plt.axes(rect2)
ax2.tick_params(direction='in', labelleft=False)
ax0.matshow(m, norm=matplotlib.colors.LogNorm())
# divider = make_axes_locatable(ax)
# cax = divider.append_axes('right', size='95%', pad=0)
ax1.bar(np.arange(m.shape[1]), np.apply_along_axis(scipy.stats.entropy, 0, m))
# divider = make_axes_locatable(ax)
# cax = divider.append_axes('bottom', size='95%', pad=0)
ax2.barh(np.arange(m.shape[0]), np.apply_along_axis(scipy.stats.entropy, 1, m), orientation='horizontal')
plt.savefig('/data/l989o/a/so.png')
plt.style.use('default')
编辑
尝试向绘图添加细节,如轴标签或色标,我发现一般情况可能更复杂。我添加了用于添加其他绘图元素的更一般情况的代码以及代码。
请注意,我注意到我必须反转右侧的条形图,因为在使用 orientation=horizontal
时,条形图的顺序与图像的其中一行相反。
# from mpl_toolkits.axes_grid1 import make_axes_locatable
import functools
plt.style.use('dark_background')
m = np.random.rand(58, 226) * 20
# definitions for the axes
left, width = 0.1, 0.65
bottom, height = 0.1, 0.65
spacing = 0.005
rect0 = [left, bottom, width, height]
rect1 = [left, bottom + height + spacing, width, 0.2]
rect2 = [left + width + spacing, bottom, 0.2, height]
# start with a rectangular Figure
fig = plt.figure(figsize=(20, 8))
ax0 = plt.axes(rect0)
ax0.tick_params(direction='in', top=True, right=True)
ax1 = plt.axes(rect1)
ax1.tick_params(direction='in', labelbottom=False)
ax2 = plt.axes(rect2)
ax2.tick_params(direction='in', labelleft=False)
t = 10
n = 2
cmap = matplotlib.colors.LinearSegmentedColormap.from_list(None, plt.cm.Set1(range(0, n)), n)
im = ax0.imshow(m > t, cmap=cmap)
ax0.set_xlabel('image')
ax0.set_ylabel('cluster label')
divider = make_axes_locatable(ax0)
cax = divider.append_axes('left', size='1%', pad=1)
cbar = fig.colorbar(im, ticks=range(n), cax=cax)
# cbar.set_lim(-0.5, n - 0.5)
cbar.ax.tick_params(length=0)
cbar.set_ticks([0.25, 0.75])
cbar.set_ticklabels([f'<= {t}', f'> {t}'])
cbar.ax.set_title('# cells')
# divider = make_axes_locatable(ax)
# cax = divider.append_axes('right', size='95%', pad=0)
def sum_treshold(v, threshold):
return np.sum(v > threshold)
ax1.bar(np.arange(m.shape[1]), np.apply_along_axis(functools.partial(sum_treshold, threshold=t), 0, m))
ax1.set_xlim([0, m.shape[1]])
# divider = make_axes_locatable(ax)
# cax = divider.append_axes('bottom', size='95%', pad=0)
ax2.barh(np.arange(m.shape[0])[::-1], np.apply_along_axis(functools.partial(sum_treshold, threshold=t), 1, m), orientation='horizontal')
ax2.set_ylim([0, m.shape[0]])
plt.savefig('/data/l989o/a/so.png')
plt.style.use('default')
编辑 2
这是最终输出的示例。为了获得这一点,我进行了疯狂的二进制搜索并设置了硬编码坐标(这当然只适用于我拥有的特定数据矩阵,而不适用于一般情况)。
我不确定这是否会为您提供您想要的确切布局,但这里的一些内容可能会有所帮助。
此答案使用 gridspec
定义子图的相对比率,使用 inset_axes
和 transform
来添加颜色条。 @Marc 的回答 是一个很好的简单示例,说明如何使用 gridspec
,如果那部分令人困惑的话。
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib.gridspec as gridspec
import matplotlib.colors as mcolors
import numpy as np
m = np.random.rand(58, 226) * 20
fig = plt.figure(figsize=(20,8), constrained_layout=True)
gs = fig.add_gridspec(2, 3)
ax1 = fig.add_subplot(gs[0, 0:2])
ax2 = fig.add_subplot(gs[1, 0:2])
## can add these if you need to share axes:, sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[:, -1])
ax1.bar(np.arange(m.shape[1]), np.arange(m.shape[1]))
vals = ax2.imshow(np.random.random((20,10)), cmap='rainbow', aspect='auto')
## aspect = 'auto' follows the established gridpec space
## default for imshow is equal axis
ax3.barh(np.arange(m.shape[1]), np.arange(m.shape[1]))
cbax2=ax2.inset_axes([1.05,0,0.03,1], transform=ax2.transAxes)
## the inset axes inputs are x,y,width,height
## the transform "anchors" these relative to the ax2 axis
## so here we are saying start at 5% past the ax2 width; start at the bottom of ax2 (y=0);
### make the inset axis 3% as wide as the ax2 axis; and make it 100% as tall as the ax2 axis
cbar2=fig.colorbar(vals, cax=cbax2, format = '%1.2g', orientation='vertical')
根据评论更新:
这是否更接近您需要的答案?
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib.gridspec as gridspec
import matplotlib.colors as mcolors
import numpy as np
import functools
def sum_treshold(v, threshold):
return np.sum(v > threshold)
m = np.random.rand(58, 226) * 20
t = 10
n = 2
cmap = mcolors.LinearSegmentedColormap.from_list(None, plt.cm.Set1(range(0, n)), n)
fig = plt.figure(figsize=(20,8), constrained_layout=True)
gs = fig.add_gridspec(2, 3)
ax1 = fig.add_subplot(gs[0, 0:2])
ax2 = fig.add_subplot(gs[1, 0:2], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[1, -1], sharey = ax1)
ax1.bar(np.arange(m.shape[1]),
np.apply_along_axis(functools.partial(sum_treshold, threshold=t), 0, m))
ax1.set_xlim([0, m.shape[1]])
im = ax2.imshow(m > t, cmap=cmap)
ax3.barh(np.arange(m.shape[0])[::-1],
np.apply_along_axis(functools.partial(sum_treshold, threshold=t), 1, m),
orientation='horizontal')
ax3.set_ylim([0, m.shape[0]])
cbax2=ax2.inset_axes([-0.10,0,0.03,1], transform=ax2.transAxes)
cbar2=fig.colorbar(im, cax=cbax2, format = '%1.2g', orientation='vertical')
由于您已经计算了轴的所有尺寸,因此您可以调整它们以遵循图像纵横比施加的限制。您需要通过更改 height
或更改图形高度来更改 ax0
的高度。如果宽高比在其他方面是错误的,则需要对宽度做类似的事情。
要添加颜色条,您需要从一开始就保留一些space,或者将其放在右上角的空白处。
这是一个示例,现在包括 space 用于左侧的颜色栏,底部移动到绘图的中心。现在子图之间的间距相等。
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
plt.style.use('dark_background')
m = np.random.randn(25, 200).cumsum(axis=0).cumsum(axis=1)
m -= m.min()
m *= 20 / m.max()
# definitions for the axes
left, width = 0.1, 0.65
bottom, height = 0.1, 0.65
spacing = 0.005
width_2 = 0.2
height_1 = 0.2
fig_width, fig_height = 20, 8
aspect_m = m.shape[0] / m.shape[1]
aspect_rect = fig_height * height / (fig_width * width)
if aspect_m < aspect_rect: # either reduce the fig_height, or reduce adapt rectangle height
new_height = aspect_m * (fig_width * width) / fig_height
# optionally increase height_1 and/or increase bottom
bottom += (height - new_height) / 2
height = new_height
else: # similar for the width
width = fig_height * height / fig_width / aspect_m
# optionally increase width_2 and/or increase left
rect0 = [left, bottom, width, height]
rect1 = [left, bottom + height + spacing, width, height_1]
rect2 = [left + width + spacing * fig_height / fig_width, bottom, width_2, height]
rectcbar = [left - 0.06, bottom, 0.01, height]
fig = plt.figure(figsize=(fig_width, fig_height))
ax0 = plt.axes(rect0)
ax0.tick_params(direction='in', top=True, right=True)
ax1 = plt.axes(rect1)
ax1.tick_params(direction='in', labelbottom=False)
ax2 = plt.axes(rect2)
ax2.tick_params(direction='in', labelleft=False)
cbarax = plt.axes(rectcbar)
t = 10
cmap = matplotlib.colors.ListedColormap(['red', 'dodgerblue'])
im = ax0.imshow(m > t, cmap=cmap, origin='lower')
ax0.set_xlabel('image')
ax0.set_ylabel('cluster label')
cbar = fig.colorbar(im, cax=cbarax)
cbar.ax.tick_params(length=0)
cbar.set_ticks([0.25, 0.75])
cbar.ax.set_yticklabels([f'≤ {t}', f'> {t}'])
cbar.ax.set_title('# cells')
ax1.bar(np.arange(m.shape[1]), np.sum(m > t, axis=0))
ax2.barh(np.arange(m.shape[0]), np.sum(m > t, axis=1))
ax0.set_aspect('equal')
ax1.get_shared_x_axes().join(ax1, ax0)
ax2.get_shared_y_axes().join(ax2, ax0)
plt.show()
我正在尝试在 imshow 图的顶部添加一个条形图,在右侧添加另一个条形图,条形图与 imshow“单元格”对齐。
我已经尝试过使用本例 adding histograms at the margins of a scatterplot) 中使用的方法和 make_axes_locatable
.
我得到的结果如图所示。有两个问题我无法解决:
imshow
图的实际大小小于我绘制它的轴的大小,因为我想保持矩阵纵横比,所以实际图将严格包含在轴- 即使这不是问题(见上图),条形图也不会与
imshow
单元格对齐。
这是我的代码
# from mpl_toolkits.axes_grid1 import make_axes_locatable
plt.style.use('dark_background')
m = np.random.rand(25, 200)
# definitions for the axes
left, width = 0.1, 0.65
bottom, height = 0.1, 0.65
spacing = 0.005
rect0 = [left, bottom, width, height]
rect1 = [left, bottom + height + spacing, width, 0.2]
rect2 = [left + width + spacing, bottom, 0.2, height]
# start with a rectangular Figure
fig = plt.figure(figsize=(20, 8))
ax0 = plt.axes(rect0)
ax0.tick_params(direction='in', top=True, right=True)
ax1 = plt.axes(rect1)
ax1.tick_params(direction='in', labelbottom=False)
ax2 = plt.axes(rect2)
ax2.tick_params(direction='in', labelleft=False)
ax0.matshow(m, norm=matplotlib.colors.LogNorm())
# divider = make_axes_locatable(ax)
# cax = divider.append_axes('right', size='95%', pad=0)
ax1.bar(np.arange(m.shape[1]), np.apply_along_axis(scipy.stats.entropy, 0, m))
# divider = make_axes_locatable(ax)
# cax = divider.append_axes('bottom', size='95%', pad=0)
ax2.barh(np.arange(m.shape[0]), np.apply_along_axis(scipy.stats.entropy, 1, m), orientation='horizontal')
plt.savefig('/data/l989o/a/so.png')
plt.style.use('default')
编辑 尝试向绘图添加细节,如轴标签或色标,我发现一般情况可能更复杂。我添加了用于添加其他绘图元素的更一般情况的代码以及代码。
请注意,我注意到我必须反转右侧的条形图,因为在使用 orientation=horizontal
时,条形图的顺序与图像的其中一行相反。
# from mpl_toolkits.axes_grid1 import make_axes_locatable
import functools
plt.style.use('dark_background')
m = np.random.rand(58, 226) * 20
# definitions for the axes
left, width = 0.1, 0.65
bottom, height = 0.1, 0.65
spacing = 0.005
rect0 = [left, bottom, width, height]
rect1 = [left, bottom + height + spacing, width, 0.2]
rect2 = [left + width + spacing, bottom, 0.2, height]
# start with a rectangular Figure
fig = plt.figure(figsize=(20, 8))
ax0 = plt.axes(rect0)
ax0.tick_params(direction='in', top=True, right=True)
ax1 = plt.axes(rect1)
ax1.tick_params(direction='in', labelbottom=False)
ax2 = plt.axes(rect2)
ax2.tick_params(direction='in', labelleft=False)
t = 10
n = 2
cmap = matplotlib.colors.LinearSegmentedColormap.from_list(None, plt.cm.Set1(range(0, n)), n)
im = ax0.imshow(m > t, cmap=cmap)
ax0.set_xlabel('image')
ax0.set_ylabel('cluster label')
divider = make_axes_locatable(ax0)
cax = divider.append_axes('left', size='1%', pad=1)
cbar = fig.colorbar(im, ticks=range(n), cax=cax)
# cbar.set_lim(-0.5, n - 0.5)
cbar.ax.tick_params(length=0)
cbar.set_ticks([0.25, 0.75])
cbar.set_ticklabels([f'<= {t}', f'> {t}'])
cbar.ax.set_title('# cells')
# divider = make_axes_locatable(ax)
# cax = divider.append_axes('right', size='95%', pad=0)
def sum_treshold(v, threshold):
return np.sum(v > threshold)
ax1.bar(np.arange(m.shape[1]), np.apply_along_axis(functools.partial(sum_treshold, threshold=t), 0, m))
ax1.set_xlim([0, m.shape[1]])
# divider = make_axes_locatable(ax)
# cax = divider.append_axes('bottom', size='95%', pad=0)
ax2.barh(np.arange(m.shape[0])[::-1], np.apply_along_axis(functools.partial(sum_treshold, threshold=t), 1, m), orientation='horizontal')
ax2.set_ylim([0, m.shape[0]])
plt.savefig('/data/l989o/a/so.png')
plt.style.use('default')
编辑 2 这是最终输出的示例。为了获得这一点,我进行了疯狂的二进制搜索并设置了硬编码坐标(这当然只适用于我拥有的特定数据矩阵,而不适用于一般情况)。
我不确定这是否会为您提供您想要的确切布局,但这里的一些内容可能会有所帮助。
此答案使用 gridspec
定义子图的相对比率,使用 inset_axes
和 transform
来添加颜色条。 @Marc 的回答 gridspec
,如果那部分令人困惑的话。
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib.gridspec as gridspec
import matplotlib.colors as mcolors
import numpy as np
m = np.random.rand(58, 226) * 20
fig = plt.figure(figsize=(20,8), constrained_layout=True)
gs = fig.add_gridspec(2, 3)
ax1 = fig.add_subplot(gs[0, 0:2])
ax2 = fig.add_subplot(gs[1, 0:2])
## can add these if you need to share axes:, sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[:, -1])
ax1.bar(np.arange(m.shape[1]), np.arange(m.shape[1]))
vals = ax2.imshow(np.random.random((20,10)), cmap='rainbow', aspect='auto')
## aspect = 'auto' follows the established gridpec space
## default for imshow is equal axis
ax3.barh(np.arange(m.shape[1]), np.arange(m.shape[1]))
cbax2=ax2.inset_axes([1.05,0,0.03,1], transform=ax2.transAxes)
## the inset axes inputs are x,y,width,height
## the transform "anchors" these relative to the ax2 axis
## so here we are saying start at 5% past the ax2 width; start at the bottom of ax2 (y=0);
### make the inset axis 3% as wide as the ax2 axis; and make it 100% as tall as the ax2 axis
cbar2=fig.colorbar(vals, cax=cbax2, format = '%1.2g', orientation='vertical')
根据评论更新: 这是否更接近您需要的答案?
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib.gridspec as gridspec
import matplotlib.colors as mcolors
import numpy as np
import functools
def sum_treshold(v, threshold):
return np.sum(v > threshold)
m = np.random.rand(58, 226) * 20
t = 10
n = 2
cmap = mcolors.LinearSegmentedColormap.from_list(None, plt.cm.Set1(range(0, n)), n)
fig = plt.figure(figsize=(20,8), constrained_layout=True)
gs = fig.add_gridspec(2, 3)
ax1 = fig.add_subplot(gs[0, 0:2])
ax2 = fig.add_subplot(gs[1, 0:2], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[1, -1], sharey = ax1)
ax1.bar(np.arange(m.shape[1]),
np.apply_along_axis(functools.partial(sum_treshold, threshold=t), 0, m))
ax1.set_xlim([0, m.shape[1]])
im = ax2.imshow(m > t, cmap=cmap)
ax3.barh(np.arange(m.shape[0])[::-1],
np.apply_along_axis(functools.partial(sum_treshold, threshold=t), 1, m),
orientation='horizontal')
ax3.set_ylim([0, m.shape[0]])
cbax2=ax2.inset_axes([-0.10,0,0.03,1], transform=ax2.transAxes)
cbar2=fig.colorbar(im, cax=cbax2, format = '%1.2g', orientation='vertical')
由于您已经计算了轴的所有尺寸,因此您可以调整它们以遵循图像纵横比施加的限制。您需要通过更改 height
或更改图形高度来更改 ax0
的高度。如果宽高比在其他方面是错误的,则需要对宽度做类似的事情。
要添加颜色条,您需要从一开始就保留一些space,或者将其放在右上角的空白处。
这是一个示例,现在包括 space 用于左侧的颜色栏,底部移动到绘图的中心。现在子图之间的间距相等。
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
plt.style.use('dark_background')
m = np.random.randn(25, 200).cumsum(axis=0).cumsum(axis=1)
m -= m.min()
m *= 20 / m.max()
# definitions for the axes
left, width = 0.1, 0.65
bottom, height = 0.1, 0.65
spacing = 0.005
width_2 = 0.2
height_1 = 0.2
fig_width, fig_height = 20, 8
aspect_m = m.shape[0] / m.shape[1]
aspect_rect = fig_height * height / (fig_width * width)
if aspect_m < aspect_rect: # either reduce the fig_height, or reduce adapt rectangle height
new_height = aspect_m * (fig_width * width) / fig_height
# optionally increase height_1 and/or increase bottom
bottom += (height - new_height) / 2
height = new_height
else: # similar for the width
width = fig_height * height / fig_width / aspect_m
# optionally increase width_2 and/or increase left
rect0 = [left, bottom, width, height]
rect1 = [left, bottom + height + spacing, width, height_1]
rect2 = [left + width + spacing * fig_height / fig_width, bottom, width_2, height]
rectcbar = [left - 0.06, bottom, 0.01, height]
fig = plt.figure(figsize=(fig_width, fig_height))
ax0 = plt.axes(rect0)
ax0.tick_params(direction='in', top=True, right=True)
ax1 = plt.axes(rect1)
ax1.tick_params(direction='in', labelbottom=False)
ax2 = plt.axes(rect2)
ax2.tick_params(direction='in', labelleft=False)
cbarax = plt.axes(rectcbar)
t = 10
cmap = matplotlib.colors.ListedColormap(['red', 'dodgerblue'])
im = ax0.imshow(m > t, cmap=cmap, origin='lower')
ax0.set_xlabel('image')
ax0.set_ylabel('cluster label')
cbar = fig.colorbar(im, cax=cbarax)
cbar.ax.tick_params(length=0)
cbar.set_ticks([0.25, 0.75])
cbar.ax.set_yticklabels([f'≤ {t}', f'> {t}'])
cbar.ax.set_title('# cells')
ax1.bar(np.arange(m.shape[1]), np.sum(m > t, axis=0))
ax2.barh(np.arange(m.shape[0]), np.sum(m > t, axis=1))
ax0.set_aspect('equal')
ax1.get_shared_x_axes().join(ax1, ax0)
ax2.get_shared_y_axes().join(ax2, ax0)
plt.show()