Funções Universais do NumPy

NumPyBeginner
Pratique Agora

Introdução

Neste laboratório, você aprenderá os fundamentos das Funções Universais (Universal Functions) do NumPy, comumente conhecidas como ufuncs. As ufuncs são um pilar da computação de alto desempenho em Python, permitindo que você realize operações rápidas e elemento a elemento em arrays inteiros de dados. Cobriremos aritmética básica, o poderoso conceito de broadcasting, métodos de agregação e como controlar os tipos de dados dos seus resultados. Ao final deste laboratório, você será capaz de usar ufuncs para escrever código de processamento de dados mais limpo e eficiente.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível iniciante com uma taxa de conclusão de 89%. Recebeu uma taxa de avaliações positivas de 100% dos estudantes.

Aritmética Básica com Ufuncs

Em sua essência, as ufuncs realizam operações elemento a elemento. Isso significa que, ao aplicar uma operação a dois arrays, a operação é realizada em cada par de elementos correspondentes. As ufuncs mais comuns são os operadores aritméticos padrão como +, -, * e /.

Vamos começar realizando uma adição simples em dois arrays NumPy.

Primeiro, abra o arquivo ufunc_examples.py no explorador de arquivos à esquerda. Substitua o conteúdo existente pelo seguinte código. Este código importa o NumPy, cria dois arrays e os soma.

import numpy as np

## Create two arrays
arr1 = np.array([0, 2, 3, 4])
arr2 = np.array([1, 1, -1, 2])

## The '+' operator is a ufunc that adds the arrays element-wise
result = arr1 + arr2

## Print the result
print("Step 1 Result:")
print(result)

Após adicionar o código, salve o arquivo. Agora, execute o script a partir do terminal para ver a saída.

python ufunc_examples.py

Você deverá ver o resultado da adição elemento a elemento.

Step 1 Result:
[1 3 2 6]

Isso demonstra o comportamento fundamental de uma ufunc: arr1[0] é somado a arr2[0], arr1[1] a arr2[1], e assim por diante, produzindo um novo array com os resultados.

Broadcasting em Ação

O broadcasting é um mecanismo poderoso que permite ao NumPy trabalhar com arrays de diferentes formas durante operações aritméticas. Internamente, o NumPy "transmite" (broadcasts) o array menor através do array maior para que eles tenham formas compatíveis.

Um exemplo comum é multiplicar cada elemento de um array por um único número. Vamos também ver um caso mais complexo onde um array 1D é transmitido através de um array 2D.

Modifique seu arquivo ufunc_examples.py. Adicione o seguinte código ao final do script.

## --- Appended code for Step 2 ---

## Broadcasting a scalar to an array
arr1 = np.array([1, 2, 3])
scalar_result = arr1 * 10
print("\nStep 2 Result (Scalar Broadcast):")
print(scalar_result)

## Broadcasting a 1D array to a 2D array
arr2d = np.array([[1], [2], [3]]) ## Shape (3, 1)
arr1d = np.array([1, 2, 3])      ## Shape (3,)
broadcast_result = arr2d * arr1d
print("\nStep 2 Result (Array Broadcast):")
print(broadcast_result)

Salve o arquivo e execute-o novamente no terminal.

python ufunc_examples.py

Você verá a saída para o Passo 1 e o Passo 2.

Step 1 Result:
[1 3 2 6]

Step 2 Result (Scalar Broadcast):
[10 20 30]

Step 2 Result (Array Broadcast):
[[1 2 3]
 [2 4 6]
 [3 6 9]]

No segundo exemplo, o array 1D arr1d (forma (3,)) e o array 2D arr2d (forma (3, 1)) são transmitidos juntos para uma forma comum de (3, 3) antes que a multiplicação elemento a elemento ocorra.

Agregando Arrays com .reduce()

Além das operações elemento a elemento, as ufuncs possuem métodos especiais para realizar agregações. O método .reduce() é um dos mais úteis. Ele aplica uma ufunc repetidamente ao longo de um eixo especificado de um array até que reste apenas uma dimensão.

Por exemplo, np.add.reduce(arr) é equivalente a np.sum(arr). Vamos ver como isso funciona em um array 2D.

Anexe o seguinte código ao seu arquivo ufunc_examples.py.

## --- Appended code for Step 3 ---

## Create a 3x3 array
arr = np.arange(9).reshape(3, 3)
print("\nStep 3 Original Array:")
print(arr)

## Reduce the array by summing along axis 1 (the columns)
## This will sum the elements in each row.
## For row 0: 0 + 1 + 2 = 3
## For row 1: 3 + 4 + 5 = 12
## For row 2: 6 + 7 + 8 = 21
reduced_result = np.add.reduce(arr, axis=1)

print("\nStep 3 Result (reduce on axis=1):")
print(reduced_result)

Salve o arquivo e execute-o.

python ufunc_examples.py

A saída agora incluirá os resultados desta etapa.

... (previous output) ...

Step 3 Original Array:
[[0 1 2]
 [3 4 5]
 [6 7 8]]

Step 3 Result (reduce on axis=1):
[ 3 12 21]

Como você pode ver, .reduce() colapsou o array ao longo do eixo especificado aplicando a operação add aos seus elementos.

Especificando Tipos de Dados de Saída

O NumPy geralmente determina o tipo de dados do array de saída automaticamente. No entanto, você pode especificar explicitamente o tipo de dados de saída usando o argumento dtype. Isso é útil para controlar o uso de memória ou garantir a precisão numérica.

Vamos realizar uma redução usando multiplicação e forçar a saída a ser um número de ponto flutuante (floating-point), mesmo que a entrada seja um array de inteiros.

Adicione o seguinte código ao final de ufunc_examples.py.

## --- Appended code for Step 4 ---

## Use the same 3x3 array from Step 3
arr = np.arange(1, 10).reshape(3, 3) ## Using 1-9 to avoid multiplying by zero
print("\nStep 4 Original Array:")
print(arr)

## Reduce with multiplication, casting the output to float
## For row 0: 1 * 2 * 3 = 6
## For row 1: 4 * 5 * 6 = 120
## For row 2: 7 * 8 * 9 = 504
multiply_result = np.multiply.reduce(arr, axis=1, dtype=float)

print("\nStep 4 Result (multiply.reduce with dtype=float):")
print(multiply_result)

Salve e execute o script.

python ufunc_examples.py

Observe a saída para o Passo 4.

... (previous output) ...

Step 4 Original Array:
[[1 2 3]
 [4 5 6]
 [7 8 9]]

Step 4 Result (multiply.reduce with dtype=float):
[  6. 120. 504.]

Note os pontos finais (.) no array de saída [ 6. 120. 504.]. Isso indica que os elementos agora são números de ponto flutuante, como especificamos com dtype=float.

Substituindo o Comportamento de Ufunc

O sistema de ufuncs do NumPy é extensível. Você pode criar seus próprios objetos semelhantes a arrays que definem como as ufuncs devem operar sobre eles. Este é um recurso avançado, geralmente feito por meio de subclassificação da ndarray do NumPy e sobrescrita de métodos especiais como __add__ (para o operador +).

Vamos criar uma classe de array personalizada simples que imprime uma mensagem sempre que a adição é realizada sobre ela.

Adicione este último bloco de código a ufunc_examples.py.

## --- Appended code for Step 5 ---

## Define a custom array class by subclassing np.ndarray
class MyArray(np.ndarray):
    def __add__(self, other):
        print("\nStep 5: Custom add method called!")
        ## Call the original implementation from the parent class
        return super().__add__(other)

## Create an instance of our custom class
## We must use .view() to cast the ndarray to our custom class
my_arr = np.array([10, 20, 30]).view(MyArray)

## Perform addition, which will trigger our custom method
override_result = my_arr + 5

print("Step 5 Result (Overridden Ufunc):")
print(override_result)

Salve o arquivo e execute-o pela última vez.

python ufunc_examples.py

Verifique a saída final.

... (previous output) ...

Step 5: Custom add method called!
Step 5 Result (Overridden Ufunc):
[15 25 35]

Você pode ver que nossa mensagem personalizada foi impressa antes do resultado da adição, confirmando que nosso método __add__ foi chamado. Isso demonstra a poderosa flexibilidade do sistema de ufuncs.

Resumo

Neste laboratório, você aprendeu os fundamentos das Funções Universais (ufuncs) do NumPy. Começamos com operações aritméticas básicas elemento a elemento, que formam a base da computação vetorizada. Em seguida, você explorou o broadcasting, um recurso fundamental que permite ao NumPy realizar operações em arrays de formas diferentes. Também abordamos como usar métodos de ufunc como .reduce() para agregação de dados e como controlar o tipo de dados de saída com o argumento dtype. Finalmente, você viu um exemplo avançado de como personalizar o comportamento de ufuncs por meio da subclassificação de np.ndarray. Com essas habilidades, você está agora mais preparado para escrever código numérico eficiente, legível e poderoso com o NumPy.