介绍
在本实验中,你将学习 NumPy 通用函数(Universal Functions),通常称为 ufuncs 的基础知识。Ufuncs 是 Python 中高性能计算的基石,它们允许你对整个数据数组执行快速的、逐元素的(element-wise)操作。我们将涵盖基本的算术运算、强大的广播(broadcasting)概念、聚合方法以及如何控制结果的数据类型。在本实验结束时,你将能够使用 ufuncs 来编写更简洁、更高效的数据处理代码。
在本实验中,你将学习 NumPy 通用函数(Universal Functions),通常称为 ufuncs 的基础知识。Ufuncs 是 Python 中高性能计算的基石,它们允许你对整个数据数组执行快速的、逐元素的(element-wise)操作。我们将涵盖基本的算术运算、强大的广播(broadcasting)概念、聚合方法以及如何控制结果的数据类型。在本实验结束时,你将能够使用 ufuncs 来编写更简洁、更高效的数据处理代码。
Ufuncs 的核心是执行逐元素操作。这意味着当你将一个操作应用于两个数组时,该操作会作用于每一对对应的元素。最常见的 ufuncs 是标准的算术运算符,如 +、-、* 和 /。
让我们开始对两个 NumPy 数组执行简单的加法运算。
首先,从左侧的文件浏览器中打开 ufunc_examples.py 文件。将现有内容替换为以下代码。这段代码导入 NumPy,创建两个数组并将它们相加。
import numpy as np
## Create two arrays
arr1 = np.array([0, 2, 3, 4])
arr2 = np.array([1, 1, -1, 2])
## The '+' operator is a ufunc that adds the arrays element-wise
result = arr1 + arr2
## Print the result
print("Step 1 Result:")
print(result)
添加代码后,保存文件。现在,从终端运行脚本以查看输出。
python ufunc_examples.py
你应该会看到逐元素加法的结果。
Step 1 Result:
[1 3 2 6]
这演示了 ufunc 的基本行为:arr1[0] 与 arr2[0] 相加,arr1[1] 与 arr2[1] 相加,依此类推,生成一个包含结果的新数组。
广播(Broadcasting)是一种强大的机制,它允许 NumPy 在算术运算中处理不同形状的数组。在底层,NumPy 会将较小的数组“广播”到较大的数组上,以便它们具有兼容的形状。
一个常见的例子是将数组的每个元素乘以一个数字。我们还将看到一个更复杂的场景,即一个一维数组被广播到一个二维数组上。
修改你的 ufunc_examples.py 文件。在脚本末尾添加以下代码。
## --- Appended code for Step 2 ---
## Broadcasting a scalar to an array
arr1 = np.array([1, 2, 3])
scalar_result = arr1 * 10
print("\nStep 2 Result (Scalar Broadcast):")
print(scalar_result)
## Broadcasting a 1D array to a 2D array
arr2d = np.array([[1], [2], [3]]) ## Shape (3, 1)
arr1d = np.array([1, 2, 3]) ## Shape (3,)
broadcast_result = arr2d * arr1d
print("\nStep 2 Result (Array Broadcast):")
print(broadcast_result)
再次保存文件并在终端中运行它。
python ufunc_examples.py
你将看到 Step 1 和 Step 2 的输出。
Step 1 Result:
[1 3 2 6]
Step 2 Result (Scalar Broadcast):
[10 20 30]
Step 2 Result (Array Broadcast):
[[1 2 3]
[2 4 6]
[3 6 9]]
在第二个例子中,一维数组 arr1d(形状为 (3,))和二维数组 arr2d(形状为 (3, 1))被广播到一个共同的形状 (3, 3),然后才进行逐元素乘法运算。
.reduce() 聚合数组除了逐元素操作,ufuncs 还具有执行聚合的特殊方法。.reduce() 方法是最有用的方法之一。它沿着数组的指定轴重复应用一个 ufunc,直到只剩下一个维度。
例如,np.add.reduce(arr) 等同于 np.sum(arr)。让我们看看它在二维数组上的工作方式。
将以下代码附加到你的 ufunc_examples.py 文件中。
## --- Appended code for Step 3 ---
## Create a 3x3 array
arr = np.arange(9).reshape(3, 3)
print("\nStep 3 Original Array:")
print(arr)
## Reduce the array by summing along axis 1 (the columns)
## This will sum the elements in each row.
## For row 0: 0 + 1 + 2 = 3
## For row 1: 3 + 4 + 5 = 12
## For row 2: 6 + 7 + 8 = 21
reduced_result = np.add.reduce(arr, axis=1)
print("\nStep 3 Result (reduce on axis=1):")
print(reduced_result)
保存文件并执行它。
python ufunc_examples.py
输出现在将包含此步骤的结果。
... (previous output) ...
Step 3 Original Array:
[[0 1 2]
[3 4 5]
[6 7 8]]
Step 3 Result (reduce on axis=1):
[ 3 12 21]
如你所见,.reduce() 通过对其元素应用 add 操作,沿着指定的轴折叠了数组。
NumPy 通常会自动确定输出数组的数据类型。但是,你可以使用 dtype 参数显式指定输出数据类型。这对于控制内存使用或确保数值精度非常有用。
让我们使用乘法执行一个归约操作,并将输出强制转换为浮点数,即使输入是整数数组。
将以下代码添加到 ufunc_examples.py 的末尾。
## --- Appended code for Step 4 ---
## Use the same 3x3 array from Step 3
arr = np.arange(1, 10).reshape(3, 3) ## Using 1-9 to avoid multiplying by zero
print("\nStep 4 Original Array:")
print(arr)
## Reduce with multiplication, casting the output to float
## For row 0: 1 * 2 * 3 = 6
## For row 1: 4 * 5 * 6 = 120
## For row 2: 7 * 8 * 9 = 504
multiply_result = np.multiply.reduce(arr, axis=1, dtype=float)
print("\nStep 4 Result (multiply.reduce with dtype=float):")
print(multiply_result)
保存并运行脚本。
python ufunc_examples.py
观察 Step 4 的输出。
... (previous output) ...
Step 4 Original Array:
[[1 2 3]
[4 5 6]
[7 8 9]]
Step 4 Result (multiply.reduce with dtype=float):
[ 6. 120. 504.]
注意输出数组 [ 6. 120. 504.] 中的尾随点 (.)。这表明元素现在是浮点数,正如我们用 dtype=float 指定的那样。
NumPy 的 ufunc 系统是可扩展的。你可以创建自己的类数组(array-like)对象,定义 ufuncs 如何在它们上面操作。这是一个高级功能,通常通过继承 NumPy 的 ndarray 并重写特殊方法(如 __add__,用于 + 运算符)来实现。
让我们创建一个简单的自定义数组类,当对它执行加法操作时,它会打印一条消息。
将这个最后的代码块添加到 ufunc_examples.py。
## --- Appended code for Step 5 ---
## Define a custom array class by subclassing np.ndarray
class MyArray(np.ndarray):
def __add__(self, other):
print("\nStep 5: Custom add method called!")
## Call the original implementation from the parent class
return super().__add__(other)
## Create an instance of our custom class
## We must use .view() to cast the ndarray to our custom class
my_arr = np.array([10, 20, 30]).view(MyArray)
## Perform addition, which will trigger our custom method
override_result = my_arr + 5
print("Step 5 Result (Overridden Ufunc):")
print(override_result)
保存文件并最后运行一次。
python ufunc_examples.py
检查最终输出。
... (previous output) ...
Step 5: Custom add method called!
Step 5 Result (Overridden Ufunc):
[15 25 35]
你可以看到,我们的自定义消息在加法结果之前被打印出来,这证实了我们的 __add__ 方法被调用了。这展示了 ufunc 系统强大的灵活性。
在本实验中,你学习了 NumPy 通用函数(ufuncs)的基础知识。我们从基本的逐元素算术运算开始,这是向量化计算的基础。然后,你探索了 broadcasting,这是一个允许 NumPy 对不同形状的数组执行操作的关键特性。我们还介绍了如何使用 .reduce() 等 ufunc 方法进行数据聚合,以及如何使用 dtype 参数控制输出数据类型。最后,你看到了一个通过继承 np.ndarray 来自定义 ufunc 行为的高级示例。掌握了这些技能,你将能更有效地编写高效、可读且强大的 NumPy 数值代码。