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

192 lines
4.5 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.

# 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.
* 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.