在matplotlib的3d图中将箭头放在矢量上

28 python plot matplotlib

我绘制了一些3D数据的特征向量,并想知道目前(已经)是否有办法将箭头放在线上?如果有人给我一个提示,那会很棒.在此输入图像描述

import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

####################################################
# This part is just for reference if
# you are interested where the data is
# coming from
# The plot is at the bottom
#####################################################

# Generate some example data
mu_vec1 = np.array([0,0,0])
cov_mat1 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class1_sample = np.random.multivariate_normal(mu_vec1, cov_mat1, 20)

mu_vec2 = np.array([1,1,1])
cov_mat2 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class2_sample = np.random.multivariate_normal(mu_vec2, cov_mat2, 20)

# concatenate data for PCA
samples = np.concatenate((class1_sample, class2_sample), axis=0)

# mean values
mean_x = mean(samples[:,0])
mean_y = mean(samples[:,1])
mean_z = mean(samples[:,2])

#eigenvectors and eigenvalues
eig_val, eig_vec = np.linalg.eig(cov_mat)

################################
#plotting eigenvectors
################################    

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

ax.plot(samples[:,0], samples[:,1], samples[:,2], 'o', markersize=10, color='green', alpha=0.2)
ax.plot([mean_x], [mean_y], [mean_z], 'o', markersize=10, color='red', alpha=0.5)
for v in eig_vec:
    ax.plot([mean_x, v[0]], [mean_y, v[1]], [mean_z, v[2]], color='red', alpha=0.8, lw=3)
ax.set_xlabel('x_values')
ax.set_ylabel('y_values')
ax.set_zlabel('z_values')

plt.title('Eigenvectors')

plt.draw()
plt.show()
Run Code Online (Sandbox Code Playgroud)

CT *_*Zhu 47

要将箭头补丁添加到3D绘图中,简单的解决方案是使用中FancyArrowPatch定义的类/matplotlib/patches.py.然而,它仅适用于2D情节(在写作的时候),因为它posAposB应该是长度为2元组.

因此,我们创建一个新的箭头补丁类,命名它Arrow3D,继承自FancyArrowPatch.我们唯一需要覆盖它posAposB.要做到这一点,我们开始Arrow3dposAposB(0,0)秒.然后使用3D将3D坐标xs, ys, zs从3D投影到2D proj3d.proj_transform(),并且将得到的2D坐标分配给posAposB使用.set_position()方法,替换(0,0)s.这样我们就可以使用3D箭头了.

投影步骤进入.draw方法,该方法将覆盖对象的.draw方法FancyArrowPatch.

这可能看起来像一个黑客.然而,mplot3d目前仅提供(再次)仅通过提供3D-2D投影的简单3D绘图能力,并且基本上完成2D中的所有绘图,其不是真正的3D.

import numpy as np
from numpy import *
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import proj3d

class Arrow3D(FancyArrowPatch):
    def __init__(self, xs, ys, zs, *args, **kwargs):
        FancyArrowPatch.__init__(self, (0,0), (0,0), *args, **kwargs)
        self._verts3d = xs, ys, zs

    def draw(self, renderer):
        xs3d, ys3d, zs3d = self._verts3d
        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
        self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))
        FancyArrowPatch.draw(self, renderer)

####################################################
# This part is just for reference if
# you are interested where the data is
# coming from
# The plot is at the bottom
#####################################################

# Generate some example data
mu_vec1 = np.array([0,0,0])
cov_mat1 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class1_sample = np.random.multivariate_normal(mu_vec1, cov_mat1, 20)

mu_vec2 = np.array([1,1,1])
cov_mat2 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class2_sample = np.random.multivariate_normal(mu_vec2, cov_mat2, 20)
Run Code Online (Sandbox Code Playgroud)

实际绘图.请注意,我们只需更改代码的一行,即添加新的箭头艺术家:

# concatenate data for PCA
samples = np.concatenate((class1_sample, class2_sample), axis=0)

# mean values
mean_x = mean(samples[:,0])
mean_y = mean(samples[:,1])
mean_z = mean(samples[:,2])

#eigenvectors and eigenvalues
eig_val, eig_vec = np.linalg.eig(cov_mat1)

################################
#plotting eigenvectors
################################    

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

ax.plot(samples[:,0], samples[:,1], samples[:,2], 'o', markersize=10, color='g', alpha=0.2)
ax.plot([mean_x], [mean_y], [mean_z], 'o', markersize=10, color='red', alpha=0.5)
for v in eig_vec:
    #ax.plot([mean_x,v[0]], [mean_y,v[1]], [mean_z,v[2]], color='red', alpha=0.8, lw=3)
    #I will replace this line with:
    a = Arrow3D([mean_x, v[0]], [mean_y, v[1]], 
                [mean_z, v[2]], mutation_scale=20, 
                lw=3, arrowstyle="-|>", color="r")
    ax.add_artist(a)
ax.set_xlabel('x_values')
ax.set_ylabel('y_values')
ax.set_zlabel('z_values')

plt.title('Eigenvectors')

plt.draw()
plt.show()
Run Code Online (Sandbox Code Playgroud)

final_output

有关更多详细信息,请查看此帖子,该帖子启发了此问题.

  • 遗憾的是,编辑队列目前已满;要添加对 matplotlib>=3.5 的兼容性,请在类定义中添加 `do_3d_projection = draw` 别名。 (2认同)
  • @mueslo 我尝试让这段代码在较新版本的 matplotlib 中运行。那么我到底要在哪里添加这行代码呢?它是类属性还是实例属性?它是指 self.draw() 还是 plt.draw() ? (2认同)

Mat*_*att 16

另一种选择:您也可以使用该plt.quiver函数,它允许您非常轻松地生成箭头向量,而无需任何额外的导入或类。

要复制您的示例,您将替换:

for v in eig_vec:
    ax.plot([mean_x, v[0]], [mean_y, v[1]], [mean_z, v[2]], color='red', alpha=0.8, lw=3)
Run Code Online (Sandbox Code Playgroud)

和:

for v in eig_vec:
    ax.quiver(
        mean_x, mean_y, mean_z, # <-- starting point of vector
        v[0] - mean_x, v[1] - mean_y, v[2] - mean_z, # <-- directions of vector
        color = 'red', alpha = .8, lw = 3,
    )
Run Code Online (Sandbox Code Playgroud)


Rul*_*uli 12

新版本的 matplotlib 会抛出AttributeError: 'Arrow3D' object has no attribute 'do_3d_projection'旧定义Arrow3D。这里有几条评论询问,但仍然不清楚。你必须添加 function do_3d_projection(),而draw()不再需要了。当前代码如下所示:

import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import proj3d

class Arrow3D(FancyArrowPatch):
    def __init__(self, xs, ys, zs, *args, **kwargs):
        super().__init__((0,0), (0,0), *args, **kwargs)
        self._verts3d = xs, ys, zs

    def do_3d_projection(self, renderer=None):
        xs3d, ys3d, zs3d = self._verts3d
        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)
        self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))

        return np.min(zs)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
arrow_prop_dict = dict(mutation_scale=20, arrowstyle='-|>', color='k', shrinkA=0, shrinkB=0)
a = Arrow3D([0, 10], [0, 0], [0, 0], **arrow_prop_dict)
ax.add_artist(a)

plt.show()
Run Code Online (Sandbox Code Playgroud)

帮助来自github