# numpy

* faster computing than native python (C-wrapper), **30x faster** `np.array` manipulation than `list`
* favourite in data processing tasks, AI (keras)
* image (via `cv2`) as numpy 2D array
* pandas for data analysis


```python
import numpy as np
```


```python
# if it's not installed yet, let's use "magic command" `!`
!pip install numpy
```

    Collecting numpy
      Using cached numpy-1.20.1-cp38-cp38-win_amd64.whl (13.7 MB)
    Installing collected packages: numpy
    Successfully installed numpy-1.20.1


    WARNING: You are using pip version 20.1.1; however, version 21.0.1 is available.
    You should consider upgrading via the 'c:\prace\python-workshop-org\venv\scripts\python.exe -m pip install --upgrade pip' command.



```python
numbers = range(5)
a = np.array(numbers)
print(a)

b = np.arange(5)
print(b)
```

    [0 1 2 3 4]
    [0 1 2 3 4]



```python
# compare each item individually
print(a == b)
```

    [ True  True  True  True  True]



```python
# compare list content as singe object
list([1,2,3,4,5]) == list([1,2,3,4,5])
```




    True



# range vs linspace


```python
numbers = list(range(0, 10))
print(numbers)
```

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



```python
numbers = np.linspace(0, 10)
print(numbers)
```

    [ 0.          0.20408163  0.40816327  0.6122449   0.81632653  1.02040816
      1.2244898   1.42857143  1.63265306  1.83673469  2.04081633  2.24489796
      2.44897959  2.65306122  2.85714286  3.06122449  3.26530612  3.46938776
      3.67346939  3.87755102  4.08163265  4.28571429  4.48979592  4.69387755
      4.89795918  5.10204082  5.30612245  5.51020408  5.71428571  5.91836735
      6.12244898  6.32653061  6.53061224  6.73469388  6.93877551  7.14285714
      7.34693878  7.55102041  7.75510204  7.95918367  8.16326531  8.36734694
      8.57142857  8.7755102   8.97959184  9.18367347  9.3877551   9.59183673
      9.79591837 10.        ]



```python
numbers = np.linspace(0, 10, 50)
print(numbers)
```

    [ 0.          0.20408163  0.40816327  0.6122449   0.81632653  1.02040816
      1.2244898   1.42857143  1.63265306  1.83673469  2.04081633  2.24489796
      2.44897959  2.65306122  2.85714286  3.06122449  3.26530612  3.46938776
      3.67346939  3.87755102  4.08163265  4.28571429  4.48979592  4.69387755
      4.89795918  5.10204082  5.30612245  5.51020408  5.71428571  5.91836735
      6.12244898  6.32653061  6.53061224  6.73469388  6.93877551  7.14285714
      7.34693878  7.55102041  7.75510204  7.95918367  8.16326531  8.36734694
      8.57142857  8.7755102   8.97959184  9.18367347  9.3877551   9.59183673
      9.79591837 10.        ]



```python
np.linspace?
```

# matrix shape


```python
matrix = np.arange(60)
matrix
```




    array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
           17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
           34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
           51, 52, 53, 54, 55, 56, 57, 58, 59])




```python
matrix.shape
```




    (60,)




```python
matrix.reshape(4, 15)
```




    array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14],
           [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
           [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44],
           [45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59]])




```python
matrix.reshape(6, 10)
```




    array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
           [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
           [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
           [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
           [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
           [50, 51, 52, 53, 54, 55, 56, 57, 58, 59]])




```python
# original shape is just 1D vector
matrix.reshape(60,)
```




    array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
           17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
           34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
           51, 52, 53, 54, 55, 56, 57, 58, 59])




```python
m = matrix.reshape(6, 10)
print(m)
print(m.shape)
```

    [[ 0  1  2  3  4  5  6  7  8  9]
     [10 11 12 13 14 15 16 17 18 19]
     [20 21 22 23 24 25 26 27 28 29]
     [30 31 32 33 34 35 36 37 38 39]
     [40 41 42 43 44 45 46 47 48 49]
     [50 51 52 53 54 55 56 57 58 59]]
    (6, 10)


#### @ show trick with double click to left header of cell


```python
# transform to 3D
print(matrix.reshape(6, 5, 2))
```

    [[[ 0  1]
      [ 2  3]
      [ 4  5]
      [ 6  7]
      [ 8  9]]
    
     [[10 11]
      [12 13]
      [14 15]
      [16 17]
      [18 19]]
    
     [[20 21]
      [22 23]
      [24 25]
      [26 27]
      [28 29]]
    
     [[30 31]
      [32 33]
      [34 35]
      [36 37]
      [38 39]]
    
     [[40 41]
      [42 43]
      [44 45]
      [46 47]
      [48 49]]
    
     [[50 51]
      [52 53]
      [54 55]
      [56 57]
      [58 59]]]


# slicing


```python
print(m)
```

    [[ 0  1  2  3  4  5  6  7  8  9]
     [10 11 12 13 14 15 16 17 18 19]
     [20 21 22 23 24 25 26 27 28 29]
     [30 31 32 33 34 35 36 37 38 39]
     [40 41 42 43 44 45 46 47 48 49]
     [50 51 52 53 54 55 56 57 58 59]]



```python
# slice by 1. dimension (row)
m[1]
```




    array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])




```python
m[1:4]
```




    array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
           [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
           [30, 31, 32, 33, 34, 35, 36, 37, 38, 39]])




```python
# slice by 2. dimension (column)
m[:, 2]
```




    array([ 2, 12, 22, 32, 42, 52])




```python
# subset (indexing from 0!)
m[2:4, 3:7]
```




    array([[23, 24, 25, 26],
           [33, 34, 35, 36]])




```python
# subset (indexing from 0!)
m[2:4, 3:7]
```




    array([[23, 24, 25, 26],
           [33, 34, 35, 36]])



#### @ slice by axes with comma, or [ ]


```python
m[2][5]
```




    25




```python
m[2,5]
```




    25



# modify matrix by assignment of single value
#### @ single value replacement


```python
m[5,2] = 999
print(m)
```

    [[  0   1   2   3   4   5   6   7   8   9]
     [ 10  11  12  13  14  15  16  17  18  19]
     [ 20  21  22  23  24  25  26  27  28  29]
     [ 30  31  32  33  34   0   0  37  38  39]
     [ 40  41  42  43  44   0   0  47  48  49]
     [ 50  51 999  53  54  55  56  57  58  59]]



```python
number = m[5,2]
print(number)
```

    999



```python
number = 123
print(number)
```

    123



```python
# and it's not modified (as expected)
print(m)
```

    [[  0   1   2   3   4   5   6   7   8   9]
     [ 10  11  12  13  14  15  16  17  18  19]
     [ 20  21  22  23  24  25  26  27  28  29]
     [ 30  31  32  33  34   0   0  37  38  39]
     [ 40  41  42  43  44   0   0  47  48  49]
     [ 50  51 999  53  54  55  56  57  58  59]]


# modify matrix by block assignment


```python
# maybe there is prettier way
m = np.arange(60).reshape(6, 10)
print(m)
```

    [[ 0  1  2  3  4  5  6  7  8  9]
     [10 11 12 13 14 15 16 17 18 19]
     [20 21 22 23 24 25 26 27 28 29]
     [30 31 32 33 34 35 36 37 38 39]
     [40 41 42 43 44 45 46 47 48 49]
     [50 51 52 53 54 55 56 57 58 59]]


### np.zeros


```python
zeros = np.zeros((2,2))
print(zeros)
```

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



```python
print(m[3:5, 5:7])
```

    [[35 36]
     [45 46]]


note: same shape


```python
m[3:5, 5:7] = zeros
print(m)
```

    [[ 0  1  2  3  4  5  6  7  8  9]
     [10 11 12 13 14 15 16 17 18 19]
     [20 21 22 23 24 25 26 27 28 29]
     [30 31 32 33 34  0  0 37 38 39]
     [40 41 42 43 44  0  0 47 48 49]
     [50 51 52 53 54 55 56 57 58 59]]


# matrix views - replacement within submatrix

* slicing returns *views* (=shallow copy)
* `.copy()` to get deep copy


```python
m = np.arange(60).reshape(6, 10)
print(m)
```

    [[ 0  1  2  3  4  5  6  7  8  9]
     [10 11 12 13 14 15 16 17 18 19]
     [20 21 22 23 24 25 26 27 28 29]
     [30 31 32 33 34 35 36 37 38 39]
     [40 41 42 43 44 45 46 47 48 49]
     [50 51 52 53 54 55 56 57 58 59]]



```python
submatrix = m[0:3, 0:3]
print(submatrix)
```

    [[ 0  1  2]
     [10 11 12]
     [20 21 22]]



```python
submatrix[1,1] = 9999
print(submatrix)
```

    [[   0    1    2]
     [  10 9999   12]
     [  20   21   22]]



```python
print(m)
```

    [[   0    1    2    3    4    5    6    7    8    9]
     [  10 9999   12   13   14   15   16   17   18   19]
     [  20   21   22   23   24   25   26   27   28   29]
     [  30   31   32   33   34   35   36   37   38   39]
     [  40   41   42   43   44   45   46   47   48   49]
     [  50   51   52   53   54   55   56   57   58   59]]


# Matrix basics

### broadcasting


```python
a = np.array([[1,2],
    [3,4]
])
print(a)
```

    [[1 2]
     [3 4]]



```python
a + 100
```




    array([[101, 102],
           [103, 104]])




```python
a * 5
```




    array([[ 5, 10],
           [15, 20]])



#### `*` will still apply broadcasting!


```python
# np.array([1,5]) works as well
# a * np.array([1, 5])
(a * np.array([1, 5]))
```




    array([[ 1, 10],
           [ 3, 20]])



# matrix multiplication


```python
a.dot([1,5])
```




    array([11, 23])



matrix muliplication has special operator in python `@`


```python
a @ np.array([1, 5])
```




    array([11, 23])



## transposing matrix


```python
a
```




    array([[1, 2],
           [3, 4]])




```python
a.T
```




    array([[1, 3],
           [2, 4]])



# compare matrixes


```python
a = np.arange(5)
b = np.arange(5)

print(f'{a=}')
print(f'{b=}')
```

    a=array([0, 1, 2, 3, 4])
    b=array([0, 1, 2, 3, 4])


#### @ compare as single entity


```python
list(a) == list(b)
```




    True



#### @ item by item comparison


```python
a == b
```




    array([ True,  True,  True,  True,  True])



#### @ comapre operators as 'mask'


```python
a >= 2
```




    array([False, False,  True,  True,  True])




```python
a[a >= 2]
```




    array([2, 3, 4])



#### @ is same as mask from list of bools


```python
mask = [False, False, True, True, True]
a[mask]
```




    array([2, 3, 4])



# `.flatten` vs `.ravel`

* `.flatten` creates deep copy
* `.ravel` work as *view* (more memory efficient)


```python
matrix = np.array(([1,2], [3,4]))
print(matrix)
```

    [[1 2]
     [3 4]]



```python
changed = matrix.ravel()
print(changed)
```

    [1 2 3 4]



```python
changed = matrix.flatten()
print(changed)
```

    [1 2 3 4]



```python
changed[3] = 999
print(changed)
print("")
print(matrix)
```

    [  1   2   3 999]
    
    [[1 2]
     [3 4]]


# save / load `np.ndarray`

* `np.save`, `np.load` - save dimension, datatype, ...
* `np.savetxt`, `np.loadtxt` - save as CSV


```python
vector = np.arange(10).reshape(2,5)
print(vector)
np.save('export.npy', vector)
np.savetxt('export.csv', vector)
```

    [[0 1 2 3 4]
     [5 6 7 8 9]]



```python
np.load('export.npy')
```




    array([[0, 1, 2, 3, 4],
           [5, 6, 7, 8, 9]])




```python
np.loadtxt('export.csv')
```




    array([[0., 1., 2., 3., 4.],
           [5., 6., 7., 8., 9.]])




```python
%load export.csv
```


```python
m = matrix.reshape(6, 10)
m
```




    array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
           [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
           [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
           [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
           [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
           [50, 51, 52, 53, 54, 55, 56, 57, 58, 59]])



# let numpy to solve linear equations

It's an individual task as homework 😉

# sympy
https://docs.sympy.org/latest/index.html

* symbolic computation
  * simplyfing expressions
  * variable substitions
* integrals
* derivatives
* LaTeX output support (`sympy.init_printing(use_latex=False, use_unicode=False)`)


```python
import sympy
x = sympy.symbols('x')
```


```python
expr = 2*x + 3
expr
```




$\displaystyle 2 x + 3$




```python
expr.subs(x, 2)
```




$\displaystyle 7$




```python
a, b, c = sympy.symbols('a b c')
expr = (x + 1)*(x-1)
expr
```




$\displaystyle \left(x - 1\right) \left(x + 1\right)$




```python
sympy.simplify(expr)
```




$\displaystyle x^{2} - 1$




```python
sympy.Integral(1/x) + sympy.sqrt(x + 1)
```
