You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] set_3d_properties type error in Matplotlib 3.5.1 #22308
I had a code that uses animation.FuncAnimation to generate an animation, where I update a mpl_toolkits.mplot3d.art3d.Line3D object using set_data(a, b) and set_3d_properties(c) where a, b, and c are of type numpy.float64. This code works perfectly in Matplotlib 3.4.3. However, after I upgraded to Matplotlib 3.5.1, it fails with the following error
File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/mpl_toolkits/mplot3d/art3d.py", line 175, in set_3d_properties
zs = np.broadcast_to(zs, len(xs))
TypeError: object of type'numpy.float64' has no len()
Source
I checked the source code of mpl_toolkits/mplot3d/art3d.py and found that in Matplotlib 3.4.3, line 175 (the line where the error occurred) was zs = np.broadcast_to(zs, xs.shape) instead, and this avoids the error. I was able to make my code run again in Matplotlib 3.5.1 by changing my code to set_data([a], [b]) and set_3d_properties([c]), but as expected, this would not run in 3.4.3. I am using Python 3.10.
Was this change in line 175 of mpl_toolkits/mplot3d/art3d.py intended?
Can you please provide a simple self-contained example?
Here's an example. If you use the first two lines in update, it works fine in earlier versions but throws a TypeError in Matplotlib 3.5.1 because len() doesn't support numpy.float64. This is the same issue pointed out by @christofhapp in #20555.
importnumpyasnpimportmatplotlib.pyplotaspltimportmatplotlib.animationasanimationimportmpl_toolkits.mplot3d.axes3dasp3defupdate(step, xs, ys, zs, point):
# use the following two lines for matplotlib 3.4.3point.set_data(xs[step], ys[step])
point.set_3d_properties(zs[step])
# use the following two lines for matplotlib 3.5.1#point.set_data([xs[step]], [ys[step]])#point.set_3d_properties([zs[step]])returnpointfig=plt.figure()
ax=p3.Axes3D(fig, auto_add_to_figure=False)
fig.add_axes(ax)
point=ax.plot([], [], [], '.')[0]
xs=np.arange(100)
ys=np.arange(100)
zs=np.arange(100)
ax.set_xlim3d([0,100])
ax.set_ylim3d([0,100])
ax.set_zlim3d([0,100])
anim=animation.FuncAnimation(fig, update, 100,
fargs=(xs, ys, zs, point), interval=1, blit=False)
plt.show()
Expected outcome
In v3.4.3
bug_test.mp4
Actual outcome
In v3.5.1
File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/mpl_toolkits/mplot3d/art3d.py", line 175, in set_3d_properties
zs = np.broadcast_to(zs, len(xs))
TypeError: object of type'numpy.float64' has no len()
The error is that xs is not a sequence, but they must be a sequence in 2D, so that should be required in 3D as well. This may have been a regression, but it is how it's supposed to work (it is required when initializing, but .set_data is not also validating.)
No! It absolutely makes sense to plot a scalar (single point). A scalar should be interpreted as an array with one element aswell. Because it's what it is mathematically. Don't throw a failure, make it plot a single point.
A scalar should be interpreted as an array with one element aswell. Because it's what it is mathematically.
I disagree that this is cut and dry, there is a significant amount of degeneracy at the scalar<->array boundary which is difficult to model correctly in software (see the difficulties / confusion that comes up around numpy scalars). I do not think you are going to have much success changing anyone's mind with this argument.
If your argument is "It is very convenient for me to pass scalars into these methods and have mpl do the scalar -> length 1 1D array", that is a reasonable position and one I find persuasive because we automatically up-cast on ax.plot for example.
@tacaswell I don't know what you mean by up-cast.
Yes, you're right, I didn't want to say a scalar should be of type array. What i wanted to say is more:
I want to be able to pass a scalar and the target function interprets that as an array if it is appropriate for the functionality. e.g. I want to plot points in ax.plot and my "sequence" can have any number of points including 1 and 0. I don't want to write a case distinction for that.
Passing a scalar to Axes.plot is fine, and it adjusts single values into a sequence. But a Line2D is an object that represents a line, and it requires sequences. This is enforced on creation, and the lack of enforcement on set_data is a bug.
Passing a scalar to Axes.plot is fine, and it adjusts single values into a sequence. But a Line2D is an object that represents a line, and it requires sequences. This is enforced on creation, and the lack of enforcement on set_data is a bug.
I agree with this assessment. I initially reported this because I want to know if the issue was due to a lack of enforcement for sequences or a lack of support for scalars. Clearly Line2D and Line3D objects are intended for sequences.
But if i make a point plot with more than 1 point with ax.plot it becomes a Line2D automatically and if i want to update that plot with a point vector of length 1 later, it's not possible, because it's a Line2D.
And it's not consistent with the official matplotlib description on matplotlib.org: "Plot y versus x as lines and/or markers."
the resulting object should be able to contain and update markers of length 1.
This works as documented - if someone wants to pass a singleton, they need only wrap in a list or array. Our casting to 1-D objects is very convoluted, and I'd be very 👎 on moving it into Line2D.set_data.
Independently of whether we actually want to support that, I think the "correct" (i.e., not triggering this issue) way to spell zs = np.broadcast_to(zs, len(xs)) (and the similar other uses of broadcast_to in the same module) would have been _, zs = np.broadcast_arrays(xs, zs)?
Activity
QuLogic commentedon Jan 25, 2022
Can you please provide a simple self-contained example?
tacaswell commentedon Jan 25, 2022
Looks like that change was from #20555 which fixed one bug but introduced another.
jiashuhan commentedon Jan 25, 2022
Here's an example. If you use the first two lines in
update, it works fine in earlier versions but throws a TypeError in Matplotlib 3.5.1 becauselen()doesn't supportnumpy.float64. This is the same issue pointed out by @christofhapp in #20555.Expected outcome
In v3.4.3
bug_test.mp4
Actual outcome
In v3.5.1
Fix xs length
tacaswell commentedon Jan 27, 2022
will work correctly on all version of Matplotlib.
Previously, numpy scalars would work, but if you use built in floats
will fail on 3.4.3.
QuLogic commentedon Jan 27, 2022
The error is that
xsis not a sequence, but they must be a sequence in 2D, so that should be required in 3D as well. This may have been a regression, but it is how it's supposed to work (it is required when initializing, but.set_datais not also validating.)christofhapp commentedon Jan 27, 2022
No! It absolutely makes sense to plot a scalar (single point). A scalar should be interpreted as an array with one element aswell. Because it's what it is mathematically. Don't throw a failure, make it plot a single point.
tacaswell commentedon Jan 27, 2022
I disagree that this is cut and dry, there is a significant amount of degeneracy at the scalar<->array boundary which is difficult to model correctly in software (see the difficulties / confusion that comes up around numpy scalars). I do not think you are going to have much success changing anyone's mind with this argument.
If your argument is "It is very convenient for me to pass scalars into these methods and have mpl do the scalar -> length 1 1D array", that is a reasonable position and one I find persuasive because we automatically up-cast on
ax.plotfor example.christofhapp commentedon Jan 27, 2022
@tacaswell I don't know what you mean by up-cast.
Yes, you're right, I didn't want to say a scalar should be of type array. What i wanted to say is more:
I want to be able to pass a scalar and the target function interprets that as an array if it is appropriate for the functionality. e.g. I want to plot points in ax.plot and my "sequence" can have any number of points including 1 and 0. I don't want to write a case distinction for that.
QuLogic commentedon Jan 27, 2022
Passing a scalar to
Axes.plotis fine, and it adjusts single values into a sequence. But aLine2Dis an object that represents a line, and it requires sequences. This is enforced on creation, and the lack of enforcement onset_datais a bug.jiashuhan commentedon Jan 27, 2022
I agree with this assessment. I initially reported this because I want to know if the issue was due to a lack of enforcement for sequences or a lack of support for scalars. Clearly
Line2DandLine3Dobjects are intended for sequences.christofhapp commentedon Jan 27, 2022
But if i make a point plot with more than 1 point with ax.plot it becomes a Line2D automatically and if i want to update that plot with a point vector of length 1 later, it's not possible, because it's a Line2D.
And it's not consistent with the official matplotlib description on matplotlib.org: "Plot y versus x as lines and/or markers."
the resulting object should be able to contain and update markers of length 1.
8 remaining items
jklymak commentedon Jun 20, 2022
This works as documented - if someone wants to pass a singleton, they need only wrap in a list or array. Our casting to 1-D objects is very convoluted, and I'd be very 👎 on moving it into
Line2D.set_data.anntzer commentedon Jun 20, 2022
Independently of whether we actually want to support that, I think the "correct" (i.e., not triggering this issue) way to spell
zs = np.broadcast_to(zs, len(xs))(and the similar other uses of broadcast_to in the same module) would have been_, zs = np.broadcast_arrays(xs, zs)?tacaswell commentedon Jun 20, 2022
This is incorrect, everyone agrees that
[1,]should work (and it does), the question is should1work (and lets not bring numpy scalar into this ;) ).Telemaharry commentedon Jun 22, 2023
Was this issue fixed? I am having the same issue.