6.11. Reduções

Uma redução colapsa um array ao longo de um ou mais eixos por meio de soma, média, mínimo e assim por diante. Cada redução é uma única chamada de biblioteca aplicada ao array inteiro, muito mais rápida do que o laço Python equivalente. O numpy cobre as mais corriqueiras:

  • sum() – total de todos os elementos

  • mean() – média aritmética (soma dividida pela quantidade de elementos)

  • std() – desvio padrão; ddof= ajusta o divisor (N - ddof)

  • min() / max() – o menor e o maior elemento

  • median() – valor central quando os elementos estão ordenados (percentil 50)

  • argmin() / argmax() – o índice do elemento mínimo ou máximo

  • all() / any() – reduções de valor-verdade sobre arrays booleanos

6.11.1. Sem a palavra-chave axis

Chamada sem axis=, uma redução retorna um escalar que cobre o array inteiro:

a = np.array([1, 2, 3, 4], dtype=np.float)
np.sum(a)           # 10.0
np.mean(a)          # 2.5
np.std(a)           # 1.118...
np.median(a)        # 2.5

b = np.array([40, 10, 30, 20], dtype=np.float)
np.max(b)           # 40.0
np.argmax(b)        # 0  (index of the maximum)

6.11.2. Com a palavra-chave axis

axis= contrai um eixo nomeado e mantém os demais intactos. O resultado é um array de uma dimensão (rank) a menos que a entrada:

m = np.arange(12, dtype=np.float).reshape((3, 4))

np.sum(m)               # 66.0          - scalar
np.sum(m, axis=0)       # length-4      - column sums
np.sum(m, axis=1)       # length-3      - row sums

A mesma regra de forma vale para toda redução: axis=0 colapsa o primeiro eixo, axis=1 colapsa o segundo, e assim por diante. Média / desvio padrão ao longo de uma linha, por exemplo, são escritos como np.mean(m, axis=1) e np.std(m, axis=1). O resultado tem o comprimento do outro eixo.

A palavra-chave keepdims=True mantém o eixo contraído no lugar com comprimento 1 em vez de removê-lo. A distinção importa quando o resultado reduzido precisa fazer broadcasting de volta contra o original: keepdims preserva a dimensão (rank), o que mantém as regras de broadcasting alinhadas eixo a eixo.

Subtrair de cada linha a média dessa linha é o uso canônico:

m = np.arange(12, dtype=np.float).reshape((3, 4))
row_means = np.mean(m, axis=1, keepdims=True)
# row_means has shape (3, 1)
centred = m - row_means
# (3, 4) - (3, 1) -> (3, 4), each row centred on its own mean

Sem keepdims, np.mean(m, axis=1) retorna um resultado 1-D de forma (3,). O broadcasting de (3, 4) - (3,) alinha (3,) como (1, 3) após o acréscimo de dimensão à esquerda, o que é incompatível com (3, 4): os últimos eixos discordam (4 contra 3) e nenhum deles é 1, então o numpy lança ValueError. keepdims=True é o que mantém a subtração válida.

6.11.3. O layout importa

Combinado com o layout row-major abordado em Formato e strides, reduzir ao longo do último eixo é o caso mais barato. A redução percorre o bloco de dados na direção em que ele está armazenado, sem saltos de uma linha para outra:

m = np.arange(2000, dtype=np.float).reshape((2, 1000))
np.sum(m, axis=1)       # cheap - long axis is the inner one
np.sum(m, axis=0)       # has to jump rows on every step

Quando a aplicação pode escolher como dispor um buffer, coloque o eixo mais longo por último para que as reduções ao longo dele rodem na direção rápida.

6.11.4. Iteráveis como entrada

A maioria das reduções aceita um iterável Python (uma list, um range, uma tupla) no lugar de um ndarray. A conveniência custa alguns microssegundos pela conversão implícita – o que se acumula rápido em um laço. Quando os mesmos dados são reduzidos várias vezes, construa o ndarray uma vez e reaproveite-o.