Skip to content

    Chapter 28, Intermediate

    Sliding windows and series

    Moving averages, window maxima, image filters: all of this is "look a piece, take a step, repeat". NumPy does this without a loop, and without copying the data.

    Code for this chapter: intermediario/cap28_janelas_series.py

    `sliding_window_view`

    The function creates a view with all the windows of a given size, one per line. By aggregating each window, you get the moving average:

    intermediario/cap28_janelas_series.pylinhas 10 a 17
    import numpy as np
    from numpy.lib.stride_tricks import sliding_window_view
    
    serie = np.array([1, 2, 3, 4, 5, 6], dtype=float)
    janelas = sliding_window_view(serie, 3)
    print(janelas)
    print(janelas.mean(axis=1))
    print(np.shares_memory(serie, janelas), janelas.flags["WRITEABLE"])
    
    Output
    [[1. 2. 3.]
     [2. 3. 4.]
     [3. 4. 5.]
     [4. 5. 6.]]
    [2. 3. 4. 5.]
    True False
    

    The windows were not copied: the last result shows that they share memory with the series. And it is read-only, because several windows point to the same numbers, and writing in one messes up the others. This view is possible because of the strides (chapter 29).

    Two other forms of moving average

    The cumulative sum allows calculating any moving average in one pass, regardless of the window size. Convolution is the "classic" form of signal processing:

    intermediario/cap28_janelas_series.pylinhas 22 a 28
    def media_movel(x, k):
        acumulada = np.cumsum(np.insert(x, 0, 0.0))
        return (acumulada[k:] - acumulada[:-k]) / k
    
    
    print(np.allclose(media_movel(serie, 3), janelas.mean(axis=1)))
    print(np.convolve(serie, np.ones(3) / 3, mode="valid"))
    
    Output
    True
    [2. 3. 4. 5.]
    
    TechniqueMemoryWhen to use
    sliding_window_view + aggregationView, without copy, but the aggregation goes through all the windowsAny function (mean, max, median)
    cumsumOne passSums and means, with a window of any size
    np.convolveCreates the resultLinear filters

    Accumulators: running max and max drop

    The accumulators store the result so far. The np.maximum.accumulate gives the highest value seen up to each point. With it, the maximum drawdown (drawdown) of a price series: the largest loss since a previous peak, a common risk measure in finance:

    intermediario/cap28_janelas_series.pylinhas 33 a 36
    precos = np.array([100, 110, 105, 120, 90, 95], dtype=float)
    pico = np.maximum.accumulate(precos)
    queda = (precos - pico) / pico
    print(pico, queda.min().round(3))
    
    Output
    [100. 110. 110. 120. 120. 120.] -0.25
    

    The maximum drawdown was 25%: from a peak of 120 to 90.

    Windows in two dimensions

    The same function accepts a window with one dimension per axis, and it is the basis of image filters. Here, each block of 3 by 3 from a 5 by 5 image becomes a window:

    intermediario/cap28_janelas_series.pylinhas 41 a 43
    img = np.arange(25, dtype=float).reshape(5, 5)
    blocos = sliding_window_view(img, (3, 3))
    print(blocos.shape, blocos.mean(axis=(-1, -2))[0, 0])
    
    Output
    (3, 3, 3, 3) 6.0
    

    There are 3 × 3 = 9 possible windows, each with 3 × 3 values. The project in chapter 42 uses this to blur and detect edges in an image.

    Exercise 1

    Moving maximum

    Write maximo_movel(x, k) that returns the highest value from each window of size k.