## Monday 8 April 2024

### Matrix Indexing and slicing

Demystifying Matrix Indexing in NumPy:

Indexing in NumPy unlocks the ability to access and manipulate specific elements within your 2D matrices. Here's a breakdown to empower you:

The Matrix Playground:

Imagine a NumPy matrix as a grid, with rows and columns intersecting to create cells. Each cell holds a single value, and you can pinpoint or modify these values using indexing.

Zero-Based Indexing:

Remember, NumPy indexing starts from 0. The first row has an index of 0, the second row has an index of 1, and so on. Similarly, columns start indexing from 0 as well.

Accessing Individual Elements:

• Syntax: matrix[row_index, column_index]

• Example: Consider the following matrix:

Python

import numpy as np
matrix = np.array([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])

To access the element at the second row (index 1) and first column (index 0), you'd use:

Python

specific_element = matrix[1, 0# Accesses the value 50

Slicing for Sub-matrices (covered in detail previously):

Slicing allows you to extract a subset of rows and columns from the matrix. We won't delve deep into slicing here, but remember you can use:

Python

matrix[start_row:end_row:step_row, start_col:end_col:step_col]

Beyond basic element access, indexing empowers you to perform more elaborate selections:

1. Boolean Indexing:

• Create a boolean mask (array of True/False values) based on a condition.

• Use this mask as an index to select elements that meet the condition.

Python

# Elements greater than 50
condition = matrix > 50
filtered_elements = matrix[condition]

1. Integer Arrays as Indices:

• Construct an integer array representing the desired row and column indices.

• Use this array to select specific elements.

Python

# Access elements at indices (1, 0) and (2, 2)
desired_indices = np.array([(1, 0), (2, 2)])
selected_elements = matrix[desired_indices[:, 0], desired_indices[:, 1]]

1. Newaxis (None):

• Used to insert an extra dimension of size 1.

• Enables broadcasting for element-wise operations with scalars.

Python

# Add 10 to each row
matrix = matrix + np.newaxis[:, 10]

Key Points:

• Indexing provides granular control over accessing and modifying elements within matrices.

• Understand zero-based indexing to navigate rows and columns accurately.

• Explore boolean indexing, integer array indexing, and newaxis for more advanced selection and manipulation.

Two common ways to swap the first and last rows of a NumPy matrix:

Method 1: Using a Temporary Variable

1. Store the First Row: Create a temporary variable to hold a copy of the first row.

2. Overwrite the First Row: Assign the last row to the first row.

3. Replace the Last Row: Assign the stored copy (original first row) to the last row.

Python

import numpy as np

matrix = np.arange(12).reshape(4, 3)
print("Original Matrix:\n", matrix)

# Store first row temporarily
temp = matrix[0, :].copy()

# Swap first and last row
matrix[0, :] = matrix[-1, :]
matrix[-1, :] = temp

print("Modified Matrix:\n", matrix)

Method 2: Simultaneous Assignment (Pythonic!)

This leverages Python's ability to perform tuple unpacking and simultaneous assignment.

Python

matrix[0, :], matrix[-1, :] = matrix[-1, :], matrix[0, :]

Explanation

• matrix[0, :] and matrix[-1, :]: Select the first and last rows respectively.

• Simultaneous Assignment: Python unpacks the values on the right side, assigning the last row to the first and the original first row to the last, effectively swapping them.

Important:

• Copy When Needed: If you plan to use the original first row elsewhere in your code, ensure you use .copy() in Method 1 to store a separate copy of it.

Choosing a Method

• Clarity: Method 1 might be easier to understand for beginners due to its explicit steps.

• Efficiency: Method 2 is often slightly more efficient and considered more Pythonic.

Slicing Magic in NumPy Matrices: A Detailed Exploration

Slicing in NumPy unlocks powerful ways to extract and manipulate specific portions of your 2D matrices. Here's a breakdown to empower you:

The Matrix Playground:

Imagine a NumPy matrix like a grid, with rows and columns intersecting to create cells. Each cell holds a single value, and you can access or modify these values using indexing and slicing.

Zero-Based Indexing:

Remember, indexing in NumPy starts from 0. The first row has an index of 0, the second row has an index of 1, and so on. Similarly, columns start indexing from 0 as well.

Accessing Individual Elements:

• Syntax: matrix[row_index, column_index]

• Example: Let's consider the following matrix:

Python

import numpy as np
matrix = np.array([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])

To access the element at the second row (index 1) and first column (index 0), you'd use:

Python

specific_element = matrix[1, 0# Accesses the value 40

Slicing for Sub-matrices:

Slicing allows you to extract a subset of rows and columns from the matrix.

• Syntax: matrix[start_row:end_row:step_row, start_col:end_col:step_col]

• start_row: Starting row index (inclusive)

• end_row: Ending row index (exclusive)

• step_row: Step size for rows (optional, defaults to 1)

• start_col: Starting column index (inclusive)

• end_col: Ending column index (exclusive)

• step_col: Step size for columns (optional, defaults to 1)

Common Slicing Techniques:

1. Extracting a Row:

Python

second_row = matrix[1, :]  # Selects all elements in the second row (index 1)

1. Extracting a Column:

Python

first_column = matrix[:, 0# Selects all elements in the first column (index 0)

1. Sub-matrix (Top-Left Corner):

Python

top_left_2x2 = matrix[:2, :2# Extracts a 2x2 sub-matrix from the top-left corner

Key Points:

• Colon (:) is your friend! It means "select all elements along that dimension."

• Order Matters: The order you specify rows and columns in the slicing syntax matters (row, column).

1. Reverse Rows:

Python

reversed_matrix = matrix[::-1, :]  # Reverses the order of rows

1. Alternate Columns:

Python

alternate_cols = matrix[:, ::2# Selects every other column

1. Diagonal Elements:

Python

# Method 1: Using np.diag()
diagonal_elements = np.diag(matrix)

# Method 2: Direct Indexing
diagonal_elements = matrix[np.arange(matrix.shape[0]), np.arange(matrix.shape[0])]

Important Note:

Slicing often creates views into the original array. Changes made to the slice can affect the original matrix. If you need an independent copy, use the .copy() method.

Let's Practice!

Here are some challenges to test your slicing skills:

1. Extract a specific "checkerboard" pattern of elements (e.g., only even-numbered rows and odd-numbered columns).

2. Rotate the matrix by 90 degrees using slicing (think about reordering rows and columns).

3. Extract elements satisfying a certain condition (e.g., all elements greater than a specific value).

Feel free to ask for help with these challenges, or if you have any other slicing scenarios in mind!

checkerboard pattern using NumPy's slicing prowess. Here's how to extract even-numbered rows and odd-numbered columns:

Steps:

1. Slice even rows: Select rows with indices 0, 2, 4, etc.

2. Slice odd columns: Select columns with indices 1, 3, 5, etc.

Example

Python

import numpy as np

matrix = np.arange(1, 26).reshape(5, 5# Create a sample 5x5 matrix
print(matrix)

# Extract the checkerboard pattern
checkerboard_slice = matrix[::2, 1::2]
print(checkerboard_slice)

Explanation

• matrix[::2, 1::2]

• ::2 for the row dimension takes every other row (starting from 0, so the even rows).

• 1::2 for the column dimension takes every other column starting from index 1 (the odd columns).

Customization

• Different starting points: Modify the starting index in the slices to control where your checkerboard pattern begins.

• Larger matrices: This technique scales effortlessly to larger matrices!

Variations

Fancy a different checkerboard look? Here's how to get odd rows and even columns:

Python

alternate_checkerboard = matrix[1::2, ::2]

Here are two primary methods to rotate a matrix by 90 degrees using NumPy:

Method 1: Transpose and Reverse Columns

1. Transpose: Swap the rows and columns using NumPy's transpose() method or the .T attribute. This gives a 'sideways' version of the matrix.

2. Reverse Columns: Reverse the order of the columns to complete the 90-degree rotation.

Python

import numpy as np

matrix = np.arange(1, 10).reshape(3, 3)

# Step 1: Transpose
transposed_matrix = matrix.T

# Step 2: Reverse columns (rotate clockwise)
rotated_90_degrees = transposed_matrix[:, ::-1

print(rotated_90_degrees)

Method 2: Direct Indexing and Reordering

This method accesses elements in a specific order to construct the rotated matrix directly.

Python

# Rotate clockwise
rotated_90_degrees = matrix[::-1].T

Explanation of Method 2

• [::-1]: Reverses the order of the rows (top-to-bottom).

• .T : Transposes the reversed matrix, effectively rotating it.

Rotating Counter-Clockwise

To rotate by 90 degrees counter-clockwise, reverse the rows after transposing:

Python

rotated_90_counterclockwise = matrix.T[::-1]

Key Points

• Method Choice: Both methods are valid; choose the one that aligns best with your understanding of how the rotation works.

• Rotation Direction: Ensure you understand the difference between clockwise and counter-clockwise rotations and adjust the slicing accordingly.

Challenges

1. In-Place Rotation: Could you modify the matrix in-place (without creating a new copy) to achieve rotation? (Hint: This gets trickier.)

2. 180-Degree Rotation: How would you use similar slicing techniques to rotate a matrix by 180 degrees?

NumPy's boolean indexing allows you to effortlessly extract elements from a matrix (or array) based on specific conditions. Here's how to do it:

Steps

1. Create a Boolean Mask: Compare your matrix with the desired condition. This creates an array of True/False values the same shape as your matrix.

2. Indexing with the Mask: Use this boolean mask as an index for your matrix to select only the elements where the mask is True.

Example: Elements Greater than 10

Python

import numpy as np

matrix = np.arange(16).reshape(4, 4)
print(matrix)

condition = matrix > 10

# Step 2: Indexing with the mask
elements_greater_than_10 = matrix[condition]
print(elements_greater_than_10)

Explanation

1. matrix > 10: This comparison creates a boolean array where each element is True if the corresponding element in matrix is greater than 10, and False otherwise.

2. matrix[condition]: NumPy uses the boolean mask to select only the elements where the mask has the value True.

Customization

You can easily adapt this for various conditions:

• Less Than a Value:
Python
elements_less_than_5 = matrix[matrix < 5]

• Within a Range:
Python
elements_between_5_and_12 = matrix[(matrix >= 5) & (matrix <= 12)]  # Using '&' for 'and'

Key Points

• Boolean Mask: The boolean array serves as a blueprint for selecting elements based on your condition.

• Logical Operators: Utilize operators like >, <, >=, <=, == (element-wise equality), & (and), | (or) within your conditions.

Practice Exercises

1. Extract all the even numbers from the matrix.

2. Find those elements that are multiples of 3 or 5.

Let me know if you'd like help with these exercises or would like to explore more complex conditional extractions!