Numpy Basics

Numpy is an essential library for scientists.

The two main motivations for using it are:

  • The implementation of the concept of vectors and matrices in Python

  • Access to classical mathematical functions (such as trigonometric functions)

This library facilitates data formatting for many other advanced scientific libraries: Pandas, Scipy, Scikit-image, OpenCV, Keras, TensorFlow…

For more information about this library: https://numpy.org/

To use this library, you need to import it in your script:

import numpy as np

Mathematical Functions

Numpy is primarily a library dedicated to mathematics and offers a set of basic functions: square root, power, exponential, trigonometric functions, etc. Some constants are also defined, such as \(\pi\).

print("Pi :", np.pi)
print("Square root :", np.sqrt(2))
print("Exponential :", np.exp(1))
print("Sine :", np.sin(3))

The previous example gives the result:

Pi : 3.141592653589793
Square root : 1.4142135623730951
Exponential : 2.718281828459045
Sine : 0.1411200080598672

Using Vectors

Numpy provides support for arrays, matrices, and a collection of mathematical functions to operate on these data structures.

Creating Vectors

Vector from a List

To create a vector with Numpy, you use the array function.

a_vect = np.array([1, 2, 3, 4, 5])
b_vect = np.array([-1, -2, -3, -4, -5])

You can then display these vectors using Python’s print function:

print(a_vect)
print(b_vect)

These commands produce the following output:

[1 2 3 4 5]
[-1 -2 -3 -4 -5]

Linearly Spaced Vectors

The linspace function generates a vector of predefined size from a start value to an end value, with a constant step that depends on the interval and the size of the vector.

# np.linspace(start, stop, size)
x_lin = np.linspace(0, 10, 15)  # Creates a vector of size 15 from 0 to 10 with a constant step
>>> print("np.linspace:\n", x_lin)

[ 0. 0.71428571 1.42857143 2.14285714 2.85714286 3.57142857 4.28571429 5. 5.71428571 6.42857143 7.14285714 7.85714286 8.57142857 9.28571429 10. ]

There is also the arange function which generates a vector from a start value to an end value, with a predefined constant step.

# np.arange(start, stop, step)
x_ara = np.arange(0, 20, 2.1)  # Creates a vector from 0 to 18.9 with a step of 2.1
>>> print("np.arange:\n", x_ara)
[ 0.   2.1  4.2  6.3  8.4 10.5 12.6 14.7 16.8 18.9]

When the step is not provided to the arange function, it uses a default value of 1.

Pre-filled Vectors with 0 or 1

The zeros function generates a vector of a predefined size with all elements set to 0.

# np.zeros(size)
vect_zeros = np.zeros(3)  # Creates a vector of 3 elements, each with a value of 0
# np.ones(size)
vect_ones = np.ones(3)  # Creates a vector of 3 elements, each with a value of 1

Operations on Vectors

Number of Elements

To find the number of elements in a vector, you can use one of the following commands:

print(len(b_vect))
print(b_vect.size)
print(b_vect.shape)

Unlike Python native lists, Numpy array objects allow for vectorized operations.

Element-wise Addition

The + operator allows for element-wise addition between two vectors.

c_vect = a_vect + b_vect
>>> print(c_vect)
[0 0 0 0 0]

Multiplying Elements by a Scalar

You can multiply all elements of a vector by a scalar using the * operator.

k_vect = a_vect * 2.1
>>> print(k_vect)
[ 2.1  4.2  6.3  8.4 10.5]

Element-wise Multiplication

It is also possible to perform element-wise multiplication of vectors using the * operator.

m_vect = a_vect * b_vect
>>> print(m_vect)
[ -1  -4  -9 -16 -25]

Indexing Vectors

Indexing of Numpy vectors is the same as for native Python lists.

In the following example, a vector named vect_1D of 3 elements is generated, indexed from 0 to 2. You access the i-th element of this vector using the command vect_1D[i]. In this example, the first element of this vector is accessed.

vect_1D = np.array([1, 2, 3])
first_element = vect_1D[0]
>>> print(first_element)
0

Concatenating Vectors

Vectors can be concatenated using the concatenate function.

In the following example, two vectors x1 and x2 of the same dimension are concatenated.

x1 = np.arange(0, 5, 1)
x2 = np.arange(-2.5, 2.5, 1)
x_concat = np.concatenate((x1, x2))
>>> print(x_concat)
[ 0.   1.   2.   3.   4.  -2.5 -1.5 -0.5  0.5  1.5]

Statistics on arrays

data = np.array([1, 2, 3, 4, 5])
mean = np.mean(data)
std_dev = np.std(data)

print("Mean:", mean)
print("Standard Deviation:", std_dev)

Functions and arrays

Functions can operate on arrays to perform element-wise operations or apply mathematical functions to array elements. NumPy functions are vectorized, meaning they operate on entire arrays at once.

x_vect = np.array([1, 2, 3, 4])
x_vect_exp = np.exp(x_vect)
>>> print(x_vect_exp)
[ 2.71828183  7.3890561  20.08553692 54.59815003]

This method can also be used with functions that you have defined yourself.

In the following example, we provide a function \(f(x) = 4 \cdot x^2 - 3 \cdot x + 9\) and wish to calculate the value of this function for different values of \(x\). These values are then stored in a vector.

# Function
def f(x):
        return 4*x**2 - 3*x + 9

y_vect = f(x_vect)
>>> print(y_vect)
[10 19 36 61]

Using matrices

Numpy was created to handle matrices, called arrays.

Other libraries such as SciPy and Pandas can also work with matrices in different contexts.

Creating matrices

Matrix from lists

# Create a 2D array (matrix)

matrix = np.array([[1, 2, 3], [4, 5, 6]])
>>> print(matrix)
array([[1, 2, 3],
       [4, 5, 6]])

Pre-filled Matrix

The functions seen for vectors can be used to create arrays of more than 1 dimension.

# Create a matrix of zeros
zeros_matrix = np.zeros((3, 3))  # 3x3 matrix filled with zeros

# Create a matrix of ones
ones_matrix = np.ones((2, 4))  # 2x4 matrix filled with ones

There are also specific functions to create particular matrix.

# Create an identity matrix
identity_matrix = np.eye(3)  # 3x3 identity matrix

For testing some algoritms, a matrix filled with random numbers can be useful.

# Create a matrix with random values
random_matrix = np.random.rand(3, 3)  # 3x3 matrix with random values between 0 and 1

Accessing to elements

You can access to a specific element by its index:

# Accessing elements
element = matrix[1, 2]  # Element in 2nd row, 3rd column
>>> print(element)
6

You can generate a new matrix based on a part of another one by slicing:

sub_matrix = matrix[0:2, 1:3]  # Rows 0 to 1, Columns 1 to 2
>>> print(sub_matrix)
array([[2, 3],
       [5, 6]])

Matrix Operations

Element-wise Addition

The + operator allows for element-wise addition between two matrices.

matrix1 = np.array([[1, 2], [3, 4]])
matrix2 = np.array([[5, 6], [7, 8]])
sum_matrix = matrix1 + matrix2
>>> print(sum_matrix)
array([[ 6,  8],
       [10, 12]])

Element-wise Multiplication

The * operator allows for element-wise multiplication between two matrices.

matrix1 = np.array([[1, 2], [3, 4]])
matrix2 = np.array([[5, 6], [7, 8]])
product_matrix = matrix1 * matrix2
>>> print(product_matrix)
array([[ 5, 12],
       [21, 32]])

Dot product of arrays

The dot product or scalar product is given by the dot function.

Using the `dot` Function:

matrix1 = np.array([[1, 2], [3, 4]])
matrix2 = np.array([[5, 6], [7, 8]])
result = np.dot(matrix1, matrix2)
>>> print(result)
array([[19, 22],
       [43, 50]])

Matrix Transposition and Inversion

The transposition of a matrix can be viewed by the T attribute of an array.

transposed_matrix = matrix1.T
>>> print(transposed_matrix)
array([[1, 3],
       [2, 4]])

The inversion of a matrix can be performed by the inv method of the linalg sublibrary of Numpy.

inverse_matrix = np.linalg.inv(matrix1)
>>> print(inverse_matrix)
array([[-2. ,  1. ],
       [ 1.5, -0.5]])

Note

The matrix must be square (same number of rows and columns) and non-singular (determinant not equal to zero) to be invertible.

Matrix Determinant and Eigenvalues

To calculate the determinant of a matrix, you can use the det method of the linalg sublibrary of Numpy.

det = np.linalg.det(matrix1)
>>> print(det)
-2

To calculate the eigenvalues and the eigenvectors of a matrix, you can use the eig method of the linalg sublibrary of Numpy.

eigenvalues, eigenvectors = np.linalg.eig(matrix1)
>>> print(eigenvalues)
array([-0.37228132,  5.37228132])
>>> print(eigenvectors)
array([[-0.82456484, -0.41597356],
       [ 0.56576746, -0.90937671]])

Conditionals on arrays

Conditionals on arrays allow you to perform element-wise operations based on certain conditions. This is useful for filtering, modifying, or performing calculations on specific elements within an array.

Findind Array Elements

You can find array elements based on a condition. The output is a boolean vector containing True if the condition is true for this element or False otherwise.

v = np.array([1, 5, 10, 6, 8])
k = (v >= 6)
>>> print(k)
array([False, False,  True,  True,  True])

Filtering an Array

You can use a condition to filter elements of an array.

arr = np.array([1, 2, 3, 4, 5, 6])
filtered_arr = arr[arr > 3]
>>> print(filtered_arr)  # Output:
[4 5 6]

Using np.where

The np.where() function is very powerful for applying conditions. It returns elements chosen from two arrays based on a condition or can be used to get indices where a condition is met.

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

# Replace elements greater than 3 with 10, others with 0
result = np.where(arr > 3, 10, 0)
>>> print(result)
[ 0  0  0 10 10 10]

You can also use the np.where() function to get the indices of elements that meet a condition:

indices = np.where(arr > 3)
>>> print(indices)
array([3, 4, 5]),)

Combining Conditions

You can combine multiple conditions using logical operators such as & (and), | (or), and ~ (not).

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

# Elements greater than 2 and less than 5
result = arr[(arr > 2) & (arr < 5)]
>>> print(result)
[3 4]

Array type and conversion

A ndarray (N-dimensional array) is a powerful data structure that can hold data of a specific type.

Accessing the Data Type of a ndarray

To access the data type of the elements in a NumPy array, you use the .dtype attribute.

arr = np.array([1, 2, 3, 4])
type_arr = arr.dtype
>>> print(type_arr)
int64 (or int32 depending on your system)

Forcing a Data Type in a ndarray

You can enforce a specific data type when creating a NumPy array using the dtype parameter.

Specify Data Type During Array Creation

You can specify the data type directly when you create the array:

arr = np.array([1, 2, 3, 4], dtype=float)

print(arr)       # Output: [1. 2. 3. 4.]
print(arr.dtype) # Output: float64

Convert an Existing Array to a Different Data Type

You can change the data type of an existing array using the .astype() method. This method returns a copy of the array with the specified data type.

arr = np.array([1, 2, 3, 4])

# Convert to a different data type
arr_float = arr.astype(float)

print(arr_float)     # Output: [1. 2. 3. 4.]
print(arr_float.dtype)  # Output: float64

Arrays vs Lists

Arrays and lists are both used to store collections of items, but they have distinct advantages depending on the context in which they are used. Below are the advantages of using arrays compared to lists, particularly focusing on numerical and scientific computing scenarios:

  • Memory Efficiency: Arrays are typically more memory-efficient than lists. In languages like Python (using libraries such as NumPy), arrays have a fixed size and type, which reduces overhead compared to lists that can store mixed data types.

  • Speed of Operations: Array operations are generally faster due to vectorization. Libraries like NumPy use optimized C and Fortran libraries, which means operations on arrays can be executed more quickly than the equivalent operations on lists.

  • Homogeneous Data Types: Arrays are designed to store elements of a single data type. This homogeneity allows for optimizations and reduces the complexity associated with type checking. Lists, on the other hand, can store mixed data types, which can introduce inefficiencies.

  • Vectorized Operations: Arrays support vectorized operations, meaning you can perform operations on entire arrays at once without explicit loops. This is particularly useful for mathematical and scientific computations. For instance, adding two arrays element-wise is a single operation with arrays but requires a loop with lists.

  • Linear Algebra: Libraries like NumPy provide extensive support for linear algebra operations on arrays, including matrix multiplication, eigenvalue computation, and more. Lists do not inherently support these operations.

Examples

Functions

To illustrate the benefit of using vectors (or matrices later), here’s an example using classic lists. This example is not recommended…

# Version with lists: not recommended!
x_list = [1, 2, 3, 4]  # Not recommended!
for i in x_list:  # Not recommended!
        print(f"exp({i}) ="{np.exp(i)}")  # Not recommended!

This method produces the following results:

exp(1) = 2.718281828459045
exp(2) = 7.38905609893065
exp(3) = 20.085536923187668
exp(4) = 54.598150033144236

A more robust and less time-consuming solution is the use of vectors (or matrices).

x_vect = np.array([1, 2, 3, 4])
x_vect_exp = np.exp(x_vect)
>>> print(x_vect_exp)
[ 2.71828183  7.3890561  20.08553692 54.59815003]

http://lense.institutoptique.fr/mine/python-numpy/

http://lense.institutoptique.fr/mine/python-numpy-matrices-et-calculs/