Numpy multi-dimensional array indexing swaps axis order - python

I am working with multi-dimensional Numpy arrays. I have noticed some inconsistent behavior when accessing these arrays with other index arrays. For example:
import numpy as np
start = np.zeros((7,5,3))
a = start[:,:,np.arange(2)]
b = start[0,:,np.arange(2)]
c = start[0,:,:2]
print 'a:', a.shape
print 'b:', b.shape
print 'c:', c.shape
In this example, I get the result:
a: (7, 5, 2)
b: (2, 5)
c: (5, 2)
This confuses me. Why do "b" and "c" not have the same dimensions? Why does "b" swap the axis order, but not "a"?
I have been able to design my code around these inconsistencies thanks to lots of unit tests, but understanding what is going on would be appreciated.
For reference, I am using Python 2.7.3, and Numpy 1.6.2 via MacPorts.

Syntactically, this looks like an inconsistency, but semantically, you're doing two very different things here. In your definition of a and b, you're doing advanced indexing, sometimes called fancy indexing, which returns a copy of the data. In your definition of c, you're doing basic slicing, which returns a view of the data.
To tell the difference, it helps to understand how indices are passed to python objects. Here are some examples:
>>> class ShowIndex(object):
... def __getitem__(self, index):
... print index
...
>>> ShowIndex()[:,:]
(slice(None, None, None), slice(None, None, None))
>>> ShowIndex()[...,:]
(Ellipsis, slice(None, None, None))
>>> ShowIndex()[0:5:2,::-1]
(slice(0, 5, 2), slice(None, None, -1))
>>> ShowIndex()[0:5:2,np.arange(3)]
(slice(0, 5, 2), array([0, 1, 2]))
>>> ShowIndex()[0:5:2]
slice(0, 5, 2)
>>> ShowIndex()[5, 5]
(5, 5)
>>> ShowIndex()[5]
5
>>> ShowIndex()[np.arange(3)]
[0 1 2]
As you can see, there are many different possible configurations. First, individual items may be passed, or tuples of items may be passed. Second, the tuples may contain slice objects, Ellipsis objects, plain integers, or numpy arrays.
Basic slicing is activated when you pass only objects like int, slice, or Ellipsis objects, or None (which is the same as numpy.newaxis). These can be passed singly or in a tuple. Here's what the docs have to say about how basic slicing is activated:
Basic slicing occurs when obj is a slice object (constructed by start:stop:step notation inside of brackets), an integer, or a tuple of slice objects and integers. Ellipsis and newaxis objects can be interspersed with these as well. In order to remain backward compatible with a common usage in Numeric, basic slicing is also initiated if the selection object is any sequence (such as a list) containing slice objects, the Ellipsis object, or the newaxis object, but no integer arrays or other embedded sequences.
Advanced indexing is activated when you pass a numpy array, a non-tuple sequence containing only integers or containing subsequences of any kind, or a tuple containing an array or subsequence.
For details on how advanced indexing and basic slicing differ, see the docs (linked to above). But in this particular case, it's clear to me what's happening. It has to do with the following behavior when using partial indexing:
The rule for partial indexing is that the shape of the result (or the interpreted shape of the object to be used in setting) is the shape of x with the indexed subspace replaced with the broadcasted indexing subspace. If the index subspaces are right next to each other, then the broadcasted indexing space directly replaces all of the indexed subspaces in x. If the indexing subspaces are separated (by slice objects), then the broadcasted indexing space is first, followed by the sliced subspace of x.
In your definition of a, which uses advanced indexing, you effectively pass the sequence [0, 1] in as the third item of the tuple, and since no broadcasting happens (because there is no other sequence), everything happens as expected.
In your definition of b, also using advanced indexing, you effectively pass two sequences, [0], the first item (which is converted into an intp array), and [0, 1], the third item. These two items are broadcast together, and the result has the same shape as the third item. However, since broadcasting has happened, we're faced with a problem: where in the new shape tuple do we insert the broadcasted shape? As the docs say,
there is no unambiguous place to drop in the indexing subspace, thus it is tacked-on to the beginning.
So the 2 that results from broadcasting is moved to the beginning of the shape tuple, producing an apparent transposition.

Related

Why does NumPy advanced indexing yield different results for list of lists and numpy array?

I have a question about NumPy's advanced indexing.
I found this question, but I guess my question is slightly different.
In the example below x_array is the expected result. But when I tried the same with a list the result is different.
From the numpy doc:
Advanced indexing is triggered when the selection object, obj, is a
non-tuple sequence object, an ndarray (of data type integer or bool),
or a tuple with at least one sequence object or ndarray (of data type
integer or bool). There are two types of advanced indexing: integer
and Boolean.
import numpy as np
vertices = np.arange(9).reshape((3,3))
idx_list = [[0, 1, 2],
[0, 2, 1]]
x_list = vertices[idx_list]
print('list')
print(x_list)
#this works as expected
idx_array = np.array(idx_list)
x_array = vertices[idx_array]
print('array')
print(x_array)
idx_list should trigger advanced indexing as it is a "non-tuple sequence object?" Or is a list and a tuple the same here and it is "a tuple with at least one sequence object"
Using the list yields the same as when passing the two list entries separated by a comma within the square brackets (one for each dimension).
x_list_2 = vertices[idx_list[0], idx_list[1]]
This is also the behaviour I expect.
In the end it comes down to what is mentioned in https://stackoverflow.com/a/40599589/7919597
From numpy's indexing documentation:
In order to remain backward compatible with a common usage in Numeric,
basic slicing is also initiated if the selection object is any
non-ndarray sequence (such as a list) containing slice objects, the
Ellipsis object, or the newaxis object, but not for integer arrays or
other embedded sequences.
The example with the list triggers an undocumented part of the backward compatibility logic, as described in a comment in the source code:
/*
* Sequences < NPY_MAXDIMS with any slice objects
* or newaxis, Ellipsis or other arrays or sequences
* embedded, are considered equivalent to an indexing
* tuple. (`a[[[1,2], [3,4]]] == a[[1,2], [3,4]]`)
*/
Your answer doesn't explain anything. Here are the ideas:
First, since your idx_list is of dimension 2, something is done in the background:
idx_list = [[0, 1, 2],
[0, 2, 1]]
x_list = vertices[tuple(idx_list)] # wrapping with a `tuple()`
If you do this explicitly, you can get rid of the (annoying) FutureWarning:
FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
(this answers your OP) Second, your expected behaviour(x_array, shape(2,3,3)):
[[[0 1 2]
[3 4 5]
[6 7 8]]
[[0 1 2]
[6 7 8]
[3 4 5]]]
can be done in either (as your OP):
vertices[np.array(idx_list)]
or by list, too:
vertices[tuple([list])]
# or this shorthand with warning
# vertices[[list]]
Notice that I've wrapped one additional bracket([list]) to make sure that the first component of the resulting tuple is of dimension 2, which results in the same meaning as np.array(idx_list)

what is this 'sequence of arrays' created by numpy's np.stack function in Python?

I'm reading the documentation on np.stack:
Join a sequence of arrays along a new axis.
output: ndarray
So np.stack is going to take, say, 2 numpy array and return what? It will return a new array, which contains a, um, sequence of arrays?
I can't visualize what an array consisting of a sequence of arrays is, so how about I run a little experiment:
import numpy as np
from random import randint
arrays = [2.5 * np.random.randn(1,2)+ 3 for _ in range(1,3)]
arrays = [a.astype(int) for a in arrays]
arrays
This gives me:
[array([[1, 2]]), array([[2, 3]])]
Then,
np.stack(arrays, axis=0)
gives
array([[[1, 2]],
[[2, 3]]])
Pretending for a second that the printout is not basically unreadable (10 square brackets, really?), I see what appears to be 2 arrays, in an array, in a ordered sequence. I guess the documentation is correct, but I still have no mental visualization of what this object looks like.
Maybe I should look at the dimensions:
np.stack(arrays, axis=0).shape
gives
(2, 1, 2)
So we have two rows, one column, and two layers of this? Isn't that one array?
My questions are:
What exactly is a 'sequence of arrays' and how does an array possess a notion of order, as does a sequence by definition?
Why would anyone ever want a 'sequence of arrays' anyway, whatever that is, as opposed to concatenating multiple arrays into one (as the .shape implies this really is anyways)?
Why did they call this function "stack" and how does the colloquial use of this word attempt to be helpful?
Thanks.
EDIT too many good answers...having trouble selecting one for the checkmark...
What exactly is a 'sequence of arrays' and how does an array possess a notion of order, as does a sequence by definition?
A sequence is an abstract-data type that, as you intuited, is an ordered collection of items. In Python, a sequence can be assumed to implement __getitem__ and __len__, that is, it supports bracketed indexing, e.g. seq[0] or seq[1], and has a len. It also implements __contains__, __iter__, __reversed__, index, and count. Built-in sequence types include list, tuple, str, bytes, range and memoryview. A numpy.ndarray is a sequence.
Why would anyone ever want a 'sequence of arrays' anyway, whatever that is, as opposed to concatenating multiple arrays into one (as the .shape implies this really is anyways)?
The documentation is letting you know that the function accepts any sequence of arrays. A multidimensional array is a sequence of arrays, itself, or you can pass a list or tuple of arrays you want to "stack" on top (or up against) each other. The function returns a numpy.ndarray, which is any numpy array (n-dimensional array). It is a slightly different operation than concatenate. See below.
Why did they call this function "stack" and how does the colloquial use of this word attempt to be helpful?
Because it stacks stuff together. According to the docs for np.stack, it "Join[s] a sequence of arrays along a new axis.", np.concatenate on the other hand: "Join[s] a sequence of arrays along an existing axis."
Looking at the example in the docs is helpful.
>>> a = np.array([1, 2, 3])
>>> b = np.array([2, 3, 4])
>>> np.stack((a, b), axis=0)
array([[1, 2, 3],
[2, 3, 4]])
>>> np.stack((a, b), axis=1)
array([[1, 2],
[2, 3],
[3, 4]])
>>>
np.concatenate does something different:
>>> np.concatenate((a,b))
array([1, 2, 3, 2, 3, 4])
It is one of many related stacking, concatenating, appending operations on np.arrays that come built-in.
I think your problem starts with np.random.randn(1,2) possibly not giving you what you expect. This is going to give you a (1,2) array.
I helps to think of this as a nested list. The "outer" list has one item, and this item is the "inner" list of two items (in fact this is the exact way it is represented inside the array() wrapper).
Now you make a list of two of these arrays. These brackets are outside the array wrapper so it is just a list. the np.stack command, then, moves the brackets inside the wrapper, in a certain way according to the axis command. In this case axis=0, and the number of elements in the 0 axis becomes the number of items in the outer list. The other two dimensions move over, and the shape becomes (2,1,2)
As a list, this would be a list of two items, each item being a list of a single item, this single item being a list of two numbers.
There are many different ways to arrange these arrays, other than stack. The other major one is np.concatenate, which will let you join them along an existing axis (axis=0 will have an output of shape (2,2), while axis=1 will have shape (1,4)) stack is for when you want a new axis to join them along.
In Python parlance, "sequence" is just a thing that can contain other things, has a sense of "order", and can optionally indexed like seq[i]. Python is duck-typed so anything behaving like a sequence can be called one. It can be a list or a tuple, for example. In this case the ordering is definitely important.
The sequence can be built up piece-by-piece, as is common in programming. For example you may download some data using a RESTful API, build an array from each response, and append each one to a list. After you're done with requesting, you can use the accumulated "sequence of arrays" for your computation later.
If you look at the examples on that page it's pretty clear. You have a list of arrays, let's say, l = [a, b] where a and b are arrays. What stack(l) does is to create a new array with a "on top of" b, if the shapes are compatible. Of course there's also concatenate but it isn't the same. stack creates a new dimension, but concatenate connects along the axis you specify, as the name suggests.
In [1]: import numpy as np
# Ten pieces of 3x4 paper, contained in a list, or "sequence"
In [2]: arrays = [np.random.randn(3, 4) for _ in xrange(10)]
# Stack the paper so you get a paperstack with thickness 10 ;)
In [3]: np.stack(arrays, axis=0).shape
Out[3]: (10, 3, 4)
# Join each piece's long side with the next one, so you get a larger piece of paper
In [4]: np.concatenate(arrays, axis=0).shape
Out[4]: (30, 4)
From the release note:
The new function np.stack provides a general interface for joining a sequence of arrays along a new axis, complementing np.concatenate for joining along an existing axis.
Numpy arrays:
import numpy
a = np.array([1,2,3])
a.shape gives (3,) -> indicates a 1D array
b = np.array([2, 3, 4])
np.stack((a,b),axis=0) -->Stack the two arrays row-wise which means just put the first arrow on top of the second array
np.stack((a,b),axis=1) -->columns wise stack
Now answering your questions in the same order:
A sequence of arrays in the above example is a and b , a and b together form a sequence of arrays.
The reason this function is useful is - Imagine if you are trying to build a matrix where each row (array) is obtained from some special function specific to that row number , np.stack allows you to stack each array and create a 2D array with ease.
In the true sense , stack means placing things over something. In this , you are just adding an array on top of another array.
Hope this helps!
Edit - Based on a comment-
The difference between concatenate and stack (apart from functionality)-
Concat is more like merge
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
np.stack((a,b),axis=0) ->error
np.concatenate((a,b),axis=0) -->no error
np.stack is just concatenate on a new axis.
Your arrays is a list of 2 (1,2) shaped arrays. It is a sequence.
In [325]: arrays = [np.array([[1, 2]]), np.array([[2, 3]])]
In [326]: arrays
Out[326]: [array([[1, 2]]), array([[2, 3]])]
In [327]: arrays[0].shape
Out[327]: (1, 2)
In [328]: np.array(arrays)
Out[328]:
array([[[1, 2]],
[[2, 3]]])
In [329]: _.shape
Out[329]: (2, 1, 2)
That's the same as if we took np.array of this list of lists
np.array([[[1, 2]],[[2, 3]]])
np.stack with the default axis=0 does the same thing:
In [332]: np.stack(arrays).shape
Out[332]: (2, 1, 2)
It joins the 2 (1,2) arrays on a new axis (dimension). It might be clearer if arrays contained 3 arrays, producing a (3,1,2) array.
np.stack give more flexibility, allowing us to join the arrays on other new axes (I'll flag that with '):
In [335]: np.stack(arrays, axis=0).shape
Out[335]: (2', 1, 2)
In [336]: np.stack(arrays, axis=1).shape
Out[336]: (1, 2', 2)
In [337]: np.stack(arrays, axis=2).shape
Out[337]: (1, 2, 2')
Regarding the name, it's a spin off of hstack, vstack, and column_stack, which have been part of numpy for a long time. Those are all specialized applications of np.concatenate. And if you look at the code, np.source(np.stack), you'll see that np.stack is also just an application of concatenate.
The name is not any more exotic than a 'stack of papers'.
Your arrays list contains 2' (1,2) shaped arrays. np.stack adds a dimension to each (with reshape). For the default axis=0, it reshapes them to (1',1,2). Then it does a concatenate on the first axis, resulting (2',1,2) shape.

Building a tuple containing colons to be used to index a numpy array

I've created a class for dealing with multidimensional data of a specific type. This class has three attributes: A list containing the names of the axes (self.axisNames); a dictionary containing the parameter values along each axis (self.axes; keyd using the entries in axisNames); and a numpy array containing the data, with a dimension for each axis (self.intensityArr).
The class also has functions that dynamically add new axes depending on what I need for a specific case, which makes indexing the intensityArr a tricky proposition. To make indexing better I've started writing a function to build the index I need:
Inside class:
def indexIntensityArr(self,indexSpec):
# indexSpec is a dictionary containing axisName:indexVal entries (indexVal is an int)
# I want the function to return a tuple for use in indexing (see below def)
indexList = []
for axis in self.axisNames:
if axis in indexSpec:
indexList.append(indexSpec[axis])
else:
# <do something to add : to index list (tuple)>
return tuple(indexList)
Outside class:
# ... create an instance of my class called myBlob with 4 dimensions ...
mySpec = {'axis1':10,'axis3':7}
mySlicedArr = myBlob.intensityArr[myBlob.indexIntensityArr(mySpec)]
I expect the above to result in mySlicedArr being a 2-dimensional array.
What do I need to put in the 'else' clause to get an : (or equivalent) in the tuple I use to index the intensityArr? Is this perhaps a bad way to solve the problem?
Inside indexing [], a : is translated to a slice, and the whole thing is passed to __getitem__ as a tuple
indexList = []
for axis in self.axisNames:
if axis in indexSpec:
indexList.append(indexSpec[axis])
else:
indexList.append(slice(None))
There are several numpy functions that use an indexing trick like this - that is build up a tuple of index values and slices. Or if they need to vary it, they'll start with a list, which can mutate, and convert it to a tuple right before use. (e.g. np.apply_along_axis)
Yes, the full spec for slice is slice(start, stop, step), with start and stop optional. Same as for np.arange or range. And None is equivalent to the unspecified values in a : expression.
A little custom class in np.lib.index_tricks.py translates the : notation into slices:
In [61]: np.s_[:,:1,0:,::3]
Out[61]:
(slice(None, None, None),
slice(None, 1, None),
slice(0, None, None),
slice(None, None, 3))
To add to hpaulj's answer, you can very simply extend your setup to make it even more generic by using np.s_. The advantage of using this over slice is that you can use numpy's slice syntax more easily and transparently. For example:
mySpec = {'axis1': np.s_[10:15], 'axis3': np.s_[7:8]}
mySlicedArr = myBlob.intensityArr[myBlob.indexIntensityArr(mySpec)]
(Extra info: np.s_[7:8] retrieves only the 7th column, but it preserves the dimension, i.e. your sliced array will still be 4D with a shape of 1 in that dimension: very useful for broadcasting).
And if you want to use the same syntax in your function definition as well:
indexList = []
for axis in self.axisNames:
if axis in indexSpec:
indexList.append(indexSpec[axis])
else:
indexList.append(np.s_[:])
return tuple(indexList)
All of this can be done equally well with slice. You would specify np.s_[10:15] as slice(10, 15), and np.s_[:] as slice(None), as hpaulj says.

Shapes of numpy arrays

Been working with numpy for a while now. Just when I think I have arrays figured out, though, it throws me another curve. For instance, I construct the 3D array pltz, and then
>>> gridset2 = range(0, pltx.shape[2], grdspc)
>>> pltz[10,:,gridset2].shape
(17, 160)
>>> pltz[10][:,gridset2].shape
(160, 17)
Why on Earth are the two shapes different?
Since your indexing expression has both a : and a list in it, NumPy needs to apply both the basic and advanced indexing rules, and the way they interact is kind of weird. The relevant documentation is here, and you should consult it if you want to know the full details. I'll focus on the part that causes this shape mismatch.
When all components of the indexing expression that use advanced indexing are next to each other, dimensions of the result coming from advanced indexing are placed into the result in the position of the dimensions they replace. Advanced indexing components are array-likes, such as arrays, lists, and scalars; scalars can also be used in basic indexing, but for this purpose, they're considered advanced. Thus, if arr.shape == (10, 20, 30) and ind.shape = (2, 3, 4), then
arr[:, ind, :].shape == (10, 2, 3, 4, 30)
Your first expression falls into this case.
On the other hand, if components of the indexing expression that use advanced indexing are separated by components that use basic indexing, there is no unambiguous place to insert the advanced indexing dimensions. For example, with
arr[ind, :, ind]
the result needs to have dimensions of length 2, 3, 4, and 20, and there's no good place to stick the 20.
When advanced indexing components are separated by basic indexing components, NumPy sticks all dimensions resulting from advanced indexing at the start of the result array. Basic indexing components are :, ..., and np.newaxis (None). Your second expression falls into this case.
Since your second expression has advanced indexing components separated by basic indexing components and your first expression doesn't, your two expressions use different indexing rules. To avoid this, you could separate the basic indexing and advanced indexing into two stages, or you could replace the basic indexing with equivalent advanced indexing. Whatever you do, I recommend putting an explanatory comment above such code.
You should tell us the lenth of gridset2 and the shape of pltz.
But I've deduced from the documentation that user2357112 gave us that
len(gridset2) == 17
pltz.shape[1] == 160
http://docs.scipy.org/doc/numpy-1.10.0/reference/arrays.indexing.html#combining-advanced-and-basic-indexing
The advanced indexes are separated by a slice, ellipsis or newaxis.
For example x[arr1, :, arr2].
The advanced indexes are all next to
each other. For example x[..., arr1, arr2, :] but not x[arr1, :, 1]
since 1 is an advanced index in this regard.
In the first case, the
dimensions resulting from the advanced indexing operation come first
in the result array, and the subspace dimensions after that. In the
second case, the dimensions from the advanced indexing operations are
inserted into the result array at the same spot as they were in the
initial array
>>> pltz[10,:,gridset2].shape
(17, 160)
This is the first case in the quote, a slice in the middle. gridset2 is advanced indexing (e.g. [1,2,3,...]). It is put first; the [10,:] subspace is placed after.
>>> pltz[10][:,gridset2].shape
(160, 17)
with pltz[10], the new array (a view) is 2d `(160,N)'. It now puts the size 17 dim last, the 2nd case in the documentation.

Update submatrix with R-like or MATLAB-like syntax in NumPy and Python

I was a R user and I am learning Python (numpy in particular), but I cannot perform a simple task of updating a submatrix in Python which can be very easily done in R.
So I have 2 problems.
First one is say we have a 4 by 4 matrix
A = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]])
and a 2 by 2 matrix
B = np.array([[100,200],[300,400]]).
I want to grab a 2 by 2 submatrix of A formed by taking 2nd and 4th rows and columns (array([[6,8][14,16]])) and replace it to B.
I can pull the correct matrix by doing
m = [1,3]
A[m][:,m]
but nothing happens to A even after I update it to B. That is
A[m][:,m] = B
print A
and A comes out to be the same.
Is there a way to do this without using loops or maybe with a relatively simple code?
The second problem which is relatively easy is that in R, we can subset matrix with True and False. From above A, we can subset the same 2 by 2 matrix by
m = [F, T, F, T]
A[m,m]
However, in Python the same code does not seem to work because True is 1 and False is 0. I think I can convert [F,T,F,T] to [1,3] and subset, but I thought there may be a one step method to do this.
Is there a easy way to do the same operation in Python when the index is given in terms of True and False?
For part 1, from NumPy for MATLAB Users, there are examples showing both read-only and mutable access to arbitrary slices.
The read-only pattern is similar to what you already describe, A[:, m][m]. This slices the columns first, then the rows, and provides a read-only view of the returned data.
To obtain clean indices for mutating the sub-array, a convenience function is provided, np.ix_. It will stitch together its arguments into an R-like or MATLAB-like slice:
indxs = np.ix_([1,3], [1,3])
A[indxs] = B
The reason behind this is that NumPy follows certain shape-conformability rules (called "broadcasting" rules) about how to infer the shapes you intended based on the shapes present in the data. When NumPy does this for a row index and column index pair, it tries to pair them up element-wise.
So A[[1, 3], [1, 3]] under NumPy's chosen conventions, is interpreted as "Fetch for me the value of A at index (1,1) and at index (3,3)." Which is different than the conventions for this same syntax in MATLAB, Octave, or R.
If you want to get around this manually, without np.ix_, you still can, but you must write down your indices to take advantage of NumPy's broadcasting rules. What this means is you have to give NumPy a reason to believe that you want a 2x2 grid of indices instead of a 1x2 list of two specific points.
You can trick it into believing this by making your row entries into lists themselves: rows = [[1], [3]]. Now when NumPy examines the shape of this (1 x 2 instead of 1 x nothing) it will say, 'aha, the columns had better also be 1 x 2' and automatically promote the list of columns to match individually with each possible row. That's why this also will work:
A[[[1], [3]], [1, 3]] = B
For the second part of your question, the issue is that you want to let NumPy know that your array of [False, True, False, True] is a boolean array and should not be implicitly cast as any other type of array.
This can be done in many ways, but one easy way is to construct an np.array of your boolean values, and its dtype will be bool:
indxs = np.array([False, True, False, True])
print A[:, indxs][indxs] # remember, this one is read only
A[np.ix_(indxs, indxs)] = B
Another helpful NumPy convenience tool is np.s_, which is not a function (it is an instance of numpy.lib.index_tricks.IndexExpression) but can be used kind of like one.
np.s_ allows you to use the element-getting syntax (called getitem syntax in Python, after the __getitem__ method that any new-style class instances will have). By way of example:
In [60]: np.s_[[1,3], [1,3]]
Out[60]: ([1, 3], [1, 3])
In [61]: np.s_[np.ix_([1,3], [1,3])]
Out[61]:
(array([[1],
[3]]), array([[1, 3]]))
In [62]: np.s_[:, [1,3]]
Out[62]: (slice(None, None, None), [1, 3])
In [63]: np.s_[:, :]
Out[63]: (slice(None, None, None), slice(None, None, None))
In [64]: np.s_[-1:1:-2, :]
Out[64]: (slice(-1, 1, -2), slice(None, None, None))
So np.s_ basically just mirrors back to what the slice index object will look like if you were to place it inside the square brackets in order to access some array's data.
In particular, the first two of these np.s_ examples shows you the difference between plain A[[1,3], [1,3]] and the use of np.ix_([1,3], [1,3]) and how they result in different slices.

Categories