Indexing and Slicing in NumPy
NumPy supports basic indexing with integers and slices plus advanced indexing with integer or Boolean arrays. Their copy behavior differs: basic slicing normally returns a view, while advanced indexing returns a copy.
1. Boolean Indexing
Boolean indexing is a powerful feature that allows you to filter arrays using boolean conditions. When a boolean condition is applied to a NumPy array, it returns an array of the same shape, filled with True or False values. This boolean array can then be used to select elements from the original array.
1.1 Basic Boolean Indexing
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
# Create a boolean array where elements are greater than 25
bool_idx = arr > 25
print("Boolean index:", bool_idx)
# Use the boolean array to filter the original array
filtered_arr = arr[bool_idx]
print("Filtered array:", filtered_arr)
1.2 Combining Multiple Conditions
You can combine multiple conditions using logical operators like & (AND), | (OR), and ~ (NOT).
# Combine conditions to filter elements between 20 and 40
filtered_arr = arr[(arr > 20) & (arr < 40)]
print("Filtered array with combined conditions:", filtered_arr)
1.3 Using np.where for Conditional Indexing
np.where is a versatile function that allows for conditional selection. It can be used to replace elements based on a condition or return indices where the condition is True.
# Replace elements greater than 30 with 99
replaced_arr = np.where(arr > 30, 99, arr)
print("Array with replacements:", replaced_arr)
# Get the indices of elements greater than 30
indices = np.where(arr > 30)
print("Indices where condition is true:", indices)
2. Fancy Indexing
Fancy indexing allows you to select elements from an array using an array of indices. This is particularly useful when you need to access specific elements from a large array.
2.1 Indexing with Integer Arrays
You can use an array of integers to select specific elements from a NumPy array.
arr = np.array([10, 20, 30, 40, 50])
# Select elements at positions 1, 3, and 4
indices = [1, 3, 4]
selected_elements = arr[indices]
print("Selected elements:", selected_elements)
2.2 Fancy Indexing with Multi-dimensional Arrays
Fancy indexing can also be applied to multi-dimensional arrays by passing arrays of indices for each dimension.
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# Select elements (0,0), (1,1), and (2,2)
rows = np.array([0, 1, 2])
cols = np.array([0, 1, 2])
diagonal_elements = matrix[rows, cols]
print("Diagonal elements:", diagonal_elements)
2.3 Using np.take for Fancy Indexing
np.take is a NumPy function that simplifies fancy indexing by taking elements from an array along a specified axis.
arr = np.array([10, 20, 30, 40, 50])
# Take elements at positions 0, 2, and 4
selected_elements = np.take(arr, [0, 2, 4])
print("Selected elements with np.take:", selected_elements)
3. Multi-dimensional Slicing
Slicing in NumPy arrays can be extended to multiple dimensions, allowing you to extract sub-arrays from complex data structures.
3.1 Basic Multi-dimensional Slicing
In a multi-dimensional array, you can slice each dimension individually.
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# Slice the first two rows and the last two columns
sub_matrix = matrix[:2, 1:]
print("Sliced matrix:\n", sub_matrix)
3.2 Slicing with Steps
You can also specify a step value for slicing, allowing you to skip elements.
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# Slice with a step of 2
stepped_slice = arr[::2]
print("Stepped slice:", stepped_slice)
# 2D array slicing with steps
stepped_matrix = matrix[::2, ::2]
print("Stepped matrix slice:\n", stepped_matrix)
3.3 Combining Slicing with Boolean Indexing and Fancy Indexing
You can combine slicing with other indexing techniques to perform complex data manipulations.
matrix = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
# Use boolean indexing and slicing to select elements
bool_idx = matrix > 30
selected_elements = matrix[bool_idx].reshape(3, 2)
print("Selected elements after combining techniques:\n", selected_elements)
4. Views, Copies, and Assignment
Because a basic slice can share storage with its source, writing through it can change the source. Advanced-indexing results are independent copies. Assignment such as array[[1, 3]] = value still writes directly into array; no selected result is retained in that statement.
source = np.arange(6)
view = source[1:4]
copy = source[[1, 3]]
view[0] = 99
copy[0] = -1
assert source.tolist() == [0, 99, 2, 3, 4, 5]
assert np.shares_memory(source, view)
assert not np.shares_memory(source, copy)
Conclusion
Advanced indexing and slicing techniques in NumPy provide powerful tools for efficiently accessing and manipulating large datasets. By mastering boolean indexing, fancy indexing, and multi-dimensional slicing, you can perform complex operations with ease, making your data analysis workflows more efficient and expressive.