本系列是《NumPy 速通》的配套章节。
通用函数(universal function,简称 ufunc)就是对数组逐元素应用数学运算的函数。它们由 C 语言实现,一次性遍历整个数组,比 Python 循环快几十上百倍。本章讲透:一元/二元 ufunc 有哪些、nan 陷阱、out= 省内存、以及"向量化思维"。
常用一元 ufunc(一个数组 → 一个数组)
import numpy as np
arr = np.array([-1, 0, 1, 2, 4])
np.abs(arr) # [1 0 1 2 4] 绝对值
np.sqrt(arr) # [nan 0. 1. 1.414 2.] 开平方(负数→nan)
np.square(arr) # [1 0 1 4 16] 平方
np.exp(arr) # 指数 e^x
np.log(arr) # 自然对数(0→-inf,负数→nan)
np.log2(arr) # 以 2 为底
np.log10(arr) # 以 10 为底
np.sin(arr) # 三角:sin / cos / tan
np.arcsin(arr) # 反三角
np.ceil(arr) # 向上取整
np.floor(arr) # 向下取整
np.rint(arr) # 四舍五入
np.sign(arr) # 符号:-1 / 0 / 1
np.isfinite(arr) # 是否有限(识别 inf/nan)
np.round(arr, 2) # 四舍五入到 2 位小数
常用二元 ufunc(两个数组 → 一个数组)
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.add(a, b) # [5 7 9] 加法(等价 a + b)
np.subtract(a, b) # [-3 -3 -3] 减法
np.multiply(a, b) # [4 10 18] 乘法
np.divide(a, b) # [0.25 0.4 0.5] 除法
np.power(a, b) # [1 32 729] 乘方
np.maximum(a, b) # [4 5 6] 逐元素取较大
np.minimum(a, b) # [1 2 3] 逐元素取较小
np.mod(a, b) # 取余
np.hypot(a, b) # sqrt(a²+b²) 勾股
💡 很多符号运算(
+、*、>)底层就是在调用这些 ufunc(np.add、np.multiply、np.greater)——运算符只是语法糖。
⚠️ 灵魂警告:不合法输入返回 nan/inf,不抛异常!
arr = np.array([-1, 0, 4])
np.sqrt(arr) # [nan 0. 2.] 负数的平方根 → nan(只给 RuntimeWarning)
np.log(arr) # [nan -inf 1.386] 0 → -inf,负数 → nan
这些 nan / inf 会污染后续所有计算:
np.mean([1.0, 2.0, np.nan]) # nan → 均值也变 nan!
对策:做完 ufunc 后检查 + 处理:
result = np.log(arr)
np.isnan(result).any() # 有 nan 吗?
np.isinf(result).any() # 有 inf 吗?
# 过滤掉非法值
clean = result[np.isfinite(result)] # 布尔索引(第 9 章)
out= 参数:省内存的就地写法
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.add(a, b, out=a) # 把 a+b 写进 a,不新建数组
a # [5 7 9]
大数据重复运算时用 out= 省掉临时数组分配。
为什么这么快?(向量化思维)
ufunc 直接在 C 层面遍历连续内存,没有 Python 循环的解释开销。实测对比:
import time
arr = np.random.default_rng().random(1_000_000)
# ufunc 版本
t0 = time.time()
np.sqrt(arr)
print(f"ufunc: {time.time()-t0:.5f}s") # 毫秒级
# Python 循环版本
t0 = time.time()
[float(x) ** 0.5 for x in arr]
print(f"Python 循环: {time.time()-t0:.5f}s") # 慢 50~100 倍
能用 ufunc 就绝不用 Python 循环,这是写高效 NumPy 代码的第一原则。
常见"以为要循环,其实有现成 ufunc"的场景
| 想做的事 | 别用 | 该用 |
|---|---|---|
| 每个数平方 | [x**2 for x in arr] |
np.square(arr) |
| 每个数取对数 | 循环 + math.log |
np.log(arr) |
| 两列逐元素取大 | 循环 | np.maximum(c1, c2) |
| 整列标准化 | 循环 | (arr - arr.mean()) / arr.std() |
| 逐元素条件取值 | 循环 + if | np.where(mask, x, y)(第 9 章) |
小结
- ufunc = 逐元素运算,C 实现,比 Python 循环快 50~100 倍
- 一元:
np.abs / sqrt / exp / log / sin / ceil / floor… - 二元:
np.add / subtract / multiply / divide / power / maximum… - ⚠️ 非法输入返回
nan/inf不报错,会污染后续计算——做完检查np.isnan - 向量化思维:能用 ufunc 就不写循环;分支逻辑用
np.where
下一章:把整组数"算总账"(统计与聚合)。
上一篇:第 5 章 形状操作 | 下一篇:第 7 章 统计与聚合