Indexing, slicing & boolean masks

Selecting the right elements is most of what you do with arrays. NumPy offers three ways: basic indexing and slicing, boolean masks, and fancy (integer array) indexing. They behave differently with memory, which is the part that trips people up.

Basic indexing and slicing

x = np.arange(12).reshape(3, 4)
x[0, 1]        # row 0, col 1
x[:, 2]        # third column
x[1:, ::2]     # rows 1.., every 2nd column

scores = np.array([45, 88, 67, 92, 30])
passed = scores[scores >= 50]        # boolean mask
scores[scores < 50] = 50             # grace marks

Use the comma form x[0, 1], not x[0][1]. The comma form is one operation; the chained form builds a temporary row array first.

Slice syntax is start:stop:step, with stop excluded. Negative values count from the end: x[-1] is the last row, x[:, ::-1] reverses the columns.

Views versus copies

Basic slicing returns a view that shares memory with the original:

row = x[0]
row[0] = 999
print(x[0, 0])     # 999 — the original changed too
safe = x[0].copy() # independent

This is a feature: slicing a 2 GB array costs nothing. It becomes a bug when you did not expect it, so call .copy() whenever you intend to modify a slice independently.

Boolean masks

A mask is an array of True/False the same shape as the data:

sales = np.array([120, 340, 90, 610, 245])     # ₹ thousand
city = np.array(["Pune", "Delhi", "Pune", "Surat", "Delhi"])

big = sales > 200
print(big)                     # [False  True False  True  True]
print(sales[big].sum())        # 1195
print(sales[(city == "Pune") & (sales > 100)])   # [120]

Combine conditions with & (and), | (or) and ~ (not), and wrap each condition in brackets. Python's and / or do not work element-wise and raise a ValueError on arrays.

Helper Use
np.where(cond, a, b) Element-wise if/else
np.where(cond) Indices where the condition is True
mask.sum() How many rows match
mask.any() / mask.all() Is any / every element True
np.isin(a, values) Membership test against a list
band = np.where(sales > 300, "high", "normal")
idx = np.where(sales > 300)[0]         # array([1, 3])
print(np.isin(city, ["Pune", "Surat"]))

Fancy indexing

Pass an array of positions to pick elements in any order, with repeats allowed:

top = sales.argsort()[::-1][:3]     # positions of the three largest
print(city[top], sales[top])        # ['Surat' 'Delhi' 'Delhi'] [610 340 245]

Boolean and fancy indexing always return copies, not views — so sales[big] = 0 works (NumPy handles it as an assignment), but sales[big][0] = 0 silently modifies a throwaway copy.

Updating in place

gst = np.array([0.05, 0.12, 0.18, 0.05])
gst[gst == 0.05] = 0.00               # exempt the 5% slab
np.clip(sales, 0, 500, out=sales)     # cap values in place

Common mistakes

  • Using and / or instead of & / | on arrays.
  • Forgetting brackets: city == "Pune" & sales > 100 parses the & first and fails.
  • Editing a slice and being surprised the original changed (or did not, with a boolean mask).
  • Using x[0][1] in tight loops instead of x[0, 1].

Practice

Build an array of 500 random exam marks and a matching array of section labels ("A" to "D"). Count marks below 35, raise them to 35 in place, select every section-B mark above the overall mean, and print the five highest marks with their sections using argsort.

B

Written by Bablu Kumar Jha

Founder, BeeProton AI · Mumbai, Maharashtra, India