Introducción a las Funciones Universales de NumPy

NumPyBeginner
Practicar Ahora

Introducción

En este laboratorio, aprenderá los fundamentos de las Funciones Universales (Universal Functions) de NumPy, comúnmente conocidas como ufuncs. Las ufuncs son una piedra angular de la computación de alto rendimiento en Python, permitiéndole realizar operaciones rápidas y elemento a elemento en arrays completos de datos. Cubriremos aritmética básica, el potente concepto de broadcasting, métodos de agregación y cómo controlar los tipos de datos de sus resultados. Al final de este laboratorio, podrá utilizar ufuncs para escribir código de procesamiento de datos más limpio y eficiente.

Este es un Guided Lab, que proporciona instrucciones paso a paso para ayudarte a aprender y practicar. Sigue las instrucciones cuidadosamente para completar cada paso y obtener experiencia práctica. Los datos históricos muestran que este es un laboratorio de nivel principiante con una tasa de finalización del 89%. Ha recibido una tasa de reseñas positivas del 100% por parte de los estudiantes.

Aritmética Básica con Ufuncs

En su núcleo, las ufuncs realizan operaciones elemento a elemento. Esto significa que cuando aplica una operación a dos arrays, la operación se realiza en cada par de elementos correspondientes. Las ufuncs más comunes son los operadores aritméticos estándar como +, -, * y /.

Comencemos realizando una simple suma en dos arrays de NumPy.

Primero, abra el archivo ufunc_examples.py desde el explorador de archivos de la izquierda. Reemplace el contenido existente con el siguiente código. Este código importa NumPy, crea dos arrays y los suma.

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)

Después de agregar el código, guarde el archivo. Ahora, ejecute el script desde la terminal para ver la salida.

python ufunc_examples.py

Debería ver el resultado de la suma elemento a elemento.

Step 1 Result:
[1 3 2 6]

Esto demuestra el comportamiento fundamental de una ufunc: arr1[0] se suma a arr2[0], arr1[1] a arr2[1], y así sucesivamente, produciendo un nuevo array con los resultados.

Difusión en Acción

Broadcasting es un mecanismo potente que permite a NumPy trabajar con arrays de diferentes formas durante las operaciones aritméticas. Internamente, NumPy "transmite" (broadcasts) el array más pequeño a través del array más grande para que tengan formas compatibles.

Un ejemplo común es multiplicar cada elemento de un array por un solo número. Veamos también un caso más complejo donde un array 1D se transmite a través de un array 2D.

Modifique su archivo ufunc_examples.py. Agregue el siguiente código al final del 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)

Guarde el archivo y ejecútelo de nuevo desde la terminal.

python ufunc_examples.py

Verá la salida tanto del Paso 1 como del Paso 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]]

En el segundo ejemplo, el array 1D arr1d (forma (3,)) y el array 2D arr2d (forma (3, 1)) se transmiten juntos a una forma común de (3, 3) antes de que ocurra la multiplicación elemento a elemento.

Agregando Arrays con .reduce()

Además de las operaciones elemento a elemento, las ufuncs tienen métodos especiales para realizar agregaciones. El método .reduce() es uno de los más útiles. Aplica una ufunc repetidamente a lo largo de un eje especificado de un array hasta que solo queda una dimensión.

Por ejemplo, np.add.reduce(arr) es equivalente a np.sum(arr). Veamos cómo funciona en un array 2D.

Agregue el siguiente código a su archivo 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)

Guarde el archivo y ejecútelo.

python ufunc_examples.py

La salida ahora incluirá los resultados de este paso.

... (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 puede ver, .reduce() colapsó el array a lo largo del eje especificado aplicando la operación add a sus elementos.

Especificando Tipos de Datos de Salida

NumPy generalmente determina el tipo de dato del array de salida automáticamente. Sin embargo, puede especificar explícitamente el tipo de dato de salida usando el argumento dtype. Esto es útil para controlar el uso de memoria o asegurar la precisión numérica.

Realicemos una reducción usando multiplicación y forzando que la salida sea un número de punto flotante, aunque la entrada sea un array de enteros.

Agregue el siguiente código al 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)

Guarde y ejecute el script.

python ufunc_examples.py

Observe la salida del Paso 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 los puntos finales (.) en el array de salida [ 6. 120. 504.]. Esto indica que los elementos son ahora números de punto flotante, como especificamos con dtype=float.

Anulando el Comportamiento de Ufunc

El sistema de ufuncs de NumPy es extensible. Puede crear sus propios objetos similares a arrays que definen cómo las ufuncs deben operar sobre ellos. Esta es una característica avanzada que generalmente se realiza subclasificando ndarray de NumPy y sobrescribiendo métodos especiales como __add__ (para el operador +).

Creemos una clase de array personalizada simple que imprima un mensaje cada vez que se realice una suma sobre ella.

Agregue este bloque de código final 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)

Guarde el archivo y ejecútelo una última vez.

python ufunc_examples.py

Verifique la salida final.

... (previous output) ...

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

Puede ver que nuestro mensaje personalizado se imprimió antes del resultado de la suma, lo que confirma que se llamó a nuestro método __add__. Esto demuestra la poderosa flexibilidad del sistema de ufuncs.

Resumen

En este laboratorio, ha aprendido los aspectos esenciales de las Funciones Universales (ufuncs) de NumPy. Comenzamos con la aritmética básica elemento a elemento, que forma la base de la computación vectorizada. Luego exploró la difusión (broadcasting), una característica clave que permite a NumPy realizar operaciones en arrays de diferentes formas. También cubrimos cómo usar métodos de ufunc como .reduce() para la agregación de datos y cómo controlar el tipo de dato de salida con el argumento dtype. Finalmente, vio un ejemplo avanzado de cómo personalizar el comportamiento de las ufuncs subclasificando np.ndarray. Con estas habilidades, ahora está mejor equipado para escribir código numérico eficiente, legible y potente con NumPy.