Las funciones universales (ufunc) son funciones especiales de NumPy que operan en ndarrays de forma elemento por elemento.
Representan una vasta gama de funciones vectorizadas que tienen un rendimiento mucho mejor que las implementaciones iterativas y te permiten escribir código conciso. La mayoría de las ufuncs logran esto proporcionando un wrapper de Python alrededor de una implementación en C.
En este artículo, aprenderemos sobre algunas de estas funciones y un tema relacionado llamado programación orientada a arrays.
¡Empecemos!
Se trata de velocidad y claridad
Antes de explorar algunas de las funciones que probablemente uses más a menudo, hagamos una comparación rápida entre una función universal y una implementación iterativa equivalente.
Realizaremos la operación cuadrado en cada entrada de un array numpy usando tanto una función universal como una implementación iterativa. Mediremos el tiempo usando la función %timeit en un notebook de Jupyter.
import numpy as np
arr = np.arange(100000000)
%%time
squares_1 = np.square(arr)
CPU times: user 129 ms, sys: 200 ms, total: 329 ms
Wall time: 328 ms
%%time
squares_2 = np.empty(len(arr), dtype=np.int64)
for i in range(len(arr)):
squares_2[i] = arr[i] ** 2
CPU times: user 32.7 s, sys: 133 ms, total: 32.8 s
Wall time: 32.8 s
¡Wow, eso es aproximadamente 100 veces más rápido para el caso de la función universal! No solo eso, sino que el código también es mucho más directo y fácil de entender.
Bien, ahora que sabemos que son cosas realmente geniales, exploremos algunas funciones universales:
# Usaremos los siguientes arrays para los ejemplos
even = np.arange(0,20,2)
odd = np.arange(1,20,2)
print(even)
print(odd)
[ 0 2 4 6 8 10 12 14 16 18]
[ 1 3 5 7 9 11 13 15 17 19]
# add realiza suma elemento por elemento
result = np.add(even, odd)
print(result)
[ 1 5 9 13 17 21 25 29 33 37]
# subtract realiza resta elemento por elemento
result = np.subtract(even, odd)
print(result)
[-1 -1 -1 -1 -1 -1 -1 -1 -1 -1]
# multiply realiza multiplicación elemento por elemento
result = np.multiply(odd, even)
print(result)
[ 0 6 20 42 72 110 156 210 272 342]
# divide realiza división elemento por elemento (segundo_argumento / primer_argumento)
result = np.divide(even, odd)
print(result)
[0. 0.66666667 0.8 0.85714286 0.88888889 0.90909091
0.92307692 0.93333333 0.94117647 0.94736842]
# maximum devuelve un máximo elemento por elemento
result = np.maximum(even, odd)
print(result)
[ 1 3 5 7 9 11 13 15 17 19]
# minimum realiza un mínimo elemento por elemento
result = np.minimum(even, odd)
print(result)
[ 0 2 4 6 8 10 12 14 16 18]
# greater devuelve true si los valores en el primer argumento son mayores que los del segundo
# también hay un greater_equal que realiza esta operación como >=
result = np.greater(even, odd)
print(result)
[False False False False False False False False False False]
# less devuelve true si los valores en el primer argumento son menores que los del segundo
# también hay un lessequal que realiza esta operación como <=
result = np.less(even, odd)
print(result)
[ True True True True True True True True True True]
# sin calcula el seno trigonométrico elemento por elemento
result = np.sin(even)
print(result)
[ 0. 0.90929743 -0.7568025 -0.2794155 0.98935825 -0.54402111
-0.53657292 0.99060736 -0.28790332 -0.75098725]
# sqrt calcula la raíz cuadrada no negativa de un array elemento por elemento
result = np.sqrt(even)
print(result)
[0. 1.41421356 2. 2.44948974 2.82842712 3.16227766
3.46410162 3.74165739 4. 4.24264069]
# cbrt calcula la raíz cúbica de un array elemento por elemento
result = np.cbrt(even)
print(result)
[0. 1.25992105 1.58740105 1.81712059 2. 2.15443469
2.28942849 2.41014226 2.5198421 2.62074139]
# log calcula el logaritmo natural elemento por elemento
result = np.log(odd)
print(result)
[0. 1.09861229 1.60943791 1.94591015 2.19722458 2.39789527
2.56494936 2.7080502 2.83321334 2.94443898]
# log2 calcula el logaritmo base 2 elemento por elemento
result = np.log2(odd)
print(result)
[0. 1.5849625 2.32192809 2.80735492 3.169925 3.45943162
3.70043972 3.9068906 4.08746284 4.24792751]
Programación orientada a arrays
La programación orientada a arrays es la práctica de reemplazar bucles por operaciones vectorizadas. NumPy tiene por defecto una enorme cantidad de funciones que puedes usar para expresar soluciones sin tener que escribir bucles. Esto te permite resolver problemas usando una sintaxis intuitiva que otros programadores y científicos tendrán mucho más fácil entender.
Como ejemplo, imagina el siguiente problema: Tienes dos matrices, cada una representando un lado de una colección de 9 triángulos rectángulos. Te piden calcular una tercera matriz donde cada entrada es el valor de la hipotenusa de esos 9 triángulos.
sides_a = np.random.randint(low=1, high=10, size=9).reshape(3,3)
sides_b = np.random.randint(low=1, high=10, size=9).reshape(3,3)
print(sides_a)
print(sides_b)
[[5 6 9]
[8 1 4]
[2 8 6]]
[[5 9 6]
[5 2 8]
[4 6 9]]
# podemos expresar la solución en una sola línea usando sintaxis intuitiva
hypotenuse = np.sqrt(sides_a**2 + sides_b**2)
print(hypotenuse)
[[ 7.07106781 10.81665383 10.81665383]
[ 9.43398113 2.23606798 8.94427191]
[ 4.47213595 10. 10.81665383]]
Fácil, ¿verdad?
Con algo de práctica, podrás crear código increíblemente intuitivo para resolver problemas numéricos usando NumPy. ¡No solo es más eficiente que las soluciones basadas en bucles, sino que también es mucho más fácil de leer y entender!
Eso no es todo
Exploramos solo un subconjunto muy pequeño de la funcionalidad que puedes obtener de las ufuncs, si quieres saber qué otras cosas puedes hacer usando ellas revisa la documentación oficial.
Ahora estás entrando al reino del NumPy avanzado, y el tipo de problemas que puedes resolver ahora es mucho mayor. En la próxima semana, echaremos un vistazo a las reducciones (también conocidas como agregaciones), un conjunto de funciones muy útiles para análisis estadístico.
¡Gracias por leer!
Qué hacer después
- Comparte este artículo con amigos y colegas. Gracias por ayudarme a llegar a personas que podrían encontrar esta información útil.
- Puedes encontrar el código fuente para esta serie en este repo.
- Este artículo está basado en el libro: Python for Data Analysis.
- Envíame un email con preguntas, comentarios o sugerencias (está en la página Autor)