Slices and views

The goal

Sometimes we want to use or see only a part of the matrix. This can be done via slices and views

Questions to David Rotermund

1-d slices

We assume N as the number of elements and 1d:​

  • A valid index starts at 0 and runs until N-1​
  • [start:stop:step] ​ start = 1, stop=N, step=1 ​-> this results in the sequence​ 1,2,3,…,(N-1)​
  • [start:stop:1] can be shortened to [start:stop]​
  • [0:stop] can be shortened to [:stop]​
  • [start:N] can be shortened to [start:]​
  • B = A[:] or B = A[…] gives you a view of A. B has the same shape and size of A. ​
import numpy as np

a = np.arange(0, 10)
print(a[1:10:1])  # -> [1 2 3 4 5 6 7 8 9]
print(a[3:7:2])  # -> [3 5]
print(a[3:6])  # -> [3 4 5]
print(a[:6])  # -> [0 1 2 3 4 5]
print(a[5:])  # -> [5 6 7 8 9]
print(a[:])  # -> [0 1 2 3 4 5 6 7 8 9]
print(a[...])  # -> [0 1 2 3 4 5 6 7 8 9]
print(a[:9999])  # -> [0 1 2 3 4 5 6 7 8 9]
print(a[9999:])  # ->[]
  • Negative values for start and stop are understood as N-|start| and N-|stop|
  • N-1 is the last valid index. ​
  • Thus A[-1] gives us the last element of A.​

Extracting a value based on a negative index:

import numpy as np

a = np.arange(0, 10)
print(a[-1])  # -> 9
print(a[-2])  # -> 8
print(a[-9])  # -> 1
print(a[-10])  # -> 0
print(a[-11])  # IndexError: index -11 is out of bounds for axis 0 with size 10

Extracting a slice based on a negative stop point:

import numpy as np

a = np.arange(0, 10)
print(a)  # -> [0 1 2 3 4 5 6 7 8 9]
print(a[:-1])  # -> [0 1 2 3 4 5 6 7 8]
print(a[:-5])  # -> [0 1 2 3 4]
print(a[:-8])  # -> [0 1]
print(a[:-11])  # -> []
print(a[:-12])  # -> []
print(a[:-999])  # -> []

Extracting a slice based on a negative start point:

import numpy as np

a = np.arange(0, 10)
print(a)  # -> [0 1 2 3 4 5 6 7 8 9]
print(a[-3:-1])  # -> [7 8]
print(a[-1:-8])  # -> []
print(a[-9999:])  # -> [0 1 2 3 4 5 6 7 8 9]

Negative step sizes:

import numpy as np

a = np.arange(0, 10)
print(a)  # -> [0 1 2 3 4 5 6 7 8 9]
print(a[::-1])  # -> [9 8 7 6 5 4 3 2 1 0]
print(a[4:-2:-1]) # -> []
print(a[-1:5:-1]) # -> [9 8 7 6]

… (Ellipsis)

The same as the ellipsis literal “…”. Special value used mostly in conjunction with extended slicing syntax for user-defined container data types. Ellipsis is the sole instance of the types.EllipsisType type.

import numpy as np

a = np.empty((2, 3, 4, 5, 6, 7, 8))
print(a.shape)  # -> (2, 3, 4, 5, 6, 7, 8)
print(a[..., 1:2].shape)  # -> (2, 3, 4, 5, 6, 7, 1)
print(a[:, :, 1:2, ...].shape)  # -> (2, 3, 1, 5, 6, 7, 8)
print(a[0, ...].shape)  # -> (3, 4, 5, 6, 7, 8)
print(a[0, ..., 0].shape)  # -> (3, 4, 5, 6, 7)

Views

What does view mean? It means that two objects share the same memory.

import numpy as np

a = np.zeros((2, 3))
b=a
print(a)
print()
b[0,0] = 1

print(a)

Output

[[0. 0. 0.]
 [0. 0. 0.]]

[[1. 0. 0.]
 [0. 0. 0.]]

a and b are not independent. If I change b this changes automatically a too. It is of high importance to understand when a view is created. Otherwise you will get totally wrong results.

Operations which are known to create views are:

ndarray.view([dtype][, type])

New view of array with the same data

  • Using [start:stop:step] for slicing out segments results in a view. b = a[:-1]
  • A simple assignment keeps a view as a view.​ e.g. b = a
  • A mathematical operation on a view may create a new real ndarray. ​

numpy.may_share_memory

numpy.may_share_memory(a, b, /, max_work=None)

Determine if two arrays might share memory

A return of True does not necessarily mean that the two arrays share any element. It just means that they might.

Only the memory bounds of a and b are checked by default.

A simple example:

import numpy as np

a = np.zeros((2, 3))

b=a
print(np.may_share_memory(a,b)) # -> True  
import numpy as np

a = np.zeros((2, 3))
b = a[1:2, 2:3]
print(np.may_share_memory(a, b))  # -> True

b = a[:, 2:3]
print(np.may_share_memory(a, b))  # -> True

b = a[:, ::2]
print(np.may_share_memory(a, b))  # -> True

b = a[0, :]
print(np.may_share_memory(a, b))  # -> True

b = a[0, 0]
print(np.may_share_memory(a, b))  # -> False

The a[0,0] does not create a view, because this creates an interger instead of a np.ndarray. And this kind of type conversion requires the creation of a new object.

import numpy as np

a = np.zeros((2, 3))
b = a.T
print(np.may_share_memory(a, b))  # -> True

Math operations normally create new objects:

import numpy as np

a = np.zeros((2, 3))
b = a**2
print(np.may_share_memory(a, b))  # -> False

b = np.exp(a)
print(np.may_share_memory(a, b))  # -> False

b = a+1
print(np.may_share_memory(a, b))  # -> False

numpy.copy

Using copy() ensures that you continue to with no view.

numpy.copy(a, order='K', subok=False)

Return an array copy of the given object.

import numpy as np

a = np.zeros((2, 3))
b = a.copy()

print(np.may_share_memory(a, b))  # -> False

b = a[1:2, 2:3].copy()
print(np.may_share_memory(a, b))  # -> False

dtype casting (numpy.ndarray.astype)

dtype casting destroys / prevent views. However, don’t cast to the original dtype. Use copy() instead!

ndarray.astype(dtype, order='K', casting='unsafe', subok=True, copy=True)

Copy of the array, cast to a specified type.

import numpy as np

a = np.zeros((2, 3))

b = a.astype(dtype=np.float32)
print(np.may_share_memory(a, b))  # -> False

b = a.astype(dtype=np.float64)
print(np.may_share_memory(a, b))  # -> False

b = a.astype(dtype=a.dtype)
print(np.may_share_memory(a, b))  # -> False

The source code is Open Source and can be found on GitHub.