1

Let's say I have a 0d Numpy array:

x = numpy.asarray(1.)

This has type numpy.ndarray. But now operate on x:

y = 1.*x

This now has type numpy.float64. The same thing is true when using all Numpy functions. For example, numpy.sin(x) has type numpy.float64.

Why?!

Now let's say I have a function and in the course of operation that function has to promote the dimension of the array (to perform some check or other):

def fun(x):
    x = np.asarray(x)
    if x.ndim == 0:
        x = x[None]
    # Perform some checks and operate on x
    return x.squeeze()

To be consistent with Numpy I would like this function to return a numpy.float64 when a float is passed to it. But as it stands it returns a 0d array. I could instead return 1.*x.squeeze() but that seems very ugly. Is there a canonical solution to this problem?

7
  • 4
    Related: stackoverflow.com/q/773030/843953 Commented Oct 25, 2023 at 13:13
  • When writing your own functions is there a way of emulating this behaviour, so as to be consistent with Numpy functions? Commented Oct 25, 2023 at 13:24
  • I think it should take care of itself unless you specifically construct an array to return. Can you edit your question to include an example of a function you're writing where this is a problem? Commented Oct 25, 2023 at 13:32
  • Yes, you're right. I think I'm conflating two issues. But let's say that once in a function you have to promote the dimension of x (to perform some check): x = x[None]. You can squeeze back down to get a 0d array: x.squeeze(). But I would like to return a numpy.float64 if a float is passed to the function. I can use a flag and then do 1.*x.squeeze() but it seems ugly. Happy to update the question if you don't think I'm muddying the waters. Commented Oct 25, 2023 at 13:44
  • 1
    Feel free to update the question, that seems to be the actual question anyway. Commented Oct 25, 2023 at 13:53

1 Answer 1

2

A python scalar, a 0d array, and a numpy scalar:

In [134]: x=1.23; y = np.array(x); z = 1.*y    
In [135]: x,y,z
Out[135]: (1.23, array(1.23), 1.23)    
In [136]: type(x),type(y),type(z)
Out[136]: (float, numpy.ndarray, numpy.float64)

As the numpy scalars page notes, there's a lot of overlap between such a scalar and a 0d array.

They can both be indexed

In [137]: y[()], z[()]
Out[137]: (1.23, 1.23)
    
In [139]: y[()]=2.3;y
Out[139]: array(2.3)

The key difference is the scalar cannot be changed:

In [140]: z[()]=2.34
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[140], line 1
----> 1 z[()]=2.34

TypeError: 'numpy.float64' object does not support item assignment

Why do many operations return a scalar instead of a 0d array? My guess is that there's a 'finalize' method that does this sort of 'clean up'. Documentation on subclassing arrays talks about this sort of finalization.

There are several ways of making such an array is atleast 1d.

In [141]: y[None], np.atleast_1d(x), np.array(x, ndmin=1)
Out[141]: (array([2.3]), array([1.23]), array([1.23]))

np.squeeze explicitly notes that it will return a 0d array, not a numpy scalar. I'm not aware of any other operations that make such a distinction.

The doc for np.inscalar may instructive in this:

In [143]: np.isscalar(y), np.isscalar(z)
Out[143]: (False, True)

In [144]: y.ndim, z.ndim
Out[144]: (0, 0)

isscalar is python code, with several isinstance tests.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much. A finalize method makes perfect sense and would indeed explain why operations on 0d arrays return scalars. It also makes sense that squeezing all axes returns a 0d array (thanks for pointing me to the documentation). When it comes to function construction I think the correct procedure is to check floats and arrays separately. Any subsequent operations then return the expected type (as @pho points out).
FWIW I wrote a short blog post summarizing all this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.