跳到主要内容

NumPy 数学函数

本章将详细介绍 NumPy 中的数学函数,包括通用函数(ufunc)、三角函数、指数对数函数、统计函数等。这些函数是数值计算的基础。

通用函数(ufunc)

通用函数(Universal Functions,简称 ufunc)是 NumPy 的核心概念之一,它是对数组中每个元素进行操作的函数。理解 ufunc 对于编写高效的 NumPy 代码至关重要。

什么是 ufunc?

ufunc 是一种对 ndarray 进行元素级操作的函数。它的核心特性是:

  1. 向量化:自动对数组的每个元素应用相同的操作,无需显式循环
  2. 广播支持:自动处理不同形状数组之间的运算
  3. 类型转换:自动处理不同数据类型之间的转换
  4. 高效实现:底层用 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 的本质:对于二元运算 \oplusreduce([a, b, c, d]) 计算 ((ab)c)d((a \oplus b) \oplus c) \oplus 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 安全版本说明
sumnansum求和
prodnanprod求积
meannanmean均值
mediannanmedian中位数
stdnanstd标准差
varnanvar方差
maxnanmax最大值
minnanmin最小值
argmaxnanargmax最大值索引
argminnanargmin最小值索引
percentilenanpercentile百分位数
quantilenanquantile分位数
cumsumnancumsum累积和
cumprodnancumprod累积积

实战:处理真实的缺失数据

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 函数计算数值梯度,使用中心差分法,边界处使用前向/后向差分。相比 diffgradient 返回与原数组相同大小的结果。

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 - 边缘差分

ediff1ddiff 的变体,可以指定起始和结束的填充值。

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 中的数学函数:

  1. 通用函数(ufunc):元素级操作的函数,支持广播和向量化
  2. 基本算术运算:加、减、乘、除、幂等
  3. 三角函数:sin、cos、tan 及其反函数
  4. 指数对数:exp、log 系列函数,以及数值稳定的变体
  5. 统计函数:sum、mean、std、var、percentile 等,包括 NaN 安全版本
  6. 舍入函数:round、ceil、floor 等
  7. 数值处理:clip、sign、diff、gradient、convolve 等
  8. 逻辑函数:比较、布尔运算等

这些函数是数据分析、科学计算的基础,熟练掌握能够高效处理各种数值计算任务。

练习

  1. 计算自然数 1-100 的阶乘(使用 prod)
  2. 计算 0 到 2π 区间内 sin 函数的积分
  3. 给定数组,找出大于均值且小于中位数的元素
  4. 处理包含 NaN 值的数组,计算有效值的统计信息
  5. 实现滑动窗口平均(移动平均)
  6. 使用 diff 和 gradient 比较两种差分方法的区别
  7. 实现一个简单的异常值检测函数,结合多种方法