为什么更改matplotlib的Axes3D.plot()和.scatter()方法的输入会有不同的行为?

2024-09-30 20:30:55 发布

您现在位置:Python中文网/ 问答频道 /正文

在发布在问题How can I draw a multiple 3d-curves picture by Python?中的代码中,plot方法被调用了两次,由于要绘制的点没有重置,所以这些行被淹没在另一个上面。但是如果我们尝试使用分散法,我们可以看到在不同位置绘制的点,而不是{}。为什么这种行为会改变?在

代码复制如下

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


t=2  #t can be changed

fig = plt.figure()
ax=Axes3D(fig)

#data

def unitilize(x,y,z):
    r=mt.sqrt(x**2+y**2+z**2)
    return x/r, y/r, z/r

def g_1(x,y,z):
    x=t*x                
    z=z/t                
    x,y,z=unitilize(x,y,z)
    return x,y,z

stepCnt=10000            ######step 
#########data#################
xs = np.empty((stepCnt + 1,))
ys = np.empty((stepCnt + 1,))
zs = np.empty((stepCnt + 1,))

#Setting initial values
def huatu(x,y,z):   

    xs[0], ys[0], zs[0] =unitilize(x,y,z)

    for i in range(stepCnt):
        xs[i+1],ys[i+1],zs[i+1]=g_1(xs[i], ys[i], zs[i])
    return xs,ys,zs


xs3,ys3,zs3=huatu(1,10,40)
ax.plot(xs3, ys3, zs3, color='b', marker='x')

xs2,ys2,zs2=huatu(1,0,40)
ax.plot(xs2, ys2, zs2, color='r', marker='o')
plt.show()

绘图输出: enter image description here

分散输出: enter image description here


Tags: importreturnplotdefasnppltax
1条回答
网友
1楼 · 发布于 2024-09-30 20:30:55

所以,你发现了一些很奇怪的东西,我还没能找到它的确切来源。底线是Axes3D.plot(和Axes.plot绘制的线实际上是如何创建的)不会复制它们的输入数据,而是与视图一起工作。这意味着,当数据随后发生突变时,曲线图可能会发生变化。由于某些原因,同样使用视图的Axes.plot没有重现这种可变性。这可能与Axes3D对象如何更新有关,我不太清楚。在

无论如何,Axes3D.scatter另一方面创建PathCollection对象(转换为PathCollection3D),这些对象具有更复杂的内部工作。据我所知,这些对象(已经在2d中)使用一个._offsets属性,它是从输入坐标构建的ndarray。通过构造,这些数组独立于输入数据。在

让我们比较一下plot的情况,看看我的意思。对于通常的二维图:

import numpy as np
import matplotlib.pyplot as plt

fig,ax = plt.subplots()

# first set data to zero
# we'll use an ndarray as input, otherwise there's no chance to get a view
x = np.arange(3)
y = np.array([0.0,0.0,0.0])

# plot the flat line
pl, = ax.plot(x,y,'o-')

# change the axes for better comparison later; not actually relevant
ax.set_ylim([0,4])

# see that the input data are kept as views
print(pl.get_xdata().base is x)  # True
print(pl.get_ydata().base is y)  # True

# mutating x would actually change pl.get_xdata() and vice versa

# mutate y to get a nontrivial line
y[:] = [1,2,3]

# update the canvas in an interactive plot
# plt.show() probably suffices non-interactively
fig.canvas.draw()

plt.show()

结果包含原始平面零线:

2d plot case

请注意,中间的几个print调用验证了附加到plot创建的line对象的数据确实是输入数据的视图(而不是副本),因此这里的效果不佳是因为修改数据的方式反映在绘图上。在

案例比较3d:

^{pr2}$

我们只对3d轴对象(以及一个多维度)执行完全相同的操作,结果如下:

3d case

如您所见,原始源数组的变异很好地更新了绘图,与2d情况完全相反。在

我不太确定这是怎么发生的;^{}外包most of the problem to ^{}(好吧,2d部分),然后pulls out all the data along the third dimension。由于在这两种情况下,行都是由Axes.plot创建的,所以这两种方法都不会复制它们的输入数据。在

^{}非常类似地让^{}执行2d任务。虽然我不明白2d和3d的plot大小写之间的区别,但我发现这一部分更容易理解:PathCollection(3D)对象要复杂得多,如果不与原始数据数组分离,就无法进行组装。在

因此,在您的问题代码中,生成要绘制的数据的函数实际上改变(并返回)相同的数组xs,ys,zs。因为基本上每个绘图都使用相同的数组,所以您看到的结果取决于绘图调用是否对其数据源的变异敏感。对于Axes3D.plot这是这种情况,因此对数据生成函数的第二次调用修改了第一个绘图;而对于Axes3D.scatter,数据源的突变不会影响绘图,因此两个图都如预期可见。在


如果您想看到真的奇怪,请尝试使用列表输入而不是ndarray的3d示例:

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

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

# first set data to zero, but lists this time
x = np.arange(3)
y = [0.0,0.0,0.0]
z = [0.0,0.0,0.0]

# plot the flat line
pl, = ax.plot(x,y,z,'o-')

# change the axes to see the result; not actually relevant
ax.set_ylim([0,4])
ax.set_zlim([0,4])

# mutate y,z to get a nontrivial line
y[:] = [1,2,3]
z[:] = [1,2,3]


# update the canvas in an interactive plot
# plt.show() probably suffices non-interactively
fig.canvas.draw()

plt.show()

在这种情况下,我希望输入列表被转换成ndarray,因此变异不会起任何作用,我们得到一条平坦的零线。事实并非如此:

3d case using list input

显然y坐标没有改变,但是z坐标发生了变化。这太奇怪了!关键是绘图的基础数据数组:

print(pl._verts3d)
# (array([0, 1, 2]), array([ 0.,  0.,  0.]), [1, 2, 3])
print(pl._verts3d[2] is z)
# True

^{} hacks the z coordinates通过调用^{}进入绘图时,函数grabs the existing x and y arrays from the 2d plot and just slaps the z coordinates next to them。在

将{26}中的一个{26>转换为z输入是单独处理的,并且在完成所有操作后它不会受到损害。这就是为什么y和{}的突变最终只改变了{}。在


作为结束语,我查看了matplotlib问题页面,找到了2d案例的this relevant discussion。解决的办法似乎是,二维绘图不复制数据,因为这通常会增加不必要的开销。我还可以看到3d案件是如何处理不同的,这将导致令人惊讶的行为。在

不管怎样,我不这么认为nk改变传递给绘图方法的数据是合理的。如果您是故意这样做的,请使用专用方法,如pl.set_xdata()。同样,这对于3d绘图来说是不可能的(其中x/ydata属性被重新解释为引用不同种类的坐标)。所以我的建议是不要对源数组进行变异,或者手动传递一个副本,以防以后需要对这些数组进行变异。变异不能被禁止,但我也能理解为什么matplotlib开发人员不想在每种情况下都复制每一个输入。所以最有可能的解决方案是用户不应该改变他们的原始数据。有些东西告诉我,写问题中代码的人没有意识到他们一开始就在改变输入,这意味着我们仍将看到一个有效的用例,其中输入数组被有意地改变。在

相关问题 更多 >