Introduction to NumPy

Module 3: NumPy

Introduction to NumPy

What is NumPy?

Python lists can store numerical data, but calculations with many values often require loops. NumPy gives us arrays that can perform the same calculation on every value at once.

NumPy (Numerical Python) is a package for numerical computing. It provides multidimensional arrays and mathematical functions designed to operate on those arrays efficiently.

Why Use NumPy?

  • Arrays store numerical data efficiently.
  • Array operations usually require less code than equivalent list-based loops.
  • NumPy’s optimized implementation makes large calculations fast.
# First, let's import NumPy
import numpy as np
ModuleNotFoundError: No module named 'numpy'

Creating Arrays

To create an array we use the function np.array(). We must pass a list or tuple to the function.

# Create a simple array from a list
ls = [1, 2, 3, 4, 5]
arr = np.array(ls)
print(arr)
NameError: name 'np' is not defined
# Single line
# Create a simple array from a list
arr = np.array([1, 2, 3, 4, 5])
print(arr)
NameError: name 'np' is not defined
# Tuples work too!
tup = (1, 2, 3, 4, 5)
arr = np.array(tup)
print(arr)
NameError: name 'np' is not defined
# Single line
arr = np.array((1, 2, 3, 4, 5))
print(arr)
NameError: name 'np' is not defined

What happens when we initialize a NumPy array with a set?

The np.array documentation says that the object argument must be “an array, any object exposing the array interface, an object whose __array__ method returns an array, or any (nested) sequence”.

A set is not a sequence. It is unordered and does not support indexing. NumPy therefore treats the complete set as one Python object instead of turning its members into array elements.

# This creates a zero-dimensional object array, not five integer elements.
arr = np.array({1, 2, 3, 4, 5})

print(arr)
print(arr.shape)
print(arr.dtype)
NameError: name 'np' is not defined
Exercise

What will be the value of arr? Which value type will their elements have?

arr = np.array((1, 2.0, 3))

print(arr)
NameError: name 'np' is not defined

The elements of the array will all be of type float64. NumPy automatically upcasts the integer values to floats so the full array has a single consistent data type.

Exercise

What will be the value of arr? Which value type will their elements have?

arr = np.array(1, 2.0, 3)

print(arr)

This code raises an error. The np.array function expects one main object argument, typically a list or tuple, but here it receives three separate values instead.


Array Types

Every NumPy array has one data type, available through its dtype property. NumPy supports many types, including booleans, integers, floating-point numbers, complex numbers, and strings. When the input mixes types, NumPy chooses one type capable of representing every value.

# Array of strings
a = np.array(["a", "b", "c"])

print(a)
NameError: name 'np' is not defined
# Array of floats
a = np.array([1, 2.2, 3.3])

# Notice how '1' is turned into a float '1.'
print(a)
NameError: name 'np' is not defined
# Mixed array
a = np.array([1, "two", 3])

# Notice that '1' and '3' are turned into strings!
print(a)
NameError: name 'np' is not defined

We can specify the NumPy array type in the second argument of np.array().

a = np.array([1, 2, 3], float)
print(a)
NameError: name 'np' is not defined
a = np.array([1, 2, 3], str)
print(a)
NameError: name 'np' is not defined

Multi-Dimensional Arrays

Nested list are used to create multi-dimensional arrays.

# Create a 2D array (Matrix)
# We will use nested lists
row_0 = [1, 2, 3]
row_1 = [4, 5, 6]
row_2 = [7, 8, 9]
ls_matrix = [row_0, row_1, row_2]
matrix = np.array(ls_matrix)
print(matrix)
NameError: name 'np' is not defined
# All nested list must have the same length!
row_0 = [1, 2, 3]
row_1 = [4, 5, 6, 0]
row_2 = [7, 8, 9]
ls_matrix = [row_0, row_1, row_2]
matrix = np.array(ls_matrix)
print(matrix)
# Single line
# Create a 2D array (Matrix)
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(matrix)
NameError: name 'np' is not defined
# Best format - helps clarity
# Create a 2D array (Matrix)
matrix = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
])
print(matrix)
NameError: name 'np' is not defined

In NumPy, the order of dimensions is indicated by the nesting of brackets. The outermost bracket (purple) represents the first dimension, indexed as dimension 0. The next level of brackets inside (blue) represents the second dimension, or dimension 1. If you continue adding more nested brackets, each new level represents a higher dimension. For example, the third level of brackets would be dimension 2, and so on. This hierarchical structure helps in understanding the shape and structure of multi-dimensional arrays.

Exercise

How would we create a 3D matrix?

matrix_3d = np.array([
    [[1, 2], [3, 4]],
    [[5, 6], [7, 8]]
])
print(matrix_3d)
print(matrix_3d.shape)
NameError: name 'np' is not defined

Some Functions to Quickly Create Arrays

# Create an array of zeros
zeros = np.zeros(5)
print(zeros)
NameError: name 'np' is not defined
# Create a 2x3 matrix of ones
shape = (2, 3)
ones = np.ones(shape)
print(ones)
NameError: name 'np' is not defined
# Single line
# Create a 2x3 matrix of ones
ones = np.ones((2, 3))
print(ones)
NameError: name 'np' is not defined
# Create an array of a range of numbers
range_array = np.arange(0, 10, 2)  # start, stop, step
print(range_array)
NameError: name 'np' is not defined
# Create an array of a range of numbers
range_array = np.linspace(0, 10, 6)  # start, stop, length
print(range_array)
NameError: name 'np' is not defined
Exercise

Create an array containing all odd numbers between 25 and 50.

# Try it!
odd_numbers = np.arange(25, 50, 2)
print(odd_numbers)
NameError: name 'np' is not defined
Exercise

Imagine you have a rope that is 12 meters long. You want to cut it into 7 equal lengths. Using NumPy, create an array that shows the six positions where you should make the cuts.

# Try it!
cut_points = np.linspace(0, 12, 8)[1:-1]
print(cut_points)
NameError: name 'np' is not defined

Eight equally spaced endpoints divide the rope into seven equal intervals. We remove the beginning and end because they are not cuts.


Basic Properties of Arrays

Unlike methods, properties don’t require parentheses when accessed. They provide insights into the attributes of the array. Here is a list of some key properties of NumPy arrays.

ndim: Indicates the number of dimensions of the array.

arr = np.zeros((5, 4))

print(arr.ndim)
NameError: name 'np' is not defined

shape: Returns a tuple representing the dimensions of the array. For a matrix with n rows and m columns, the shape is (n, m).

# shape returns a tuple
print(arr.shape)
NameError: name 'arr' is not defined

size: Reports the total number of elements.

print(arr.size)
NameError: name 'arr' is not defined

dtype: Reports the data type used by the elements.

print(arr.dtype)
NameError: name 'arr' is not defined

Array Indexing

Indexing 1D-arrays is the same as indexing lists, tuples or strings.

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

# Accessing single element (0-indexed)
print(arr[2])
NameError: name 'np' is not defined

Indexing multi-dimensional arrays is “similar” to indexing nested lists.

matrix = np.array([[0, 1, 2], [3, 4, 5]])

# Accessing element in 2D array (Matrix)
print(matrix[1][2])  # second row, third column
NameError: name 'np' is not defined

For NumPy arrays, we can include all indices within a single set of parentheses! This is called “indexing with a tuple”.

matrix = np.array([[0, 1, 2], [3, 4, 5]])

# Accessing element in 2D array (Matrix)
print(matrix[1, 2])  # second row, third column
NameError: name 'np' is not defined
Exercise

What will Python print?

matrix = np.array([[0, 1, 2], [3, 4, 5]])

print(matrix[-1, 0])
NameError: name 'np' is not defined
Exercise

What happens when this code runs? Explain why.

matrix = [[0, 1, 2], [3, 4, 5]]

print(matrix[2, 1])

This raises an error because lists cannot be indexed with tuples.

Exercise

What will be the value of x?

arr_A = np.array([[0, 1, 2], [3, 4, 5]])
arr_B = np.array([[6, 7, 8], [9, 10, 11]])

ls_arrays = [arr_A, arr_B]

x = ls_arrays[1][0, 2]

print(x)
NameError: name 'np' is not defined

Array Slicing

Slicing NumPy arrays is the same as slicing lists.

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

# Accessing a slice of an array
print(arr[1:4])  # Start is inclusive, end is exclusive
NameError: name 'np' is not defined
matrix = np.array([[0, 1, 2], [3, 4, 5]])

# Accessing rows/columns of a matrix
print(matrix[:, 1])  # All rows, second column
NameError: name 'np' is not defined
matrix = np.array([[0, 1, 2], [3, 4, 5]])

print(matrix[1, :])  # Second row, all columns
NameError: name 'np' is not defined
arr = np.array([0, 1, 2, 3, 4, 5, 6])

# Using steps in slicing
print(arr[0:5:2])  # Start at 0, go to 5, step by 2
NameError: name 'np' is not defined
Exercise

What slicing should we take to retrieve the array [2, 4, 6]?

arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
NameError: name 'np' is not defined
print(arr[2:7:2])
NameError: name 'arr' is not defined
Exercise

What slicing should we take to retrieve the array [7, 4, 1]?

arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
NameError: name 'np' is not defined
print(arr[7:0:-3])
NameError: name 'arr' is not defined
Exercise

What slicing should we take to retrieve the array [2, 4, 6]?

arr = np.array([
    [0, 1, 2],
    [1, 2, 3],
    [2, 3, 4],
    [3, 4, 5],
    [4, 5, 6]
])
NameError: name 'np' is not defined
print(arr[::2, 2])
NameError: name 'arr' is not defined
Exercise

What slicing should we take to retrieve the following matrix?

\[\begin{pmatrix} 1 & 2\\ 3 & 4\\ 5 & 6 \end{pmatrix}\]

arr = np.array([
    [0, 1, 2],
    [1, 2, 3],
    [2, 3, 4],
    [3, 4, 5],
    [4, 5, 6]
])
NameError: name 'np' is not defined
print(arr[::2, 1:])
NameError: name 'arr' is not defined

Practice

Deactivate AI assistant tools, and try the following exercises.

The reshape() method reorganizes an array without changing its values. The requested shape must contain the same number of elements as the original array.

Exercise

Exercise: Create an array filled with integers ordered from 1 to 12.

arr = np.arange(1, 13)
print(arr)
NameError: name 'np' is not defined
Exercise

Exercise: Reshape it to a \(3 \times 4\) matrix.

arr = np.arange(1, 13).reshape(3, 4)
print(arr)
NameError: name 'np' is not defined
Exercise

Exercise: Print its value at the first row, fourth column.

arr = np.arange(1, 13).reshape(3, 4)
print(arr[0, 3])
NameError: name 'np' is not defined

Summary and Reflection

  • A NumPy array stores elements using one data type.
  • ndim, shape, size, and dtype describe an array.
  • Indexing selects individual values; slicing selects ranges along one or more axes.
  • Slice stopping positions are excluded, just as they are for lists.

Explain in your own words why matrix[1, 2] works for a NumPy array but not for a nested Python list.


Homework and Review

Work through the Session 11 homework exercises available here.

Homework