Photo by Vlado Paunovic

NumPy is a powerful Python library that contains a large collection of mathematical functions, and supports the creation of matrices and multi-dimensional arrays to which these mathematical functions can be applied.

In this short tutorial, you will learn how to perform several of the most basic matrix operations with NumPy.

## Matrices and Arrays in NumPy

In NumPy, a matrix is defined as a specialised array that is strictly 2-dimensional, and which retains its 2-dimensionality following the application of mathematical operations. A matrix of this type can be implemented using the `np.matrix`

class, however NumPy no longer recommends use of this class as it may be removed in the future. The alternative option that is recommended by NumPy is to use the N-dimensional array type, `ndarray`

.

The key difference between an ndarray and a matrix in NumPy is that the former can be of any dimensionality, and its use is not restricted to 2-dimensional operations.

Hence, in this tutorial we shall be focusing on implementing several basic matrix operations on 2-dimensional arrays, created using `np.ndarray`

## Creating NumPy Arrays

Let’s first import the NumPy package and then proceed to create two, 2-dimensional arrays that are composed of two rows and three columns each. These arrays will be used in the ensuing examples of this tutorial:

```
# Import NumPy package
import numpy as np
# Create arrays
a1 = np.array([[0, 1, 0], [2, 3, 2]])
a2 = np.array([[3, 4, 3], [5, 6, 5]])
```

The `shape`

attribute lets us confirm the array’s dimensions:

```
# Print one of the arrays
print('Array 1:', '\n', a1, '\n Shape: \n’, a1.shape)
```

Output:

```
Array 1:
[[0 1 0]
[2 3 2]]
Shape: (2, 3)
```

## Basic Array Operations

NumPy provides its own functions to perform element-wise addition, subtraction, division and multiplication of arrays. In addition, Numpy also leverages Python’s arithmetic operators by extending their functionality to handle element-wise array operations.

Let’s start with element-wise addition between the arrays `a1`

and `a2`

as an example.

Element-wise addition of two arrays can be achieved by making use of the `np.add`

function or the overloaded `+`

operator:

```
# Using np.add
func_add = np.add(a1, a2)
# Using the + operator
op_add = a1 + a2
```

By printing out the results, it may be confirmed that they both produce the same output:

```
# Print results
print('Function: \n', func_add, '\n\n', 'Operator: \n', op_add)
```

Output:

```
Function:
[[3 5 3]
[7 9 7]]
Operator:
[[3 5 3]
[7 9 7]]
```

However, if we had to time them, we can notice a small difference:

```
import numpy as np
import timeit
def func():
a1 = np.array([[0, 1, 0], [2, 3, 2]])
a2 = np.array([[3, 4, 3], [5, 6, 5]])
np.add(a1, a2)
def op():
a1 = np.array([[0, 1, 0], [2, 3, 2]])
a2 = np.array([[3, 4, 3], [5, 6, 5]])
a1 + a2
# Timing the functions over 100000 iterations
func_time = timeit.timeit(func, number=100000)
op_time = timeit.timeit(op, number=100000)
# Print timing results
print('Function:', func_time, '\n', 'Operator:', op_time)
```

Output:

```
Function: 0.2588757239282131
Operator: 0.24321464297827333
```

Here it may be seen that the NumPy `np.add`

function performs slightly slower than the `+`

operator. This is mainly because the add function introduces type-checking to convert any *array_like* inputs (such as lists) into arrays before performing the addition operation. This, in turn, introduces an extra computational overhead over the `+`

operator.

However, such measure also makes the `np.add`

function less prone to error. For instance, applying `np.add`

to inputs of type `list`

still works (e.g. `np.add([1, 1], [2, 2])`

), whereas applying the `+`

operator results in list concatenation.

Similarly for element-wise subtraction (using `np.subtract`

or `-`

), division (using `np.divide`

or `/`

) and multiplication (using `np.multiply`

or `*`

), the NumPy functions perform type-checking, introducing a small computational overhead.

Several other operations that may come in handy include transposing and multiplying arrays.

Matrix transposition results in an orthogonal rotation of the matrix, and can be achieved using the `np.transpose`

function (which includes type-checking) or the `.T`

attribute:

```
# Using np.transpose
func_a1_T = np.transpose(a1)
# Using the .T attribute
att_a1_T = a1.T
```

Matrix multiplication can be performed using the `np.dot`

function or the `@`

operator (the latter implements the `np.matmul`

function from Python 3.5 onwards):

```
# Using np.dot
func_dot = np.dot(func_a1_T, a2)
# Using the @ operator
op_dot = func_a1_T @ a2
```

When working with 2-dimensional arrays, `np.dot`

and `np.matmul`

perform identically and both include type-checking.

## Additional Resources

**Stefania Cristina**, PhD, is a Senior Lecturer with the Department of Systems and Control Engineering at the University of Malta. Her research interests lie within the domains of computer vision and machine learning.