NumPy 数学函数
本章将详细介绍 NumPy 中的数学函数,包括通用函数(ufunc)、三角函数、指数对数函数、统计函数等。这些函数是数值计算的基础。
通用函数(ufunc)
通用函数(Universal Functions,简称 ufunc)是 NumPy 的核心概念之一,它是对数组中每个元素进行操作的函数。理解 ufunc 对于编写高效的 NumPy 代码至关重要。
什么是 ufunc?
ufunc 是一种对 ndarray 进行元素级操作的函数。它的核心特性是:
- 向量化:自动对数组的每个元素应用相同的操作,无需显式循环
- 广播支持:自动处理不同形状数组之间的运算
- 类型转换:自动处理不同数据类型之间的转换
- 高效实现:底层用 C 实现,利用 CPU 向量化指令(如 SSE、AVX)
import numpy as np
arr = np.array([1, 2, 3, 4])
# np.sin 是一个 ufunc,对每个元素分别计算正弦
result = np.sin(arr)
print(f"sin([1,2,3,4]): {result}")
# 加法也是 ufunc(np.add)
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(f"[1,2,3] + [4,5,6] = {a + b}")
# 查看 ufunc 信息
print(f"\nnp.add 的类型: {type(np.add)}")
print(f"输入数量: {np.add.nin}") # 2 个输入
print(f"输出数量: {np.add.nout}") # 1 个输出
print(f"数据类型: {np.add.types[:5]}") # 支持的数据类型
为什么 ufunc 比循环快?
ufunc 的性能优势来自多个方面:
import numpy as np
import time
# 创建大数组
arr = np.random.rand(1000000)
# 方法1:使用 Python 循环
start = time.time()
result_loop = [np.sin(x) for x in arr]
time_loop = time.time() - start
# 方法2:使用 ufunc
start = time.time()
result_ufunc = np.sin(arr)
time_ufunc = time.time() - start
print(f"Python 循环: {time_loop:.4f}s")
print(f"NumPy ufunc: {time_ufunc:.4f}s")
print(f"性能提升: {time_loop/time_ufunc:.1f}x")
性能优势的原因:
- 向量化指令:底层 C 代码使用 CPU 的 SIMD 指令(如 AVX),一次处理多个数据
- 内存连续访问:数组元素连续存储,CPU 缓存命中率更高
- 无 Python 解释器开销:循环在 C 层面执行,避免了 Python 循环的开销
- 编译器优化:NumPy 编译时针对不同 CPU 进行了优化
ufunc 的广播机制
ufunc 自动支持广播,可以处理不同形状的数组:
import numpy as np
# 标量与数组
a = np.array([1, 2, 3])
print(f"数组 + 标量: {a + 10}") # 标量广播到每个元素
# 不同形状的数组
b = np.array([[1, 2, 3], [4, 5, 6]]) # shape: (2, 3)
c = np.array([10, 20, 30]) # shape: (3,)
print(f"\n广播运算:\n{b + c}")
# 广播规则:从右向左比较形状,每个维度要么相等,要么其中一个为 1
ufunc 的方法
ufunc 对象提供了几个强大的方法,可以对数组进行更复杂的操作:
reduce 方法
reduce 将数组沿指定轴进行累积运算,最终得到一个标量(或降维后的数组):
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
# reduce: 累积操作,相当于 ((1+2)+3)+4+5
print(f"add.reduce: {np.add.reduce(arr)}") # 15 (求和)
# 其他运算也可以 reduce
print(f"multiply.reduce: {np.multiply.reduce(arr)}") # 120 (求积)
# 二维数组沿轴 reduce
arr_2d = np.array([[1, 2, 3], [4, 5, 6]])
print(f"\n沿 axis=0 reduce: {np.add.reduce(arr_2d, axis=0)}") # [5, 7, 9]
print(f"沿 axis=1 reduce: {np.add.reduce(arr_2d, axis=1)}") # [6, 15]
reduce 的本质:对于二元运算 ,reduce([a, b, c, d]) 计算 。
accumulate 方法
accumulate 返回每一步累积的结果,保留中间状态:
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
# accumulate: 返回累积过程的每一步
acc = np.add.accumulate(arr)
print(f"add.accumulate: {acc}") # [1, 3, 6, 10, 15]
# 解释: 1, 1+2=3, 1+2+3=6, 1+2+3+4=10, 1+2+3+4+5=15
# 累积乘积
acc_mul = np.multiply.accumulate(arr)
print(f"multiply.accumulate: {acc_mul}") # [1, 2, 6, 24, 120]
# 二维数组
arr_2d = np.array([[1, 2, 3], [4, 5, 6]])
print(f"\n沿 axis=0 accumulate:\n{np.add.accumulate(arr_2d, axis=0)}")
应用场景:累积和常用于计算运行总计、累积收益等。
outer 方法
outer 计算两个数组所有元素对的运算结果,生成一个二维数组:
import numpy as np
a = np.array([1, 2, 3])
b = np.array([10, 20])
# outer: 计算所有元素对的组合
outer_result = np.add.outer(a, b)
print(f"add.outer:\n{outer_result}")
# [[11, 21],
# [12, 22],
# [13, 23]]
# 等价于: a[:, None] + b[None, :]
# 更常见的用法:乘法外积
print(f"\nmultiply.outer:\n{np.multiply.outer(a, b)}")
# [[10, 20],
# [20, 40],
# [30, 60]]
# 生成乘法表
nums = np.arange(1, 10)
mult_table = np.multiply.outer(nums, nums)
print(f"\n乘法表 (部分):\n{mult_table[:5, :5]}")
outer 的本质:outer(a, b)[i, j] = a[i] ⊗ b[j],生成两个数组的笛卡尔积运算结果。
reduceat 方法
reduceat 在指定区间内进行 reduce 操作:
import numpy as np
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# reduceat: 在指定区间内进行 reduce
# indices=[0, 3, 7] 表示: [0:3], [3:7], [7:]
result = np.add.reduceat(arr, [0, 3, 7])
print(f"reduceat 结果: {result}")
# [0+1+2=3, 3+4+5+6=18, 7+8+9=24]
# 等价于:
# [arr[0:3].sum(), arr[3:7].sum(), arr[7:].sum()]
at 方法
at 方法用于在指定索引位置执行原地操作:
import numpy as np
arr = np.zeros(5)
indices = np.array([0, 1, 1, 3])
# 在指定索引位置执行操作
np.add.at(arr, indices, 1)
print(f"at 操作后: {arr}") # [1, 2, 0, 1, 0]
# 索引 0 加了 1 次,索引 1 加了 2 次,索引 3 加了 1 次
# 应用:统计频次
values = np.array([1, 2, 1, 3, 2, 1, 1, 3])
counts = np.zeros(4)
np.add.at(counts, values, 1)
print(f"频次统计: {counts}") # [0, 4, 2, 2] - 值 1 出现 4 次,值 2 出现 2 次...
基本数学运算
算术运算
import numpy as np
a = np.array([4, 9, 16])
b = np.array([2, 3, 4])
# 加法
print(f"add: {np.add(a, b)}") # [6, 12, 20]
print(f"a + b: {a + b}") # [6, 12, 20]
# 减法
print(f"subtract: {np.subtract(a, b)}") # [2, 6, 12]
print(f"a - b: {a - b}") # [2, 6, 12]
# 乘法
print(f"multiply: {np.multiply(a, b)}") # [8, 27, 64]
print(f"a * b: {a * b}") # [8, 27, 64]
# 除法
print(f"divide: {np.divide(a, b)}") # [2. 3. 4.]
print(f"a / b: {a / b}") # [2. 3. 4.]
# 整除
print(f"floor_divide: {np.floor_divide(a, b)}") # [2, 3, 4]
# 取模
print(f"mod: {np.mod(a, b)}") # [0, 0, 0]
# 幂运算
print(f"power: {np.power(a, 2)}") # [16, 81, 256]
print(f"a ** 2: {a ** 2}") # [16, 81, 256]
print(f"sqrt: {np.sqrt(a)}") # [2. 3. 4.]
求余数和divmod
import numpy as np
a = np.array([10, 20, 30])
b = np.array([3, 4, 5])
# 求余数
print(f"mod: {np.mod(a, b)}") # [1, 0, 0]
print(f"remainder: {np.remainder(a, b)}") # [1, 0, 0]
# divmod: 同时得到商和余数
quotient, remainder = np.divmod(a, b)
print(f"divmod: 商={quotient}, 余数={remainder}")
# 商=[3, 5, 6], 余数=[1, 0, 0]
# fmod: 考虑符号的取模
a2 = np.array([-5, 5])
b2 = np.array([3, 3])
print(f"fmod: {np.fmod(a2, b2)}") # [-2, 2]
print(f"mod: {np.mod(a2, b2)}") # [1, 2]
倒数和相反数
import numpy as np
arr = np.array([1, 2, 4, 0.5])
# 倒数
print(f"reciprocal: {np.reciprocal(arr)}") # [1. 0.5 0.25 2.]
# 相反数
print(f"negative: {np.negative(arr)}") # [-1. -2. -4. -0.5]
# 绝对值
arr2 = np.array([-1, -2, 3])
print(f"abs: {np.abs(arr2)}") # [1, 2, 3]
print(f"absolute: {np.absolute(arr2)}") # [1, 2, 3]
# 复数的模
arr_complex = np.array([3+4j, 5+12j])
print(f"abs (复数): {np.abs(arr_complex)}") # [5. 13.]
三角函数
NumPy 提供了完整的三角函数,所有函数都使用弧度制:
import numpy as np
# 角度转弧度
angles = np.array([0, 30, 45, 60, 90])
radians = np.deg2rad(angles)
print(f"角度转弧度: {radians}")
# [0. 0.52359878 0.78539816 1.57079633 1.57079633]
# 弧度转角度
print(f"弧度转角度: {np.rad2deg(radians)}")
# 基本三角函数
x = np.array([0, np.pi/2, np.pi, 3*np.pi/2])
print(f"sin: {np.sin(x)}") # [ 0. 1. 0. -1.]
print(f"cos: {np.cos(x)}") # [ 1. 0. -1. 0.]
print(f"tan: {np.tan(x)}") # [ 0. inf 0. nan]
# 反三角函数
arr = np.array([0, 0.5, 1])
print(f"arcsin: {np.arcsin(arr)}") # [0. 0.52359878 1.57079633]
print(f"arccos: {np.arccos(arr)}") # [1.57079633 1.04719755 0. ]
print(f"arctan: {np.arctan(arr)}") # [0. 0.46364761 0.78539816]
三角函数实战
import numpy as np
import matplotlib.pyplot as plt
# 生成周期信号
t = np.linspace(0, 4*np.pi, 1000)
# 正弦波
sin_wave = np.sin(t)
print(f"正弦波范围: [{sin_wave.min():.2f}, {sin_wave.max():.2f}]")
# 余弦波
cos_wave = np.cos(t)
# 复合信号
composite = sin_wave + 0.5 * cos_wave
print("信号生成完成")
双曲函数
import numpy as np
x = np.array([0, 1, 2])
# 双曲正弦
print(f"sinh: {np.sinh(x)}") # [0. 1.17520119 3.6268604]
# 双曲余弦
print(f"cosh: {np.cosh(x)}") # [1. 1.54308063 3.76219569]
# 双曲正切
print(f"tanh: {np.tanh(x)}") # [0. 0.76159416 0.96402758]
# 反双曲函数
y = np.array([0.5, 0.9, 1.0])
print(f"arcsinh: {np.arcsinh(y)}")
print(f"arctanh: {np.arctanh(y)}")
指数和对数函数
import numpy as np
arr = np.array([1, 2, 3])
# 指数函数
print(f"exp: {np.exp(arr)}") # [ 2.71828183 7.3890561 20.0855369]
print(f"exp2: {np.exp2(arr)}") # [2. 4. 8.]
print(f"expm1: {np.expm1(arr)}") # [1.71828183 6.3890561 19.0855369]
# expm1(x) = exp(x) - 1,对小数值更精确
# 对数函数
print(f"log: {np.log(arr)}") # [0. 0.69314718 1.09861229]
print(f"log2: {np.log2(arr)}") # [0. 1. 1.5849625 ]
print(f"log10: {np.log10(arr)}") # [0. 0.30103 0.47712125]
print(f"log1p: {np.log1p(arr)}") # [0.69314718 1.09861229 1.38629436]
# log1p(x) = log(1 + x),对小数值更精确
实战:数值稳定性
import numpy as np
# 问题:对于很小的值,直接计算可能不准确
x = 1e-10
print(f"exp(x) - 1: {np.exp(x) - 1}") # 可能丢失精度
print(f"expm1(x): {np.expm1(x)}") # 更精确
# log(1 + x) 的问题
x = 1e-10
print(f"log(1 + x): {np.log(1 + x)}") # 可能丢失精度
print(f"log1p(x): {np.log1p(x)}") # 更精确
统计函数
求和与累积
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
# 求和
print(f"sum: {np.sum(arr)}") # 21
print(f"sum axis=0: {np.sum(arr, axis=0)}") # [5 7 9]
print(f"sum axis=1: {np.sum(arr, axis=1)}") # [ 6 15]
# 累积和
print(f"cumsum: {np.cumsum(arr)}")
# [1 3 6 10 15 21]
print(f"cumsum axis=1:\n{np.cumsum(arr, axis=1)}")
# [[ 1 3 6]
# [ 4 9 15]]
# 乘积
print(f"prod: {np.prod(arr)}") # 720
print(f"cumprod: {np.cumprod(arr)}") # [ 1 2 6 24 120 720]
均值和标准差
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
# 均值
print(f"mean: {np.mean(arr)}") # 3.0
# 加权平均
weights = np.array([1, 2, 3, 2, 1])
print(f"average: {np.average(arr, weights=weights)}") # 3.0
# 中位数
arr2 = np.array([1, 3, 5, 7, 9])
print(f"median: {np.median(arr2)}") # 5.0
# 标准差和方差
print(f"std: {np.std(arr)}") # 1.41421356
print(f"var: {np.var(arr)}") # 2.0
# ddof 参数:自由度调整
# ddof=0: 总体标准差(除以 N)
# ddof=1: 样本标准差(除以 N-1)
arr3 = np.array([2, 4, 4, 4, 5, 5, 7, 9])
print(f"std (ddof=0): {np.std(arr3)}") # 2.0
print(f"std (ddof=1): {np.std(arr3, ddof=1)}") # 2.138...
最值函数
import numpy as np
arr = np.array([[3, 1, 5], [2, 8, 4]])
# 最大最小值
print(f"max: {np.max(arr)}") # 8
print(f"min: {np.min(arr)}") # 1
print(f"amax: {np.amax(arr)}") # 8 (别名)
# 沿轴最值
print(f"max axis=0: {np.max(arr, axis=0)}") # [3, 8, 5]
print(f"max axis=1: {np.max(arr, axis=1)}") # [5, 8]
# 返回索引
print(f"argmax: {np.argmax(arr)}") # 4 (展平后的索引)
print(f"argmax axis=0: {np.argmax(arr, axis=0)}") # [0, 1, 0]
# 范围(最大值 - 最小值)
print(f"ptp: {np.ptp(arr)}") # 7
print(f"ptp axis=1: {np.ptp(arr, axis=1)}") # [4, 6]
百分位数
import numpy as np
data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# 百分位数
print(f"percentile 50: {np.percentile(data, 50)}") # 5.5 (中位数)
print(f"percentile 25: {np.percentile(data, 25)}") # 3.25
print(f"percentile 75: {np.percentile(data, 75)}") # 7.75
# 多个百分位数
print(f"percentile [25, 50, 75]: {np.percentile(data, [25, 50, 75])}")
# 分位数(更通用的表达)
print(f"quantile 0.5: {np.quantile(data, 0.5)}") # 5.5
NaN 安全的函数
在实际数据处理中,缺失值(NaN,Not a Number)是常见的问题。理解 NaN 在计算中的行为对于正确处理数据至关重要。
为什么需要 NaN 安全函数?
NaN 的传播机制:在标准算术运算中,任何涉及 NaN 的操作都会返回 NaN。这是一种"安全"的设计——它提醒你数据存在问题。
import numpy as np
# NaN 的传播
print(f"1 + np.nan = {1 + np.nan}") # nan
print(f"np.nan * 0 = {np.nan * 0}") # nan
print(f"np.array([1, np.nan]).sum() = {np.array([1, np.nan]).sum()}") # nan
但问题在于:有时候我们需要忽略 NaN 进行计算。比如计算一组数据的平均值,如果其中包含几个 NaN,我们通常希望用有效数据的均值,而不是得到 NaN。
NaN 安全函数列表
NumPy 为常用统计函数提供了 nan 前缀版本:
import numpy as np
arr = np.array([1, 2, np.nan, 4, 5])
print(f"数组: {arr}")
print(f"有效元素数: {np.count_nonzero(~np.isnan(arr))}")
# 标准函数 vs NaN 安全函数
print(f"\n标准函数:")
print(f" sum: {np.sum(arr)}") # nan
print(f" mean: {np.mean(arr)}") # nan
print(f" std: {np.std(arr)}") # nan
print(f" var: {np.var(arr)}") # nan
print(f" max: {np.max(arr)}") # nan
print(f" min: {np.min(arr)}") # nan
print(f" argmax: {np.argmax(arr)}") # 2 (NaN 的位置!)
print(f" argmin: {np.argmin(arr)}") # 2 (NaN 的位置!)
print(f"\nNaN 安全函数:")
print(f" nansum: {np.nansum(arr)}") # 12.0 (忽略 NaN)
print(f" nanmean: {np.nanmean(arr)}") # 3.0 (12/4,只用有效元素)
print(f" nanstd: {np.nanstd(arr)}") # 基于有效元素的标准差
print(f" nanvar: {np.nanvar(arr)}") # 基于有效元素的方差
print(f" nanmax: {np.nanmax(arr)}") # 5.0
print(f" nanmin: {np.nanmin(arr)}") # 1.0
print(f" nanargmax: {np.nanargmax(arr)}") # 4 (忽略 NaN 后最大值的位置)
print(f" nanargmin: {np.nanargmin(arr)}") # 0 (忽略 NaN 后最小值的位置)
完整的 NaN 安全函数对照表
| 标准函数 | NaN 安全版本 | 说明 |
|---|---|---|
sum | nansum | 求和 |
prod | nanprod | 求积 |
mean | nanmean | 均值 |
median | nanmedian | 中位数 |
std | nanstd | 标准差 |
var | nanvar | 方差 |
max | nanmax | 最大值 |
min | nanmin | 最小值 |
argmax | nanargmax | 最大值索引 |
argmin | nanargmin | 最小值索引 |
percentile | nanpercentile | 百分位数 |
quantile | nanquantile | 分位数 |
cumsum | nancumsum | 累积和 |
cumprod | nancumprod | 累积积 |
实战:处理真实的缺失数据
import numpy as np
# 模拟传感器数据(部分缺失)
np.random.seed(42)
temperature = np.random.randn(24) * 3 + 25 # 24小时温度数据
# 随机设置一些缺失值
missing_idx = [3, 7, 15, 22]
temperature[missing_idx] = np.nan
print(f"温度数据: {temperature}")
# 统计分析
print(f"\n=== 温度统计 ===")
print(f"有效数据点: {np.count_nonzero(~np.isnan(temperature))}/{len(temperature)}")
print(f"平均温度: {np.nanmean(temperature):.2f}°C")
print(f"最高温度: {np.nanmax(temperature):.2f}°C")
print(f"最低温度: {np.nanmin(temperature):.2f}°C")
print(f"温度范围: {np.nanmax(temperature) - np.nanmin(temperature):.2f}°C")
print(f"标准差: {np.nanstd(temperature):.2f}°C")
# 按时段分析
day_temp = temperature[:12]
night_temp = temperature[12:]
print(f"\n白天平均: {np.nanmean(day_temp):.2f}°C")
print(f"夜间平均: {np.nanmean(night_temp):.2f}°C")
缺失值处理策略
除了使用 NaN 安全函数,还有其他处理缺失值的方法:
import numpy as np
data = np.array([1, 2, np.nan, 4, 5, np.nan, 7])
# 策略1: 使用 NaN 安全函数(保留 NaN)
mean1 = np.nanmean(data)
print(f"策略1 - NaN 安全均值: {mean1:.2f}")
# 策略2: 删除缺失值
data_clean = data[~np.isnan(data)]
mean2 = np.mean(data_clean)
print(f"策略2 - 删除 NaN 后均值: {mean2:.2f}")
# 策略3: 填充缺失值(用均值填充)
data_filled = np.where(np.isnan(data), np.nanmean(data), data)
mean3 = np.mean(data_filled)
print(f"策略3 - 均值填充后: {mean3:.2f}")
print(f"填充后数据: {data_filled}")
# 策略4: 插值填充(线性插值)
def linear_interpolate(arr):
"""简单的线性插值填充 NaN"""
result = arr.copy()
nan_mask = np.isnan(arr)
if nan_mask.all() or not nan_mask.any():
return result
valid_idx = np.where(~nan_mask)[0]
nan_idx = np.where(nan_mask)[0]
# 对每个 NaN 位置进行插值
for idx in nan_idx:
left = valid_idx[valid_idx < idx]
right = valid_idx[valid_idx > idx]
if len(left) > 0 and len(right) > 0:
left_idx, right_idx = left[-1], right[0]
# 线性插值
weight = (idx - left_idx) / (right_idx - left_idx)
result[idx] = arr[left_idx] * (1 - weight) + arr[right_idx] * weight
elif len(left) > 0:
result[idx] = arr[left[-1]] # 用最近的左值填充
elif len(right) > 0:
result[idx] = arr[right[0]] # 用最近的右值填充
return result
data_interp = linear_interpolate(data)
print(f"策略4 - 线性插值: {data_interp}")
NaN 安全函数的性能考量
NaN 安全函数需要额外检查每个元素,因此比标准函数稍慢:
import numpy as np
import time
# 创建包含少量 NaN 的大数组
data = np.random.rand(1000000)
data_with_nan = data.copy()
data_with_nan[::1000] = np.nan # 0.1% 的缺失值
# 性能对比
start = time.time()
_ = np.mean(data)
time_normal = time.time() - start
start = time.time()
_ = np.nanmean(data)
time_nan_safe = time.time() - start
start = time.time()
_ = np.nanmean(data_with_nan)
time_nan_data = time.time() - start
print(f"标准 mean(无 NaN): {time_normal*1000:.3f}ms")
print(f"nanmean(无 NaN): {time_nan_safe*1000:.3f}ms")
print(f"nanmean(含 NaN): {time_nan_data*1000:.3f}ms")
建议:如果确定数据中没有 NaN,使用标准函数会稍快。但如果有任何可能的缺失值,使用 NaN 安全函数更安全。
舍入函数
import numpy as np
arr = np.array([1.5, 2.5, 3.7, 4.3, 5.8])
# 四舍五入
print(f"round: {np.round(arr)}") # [2. 2. 4. 4. 6.]
print(f"around: {np.around(arr)}") # [2. 2. 4. 4. 6.]
# 指定小数位数
arr2 = np.array([1.2345, 2.5678, 3.9012])
print(f"round 2 decimals: {np.round(arr2, 2)}") # [1.23 2.57 3.9 ]
# 向上取整
print(f"ceil: {np.ceil(arr)}") # [2. 3. 4. 5. 6.]
# 向下取整
print(f"floor: {np.floor(arr)}") # [1. 2. 3. 4. 5.]
# 截断(向零取整)
arr3 = np.array([-1.5, -0.5, 0.5, 1.5])
print(f"trunc: {np.trunc(arr3)}") # [-1. -0. 0. 1.]
print(f"fix: {np.fix(arr3)}") # [-1. -0. 0. 1.]
数值处理函数
clip - 限制数值范围
clip 函数将数组中的元素限制在指定范围内,超出范围的值会被截断到边界值。这在数据处理中非常有用,比如限制像素值在 0-255 范围内。
import numpy as np
arr = np.array([1, 5, 10, 15, 20, 25])
# 将值限制在 [5, 20] 范围内
clipped = np.clip(arr, 5, 20)
print(f"原数组: {arr}")
print(f"clip(5, 20): {clipped}") # [5, 5, 10, 15, 20, 20]
# 使用数组作为边界
lower = np.array([0, 3, 8, 12, 18, 22])
upper = np.array([2, 7, 12, 18, 22, 27])
clipped_arr = np.clip(arr, lower, upper)
print(f"数组边界: {clipped_arr}")
# 实际应用:图像像素值处理
image_data = np.array([-10, 0, 128, 255, 300])
valid_pixels = np.clip(image_data, 0, 255)
print(f"\n图像像素: {valid_pixels}")
sign - 符号函数
sign 函数返回数组中每个元素的符号:正数返回 1,负数返回 -1,零返回 0。
import numpy as np
arr = np.array([-5, -1, 0, 1, 5])
# 获取符号
signs = np.sign(arr)
print(f"数组: {arr}")
print(f"符号: {signs}") # [-1, -1, 0, 1, 1]
# 应用:分离正负值
data = np.array([-3, 2, -1, 5, -4, 3])
positive = data[np.sign(data) == 1]
negative = data[np.sign(data) == -1]
print(f"\n正值: {positive}")
print(f"负值: {negative}")
# 应用:保留符号,取绝对值后处理
values = np.array([-100, -50, 0, 50, 100])
processed = np.sign(values) * np.sqrt(np.abs(values))
print(f"\n保留符号的平方根: {processed}")
diff - 差分计算
diff 函数计算数组中相邻元素的差值,常用于计算变化率、速度等。
import numpy as np
arr = np.array([1, 3, 6, 10, 15])
# 一阶差分(后一个元素减前一个元素)
diff1 = np.diff(arr)
print(f"原数组: {arr}")
print(f"一阶差分: {diff1}") # [2, 3, 4, 5]
# 二阶差分(差分的差分)
diff2 = np.diff(arr, n=2)
print(f"二阶差分: {diff2}") # [1, 1, 1]
# 多维数组差分
arr_2d = np.array([[1, 3, 6],
[2, 5, 9]])
diff_axis0 = np.diff(arr_2d, axis=0) # 沿行方向
diff_axis1 = np.diff(arr_2d, axis=1) # 沿列方向
print(f"\n二维数组差分:")
print(f"axis=0 (行): {diff_axis0}")
print(f"axis=1 (列): {diff_axis1}")
# 应用:计算离散导数
t = np.array([0, 1, 2, 3, 4])
x = t ** 2 # 位置 x = t^2
velocity = np.diff(x) / np.diff(t) # 速度 = dx/dt
print(f"\n位置: {x}")
print(f"速度 (dx/dt): {velocity}") # [1, 3, 5, 7],接近 2t
gradient - 梯度计算
gradient 函数计算数值梯度,使用中心差分法,边界处使用前向/后向差分。相比 diff,gradient 返回与原数组相同大小的结果。
import numpy as np
# 一维梯度
arr = np.array([1, 4, 9, 16, 25]) # x^2
grad = np.gradient(arr)
print(f"原数组 (x^2): {arr}")
print(f"梯度 (~2x): {grad}") # [3, 4, 6, 8, 9]
# 指定采样间距
t = np.array([0, 0.5, 1, 1.5, 2])
x = t ** 2
grad_t = np.gradient(x, t) # 指定不均匀间距
print(f"\n不均匀间距的梯度: {grad_t}")
# 指定均匀间距
x_uniform = np.array([0, 1, 4, 9, 16]) # 在 x=0,1,2,3,4 处
grad_uniform = np.gradient(x_uniform, 1.0) # 间距为 1
print(f"均匀间距梯度: {grad_uniform}")
# 二维梯度
arr_2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
grad_y, grad_x = np.gradient(arr_2d) # 返回 (y方向梯度, x方向梯度)
print(f"\n二维梯度:")
print(f"y 方向 (行): \n{grad_y}")
print(f"x 方向 (列): \n{grad_x}")
convolve - 卷积运算
convolve 函数计算两个一维数组的卷积,在信号处理和图像处理中应用广泛。
import numpy as np
signal = np.array([1, 2, 3, 4, 5])
kernel = np.array([0.5, 1, 0.5]) # 平滑核
# 卷积
result = np.convolve(signal, kernel)
print(f"信号: {signal}")
print(f"核: {kernel}")
print(f"卷积结果 (full): {result}") # 长度 = len(signal) + len(kernel) - 1
# 'valid' 模式:只计算完全重叠的部分
result_valid = np.convolve(signal, kernel, mode='valid')
print(f"卷积结果 (valid): {result_valid}")
# 'same' 模式:输出与第一个输入相同大小
result_same = np.convolve(signal, kernel, mode='same')
print(f"卷积结果 (same): {result_same}")
# 应用:移动平均
data = np.array([1, 3, 2, 5, 4, 6, 5, 7, 6])
window = np.ones(3) / 3 # 3点平均
moving_avg = np.convolve(data, window, mode='valid')
print(f"\n移动平均: {moving_avg}")
correlate - 相关运算
correlate 函数计算两个序列的互相关,常用于信号匹配和模式识别。
import numpy as np
signal = np.array([1, 2, 3, 2, 1])
pattern = np.array([3, 2, 1])
# 互相关
corr = np.correlate(signal, pattern)
print(f"信号: {signal}")
print(f"模式: {pattern}")
print(f"互相关: {corr}")
# 'full' 模式
corr_full = np.correlate(signal, pattern, mode='full')
print(f"互相关 (full): {corr_full}")
# 应用:信号匹配
signal_long = np.array([1, 2, 1, 2, 3, 2, 1, 2, 1])
template = np.array([3, 2, 1])
# 找到最匹配的位置
correlation = np.correlate(signal_long, template, mode='valid')
best_match = np.argmax(correlation)
print(f"\n最佳匹配位置: {best_match}")
print(f"匹配值: {signal_long[best_match:best_match+len(template)]}")
ediff1d - 边缘差分
ediff1d 是 diff 的变体,可以指定起始和结束的填充值。
import numpy as np
arr = np.array([1, 3, 6, 10])
# 基本差分(与 diff 相同)
diff = np.ediff1d(arr)
print(f"ediff1d: {diff}")
# 指定起始值
diff_with_start = np.ediff1d(arr, to_begin=0)
print(f"带起始值: {diff_with_start}")
# 指定结束值
diff_with_end = np.ediff1d(arr, to_end=0)
print(f"带结束值: {diff_with_end}")
# 同时指定
diff_full = np.ediff1d(arr, to_begin=[-1, 0], to_end=[0])
print(f"完整填充: {diff_full}")
逻辑函数
import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([2, 2, 2, 2])
# 元素比较
print(f"a > b: {a > b}") # [False False True True]
print(f"a == b: {a == b}") # [False True False False]
# 逻辑运算
x = np.array([True, True, False, False])
y = np.array([True, False, True, False])
print(f"logical_and: {np.logical_and(x, y)}") # [True False False False]
print(f"logical_or: {np.logical_or(x, y)}") # [True True True False]
print(f"logical_xor: {np.logical_xor(x, y)}") # [False True True False]
print(f"logical_not: {np.logical_not(x)}") # [False False True True]
# 判断数组
arr = np.array([1, 0, -1, 2])
print(f"isclose: {np.isclose(arr, 0)}") # [False True False False]
# 检查 NaN
arr_nan = np.array([1, np.nan, 3])
print(f"isnan: {np.isnan(arr_nan)}") # [False True False]
# 有限性和无穷性
print(f"isfinite: {np.isfinite(arr)}") # [True False True True]
print(f"isinf: {np.isinf(arr)}") # [False False False False]
实战:数据统计
import numpy as np
# 模拟考试成绩
scores = np.array([78, 85, 92, 67, 88, 75, 95, 82, 70, 90,
80, 85, 78, 92, 88, 73, 86, 79, 91, 84])
print("=== 成绩统计 ===")
print(f"总人数: {len(scores)}")
print(f"平均分: {np.mean(scores):.2f}")
print(f"中位数: {np.median(scores)}")
print(f"最高分: {np.max(scores)}")
print(f"最低分: {np.min(scores)}")
print(f"标准差: {np.std(scores):.2f}")
# 分位数分析
q1 = np.percentile(scores, 25)
q3 = np.percentile(scores, 75)
print(f"Q1 (25%): {q1}")
print(f"Q3 (75%): {q3}")
print(f"IQR: {q3 - q1}")
# 成绩分布
print(f"\n及格人数: {np.sum(scores >= 60)}")
print(f"优秀人数: {np.sum(scores >= 90)}")
print(f"不及格人数: {np.sum(scores < 60)}")
实战:数据归一化
数据预处理中的常用归一化技术:
import numpy as np
# 生成示例数据
data = np.array([[100, 0.001],
[200, 0.005],
[150, 0.003],
[300, 0.008]])
print("原始数据:")
print(data)
# Min-Max 归一化:缩放到 [0, 1]
def minmax_normalize(arr):
"""Min-Max 归一化"""
min_val = np.min(arr, axis=0)
max_val = np.max(arr, axis=0)
return (arr - min_val) / (max_val - min_val)
normalized = minmax_normalize(data)
print(f"\nMin-Max 归一化:")
print(normalized)
# Z-Score 标准化:均值为 0,标准差为 1
def zscore_normalize(arr):
"""Z-Score 标准化"""
mean = np.mean(arr, axis=0)
std = np.std(arr, axis=0)
return (arr - mean) / std
zscore = zscore_normalize(data)
print(f"\nZ-Score 标准化:")
print(zscore)
print(f"均值: {np.mean(zscore, axis=0)}")
print(f"标准差: {np.std(zscore, axis=0)}")
# 对数变换:处理长尾分布
long_tail = np.array([1, 10, 100, 1000, 10000])
log_transformed = np.log1p(long_tail) # log(1+x),处理零值
print(f"\n对数变换:")
print(f"原始: {long_tail}")
print(f"变换后: {log_transformed}")
实战:异常值检测
使用统计方法检测异常值:
import numpy as np
# 生成带异常值的数据
np.random.seed(42)
data = np.random.normal(100, 15, 100)
data = np.append(data, [200, 220, 180]) # 添加异常值
# 方法1:Z-Score 方法
def detect_outliers_zscore(arr, threshold=3):
"""使用 Z-Score 检测异常值"""
mean = np.mean(arr)
std = np.std(arr)
z_scores = np.abs((arr - mean) / std)
return arr[z_scores > threshold]
# 方法2:IQR 方法
def detect_outliers_iqr(arr):
"""使用 IQR 检测异常值"""
q1 = np.percentile(arr, 25)
q3 = np.percentile(arr, 75)
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
return arr[(arr < lower) | (arr > upper)]
# 方法3:使用百分位数
def detect_outliers_percentile(arr, lower_pct=1, upper_pct=99):
"""使用百分位数检测异常值"""
lower = np.percentile(arr, lower_pct)
upper = np.percentile(arr, upper_pct)
return arr[(arr < lower) | (arr > upper)]
print("异常值检测结果:")
print(f"Z-Score 方法: {detect_outliers_zscore(data)}")
print(f"IQR 方法: {detect_outliers_iqr(data)}")
print(f"百分位数方法: {detect_outliers_percentile(data)}")
# 移除异常值后的统计
clean_data = data[np.abs((data - np.mean(data)) / np.std(data)) <= 3]
print(f"\n原始数据均值: {np.mean(data):.2f}")
print(f"清理后均值: {np.mean(clean_data):.2f}")
实战:滑动窗口统计
计算滑动窗口内的统计量:
import numpy as np
def rolling_stats(arr, window_size):
"""计算滑动窗口统计量"""
n = len(arr) - window_size + 1
# 使用 stride_tricks 创建滑动窗口视图
shape = (n, window_size)
strides = (arr.strides[0], arr.strides[0])
windows = np.lib.stride_tricks.as_strided(arr, shape=shape, strides=strides)
return {
'mean': np.mean(windows, axis=1),
'std': np.std(windows, axis=1),
'min': np.min(windows, axis=1),
'max': np.max(windows, axis=1)
}
# 示例:股票价格分析
np.random.seed(42)
prices = 100 + np.cumsum(np.random.randn(20) * 2)
stats = rolling_stats(prices, 5)
print("滑动窗口统计 (窗口大小=5):")
print(f"价格: {prices}")
print(f"\n窗口均值: {stats['mean']}")
print(f"窗口标准差: {stats['std']}")
小结
本章介绍了 NumPy 中的数学函数:
- 通用函数(ufunc):元素级操作的函数,支持广播和向量化
- 基本算术运算:加、减、乘、除、幂等
- 三角函数:sin、cos、tan 及其反函数
- 指数对数:exp、log 系列函数,以及数值稳定的变体
- 统计函数:sum、mean、std、var、percentile 等,包括 NaN 安全版本
- 舍入函数:round、ceil、floor 等
- 数值处理:clip、sign、diff、gradient、convolve 等
- 逻辑函数:比较、布尔运算等
这些函数是数据分析、科学计算的基础,熟练掌握能够高效处理各种数值计算任务。
练习
- 计算自然数 1-100 的阶乘(使用 prod)
- 计算 0 到 2π 区间内 sin 函数的积分
- 给定数组,找出大于均值且小于中位数的元素
- 处理包含 NaN 值的数组,计算有效值的统计信息
- 实现滑动窗口平均(移动平均)
- 使用 diff 和 gradient 比较两种差分方法的区别
- 实现一个简单的异常值检测函数,结合多种方法