## Vectors, Arrays and NumPy

NumPy is a very powerful mathematical library for “crunching numbers” in Python, it has functions and modules for a lot of advance and not so advance mathematical operations, including linear algebra.

Recently in classes we had been learning about vector and matrix operations, and most books include examples in things like Matlab or Octave so I was curious about how we can represent this in Python, so I decided to take a look and write this as a simple reminder of simple vector and basic operations in Python using NumPy. Of course I will assume you have already NumPy installed, if not, go to your closest virtual environment and install it with a simple command (depending on your platform this could take *a while*):

```
pip install numpy
```

Later in our scripts we just need to import NumPy, and by convention we name the import as `np`

```
import numpy as np
```

### Vectors

In normal mathematical notation we declare a vector in different ways, the most common (usually) is using a vertical matrix, something like this:

Notice some books declare this vector using its *transpose* version, \((1,2,3)^\top\).

Luckily for us, declaring a vector in NumPy is simple:

```
np.array([1,2,3])
```

array([1, 2, 3])

Arrays in NumPy are super powerful, they are used to express not only vectors but matrices and multidimensional structures, they are very memory efficient and used extensively for mathematical operations.

### Matrices

In the same way, declaring the following 2x3 matrix:

This can be easily done in NumPy with `np.array`

, but notice we need to pass a *multidimensional list* instead of just a simple list:

```
np.array([[1,2,3], [4,5,6]])
```

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

Notice the usage of the same type of structure to represent arrays and matrices, this is what makes NumPy so powerful, in previous versions you have different operators for vectors and arrays, but this is not the case anymore in modern NumPy versions.

### Operations

We can use *common* operations over existing matrices and vectors, for example, sum, subtraction and *scalar multiplication*.

```
np.array([1,2,3]) + np.array([4,5,6])
5*np.array([[1,2,3], [4,5,6]])
```

array([5, 7, 9]) array([[ 5, 10, 15], [20, 25, 30]])

Matrix multiplication is a little tricky, you see, if you use the standard multiplication symbol `*`

you will get a little surprise:

```
np.array([[1,2,3], [4,5,6]]) * np.array([[2,4,6], [8,2,4]])
```

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

In this case, multiplication of two matrices is *element wise* and known as Hadamard product, the same happens with Vectors:

```
np.array([1,2,3]) * np.array([2,4,6])
```

array([ 2, 8, 18])

But what if what you want is the *real* matrix multiplication operation? Well, if you are using Python 3.5 or greater, you need to use the `__matmul__`

operator `@`

or (if you are, for some reason, using an earlier version of Python) `np.dot`

```
a, b = np.array([[1,2,3], [4,5,6]]), np.array([[2,4], [6,8], [2,4]])
a @ b
np.dot(a, b)
```

array([[20, 32], [50, 80]]) array([[20, 32], [50, 80]])

Notice this is the equivalent of the *dot product* of two vectors, so the same operation with vectors is just their dot product (or *inner product*):

```
np.array([1, 2, 3]) @ np.array([4, 5, 6])
```

32

What about the *cross product* of two vectors? Well, in this case we don’t have a convinient operator as the dot product, but we have the function `np.cross`

:

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

array([-3, 6, -3])

### Convenient shortcuts

There are some “special” matrices and we can use special methods instead of create them directly using the `np.array`

notation, for example, the *identity* matrix

Can be created using `np.identity`

```
np.identity(3)
```

array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])

Or a matrix full of zeroes:

```
np.zeros((3,2))
```

array([[0., 0.], [0., 0.], [0., 0.]])

But maybe full of ones:

```
np.ones((3,2))
```

array([[1., 1.], [1., 1.], [1., 1.]])

**Important**: Notice we pass the dimension in a *tuple*, not as a simple parameter, with the exception of `np.identity`

, passing a single number will create a vector of that size, for example:”

```
np.ones(3)
```

array([1, 1, 1])

### Special operations

We can *transpose* a matrix as well easily using the method `transpose`

in every `np.array`

object:

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

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

Another two very common operations are the determinant of a matrix, for this we have the function `np.linalg.det`

```
a = np.array([[12,23], [45,62]])
np.linalg.det(a)
```

-291.0000000000001

And our friend the inverse of a matrix, `np.linalg.inv`

```
a = np.array([[12,23], [45,62]])
np.linalg.inv(a)
```

array([[-0.21305842, 0.0790378 ], [ 0.15463918, -0.04123711]])

As you can see, we can handle vector and matrix operations in Python using NumPy as easy as in any other language, there are a lot more of functions of NumPy just for linear algebra waiting there to explore, go and take a look, remember, Python is pure love!