1

Consider the following:

import numpy as np

arr = np.arange(3 * 4 * 5).reshape((3, 4, 5))

If I slice arr using slices, I get, e.g.:

arr[:, 0:2, :].shape
# (3, 2, 5)

If now I slice arr using a mixture of slice() and tuple(), I get:

arr[:, (0, 1), :].shape
# (3, 2, 5)

np.all(arr[:, (0, 1), :] == arr[:, :2, :])
# True

and:

arr[:, :, (0, 1)].shape
# (3, 4, 2)

np.all(arr[:, :, (0, 1)] == arr[:, :, :2])
# True

HOWEVER, if I do:

arr[:, (0, 1), (0, 1)].shape
# (3, 2)

which is basically, arr[:, 0, 0] and arr[:, 1, 1] concatenated.

I was expecting to get:

arr[:, (0, 1), (0, 1)].shape
# (3, 2, 2)

np.all(arr[:, (0, 1), (0, 1)] == arr[:, :2, :2])
# True

but it is clearly not the case.

If I concatenate two separate slicing, I would be able to obtain the desired result, i.e.:

arr[:, (0, 1), :][:, :, (0, 1)].shape
# (3, 2, 2)

np.all(arr[:, (0, 1), :][:, :, (0, 1)] == arr[:, :2, :2])
# True

Is it possible to obtain the same result as arr[:, (0, 1), :][:, :, (0, 1)] but USING a single slicing?

Now, this example is not so interesting, because I could replace the tuple() with a slice(), but if that is not true, it all becomes a lot more relevant, e.g.:

arr[:, (0, 2, 3), :][:, :, (0, 2, 3, 4)]
# [[[ 0  2  3  4]
#   [10 12 13 14]
#   [15 17 18 19]]

#  [[20 22 23 24]
#   [30 32 33 34]
#   [35 37 38 39]]

#  [[40 42 43 44]
#   [50 52 53 54]
#   [55 57 58 59]]]

for which arr[:, (0, 2, 3), (0, 2, 3, 4)] would be a much more convenient syntax.

EDIT

@Divakar @hpaulj and @MadPhysicist comments / answers pointed towards a properly broadcasted Iterable to be equivalent of multiple concatenated slicing.

However, this is not the case, e.g.:

s = np.ix_((0, 1), (0, 1, 2, 3))
arr[s[0], slice(3), s[1]]
# [[[ 0  5 10]
#   [ 1  6 11]
#   [ 2  7 12]
#   [ 3  8 13]]
# 
#  [[20 25 30]
#   [21 26 31]
#   [22 27 32]
#   [23 28 33]]]

But:

arr[(0, 1), :, :][:, :3, :][:, :, (0, 1, 2, 3)]
# [[[ 0  1  2  3]
#   [ 5  6  7  8]
#   [10 11 12 13]]
# 
#  [[20 21 22 23]
#   [25 26 27 28]
#   [30 31 32 33]]]

and:

np.all(arr[:2, :3, :4] == arr[(0, 1), :, :][:, :3, :][:, :, (0, 1, 2, 3)])
# True

np.all(arr[s[0], slice(3), s[1]] == arr[(0, 1), :, :][:, :3, :][:, :, (0, 1, 2, 3)])
# False
Community
  • 1
  • 1
norok2
  • 25,683
  • 4
  • 73
  • 99
  • With `np.ix_` - `x,y = np.ix_([0,2,3],[0,2,3,4]); arr[:,x,y]`. Or `arr[:,np.array([0,2,3])[:,None],[0,2,3,4]]`. – Divakar May 27 '19 at 15:06
  • You are expecting a MATLAB style of indexing blocks. But to get elements, such as diagonal in MATLAB you have to use `sub2ind` to convert subscripts to flat indexing. In effect a thing that is simple in MATLAB is (just) a bit more complex in `numpy`, and visa versa for the other. – hpaulj May 27 '19 at 16:42
  • @hpaulj This does not work the same way as multiple concatenated slicings, and I do not see a way to make it work in a single slice. – norok2 May 28 '19 at 09:57
  • Putting the slice in the middle raise a dimension order issue that's described in the doxs. https://stackoverflow.com/q/55829631/901925 – hpaulj May 28 '19 at 10:48
  • I need to test this to be sure, but I think slice could be replaced by another tuple or arange in the ix_ arguments. – hpaulj May 28 '19 at 10:55
  • @hpaulj In principle you can do that, but in general you cannot replace a `slice()` without knowing a priori what the is the size of the object to be sliced, so that a function, e.g. `ixb()` to be used transparently like `arr[ixb(a, b, c)]` in place of `arr[a, :, :][:, b, :][:, :, c]` no matter what `a`, `b` and `c` are could not be crafted this way. – norok2 May 28 '19 at 11:23

2 Answers2

2

If you want to achieve the ability to slice your array programmatically, the answer is slice objects, not sequences of indices. For example, :2 becomes slice(None, 2):

np.all(arr[:, slice(None, 2), slice(None, 2)] == arr[:, :2, :2])

Slices select a portion of an axis. Index arrays do not: they select individual elements. The shape of the index determines the shape of the output in that case.

If you want to select arbitrary indices across multiple dimensions, the shape of the index arrays must be same shape as the desired output, or broadcast to it:

arr[:, [[0], [2], [3]], [[0, 2, 3, 4]]]
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • `:2` -> `slice(None, 2)` is irrelevant to the question. As far as same-shape broadcasting is concerned, is NumPy supporting a programmatic way of getting from e.g. `slice(None), (0, 2, 3), (0, 2, 3, 4)` to `slice(None), [[0], [2], [3]], [[0, 2, 3, 4]]]`? `np.ix_()` does half of the trick, but does not mix with `slice()`. – norok2 May 27 '19 at 15:28
  • 1
    @norok. Not entirely irrelevant. Hopefully I've clarified why I included that bit. – Mad Physicist May 27 '19 at 15:37
  • BUT this does not seem to work in general, see the updated question. – norok2 May 28 '19 at 09:59
1

The ix_ can be combined with a slice via tuple concatenation:

In [568]: arr[(slice(None),)+np.ix_((0,2,3),(0,2,3,4))]                                               
Out[568]: 
array([[[ 0,  2,  3,  4],
        [10, 12, 13, 14],
        [15, 17, 18, 19]],

       [[20, 22, 23, 24],
        [30, 32, 33, 34],
        [35, 37, 38, 39]],

       [[40, 42, 43, 44],
        [50, 52, 53, 54],
        [55, 57, 58, 59]]])

The ix_ tuple:

In [569]: np.ix_((0,2,3),(0,2,3,4))                                                                   
Out[569]: 
(array([[0],
        [2],
        [3]]), array([[0, 2, 3, 4]]))

tuple concatenation:

In [570]: (slice(None),)+np.ix_((0,2,3),(0,2,3,4))                                                    
Out[570]: 
(slice(None, None, None), array([[0],
        [2],
        [3]]), array([[0, 2, 3, 4]]))
In [571]: arr[_]                                                                                      
Out[571]: 
array([[[ 0,  2,  3,  4],
        [10, 12, 13, 14],
        [15, 17, 18, 19]],
        ....

The idea of building a tuple with Python code, and then using it in an indexing expression is used in a number of numpy functions.

Another way to construct this indexing tuple is:

In [581]: arr[(slice(None), *np.ix_((0,2,3),(0,2,3,4)))]                                              
Out[581]: 
array([[[ 0,  2,  3,  4],
        [10, 12, 13, 14],
        [15, 17, 18, 19]],
       ...

This takes advantage of the Python '*' unpacking within a tuple (but not directly within an indexing expression).

It's in effect a way of doing arr[:,*ix_[...]], which produces a syntax error.

In sum:

  • numpy operates within Python, so is subject to all of its syntactic rules

  • numpy makes the 'element' indexing easy. The block indexing is a bit harder, though it actually follows the same broadcasting rules.

  • MATLAB has its own language and syntax. It makes the block indexing easy, but element indexing, as for example getting a diagonal, is more awkward, requiring an extra sub2ind function call.

hpaulj
  • 221,503
  • 14
  • 230
  • 353