零基础学 NumPy:核心概念快速入门
1. 告别列表:为什么你需要 NumPy?
欢迎来到科学计算的世界!如果你是 Python 的新手,或者刚开始接触数据分析、机器学习等领域,你可能已经习惯使用 Python 的内置 list
(列表)来存储数据。列表非常灵活,可以容纳不同类型的数据,也能方便地增删改查。
但是,当你需要处理大量的数值数据时——比如数千行、数百万行的数据表格,或者高维度的图像、声音数据时——Python 列表的不足就显现出来了:
- 速度慢: Python 列表不是专门为数值计算设计的。执行数学运算时,往往需要通过循环遍历每个元素,效率低下。
- 内存占用高: Python 列表存储的是对象的引用,而不是数值本身。这意味着每个元素都需要额外的内存开销,且无法像 C 或 Fortran 中的数组那样紧凑地存储数值。
- 功能有限: 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)
: 在指定的start
和stop
之间,创建指定数量 (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]]
``
arr2d` 的行方向进行了广播。
这里 arr1d 沿着
示例 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 数组吧!实践是最好的老师。祝你学习顺利!