Универсальные функции (ufunc)
Универсальная функция (ufunc) — это функция NumPy, которая применяется к массивам поэлементно, поддерживает broadcasting и работает на C-скорости. Примеры: np.add, np.sqrt, np.sin, np.maximum. Когда вы пишете a + b для двух массивов, под капотом вызывается np.add — это и есть ufunc.
Все ufunc делятся на унарные (один аргумент: np.sqrt, np.exp) и бинарные (два аргумента: np.add, np.multiply). У каждой бинарной ufunc есть полезные методы: reduce, accumulate, outer, reduceat.
Зачем нужно
Скорость. Цикл
for i in range(...)в Python в 50–100 раз медленнее, чем ufunc на тех же данных.Краткость.
np.sin(angles)понятнее, чем цикл сmath.sin.Broadcasting. ufunc автоматически согласует формы массивов разной размерности.
Универсальный интерфейс. Любая ufunc работает с любым
dtypeи любой формой.
Пример 1. Поэлементная арифметика
import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
print(np.add(a, b)) # то же, что a + b
print(np.multiply(a, b)) # то же, что a * b
print(np.power(a, 2)) # квадрат каждого
[11 22 33 44]
[ 10 40 90 160]
[ 1 4 9 16]
Пример 2. Математические функции
import numpy as np
angles_deg = np.array([0, 30, 45, 60, 90])
angles_rad = np.deg2rad(angles_deg)
print(np.round(np.sin(angles_rad), 3))
print(np.round(np.cos(angles_rad), 3))
print(np.round(np.exp(np.array([0, 1, 2])), 3))
[0. 0.5 0.707 0.866 1. ]
[1. 0.866 0.707 0.5 0. ]
[1. 2.718 7.389]
Пример 3. Broadcasting
ufunc автоматически «растягивают» массивы меньшей размерности.
import numpy as np
# Матрица 3x4 — например, 3 датчика по 4 измерения
data = np.array([
[20, 21, 19, 22],
[25, 26, 24, 27],
[30, 31, 29, 32],
])
offsets = np.array([0.5, -0.3, 0.1, 0.0]) # калибровка по столбцам
calibrated = data + offsets # offsets «растягивается» на каждую строку
print(calibrated)
[[20.5 20.7 19.1 22. ]
[25.5 25.7 24.1 27. ]
[30.5 30.7 29.1 32. ]]
Пример 4. Метод reduce
ufunc.reduce сворачивает массив по оси, последовательно применяя ufunc.
import numpy as np
a = np.array([1, 2, 3, 4, 5])
print(np.add.reduce(a)) # сумма = ((((1+2)+3)+4)+5)
print(np.multiply.reduce(a)) # факториал 5!
print(np.maximum.reduce(a)) # максимум
15
120
5
Для матриц можно указать ось:
import numpy as np
m = np.array([[1, 2, 3], [4, 5, 6]])
print(np.add.reduce(m, axis=0)) # суммы по столбцам
print(np.add.reduce(m, axis=1)) # суммы по строкам
[5 7 9]
[ 6 15]
Пример 5. Метод accumulate
accumulate — как reduce, но сохраняет промежуточные результаты. Удобно для скользящих сумм.
import numpy as np
pulses = np.array([1, 0, 1, 1, 0, 1, 1, 1])
total = np.add.accumulate(pulses)
print(total) # счётчик импульсов
[1 1 2 3 3 4 5 6]
Эквивалент функции np.cumsum, но через метод ufunc это можно делать с любой бинарной операцией — например, np.maximum.accumulate для бегущего максимума.
Пример 6. Поэлементные сравнения и логика
Операторы сравнения тоже ufunc.
import numpy as np
temps = np.array([18.0, 22.5, 25.1, 30.2, 19.7])
hot = np.greater(temps, 24) # то же, что temps > 24
safe = np.less_equal(temps, 30) # то же, что temps <= 30
print(hot)
print(safe)
print(np.logical_and(hot, safe))
[False False True True False]
[ True True True False True]
[False False True False False]
Пример 7. where — условие в виде ufunc
np.where(cond, a, b) возвращает a, где cond истинно, и b иначе — поэлементно.
import numpy as np
raw = np.array([-3.0, 1.5, -0.1, 4.2, -5.0])
# ограничим отрицательные нулём (как ReLU)
clipped = np.where(raw < 0, 0, raw)
print(clipped)
[0. 1.5 0. 4.2 0. ]
Пример 8. Своя ufunc через np.frompyfunc
Если нужной операции нет в NumPy, можно «обернуть» Python-функцию. Скорость будет ниже встроенных ufunc, но синтаксис тот же.
import numpy as np
def adc_to_voltage(adc):
# 10-битный АЦП Arduino, 5В опорное
return adc * (5.0 / 1023.0)
uf = np.frompyfunc(adc_to_voltage, 1, 1)
readings = np.array([0, 256, 512, 768, 1023])
volts = uf(readings).astype(float)
print(np.round(volts, 3))
[0. 1.251 2.503 3.754 5. ]
Для производительной собственной ufunc лучше использовать numba или cython.
Подводные камни
dtype overflow.
np.array([200], dtype=np.uint8) + 100даёт[44]из-за переполнения. NumPy не предупреждает — следите за типами.Несовместимые формы. Broadcasting работает только если размерности либо равны, либо одна из них равна 1. Иначе —
ValueError.Деление на ноль.
np.divide(1, 0)вернётinfи предупреждение, а не исключение. Контролируйте черезnp.errstate.frompyfunc возвращает object. Результат имеет
dtype=object— почти всегда нужно явно привести к float/int.reduce на пустом массиве. Без
initialупадёт с ошибкой для большинства ufunc.
См. также
Векторизация в NumPy — почему ufunc быстрее циклов
Создание массивов NumPy — создание входных массивов
Интерполяция в NumPy — интерполяция данных
Примечание
Лицензия и источники
Техническое описание адаптировано из официальной документации NumPy (https://numpy.org/doc/stable/), доступной под BSD-3-Clause License. Перевод на русский, примеры и пояснения — © AlashEd Wiki.