Skip to content

Basic Indexing

NumPy arrays support flexible indexing to access individual elements.

Positive Indexing

Access elements using zero-based positive indices.

1. 1D Array Access

import numpy as np

def main():
    a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(f"{a[1] = }")

    b = np.array(a)
    print(f"{b[1] = }")

if __name__ == "__main__":
    main()

Output:

a[1] = 1
b[1] = 1

2. 2D Array Access

import numpy as np

def main():
    a = [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
    print(f"{a[1] = }")

    b = np.array(a)
    print(f"{b[1] = }")

if __name__ == "__main__":
    main()

Output:

a[1] = [1, 2, 3]
b[1] = array([1, 2, 3])

Negative Indexing

Access elements from the end using negative indices.

1. 1D Negative Index

import numpy as np

def main():
    a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(f"{a[-2] = }")

    b = np.array(a)
    print(f"{b[-2] = }")

if __name__ == "__main__":
    main()

Output:

a[-2] = 8
b[-2] = 8

2. 2D Negative Index

import numpy as np

def main():
    a = [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
    print(f"{a[-2] = }")

    b = np.array(a)
    print(f"{b[-2] = }, {b[-2].shape = }")
    print(f"{b[-2:-1] = }, {b[-2:-1].shape = }")

if __name__ == "__main__":
    main()

Output:

a[-2] = [3, 4, 5]
b[-2] = array([3, 4, 5]), b[-2].shape = (3,)
b[-2:-1] = array([[3, 4, 5]]), b[-2:-1].shape = (1, 3)

List-like Indexing

Chain indices with multiple brackets, works for both lists and arrays.

1. Positive Chaining

import numpy as np

def main():
    a = [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
    print(f"{a[1][1] = }")

    b = np.array(a)
    print(f"{b[1][1] = }")

if __name__ == "__main__":
    main()

Output:

a[1][1] = 2
b[1][1] = 2

2. Negative Chaining

import numpy as np

def main():
    a = [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
    print(f"{a[-2][-2] = }")

    b = np.array(a)
    print(f"{b[-2][-2] = }")

if __name__ == "__main__":
    main()

Output:

a[-2][-2] = 4
b[-2][-2] = 4

NumPy-Only Indexing

Use comma-separated indices inside single brackets (NumPy exclusive).

1. Tuple Syntax

import numpy as np

def main():
    a = [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
    try:
        print(f"{a[1, 1] = }")
    except TypeError as e:
        print(f"List error: {e}")

    b = np.array(a)
    print(f"{b[1, 1] = }")

if __name__ == "__main__":
    main()

Output:

List error: list indices must be integers or slices, not tuple
b[1, 1] = 2

2. Negative Tuple

import numpy as np

def main():
    a = [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
    try:
        print(f"{a[-2, -2] = }")
    except TypeError as e:
        print(f"List error: {e}")

    b = np.array(a)
    print(f"{b[-2, -2] = }")

if __name__ == "__main__":
    main()

Output:

List error: list indices must be integers or slices, not tuple
b[-2, -2] = 4

Iteration Example

Print all elements of a 2D array using indexing.

1. Nested Loop

import numpy as np

def main():
    a = np.array([[1, 2, 3], [4, 5, 6]])
    rows, cols = a.shape

    for i in range(rows):
        for j in range(cols):
            element = a[i, j]
            if j != cols - 1:
                print(element, end=' ')
            else:
                print(element)

if __name__ == "__main__":
    main()

Output:

1 2 3
4 5 6

2. Shape Unpacking

Use a.shape to get dimensions for iteration bounds.


Runnable Example: array_indexing_tutorial.py

"""
03_array_indexing.py - Indexing and Slicing

🔗 CRITICAL: Slices return VIEWS not COPIES! (Topic #24)
"""

import numpy as np

if __name__ == "__main__":

    print("="*80)
    print("ARRAY INDEXING AND SLICING")
    print("="*80)
    print("\n🔗 Views vs Copies - Critical concept from Topic #24!")

    # ============================================================================
    # 1D Indexing (like Python lists)
    # ============================================================================

    print("\n" + "="*80)
    print("1D Indexing")
    print("="*80)

    arr = np.array([10, 20, 30, 40, 50])
    print(f"Array: {arr}")
    print(f"  arr[0] = {arr[0]}")  # First element
    print(f"  arr[-1] = {arr[-1]}")  # Last element
    print(f"  arr[1:4] = {arr[1:4]}")  # Slicing

    # ============================================================================
    # CRITICAL: Slices are VIEWS! (Topic #24)
    # ============================================================================

    print("\n" + "="*80)
    print("CRITICAL: Slices Return VIEWS (Topic #24)")
    print("="*80)

    arr = np.array([1, 2, 3, 4, 5])
    view = arr[1:4]  # Elements at index 1, 2, 3

    print(f"Original: {arr}")
    print(f"View: {view}")
    print(f"\nview.base is arr: {view.base is arr} ← It's a VIEW!")

    # Modify the view
    view[0] = 999
    print(f"\nAfter view[0] = 999:")
    print(f"  Original: {arr} ← CHANGED!")
    print(f"  View: {view}")

    print("""
    This is DIFFERENT from Python lists!
    Python: slice_copy = my_list[1:4]  # Creates COPY
    NumPy:  view = arr[1:4]            # Creates VIEW

    Why? Memory efficiency (Topic #24)!
    Views share memory, no copying needed.
    """)

    # Want an independent copy? Use .copy()
    arr = np.array([1, 2, 3, 4, 5])
    independent = arr[1:4].copy()
    independent[0] = 888

    print(f"\nUsing .copy():")
    print(f"  Original: {arr} ← Unchanged")
    print(f"  Copy: {independent}")

    # ============================================================================
    # Multi-dimensional Indexing
    # ============================================================================

    print("\n" + "="*80)
    print("2D Indexing")
    print("="*80)

    matrix = np.array([[10, 20, 30],
                       [40, 50, 60],
                       [70, 80, 90]])
    print(f"Matrix:\n{matrix}\n")

    print(f"matrix[0, 0] = {matrix[0, 0]}  ← Row 0, Col 0")
    print(f"matrix[1, 2] = {matrix[1, 2]}  ← Row 1, Col 2")
    print(f"matrix[-1, -1] = {matrix[-1, -1]}  ← Last row, last col")

    # Slicing rows and columns
    print(f"\nmatrix[0, :] = {matrix[0, :]}  ← First row (all cols)")
    print(f"matrix[:, 0] = {matrix[:, 0]}  ← First column (all rows)")
    print(f"matrix[1:, 1:] = \n{matrix[1:, 1:]}  ← Bottom-right 2x2")

    # ============================================================================
    # Boolean Indexing (returns COPY!)
    # ============================================================================

    print("\n" + "="*80)
    print("Boolean Indexing")
    print("="*80)

    arr = np.array([10, 15, 20, 25, 30])
    mask = arr > 18  # Boolean array
    print(f"Array: {arr}")
    print(f"Mask (arr > 18): {mask}")
    print(f"arr[mask] = {arr[mask]}  ← Elements where mask is True")

    print("""
    \nNote: Boolean indexing creates a COPY, not a view!
    Why? Selected elements may not be contiguous in memory.
    """)

    print("""
    \n🎯 KEY TAKEAWAYS:
    1. Slicing returns VIEWS (shares memory!)
    2. Use .copy() for independent arrays
    3. Boolean indexing returns COPIES
    4. .base attribute checks if it's a view

    🔜 NEXT: 04_basic_operations.py
    """)