一旦数据加载到 NumPy 数组中,接下来很自然地就是进行计算。NumPy 在科学计算和机器学习中广受欢迎的主要原因之一是它能够对整个数组进行快速的矢量化操作,而无需显式的 Python for 循环。在此,我们分析 NumPy 如何处理数组计算,并介绍通用函数(ufuncs)的原理。逐元素操作标准的 Python 算术运算符直接作用于 NumPy 数组,逐元素地执行操作。这是区分 NumPy 数组与 Python 列表的基本特性。思考两个数组:import numpy as np arr1 = np.array([1, 2, 3, 4]) arr2 = np.array([10, 20, 30, 40]) # 逐元素加法 print(arr1 + arr2) # Output: [11 22 33 44] # 逐元素减法 print(arr2 - arr1) # Output: [ 9 18 27 36] # 逐元素乘法 print(arr1 * arr2) # Output: [ 10 40 90 160] # 逐元素除法 print(arr2 / arr1) # Output: [10. 20. 30. 40.] # 逐元素幂运算 print(arr1 ** 2) # Output: [ 1 4 9 16]这些操作会创建包含结果的新数组。操作在数组的对应元素之间执行。这种逐元素行为也适用于比较:arr1 = np.array([1, 5, 3, 7]) arr2 = np.array([2, 4, 3, 8]) # 逐元素比较 print(arr1 > arr2) # Output: [False True False False] print(arr1 == arr2) # Output: [False False True False]这些比较返回布尔数组,这对于索引和条件逻辑非常有用,我们稍后会看到。这里的主要优势是性能。NumPy 的操作是用 C 语言实现的,并在内存块上操作,这使得它们在数值计算方面比通过循环迭代 Python 列表显著更快。理解通用函数 (ufuncs)尽管标准运算符很方便,但 NumPy 通过其通用函数(ufuncs)提供了一套更丰富的数学函数。ufunc 是一种对 ndarray 对象中的数据执行逐元素操作的函数。可以将它们视为简单函数的矢量化包装器,这些函数接受一个或多个标量输入并生成一个或多个标量输出。通用函数大致可以分类为:一元通用函数这些函数作用于单个数组,对每个元素执行操作。arr = np.arange(1, 6) # 创建 [1 2 3 4 5] print(arr) # 每个元素的平方根 print(np.sqrt(arr)) # Output: [1. 1.41421356 1.73205081 2. 2.23606798] # 每个元素的指数(e^x) print(np.exp(arr)) # Output: [ 2.71828183 7.3890561 20.08553692 54.59815003 148.4131591 ] # 自然对数(以 e 为底) # 注意:log(0) 是 -inf,负数的 log 是 nan arr_pos = np.array([1, np.e, np.e**2]) print(np.log(arr_pos)) # Output: [0. 1. 2.] # 每个元素的正弦(假定为弧度) angles = np.array([0, np.pi/2, np.pi]) print(np.sin(angles)) # Output: [0.0000000e+00 1.0000000e+00 1.2246468e-16] # 注意接近 0 时的浮点精度 # 绝对值 arr_neg = np.array([-1, 2, -3.5]) print(np.abs(arr_neg)) # Output: [1. 2. 3.5]其他常见的一元通用函数包括 np.cos、np.tan、np.log10、np.ceil、np.floor、np.round、np.isnan(检查是否为非数字)和 np.isinf(检查是否为无穷大)。二元通用函数这些函数接受两个数组作为输入(尽管广播,即下一节会讲到的内容,允许兼容的形状),并逐元素执行操作。许多二元通用函数与算术运算符相对应。arr1 = np.array([1, 5, 3, 8]) arr2 = np.array([2, 4, 3, 7]) # 逐元素加法(等同于 arr1 + arr2) print(np.add(arr1, arr2)) # Output: [ 3 9 6 15] # 逐元素最大值 print(np.maximum(arr1, arr2)) # Output: [2 5 3 8] # 逐元素最小值 print(np.minimum(arr1, arr2)) # Output: [1 4 3 7] # 逐元素幂(有时等同于 arr1 ** arr2) # 如果需要更复杂的底数/指数类型,请使用 np.power print(np.power(arr1, arr2)) # Output: [ 1 625 27 2097152] # 模数 / 余数 print(np.mod(arr1, arr2)) # 等同于 arr1 % arr2 # Output: [1 1 0 1]其他有用的二元通用函数包括 np.subtract、np.multiply、np.divide、np.floor_divide、np.greater、np.less_equal、np.logical_and、np.logical_or 等。性能优势通用函数在处理大型数组时效率优势变得明显。让我们比较一下使用 Python 循环和 NumPy 通用函数计算 0 到 999,999 之间数字的正弦的性能。# 创建一个大型数组 large_arr = np.arange(1_000_000) # 使用 Python 循环(仅为演示,速度慢!) import math result_loop = [0.0] * 1_000_000 # 预分配列表 # %timeit for i in range(1_000_000): result_loop[i] = math.sin(large_arr[i]) # 在典型硬件上:每次循环约 200-300 毫秒 # 使用 NumPy 通用函数 # %timeit result_ufunc = np.sin(large_arr) # 在典型硬件上:每次循环约 5-10 毫秒(注意:%timeit 是 IPython 的魔术命令。如果在标准 Python 脚本中运行,您将使用 timeit 模块进行性能测试。)NumPy 通用函数通常快几个数量级,因为循环发生在高度优化、编译的 C 代码中,并在连续的内存块上操作,而不是为每个元素解释 Python 代码。这种速度对于处理机器学习中常见的大型数据集非常重要。面向数组的条件逻辑通常,您需要根据数组值应用逻辑。使用通过比较生成的布尔数组以及 np.where 等函数,提供了一种强大、矢量化的方法来实现此目的,同时避免了缓慢的 Python 循环。np.where 是三元表达式(x if condition else y)的矢量化等价物。它接受三个参数:一个条件(布尔数组)和两个值或数组($x$ 和 $y$)。它返回一个新的数组,其中条件为 True 的位置从 $x$ 中取元素,条件为 False 的位置从 $y$ 中取元素。# 示例数据:测量读数 readings = np.array([10, -5, 22, -8, 15, 0]) # 将负读数替换为 0,正读数保持不变 cleaned_readings = np.where(readings < 0, 0, readings) print(cleaned_readings) # Output: [10 0 22 0 15 0] # 根据阈值分配类别标签 threshold = 12 labels = np.where(readings > threshold, 'High', 'Low') print(labels) # Output: ['Low' 'Low' 'High' 'Low' 'High' 'Low'] # 使用两个数组作为 x 和 y arr_a = np.array([100, 200, 300, 400]) arr_b = np.array([1, 2, 3, 4]) condition = np.array([True, False, True, False]) result = np.where(condition, arr_a, arr_b) print(result) # Output: [100 2 300 4]您也可以对布尔数组执行聚合操作:bool_arr = np.array([True, False, True, False]) # 检查是否有任何元素为 True print(np.any(bool_arr)) # Output: True # 检查是否所有元素都为 True print(np.all(bool_arr)) # Output: False # 计算 True 元素的数量(True 评估为 1,False 评估为 0) print(np.sum(bool_arr)) # Output: 2这些条件和聚合工具使得复杂的、基于数组的逻辑能够简洁地表达并高效地执行。熟练掌握数组计算和通用函数对于编写高效的 NumPy 代码非常必要。它们让您能够自然地表达计算并实现高性能,构成了更复杂操作以及与 Pandas 和 Scikit-learn 等库交互的基础。下一节将介绍广播,它将这些逐元素操作扩展到形状不完全相同的数组。