Pular para o conteúdo

    Capítulo 28, Intermediário

    Janelas deslizantes e séries

    Médias móveis, máximos de uma janela, filtros de imagem: tudo isso é "olhar um pedaço, andar um passo, repetir". O NumPy faz isso sem laço, e sem copiar os dados.

    Código deste capítulo: intermediario/cap28_janelas_series.py

    `sliding_window_view`

    A função cria uma visão com todas as janelas de um tamanho dado, uma por linha. Agregando cada janela, você obtém a média móvel:

    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"])
    
    Saída
    [[1. 2. 3.]
     [2. 3. 4.]
     [3. 4. 5.]
     [4. 5. 6.]]
    [2. 3. 4. 5.]
    True False
    

    As janelas não foram copiadas: o último resultado mostra que compartilham memória com a série. E ela é somente leitura, porque várias janelas apontam para os mesmos números, e escrever em uma bagunçaria as outras. Essa visão é possível por causa dos strides (capítulo 29).

    Duas outras formas de média móvel

    A soma acumulada permite calcular qualquer média móvel em uma passada, qualquer que seja o tamanho da janela. A convolução é a forma "clássica" de processamento de sinais:

    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"))
    
    Saída
    True
    [2. 3. 4. 5.]
    
    TécnicaMemóriaQuando usar
    sliding_window_view + agregaçãoVisão, sem cópia, mas a agregação percorre todas as janelasQualquer função (média, máximo, mediana)
    cumsumUma passadaSomas e médias, com janela de qualquer tamanho
    np.convolveCria o resultadoFiltros lineares

    Acumuladores: máximo corrido e queda máxima

    Os acumuladores guardam o resultado até aqui. O np.maximum.accumulate dá o maior valor visto até cada ponto. Com ele se calcula a queda máxima (drawdown) de uma série de preços: a maior perda desde um pico anterior, uma medida de risco comum em finanças:

    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))
    
    Saída
    [100. 110. 110. 120. 120. 120.] -0.25
    

    A queda máxima foi de 25%: do pico de 120 para 90.

    Janelas em duas dimensões

    A mesma função aceita uma janela com uma dimensão por eixo, e é a base de filtros de imagem. Aqui, cada bloco de 3 por 3 de uma imagem de 5 por 5 vira uma janela:

    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])
    
    Saída
    (3, 3, 3, 3) 6.0
    

    São 3 × 3 = 9 janelas possíveis, cada uma com 3 × 3 valores. O projeto do capítulo 42 usa isso para desfocar e detectar bordas em uma imagem.

    Exercício 1

    Máximo móvel

    Escreva maximo_movel(x, k) que devolva o maior valor de cada janela de tamanho k.