使用 matplotlib 散点图绘制 3D Numpy 数组

Scatter plotting 3D Numpy array using matplotlib

我有一个数值大小为 (75, 150, 150) 的 3D numpy 数组,它表示 75 个层,x 方向有 150 个网格单元,y 方向有 150 个网格单元。有没有办法在 x y 和 z 3D 地图中绘制此 3D 数组,其中 z 维度代表我模型的层,x 和 y 是 x 和 y 维度?我有以下代码,但它给我错误:ValueError: shape mismatch: objects cannot be broadcast to a single shape.

array.shape = (75, 150, 150)

from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
n = 1687500

z=np.arange(76)
x=np.arange(151)
y=np.arange(151)

for i, j, k in x, y, z:
  y=array[k,i, :]
  x=array[k,:, j]
  colors = np.random.randint(0, 10, size=n)
  ax.scatter(z, x, y, c=colors, marker='o')
  plt.show()

您有一个包含 75*150*150 (1687500) 个元素的数组。在散点图的第一个版本中,您使用一列 75 个元素作为 Z,两列 150 个值作为 X 和 Y。您几乎忽略了 3D 数组中的所有信息。

在编辑后的版本中,x、y 和 z 均包含完整的 3D 数组。所有值都将相同。

假设数组包含75层150x150的网格,你可以绘制如下。请注意,由于点数很大,因此速度会很慢。而且,很难理解。 np.meshgrid 创建位置值数组。

要过滤掉零值,您可以将它们替换为np.nan。要根据 bins 着色,您可以使用 BoundaryNorm.

from matplotlib import pyplot as plt
from matplotlib.colors import BoundaryNorm
import numpy as np

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

num_layers = 75
num_pnt = 150
z, x, y = np.meshgrid(np.arange(1, num_layers + 1), np.arange(num_pnt), np.arange(num_pnt), indexing='ij')

# create some random test data, suppose all values outside a cone are zero
array = np.random.rand(num_layers, num_pnt, num_pnt) ** 2
array[(x - num_pnt / 2) ** 2 + (y - num_pnt / 2) ** 2 > (num_layers - z) ** 2] = 0

array[array == 0] = np.nan  # replace zeros by NaN to make them invisible

bounds = [0, 0.0001, 0.001, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10, 1.00]
norm = BoundaryNorm(bounds, len(bounds) - 1)
cmap = plt.get_cmap('turbo', len(bounds) - 1)
scat = ax.scatter(x, y, z, c=array, marker='o', cmap=cmap, norm=norm)
cbar = plt.colorbar(scat, ax=ax, ticks=bounds, format='%.4f')
plt.show()

当有很多零时,有助于将它们完全过滤掉。您需要将所有数组转换为一维数组:

from matplotlib import pyplot as plt
from matplotlib.colors import BoundaryNorm
from matplotlib.ticker import FuncFormatter
import numpy as np

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

num_layers = 75
num_pnt = 150
z, x, y = np.meshgrid(np.arange(1, num_layers + 1), np.arange(num_pnt), np.arange(num_pnt), indexing='ij')

# create some random test data, suppose all values outside a cone are zero
array = np.random.rand(num_layers, num_pnt, num_pnt) ** 2
array[np.abs((x - num_pnt / 2) ** 2 + (y - num_pnt / 2) ** 2 - (num_layers - z) ** 2) > 5] = 0

# make the arrays 1D, so they are easier to filter
array = array.ravel()
filter = array != 0
x = x.ravel()[filter]
y = y.ravel()[filter]
z = z.ravel()[filter]
array = array[filter]

bounds = [0, 0.0001, 0.001, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10, 1.00]
norm = BoundaryNorm(bounds, len(bounds) - 1)
cmap = plt.get_cmap('turbo', len(bounds) - 1)
scat = ax.scatter(x, y, z, c=array, marker='o', cmap=cmap, norm=norm)
cbar = plt.colorbar(scat, ax=ax, ticks=bounds, format=FuncFormatter(lambda x, pos: f'{x * 100:3g} %'))
plt.show()