Python,Matplotlib:在 3D 中沿 z 轴将多个热图堆叠在一起
Python, Matplotlib: Stack multiple heatmaps on top of each other along z-axis in 3D
我有两个热图,我想在 3D 视图中将它们叠加显示。热图是用 imshow() 绘制的,它正确地将数据的每个元素设置为图中的彩色方块。但是,当使用 plot_surface() 绘制相同的热图时,现在每个角代表每个元素。请注意,第一个图形有 15x15 个正方形,每个正方形的中间都有刻度,第二个图形只有 14x14 个正方形,每个正方形的角都有刻度。由于我正在处理离散数据并且对每个 (x,y) 组合感兴趣,因此第二种表示没有意义。
如何才能使 3D 热图的显示方式与 2D 热图的显示方式相同?也就是说,我如何绘制 3D 图,在每个正方形的中间设置 x 和 y 刻度,并正确绘制 15x15 元素? (请注意,热图中的颜色目前与 2D 到 3D 的情况不同也没关系)
代码
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Dummy data
X = range(2, 16+1)
Y = range(2, 16+1)
xs, ys = np.meshgrid(X, Y)
zs1 = np.random.rand(15,15)
zs2 = np.random.rand(15,15)
# Imshow 2D plot
_, (ax1, ax2) = plt.subplots(2,1)
plot = ax1.imshow(np.flip(zs1, 0), cmap=plt.cm.RdYlGn, interpolation='none', extent=[1.5, 16.5, 1.5, 16.5])
plot = ax2.imshow(np.flip(zs2, 0), cmap=plt.cm.RdYlGn, interpolation='none', extent=[1.5, 16.5, 1.5, 16.5])
plt.draw()
# Surface 3D plot
fig = plt.figure()
ax2 = Axes3D(fig)
plot = ax2.plot_surface(xs, ys, zs1, rstride=1, cstride=1,
antialiased=False, linewidth=0, cmap=plt.cm.RdYlGn)
plot = ax2.plot_surface(xs, ys, zs2 + 1000, rstride=1, cstride=1,
antialiased=False, linewidth=0, cmap=plt.cm.RdYlGn)
plt.show()
二维热图。请注意,每个正方形中间有 15x15 个元素和刻度。
3D 热图。请注意,每个方块的角上只有 14x14 的元素和刻度。我希望这些以与 2D 热图相同的方式显示!!
我想你已经理解了核心问题:plot_surface
是为了绘制表面,而不是倾斜的热图。例如,您大幅增加 z 范围以在 3 维中“展平”两个表面,因为这些表面的值分别在 [0, 1] 和 [1000, 1001] 区间内。
因为 plot_surfaces
适用于表面,所以它将您的样本解释为点估计值,然后在点估计值之间进行插值以计算点之间平均表面高度的估计值。因此,一个 15x15 的点估计数组会产生 14x14 的表面,并且 none 的颜色匹配,尽管您应用的是相同的颜色图。如果您觉得这种行为不符合逻辑,我会推荐 famous/infamous 文章 "A pixel is not a little square! A pixel is not a little square! A pixel is not a little square! (A voxel is not a little cube!)" 进一步阅读。
理解了为什么 plot_surfaces
以这种方式处理数据后,很明显,一种解决方案是对数据进行上采样:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Dummy data
zs1 = np.random.rand(15,15)
zs2 = np.random.rand(15,15)
# Imshow 2D plot
fig, (ax1, ax2) = plt.subplots(2,1)
plot = ax1.imshow(np.flip(zs1, 0), cmap=plt.cm.RdYlGn, interpolation='none', extent=[1.5, 16.5, 1.5, 16.5], vmin=0, vmax=1)
plot = ax2.imshow(np.flip(zs2, 0), cmap=plt.cm.RdYlGn, interpolation='none', extent=[1.5, 16.5, 1.5, 16.5], vmin=0, vmax=1)
plt.draw()
# Surface 3D plot
upsample_by = 20
X = np.linspace(2, 16, 15*upsample_by)
Y = np.linspace(2, 16, 15*upsample_by)
xs, ys = np.meshgrid(X, Y)
zs1 = np.repeat(np.repeat(zs1, upsample_by, axis=0), upsample_by, axis=1)
zs2 = np.repeat(np.repeat(zs2, upsample_by, axis=0), upsample_by, axis=1)
fig3 = plt.figure()
ax3 = Axes3D(fig3)
plot = ax3.plot_surface(xs, ys, zs1, rstride=1, cstride=1,
antialiased=False, linewidth=0, cmap=plt.cm.RdYlGn, vmin=0, vmax=1)
plot = ax3.plot_surface(xs, ys, zs2 + 1000, rstride=1, cstride=1,
antialiased=False, linewidth=0, cmap=plt.cm.RdYlGn, vmin=1000, vmax=1001)
plt.show()
我不建议这样做,因为总而言之,您的解决方案非常老套,而我的调整只会让情况变得更糟。就个人而言,我会将每个单独的像素绘制为 3D 中的一个小方块,并适当着色。 This matplotlib tutorial 演示了如何使用 3-D 投影向轴添加 2-D 补丁。
基于此,我们可以编写一个小函数,在定义的高度绘制 3D 热图:
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import mpl_toolkits.mplot3d.art3d as art3d
def tilted_heatmap_in_3d(arr, z, cmap=plt.cm.RdYlGn, ax=None):
if ax is None:
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
for ii, row in enumerate(arr):
for jj, value in enumerate(row):
r = Rectangle((ii-0.5, jj-0.5), 1, 1, color=cmap(value))
ax.add_patch(r)
art3d.pathpatch_2d_to_3d(r, z=z, zdir="z")
ax.set_xlim(-1, ii+1)
ax.set_ylim(-1, jj+1)
ax.set_zlim(0, 2*z)
ax.get_figure().canvas.draw()
if __name__ == '__main__':
tilted_heatmap_in_3d(np.random.rand(15, 15), z=5)
plt.show()
我有两个热图,我想在 3D 视图中将它们叠加显示。热图是用 imshow() 绘制的,它正确地将数据的每个元素设置为图中的彩色方块。但是,当使用 plot_surface() 绘制相同的热图时,现在每个角代表每个元素。请注意,第一个图形有 15x15 个正方形,每个正方形的中间都有刻度,第二个图形只有 14x14 个正方形,每个正方形的角都有刻度。由于我正在处理离散数据并且对每个 (x,y) 组合感兴趣,因此第二种表示没有意义。
如何才能使 3D 热图的显示方式与 2D 热图的显示方式相同?也就是说,我如何绘制 3D 图,在每个正方形的中间设置 x 和 y 刻度,并正确绘制 15x15 元素? (请注意,热图中的颜色目前与 2D 到 3D 的情况不同也没关系)
代码
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Dummy data
X = range(2, 16+1)
Y = range(2, 16+1)
xs, ys = np.meshgrid(X, Y)
zs1 = np.random.rand(15,15)
zs2 = np.random.rand(15,15)
# Imshow 2D plot
_, (ax1, ax2) = plt.subplots(2,1)
plot = ax1.imshow(np.flip(zs1, 0), cmap=plt.cm.RdYlGn, interpolation='none', extent=[1.5, 16.5, 1.5, 16.5])
plot = ax2.imshow(np.flip(zs2, 0), cmap=plt.cm.RdYlGn, interpolation='none', extent=[1.5, 16.5, 1.5, 16.5])
plt.draw()
# Surface 3D plot
fig = plt.figure()
ax2 = Axes3D(fig)
plot = ax2.plot_surface(xs, ys, zs1, rstride=1, cstride=1,
antialiased=False, linewidth=0, cmap=plt.cm.RdYlGn)
plot = ax2.plot_surface(xs, ys, zs2 + 1000, rstride=1, cstride=1,
antialiased=False, linewidth=0, cmap=plt.cm.RdYlGn)
plt.show()
二维热图。请注意,每个正方形中间有 15x15 个元素和刻度。
3D 热图。请注意,每个方块的角上只有 14x14 的元素和刻度。我希望这些以与 2D 热图相同的方式显示!!
我想你已经理解了核心问题:plot_surface
是为了绘制表面,而不是倾斜的热图。例如,您大幅增加 z 范围以在 3 维中“展平”两个表面,因为这些表面的值分别在 [0, 1] 和 [1000, 1001] 区间内。
因为 plot_surfaces
适用于表面,所以它将您的样本解释为点估计值,然后在点估计值之间进行插值以计算点之间平均表面高度的估计值。因此,一个 15x15 的点估计数组会产生 14x14 的表面,并且 none 的颜色匹配,尽管您应用的是相同的颜色图。如果您觉得这种行为不符合逻辑,我会推荐 famous/infamous 文章 "A pixel is not a little square! A pixel is not a little square! A pixel is not a little square! (A voxel is not a little cube!)" 进一步阅读。
理解了为什么 plot_surfaces
以这种方式处理数据后,很明显,一种解决方案是对数据进行上采样:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Dummy data
zs1 = np.random.rand(15,15)
zs2 = np.random.rand(15,15)
# Imshow 2D plot
fig, (ax1, ax2) = plt.subplots(2,1)
plot = ax1.imshow(np.flip(zs1, 0), cmap=plt.cm.RdYlGn, interpolation='none', extent=[1.5, 16.5, 1.5, 16.5], vmin=0, vmax=1)
plot = ax2.imshow(np.flip(zs2, 0), cmap=plt.cm.RdYlGn, interpolation='none', extent=[1.5, 16.5, 1.5, 16.5], vmin=0, vmax=1)
plt.draw()
# Surface 3D plot
upsample_by = 20
X = np.linspace(2, 16, 15*upsample_by)
Y = np.linspace(2, 16, 15*upsample_by)
xs, ys = np.meshgrid(X, Y)
zs1 = np.repeat(np.repeat(zs1, upsample_by, axis=0), upsample_by, axis=1)
zs2 = np.repeat(np.repeat(zs2, upsample_by, axis=0), upsample_by, axis=1)
fig3 = plt.figure()
ax3 = Axes3D(fig3)
plot = ax3.plot_surface(xs, ys, zs1, rstride=1, cstride=1,
antialiased=False, linewidth=0, cmap=plt.cm.RdYlGn, vmin=0, vmax=1)
plot = ax3.plot_surface(xs, ys, zs2 + 1000, rstride=1, cstride=1,
antialiased=False, linewidth=0, cmap=plt.cm.RdYlGn, vmin=1000, vmax=1001)
plt.show()
我不建议这样做,因为总而言之,您的解决方案非常老套,而我的调整只会让情况变得更糟。就个人而言,我会将每个单独的像素绘制为 3D 中的一个小方块,并适当着色。 This matplotlib tutorial 演示了如何使用 3-D 投影向轴添加 2-D 补丁。
基于此,我们可以编写一个小函数,在定义的高度绘制 3D 热图:
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import mpl_toolkits.mplot3d.art3d as art3d
def tilted_heatmap_in_3d(arr, z, cmap=plt.cm.RdYlGn, ax=None):
if ax is None:
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
for ii, row in enumerate(arr):
for jj, value in enumerate(row):
r = Rectangle((ii-0.5, jj-0.5), 1, 1, color=cmap(value))
ax.add_patch(r)
art3d.pathpatch_2d_to_3d(r, z=z, zdir="z")
ax.set_xlim(-1, ii+1)
ax.set_ylim(-1, jj+1)
ax.set_zlim(0, 2*z)
ax.get_figure().canvas.draw()
if __name__ == '__main__':
tilted_heatmap_in_3d(np.random.rand(15, 15), z=5)
plt.show()