Skip to content

    Chapter 15, Basic

    Universal functions (ufuncs)

    Square root, logarithm, exponential, trigonometry: all elementary mathematics applied to the entire array at once, in compiled code, with broadcasting included.

    Code for this chapter: básico/cap15_ufuncs.py

    Mathematics over the entire array

    A ufunc (universal function) applies an operation to each element. The operators +, -, * and / are, underneath, np.add, np.subtract, np.multiply and np.divide:

    básico/cap15_ufuncs.pylinhas 10 a 16
    import numpy as np
    
    a = np.array([1.0, 4.0, 9.0])
    print(np.sqrt(a), np.exp(np.array([0, 1])), np.log(np.array([1, np.e])))
    print(np.add(a, 1), np.multiply(a, a))
    print(np.abs(np.array([-3, 2])), np.round(np.array([1.234, 5.678]), 1))
    print(np.clip(np.array([-5, 3, 12]), 0, 10))
    
    Output
    [1. 2. 3.] [1.         2.71828183] [0. 1.]
    [ 2.  5. 10.] [ 1. 16. 81.]
    [3 2] [1.2 5.7]
    [ 0  3 10]
    

    The np.clip(x, mínimo, máximo) "squeeze" the values into a range: everything below the minimum becomes the minimum, and everything above the maximum becomes the maximum. It is widely used to clean data with impossible values.

    Impossible results become `nan` and `inf`

    An impossible calculation (square root of a negative number, division by zero) does not crash the program: NumPy issues a warning and returns a special value, nan ("not a number") or inf (infinity). Execution continues:

    básico/cap15_ufuncs.pylinhas 21 a 26
    import warnings
    
    with warnings.catch_warnings(record=True) as avisos:
        warnings.simplefilter("always")
        resultado = np.sqrt(np.array([4.0, -4.0]))
    print(resultado, avisos[0].category.__name__)
    
    Output
    [ 2. nan] RuntimeWarning
    

    This behavior is convenient, and also dangerous: the nan spreads. Any calculation with a nan produces nan, and the error appears far from where it was born. Chapter 25 deals with missing data. If you know what you're doing, the np.errstate silences warnings in a snippet:

    básico/cap15_ufuncs.pylinhas 28 a 29
    with np.errstate(invalid="ignore", divide="ignore"):
        print(np.array([1.0, 0.0]) / np.array([0.0, 0.0]))
    
    Output
    [inf nan]
    

    Choose where to calculate: `where` and `out`

    Ufuncs accept two parameters that save memory and avoid unwanted calculations. The where tells in which positions to calculate, and the out tells where to store the result, instead of creating a new array. Where the where is false, the value of out remains:

    básico/cap15_ufuncs.pylinhas 34 a 37
    x = np.array([1.0, 4.0, 9.0, 16.0])
    saida = np.zeros_like(x)
    np.sqrt(x, out=saida, where=x > 5)
    print(saida)
    
    Output
    [0. 0. 3. 4.]
    

    Integer division and remainder

    The operators // and % are also ufuncs, and integer division rounds down, even with negative numbers:

    básico/cap15_ufuncs.pylinha 42
    print(np.array([7, -7]) // 2, np.mod(-7, 3))
    
    Output
    [ 3 -4] 2
    

    Do not mix math with NumPy

    The module math works with a number at a time and fails with arrays (chapter 12). NumPy not only accepts arrays but also accepts a single number, so in numerical code, prefer np.sqrt, np.exp and company, even for single values, and the code works for both cases.

    Exercise 1

    The sigmoid function

    Write sigmoide(x), equal to 1 / (1 + e^(-x)). It is the function that "compresses" any number to the range between 0 and 1, and it is the basis of much in machine learning.