pytutorial/numpy/slices_views/README.md
David Rotermund 93ad463b9c
Update README.md
Signed-off-by: David Rotermund <54365609+davrot@users.noreply.github.com>
2023-12-14 17:33:39 +01:00

269 lines
6.3 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Slices and views
{:.no_toc}
<nav markdown="1" class="toc-class">
* TOC
{:toc}
</nav>
## 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](mailto:davrot@uni-bremen.de)
## 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.
```python
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:
```python
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:
```python
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:
```python
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:
```python
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)](https://docs.python.org/dev/library/constants.html#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.
```python
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.
```python
import numpy as np
a = np.zeros((2, 3))
b=a
print(a)
print()
b[0,0] = 1
print(a)
```
Output
```python
[[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
* Transposition (e.g. b=a.T)
* [ndarray.view()](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.view.html)
```python
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](https://numpy.org/doc/stable/reference/generated/numpy.may_share_memory.html)
```python
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:
```python
import numpy as np
a = np.zeros((2, 3))
b=a
print(np.may_share_memory(a,b)) # -> True
```
```python
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.
```python
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:
```python
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](https://numpy.org/doc/stable/reference/generated/numpy.copy.html)
Using [copy()](https://numpy.org/doc/stable/reference/generated/numpy.copy.html) ensures that you continue to with *no* view.
```python
numpy.copy(a, order='K', subok=False)
```
> Return an array copy of the given object.
```python
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](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.astype.html))
dtype casting destroys / prevent views. However, dont cast to the original dtype. Use copy() instead!
```python
ndarray.astype(dtype, order='K', casting='unsafe', subok=True, copy=True)
```
> Copy of the array, cast to a specified type.
```python
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
```