UFuncs and Vectorization¶
Universal Functions¶
1. Definition¶
UFuncs are vectorized wrappers around C-implemented operations:
import numpy as np
x = np.array([0, np.pi/2, np.pi])
y = np.sin(x) # UFuncs operates element-wise
print(y) # [0. 1. 0.]
Characteristics: - Implemented in C for speed - Operate element-wise - Support broadcasting - Type-flexible with automatic promotion
2. Performance¶
import time
# Python loop (slow)
x_list = [i * 0.001 for i in range(1000000)]
start = time.time()
y_list = [np.sin(xi) for xi in x_list]
print(f"List: {time.time() - start:.3f}s")
# NumPy ufunc (fast)
x_array = np.linspace(0, 1000, 1000000)
start = time.time()
y_array = np.sin(x_array)
print(f"UFuncs: {time.time() - start:.3f}s")
UFuncs are typically 100x faster than Python loops.
3. Type Detection¶
print(isinstance(np.sin, np.ufunc)) # True
print(isinstance(np.add, np.ufunc)) # True
Common UFuncs¶
1. Arithmetic¶
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.add(a, b)) # [5 7 9]
print(np.subtract(a, b)) # [-3 -3 -3]
print(np.multiply(a, b)) # [4 10 18]
print(np.divide(a, b)) # [0.25 0.4 0.5]
print(np.power(a, 2)) # [1 4 9]
2. Trigonometric¶
x = np.array([0, np.pi/4, np.pi/2])
print(np.sin(x)) # [0. 0.707 1.]
print(np.cos(x)) # [1. 0.707 0.]
print(np.tan(x)) # [0. 1. inf]
print(np.arctan2(1, 1)) # 0.785 (π/4)
3. Exponential and Logarithmic¶
x = np.array([1, 2, 3])
print(np.exp(x)) # [2.718 7.389 20.086]
print(np.log(x)) # [0. 0.693 1.099]
print(np.log10(x)) # [0. 0.301 0.477]
print(np.sqrt(x)) # [1. 1.414 1.732]
Broadcasting with UFuncs¶
1. Scalar and Array¶
arr = np.array([1, 2, 3])
result = arr + 10 # Broadcasts scalar
print(result) # [11 12 13]
2. Array and Array¶
arr1 = np.array([[1], [2], [3]]) # (3, 1)
arr2 = np.array([10, 20, 30]) # (3,)
result = arr1 + arr2 # (3, 3)
# [[11 21 31]
# [12 22 32]
# [13 23 33]]
3. Multi-dimensional¶
a = np.ones((3, 1, 4))
b = np.ones((1, 2, 4))
c = a + b # Broadcasts to (3, 2, 4)
print(c.shape) # (3, 2, 4)
Advanced UFuncs Features¶
1. Output Arrays¶
x = np.array([1.0, 2.0, 3.0])
out = np.empty_like(x)
np.sin(x, out=out) # Writes result to out, avoids allocation
print(out)
2. Reduction Methods¶
arr = np.array([1, 2, 3, 4, 5])
# UFuncs have reduce methods
print(np.add.reduce(arr)) # 15 (sum)
print(np.multiply.reduce(arr)) # 120 (product)
3. Accumulate¶
arr = np.array([1, 2, 3, 4, 5])
print(np.add.accumulate(arr)) # [1 3 6 10 15] (cumulative sum)
Comparison UFuncs¶
1. Element-wise Comparison¶
a = np.array([1, 2, 3])
b = np.array([1, 3, 2])
print(np.greater(a, b)) # [False False True]
print(np.less_equal(a, b)) # [True True False]
print(np.equal(a, b)) # [True False False]
print(np.not_equal(a, b)) # [False True True]
2. Maximum and Minimum¶
a = np.array([1, 5, 3])
b = np.array([4, 2, 6])
print(np.maximum(a, b)) # [4 5 6] element-wise max
print(np.minimum(a, b)) # [1 2 3] element-wise min
3. Logical Operations¶
a = np.array([True, False, True])
b = np.array([True, True, False])
print(np.logical_and(a, b)) # [True False False]
print(np.logical_or(a, b)) # [True True True]
print(np.logical_not(a)) # [False True False]
Custom UFuncs¶
1. Using frompyfunc¶
def custom_func(x, y):
return x**2 + y**2
ufunc = np.frompyfunc(custom_func, 2, 1) # 2 inputs, 1 output
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
result = ufunc(a, b)
print(result) # [17 29 45]
2. Numba Vectorize¶
from numba import vectorize
@vectorize
def fast_func(x, y):
return x**2 + y**2
result = fast_func(np.array([1, 2, 3]), np.array([4, 5, 6]))
3. Performance Comparison¶
Custom UFuncs via Numba can match C-level performance while being pure Python.