Why is the shape of a singleton array the empty tuple?
On Sat, Mar 6, 2010 at 9:37 PM, Ian Mallett <geometrian@gmail.com> wrote:
x = numpy.array(3) x array(3) x.shape () y = numpy.array([3]) y array([3]) y.shape (1,)
Ian
Thanks, Ian. I already figured out how to make it not so, but I still want to understand the design reasoning behind it being so in the first place (thus the use of the question "why (is it so)," not "how (to make it different)"). DG
On Sat, Mar 6, 2010 at 9:46 PM, David Goldsmith <d.l.goldsmith@gmail.com>wrote:
Thanks, Ian. I already figured out how to make it not so, but I still want to understand the design reasoning behind it being so in the first place (thus the use of the question "why (is it so)," not "how (to make it different)").
Well, I can't help you with that. I would also ask why this design even exists? Equating an array with a single number doesn't make sense to me. Ian
On Sat, Mar 6, 2010 at 10:26 PM, Ian Mallett <geometrian@gmail.com> wrote:
On Sat, Mar 6, 2010 at 9:46 PM, David Goldsmith <d.l.goldsmith@gmail.com>wrote:
Thanks, Ian. I already figured out how to make it not so, but I still want to understand the design reasoning behind it being so in the first place (thus the use of the question "why (is it so)," not "how (to make it different)").
Well, I can't help you with that. I would also ask why this design even exists? Equating an array with a single number doesn't make sense to me. Ian
Here's an (unintended) use case: I wanted to convert anything in an array that's close to zero to be zero (and leave the rest as is), but I want it to be robust so that if it receives a scalar, it can work w/ that, too. Here's my (working) code (I'm sure that once Robert sees it, he'll be able to replace it w/ a one-liner): def convert_close(arg): arg = N.array(arg) if not arg.shape: arg = N.array((arg,)) if arg.size: t = N.array([0 if N.allclose(temp, 0) else temp for temp in arg]) if len(t.shape) - 1: return N.squeeze(t) else: return t else: return N.array() At first I wasn't "casting" arg to be an array upon entry, but I found that if arg is a scalar, my list comprehension failed, so I had choose _some_ sort of sequence to cast scalars to; since arg will typically be an array and that's what I wanted to return as well, it seemed most appropriate to "cast" incoming scalars to arrays. So I added the arg = N.array(arg) at the beginning (N.array(array) = array, and N.array(non-array seq) does the "right" thing as well), but the list comprehension still wouldn't work if arg was a scalar upon entry; after many print statements and much interactive experimenting, I finally figured out that this is because the shape of N.array(scalar) is () (and I thence immediately guessed, correctly of course, that N.array((scalar,)) has shape (1,)). So I added the if not arg.shape: to detect and correct for those zero size arrays, and now it works fine, but I'd still like to know _why_ N.array(scalar).shape == () but N.array((scalar,)).shape == (1,). No biggy, just curious. DG
First, to David's routine: 2010/3/7 David Goldsmith <d.l.goldsmith@gmail.com>:
def convert_close(arg): arg = N.array(arg) if not arg.shape: arg = N.array((arg,)) if arg.size: t = N.array([0 if N.allclose(temp, 0) else temp for temp in arg]) if len(t.shape) - 1: return N.squeeze(t) else: return t else: return N.array()
Ok, chaps, let's code: import numpy def convert_close(ndarray, atol = 1e-5, rtol = 1e-8): ndarray_abs = abs(ndarray) mask = (ndarray_abs > atol + rtol * ndarray_abs) return ndarray * mask
python -i close.py
a = numpy.asarray([1e-6]) convert_close(a) array([ 0.]) a = numpy.asarray([1e-6, 1]) convert_close(a) array([ 0., 1.]) a = numpy.asarray(1e-6) convert_close(a) 0.0 a = numpy.asarray([-1e-6, 1]) convert_close(a) array([ 0., 1.])
It's not as good as Robert's (so far virtual) solution, but :-)
On Sat, Mar 6, 2010 at 10:26 PM, Ian Mallett <geometrian@gmail.com> wrote:
On Sat, Mar 6, 2010 at 9:46 PM, David Goldsmith <d.l.goldsmith@gmail.com> wrote:
Thanks, Ian. I already figured out how to make it not so, but I still want to understand the design reasoning behind it being so in the first place (thus the use of the question "why (is it so)," not "how (to make it different)").
1. First from a mathematical point of view (don't be frightened): When an array has shape ndarray.shape, then the number of elements contained is: numpy.asarray(ndarray.shape).mul() When I type now:
numpy.asarray([]).prod() 1.0
This is the .shape of an scalar ndarray (without any strides), and therefore such a scalar ndarray holds exactly one item. Or, for hard-core friends (-:
numpy.asarray([[]]) array([], shape=(1, 0), dtype=float64) numpy.asarray([[]]).prod() 1.0
So, ndarrays without elements yield .prod() == 1.0. This is sensible, because the product shall be algorithmically defined as: def prod(ndarray): product = 1.0 for item in ndarray.flatten(): product *= item return product Thus, the product of nothing is defined to be one to be consistent. One would end up with the same using a recursive definition of prod() instead of this iterative one. 2. From programmer's point of view. You can always write: ndarray[()]. This means, to give no index at all. Indeed, writing: ndarray[1, 2] is equivalent to writing: ndarray[(1, 2)] , as keys are always passed as a tuple or a scalar. Scalar in case of: ndarray[42] . Now, the call: ndarray[()] shall return 'something', which is the complete ndarray, because we didn't indice anything. For multidimensional arrays: a = numpy.ndarray([[1, 2], [3, 4]]) the call: a[0] shall return: array([1, 2]). This is clear. But now, what to return, if we consume all the indices available, e.g. when writing: a[0, 0] ? This means, we return the scalar array array(1) . That's another meaning of scalar arrays. When indicing an ndarray a with a tuple of length N_key, without slices, the return shape will be always: a.shape[N_key:] This means, using all indices available returns a shape: a.shape[a.ndim:] == [] , i.e., a scalar "without" shape. To conclude, everything is consistent when allowing scalar arrays, and everything breaks down if we don't. They are some kind of 0, like the 0 in the whole numbers, which the Roman's didn't know of. It makes things simpler (and more consistent). Also it unifies scalars and arrays to only on kind of type, which is a great deal. Friedrich
On Sun, Mar 7, 2010 at 4:30 AM, Friedrich Romstedt < friedrichromstedt@gmail.com> wrote:
First, to David's routine:
2010/3/7 David Goldsmith <d.l.goldsmith@gmail.com>:
def convert_close(arg): arg = N.array(arg) if not arg.shape: arg = N.array((arg,)) if arg.size: t = N.array([0 if N.allclose(temp, 0) else temp for temp in arg]) if len(t.shape) - 1: return N.squeeze(t) else: return t else: return N.array()
Ok, chaps, let's code:
import numpy
def convert_close(ndarray, atol = 1e-5, rtol = 1e-8): ndarray_abs = abs(ndarray) mask = (ndarray_abs > atol + rtol * ndarray_abs) return ndarray * mask
python -i close.py
a = numpy.asarray([1e-6]) convert_close(a) array([ 0.]) a = numpy.asarray([1e-6, 1]) convert_close(a) array([ 0., 1.]) a = numpy.asarray(1e-6) convert_close(a) 0.0 a = numpy.asarray([-1e-6, 1]) convert_close(a) array([ 0., 1.])
Great, thanks! DG
participants (3)
-
David Goldsmith
-
Friedrich Romstedt
-
Ian Mallett