fd632d113f
Signed-off-by: David Rotermund <54365609+davrot@users.noreply.github.com> |
||
---|---|---|
.. | ||
README.md |
Slices and views
{:.no_toc}
* TOC {:toc}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:
-
Slicing
-
Reshaping
-
ndarray.view()
-
Transposition
-
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