Chapter 9, Basic
Boolean and fancy indexing
It is the technique that replaces the loop with `if`. Instead of iterating through the elements asking "does this work?", you describe the condition and NumPy returns only those that work.
Code for this chapter: básico/cap09_indexacao_booleana.py
A condition becomes a mask
Comparing an array with a value produces another array, of booleans, called a mask. Using it as an index filters the true elements:
import numpy as np
notas = np.array([78, 92, 45, 88, 91, 30, 85])
mascara = notas > 75
print(mascara)
print(notas[mascara])
print(notas[notas > 75])
print((notas > 75).sum(), (notas > 75).mean().round(3))
[ True True False True True False True]
[78 92 88 91 85]
[78 92 88 91 85]
5 0.714
As True is 1 and False is 0, the sum() of a mask counts how many elements pass, and the mean() gives the proportion. Here, 5 out of 7 grades (71.4%) passed 75. It is one of the most used tricks in data analysis.
Multiple conditions: `&`, `|` and `~`
To combine conditions, use the operators & (and), | (or) and ~ (no), and always put each condition in parentheses, because these operators have higher precedence than comparison:
presenca = np.array([95, 60, 80, 92, 70, 88, 97])
print(notas[(notas > 80) & (presenca > 90)])
print(notas[(notas < 50) | (presenca < 65)])
print(notas[~(notas > 80)])
[88 85]
[92 45 30]
[78 45 30]
Using and and or in Python is the classic mistake, and NumPy responds with a message that seems enigmatic:
try:
notas[(notas > 80) and (presenca > 90)]
except ValueError as erro:
print(erro)
The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
The and in Python needs to decide if an entire array is true or false, and an array with multiple elements does not have a single answer. That’s why there are & and |, which operate element-wise.
Fancy indexing: a list of positions
By passing a list of indices, you choose the positions you want, in the order you want, including repeating:
nums = np.array([10, 20, 30, 40, 50])
print(nums[[0, 2, 4]])
print(nums[[4, 4, 0]])
tabela = np.arange(12).reshape(3, 4)
print(tabela[[0, 2], :])
print(tabela[[0, 1], [1, 3]])
[10 30 50]
[50 50 10]
[[ 0 1 2 3]
[ 8 9 10 11]]
[1 7]
With two lists, NumPy combines them pairwise: the last result is tabela[0, 1] and tabela[1, 3], and not a 2×2 block.
Mask and fancy return copies
Unlike slicing, boolean indexing and fancy indexing always return a copy. Changing the result does not affect the original:
recorte = nums[[0, 1]]
recorte[0] = 999
print(nums[0], np.shares_memory(nums, recorte))
10 False
| Technique | Returns | Shares memory with the original |
|---|---|---|
Slice (a[1:3]) | View | Yes |
Mask (a[a > 0]) | Copy | No |
Fancy (a[[0, 2]]) | Copy | No |
Exercise 1
Approved and above average
Write aprovados(notas, minimo=60) (the grades that meet the minimum) and acima_da_media(notas) (the positions of the grades above average, with np.nonzero).