# Numerical Python

## Contents

# Numerical Python¶

The ability to do basic things with numerical arrays (vectors and matrices) is extremely important in data science applications.
We do numerical algebra in Python using the Numpy.
Numpy is a **huge** Python library and it has a lot of details.
We are going to cover the very basics here.
When there is something specific that you want to do, the best way to figure out how to do it is to Google it.

First, let’s import `numpy`

.
We typically, do it by creating a shortcut called `np`

:

```
import numpy as np
```

In this way you can access whatever is in `numpy`

by going throuh `np`

which is less characters.
Let’s create a 1D numpy array from a list:

```
a = np.array([1.2, 2.0, 3.0, -1.0, 2.0])
print(a)
```

```
[ 1.2 2. 3. -1. 2. ]
```

This behaves just like a vector in Matlab. You can multiply with a number:

```
2.0 * a
```

```
array([ 2.4, 4. , 6. , -2. , 4. ])
```

```
a * 3
```

```
array([ 3.6, 6. , 9. , -3. , 6. ])
```

You can add another vector (of the same size):

```
b = np.array([1, 2, 3, 4, 5])
c = a + b
c
```

```
array([2.2, 4. , 6. , 3. , 7. ])
```

You can raise all elements to a power:

```
a ** 3
```

```
array([ 1.728, 8. , 27. , -1. , 8. ])
```

Or you can apply a standard function to all elements:

```
np.exp(a)
```

```
array([ 3.32011692, 7.3890561 , 20.08553692, 0.36787944, 7.3890561 ])
```

```
np.cos(a)
```

```
array([ 0.36235775, -0.41614684, -0.9899925 , 0.54030231, -0.41614684])
```

```
np.sin(a)
```

```
array([ 0.93203909, 0.90929743, 0.14112001, -0.84147098, 0.90929743])
```

```
np.tan(a)
```

```
array([ 2.57215162, -2.18503986, -0.14254654, -1.55740772, -2.18503986])
```

Notice that the functions you can use are in `np`

.
Here is how you can get how many elements you have in the array:

```
a.shape
```

```
(5,)
```

Also, to see if this is a 1D array (higher dimensional arrays, 2D, 3D, etc. are also possible), you can use this:

```
a.ndim
```

```
1
```

You access the elements of the array just like the elements of a list:

```
a[0]
```

```
1.2
```

```
a[1]
```

```
2.0
```

```
a[2:4]
```

```
array([ 3., -1.])
```

```
a[::2]
```

```
array([1.2, 3. , 2. ])
```

```
a[::-1]
```

```
array([ 2. , -1. , 3. , 2. , 1.2])
```

You can find the minimum or the maximum of the array like this:

```
a.min()
```

```
-1.0
```

```
a.max()
```

```
3.0
```

So, notice that `a`

is an object of a class.
The class is called `np.ndarray`

:

```
type(a)
```

```
numpy.ndarray
```

and `min()`

and `max()`

are functions of the `np.ndarray`

class.
Another function of the class is the dot product:

```
a.dot(b)
```

```
20.2
```

You can achieve the same thing with the matrix multiplication operator “@”:

```
a @ b
```

```
20.2
```

There is also a submodule called `numpy.linalg`

which has some linear algebra functions you can apply to arrays.
For 1D arrays (aka vectors) the vector norm is a useful one:

```
np.linalg.norm(a)
```

```
4.409081537009721
```

```
help(np.linalg.norm)
```

```
Help on function norm in module numpy.linalg:
norm(x, ord=None, axis=None, keepdims=False)
Matrix or vector norm.
This function is able to return one of eight different matrix norms,
or one of an infinite number of vector norms (described below), depending
on the value of the ``ord`` parameter.
Parameters
----------
x : array_like
Input array. If `axis` is None, `x` must be 1-D or 2-D, unless `ord`
is None. If both `axis` and `ord` are None, the 2-norm of
``x.ravel`` will be returned.
ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional
Order of the norm (see table under ``Notes``). inf means numpy's
`inf` object. The default is None.
axis : {None, int, 2-tuple of ints}, optional.
If `axis` is an integer, it specifies the axis of `x` along which to
compute the vector norms. If `axis` is a 2-tuple, it specifies the
axes that hold 2-D matrices, and the matrix norms of these matrices
are computed. If `axis` is None then either a vector norm (when `x`
is 1-D) or a matrix norm (when `x` is 2-D) is returned. The default
is None.
.. versionadded:: 1.8.0
keepdims : bool, optional
If this is set to True, the axes which are normed over are left in the
result as dimensions with size one. With this option the result will
broadcast correctly against the original `x`.
.. versionadded:: 1.10.0
Returns
-------
n : float or ndarray
Norm of the matrix or vector(s).
See Also
--------
scipy.linalg.norm : Similar function in SciPy.
Notes
-----
For values of ``ord < 1``, the result is, strictly speaking, not a
mathematical 'norm', but it may still be useful for various numerical
purposes.
The following norms can be calculated:
===== ============================ ==========================
ord norm for matrices norm for vectors
===== ============================ ==========================
None Frobenius norm 2-norm
'fro' Frobenius norm --
'nuc' nuclear norm --
inf max(sum(abs(x), axis=1)) max(abs(x))
-inf min(sum(abs(x), axis=1)) min(abs(x))
0 -- sum(x != 0)
1 max(sum(abs(x), axis=0)) as below
-1 min(sum(abs(x), axis=0)) as below
2 2-norm (largest sing. value) as below
-2 smallest singular value as below
other -- sum(abs(x)**ord)**(1./ord)
===== ============================ ==========================
The Frobenius norm is given by [1]_:
:math:`||A||_F = [\sum_{i,j} abs(a_{i,j})^2]^{1/2}`
The nuclear norm is the sum of the singular values.
Both the Frobenius and nuclear norm orders are only defined for
matrices and raise a ValueError when ``x.ndim != 2``.
References
----------
.. [1] G. H. Golub and C. F. Van Loan, *Matrix Computations*,
Baltimore, MD, Johns Hopkins University Press, 1985, pg. 15
Examples
--------
>>> from numpy import linalg as LA
>>> a = np.arange(9) - 4
>>> a
array([-4, -3, -2, ..., 2, 3, 4])
>>> b = a.reshape((3, 3))
>>> b
array([[-4, -3, -2],
[-1, 0, 1],
[ 2, 3, 4]])
>>> LA.norm(a)
7.745966692414834
>>> LA.norm(b)
7.745966692414834
>>> LA.norm(b, 'fro')
7.745966692414834
>>> LA.norm(a, np.inf)
4.0
>>> LA.norm(b, np.inf)
9.0
>>> LA.norm(a, -np.inf)
0.0
>>> LA.norm(b, -np.inf)
2.0
>>> LA.norm(a, 1)
20.0
>>> LA.norm(b, 1)
7.0
>>> LA.norm(a, -1)
-4.6566128774142013e-010
>>> LA.norm(b, -1)
6.0
>>> LA.norm(a, 2)
7.745966692414834
>>> LA.norm(b, 2)
7.3484692283495345
>>> LA.norm(a, -2)
0.0
>>> LA.norm(b, -2)
1.8570331885190563e-016 # may vary
>>> LA.norm(a, 3)
5.8480354764257312 # may vary
>>> LA.norm(a, -3)
0.0
Using the `axis` argument to compute vector norms:
>>> c = np.array([[ 1, 2, 3],
... [-1, 1, 4]])
>>> LA.norm(c, axis=0)
array([ 1.41421356, 2.23606798, 5. ])
>>> LA.norm(c, axis=1)
array([ 3.74165739, 4.24264069])
>>> LA.norm(c, ord=1, axis=1)
array([ 6., 6.])
Using the `axis` argument to compute matrix norms:
>>> m = np.arange(8).reshape(2,2,2)
>>> LA.norm(m, axis=(1,2))
array([ 3.74165739, 11.22497216])
>>> LA.norm(m[0, :, :]), LA.norm(m[1, :, :])
(3.7416573867739413, 11.224972160321824)
```

## Questions¶

Consider the two vectors:

```
r1 = np.array([1.0, -2.0, 3.0])
r2 = np.array([2.0, 2.0, -3.0])
```

Use numpy functionality to:

Find a unit vector in the direction of

`r1`

:

```
# Your code here
```

Find the angle between

`r1`

and`r2`

in radians:

```
# Your code here
```

The projection of

`r2`

on`r1`

:

```
# Your code here
```

Use numpy.cross to find the cross product between

`r2`

and`r1`

. Then, verify numerically (using numpy functionality) that \(\vec{r}_1\cdot\left(\vec{r}_1\times\vec{r}_2\right) = 0\).

```
# Your code here
```