Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does "three dots" in Python mean when indexing what looks like a number?

People also ask

What is the meaning of 3 dots in Python?

Ellipsis is a Python Object. It has no Methods. It is a singleton Object i.e, provides easy access to single instances. Various Use Cases of Ellipsis (…): Default Secondary Prompt in Python interpreter.

What does the dots mean in Python?

Dot notation indicates that you're accessing data or behaviors for a particular object type. When you use dot notation, you indicate to Python that you want to either run a particular operation on, or to access a particular property of, an object type.

What does three consecutive dots mean?

Those three little dots are called an ellipsis (plural: ellipses). The term ellipsis comes from the Greek word meaning “omission,” and that's just what an ellipsis does—it shows that something has been left out. When you're quoting someone, you can use an ellipsis to show that you've omitted some of their words.

What is ellipsis in NumPy?

In NumPy, you can use Ellipsis ( ... ) to omit intermediate dimensions when specifying elements or ranges with [] .


While the proposed duplicate What does the Python Ellipsis object do? answers the question in a general python context, its use in an nditer loop requires, I think, added information.

https://docs.scipy.org/doc/numpy/reference/arrays.nditer.html#modifying-array-values

Regular assignment in Python simply changes a reference in the local or global variable dictionary instead of modifying an existing variable in place. This means that simply assigning to x will not place the value into the element of the array, but rather switch x from being an array element reference to being a reference to the value you assigned. To actually modify the element of the array, x should be indexed with the ellipsis.

That section includes your code example.

So in my words, the x[...] = ... modifies x in-place; x = ... would have broken the link to the nditer variable, and not changed it. It's like x[:] = ... but works with arrays of any dimension (including 0d). In this context x isn't just a number, it's an array.

Perhaps the closest thing to this nditer iteration, without nditer is:

In [667]: for i, x in np.ndenumerate(a):
     ...:     print(i, x)
     ...:     a[i] = 2 * x
     ...:     
(0, 0) 0
(0, 1) 1
...
(1, 2) 5
In [668]: a
Out[668]: 
array([[ 0,  2,  4],
       [ 6,  8, 10]])

Notice that I had to index and modify a[i] directly. I could not have used, x = 2*x. In this iteration x is a scalar, and thus not mutable

In [669]: for i,x in np.ndenumerate(a):
     ...:     x[...] = 2 * x
  ...
TypeError: 'numpy.int32' object does not support item assignment

But in the nditer case x is a 0d array, and mutable.

In [671]: for x in np.nditer(a, op_flags=['readwrite']):
     ...:     print(x, type(x), x.shape)
     ...:     x[...] = 2 * x
     ...:     
0 <class 'numpy.ndarray'> ()
4 <class 'numpy.ndarray'> ()
...

And because it is 0d, x[:] cannot be used instead of x[...]

----> 3     x[:] = 2 * x
IndexError: too many indices for array

A simpler array iteration might also give insight:

In [675]: for x in a:
     ...:     print(x, x.shape)
     ...:     x[:] = 2 * x
     ...:     
[ 0  8 16] (3,)
[24 32 40] (3,)

this iterates on the rows (1st dim) of a. x is then a 1d array, and can be modified with either x[:]=... or x[...]=....

And if I add the external_loop flag from the next section, x is now a 1d array, and x[:] = would work. But x[...] = still works and is more general. x[...] is used all the other nditer examples.

In [677]: for x in np.nditer(a, op_flags=['readwrite'], flags=['external_loop']):
     ...:     print(x, type(x), x.shape)
     ...:     x[...] = 2 * x
[ 0 16 32 48 64 80] <class 'numpy.ndarray'> (6,)

Compare this simple row iteration (on a 2d array):

In [675]: for x in a:
     ...:     print(x, x.shape)
     ...:     x[:] = 2 * x
     ...:     
[ 0  8 16] (3,)
[24 32 40] (3,)

this iterates on the rows (1st dim) of a. x is then a 1d array, and can be modified with either x[:] = ... or x[...] = ....

Read and experiment with this nditer page all the way through to the end. By itself, nditer is not that useful in python. It does not speed up iteration - not until you port your code to cython.np.ndindex is one of the few non-compiled numpy functions that uses nditer.


The ellipsis ... means as many : as needed.

For people who don't have time, here is a simple example:

In [64]: X = np.reshape(np.arange(9), (3,3))

In [67]: Y = np.reshape(np.arange(2*3*4), (2,3,4))

In [70]: X
Out[70]:
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

In [71]: X[:,0]
Out[71]: array([0, 3, 6])

In [72]: X[...,0]
Out[72]: array([0, 3, 6])

In [73]: Y
Out[73]:
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])

In [74]: Y[:,0]
Out[74]:
array([[ 0,  1,  2,  3],
       [12, 13, 14, 15]])

In [75]: Y[...,0]
Out[75]:
array([[ 0,  4,  8],
       [12, 16, 20]])

In [76]: X[0,...,0]
Out[76]: array(0)

In [77]: Y[0,...,0]
Out[77]: array([0, 4, 8])

This makes it easy to manipulate only one dimension at a time.

One thing - You can have only one ellipsis in any given indexing expression, or your expression would be ambiguous about how many : should be put in each.