Hands-on examples illustrate creating and manipulating NumPy arrays. These arrays are fundamental for implementing linear algebra operations efficiently in Python. We will use the common convention of importing NumPy as np.import numpy as npIf you haven't installed NumPy yet, refer back to the "Setting Up Your Python Environment" section in Chapter 1.Creating ArraysFirst, let's revisit creating arrays. The most straightforward way is often from existing Python lists or tuples using np.array().From Python Lists:# Create a 1-dimensional array (vector) list_a = [1, 5, 2, 7] vector_a = np.array(list_a) print("Vector from list:") print(vector_a) print("Data type:", vector_a.dtype) # Check the inferred data type # Create a 2-dimensional array (matrix) list_b = [[1, 2, 3], [4, 5, 6]] matrix_b = np.array(list_b) print("\nMatrix from list of lists:") print(matrix_b) print("Data type:", matrix_b.dtype)Notice how NumPy automatically infers the data type (like int64 or float64). You can also explicitly specify the data type using the dtype argument:# Create a float array matrix_c = np.array([[1, 2], [3, 4]], dtype=np.float64) print("\nMatrix with specified float type:") print(matrix_c) print("Data type:", matrix_c.dtype)Using NumPy Functions:NumPy provides functions to create arrays with specific patterns, which is often more efficient than creating Python lists first.np.zeros(): Creates an array filled with zeros.np.ones(): Creates an array filled with ones.np.arange(): Creates an array with a range of numbers (similar to Python's range).np.linspace(): Creates an array with evenly spaced numbers over a specified interval.# Create a 3x4 matrix of zeros zeros_matrix = np.zeros((3, 4)) # Pass shape as a tuple print("\n3x4 Matrix of zeros:") print(zeros_matrix) # Create a 1D array of ones ones_vector = np.ones(5) print("\nVector of ones:") print(ones_vector) # Create an array from 0 up to (but not including) 10 range_array = np.arange(10) print("\nArray using arange(10):") print(range_array) # Create an array from 2 to 10, step 2 range_step_array = np.arange(2, 11, 2) # Start, Stop (exclusive), Step print("\nArray using arange(2, 11, 2):") print(range_step_array) # Create an array with 5 evenly spaced points between 0 and 1 linspace_array = np.linspace(0, 1, 5) # Start, Stop (inclusive), Number of points print("\nArray using linspace(0, 1, 5):") print(linspace_array)Accessing and Modifying Elements: Indexing and SlicingOnce you have an array, you need to access its elements. NumPy uses zero-based indexing, similar to Python lists.1D Arrays:my_vector = np.arange(5, 11) # Creates [5, 6, 7, 8, 9, 10] print("\nOriginal vector:", my_vector) # Access the first element (index 0) first_element = my_vector[0] print("First element:", first_element) # Access the last element (index -1) last_element = my_vector[-1] print("Last element:", last_element) # Get a slice: elements from index 1 up to (not including) index 4 slice_1_to_3 = my_vector[1:4] print("Slice [1:4]:", slice_1_to_3) # Get a slice from the beginning up to index 3 slice_start_to_2 = my_vector[:3] print("Slice [:3]:", slice_start_to_2) # Get a slice from index 3 to the end slice_3_to_end = my_vector[3:] print("Slice [3:]:", slice_3_to_end) # Modify an element my_vector[2] = 99 print("Modified vector:", my_vector) # Modify a slice my_vector[0:2] = [100, 101] # Assign a list or another NumPy array print("Vector after slice modification:", my_vector)2D Arrays (Matrices):For 2D arrays, you use comma-separated indices or slices: [row_index, column_index] or [row_slice, column_slice].my_matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) print("\nOriginal matrix:") print(my_matrix) # Access element at row 1, column 2 (value 6) element_1_2 = my_matrix[1, 2] print("Element at [1, 2]:", element_1_2) # Access the entire second row (index 1) row_1 = my_matrix[1, :] # Use ':' to select all columns print("Second row:", row_1) # Alternative syntax for selecting a full row row_1_alt = my_matrix[1] print("Second row (alternative):", row_1_alt) # Access the entire third column (index 2) col_2 = my_matrix[:, 2] # Use ':' to select all rows print("Third column:", col_2) # Get a submatrix: rows 0 and 1, columns 1 and 2 sub_matrix = my_matrix[0:2, 1:3] print("\nSubmatrix [0:2, 1:3]:") print(sub_matrix) # Modify an element my_matrix[0, 0] = -1 print("\nMatrix after modifying element [0, 0]:") print(my_matrix) # Modify a column my_matrix[:, 0] = [10, 20, 30] # Assign a list or array of the correct size print("\nMatrix after modifying first column:") print(my_matrix)Basic Arithmetic OperationsNumPy allows you to perform element-wise arithmetic operations directly on arrays, provided their shapes are compatible (or broadcastable, a concept we'll touch upon later).arr1 = np.array([1, 2, 3]) arr2 = np.array([4, 5, 6]) scalar = 2 print("\nArray 1:", arr1) print("Array 2:", arr2) print("Scalar:", scalar) # Element-wise addition addition = arr1 + arr2 print("arr1 + arr2:", addition) # Element-wise subtraction subtraction = arr1 - arr2 print("arr1 - arr2:", subtraction) # Element-wise multiplication multiplication = arr1 * arr2 print("arr1 * arr2:", multiplication) # Element-wise division division = arr1 / arr2 print("arr1 / arr2:", division) # Scalar multiplication scalar_mult = arr1 * scalar print("arr1 * scalar:", scalar_mult) # Scalar addition (adds scalar to every element) scalar_add = arr1 + scalar print("arr1 + scalar:", scalar_add) # Operations work similarly for matrices matrix1 = np.array([[1, 2], [3, 4]]) matrix2 = np.array([[5, 6], [7, 8]]) print("\nMatrix 1:") print(matrix1) print("Matrix 2:") print(matrix2) matrix_sum = matrix1 + matrix2 print("Matrix sum:") print(matrix_sum) matrix_prod = matrix1 * matrix2 # Note: This is ELEMENT-WISE multiplication print("Matrix element-wise product:") print(matrix_prod)Important Note: The * operator performs element-wise multiplication for NumPy arrays. This is different from standard matrix multiplication (the dot product), which we will cover in detail in Chapter 5.Understanding Array Attributes and ReshapingKnowing the properties of your arrays is essential. NumPy arrays have several useful attributes:ndim: Number of dimensions.shape: A tuple representing the size of the array in each dimension (rows, columns, etc.).size: Total number of elements in the array.dtype: Data type of the elements.matrix = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) print("\nSample Matrix:") print(matrix) print("Number of dimensions (ndim):", matrix.ndim) print("Shape (shape):", matrix.shape) # (rows, columns) print("Total size (size):", matrix.size) print("Data type (dtype):", matrix.dtype)You can change the shape of an array without changing its data using the reshape() method. The new shape must have the same total number of elements (size).vector = np.arange(12) # Array from 0 to 11 print("\nOriginal vector (size 12):", vector) # Reshape into a 3x4 matrix (3*4 = 12 elements) matrix_3x4 = vector.reshape(3, 4) print("\nReshaped to 3x4 matrix:") print(matrix_3x4) print("New shape:", matrix_3x4.shape) # Reshape into a 4x3 matrix (4*3 = 12 elements) matrix_4x3 = vector.reshape(4, 3) print("\nReshaped to 4x3 matrix:") print(matrix_4x3) print("New shape:", matrix_4x3.shape) # Try to reshape into an incompatible shape (e.g., 3x5 = 15 elements) try: vector.reshape(3, 5) except ValueError as e: print("\nReshape error (expected):", e) # Using -1 in reshape: NumPy calculates the dimension automatically # Reshape into a matrix with 2 rows (NumPy figures out columns) matrix_2x_auto = vector.reshape(2, -1) # -1 means "figure it out" print("\nReshaped to 2 rows (auto columns):") print(matrix_2x_auto) print("New shape:", matrix_2x_auto.shape) # Will be (2, 6) # Reshape into a matrix with 3 columns (NumPy figures out rows) matrix_auto_x3 = vector.reshape(-1, 3) print("\nReshaped to 3 columns (auto rows):") print(matrix_auto_x3) print("New shape:", matrix_auto_x3.shape) # Will be (4, 3)Reshaping is frequently used in machine learning to prepare data for different algorithms or layers in a neural network, which often expect inputs of specific dimensions.These examples cover the fundamental mechanics of creating, accessing, modifying, and inspecting NumPy arrays. As you progress through the course, you'll see these operations applied repeatedly in the context of vectors and matrices for linear algebra. Spend some time experimenting with these functions and operations to build familiarity. They are the building blocks for the more complex computations ahead.