Skip to content

    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:

    básico/cap09_indexacao_booleana.pylinhas 10 a 17
    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))
    
    Output
    [ 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:

    básico/cap09_indexacao_booleana.pylinhas 22 a 25
    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)])
    
    Output
    [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:

    básico/cap09_indexacao_booleana.pylinhas 27 a 30
    try:
        notas[(notas > 80) and (presenca > 90)]
    except ValueError as erro:
        print(erro)
    
    Output
    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:

    básico/cap09_indexacao_booleana.pylinhas 35 a 41
    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]])
    
    Output
    [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:

    básico/cap09_indexacao_booleana.pylinhas 46 a 48
    recorte = nums[[0, 1]]
    recorte[0] = 999
    print(nums[0], np.shares_memory(nums, recorte))
    
    Output
    10 False
    
    TechniqueReturnsShares memory with the original
    Slice (a[1:3])ViewYes
    Mask (a[a > 0])CopyNo
    Fancy (a[[0, 2]])CopyNo

    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).