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

时间:2017-07-19 17:52:52

标签: python matplotlib

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

代码复制在

下面
plot()

绘图输出: enter image description here

分散输出: enter image description here

1 个答案:

答案 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()

结果包含原始的零线:

2d plot case

请注意,中间的几个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轴对象(以及另外一个维度)执行完全相同的操作,结果如下:

3d case

正如您所看到的,通过原始源阵列的变异很好地更新了该图,与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,因此变异不会做任何事情,我们得到一个扁平的零线。事实并非如此:

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

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输入被单独处理,并在一切完成后立即出现。这就是变异yz最终只改变z的方式。

作为结束语,我查看了matplotlib问题页面,并找到了{2}案例的this relevant discussion。分辨率似乎是设计上的2d图不会复制他们的数据,因为这往往会增加不必要的开销。我还可以看到3d案例的处理方式不同,这会导致令人惊讶的行为。

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