import numpy as npModuleNotFoundError: No module named 'numpy'
Module 3: NumPy
import numpy as npModuleNotFoundError: No module named 'numpy'
With NumPy, you can easily perform array-with-array arithmetic, or scalar-with-array arithmetic.
# Create two arrays
a = np.array([1, 2, 3, 4])
b = np.array([4, 3, 2, 1])NameError: name 'np' is not defined
# Addition
c = a + b
print(c)NameError: name 'a' is not defined
# Subtraction
c = a - b
print(c)NameError: name 'a' is not defined
# Multiplication element-wise
c = a * b
print(c)NameError: name 'a' is not defined
# Division element-wise
c = a / b
print(c)NameError: name 'a' is not defined
# Power element-wise
c = a ** b
print(c)NameError: name 'a' is not defined
Exercise: How would you create an array containing 50 eights?
Try to solve this exercise in your notebook (using Python code) before opening the solution.
arr = np.full(50, 8)
print(arr)NameError: name 'np' is not defined
Exercise: How would you create an array containing all the powers of 2 from \(2^0\) to \(2^{10}\)?
arr = 2 ** np.arange(0, 11)
print(arr)NameError: name 'np' is not defined
Broadcasting is a powerful mechanism that allows NumPy to work with arrays of different shapes when performing arithmetic operations.
# Create a scalar and an array
scalar = 5
arr = np.array([1, 2, 3, 4])
# Add scalar to array through broadcasting
print(scalar + arr)NameError: name 'np' is not defined
Broadcasting becomes extremely powerful when dealing with multidimensional arrays.
arr1 = np.ones((3, 3))
print(arr1)NameError: name 'np' is not defined
arr2 = np.array([-1, 0, 1])
print(arr2)NameError: name 'np' is not defined
# Use broadcasting
arr3 = arr1 + arr2
print(arr3)NameError: name 'arr1' is not defined
print(arr3[0])NameError: name 'arr3' is not defined
How does broadcasing works?
Example of broadcasting:
A with shape (m, n) and a 1D vector v with shape (n,).A has m rows and n columns, while the vector v has n elements.A = np.array([[1, 2],
[3, 4],
[5, 6]])
print(A.shape)NameError: name 'np' is not defined
v = np.array([1, 0])
print(v.shape)NameError: name 'np' is not defined
# The last dimension matches! They can be added
result = A + v
print(result)NameError: name 'A' is not defined
In this case, the sum is performed along the columns of A (dimension 1) because v is added to each row of A.
If v had shape (m,) instead, an error would occur.
A = np.array([[1, 2],
[3, 4],
[5, 6]])
print(A.shape)
v = np.array([1, 0, -1])
print(v.shape)
result = A + v
print(result)NameError: name 'np' is not defined
Deactivate AI assistant tools, and try the following exercises.
Exercise: Perform the following operation:
\[5 \cdot A \cdot v\]
Where \[A=\begin{pmatrix} 1 & 2 & 3 & 4\\ 5 & 6 & 7 & 8\\ 9 & 10 & 11 & 12 \end{pmatrix}\] and \(v = [-2, -1, 0, 1]\)
A = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
])
v = np.array([-2, -1, 0, 1])
result = 5 * A * v
print(result)NameError: name 'np' is not defined
Exercise: Given any matrix \(A\), multiply the elements of its first column times 1, the second column times 2, the third times 3, and so on.
For instance:
\[A = \begin{pmatrix} 1 & 2 & 3\\ 4 & 5 & 6\\ \end{pmatrix}\]
Would end up being: \[A' = \begin{pmatrix} 1 & 4 & 9\\ 4 & 10 & 18\\ \end{pmatrix}\]
A = np.array([
[1, 2, 3],
[4, 5, 6]
])
weights = np.arange(1, A.shape[1] + 1)
result = A * weights
print(result)NameError: name 'np' is not defined
arr = np.array([1, 2, 3, 4, 5])
# Square root
print(np.sqrt(arr))NameError: name 'np' is not defined
# For higher roots, we can just operate as we do with arrays
print(arr**(1/3))NameError: name 'arr' is not defined
So why use np.sqrt if we can do **(1/2)?
Because the NumPy function is optimized. We can see it by executing the computation many times and looking at its execution time.
arr = np.arange(1, 10000) # We use a very big array
for _ in range(10000):
np.sqrt(arr)
# We are not printing anything, because we do not want to fill the output
# We only want to see how much time this takes!NameError: name 'np' is not defined
for _ in range(10000):
arr**(1/2)
# We are not printing anything, because we do not want to fill the output
# We only want to see how much time this takes!NameError: name 'arr' is not defined
The same logic applies to exponentiation!
arr = np.array([1, 2, 3, 4, 5])
# Exponentiation
print(np.exp(arr))NameError: name 'np' is not defined
NumPy can do more than just element-wise operations. It also supports matrix multiplication, transposition, and other matrix math.
Matrix multiplication:
\[A\ B = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix} \begin{pmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \end{pmatrix} = \begin{pmatrix} a_{11}b_{11} + a_{12}b_{21} & a_{11}b_{12} + a_{12}b_{22} \\ a_{21}b_{11} + a_{22}b_{21} & a_{21}b_{12} + a_{22}b_{22} \end{pmatrix}\]
# Matrix multiplication (option A)
c = np.matmul(a, b)
print(c)NameError: name 'np' is not defined
# Matrix multiplication (option B)
c = a @ b
print(c)NameError: name 'a' is not defined
Dot product?: The term “dot product” can be confussing when applied to matrices. For some, a dot product of two matrices is the following.
\[A \cdot B = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix} \cdot \begin{pmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \end{pmatrix} = a_{11} b_{11} + a_{12} b_{12} + a_{21} b_{21} + a_{22} b_{22}\]
However, for NumPy, a dot product of two matrices is a matrix mutliplication.
\[A \cdot B = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix} \cdot \begin{pmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \end{pmatrix} = \begin{pmatrix} a_{11} b_{11} & a_{21} b_{12} \\ a_{12} b_{21} & a_{22} b_{22} \end{pmatrix}\]
# Dot product?
c = np.dot(a, b)
print(c)NameError: name 'np' is not defined
Very careful with np.dot()!
Using np.dot() with two matrices, it will compute the matrix multiplication, not their dot product!
# To compute the actual dot product
c = np.sum(a * b)
print(c)NameError: name 'np' is not defined
Both dot and matmul are used for matrix multiplication in NumPy, but they behave differently, especially when dealing with higher-dimensional arrays (i.e., tensors). The primary difference emerges in multi-dimensional array multiplication.
The official documentation says: matmul differs from dot in two important ways.
Cross product:
\[A \times B = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix} \times \begin{pmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \end{pmatrix} = \begin{pmatrix} a_{12}b_{21} - a_{11}b_{22} \\ a_{21}b_{12} - a_{22}b_{11} \end{pmatrix}\]
# Create two arrays
a = np.array([[1, 2], [3, 4]])
b = np.array([[4, 3], [2, 1]])
# Cross product
c = np.cross(a, b)
print(c)NameError: name 'np' is not defined
Transposition:
\[A^T = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix}^T = \begin{pmatrix} a_{11} & a_{21} \\ a_{12} & a_{22} \end{pmatrix}\]
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a)
# Matrix transposition
b = a.T
print(b)NameError: name 'np' is not defined
Determinants provide information about the matrix’s invertibility, while the inverse of a matrix is crucial for solving systems of linear equations.
Exercise: Assuming the matrix you created is \(A\), perform the following operation (cross product):
\[A \times B / 6\]
Where
\[B=\begin{pmatrix} -1 & 0 & 0 & -1\\ 0 & 1 & 1 & 0\\ 1 & -1 & 1 & -1 \end{pmatrix}\]
A = np.arange(1, 13).reshape(3, 4)
B = np.array([
[-1, 0, 0, -1],
[0, 1, 1, 0],
[1, -1, 1, -1]
])
result = np.cross(A, B) / 6
print(result)NameError: name 'np' is not defined
a = np.array([[1, 2], [3, 4]])
det_a = np.linalg.det(a)
print(det_a)NameError: name 'np' is not defined
a = np.array([[1, 2], [3, 4]])
inverse_a = np.linalg.inv(a)
print(inverse_a)NameError: name 'np' is not defined
NumPy operates in radians. If you are using degrees, make sure to convert them first!
angles = np.array([0, 30, 45, 60, 90]) # in degrees
# NumPy operates in radians!
angles_rad = np.radians(angles)
print(angles_rad)NameError: name 'np' is not defined
sines = np.sin(angles_rad)
print(sines)NameError: name 'np' is not defined
cosines = np.cos(angles_rad)
print(cosines)NameError: name 'np' is not defined
tangents = np.tan(angles_rad)
print(tangents)NameError: name 'np' is not defined
The mean is the average of a data set.
arr = np.array([1, 2, 3, 4, 5])
# Mean
print("Mean:", np.mean(arr))NameError: name 'np' is not defined
The median is the middle value when the data set is ordered.
arr = np.array([1, 2, 3, 4, 5])
# Median
print("Median:", np.median(arr))NameError: name 'np' is not defined
The standard deviation measures the dispersion of data from its mean.
arr = np.array([1, 2, 3, 4, 5])
# Standard Deviation
print("Standard Deviation:", np.std(arr))NameError: name 'np' is not defined
Variance is the square of the standard deviation.
arr = np.array([1, 2, 3, 4, 5])
# Variance
print("Variance:", np.var(arr))NameError: name 'np' is not defined
Exercise: Given a set of student grades, compute the mean, median, mode, standard deviation, and variance.
grades = np.array([85, 90, 78, 92, 88, 90])
print("Mean:", np.mean(grades))
print("Median:", np.median(grades))
print("Mode:", 90)
print("Standard Deviation:", np.std(grades))
print("Variance:", np.var(grades))NameError: name 'np' is not defined
grades = np.array([85, 90, 78, 92, 88])
sorted_indices = np.argsort(grades)
print(f"Indices to sort grades: {sorted_indices}")NameError: name 'np' is not defined
max_grade_index = np.argmax(grades)
print(f"Index of highest grade: {max_grade_index}")NameError: name 'np' is not defined
min_grade_index = np.argmin(grades)
print(f"Index of lowest grade: {min_grade_index}")NameError: name 'np' is not defined
arr = np.array([[1, 2], [3, 4]])
print("Array:\n", arr)
total_sum = np.sum(arr)
print(f"Sum: {total_sum}")NameError: name 'np' is not defined
# We sum across dimension 0
col_sum = np.sum(arr, axis=0)
print(f"Sum of columns: {col_sum}")NameError: name 'np' is not defined
# We sum across dimension 1
row_sum = np.sum(arr, axis=1)
print(f"Sum of rows: {row_sum}")NameError: name 'np' is not defined
cumulative_sum = np.cumsum(arr)
print(f"Cumulative Sum: {cumulative_sum}")NameError: name 'np' is not defined
product = np.prod(arr)
print(f"Product: {product}")NameError: name 'np' is not defined
Deactivate AI assistant tools, and try the following exercises.
Exercise: Given a “magic square” matrix \(A\):
\[A = \begin{pmatrix} 23 & 28 & 21\\ 22 & 24 & 26\\ 27 & 20 & 25 \end{pmatrix}\]
Normalize its values (subtract mean and divide by standard deviation). We call the new matrix \(B\).
\[B[i,j] = \frac{A[i,j] - \text{mean}(A)}{\text{std}(A)}\]
arr = np.array([[23, 28, 21], [22, 24, 26], [27, 20, 25]])
b = (arr - np.mean(arr)) / np.std(arr)
print(b)NameError: name 'np' is not defined
Exercise: Compute \(B^T\).
arr = np.array([[23, 28, 21], [22, 24, 26], [27, 20, 25]])
b = (arr - np.mean(arr)) / np.std(arr)
print(b.T)NameError: name 'np' is not defined
Exercise: Compute \(A \cdot B^T\).
arr = np.array([[23, 28, 21], [22, 24, 26], [27, 20, 25]])
b = (arr - np.mean(arr)) / np.std(arr)
print(arr @ b.T)NameError: name 'np' is not defined
Example: Can you find some interesting properties about this magic square matrix \(A\)?
Try to sum its rows, its columns, its diagonal.
arr = np.array([[23, 28, 21], [22, 24, 26], [27, 20, 25]])
print(np.sum(arr, axis=0))
print(np.sum(arr, axis=1))
print(np.trace(arr))NameError: name 'np' is not defined