6.11. Reduções

Uma redução colapsa um array ao longo de um ou mais eixos calculando somas, médias, mínimos, entre outros. Cada redução é uma única chamada à biblioteca aplicada ao array completo, muito mais rápida do que o ciclo Python equivalente. O numpy cobre as mais comuns:

  • sum() – soma de todos os elementos

  • mean() – média aritmética (soma dividida pelo número de elementos)

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

  • min() / max() – elemento mais pequeno e maior

  • 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 lógico em arrays booleanos

6.11.1. Sem a palavra-chave axis

Sem axis=, uma redução devolve um escalar que abrange todo o array:

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 específico e deixa os restantes intactos. O resultado é um array de classificação uma unidade inferior à 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 aplica-se a todas as reduções: axis=0 colapsa o primeiro eixo, axis=1 colapsa o segundo, e assim por diante. A média e o desvio padrão ao longo de uma linha, por exemplo, escrevem-se 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 o eliminar. A distinção é importante quando o resultado reduzido precisa de ser difundido de volta contra o original: keepdims preserva a classificação, o que mantém as regras de difusão alinhadas eixo a eixo.

Subtrair a média de cada linha a essa 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) devolve um resultado unidimensional de forma (3,). Difundir (3, 4) - (3,) alinha (3,) como (1, 3) após o acréscimo de classificação, o que é incompatível com (3, 4): os últimos eixos discordam (4 contra 3) e nenhum é 1, por isso o numpy lança ValueError. É o keepdims=True que mantém a subtração válida.

6.11.3. A disposição importa

Combinado com a disposição row-major abordada em Forma e strides, reduzir ao longo do último eixo é o caso mais económico. A redução percorre o bloco de dados na direção em que está armazenado, sem saltos de linha para linha:

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 tem a possibilidade de escolher a disposição de um buffer, coloque o eixo longo por último para que as reduções ao longo do mesmo corram na direção mais rápida.

6.11.4. Iteráveis como entrada

A maioria das reduções aceita um iterável Python (uma list, um range, um tuplo) em vez de um ndarray. A conveniência tem um custo de alguns microssegundos pela conversão implícita – que se acumula rapidamente dentro de um ciclo. Quando os mesmos dados são reduzidos várias vezes, construa o ndarray uma vez e passe-o como argumento.