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:
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"])
[[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:
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"))
True
[2. 3. 4. 5.]
| Técnica | Memória | Quando usar |
|---|---|---|
sliding_window_view + agregação | Visão, sem cópia, mas a agregação percorre todas as janelas | Qualquer função (média, máximo, mediana) |
cumsum | Uma passada | Somas e médias, com janela de qualquer tamanho |
np.convolve | Cria o resultado | Filtros 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:
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))
[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:
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])
(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.