ch8s3_IndexingAndSlicing

Indexing and slicing are essential tools for **accessing, selecting, and modifying** specific elements in NumPy arrays.

Chapter 8: Introduction to NumPy

Sub-Chapter: Indexing and Slicing โ€” Accessing and Manipulating Array Data

Indexing and slicing are essential tools for accessing, selecting, and modifying specific elements in NumPy arrays.
They enable efficient subsetting, filtering, and reshaping of data โ€” all while taking advantage of NumPyโ€™s vectorized and memory-efficient design.


๐Ÿงฉ 1. Indexing in NumPy vs. Python Lists

While Python lists require nested loops for multi-dimensional access, NumPy arrays support multi-axis indexing directly and return views instead of copies (in most cases).

import numpy as np

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

print(matrix[0, 1])  # Access row 0, column 1 โ†’ 20
print(matrix[2])     # Access entire third row

๐Ÿง  In NumPy, slicing often returns a view, not a copy โ€” meaning modifying the slice affects the original array.


๐Ÿ”ข 2. Basic Indexing โ€” Single and Multi-Dimensional

1D Arrays

arr = np.array([10, 20, 30, 40, 50])
print(arr[0])   # 10
print(arr[-1])  # 50 (negative index)

2D Arrays

matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

print(matrix[1, 2])  # Element at row 1, column 2 โ†’ 6
print(matrix[2])     # Entire 3rd row โ†’ [7 8 9]
print(matrix[:, 0])  # Entire first column โ†’ [1 4 7]

3D Arrays

tensor = np.arange(27).reshape(3, 3, 3)
print(tensor[1, 2, 0])  # Access single element

โœ‚๏ธ 3. Slicing โ€” Extracting Subsets

Slicing syntax: [start : stop : step]

arr = np.array([10, 20, 30, 40, 50, 60])
print(arr[1:4])     # [20 30 40]
print(arr[:3])      # [10 20 30]
print(arr[::2])     # [10 30 50]
print(arr[::-1])    # Reverse โ†’ [60 50 40 30 20 10]

2D Slicing

matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

sub_matrix = matrix[:2, 1:]
print(sub_matrix)
# [[2 3]
#  [5 6]]

Visual Diagram

[[1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]]
  โ†‘โ†‘โ†‘
  |โ””โ”€ Columns 1 to 2
  โ””โ”€โ”€ Rows 0 to 1

โšก Slicing creates a view, not a copy. To get a copy, use .copy() explicitly.


๐ŸŽญ 4. Conditional (Boolean) Indexing

You can use logical expressions to create boolean masks that filter elements.

data = np.array([10, 20, 30, 40, 50])
mask = data > 25
filtered = data[mask]
print(filtered)  # [30 40 50]

Combining Conditions

arr = np.array([5, 15, 25, 35, 45, 55])
subset = arr[(arr > 20) & (arr < 50)]
print(subset)  # [25 35 45]

Use & for AND, | for OR, and parentheses around each condition.

Conditional Modification

arr[arr > 40] = 999
print(arr)  # [  5  15  25  35 999 999]

๐ŸŽฏ 5. Fancy Indexing โ€” Using Arrays of Indices

Fancy indexing lets you use integer arrays or lists to select arbitrary elements or rows.

arr = np.array([10, 20, 30, 40, 50])
indices = [0, 2, 4]
print(arr[indices])  # [10 30 50]

2D Fancy Indexing

matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

rows = np.array([0, 2])
cols = np.array([1, 2])
print(matrix[rows, cols])  # [2 9]

Selecting Rows and Columns Together

selected_rows = matrix[[0, 2]]     # Select 1st and 3rd rows
selected_cols = matrix[:, [0, 2]]  # Select 1st and 3rd columns

๐Ÿง  6. Mixing Fancy Indexing and Slicing

You can combine fancy indexing with slicing โ€” though it creates a copy, not a view.

matrix = np.arange(12).reshape(3, 4)

print(matrix[[0, 2], 1:3])
# [[1 2]
#  [9 10]]

๐Ÿ” 7. Using Helper Functions for Indexing

NumPy provides several powerful utilities for complex indexing.

np.where() โ€” Conditional Extraction

arr = np.array([10, 20, 30, 40, 50])
indices = np.where(arr > 25)
print(indices)       # (array([2, 3, 4]),)
print(arr[indices])  # [30 40 50]

np.take() โ€” Extract by Index Along Axis

matrix = np.arange(9).reshape(3, 3)
print(np.take(matrix, [0, 4, 8]))  # [0 4 8]

np.ix_() โ€” Cartesian Indexing

A = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])

rows = np.array([0, 2])
cols = np.array([1, 2])
print(A[np.ix_(rows, cols)])
# [[2 3]
#  [8 9]]

๐Ÿงฎ 8. Practical Examples

Extracting Odd Numbers

arr = np.arange(1, 11)
odds = arr[arr % 2 == 1]
print(odds)  # [1 3 5 7 9]

Normalizing Selected Columns

data = np.array([[10, 20, 30],
                 [40, 50, 60],
                 [70, 80, 90]])

cols = [0, 2]
data[:, cols] = (data[:, cols] - data[:, cols].mean()) / data[:, cols].std()

Modifying Elements Based on Condition

arr = np.array([10, 20, 30, 40, 50])
arr[arr < 30] = 0
print(arr)  # [ 0  0 30 40 50]

โš™๏ธ 9. Views vs Copies โ€” Important Concept

OperationReturnsModifies Original?
Basic slice (arr[1:3])Viewโœ… Yes
Fancy indexing (arr[[1,3]])CopyโŒ No
Boolean mask (arr[arr > 5])CopyโŒ No

Always use .copy() when you need to preserve the original data.


๐Ÿงพ 10. Quick Reference Table

TypeDescriptionExample
Basic IndexingAccess element(s)arr[1, 2]
SlicingExtract rangearr[1:4], arr[:, 1:]
Negative IndexingFrom endarr[-1]
Boolean MaskingFilter by conditionarr[arr > 10]
Fancy IndexingUse index arraysarr[[0,2,4]]
Helper FunctionsComplex indexingnp.where, np.take, np.ix_

๐Ÿงญ 11. Best Practices

โœ… Prefer slicing for large subsets (efficient, returns view).
โœ… Use .copy() when modifying slices independently.
โœ… Combine boolean masks with broadcasting for expressive filters.
โœ… Avoid mixing fancy and boolean indexing in the same call (hard to debug).
โœ… Use np.where() for conditional selection or replacement.


๐Ÿง  Summary

ConceptDescriptionExample
IndexingAccessing single/multiple elementsmatrix[1,2]
SlicingExtracting viewsmatrix[:2, 1:]
Boolean MaskingConditional filteringarr[arr>5]
Fancy IndexingArbitrary selectionmatrix[[0,2], [1,2]]
np.whereConditional positionsnp.where(arr>10)

Mastering indexing and slicing unlocks true NumPy power โ€” enabling expressive, fast, and memory-efficient data operations.