Skip to content

    Chapter 21, Intermediate

    Vectorized conditions: where, select and clip

    The `if` does not accept an array. To apply a decision to each element, you use functions that make the choice for the entire array at once.

    Code for this chapter: intermediario/cap21_condicoes.py

    `np.where`: the `if` of the array

    With three arguments, the np.where(condição, se_verdadeiro, se_falso) chooses, element by element, the value of one of the two outputs. With a single argument, it returns the positions where the condition is true:

    intermediario/cap21_condicoes.pylinhas 10 a 15
    import numpy as np
    
    notas = np.array([55, 72, 91, 40, 68])
    print(np.where(notas >= 60, "aprovado", "reprovado"))
    print(np.where(notas >= 60, notas, 0))
    print(np.where(notas > 70))
    
    Output
    ['reprovado' 'aprovado' 'aprovado' 'reprovado' 'aprovado']
    [ 0 72 91  0 68]
    (array([1, 2]),)
    

    The result of a single argument is a tuple with an array of positions per axis, and that is why it shows the (array([1, 2]),).

    `np.select`: multiple ranges

    With more than two options, chaining where becomes messy. The np.select takes a list of conditions and a list of results, and uses the result of the first true condition. Therefore, the order matters: the more restrictive conditions come first:

    intermediario/cap21_condicoes.pylinhas 20 a 22
    condicoes = [notas >= 90, notas >= 70, notas >= 60]
    rotulos = ["A", "B", "C"]
    print(np.select(condicoes, rotulos, default="D"))
    
    Output
    ['D' 'B' 'A' 'D' 'C']
    

    Limit and compare element by element

    The np.clip limits the values to a range. The np.maximum and np.minimum compare two arrays (or an array and a number) element by element, and the np.maximum(x, 0) is the ReLU function, one of the most used in neural networks:

    intermediario/cap21_condicoes.pylinhas 27 a 30
    print(np.maximum(notas, 50))
    
    x = np.array([-2.0, -0.5, 0.5, 2.0])
    print(np.piecewise(x, [x < 0, x >= 0], [lambda t: t ** 2, lambda t: t]))
    
    Output
    [55 72 91 50 68]
    [4.   0.25 0.5  2.  ]
    

    The np.piecewise applies a different function in each range, useful for piecewise defined functions.

    The trap: `where` calculates both sides

    The np.where evaluates both entire expressions before choosing. If one of them generates an error or a warning on some element (a division by zero), the warning appears even if that element was never chosen:

    intermediario/cap21_condicoes.pylinhas 35 a 38
    den = np.array([2.0, 0.0, 4.0])
    with np.errstate(divide="ignore"):
        ingenuo = np.where(den != 0, 1 / den, 0.0)
    print(ingenuo)
    
    Output
    [0.5  0.   0.25]
    

    To not calculate where it shouldn't, use the where and out of the ufunc itself (chapter 15):

    intermediario/cap21_condicoes.pylinhas 40 a 41
    seguro = np.divide(1.0, den, out=np.zeros_like(den), where=den != 0)
    print(seguro)
    
    Output
    [0.5  0.   0.25]
    

    The result is the same, but the second form doesn't even attempt to divide by zero.

    Exercise 1

    Classify the BMI

    Write classificar_imc(imc) that returns "abaixo" (less than 18.5), "normal" (less than 25), "sobrepeso" (less than 30) or "obesidade", for an array of values, with np.select.