matplotlib 中的 3D 离散热图
Posted
技术标签:
【中文标题】matplotlib 中的 3D 离散热图【英文标题】:3D discrete heatmap in matplotlib 【发布时间】:2017-04-12 17:19:03 【问题描述】:我在python中有一个包含3维数据的元组列表,其中每个元组的形式为:(x,y,z,data_value),即,我在每个(x,y,z)处都有数据值协调。我想制作一个 3D 离散热图,其中颜色代表我的元组列表中 data_values 的值。在这里,我给出了一个二维数据集的热图示例,其中我有一个 (x, y, data_value) 元组列表:
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
from random import randint
# x and y coordinates
x = np.array(range(10))
y = np.array(range(10,15))
data = np.zeros((len(y),len(x)))
# Generate some discrete data (1, 2 or 3) for each (x, y) pair
for i,yy in enumerate(y):
for j, xx in enumerate(x):
data[i,j] = randint(1,3)
# Map 1, 2 and 3 to 'Red', 'Green' qnd 'Blue', respectively
colormap = colors.ListedColormap(['Red', 'Green', 'Blue'])
colorbar_ticklabels = ['1', '2', '3']
# Use matshow to create a heatmap
fig, ax = plt.subplots()
ms = ax.matshow(data, cmap = colormap, vmin=data.min() - 0.5, vmax=data.max() + 0.5, origin = 'lower')
# x and y axis ticks
ax.set_xticklabels([str(xx) for xx in x])
ax.set_yticklabels([str(yy) for yy in y])
ax.xaxis.tick_bottom()
# Put the x- qnd y-axis ticks at the middle of each cell
ax.set_xticks(np.arange(data.shape[1]), minor = False)
ax.set_yticks(np.arange(data.shape[0]), minor = False)
# Set custom ticks and ticklabels for color bar
cbar = fig.colorbar(ms,ticks = np.arange(np.min(data),np.max(data)+1))
cbar.ax.set_yticklabels(colorbar_ticklabels)
plt.show()
这会生成如下图:
如果我的数据具有第三维,我如何在 3D 空间中制作类似的图(即具有 z 轴)。例如,如果
# x and y and z coordinates
x = np.array(range(10))
y = np.array(range(10,15))
z = np.array(range(15,20))
data = np.zeros((len(y),len(x), len(y)))
# Generate some random discrete data (1, 2 or 3) for each (x, y, z) triplet.
# Am I defining i, j and k correctly here?
for i,yy in enumerate(y):
for j, xx in enumerate(x):
for k, zz in enumerate(z):
data[i,j, k] = randint(1,3)
我听起来plot_surface in mplot3d应该可以做到这一点,但是这个函数的输入中的z本质上是数据在(x, y)坐标处的值,即(x, y, z = data_value),这与我所拥有的不同,即(x,y,z,data_value)。
【问题讨论】:
您想要一个 3d 表面,其中绘图的颜色是 x、y 和 z 的函数? 正确!不过有一个离散的颜色条。 您可能想查看mayavi
的contour3d
,它允许您在 3d 中绘制标量场的等值面。
如果您不介意语言转换和许可问题,Mathematica 内置了Image3D
、ContourPlot3D
Mathematica 中的 Image3D 听起来很像我想要的。我只是希望在 python/matplotlib 中有一个等价物。
【参考方案1】:
新答案:
看来我们真的很想在这里有一款 3D 俄罗斯方块游戏 ;-)
所以这里有一种方法可以绘制不同颜色的立方体来填充数组(x,y,z)
给定的空间。
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm
import matplotlib.colorbar
import matplotlib.colors
def cuboid_data(center, size=(1,1,1)):
# code taken from
# http://***.com/questions/30715083/python-plotting-a-wireframe-3d-cuboid?noredirect=1&lq=1
# suppose axis direction: x: to left; y: to inside; z: to upper
# get the (left, outside, bottom) point
o = [a - b / 2 for a, b in zip(center, size)]
# get the length, width, and height
l, w, h = size
x = [[o[0], o[0] + l, o[0] + l, o[0], o[0]], # x coordinate of points in bottom surface
[o[0], o[0] + l, o[0] + l, o[0], o[0]], # x coordinate of points in upper surface
[o[0], o[0] + l, o[0] + l, o[0], o[0]], # x coordinate of points in outside surface
[o[0], o[0] + l, o[0] + l, o[0], o[0]]] # x coordinate of points in inside surface
y = [[o[1], o[1], o[1] + w, o[1] + w, o[1]], # y coordinate of points in bottom surface
[o[1], o[1], o[1] + w, o[1] + w, o[1]], # y coordinate of points in upper surface
[o[1], o[1], o[1], o[1], o[1]], # y coordinate of points in outside surface
[o[1] + w, o[1] + w, o[1] + w, o[1] + w, o[1] + w]] # y coordinate of points in inside surface
z = [[o[2], o[2], o[2], o[2], o[2]], # z coordinate of points in bottom surface
[o[2] + h, o[2] + h, o[2] + h, o[2] + h, o[2] + h], # z coordinate of points in upper surface
[o[2], o[2], o[2] + h, o[2] + h, o[2]], # z coordinate of points in outside surface
[o[2], o[2], o[2] + h, o[2] + h, o[2]]] # z coordinate of points in inside surface
return x, y, z
def plotCubeAt(pos=(0,0,0), c="b", alpha=0.1, ax=None):
# Plotting N cube elements at position pos
if ax !=None:
X, Y, Z = cuboid_data( (pos[0],pos[1],pos[2]) )
ax.plot_surface(X, Y, Z, color=c, rstride=1, cstride=1, alpha=0.1)
def plotMatrix(ax, x, y, z, data, cmap="jet", cax=None, alpha=0.1):
# plot a Matrix
norm = matplotlib.colors.Normalize(vmin=data.min(), vmax=data.max())
colors = lambda i,j,k : matplotlib.cm.ScalarMappable(norm=norm,cmap = cmap).to_rgba(data[i,j,k])
for i, xi in enumerate(x):
for j, yi in enumerate(y):
for k, zi, in enumerate(z):
plotCubeAt(pos=(xi, yi, zi), c=colors(i,j,k), alpha=alpha, ax=ax)
if cax !=None:
cbar = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap,
norm=norm,
orientation='vertical')
cbar.set_ticks(np.unique(data))
# set the colorbar transparent as well
cbar.solids.set(alpha=alpha)
if __name__ == '__main__':
# x and y and z coordinates
x = np.array(range(10))
y = np.array(range(10,15))
z = np.array(range(15,20))
data_value = np.random.randint(1,4, size=(len(x), len(y), len(z)) )
print data_value.shape
fig = plt.figure(figsize=(10,4))
ax = fig.add_axes([0.1, 0.1, 0.7, 0.8], projection='3d')
ax_cb = fig.add_axes([0.8, 0.3, 0.05, 0.45])
ax.set_aspect('equal')
plotMatrix(ax, x, y, z, data_value, cmap="jet", cax = ax_cb)
plt.savefig(__file__+".png")
plt.show()
我发现在这里很难看到任何东西,但这可能是一个品味问题,现在希望也能回答这个问题。
原答案:
看来我误解了这个问题。因此,以下不回答问题。暂时我把它留在这里,以保持下面的 cmets 可供其他人使用。
我认为plot_surface
适合指定的任务。
基本上,您将绘制一个具有由点 X,Y,Z
在 3D 中给出的形状的表面,并使用来自 data_values
的值对其进行着色,如下面的代码所示。
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
# as plot_surface needs 2D arrays as input
x = np.arange(10)
y = np.array(range(10,15))
# we make a meshgrid from the x,y data
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
# data_value shall be represented by color
data_value = np.random.rand(len(y), len(x))
# map the data to rgba values from a colormap
colors = cm.ScalarMappable(cmap = "viridis").to_rgba(data_value)
# plot_surface with points X,Y,Z and data_value as colors
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=colors,
linewidth=0, antialiased=True)
plt.show()
【讨论】:
这不是我想要的。在您的示例中,每个 (x,y) 对的 z 只有一个值。在我的例子中,z 是一个独立的坐标,即对于每个 (x,y) 对,我将拥有 z 的所有可能值(而不仅仅是单个值)。我的情况是,我遗漏了一些东西,如果您使用我在示例代码中提供的 3D 数据修改您的示例,这将非常有帮助。 @user3076813 我可能完全误解了你的问题。但在那种情况下,我认为你首先需要定义你的映射。我确实是从 R x R -> R (2D -> 1D 映射)映射的。如果你真的想要一个 R x R x R -> R 映射,结果图应该是什么样子?一大堆半透明的立方体?一旦你定义了它,我们可以看看 matplotlib 是否可行。 我实际上想要一个 R x R x R --> Z+ 映射(其中 Z+ 是非负整数的集合)。对于我上面给出的 2D 示例,每个 (x, y) 点都有一个彩色方块。对于 3D 情况,我希望每个 (x,y,z) 点都有一个(如果可能的话是半透明的)彩色立方体。 好的,令人困惑的是,您在 cmets 中用“正确”回答了“您想要一个 3d 表面 [...]”的问题。在某种程度上,我非常怀疑你想要的实际上是提供一个信息丰富的情节。 很抱歉给您带来了困惑。我想我误解了这个问题。那么,没有办法在python中做这样的事情吗?有什么建议可以制作更丰富的情节吗?【参考方案2】:我已经更新了上面的代码以兼容新版本的 matplot lib。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colorbar
from matplotlib import cm
viridis = cm.get_cmap('plasma', 8) #Our color map
def cuboid_data(center, size=(1,1,1)):
# code taken from
# http://***.com/questions/30715083/python-plotting-a-wireframe-3d-cuboid?noredirect=1&lq=1
# suppose axis direction: x: to left; y: to inside; z: to upper
# get the (left, outside, bottom) point
o = [a - b / 2 for a, b in zip(center, size)]
# get the length, width, and height
l, w, h = size
x = np.array([[o[0], o[0] + l, o[0] + l, o[0], o[0]], # x coordinate of points in bottom surface
[o[0], o[0] + l, o[0] + l, o[0], o[0]], # x coordinate of points in upper surface
[o[0], o[0] + l, o[0] + l, o[0], o[0]], # x coordinate of points in outside surface
[o[0], o[0] + l, o[0] + l, o[0], o[0]]]) # x coordinate of points in inside surface
y = np.array([[o[1], o[1], o[1] + w, o[1] + w, o[1]], # y coordinate of points in bottom surface
[o[1], o[1], o[1] + w, o[1] + w, o[1]], # y coordinate of points in upper surface
[o[1], o[1], o[1], o[1], o[1]], # y coordinate of points in outside surface
[o[1] + w, o[1] + w, o[1] + w, o[1] + w, o[1] + w]]) # y coordinate of points in inside surface
z = np.array([[o[2], o[2], o[2], o[2], o[2]], # z coordinate of points in bottom surface
[o[2] + h, o[2] + h, o[2] + h, o[2] + h, o[2] + h], # z coordinate of points in upper surface
[o[2], o[2], o[2] + h, o[2] + h, o[2]], # z coordinate of points in outside surface
[o[2], o[2], o[2] + h, o[2] + h, o[2]]]) # z coordinate of points in inside surface
return x, y, z
def plotCubeAt(pos=(0,0,0), c="b", alpha=0.1, ax=None):
# Plotting N cube elements at position pos
if ax !=None:
X, Y, Z = cuboid_data( (pos[0],pos[1],pos[2]) )
ax.plot_surface(X, Y, Z, color=c, rstride=1, cstride=1, alpha=0.1)
def plotMatrix(ax, x, y, z, data, cmap=viridis, cax=None, alpha=0.1):
# plot a Matrix
norm = matplotlib.colors.Normalize(vmin=data.min(), vmax=data.max())
colors = lambda i,j,k : matplotlib.cm.ScalarMappable(norm=norm,cmap = cmap).to_rgba(data[i,j,k])
for i, xi in enumerate(x):
for j, yi in enumerate(y):
for k, zi, in enumerate(z):
plotCubeAt(pos=(xi, yi, zi), c=colors(i,j,k), alpha=alpha, ax=ax)
if cax !=None:
cbar = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap,
norm=norm,
orientation='vertical')
cbar.set_ticks(np.unique(data))
# set the colorbar transparent as well
cbar.solids.set(alpha=alpha)
if __name__ == '__main__':
# x and y and z coordinates
x = np.array(range(10))
y = np.array(range(10,15))
z = np.array(range(15,20))
data_value = np.random.randint(1,4, size=(len(x), len(y), len(z)) )
print(data_value.shape)
fig = plt.figure(figsize=(10,4))
ax = fig.add_axes([0.1, 0.1, 0.7, 0.8], projection='3d')
ax_cb = fig.add_axes([0.8, 0.3, 0.05, 0.45])
ax.set_aspect('auto')
plotMatrix(ax, x, y, z, data_value, cmap=viridis, cax = ax_cb)
plt.savefig(__file__+".png")
plt.show()
【讨论】:
以上是关于matplotlib 中的 3D 离散热图的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 matplotlib 在 python 中绘制 3D 密度图