2

In a sense, this already has an excellent answer:

One should not think too hard about it. It's ultimately better for the mental health and longevity of the individual.

Mental health and longevity are of course nice, but what about this individual's pride which took another hit trying to be clever and cruelly being denied by numpy:

Consider the following where we start with some byte data:

a = np.linspace(0,255,6, dtype=np.uint8)
a
# array([  0,  51, 102, 153, 204, 255], dtype=uint8)

Let's assume we want to add something and promote the type, so it does not wrap around. With a scalar, this does not work:

b = np.uint16(1)

a + b
# array([  1,  52, 103, 154, 205,   0], dtype=uint8)

But with an array, it does:

c = np.ones(1, np.uint16)

a + c
# array([  1,  52, 103, 154, 205, 256], dtype=uint16)

So I thought let's make an array.

b[...]
# array(1, dtype=uint16)
np.isscalar(b[...])
# False

But, alas:

a + b[...]
# array([  1,  52, 103, 154, 205,   0], dtype=uint8)

Why does this 0d array behave like a scalar here?

Paul Panzer
  • 51,835
  • 3
  • 54
  • 99
  • Actually `np.isscalar(b)` and `b=np.uint(16)` returned True for me. What version are you using? Numpy 1.15 here. – knh190 May 28 '19 at 01:27
  • What about `np.isscalar(b[...])` (make sure not to miss the Ellipsis)? – Paul Panzer May 28 '19 at 01:30
  • It's `False`...! But doc said you should use `ndim` almost everywhere. – knh190 May 28 '19 at 01:32
  • 1
    `np.isscalar(b[...])` is False on Numpy 1.16.3, as b[...] is indeed a numpy array. I think this answer (https://stackoverflow.com/a/42191121/10640534) will help. – Patol75 May 28 '19 at 01:33
  • Because a 0-dimensional array is an ndarray instance containing exactly one array scalar – Chrispresso May 28 '19 at 02:24
  • `isscalar` is just a bunch of `type` and `isinstance` tests. I don't know if there's good use for the `np.uint8(1)` style of construction. It strikes me as beginnerish. `np.array(1, np.uint8)` looks better, with a slightly fuller set of methods. – hpaulj May 28 '19 at 02:28
  • Seems that the underlying question is why `np.array([1], np.uint16)` produces the dtype promotion, but `np.array(1, np.uint16)` does not (i.e. the 1d vs the 0d). – hpaulj May 28 '19 at 02:29
  • @hpaulj any array as long as it is not 0d, in fact even an empty one `a + np.ones((0,1),uint16)` – Paul Panzer May 28 '19 at 02:37

1 Answers1

2

https://docs.scipy.org/doc/numpy/reference/ufuncs.html#casting-rules

last paragraph:

Mixed scalar-array operations use a different set of casting rules that ensure that a scalar cannot “upcast” an array unless the scalar is of a fundamentally different kind of data (i.e., under a different hierarchy in the data-type hierarchy) than the array. This rule enables you to use scalar constants in your code (which, as Python types, are interpreted accordingly in ufuncs) without worrying about whether the precision of the scalar constant will cause upcasting on your large (small precision) array.

I take this to mean that the following expressions have the same effect:

In [56]: np.add(a,1)                                                                 
Out[56]: array([  1,  52, 103, 154, 205,   0], dtype=uint8)
In [57]: np.add(a,np.array(1))                                                       
Out[57]: array([  1,  52, 103, 154, 205,   0], dtype=uint8)

For this to be true, a 0d cannot "upcast". But a list behaves like a 1d array, and does "upcast"

In [60]: np.add(a,[1])                                                               
Out[60]: array([  1,  52, 103, 154, 205, 256])
In [61]: np.add(a,np.array([1]))                                                     
Out[61]: array([  1,  52, 103, 154, 205, 256])

https://docs.scipy.org/doc/numpy/reference/arrays.scalars.html

Array scalars include np.uint8(1) etc.

The array scalar objects have an array priority of NPY_SCALAR_PRIORITY (-1,000,000.0).

In [67]: np.uint8(1).__array_priority__                                              
Out[67]: -1000000.0
In [68]: np.array(1,'uint8').__array_priority__                                      
Out[68]: 0.0

Array scalars have exactly the same methods as arrays. The default behavior of these methods is to internally convert the scalar to an equivalent 0-dimensional array and to call the corresponding array method.

np.isscalar does:

        (isinstance(num, generic)
        or type(num) in ScalarType
        or isinstance(num, numbers.Number))

np.isscalar recommends using np.ndim(x) == 0. This first checks for a .ndim attribute (which would be the case for 0d arrays), and failing that, tries np.asarray(x).ndim. So in that sense, anything that can be made into a 0d array qualifies as 'scalar'. That may be too broad, since a dictionary counts: npdim({}).

hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • My whole point is that `np.array(1)` according to numpy's own "official" test, `isscalar`, is not a scalar, so, technically, what you are quoting from the docs does not apply. – Paul Panzer May 28 '19 at 03:01
  • `np.array(1)`, a 0d array, is not an `array scalar`, by the function test, or the `array scalars` documentation. But computationally they are similar, because the array scalar is 'promoted' to 0d array before being use. – hpaulj May 28 '19 at 03:08