在问题How can I draw a multiple 3d-curves picture by Python?中发布的代码中,绘图方法被调用两次,因为要绘制的点没有重置,所以这些线被淹没在另一个上面。但是如果我们尝试使用散射方法,我们可以看到在不同位置绘制的点,而不是onDowngrade()
。为什么这会改变行为?
代码复制在
下面plot()
答案 0 :(得分:2)
所以,你发现了一些非常奇怪的东西,精确的来源我无法追查。底线是由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()
结果包含原始的零线:
请注意,中间的几个print
调用验证附加到plot
创建的行对象的数据确实是输入数据的视图(而不是副本),因此效果不佳这是因为修改数据的方式正在图中反映出来。
比较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
# 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])
z = np.array([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()
我们只使用3d轴对象(以及另外一个维度)执行完全相同的操作,结果如下:
正如您所看到的,通过原始源阵列的变异很好地更新了该图,与2d情况形成鲜明对比。
我不确定这是怎么发生的; Axes3D.plot
外包most of the problem to Axes.plot
(嗯,第2部分),然后是pulls out all the data along the third dimension。由于在两种情况下都由Axes.plot
创建了行,因此不会复制输入数据也就不足为奇了。
Axes3D.scatter
非常类似地让Axes.scatter
完成第二份工作。虽然我不明白{2}和3d之间的plot
情况有何不同,但我觉得这部分更容易理解:PathCollection(3D)
对象要复杂得多,如果不与原始对象分离,就无法组合数据阵列。
因此,在您的问题的代码中,生成要绘制的数据的函数实际上会改变(并返回)相同的数组xs,ys,zs
。由于每个绘图使用的数组基本相同,因此您看到的结果取决于绘图调用是否对其数据源的变异敏感。对于Axes3D.plot
,就是这种情况,因此第二次调用数据生成函数会修改第一个图;而对于Axes3D.scatter
,数据源的突变不会影响绘图,因此两个绘图都可以按预期显示。
如果你想看到真的很奇怪,请尝试使用list输入而不是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()
在这种情况下,我希望输入列表转换为ndarrays,因此变异不会做任何事情,我们得到一个扁平的零线。事实并非如此:
显然y坐标不会改变,但z坐标会发生变异。现在这很奇怪!关键是图的基础数据数组:
print(pl._verts3d)
# (array([0, 1, 2]), array([ 0., 0., 0.]), [1, 2, 3])
print(pl._verts3d[2] is z)
# True
当Axes3D.plot
hacks the z coordinates通过调用mplot3d.art3d.line_2d_to_3d
进入情节时,功能grabs the existing x and y arrays from the 2d plot and just slaps the z coordinates next to them。
换句话说,Axes.plot
将输入列表y
转换为数组,在此步骤y
的突变不会影响绘图。另一方面,z
输入被单独处理,并在一切完成后立即出现。这就是变异y
和z
最终只改变z
的方式。
作为结束语,我查看了matplotlib问题页面,并找到了{2}案例的this relevant discussion。分辨率似乎是设计上的2d图不会复制他们的数据,因为这往往会增加不必要的开销。我还可以看到3d案例的处理方式不同,这会导致令人惊讶的行为。
无论如何,我认为改变传递给绘图方法的数据是不合理的。如果您是故意这样做,请使用pl.set_xdata()
等专用方法。然后再次这对于3d图是不可能的(其中x / ydata属性被重新解释以引用不同类型的坐标)。所以我的建议是不要改变源数组,也不要手动传递副本,以防以后想要改变它们。不能禁止变异,但我也可以看到matplotlib开发人员为什么不想在每种情况下复制每一个输入。所以最可能的解决方案是用户不应该改变他们的原始数据。有些东西告诉我,在问题中编写代码的人没有意识到他们正在改变他们的输入,这意味着我们仍然会看到一个有效的用例,其中输入数组是故意变异的。< / p>