Working with arrays

Part of the free LogicWiz Python course, Module 10: NumPy and pandas. The examples and practice run in the app.

Riya's class teacher has every student's English, Maths and CS marks in one table. She wants each student's total, the top mark in every subject, and everyone who scored above 75 in Maths, without writing a single loop.

You'll learn to

  • Index and slice a 2D array by row and column
  • Sum, average and find maxima along an axis
  • Filter with Boolean masks like `marks[marks > 75]`
  • Know that a slice is a view, and when to `.copy()`

Learn

Rows and columns

import numpy as np

# rows = students, columns = Eng, Maths, CS
marks = np.array([[78, 92, 88],
                  [65, 58, 71],
                  [90, 85, 95],
                  [55, 76, 60]])
print(marks[2, 1])        # row 2, column 1
print(marks[0])           # the first student's row
print(marks[:, 1])        # every row, column 1: the Maths column
print(marks[1:3, 0:2])    # rows 1-2, columns 0-1

Inside [ ], the part before the comma picks rows and the part after picks columns. A lone : means "all".

Which way is axis 0?

Aggregates like sum, mean and max take an axis:

  • axis=0 goes down the rows and gives one answer per column (each subject's total).
  • axis=1 goes across the columns and gives one answer per row (each student's total).
  • No axis: one answer for the whole array.

Memory trick: the axis you name is the one that disappears. A (4, 3) array summed on axis 0 becomes shape (3,).

Totals per student, toppers per subject

import numpy as np

marks = np.array([[78, 92, 88],
                  [65, 58, 71],
                  [90, 85, 95],
                  [55, 76, 60]])
print(marks.sum(axis=1))           # per student
print(marks.max(axis=0))           # per subject
print(marks.argmax(axis=0))        # which student topped each subject
print(marks.mean(axis=0).round(1))
print(marks.sum())

Boolean masks: filtering without a loop

import numpy as np

maths = np.array([92, 58, 85, 76])
print(maths > 75)                  # a mask of True/False
print(maths[maths > 75])           # keep only the True positions
print((maths > 75).sum())          # True counts as 1
print(maths[(maths > 60) & (maths < 90)])
print(np.where(maths >= 75, "Pass", "Retest"))

np.where(condition, a, b) picks a where the condition is True and b elsewhere: an if/else for every element.

Use &, | and ~, not and, or, not, and wrap each condition in brackets. (m > 60) and (m < 90) raises ValueError: The truth value of an array with more than one element is ambiguous. Without brackets, m > 60 & m < 90 is read the wrong way round, because & binds tighter than >.

A slice is a view, not a copy

import numpy as np

stock = np.array([50, 40, 30, 20])
first_two = stock[:2]
first_two[0] = 0             # changes stock too!
print(stock)

safe = stock[2:].copy()
safe[0] = 999                # stock is untouched
print(stock)

lst = [50, 40, 30]
part = lst[:2]
part[0] = 0                  # a list slice IS a copy
print(lst)

A NumPy slice is a view: a new window onto the same memory. That saves time and memory on big data, but changes show through. A Boolean mask like stock[stock > 25] always makes a copy.

The Boolean-filter idea, marks[marks > 75], is exactly how the CBSE IP practicals filter pandas data (df[df["marks"] > 75], lesson 10.4). Learn it once here and it carries over.

In one line

m[r, c], m[:, c], m[r1:r2, c1:c2] slice 2D arrays; axis=0 gives per-column answers, axis=1 per-row; m[m > 75] filters (use &, | with brackets); a slice is a view, so .copy() when you need independence.

Interview corner

What is the difference between a view and a copy in NumPy?

A view (from basic slicing like a[1:3], or reshape) shares memory with the original, so changes show in both. A copy (from .copy(), Boolean masks or fancy indexing like a[[0, 2]]) has its own data. b.base is a tells you if b is a view of a.

What does `axis` mean in `arr.sum(axis=0)`?

It names the dimension that gets collapsed. For a 2D array, axis=0 aggregates down the rows, giving one result per column; axis=1 aggregates across the columns, giving one per row.

How do you filter a NumPy array by a condition?

Use a Boolean mask: arr[arr > 75]. Combine conditions with &, |, ~ and brackets: arr[(arr > 60) & (arr < 90)]. Use np.where(cond, a, b) to choose values element by element.