简体   繁体   English

Python、Matplotlib:在 3D 中沿 z 轴堆叠多个热图

[英]Python, Matplotlib: Stack multiple heatmaps on top of each other along z-axis in 3D

I have two heatmaps that I want to display on top of each other in a 3D view.我有两个要在 3D 视图中叠加显示的热图。 The heatmaps are plotted with imshow(), which correctly sets each element of the data as a colored square in the plot.热图是用 imshow() 绘制的,它正确地将数据的每个元素设置为 plot 中的彩色方块。 However, when plotting the same heatmaps with plot_surface() each corner now instead represents each element.但是,当使用 plot_surface() 绘制相同的热图时,现在每个角都代表每个元素。 Note that the first figure has 15x15 squares and ticks at the middle of each square, and that the second figure only has 14x14 squares and ticks in each square's corner.请注意,第一个图有 15x15 的方格,每个方格的中间都有刻度,第二个图只有 14x14 的方格,每个方格的角落都有刻度。 Since I'm working with discrete data and am interested in each (x,y) combination, the second representation doesn't make sense.由于我正在处理离散数据并且对每个 (x,y) 组合感兴趣,因此第二个表示没有意义。

How can I make it so that the heatmaps in 3D are displayed in the same way as the 2D heatmaps?如何使 3D 中的热图以与 2D 热图相同的方式显示? That is, how can I plot a 3D plot that sets x and y ticks in the middle of each square, and correctly plots 15x15 elements?也就是说,我怎样才能 plot a 3D plot 在每个正方形中间设置 x 和 y 刻度,并正确绘制 15x15 元素? (Note that it's fine that the colors in the heatmaps currently differ from the 2D to 3D case) (请注意,热图中的 colors 目前与 2D 到 3D 的情况不同,这很好)

Code代码

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()

二维热图

The 2D heatmaps.二维热图。 Note that there are 15x15 elements and ticks in the middle of each square.请注意,每个正方形的中间有 15x15 个元素和刻度。

3D 热图

The 3D heatmaps. 3D 热图。 Note that there are only 14x14 elements and ticks in each square's corner.请注意,每个正方形的角只有 14x14 个元素和刻度。 I want these to be display in the same way as the 2D heatmaps!!我希望这些以与 2D 热图相同的方式显示!!

I think you have understood the core problem: plot_surface is meant to plot surfaces, not tilted heatmaps.我想你已经理解了核心问题: plot_surface是指 plot 表面,而不是倾斜的热图。 For example, you increase the z-range massively to "flatten" the two surfaces in 3 dimensions, as the surfaces then have values in the intervals [0, 1] and [1000, 1001], respectively.例如,您大幅增加 z 范围以在 3 个维度上“展平”两个曲面,因为这些曲面的值分别在 [0, 1] 和 [1000, 1001] 区间内。

Because plot_surfaces is meant for, well, surfaces, it interprets your samples as point estimates and then interpolates between point estimates to compute an estimate of the average surface height between the points.因为plot_surfaces是用于表面的,它会将您的样本解释为点估计值,然后在点估计值之间进行插值以计算点之间平均表面高度的估计值。 Hence a 15x15 array of point estimates results in 14x14 surfaces, and none of the colors match although you are applying the same colormap.因此,一个 15x15 的点估计数组会产生 14x14 的表面,尽管您正在应用相同的颜色图,但 colors 都不匹配。 I would recommend the famous/infamous essay "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!)" for further reading if this behaviour does not seem logical to you.如果有这种行为,我会推荐著名/臭名昭著的文章“像素不是小正方形!像素不是小正方形!像素不是小正方形!(体素不是小立方体!)”以供进一步阅读对你来说似乎不合逻辑。

Having understood why plot_surfaces handles the data the way it does, it becomes clear that one solution would be to upsample your data:了解了为什么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()

在此处输入图像描述

在此处输入图像描述

I don't recommend doing this, as all in all, your solution is pretty hacky and my tweak only makes it worse.我不建议这样做,因为总而言之,您的解决方案非常老套,我的调整只会让情况变得更糟。 Personally, I would draw each individual pixel as a little square in 3D, appropriately colored.就个人而言,我会将每个单独的像素绘制为 3D 中的一个小正方形,并适当着色。 This matplotlib tutorial demonstrates how to add 2-D patches to axes with 3-D projections. 此 matplotlib 教程演示了如何使用 3-D 投影向轴添加 2-D 补丁。

Based on this, we can write a little function that draws heatmaps in 3D at defined heights:基于此,我们可以编写一个小 function 来绘制 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()

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM