## 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:

$$\begin{pmatrix}1\\2\\3\end{pmatrix}$$

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])

Out[*]:
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:

$$\begin{bmatrix} 1&2&3\\ 4&5&6 \end{bmatrix}$$

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]])

Out[*]:
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]])

Out[*]:
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]])

Out[*]:
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])

Out[*]:
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)

Out[*]:
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])

Out[*]:
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)

Out[*]:
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

$$\begin{bmatrix} 1&0&0\\ 0&1&0\\ 0&0&1\end{bmatrix}$$

Can be created using np.identity

np.identity(3)

Out[*]:
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])


Or a matrix full of zeroes:

np.zeros((3,2))

Out[*]:
array([[0., 0.],
[0., 0.],
[0., 0.]])


But maybe full of ones:

np.ones((3,2))

Out[*]:
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)

Out[*]:
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()

Out[*]:
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)

Out[*]:
-291.0000000000001


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

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

Out[*]:
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!