3

numpy.array(value) evaluates to true, if value is int, float or complex. The result seems to be a shapeless array (numpy.array(value).shape returns ()).

Reshaping the above like so numpy.array(value).reshape(1) works fine and numpy.array(value).reshape(1).squeeze() reverses this and again results in a shapeless array.

What is the rationale behind this behavior? Which use-cases exist for this behaviour?

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
fabianegli
  • 2,056
  • 1
  • 18
  • 35
  • You get a scalar from a scalar. What's *not* rational about that? – Mad Physicist Jul 31 '19 at 11:43
  • Also, the call to squeeze is missing parens – Mad Physicist Jul 31 '19 at 11:43
  • You can pass `ndmin=1` if you don't want to get a scalar. – Mad Physicist Jul 31 '19 at 11:44
  • 1
    The way I understand it, a scalar is not an array. But `type(np.array(11))` is `numpy.ndarray`. My issue is not that I do not understand what a scalar is or what an array is but that numpy gives a scalar the type array. – fabianegli Jul 31 '19 at 11:49
  • 2
    It's a 0D array, not shape-less, the shape is the zero-length tuple `()`. There is nothing illogical about it. It is also quite useful for example if you are writing a function that you want to be able to process arrays and scalars alike. – Paul Panzer Jul 31 '19 at 12:27

1 Answers1

3

When you create a zero-dimensional array like np.array(3), you get an object that behaves as an array in 99.99% of situations. You can inspect the basic properties:

>>> x = np.array(3)
>>> x
array(3)
>>> x.ndim
0
>>> x.shape
()
>>> x[None]
array([3])
>>> type(x)
numpy.ndarray
>>> x.dtype
dtype('int32')

So far so good. The logic behind this is simple: you can process any array-like object the same way, regardless of whether is it a number, list or array, just by wrapping it in a call to np.array.

One thing to keep in mind is that when you index an array, the index tuple must have ndim or fewer elements. So you can't do:

>>> x[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: too many indices for array

Instead, you have to use a zero-sized tuple (since x[] is invalid syntax):

>>> x[()]
3

You can also use the array as a scalar instead:

>>> y = x + 3
>>> y
6
>>> type(y)
numpy.int32

Adding two scalars produces a scalar instance of the dtype, not another array. That being said, you can use y from this example in exactly the same way you would x, 99.99% of the time, since dtypes inherit from ndarray. It does not matter that 3 is a Python int, since np.add will wrap it in an array regardless. y = x + x will yield identical results.

One difference between x and y in these examples is that x is not officially considered to be a scalar:

>>> np.isscalar(x)
False
>>> np.isscalar(y)
True

The indexing issue can potentially throw a monkey wrench in your plans to index any array like-object. You can easily get around it by supplying ndmin=1 as an argument to the constructor, or using a reshape:

>>> x1 = np.array(3, ndmin=1)
>>> x1
array([3])

>>> x2 = np.array(3).reshape(-1)
>>> x2
array([3])

I generally recommend the former method, as it requires no prior knowledge of the dimensionality of the input.

FurtherRreading:

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • 2
    `x[()]` - you can always index with a `ndim` tuple. – hpaulj Jul 31 '19 at 17:42
  • @hpaulj. Thanks for the tip. I updated the language. – Mad Physicist Jul 31 '19 at 19:21
  • Thank you for this answer and the link to the further reading. This made for an educational evening. – fabianegli Jul 31 '19 at 23:37
  • I just stumbled upon another way to get the value of a scalar out of a numpy array with the [`item`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.item.html#numpy.ndarray.item) function: `x.item()`. Inspiration came from [here](https://stackoverflow.com/a/45661259) when researching how to work with `numpy.savez()` – fabianegli Jan 25 '21 at 11:21