零基础学 NumPy:核心概念快速入门 – wiki基地


零基础学 NumPy:核心概念快速入门

1. 告别列表:为什么你需要 NumPy?

欢迎来到科学计算的世界!如果你是 Python 的新手,或者刚开始接触数据分析、机器学习等领域,你可能已经习惯使用 Python 的内置 list(列表)来存储数据。列表非常灵活,可以容纳不同类型的数据,也能方便地增删改查。

但是,当你需要处理大量的数值数据时——比如数千行、数百万行的数据表格,或者高维度的图像、声音数据时——Python 列表的不足就显现出来了:

  1. 速度慢: Python 列表不是专门为数值计算设计的。执行数学运算时,往往需要通过循环遍历每个元素,效率低下。
  2. 内存占用高: Python 列表存储的是对象的引用,而不是数值本身。这意味着每个元素都需要额外的内存开销,且无法像 C 或 Fortran 中的数组那样紧凑地存储数值。
  3. 功能有限: Python 列表没有内置的数学函数来直接进行向量化运算(比如将每个元素都加上同一个数,或者计算所有元素的平均值)。

NumPy (Numerical Python) 就是为解决这些问题而生的。它是 Python 生态系统中进行科学计算的基础库,提供了一个强大的多维数组对象,以及大量用于处理这些数组的函数。几乎所有基于 Python 的科学计算库(如 Pandas、SciPy、Matplotlib、scikit-learn 等)都依赖于 NumPy。

学习 NumPy 的好处:

  • 极高的计算速度: NumPy 数组操作通常在 C 或 Fortran 中实现,远快于 Python 原生循环。
  • 内存效率高: NumPy 数组存储的是同一种数据类型的数值,内存使用更紧凑高效。
  • 强大的功能: 提供大量的数学函数、线性代数、傅里叶变换、随机数生成等功能,并且支持广播(Broadcasting)机制,使得不同形状的数组也能方便地进行运算。
  • 代码简洁: 通过向量化操作,可以用一行 NumPy 代码代替复杂的 Python 循环,代码更易读、更简洁。

简而言之,如果你想在 Python 中高效地进行任何形式的数值计算、数据处理或科学建模,NumPy 是你必须掌握的工具。

2. 准备工作:安装 NumPy

开始使用 NumPy 非常简单,只需要打开你的终端或命令提示符,运行以下命令即可:

bash
pip install numpy

如果你使用 Anaconda 环境,通常 NumPy 已经预装好了。如果需要安装,可以使用:

bash
conda install numpy

安装完成后,你就可以在 Python 脚本或交互式环境中导入它了:

python
import numpy as np

按照惯例,我们将 NumPy 简称为 np,这是社区广泛接受的约定。

3. NumPy 的核心:ndarray 对象

NumPy 最核心的数据结构是 ndarray (n-dimensional array),即 N 维数组。

3.1 ndarray vs. Python 列表

让我们再次强调 ndarray 和 Python 列表的主要区别:

  • 维度: 列表可以是嵌套的(模拟多维),但 NumPy 数组是真正意义上的多维(可以是一维、二维、三维,甚至更高维)。
  • 数据类型: ndarray 的所有元素必须是 同一种数据类型 (homogeneous),而列表可以包含不同类型的元素 (heterogeneous)。这是 NumPy 高效性的关键之一。
  • 功能: ndarray 支持大量的数学和逻辑运算,可以直接应用于整个数组。

3.2 数组的属性

一个 ndarray 对象有几个重要的属性,帮助我们理解和操作数组:

  • .ndim: 数组的维度(轴的数量)。
  • .shape: 数组在每个维度上的大小,返回一个元组。例如,一个 2×3 的矩阵,其 shape 是 (2, 3)
  • .size: 数组中元素的总数,等于 shape 中所有元素的乘积。
  • .dtype: 数组元素的类型。NumPy 支持多种数据类型,如 int32 (32位整数), float64 (64位浮点数), bool (布尔值) 等。
  • .itemsize: 数组中每个元素占用的字节数。
  • .data: 包含实际数组元素的缓冲区对象。通常我们不需要直接操作它。

让我们通过一个简单的例子来看看这些属性:

“`python
import numpy as np

创建一个二维数组

arr = np.array([[1, 2, 3],
[4, 5, 6]])

print(“数组:”)
print(arr)

print(“\n维度 (ndim):”, arr.ndim)
print(“形状 (shape):”, arr.shape)
print(“元素总数 (size):”, arr.size)
print(“元素类型 (dtype):”, arr.dtype)
print(“每个元素占字节数 (itemsize):”, arr.itemsize)
“`

运行结果可能如下:

“`
数组:
[[1 2 3]
[4 5 6]]

维度 (ndim): 2
形状 (shape): (2, 3)
元素总数 (size): 6
元素类型 (dtype): int64
每个元素占字节数 (itemsize): 8
“`

可以看到,这是一个 2 行 3 列的二维数组,总共有 6 个元素,默认的数据类型是 64 位整数。

4. 创建 NumPy 数组

NumPy 提供了多种创建数组的方式,以满足不同的需求:

4.1 从 Python 列表或元组创建

这是最常用的方式之一。将 Python 列表或元组作为参数传递给 np.array() 函数。

“`python

从列表创建一维数组

list1 = [1, 2, 3, 4, 5]
arr1 = np.array(list1)
print(“一维数组:”, arr1)
print(“类型:”, type(arr1))

从嵌套列表创建二维数组

list2d = [[1, 2, 3], [4, 5, 6]]
arr2d = np.array(list2d)
print(“\n二维数组:\n”, arr2d)

从元组创建

tuple_data = (10, 20, 30)
arr_from_tuple = np.array(tuple_data)
print(“\n从元组创建:”, arr_from_tuple)

指定数据类型

arr_float = np.array([1, 2, 3], dtype=np.float64)
print(“\n指定数据类型为 float64:”, arr_float)
print(“数据类型:”, arr_float.dtype)

arr_complex = np.array([1+2j, 3+4j])
print(“\n复数类型数组:”, arr_complex)
print(“数据类型:”, arr_complex.dtype)
“`

注意: 当从嵌套列表创建多维数组时,嵌套列表的“形状”必须是规则的,即每个内部列表的长度必须相同,否则 NumPy 可能无法创建规则的 ndarray,或者创建一个 dtype=object 的数组,这将失去 NumPy 的性能优势。

4.2 创建特定内容的数组

NumPy 提供了一系列函数来创建具有特定初始值的数组,这在初始化操作中非常有用。

  • np.zeros(shape, dtype=float): 创建一个指定 shape 和 dtype 的全零数组。
  • np.ones(shape, dtype=float): 创建一个指定 shape 和 dtype 的全一数组。
  • np.full(shape, fill_value, dtype=None): 创建一个指定 shape,并用 fill_value 填充的数组。
  • np.empty(shape, dtype=float): 创建一个指定 shape 的数组,其元素值是随机的(取决于内存中的现有内容),但创建速度最快。

“`python

创建一个 2×3 的全零数组

zeros_arr = np.zeros((2, 3))
print(“\n全零数组:\n”, zeros_arr)

创建一个 3×2 的全一数组,指定整数类型

ones_arr = np.ones((3, 2), dtype=np.int32)
print(“\n全一数组 (int32):\n”, ones_arr)

创建一个 2×2 的全 7 数组

full_arr = np.full((2, 2), 7)
print(“\n全 7 数组:\n”, full_arr)

创建一个 2×3 的空数组 (值不确定)

empty_arr = np.empty((2, 3))
print(“\n空数组 (值不确定):\n”, empty_arr)
“`

4.3 创建序列数组

类似 Python 的 range() 函数,NumPy 提供了 np.arange()。此外,还有一个非常有用的 np.linspace()

  • np.arange(start, stop, step, dtype=None): 创建一个等差序列数组。类似于 range(),但不包含 stop 值。
  • np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None): 在指定的 startstop 之间,创建指定数量 (num) 的均匀分布的样本。默认包含 stop

“`python

创建一个从 0 到 9 的数组

arr_range = np.arange(10)
print(“\narange(10):”, arr_range)

创建一个从 5 到 14,步长为 2 的数组

arr_range_step = np.arange(5, 15, 2)
print(“arange(5, 15, 2):”, arr_range_step)

创建一个从 0 到 10,包含 5 个元素的数组 (均匀分布)

arr_linspace = np.linspace(0, 10, 5)
print(“linspace(0, 10, 5):”, arr_linspace) # 输出 [ 0. 2.5 5. 7.5 10. ]

不包含 endopint

arr_linspace_noend = np.linspace(0, 10, 5, endpoint=False)
print(“linspace(0, 10, 5, endpoint=False):”, arr_linspace_noend) # 输出 [0. 2. 4. 6. 8.]
“`

4.4 创建随机数组

NumPy 的 np.random 模块提供了各种创建随机数组的函数。

  • np.random.rand(d0, d1, ..., dn): 创建指定 shape 的 [0, 1) 之间的均匀分布随机数数组。
  • np.random.randn(d0, d1, ..., dn): 创建指定 shape 的标准正态分布(均值为 0,标准差为 1)随机数数组。
  • np.random.randint(low, high=None, size=None, dtype='int'): 创建指定 size 的 [low, high) 之间的随机整数数组。

“`python

创建一个 3×2 的 [0, 1) 随机数数组

rand_arr = np.random.rand(3, 2)
print(“\nrand(3, 2):\n”, rand_arr)

创建一个 2×2 的标准正态分布随机数数组

randn_arr = np.random.randn(2, 2)
print(“\nrandn(2, 2):\n”, randn_arr)

创建一个包含 5 个 [0, 10) 之间随机整数的一维数组

randint_arr = np.random.randint(0, 10, 5)
print(“\nrandint(0, 10, 5):”, randint_arr)

创建一个 2×3 的 [1, 7) 之间随机整数数组

randint_2d_arr = np.random.randint(1, 7, size=(2, 3))
print(“\nrandint(1, 7, size=(2, 3)):\n”, randint_2d_arr)
“`

5. 访问数组元素:索引和切片

就像 Python 列表一样,你可以通过索引和切片来访问和修改 NumPy 数组的元素,但 NumPy 提供了更强大的多维索引和切片功能。

5.1 一维数组的索引和切片

与 Python 列表完全相同:

  • 索引: 使用整数访问单个元素 (从 0 开始)。
  • 切片: 使用 [start:stop:step] 访问子数组。

“`python
arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(“原数组:”, arr)

索引

print(“第一个元素:”, arr[0]) # 0
print(“最后一个元素:”, arr[-1]) # 9

切片

print(“前五个元素:”, arr[:5]) # [0 1 2 3 4]
print(“从索引 5 开始的元素:”, arr[5:]) # [5 6 7 8 9]
print(“从索引 2 到 7 的元素:”, arr[2:8]) # [2 3 4 5 6 7]
print(“每隔一个元素:”, arr[::2]) # [0 2 4 6 8]
print(“反转数组:”, arr[::-1]) # [9 8 7 6 5 4 3 2 1 0]

切片是原数组的视图 (View)

修改切片会影响原数组 (注意,这与Python列表切片行为不同!)

slice_arr = arr[5:8]
print(“\n切片 (view):”, slice_arr)
slice_arr[0] = 99 # 修改切片中的第一个元素 (原数组索引 5 的元素)
print(“修改切片后:”, slice_arr)
print(“原数组也改变了:”, arr) # 原数组变为 [ 0 1 2 3 4 99 6 7 8 9]

如果想获得副本 (Copy),使用 .copy() 方法

arr_copy = arr[5:8].copy()
arr_copy[0] = 100
print(“\n副本 (copy):”, arr_copy)
print(“原数组没有改变:”, arr)
“`

重要提示: NumPy 切片通常是原数组的 视图 (View),而不是副本 (Copy)。这意味着通过切片对元素进行的修改会直接反映在原数组上。如果你需要一个独立的副本,请显式使用 .copy() 方法。

5.2 多维数组的索引和切片

多维数组的索引使用逗号分隔每个维度上的索引或切片。例如,对于二维数组(矩阵),使用 [row_index, column_index]

“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(“\n二维数组:\n”, arr2d)

访问单个元素 (第1行, 第2列)

print(“arr2d[0, 1]:”, arr2d[0, 1]) # 输出 2 (索引从0开始)

访问第一行

print(“arr2d[0, :]:”, arr2d[0, :]) # 输出 [1 2 3]
print(“arr2d[0]:”, arr2d[0]) # 也可以简写成这样,输出 [1 2 3]

访问第二列

print(“arr2d[:, 1]:”, arr2d[:, 1]) # 输出 [2 5 8]

访问子矩阵 (前两行, 后两列)

print(“arr2d[:2, 1:]:\n”, arr2d[:2, 1:])

输出:

[[2 3]

[5 6]]

访问特定行和列 (例如,第0行和第2行,以及第1列)

注意这里需要使用花括号 [] 包裹索引列表 (这是 fancy indexing)

print(“arr2d[[0, 2], 1]:”, arr2d[[0, 2], 1]) # 输出 [2 8] (第0行第1列 和 第2行第1列)

访问特定行和列交叉处的元素

print(“arr2d[[0, 2], [1, 0]]:”, arr2d[[0, 2], [1, 0]]) # 输出 [2 7] (第0行第1列 和 第2行第0列)
“`

5.3 布尔索引 (Boolean Indexing)

布尔索引是一种非常强大和灵活的访问数组元素的方式。你可以使用一个与原数组形状相同(或者可以广播)的布尔数组来选择对应的元素。布尔数组中为 True 的位置的元素会被选中。

“`python
arr = np.arange(10)
print(“\n原数组:”, arr)

创建一个布尔数组 (例如,判断哪些元素大于 5)

condition = arr > 5
print(“布尔条件 (arr > 5):”, condition) # 输出 [False False False False False False True True True True]

使用布尔数组进行索引

selected_elements = arr[condition]
print(“大于 5 的元素:”, selected_elements) # 输出 [6 7 8 9]

可以将条件直接放在方括号内

print(“大于 5 的元素 (直接):”, arr[arr > 5]) # 输出 [6 7 8 9]

布尔索引也可以用于赋值

arr[arr > 5] = 0
print(“大于 5 的元素赋值为 0:”, arr) # 输出 [0 1 2 3 4 5 0 0 0 0]

在多维数组中使用布尔索引

arr2d = np.array([[1, -2, 3],
[-4, 5, -6],
[7, -8, 9]])
print(“\n二维数组:\n”, arr2d)

选中所有正数

positive_elements = arr2d[arr2d > 0]
print(“所有正数:”, positive_elements) # 输出 [1 3 5 7 9] (结果会展平为一维数组)

将所有负数赋值为 0

arr2d[arr2d < 0] = 0
print(“负数赋值为 0:\n”, arr2d)

输出:

[[1 0 3]

[0 5 0]

[7 0 9]]

“`

布尔索引是进行数据过滤和处理的利器,非常常用。

5.4 花式索引 (Fancy Indexing)

花式索引是指使用一个整数数组或列表来作为索引,以任意顺序选择数组中的元素。

“`python
arr = np.arange(100, 200, 10) # [100 110 120 130 140 150 160 170 180 190]
print(“\n原数组:”, arr)

使用一个索引数组选择元素

indices = [1, 5, 2, 8]
selected = arr[indices]
print(“使用索引数组 [1, 5, 2, 8] 选择:”, selected) # 输出 [110 150 120 180]

使用花式索引进行赋值

arr[indices] = [999, 888, 777, 666]
print(“使用花式索引赋值:”, arr) # 输出 [100 999 777 130 140 888 160 170 666 190]

多维数组的花式索引 (见上面的例子 arr2d[[0, 2], [1, 0]])

“`

6. 数组运算:向量化与广播

NumPy 强大的地方在于它支持向量化操作,这意味着你可以直接对整个数组执行数学运算,而无需编写显式的循环。

6.1 元素级运算 (Element-wise Operations)

当对两个形状相同的 NumPy 数组进行基本算术运算时 (+, -, , /, *),运算会应用到数组中的每个对应元素上。

“`python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])

print(“arr1:”, arr1)
print(“arr2:”, arr2)

print(“\n相加 (arr1 + arr2):”, arr1 + arr2) # [5 7 9]
print(“相减 (arr1 – arr2):”, arr1 – arr2) # [-3 -3 -3]
print(“相乘 (arr1 * arr2):”, arr1 * arr2) # [4 10 18] – 注意,这是元素级乘法! 不是矩阵乘法!
print(“相除 (arr1 / arr2):”, arr1 / arr2) # [0.25 0.4 0.5 ]
print(“幂运算 (arr1 ** 2):”, arr1 ** 2) # [1 4 9]
“`

同样的操作也适用于多维数组,只要它们的形状兼容(对于元素级运算,通常是形状相同)。

“`python
arr2d_a = np.array([[1, 2], [3, 4]])
arr2d_b = np.array([[5, 6], [7, 8]])

print(“\narr2d_a:\n”, arr2d_a)
print(“arr2d_b:\n”, arr2d_b)
print(“\narr2d_a + arr2d_b:\n”, arr2d_a + arr2d_b)

输出:

[[ 6 8]

[10 12]]

“`

6.2 通用函数 (Universal Functions, ufuncs)

NumPy 提供了大量的通用函数,它们也是执行元素级操作的函数。这些函数通常比直接使用运算符更灵活,例如它们支持额外的参数。常见的 ufuncs 包括 np.sqrt(), np.sin(), np.cos(), np.exp(), np.log(), np.abs(), np.maximum(), np.minimum() 等。

“`python
arr = np.array([1, 4, 9, 16])

print(“\n原数组:”, arr)
print(“平方根:”, np.sqrt(arr)) # [1. 2. 3. 4.]

arr_trig = np.array([0, np.pi/2, np.pi])
print(“\n三角函数输入:”, arr_trig)
print(“sin:”, np.sin(arr_trig)) # [0.00000000e+00 1.00000000e+00 1.22464680e-16] (接近0)

arr_comp = np.array([2, 5, 1, 8])
arr_comp2 = np.array([3, 4, 6, 2])
print(“\n比较两个数组的元素:”, np.maximum(arr_comp, arr_comp2)) # 输出 [3 5 6 8]
“`

ufuncs 使得对整个数组执行复杂的数学运算变得非常简单。

6.3 广播 (Broadcasting)

广播是 NumPy 中一个非常重要的概念,它描述了 NumPy 如何在算术运算期间处理具有不同形状的数组。在某些规则下,较小的数组会被“广播”到较大数组的形状,以便进行元素级运算,而无需复制数据,这大大提高了效率。

广播规则:
当对两个数组进行操作时,NumPy 会逐个维度比较它们的形状,从尾部维度开始。如果满足以下条件之一,则两个维度是兼容的:
1. 它们大小相等。
2. 其中一个维度的大小是 1。

如果不满足这些条件,并且无法通过扩展维度(在形状前面添加大小为 1 的维度)来使其兼容,则会引发错误。如果一个数组的维度少于另一个,则会在其形状前面填充大小为 1 的维度,直到维度数量匹配。

理解广播的最佳方法是看例子:

示例 1:数组 + 标量

“`python
arr = np.array([1, 2, 3])
scalar = 5

标量 5 会被广播成形状与 arr 相同的数组 [5 5 5]

然后进行元素级相加

print(“\n数组 + 标量:”, arr + scalar) # [6 7 8]
“`

示例 2:一维数组 + 二维数组

“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6]]) # shape (2, 3)
arr1d = np.array([10, 20, 30]) # shape (3,)

print(“\narr2d:\n”, arr2d)
print(“arr1d:”, arr1d)

arr1d 的 shape 是 (3,),会被扩展为 (1, 3)

然后沿第一个轴(大小为 2)广播,变成形状 (2, 3) 的数组 [[10 20 30], [10 20 30]]

最后进行元素级相加

print(“\narr2d + arr1d (行广播):\n”, arr2d + arr1d)

输出:

[[11 22 33]

[14 25 36]]

``
这里 arr1d 沿着
arr2d` 的行方向进行了广播。

示例 3:不同形状的二维数组

“`python
arr_a = np.array([[1, 2], [3, 4]]) # shape (2, 2)
arr_b = np.array([[10], [20]]) # shape (2, 1)

print(“\narr_a:\n”, arr_a)
print(“arr_b:\n”, arr_b)

比较形状 (2, 2) 和 (2, 1) 从尾部开始:

尾部维度:2 和 1 –> 兼容 (一个为 1)

下一个维度:2 和 2 –> 兼容 (大小相等)

arr_b 的 (2, 1) 会沿第二个轴(大小为 2)广播,变成形状 (2, 2) 的 [[10 10], [20 20]]

然后进行元素级相加

print(“\narr_a + arr_b:\n”, arr_a + arr_b)

输出:

[[11 12]

[23 24]]

“`

广播是一个非常节省内存和计算资源的机制,但初学者可能会觉得有些难以理解。关键在于记住比较规则并多练习。如果形状不兼容,NumPy 会抛出 ValueError: operands could not be broadcast together with shapes ... 错误。

7. 形状操作 (Shape Manipulation)

改变数组的形状而不改变其数据是常见的操作。

7.1 Reshape

reshape() 方法用于改变数组的形状。新形状必须与原形状的总元素数相同。

“`python
arr = np.arange(12) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(“\n原数组:”, arr)

将一维数组重塑为 3×4 的二维数组

reshaped_arr = arr.reshape((3, 4))
print(“重塑为 3×4:\n”, reshaped_arr)

输出:

[[ 0 1 2 3]

[ 4 5 6 7]

[ 8 9 10 11]]

使用 -1 自动计算维度大小

将数组重塑为 4 行 x 列

reshaped_arr_auto = arr.reshape((4, -1))
print(“重塑为 4 行 (-1 列):\n”, reshaped_arr_auto)

输出:

[[ 0 1 2]

[ 3 4 5]

[ 6 7 8]

[ 9 10 11]]

注意:reshape() 通常返回原数组的视图,如果新旧形状元素顺序不同,则可能是副本。

“`

7.2 Flatten 和 Ravel

flatten()ravel() 方法可以将多维数组展平(变成一维数组)。

“`python
arr2d = np.array([[1, 2], [3, 4]])
print(“\n原二维数组:\n”, arr2d)

flatten() 返回总是副本

flattened_arr = arr2d.flatten()
print(“flatten():”, flattened_arr)

ravel() 返回视图或副本,取决于具体实现和内存布局,通常优先视图

raveled_arr = arr2d.ravel()
print(“ravel():”, raveled_arr)

修改 flatten() 的结果不会影响原数组

flattened_arr[0] = 99
print(“修改 flatten() 结果:”, flattened_arr)
print(“原二维数组 (不受影响):\n”, arr2d)

修改 ravel() 的结果可能会影响原数组 (在此例中会)

raveled_arr[0] = 100
print(“修改 ravel() 结果:”, raveled_arr)
print(“原二维数组 (可能会改变):\n”, arr2d)

输出:

[[100 2]

[ 3 4]]

``
对于初学者,了解
flatten()保证是副本,而ravel()` 更注重性能(可能是视图)就足够了。

7.3 转置 (Transpose)

.T 属性或 np.transpose() 函数用于数组的转置(行列互换)。

“`python
arr2d = np.arange(6).reshape((2, 3))
print(“\n原数组:\n”, arr2d)

输出:

[[0 1 2]

[3 4 5]]

print(“\n转置 (.T):\n”, arr2d.T)

输出:

[[0 3]

[1 4]

[2 5]]

print(“\n转置 (np.transpose):\n”, np.transpose(arr2d))

输出:

[[0 3]

[1 4]

[2 5]]

“`
转置在矩阵运算中非常常见。

8. 聚合函数 (Aggregation Functions)

NumPy 提供了许多用于计算数组中元素总和、平均值、最小值、最大值等聚合统计量的函数。

  • sum(): 计算总和。
  • mean(): 计算平均值。
  • min(), max(): 计算最小值和最大值。
  • std(): 计算标准差。
  • var(): 计算方差。
  • argmin(), argmax(): 返回最小值和最大值的索引。

这些函数都可以作为 ndarray 的方法调用,或者作为 NumPy 的函数调用 (np.sum(arr))。

“`python
arr = np.arange(1, 10) # [1 2 3 4 5 6 7 8 9]
print(“\n原数组:”, arr)

print(“总和:”, arr.sum()) # 45
print(“平均值:”, arr.mean()) # 5.0
print(“最小值:”, arr.min()) # 1
print(“最大值:”, arr.max()) # 9
print(“标准差:”, arr.std()) # 约 2.58
print(“最小值的索引:”, arr.argmin()) # 0
print(“最大值的索引:”, arr.argmax()) # 8
“`

8.1 按轴进行聚合

对于多维数组,聚合函数可以通过 axis 参数指定沿着哪个轴进行计算。

  • axis=0: 沿着列方向(跨行)进行计算。
  • axis=1: 沿着行方向(跨列)进行计算。
  • None (默认): 对整个数组进行计算,返回一个标量。

想象一个 2×3 的二维数组:
[[a b c],
[d e f]]

* 沿着 axis=0 聚合:结果会是长度为 3 的一维数组 [a+d, b+e, c+f] (对每一列求和)。
* 沿着 axis=1 聚合:结果会是长度为 2 的一维数组 [a+b+c, d+e+f] (对每一行求和)。

“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6]])
print(“\n二维数组:\n”, arr2d)

print(“\n所有元素的总和:”, arr2d.sum()) # 21

print(“沿着 axis=0 求和 (每列求和):”, arr2d.sum(axis=0)) # [5 7 9]
print(“沿着 axis=1 求和 (每行求和):”, arr2d.sum(axis=1)) # [6 15]

print(“\n沿着 axis=0 求平均 (每列平均):”, arr2d.mean(axis=0)) # [2.5 3.5 4.5]
print(“沿着 axis=1 求平均 (每行平均):”, arr2d.mean(axis=1)) # [3. 5.]
“`

理解 axis 参数对于处理多维数据(如图像、时间序列等)至关重要。

9. 读写文件

NumPy 提供了简单的方法来将数组保存到文件和从文件加载。最常用的是 .npy 格式,它是 NumPy 特有的二进制格式,可以保存数组及其数据类型、形状等信息。

  • np.save('filename.npy', array): 将单个数组保存到 .npy 文件。
  • np.load('filename.npy'): 从 .npy 文件加载数组。
  • np.savez('filename.npz', array1=arr1, array2=arr2): 将多个数组保存到压缩的 .npz 文件中,以关键字参数指定数组名称。
  • np.load('filename.npz'): 从 .npz 文件加载,返回一个字典状的对象,可以通过键访问保存的数组。

“`python
arr = np.arange(10)

保存到 .npy 文件

np.save(‘my_array.npy’, arr)
print(“\n数组已保存到 my_array.npy”)

从 .npy 文件加载

loaded_arr = np.load(‘my_array.npy’)
print(“从 my_array.npy 加载:”, loaded_arr)

创建两个数组

arr_a = np.array([1, 2, 3])
arr_b = np.array([[10, 20], [30, 40]])

保存到 .npz 文件

np.savez(‘multiple_arrays.npz’, array_a=arr_a, array_b=arr_b)
print(“多个数组已保存到 multiple_arrays.npz”)

从 .npz 文件加载

loaded_npz = np.load(‘multiple_arrays.npz’)
print(“从 multiple_arrays.npz 加载:”)
print(“数组 ‘array_a’:”, loaded_npz[‘array_a’])
print(“数组 ‘array_b’:\n”, loaded_npz[‘array_b’])

加载 .npz 文件后,记得关闭文件对象

loaded_npz.close()

注意:对于文本文件 (如 .txt, .csv),可以使用 np.loadtxt() 和 np.savetxt(),但这通常不如 Pandas 灵活。

“`

10. 为什么 NumPy 比 Python 列表快?一个简单的例子

让我们通过一个简单的例子来直观感受一下 NumPy 的速度优势。计算两个包含大量数字的列表/数组的和。

“`python
import time

list_size = 1000000 # 一百万个数字

使用 Python 列表

list1 = list(range(list_size))
list2 = list(range(list_size))

start_time = time.time()
list_sum = [list1[i] + list2[i] for i in range(list_size)] # 使用列表推导式
end_time = time.time()
print(f”\n使用 Python 列表求和耗时: {end_time – start_time:.6f} 秒”)

使用 NumPy 数组

numpy_arr1 = np.arange(list_size)
numpy_arr2 = np.arange(list_size)

start_time = time.time()
numpy_sum = numpy_arr1 + numpy_arr2 # 使用 NumPy 向量化操作
end_time = time.time()
print(f”使用 NumPy 数组求和耗时: {end_time – start_time:.6f} 秒”)
“`

运行这个例子,你会看到 NumPy 的求和速度通常比 Python 列表快几个数量级!这就是 NumPy 在处理大规模数值数据时如此受欢迎的核心原因。

11. 接下来呢?NumPy 的更多功能

这篇文章涵盖了 NumPy 的核心概念,足以让你入门并开始进行基本的数组操作。但 NumPy 的功能远不止于此。在你掌握了这些基础之后,可以进一步学习:

  • 矩阵运算和线性代数: NumPy 提供了 np.dot(), np.matmul(), np.linalg 模块等,用于矩阵乘法、求逆、行列式、特征值等。
  • 随机数生成: np.random 模块提供了更丰富的随机数分布(正态分布、泊松分布等)和种子控制。
  • 更高级的索引: 例如使用 np.ix_ 构建网格索引。
  • 排序、搜索和计数: np.sort(), np.where(), np.unique(), np.bincount() 等。
  • 统计函数: 更多的统计量计算,如百分位数、相关系数等。
  • 比较操作和逻辑操作: (>, <, ==, !=, &, |, ~) 它们也都是元素级的,结果是布尔数组。
  • 结构化数组: 允许数组中的元素包含不同类型的数据(类似 C 语言的结构体)。

12. 总结

NumPy 是 Python 科学计算的基石。通过本文的学习,你应该已经掌握了以下核心概念:

  • 为什么选择 NumPy: 高效的数值计算能力,克服 Python 列表的不足。
  • ndarray: NumPy 的核心多维同质数组对象,及其重要属性 (shape, dtype, ndim, size)。
  • 创建数组: 从列表、zeros/ones/full/empty、arange/linspace、随机数等多种方法。
  • 索引和切片: 强大的多维索引、切片、布尔索引和花式索引,以及视图与副本的区别。
  • 数组运算: 高效的元素级运算和通用函数 ufuncs。
  • 广播 (Broadcasting): 理解 NumPy 如何处理不同形状数组的运算。
  • 形状操作: reshape, flatten, ravel, 转置 (.T)。
  • 聚合函数: sum, mean, min, max 等,以及按轴 (axis) 计算。
  • 文件读写: 使用 .npy.npz 格式保存和加载数组。

掌握这些核心概念是你迈入 Python 数据科学和机器学习领域的重要一步。通过大量的练习,你将能更熟练地运用 NumPy 解决实际问题。

现在,打开你的 Python 环境,动手尝试创建和操作 NumPy 数组吧!实践是最好的老师。祝你学习顺利!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部