Python: Slicing a Multi-Dimensional Array

Python: slicing a multi-dimensional array

If you use numpy, this is easy:

slice = arr[:2,:2]

or if you want the 0's,

slice = arr[0:2,0:2]

You'll get the same result.

*note that slice is actually the name of a builtin-type. Generally, I would advise giving your object a different "name".


Another way, if you're working with lists of lists*:

slice = [arr[i][0:2] for i in range(0,2)]

(Note that the 0's here are unnecessary: [arr[i][:2] for i in range(2)] would also work.).

What I did here is that I take each desired row 1 at a time (arr[i]). I then slice the columns I want out of that row and add it to the list that I'm building.

If you naively try: arr[0:2] You get the first 2 rows which if you then slice again arr[0:2][0:2], you're just slicing the first two rows over again.

*This actually works for numpy arrays too, but it will be slow compared to the "native" solution I posted above.

Numpy multi-dimensional array slicing

You can use np.r_ to concatenate slice objects:

newarr [:,:,1:10] = oldarr[:,:,np.r_[1:7,8:11]] 

Example:

np.r_[1:4,6:8]
array([1, 2, 3, 6, 7])

How is multidimensional array slicing/indexing implemented in numpy?

We can verify your first level inferences with a simple class:

In [137]: class Foo():
...: def __getitem__(self,arg):
...: print(arg)
...: return None
...:
In [138]: f=Foo()
In [139]: f[1]
1
In [140]: f[::3]
slice(None, None, 3)
In [141]: f[,]
File "<ipython-input-141-d115e3c638fb>", line 1
f[,]
^
SyntaxError: invalid syntax

In [142]: f[:,]
(slice(None, None, None),)
In [143]: f[:,:3,[1,2,3]]
(slice(None, None, None), slice(None, 3, None), [1, 2, 3])

numpy uses code like this in np.lib.index_tricks.py to implement "functions" like np.r_ and np.s_. They are actually class instances that use an index syntax.

It's worth noting that it's the comma, most so than the () that creates a tuple:

In [145]: 1,
Out[145]: (1,)
In [146]: 1,2
Out[146]: (1, 2)
In [147]: () # exception - empty tuple, no comma
Out[147]: ()

That explains the syntax. But the implementation details are left up to the object class. list (and other sequences like string) can work with integers and slice objects, but give an error when given a tuple.

numpy is happy with the tuple. In fact passing a tuple via getitem was added years ago to base Python because numpy needed it. No base classes use it (that I know of); but user classes can accept a tuple, as my example shows.

As for the numpy details, that requires some knowledge of numpy array storage, including the role of the shape, strides and data-buffer. I'm not sure if I want get into those now.

A few days ago I explored one example of multidimensional indexing, and discovered some nuances that I wasn't aware of (or ever seen documented)

view of numpy with 2D slicing

For most of us, understanding the how-to of indexing is more important than knowing the implementation details. I suspect there are textbooks, papers and even Wiki pages that describe 'strided' multidimensional indexing. numpy isn't the only place that uses it.

https://numpy.org/doc/stable/reference/arrays.indexing.html

This looks like a nice intro to numpy arrays

https://ajcr.net/stride-guide-part-1/

Slicing multi-dimensional array with another array

You could do this:

Q[tuple(s)]

Or this:

np.take(Q, s)

Both of these yield array([0.58383736, 0.80486868]).

I'm afraid I don't have a great intuition for exactly why the tuple version of s works differently from indexing with s itself. The other thing I intuitively tried is Q[*s] but that's a syntax error.

tuple as index in multidimensional array together with slicing

You can do the following:

import numpy as np

A = np.arange(12).reshape((2, 3, 2))
print(A)

x = [1, 1]
print(A[(slice(None), *x)])

You can use slice(None) instead of : to build a tuple of slices. The tuple environment allows for value unpacking with the * operator.

Output:

[[[ 0  1]
[ 2 3]
[ 4 5]]

[[ 6 7]
[ 8 9]
[10 11]]]

[3 9]

To verify it matches:

import numpy as np

A = np.arange(12).reshape((2, 3, 2))
x = [1, 1]
s = (slice(None), *x)
print(np.allclose(A[s], A[:, 1, 1])) # True

*This is a modification of answers found here: Slicing a numpy array along a dynamically specified axis


Edit to reflect edit on question and comment:

To clarify, you can unpack any iterable you like in the tuple environment. The * operator functions normally in within the tuple. Order your elements however you like. Mix in different iterables, types, slice(None), how ever you want to build your slices, as long as you end up with a valid sequence of values, it will behave as expected.

import numpy as np

A = np.arange(12).reshape((2, 3, 2))
t = [True, False]
x = [1, 1]
print(np.allclose(A[(*t, *x)], A[True, False, 1, 1])) # True

You can also add full lists as well in the tuple:

print(np.allclose(A[(t, *x)], A[[True, False], 1, 1]))  # True

Python array slicing -- How can 2D array slicing be implemented?

obj[,:3] is not valid python so it will raise a SyntaxError -- Therefore, you can't have that syntax in your source file. (It fails when you try to use it on a numpy array as well)

Getting indices of different lengths to slice a multidimensional numpy array

I think this should work:

m = np.arange(F.shape[1]) < K
Rnew = R.copy()
Rnew[np.nonzero(m)[0], np.argsort(F)[m]] += 1

Since the first line uses broadcasting, np.tile() is not needed.

Notice that there is a possible ambiguity of the results: since each row of F has values that are repeated several times (e.g. 0.1 in the first row and -0.4 in the second) np.argsort() may give different orderings of elements of F, depending on how these equal values get sorted. This may change which entries of the matrix R will get incremented. For example, instead of incrementing R[0, 7], R[0, 8], and R[1, 7], the code may increment the entries R[0, 2], R[0, 9] and R[1, 1]. To get unambiguous results you can specify that np.argsort() must use a stable sorting algorithm, which will preserve the relative order of elements with equal values:

m = np.arange(F.shape[1]) < K
Rnew = R.copy()
Rnew[np.nonzero(m)[0], np.argsort(F, kind="stable")[m]] += 1

In this particular example this will increment the entries R[0, 2], R[0, 7] and R[1, 1]. You need to decide if this is the result that meets your needs.



Related Topics



Leave a reply



Submit