【问题标题】:Python, Matplotlib: Stack multiple heatmaps on top of each other along z-axis in 3DPython,Matplotlib:在 3D 中沿 z 轴堆叠多个热图
【发布时间】:2021-07-22 23:45:15
【问题描述】:

我有两个要在 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()

2D 热图。请注意,每个正方形的中间有 15x15 个元素和刻度。

3D 热图。请注意,每个正方形的角上只有 14x14 个元素和刻度。我希望这些以与 2D 热图相同的方式显示!!

【问题讨论】:

    标签: python matplotlib plot heatmap


    【解决方案1】:

    我想你已经理解了核心问题:plot_surface 用于绘制表面,而不是倾斜的热图。例如,您大幅增加 z 范围以在 3 个维度上“展平”两个曲面,因为这些曲面的值分别在 [0, 1] 和 [1000, 1001] 区间内。

    因为plot_surfaces 是用于表面的,它会将您的样本解释为点估计值,然后在点估计值之间进行插值以计算点之间平均表面高度的估计值。因此,一个 15x15 的点估计数组会产生 14x14 的表面,尽管您正在应用相同的颜色图,但没有任何颜色匹配。如果您觉得这种行为不合逻辑,我会推荐著名/臭名昭著的文章 "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 演示了如何将 2-D 补丁添加到具有 3-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()
    

    【讨论】:

    • 是的,这是一种更好的方法。非常感谢
    猜你喜欢
    • 1970-01-01
    • 2012-12-09
    • 1970-01-01
    • 2020-11-09
    • 2016-04-21
    • 2021-01-19
    • 2019-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多