NumPy arrays iterate over the left-most axis first. Thus if B has shape (2,3,4), then B[0] has shape (3,4) and B[1] has shape (3,4). In this sense, you could think of B as 2 arrays of shape (3,4). You can sort of see the two arrays in the repr of B:

In [233]: B = np.arange(2*3*4).reshape((2,3,4))
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],       <-- first (3,4) array 
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],      <-- second (3,4) array 
        [20, 21, 22, 23]]])

You can also think of B as containing four 2x3 arrays by iterating over the last index first:

for i in range(4):
    print(B[:,:,i])

# [[ 0  4  8]
#  [12 16 20]]
# [[ 1  5  9]
#  [13 17 21]]
# [[ 2  6 10]
#  [14 18 22]]
# [[ 3  7 11]
#  [15 19 23]]

but you could just as easily think of B as three 2x4 arrays:

for i in range(3):
    print(B[:,i,:])

# [[ 0  1  2  3]
#  [12 13 14 15]]
# [[ 4  5  6  7]
#  [16 17 18 19]]
# [[ 8  9 10 11]
#  [20 21 22 23]]

NumPy arrays are completely flexible this way. But as far as the repr of B is concerned, what you see corresponds to two (3x4) arrays since B iterates over the left-most axis first.

for arr in B:
    print(arr)

# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]
# [[12 13 14 15]
#  [16 17 18 19]
#  [20 21 22 23]]
Answer from unutbu on Stack Overflow
Top answer
1 of 3
28

NumPy arrays iterate over the left-most axis first. Thus if B has shape (2,3,4), then B[0] has shape (3,4) and B[1] has shape (3,4). In this sense, you could think of B as 2 arrays of shape (3,4). You can sort of see the two arrays in the repr of B:

In [233]: B = np.arange(2*3*4).reshape((2,3,4))
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],       <-- first (3,4) array 
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],      <-- second (3,4) array 
        [20, 21, 22, 23]]])

You can also think of B as containing four 2x3 arrays by iterating over the last index first:

for i in range(4):
    print(B[:,:,i])

# [[ 0  4  8]
#  [12 16 20]]
# [[ 1  5  9]
#  [13 17 21]]
# [[ 2  6 10]
#  [14 18 22]]
# [[ 3  7 11]
#  [15 19 23]]

but you could just as easily think of B as three 2x4 arrays:

for i in range(3):
    print(B[:,i,:])

# [[ 0  1  2  3]
#  [12 13 14 15]]
# [[ 4  5  6  7]
#  [16 17 18 19]]
# [[ 8  9 10 11]
#  [20 21 22 23]]

NumPy arrays are completely flexible this way. But as far as the repr of B is concerned, what you see corresponds to two (3x4) arrays since B iterates over the left-most axis first.

for arr in B:
    print(arr)

# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]
# [[12 13 14 15]
#  [16 17 18 19]
#  [20 21 22 23]]
2 of 3
9

I hope the below example would clarify the second part of your question where you have asked about getting a 2X3 matrics when you type print(B[:,:,1])

import numpy as np
B = [[[1,2,3,4],
  [5,6,7,8],
  [9,10,11,12]],

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

B = np.array(B)
print(B)
print()
print(B[:,:,1])

[[[ 1  2  3  4]
  [ 5  6  7  8]
  [ 9 10 11 12]]

 [[13 14 15 16]
  [17 18 19 20]
  [21 22 23 24]]]

[[ 2  6 10]
 [14 18 22]]

Since the dimension of B is 2X3X4, It means you have two matrices of size 3X4 as far as repr of B is concerned

Now in B[:,:,1] we are passing : , : and 1. First : indicates that we are selecting both the 3X4 matrices. The second : indicates that we are selecting all the rows from both the 3X4 matrices. The third parameter 1 indicates that we are selecting only the second column values of all the rows from both the 3X4 matrices. Hence we get

[[ 2  6 10]
 [14 18 22]]
🌐
W3Schools
w3schools.com › python › numpy › numpy_array_slicing.asp
NumPy Array Slicing
Slice elements from the beginning to index 4 (not included): import numpy as np arr = np.array([1, 2, 3, 4, 5, 6, 7]) print(arr[:4]) Try it Yourself »
🌐
Regenerativetoday
regenerativetoday.com › indexing-and-slicing-of-1d-2d-and-3d-arrays-using-numpy
Indexing and Slicing of 1D, 2D and 3D Arrays Using Numpy – Regenerative
In this case, 2 is the starting point and 3 is the interval. So the returning array stars from the element in index two. After that it takes every third element of the array till the end. Say, we don’t need till the end. We only want to output till -4. In that case we can further slice it.
🌐
GeeksforGeeks
geeksforgeeks.org › python › python-slicing-multi-dimensional-arrays
Python Slicing Multi-Dimensional Arrays - GeeksforGeeks
July 1, 2026 - Rows and columns are the basic components of a multi-dimensional array. NumPy allows to extract them easily using slicing, making it simple to access specific parts of the data.
🌐
Python Like You Mean It
pythonlikeyoumeanit.com › Module3_IntroducingNumpy › AccessingDataAlongMultipleDimensions.html
Accessing Data Along Multiple Dimensions in an Array — Python Like You Mean It
Keeping track of the meaning of an array’s various dimensions can quickly become unwieldy when working with real datasets. xarray is a Python library that provides functionality comparable to NumPy, but allows users provide explicit labels for an array’s dimensions; that is, you can name each dimension. Using an xarray to select Brad’s scores could look like grades.sel(student='Brad'), for instance. This is a valuable library to look into at your leisure. Let’s build up some intuition for arrays with a dimensionality higher than 2. The following code creates a 3-dimensional array: # a 3D array, shape-(2, 2, 2) >>> d3_array = np.array([[[0, 1], ...
🌐
Python Guides
pythonguides.com › python-numpy-3d-array
3D Arrays In Python Using NumPy
May 16, 2025 - import numpy as np # Create a sample 3D array array_3d = np.arange(24).reshape(2, 3, 4) print("Original 3D array:") print(array_3d) # Get a specific 2D array (the first one) first_matrix = array_3d[0] print("\nFirst 2D matrix:") print(first_matrix) # Get a specific row from a specific 2D array second_matrix_first_row = array_3d[1, 0] print("\nFirst row of second matrix:") print(second_matrix_first_row) # Get a specific element specific_element = array_3d[1, 2, 3] print(f"\nElement at [1,2,3]: {specific_element}") # Slice across all dimensions subset = array_3d[0:2, 1:3, 1:3] print("\nSubset sl
🌐
MachineLearningMastery
machinelearningmastery.com › home › blog › how to index, slice and reshape numpy arrays for machine learning
How to Index, Slice and Reshape NumPy Arrays for Machine Learning - MachineLearningMastery.com
June 13, 2020 - Running the example first prints the size of each dimension in the 2D array, reshapes the array, then summarizes the shape of the new 3D array. This section provides more resources on the topic if you are looking go deeper. ... In this tutorial, you discovered how to access and reshape data in NumPy arrays with Python.
Find elsewhere
Top answer
1 of 5
1
arr = np.array([[[1,2,3], [4,5,6]], [[7,8,9],[10,11,12]]])

# array([[[ 1,  2,  3],
#         [ 4,  5,  6]],

#        [[ 7,  8,  9],
#         [10, 11, 12]]])

arr.shape # ------> (2,2,3)
# Think of them as axis
# lets create the first 2 axis of (`2`, ...)

#         |(1)
#         |
#         |
#         |         
#         |---------(0)


# now lets create second 2 axis of (2, `2`, ..)

#            (1,1)
#         |
#         |---(1,0)
#         |
#         |
#         |
#         |         |(0,1)
#         |---------|---(0,0)

# now lets create the last 3 axis of (2, 2, `3`)

#           /``(1,1,0) = 10
#          |-- (1,1,1) = 11
#         | \__(1,1,2) = 12
#         |
#         |  /``(1,0,0) = 7
#         |--|--(1,0,1) = 8
#         |  \__(1,0,2) = 9
#         |
#         |
#         |         /``(0,1,0) = 4
#         |         |--(0,1,1) = 5
#         |         \__(0,1,2) = 6
#         |         |
#         |         |
#         |---------|---/``(0,0,0) = 1
#                       |--(0,0,1) = 2
#                       \__(0,0,2) = 3

# now suppose you ask
arr[0, :, :] # give me the first axis alon with all it's component

#         |
#         |         /``(0,1,0) = 4
#         |         |--(0,1,1) = 5
#         |         \__(0,1,2) = 6
#         |         |
#         |         |
#         |---------|---/``(0,0,0) = 1
#                       |--(0,0,1) = 2
#                       \__(0,0,2) = 3

# So it will print 

# array([[1, 2, 3],
#        [4, 5, 6]])

arr[:, 0, :] # you ask take all the first axis 1ut give me only the first axis of the first axis and all its components

#           
#         
#         
#         
#         |  /``(1,0,0) = 7
#         |--|--(1,0,1) = 8
#         |  \__(1,0,2) = 9
#         |
#         |
#         |         
#         |         
#         |         
#         |         
#         |         
#         |---------|---/``(0,0,0) = 1
#                       |--(0,0,1) = 2
#                       \__(0,0,2) = 3

# so you get the output

# array([[1, 2, 3],
#        [7, 8, 9]])

# like wise you ask
print(arr[0:2, : , 2])
# so you are saying give (0,1) first axis, all of its children and only 3rd (index starts at 0 so 2 means 3) children
# 0:2 means 0 to 2 `excluding` 2; 0:5 means 0,1,2,3,4

#           
#          |
#         | \__(1,1,2) = 12
#         |
#         |  
#         |--
#         |  \__(1,0,2) = 9
#         |
#         |
#         |        
#         |         
#         |         \__(0,1,2) = 6
#         |         |
#         |         |
#         |---------|---/
#                       |
#                       \__(0,0,2) = 3

# so you get

# array([[ 3,  6],
#        [ 9, 12]])
2 of 5
1

Going In

Given the array:

    arr = np.array([[[1,2,3], [4,5,6]], [[7,8,9],[10,11,12]]])

The shape of arr, given by arr.shape is (2,2,3).

That basically means that if we start from outside (and ignore the first square bracket), we have 2 arrays in that scope. If we enter to one of those we can count 2 arrays and if we enter either we find 3 elements. I think having this view is quite helpful in understanding the following.

Specifying arr[1,1,2] selects the second array(index 1) in the outermost scope, and then selects the second array in the following scope and then selects the third element. The output is a single number:

    12

specifying arr[:,1,2] first simultaneously selects all arrays at the outermost scope and then for each of these selects the second array (index 1). When we then enter into the next scope, we pick out the 3rd elements. This outputs two numbers:

    array([ 6, 12])

specifying arr[:, : , 2] outputs 4 numbers since 1. at the first scope we selected all arrays (2) 2. at the next scope we selected all array (2 for each in the first)

    array([[ 3,  6],
           [ 9, 12]])

Coming out

Instinctually, the reason why they appear as 2x2 array can be viewed as receding from the lowermost scope. Elements are getting enclosed in square brackets because that share a scope. 3 and 6 will be in one array while 9 and 12 will be in another array.

Top answer
1 of 2
1

Try this, which is similar structure to your arr[:,idx,idx] but using np.ix_(). Do read the documentation for np.ix().-

idx = [1,3,4]
ixgrid = np.ix_(idx,idx)
arr[:,ixgrid[0],ixgrid[1]]
array([[[83, 36, 87],
        [39, 46, 88],
        [37, 77, 72]],

       [[64, 99, 88],
        [32,  9, 57],
        [31, 23, 35]]])

Explanation

What you are WANT to do is extract a mesh from the last 2 axes of the array. But what you are doing is extract exact indexes from each of the 2 axes.

  • When you use arr[:,(1,3,4),(1,3,4)], you are essentially asking for (1,1), (3,3) and (4,4) from the two matrices arr[0] and arr[1]

  • What you need is to extract a mesh. This can be achieved with np.ix_ and the magic of broadcasting.

If you ask for ...

[[1],
 [3],   and   [1,3,4]
 [4]]

... which is what the np.ix_ constructs, you broadcast the indexes and instead ask for a cross product between them, which is (1,1), (1,3), (1,4), (3,1), (3,3)... etc.

Hope that clarifies why you get the result you are getting and how you can actually get what you need.

2 of 2
0

The problem

Advanced indexing expects all dimensions to be indexed explicitly. What you're doing here is grabbing the elements at coordinates (1, 1), (3, 3), (4, 4) in each array along axis 0.

The solution

What you need to do is this instead:

idx = (1, 3, 4)  # the indices of interest
arr[np.ix_((0, 1), idx, idx)]

Where (0, 1) corresponds to the first two arrays along axis 0.

Output:

array([[[83, 36, 87],
        [39, 46, 88],
        [37, 77, 72]],

       [[64, 99, 88],
        [32,  9, 57],
        [31, 23, 35]]], dtype=int64)

As shown above, np.ix_((0, 1), idx, idx)) produces an object which can be used for advanced indexing. The (0, 1) means that you're explicitly selecting the elements from the arrays arr[0] and arr[1]. If you have a more general 3D array of shape (n, m, q) and want to grab the same subarray out of every array along axis 0, you can use

np.ix_(np.arange(arr.shape[0]), idx, idx))

As your indices. Note that idx is repeated here because you wanted those specific indices but in general they don't need to match.

Generalizing

More generally, you can slice and dice however you want like so:

In [1]: arrays_to_select = (0, 1)

In [2]: rows_to_select = (1, 3, 4)

In [3]: cols_to_select = (1, 3, 4)

In [4]: indices = np.ix_(arrays_to_select, rows_to_select, cols_to_select)

In [5]: arr[indices]
Out[5]:
array([[[83, 36, 87],
        [39, 46, 88],
        [37, 77, 72]],

       [[64, 99, 88],
        [32,  9, 57],
        [31, 23, 35]]], dtype=int64)

Let's consider some other shape:

In [4]: x = np.random.randint(0, 9, (4, 3, 5))

In [5]: x
Out[5]:
array([[[1, 0, 2, 1, 0],
        [3, 5, 1, 4, 3],
        [1, 8, 1, 4, 2]],

       [[1, 6, 8, 2, 8],
        [0, 0, 4, 2, 3],
        [8, 5, 6, 2, 5]],

       [[4, 4, 8, 6, 0],
        [3, 0, 1, 2, 8],
        [0, 8, 2, 4, 3]],

       [[7, 8, 8, 1, 4],
        [5, 7, 4, 8, 5],
        [7, 5, 5, 3, 4]]])

In [6]: rows = (0, 2)

In [7]: cols = (0, 2, 3, 4)

By using those rows and cols, you'll be grabbing the subarrays composed of all the elements from columns 0 through 4, from only the rows 0 and 2. Let's verify that with the first array along axis 0:

In [8]: arrs = (0,)  # A 1-tuple which will give us only the first array along axis 0

In [9]: x[np.ix_(arrs, rows, cols)]
Out[9]:
array([[[1, 2, 1, 0],
        [1, 1, 4, 2]]])

Now suppose you want the subarrays produced by rows and cols of only the first and last arrays along axis 0. You can explicitly select (0, -1):

In [10]: arrs = (0, -1)

In [11]: x[np.ix_(arrs, rows, cols)]
Out[11]:
array([[[1, 2, 1, 0],
        [1, 1, 4, 2]],

       [[7, 8, 1, 4],
        [7, 5, 3, 4]]])

If, instead, you want that same subarray from all the arrays along axis 0:

In [12]: arrs = np.arange(x.shape[0])

In [13]: arrs
Out[13]: array([0, 1, 2, 3])

In [14]: x[np.ix_(arrs, rows, cols)]
Out[14]:
array([[[1, 2, 1, 0],
        [1, 1, 4, 2]],

       [[1, 8, 2, 8],
        [8, 6, 2, 5]],

       [[4, 8, 6, 0],
        [0, 2, 4, 3]],

       [[7, 8, 1, 4],
        [7, 5, 3, 4]]])
🌐
Pythoninformer
pythoninformer.com › python-libraries › numpy › index-and-slice
PythonInformer - Indexing and slicing numpy arrays
February 4, 2018 - However, for trailing indices, simply omitting the index counts as a full slice. So for 2D arrays: a2[1:3,:] # is the same as a2[1:3] For 3D arrays: a3[1:,:2,:] # is the same as a3[1:,:2] a3[1:,:,:] # is the same as a3[1:,:] # and is also the same as a3[1:] As we saw earlier, you can use an index to select a particular plane column or row. Here we select row 1, columns 2:4: import numpy as np a2 = np.array([[10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24], [25, 26, 27, 28, 29]]) print(a2[1,2:4]) # [17 18] You can also use a slice of length 1 to do something similar (slice 1:2 instead of index 1): print(a2[1:2,2:4]) # [[17 18]] Notice the subtle difference.
🌐
GeeksforGeeks
geeksforgeeks.org › python-slicing-multi-dimensional-arrays
Python slicing multi-dimensional arrays - GeeksforGeeks
July 9, 2024 - In this example, we first create a 3-D NumPy array called array_3d. Then, we use negative indexing to slice the last row from each 2-D matrix within the 3-D array.
Top answer
1 of 1
5

This computation is not slow because of Numpy, but because of your hardware, and actually on most modern hardware. The main reason it is slow is because of random RAM accesses resulting in a latency-bound computation.

The input array is big and thus cannot be stored in CPU cache but in RAM. Modern RAM can have a quite high throughput but each fetch require a pretty big latency (about 80ns per random fetch on recent x86 processors). While the throughput of new RAM devices tends to significantly improve over time, the it is barely the case for latency. Fetching one (8-bytes) double-precision floating-point number at a time sequentially would result in a throughput of size_of_double/RAM_latency = 8/80e-9 ≈ 95 MiB/s. This is a fraction of what modern mainstream RAM devices can do (dozens of GiB/s)

To solve this problem, modern processors can fetch several memory block at a time and try to predict RAM accesses and load data ahead of time using prefetching units and speculation. This works well for predictible access patterns (especially contiguous loads) but not at all with a random access pattern like in your code. Still modern processors succeed to fetch multiple memory blocks in parallel with on a sequential code, but not enough so this kind of code can be fast enough (the throughput of your code is about 400 MiB/s). Not to mention mainstream x86 processors load systematically cache-lines of 64-bytes from RAM devices while you only need 8-bytes.

One solution is to parallelize this operation. However, this is not very efficient because of cache-lines (you will barely get more than 10% of the maximal throughput).

An alternative solution is to transpose your input data so that so that the fetched memory blocks can be more contiguous. Here is an example:

transposedLookup = np.array(lookup.T)
%timeit transposedLookup[m[:,0], m[:,1],:].T

Note that the first transposition will be rather slow (mainly because it is not yet optimized by Numpy, but also because of the access pattern) and requires twice the amount of RAM. You can use this solution to speed up the transposition. It is also possible to transpose the input matrix in-place if it is cubic. It would be even better if you could generate the array directly in its transposed form.

Also note that the second transposition is fast because it is done lazily, but even an eagerly transposition is still several times faster than the original code.

Here are the timings on my machine:

Original code: 14.1 ms
Eager Numpy transposition: 2.6 ms
Lazy Numpy transposition: 0.6 ms

EDIT: note that the same thing apply for m.

🌐
Medium
medium.com › @whyamit101 › understanding-numpy-slicing-with-examples-57351d21387c
Understanding NumPy Slicing with Examples | by why amit | Medium
February 9, 2025 - Here’s where things get even more interesting. With 2D and 3D arrays, slicing lets you extract specific rows, columns, or even submatrices.
🌐
TutorialsPoint
tutorialspoint.com › numpy › numpy_slicing.htm
NumPy - Slicing
Let us create a 3D array using the arange() function and reshape it with values containing 0 to 23 then reshape this 1D into 3D representing a (2*3*4) matrix and use slice object for slicing to get a subarray which selects the first layer (depth), all rows, and the first 2 columns − · import numpy as np arr_3d = np.arange(24).reshape(2, 3, 4) print("Original 3D array: \n" , arr_3d) subarray = arr_3d[0, :, :2] print("\nSliced subarray:", subarray)