如何学习 NumPy?这篇超详细教程带你轻松入门数据科学基石!
欢迎来到数据科学的世界!如果你正踏上这条令人兴奋的道路,那么你很快就会遇到一个名字:NumPy。NumPy 是 Python 中用于科学计算和数据分析的核心库,尤其擅长处理大型多维数组和矩阵。无论是进行复杂的数学运算、统计分析,还是为机器学习模型准备数据,NumPy 都是不可或缺的强大工具。
对于初学者来说,NumPy 的概念和功能可能看起来有些 daunting(令人望而生畏),但请放心,它远没有你想的那么复杂。一旦你掌握了 NumPy 的核心思想——多维数组(ndarray
)以及它提供的各种便捷操作,你的数据处理效率将会大大提升。
本文将作为你的 NumPy 入门指南。我们将从最基础的概念开始,一步步深入,直到你能够熟练使用 NumPy 进行常见的数据操作。准备好了吗?让我们开始这段 NumPy 学习之旅吧!
1. 为什么需要学习 NumPy?它有什么优势?
在深入学习之前,我们先来了解一下 NumPy 为什么如此重要,以及它相对于 Python 原生列表(list)的优势。
想象一下,你需要对一百万个数字执行同一个数学运算(比如,每个数字都加 5)。如果使用 Python 列表,你可能需要写一个循环:
“`python
Python 列表示例
my_list = list(range(1000000))
new_list = []
for number in my_list:
new_list.append(number + 5)
这段代码可以工作,但对于大量数据,它相对较慢
“`
这段代码是可行的,但在处理大量数据时,Python 循环的效率通常不高。这是因为 Python 的列表是动态的,可以存储不同类型的对象,并且循环是在解释器层面进行的。
现在,让我们看看 NumPy 如何处理相同的任务:
“`python
NumPy 数组示例
import numpy as np
my_array = np.arange(1000000) # 创建一个包含100万个元素的NumPy数组
new_array = my_array + 5
这段代码非常快!
“`
看到了吗?代码更简洁,而且 NumPy 数组的操作是向量化的(vectorized),这意味着运算是在底层编译好的代码(通常是 C、C++ 或 Fortran)中执行的,速度非常快。
NumPy 的主要优势归结为以下几点:
- 性能卓越: NumPy 操作是在编译过的代码中执行的,比 Python 列表上的循环快得多。尤其在处理大规模数组时,性能优势更为显著。
- 内存效率高: NumPy 数组是同构的(homogeneous),即所有元素的数据类型相同。这使得 NumPy 能够更紧凑地存储数据,从而节省内存。而 Python 列表可以存储不同类型的对象,每个对象都需要额外的开销来存储类型信息和引用计数。
- 功能丰富: NumPy 提供了大量的数学函数、统计函数、线性代数函数、随机数生成功能等,这些都是科学计算中常用的工具。
- 生态系统基石: 许多流行的数据科学库,如 Pandas、SciPy、Matplotlib、Scikit-learn、TensorFlow 和 PyTorch,都是建立在 NumPy 数组的基础之上的。学习 NumPy 是使用这些库的前提。
因此,无论你是想进行数据分析、机器学习、图像处理、信号处理还是任何需要处理大量数值数据的任务,掌握 NumPy 都是至关重要的。
2. 安装 NumPy
学习 NumPy 的第一步是安装它。如果你的 Python 环境还没有安装 NumPy,可以使用 pip 包管理器进行安装。
打开你的终端或命令提示符,运行以下命令:
bash
pip install numpy
如果你使用的是 Anaconda 发行版,NumPy 通常已经预装好了。如果没有,可以使用 conda 命令安装:
bash
conda install numpy
安装完成后,你就可以在 Python 脚本或交互式环境中导入 NumPy 并开始使用了。通常,我们会使用一个标准别名 np
来导入 NumPy,这样写代码更简洁:
python
import numpy as np
从现在开始,本文中的所有 NumPy 功能都将通过 np.
前缀来访问。
3. NumPy 的核心:ndarray
NumPy 的核心是 ndarray
对象,它代表了一个多维同构数组(n-dimensional array)。
- 多维: 它可以是一维的(类似于 Python 列表)、二维的(类似于表格或矩阵)、三维的(类似于立方体),甚至更高维的。
- 同构: 数组中的所有元素必须是相同的数据类型(如整数、浮点数、布尔值等)。这与 Python 列表不同,后者可以包含各种类型的对象。
下面我们来学习如何创建和检查 ndarray
。
3.1 创建 ndarray
有多种方法可以创建 NumPy 数组:
从 Python 列表或元组创建:
这是最常见的方式之一,将已有的 Python 数据结构转换为 NumPy 数组。
“`python
从列表创建一维数组
list1 = [1, 2, 3, 4, 5]
arr1 = np.array(list1)
print(“一维数组:”, arr1)
print(“类型:”, type(arr1))
从列表的列表创建二维数组
list2 = [[1, 2, 3], [4, 5, 6]]
arr2 = np.array(list2)
print(“\n二维数组:\n”, arr2)
从元组创建
tuple1 = (10, 20, 30)
arr_from_tuple = np.array(tuple1)
print(“\n从元组创建数组:”, arr_from_tuple)
“`
创建全零、全一或指定值的数组:
在很多场景下,我们需要创建特定大小并用特定值填充的数组,NumPy 提供了方便的函数:
“`python
创建一个形状为 (2, 3) 的全零数组
zeros_array = np.zeros((2, 3))
print(“\n全零数组:\n”, zeros_array)
创建一个形状为 (3, 4) 的全一数组
ones_array = np.ones((3, 4))
print(“\n全一数组:\n”, ones_array)
创建一个形状为 (2, 2) 并用指定值填充的数组
full_array = np.full((2, 2), 7)
print(“\n指定值数组:\n”, full_array)
创建一个与现有数组形状和数据类型相同的全零数组
arr_like_zeros = np.zeros_like(arr2)
print(“\n像arr2一样的全零数组:\n”, arr_like_zeros)
“`
创建空数组:
np.empty()
创建一个数组,其内容是任意的(取决于内存中的现有内容)。这比创建全零或全一数组稍快,因为它不需要初始化数组元素,但要小心使用,因为里面的值是不可预测的。
“`python
创建一个形状为 (2, 3) 的空数组(内容随机)
empty_array = np.empty((2, 3))
print(“\n空数组:\n”, empty_array) # 注意:输出值是不确定的
“`
创建有序序列的数组:
类似于 Python 的 range()
函数,np.arange()
可以创建一个包含等间隔值的数组。
“`python
创建一个从 0 到 9 的数组
arr_range = np.arange(10)
print(“\narange 数组:”, arr_range)
创建一个从 5 到 14 的数组
arr_range_start_stop = np.arange(5, 15)
print(“arange (start, stop) 数组:”, arr_range_start_stop)
创建一个从 0 到 10,步长为 2 的数组
arr_range_step = np.arange(0, 11, 2)
print(“arange (start, stop, step) 数组:”, arr_range_step)
注意:arange 也可以处理浮点数步长,但可能存在精度问题
arr_range_float = np.arange(0, 1, 0.1)
print(“arange (float step) 数组:”, arr_range_float)
“`
np.linspace()
创建一个包含指定数量、均匀分布在指定间隔内的值的数组。这在科学计算中非常有用。
“`python
创建一个在 0 到 10 之间均匀分布的 5 个点
arr_linspace = np.linspace(0, 10, 5)
print(“\nlinspace 数组:”, arr_linspace)
创建一个在 0 到 1 之间均匀分布的 10 个点(包含起点和终点)
arr_linspace_inclusive = np.linspace(0, 1, 10)
print(“linspace 数组 (10 points):”, arr_linspace_inclusive)
“`
创建随机数的数组:
np.random
模块提供了多种创建随机数数组的功能,这在模拟和统计中非常常见。
“`python
创建一个形状为 (3, 3) 的,元素在 [0.0, 1.0) 之间均匀分布的数组
random_uniform = np.random.rand(3, 3)
print(“\n均匀分布随机数组:\n”, random_uniform)
创建一个形状为 (2, 4) 的,服从标准正态分布(均值0,方差1)的数组
random_normal = np.random.randn(2, 4)
print(“\n标准正态分布随机数组:\n”, random_normal)
创建一个形状为 (2, 3) 的,元素在 [0, 10) 之间随机整数的数组
random_integers = np.random.randint(0, 10, size=(2, 3))
print(“\n随机整数数组:\n”, random_integers)
设置随机种子,保证结果的可复现性
np.random.seed(42)
random_seeded = np.random.rand(2, 2)
print(“\n设置种子后的随机数组:\n”, random_seeded)
np.random.seed(42) # 再次设置相同种子
random_seeded_again = np.random.rand(2, 2)
print(“再次设置相同种子后的随机数组:\n”, random_seeded_again) # 输出与上面相同
“`
3.2 数组属性
创建数组后,我们可以访问其属性来了解它的结构和内容:
.shape
: 一个元组,表示数组在每个维度上的大小。.ndim
: 数组的维数(轴数)。.size
: 数组中元素的总数。.dtype
: 数组元素的数据类型。.itemsize
: 数组中每个元素占用的字节数。.nbytes
: 数组占用的总字节数(等于size * itemsize
)。
“`python
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(“数组:\n”, arr)
print(“形状 (shape):”, arr.shape)
print(“维数 (ndim):”, arr.ndim)
print(“元素总数 (size):”, arr.size)
print(“数据类型 (dtype):”, arr.dtype)
print(“单个元素字节数 (itemsize):”, arr.itemsize)
print(“总字节数 (nbytes):”, arr.nbytes)
arr_float = np.array([[1.1, 2.2], [3.3, 4.4]], dtype=np.float64)
print(“\n浮点数数组:\n”, arr_float)
print(“数据类型 (dtype):”, arr_float.dtype)
print(“单个元素字节数 (itemsize):”, arr_float.itemsize)
“`
数据类型(dtype
)是 NumPy 数组的一个重要特性。默认情况下,NumPy 会尝试从输入数据推断最合适的数据类型,但你也可以在创建数组时通过 dtype
参数明确指定。常见的数据类型包括:
np.int64
(或np.int32
,np.int16
):64位(或32位,16位)整数np.float64
(或np.float32
):64位(或32位)浮点数np.bool
:布尔值np.str_
:字符串np.complex128
:复数
指定合适的数据类型可以节省内存并确保计算精度。
4. 索引和切片
访问 NumPy 数组中的元素与 Python 列表类似,但功能更强大,尤其是在处理多维数组时。
4.1 基本索引
-
一维数组: 与 Python 列表相同,使用方括号和索引号。
python
arr = np.arange(10)
print("原数组:", arr)
print("第一个元素:", arr[0])
print("最后一个元素:", arr[-1])
print("索引为 3 的元素:", arr[3]) -
多维数组: 使用逗号分隔每个维度的索引。例如,对于二维数组
arr[row_index, column_index]
。python
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("\n二维数组:\n", arr2d)
print("第 0 行,第 0 列的元素:", arr2d[0, 0])
print("第 1 行,第 2 列的元素:", arr2d[1, 2])
print("第 2 行,第 1 列的元素:", arr2d[2, 1])
4.2 切片 (Slicing)
切片用于获取数组的子部分。语法与 Python 列表切片类似:[start:stop:step]
。
-
一维数组:
python
arr = np.arange(10)
print("原数组:", arr)
print("前 5 个元素:", arr[:5]) # 从开头到索引 4
print("索引 5 及之后的元素:", arr[5:]) # 从索引 5 到结尾
print("索引 2 到 7 之间的元素:", arr[2:8]) # 从索引 2 到索引 7
print("每隔一个元素:", arr[::2]) # 从头到尾,步长为 2
print("反转数组:", arr[::-1]) # 反转数组 -
多维数组: 可以对每个维度进行切片,用逗号分隔。
“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(“\n二维数组:\n”, arr2d)切片获取第一行
print(“第一行:”, arr2d[0, :]) # 或直接 arr2d[0]
切片获取所有行的第三列
print(“第三列:”, arr2d[:, 2])
切片获取前两行,所有列
print(“前两行:\n”, arr2d[:2, :]) # 或 arr2d[:2]
切片获取所有行,前两列
print(“前两列:\n”, arr2d[:, :2])
切片获取子矩阵:第一行到第二行(不含),第一列到第二列(不含)
print(“子矩阵:\n”, arr2d[:2, :2])
切片获取子矩阵:从索引 1 的行到索引 2 的行,从索引 1 的列到索引 2 的列
print(“另一个子矩阵:\n”, arr2d[1:3, 1:3])
切片时使用步长
print(“隔行取:\n”, arr2d[::2, :])
print(“隔列取:\n”, arr2d[:, ::2])
“`
重要提示: NumPy 数组的切片是原始数组的 视图(view),而不是副本(copy)。这意味着对切片进行的修改也会反映在原始数组中。如果需要一个独立的副本,请使用 .copy()
方法。
“`python
arr_original = np.arange(10)
arr_slice = arr_original[5:8]
print(“\n原始数组:”, arr_original)
print(“切片:”, arr_slice)
修改切片
arr_slice[0] = 99
print(“修改切片后 – 原始数组:”, arr_original) # 原始数组也变了!
print(“修改切片后 – 切片:”, arr_slice)
创建一个副本
arr_copy = arr_original.copy()
print(“\n创建副本:”, arr_copy)
修改副本
arr_copy[0] = 100
print(“修改副本后 – 原始数组:”, arr_original) # 原始数组不受影响
print(“修改副本后 – 副本:”, arr_copy)
“`
理解视图和副本对于避免程序中不期望的副作用至关重要。
4.3 花式索引 (Fancy Indexing) 和布尔索引 (Boolean Indexing)
除了基本索引和切片,NumPy 还提供了更灵活的高级索引方法。
花式索引: 使用一个整数数组来指定要访问的元素或行/列的索引。这将返回一个包含指定元素的 副本。
“`python
arr = np.arange(100).reshape(10, 10)
print(“\n原数组 (10×10):\n”, arr)
获取指定行 (索引 0, 2, 5)
rows = arr[[0, 2, 5]]
print(“\n获取指定行:\n”, rows)
获取指定列 (索引 1, 4, 8)
cols = arr[:, [1, 4, 8]]
print(“获取指定列:\n”, cols)
获取指定位置的元素 (索引对: (0, 1), (2, 4), (5, 8))
注意:这会创建一个一维数组,包含这些特定位置的元素
elements = arr[[0, 2, 5], [1, 4, 8]]
print(“获取指定位置元素:”, elements)
如果你想获取由这些行和列交叉形成的子数组,你需要使用切片和索引的组合,或者更复杂的花式索引技巧,但上面的例子是花式索引的基本用法。
例如,获取第 0, 2, 5 行,和第 1, 4 列交叉的子数组
sub_array = arr[[0, 2, 5]][:, [1, 4]]
print(“获取指定行和指定列交叉的子数组:\n”, sub_array)
“`
布尔索引 (掩码索引): 使用一个与数组形状相同的布尔数组来选择元素。布尔数组中为 True
的位置对应的元素会被选中。这通常用于根据条件过滤数组元素,返回的是一个一维数组(即便原数组是多维的),包含所有满足条件的元素的 副本。
“`python
arr = np.arange(10)
print(“\n原数组:”, arr)
创建一个布尔数组(掩码)
mask = arr > 5
print(“布尔掩码:”, mask)
使用布尔掩码选择元素
selected_elements = arr[mask]
print(“大于 5 的元素:”, selected_elements)
可以直接在索引位置使用条件表达式
selected_elements_direct = arr[arr % 2 == 0]
print(“偶数元素:”, selected_elements_direct)
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(“\n二维数组:\n”, arr2d)
找到所有大于 5 的元素
mask2d = arr2d > 5
print(“二维布尔掩码:\n”, mask2d)
selected_elements2d = arr2d[mask2d]
print(“二维数组中大于 5 的元素:”, selected_elements2d) # 结果是一维数组
“`
布尔索引是 NumPy 中非常强大的数据过滤方式。
5. 数组运算
NumPy 数组支持各种各样的运算,包括元素级运算、广播机制以及矩阵运算。
5.1 元素级运算
NumPy 的核心优势之一是其对数组的向量化操作。基本的数学运算符(+
, -
, *
, /
, **
等)以及比较运算符(>
, <
, ==
, !=
, >=
, <=
)都可以在数组上执行元素级运算。这意味着运算会独立地应用于数组中的每个元素。
“`python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
加法
print(“加法:”, arr1 + arr2) # [1+4, 2+5, 3+6] -> [5 7 9]
减法
print(“减法:”, arr1 – arr2) # [1-4, 2-5, 3-6] -> [-3 -3 -3]
乘法 (注意:这是元素级乘法,不是矩阵乘法)
print(“乘法:”, arr1 * arr2) # [14, 25, 3*6] -> [4 10 18]
除法
print(“除法:”, arr1 / arr2) # [1/4, 2/5, 3/6] -> [0.25 0.4 0.5 ]
指数
print(“指数:”, arr1 ** 2) # [1^2, 2^2, 3^2] -> [1 4 9]
余数
print(“余数:”, arr2 % arr1) # [4%1, 5%2, 6%3] -> [0 1 0]
比较运算
print(“arr1 > 2:”, arr1 > 2) # [False False True]
print(“arr1 == arr2:”, arr1 == arr2) # [False False False]
“`
NumPy 还提供了对应的函数版本,这些函数被称为 通用函数 (Universal Functions 或 ufuncs)。Ufuncs 是对 NumPy 数组进行快速元素级操作的函数。
python
print("np.add(arr1, arr2):", np.add(arr1, arr2))
print("np.sqrt(arr1):", np.sqrt(arr1))
print("np.exp(arr1):", np.exp(arr1))
print("np.sin(arr1):", np.sin(arr1))
print("np.maximum(arr1, arr2):", np.maximum(arr1, arr2)) # 元素级取最大值
5.2 广播 (Broadcasting)
广播是 NumPy 的一个强大机制,它允许 NumPy 在执行算术运算时,对不同形状的数组进行处理。当两个数组的形状不同时,NumPy 会尝试根据一组广播规则来“扩展”较小数组的形状,使其与较大数组的形状兼容,然后再执行元素级运算。
广播规则:
- 如果两个数组的维数不同,维数较小的数组会在前面填充形状为 1 的维度,直到它们的维数相同。
- 从后往前比较数组的形状(维度):
- 如果两个维度大小相同,或者其中一个维度的大小为 1,则它们是兼容的。
- 如果一个维度大小为 1,另一个大于 1,则大小为 1 的维度会被“广播”到与另一个维度相同的大小。
- 如果维度大小不同且都不为 1,则广播会失败,抛出错误。
理解广播可以通过一些例子:
数组与标量相加:
python
arr = np.array([1, 2, 3])
scalar = 5
print("数组 + 标量:", arr + scalar) # 标量 5 被广播到数组 [5, 5, 5]
一维数组与二维数组相加:
这取决于它们的形状是否兼容。
“`python
arr2d = np.array([[1, 2, 3], [4, 5, 6]]) # 形状 (2, 3)
arr1d = np.array([10, 20, 30]) # 形状 (3,)
print(“二维数组 + 一维数组:\n”, arr2d + arr1d)
arr1d 的形状 (3,) 被广播为 (1, 3),然后这个 (1, 3) 再被广播到 (2, 3)
相当于 [[10, 20, 30], [10, 20, 30]]
然后执行元素级加法:
[[1, 2, 3] + [[10, 20, 30] = [[1+10, 2+20, 3+30] = [[11 22 33]
[4, 5, 6]] [10, 20, 30]] [4+10, 5+20, 6+30]] [14 25 36]]
再看一个例子
arr2d_b = np.array([[1, 2], [3, 4], [5, 6]]) # 形状 (3, 2)
arr1d_b = np.array([10, 20]) # 形状 (2,)
print(“\n另一个二维数组 + 一维数组:\n”, arr2d_b + arr1d_b)
arr1d_b 的形状 (2,) 被广播为 (1, 2),然后这个 (1, 2) 再被广播到 (3, 2)
相当于 [[10, 20], [10, 20], [10, 20]]
然后执行元素级加法:
[[1, 2] + [[10, 20] = [[1+10, 2+20] = [[11 22]
[3, 4] [10, 20] [3+10, 4+20] [13 24]
[5, 6]] [10, 20]] [5+10, 6+20]] [15 26]]
形状不兼容的例子
arr2d_c = np.array([[1, 2], [3, 4]]) # 形状 (2, 2)
arr1d_c = np.array([10, 20, 30]) # 形状 (3,)
print(arr2d_c + arr1d_c) # 这会引发 ValueError: operands could not be broadcast together with shapes (2,2) (3,)
“`
理解广播规则对于高效使用 NumPy 进行数组运算非常重要。
5.3 矩阵/线性代数运算
NumPy 的 np.dot()
函数或 @
运算符用于执行矩阵乘法(点积)。这与前面提到的元素级乘法 *
不同。
“`python
matrix_a = np.array([[1, 2], [3, 4]]) # 形状 (2, 2)
matrix_b = np.array([[5, 6], [7, 8]]) # 形状 (2, 2)
矩阵乘法(使用 @ 运算符,Python 3.5+)
matrix_product = matrix_a @ matrix_b
print(“矩阵乘法 (@):\n”, matrix_product)
等价于 np.dot()
matrix_product_dot = np.dot(matrix_a, matrix_b)
print(“矩阵乘法 (np.dot):\n”, matrix_product_dot)
元素级乘法回顾
element_wise_product = matrix_a * matrix_b
print(“元素级乘法 (*):\n”, element_wise_product)
向量点积 (两个一维数组的点积就是对应元素乘积之和)
vec1 = np.array([1, 2])
vec2 = np.array([3, 4])
dot_product = np.dot(vec1, vec2) # 13 + 24 = 3 + 8 = 11
print(“\n向量点积:”, dot_product)
“`
NumPy 的 np.linalg
模块提供了更多线性代数功能,如计算逆矩阵、行列式、特征值等。
“`python
matrix = np.array([[1, 2], [3, 4]])
计算逆矩阵
try:
inverse_matrix = np.linalg.inv(matrix)
print(“\n逆矩阵:\n”, inverse_matrix)
# 验证:矩阵乘以其逆矩阵应得到单位矩阵
print(“矩阵 * 逆矩阵:\n”, matrix @ inverse_matrix)
except np.linalg.LinAlgError:
print(“\n矩阵是奇异的,无法计算逆矩阵。”)
计算行列式
determinant = np.linalg.det(matrix)
print(“行列式:”, determinant)
“`
6. 数组操作和重塑
NumPy 提供了多种函数来改变数组的形状、合并数组、分割数组等。
6.1 重塑 (Reshaping)
reshape()
方法允许你在不改变数组数据的情况下改变其形状。新的形状必须与原始数组的元素总数兼容。
“`python
arr = np.arange(12)
print(“原数组:”, arr) # 形状 (12,)
将一维数组重塑为 3×4 的二维数组
reshaped_arr = arr.reshape((3, 4))
print(“\n重塑为 3×4:\n”, reshaped_arr)
将数组重塑为 4×3
reshaped_arr2 = arr.reshape((4, 3))
print(“重塑为 4×3:\n”, reshaped_arr2)
可以使用 -1 让 NumPy 自动计算一个维度的大小
reshaped_arr3 = arr.reshape((2, -1)) # NumPy 会计算出第二个维度是 12 / 2 = 6
print(“重塑为 2x?:\n”, reshaped_arr3)
reshaped_arr4 = arr.reshape((-1, 3)) # NumPy 会计算出第一个维度是 12 / 3 = 4
print(“重塑为 ?x3:\n”, reshaped_arr4)
重塑为三维数组
reshaped_arr5 = arr.reshape((2, 2, 3))
print(“重塑为 2x2x3:\n”, reshaped_arr5)
“`
ravel()
或 flatten()
可以将多维数组展平(转换为一维数组)。ravel()
返回一个视图(如果可能),而 flatten()
总是返回一个副本。
“`python
arr2d = np.arange(12).reshape(3, 4)
print(“\n原二维数组:\n”, arr2d)
raveled_arr = arr2d.ravel() # 返回视图(如果可能)
print(“展平 (ravel):\n”, raveled_arr)
flattened_arr = arr2d.flatten() # 返回副本
print(“展平 (flatten):\n”, flattened_arr)
修改 ravel() 返回的视图会影响原数组
raveled_arr[0] = 999
print(“修改 ravel() 后的原数组:\n”, arr2d)
修改 flatten() 返回的副本不会影响原数组
flattened_arr[0] = 888
print(“修改 flatten() 后的原数组:\n”, arr2d) # 原数组不变
“`
6.2 转置 (Transposing)
transpose()
方法或 .T
属性用于转置数组(交换行列)。
“`python
arr = np.arange(6).reshape(2, 3)
print(“\n原数组:\n”, arr)
转置
transposed_arr = arr.transpose()
print(“转置:\n”, transposed_arr)
使用 .T 属性
transposed_arr_T = arr.T
print(“转置 (.T):\n”, transposed_arr_T)
“`
对于更高维度的数组,transpose()
可以接受一个轴的顺序元组来重新排列轴。
6.3 合并 (Concatenating/Stacking)
可以使用 np.concatenate()
或更方便的 np.vstack()
(row_stack
) 和 np.hstack()
(column_stack
) 来合并多个数组。
np.concatenate()
允许你指定沿哪个轴进行合并。
“`python
arr1 = np.array([[1, 2], [3, 4]]) # 形状 (2, 2)
arr2 = np.array([[5, 6], [7, 8]]) # 形状 (2, 2)
沿轴 0 合并(垂直堆叠,像 stacking rows)
concatenated_axis0 = np.concatenate([arr1, arr2], axis=0)
print(“\n沿轴 0 合并:\n”, concatenated_axis0)
结果形状 (4, 2)
沿轴 1 合并(水平堆叠,像 stacking columns)
concatenated_axis1 = np.concatenate([arr1, arr2], axis=1)
print(“沿轴 1 合并:\n”, concatenated_axis1)
结果形状 (2, 4)
“`
np.vstack()
和 np.hstack()
是 np.concatenate()
的特殊情况,更直观:
np.vstack()
: 垂直堆叠(沿轴 0 合并)。np.hstack()
: 水平堆叠(沿轴 1 合并)。
“`python
vstacked_arr = np.vstack([arr1, arr2])
print(“\n垂直堆叠 (vstack):\n”, vstacked_arr)
hstacked_arr = np.hstack([arr1, arr2])
print(“水平堆叠 (hstack):\n”, hstacked_arr)
注意:vstack 和 hstack 对一维数组的处理
arr_1d_a = np.array([1, 2])
arr_1d_b = np.array([3, 4])
vstacked_1d = np.vstack([arr_1d_a, arr_1d_b])
print(“\n垂直堆叠一维数组:\n”, vstacked_1d) # 将一维数组视为一行,堆叠后变成二维 (2, 2)
hstacked_1d = np.hstack([arr_1d_a, arr_1d_b])
print(“水平堆叠一维数组:\n”, hstacked_1d) # 将一维数组连接起来,仍然是一维 (4,)
“`
np.stack()
可以在新创建的轴上堆叠数组。
“`python
arr1 = np.array([1, 2])
arr2 = np.array([3, 4])
stacked_arr = np.stack([arr1, arr2], axis=0) # 沿新轴 0 堆叠
print(“\nnp.stack (axis=0):\n”, stacked_arr) # 结果形状 (2, 2)
stacked_arr_axis1 = np.stack([arr1, arr2], axis=1) # 沿新轴 1 堆叠
print(“np.stack (axis=1):\n”, stacked_arr_axis1) # 结果形状 (2, 2)
“`
6.4 分割 (Splitting)
np.split()
, np.vsplit()
, np.hsplit()
用于将数组分割成多个较小的数组。
np.split(arr, indices_or_sections, axis)
: 按指定轴和索引或段数分割。np.vsplit(arr, indices_or_sections)
: 沿垂直方向(轴 0)分割。np.hsplit(arr, indices_or_sections)
: 沿水平方向(轴 1)分割。
“`python
arr = np.arange(16).reshape(4, 4)
print(“\n原数组:\n”, arr)
垂直分割成 2 等份
v_split = np.vsplit(arr, 2)
print(“\n垂直分割 (2 份):\n”, v_split) # 结果是包含两个数组的列表
垂直分割,在索引 1 和 3 之后分割
v_split_indices = np.vsplit(arr, [1, 3])
print(“垂直分割 (索引 [1, 3]):\n”, v_split_indices)
水平分割成 4 等份
h_split = np.hsplit(arr, 4)
print(“水平分割 (4 份):\n”, h_split) # 结果是包含四个数组的列表
水平分割,在索引 1 和 3 之后分割
h_split_indices = np.hsplit(arr, [1, 3])
print(“水平分割 (索引 [1, 3]):\n”, h_split_indices)
“`
7. 聚合函数 (Aggregation Functions)
NumPy 提供了许多用于计算数组统计量的聚合函数,例如求和、求平均值、找到最大/最小值等。这些函数也可以通过数组的方法来调用。
“`python
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(“原数组:\n”, arr)
所有元素的总和
total_sum = np.sum(arr) # 或 arr.sum()
print(“\n所有元素总和:”, total_sum)
平均值
mean_value = np.mean(arr) # 或 arr.mean()
print(“所有元素平均值:”, mean_value)
最大值和最小值
max_value = np.max(arr) # 或 arr.max()
min_value = np.min(arr) # 或 arr.min()
print(“最大值:”, max_value)
print(“最小值:”, min_value)
找到最大值和最小值的索引
argmax_index = np.argmax(arr) # 或 arr.argmax()
argmin_index = np.argmin(arr) # 或 arr.argmin()
print(“最大值展平后的索引:”, argmax_index) # 展平后索引 5
print(“最小值展平后的索引:”, argmin_index) # 展平后索引 0
标准差和方差
std_dev = np.std(arr) # 或 arr.std()
variance = np.var(arr) # 或 arr.var()
print(“标准差:”, std_dev)
print(“方差:”, variance)
“`
沿着轴进行计算:
聚合函数通常有一个 axis
参数,允许你在特定维度上进行计算。
axis=0
:沿垂直方向(行)操作,对每一列进行计算。结果的维数会减少一维。axis=1
:沿水平方向(列)操作,对每一行进行计算。结果的维数会减少一维。
“`python
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(“\n原数组:\n”, arr)
沿轴 0 求和 (每列的和)
sum_axis0 = np.sum(arr, axis=0) # 或 arr.sum(axis=0)
print(“\n沿轴 0 求和 (每列):”, sum_axis0) # [1+4, 2+5, 3+6] -> [5 7 9]
沿轴 1 求和 (每行的和)
sum_axis1 = np.sum(arr, axis=1) # 或 arr.sum(axis=1)
print(“沿轴 1 求和 (每行):”, sum_axis1) # [1+2+3, 4+5+6] -> [6 15]
沿轴 0 求平均值 (每列的平均值)
mean_axis0 = np.mean(arr, axis=0) # 或 arr.mean(axis=0)
print(“沿轴 0 平均值:”, mean_axis0) # [(1+4)/2, (2+5)/2, (3+6)/2] -> [2.5 3.5 4.5]
沿轴 1 最大值 (每行的最大值)
max_axis1 = np.max(arr, axis=1) # 或 arr.max(axis=1)
print(“沿轴 1 最大值:”, max_axis1) # [max(1,2,3), max(4,5,6)] -> [3 6]
“`
理解 axis
参数对于处理多维数据集至关重要,例如在数据表中计算每列的平均值或每行的总和。
8. 文件输入/输出 (File I/O)
NumPy 可以方便地将数组保存到文件或从文件加载。
np.save(filename, array)
: 将单个数组保存为.npy
格式(NumPy 专用的二进制格式)。np.load(filename)
: 从.npy
文件加载数组。np.savez(filename, **kwds)
: 将多个数组保存为.npz
格式(一个包含多个.npy
文件的压缩包)。np.load(filename)
: 从.npz
文件加载多个数组(返回一个字典状对象)。np.savetxt(filename, array, delimiter=' ')
: 将数组保存为文本文件。np.loadtxt(filename, delimiter=None)
: 从文本文件加载数据。
“`python
arr = np.arange(10).reshape(2, 5)
print(“\n要保存的数组:\n”, arr)
保存为 .npy 文件
np.save(‘my_array.npy’, arr)
print(“数组已保存到 my_array.npy”)
从 .npy 文件加载
loaded_arr = np.load(‘my_array.npy’)
print(“从 my_array.npy 加载的数组:\n”, loaded_arr)
保存多个数组为 .npz 文件
arr1 = np.array([1, 2, 3])
arr2 = np.random.rand(2, 2)
np.savez(‘multiple_arrays.npz’, array1=arr1, array2=arr2)
print(“\n多个数组已保存到 multiple_arrays.npz”)
从 .npz 文件加载
loaded_npz = np.load(‘multiple_arrays.npz’)
print(“从 multiple_arrays.npz 加载的数组:”)
print(“array1:”, loaded_npz[‘array1’])
print(“array2:\n”, loaded_npz[‘array2’])
loaded_npz 是一个字典状对象,可以使用 .keys() 查看包含的数组名称
print(“包含的数组名称:”, loaded_npz.keys())
保存为文本文件 (例如 CSV)
np.savetxt(‘my_array.csv’, arr, delimiter=’,’)
print(“\n数组已保存到 my_array.csv (文本格式)”)
从文本文件加载
loaded_txt = np.loadtxt(‘my_array.csv’, delimiter=’,’)
print(“从 my_array.csv 加载的数组:\n”, loaded_txt)
清理生成的文件 (可选)
import os
os.remove(‘my_array.npy’)
os.remove(‘multiple_arrays.npz’)
os.remove(‘my_array.csv’)
“`
np.savetxt
和 np.loadtxt
对于导入/导出标准的表格数据非常方便。
9. NumPy 与其他库的结合
如前所述,NumPy 是 Python 数据科学栈的基础。许多其他库都接受 NumPy 数组作为输入或返回 NumPy 数组作为输出。
- Pandas: Pandas 的 DataFrame 对象内部使用了 NumPy 数组来存储数据。Pandas 提供了更高级的数据结构(如 Series 和 DataFrame)和数据分析工具,但其性能和功能依赖于底层的 NumPy 实现。你可以轻松地在 Pandas DataFrame 和 NumPy 数组之间转换。
- Matplotlib: 这个流行的绘图库可以接收 NumPy 数组作为绘图数据。
- SciPy: SciPy 提供了更高级的科学计算模块,如优化、积分、插值、信号处理等。SciPy 的许多功能都基于 NumPy 数组。
- Scikit-learn: 机器学习库 Scikit-learn 主要使用 NumPy 数组作为其算法的输入数据结构。
- 深度学习框架 (TensorFlow, PyTorch): 这些框架使用自己的张量(Tensor)对象,但这些张量对象与 NumPy 数组概念非常相似,并且通常可以方便地与 NumPy 数组进行转换。
这意味着你学习的 NumPy 技能可以直接应用于这些更高级的库,为你打开更广阔的数据科学应用领域。
10. 学习建议和最佳实践
- 理解
ndarray
的核心概念: 它是同构的、多维的。这是 NumPy 所有功能的基础。 - 掌握索引和切片: 这是访问和操作数组数据的基本技能。理解视图和副本的区别非常重要。
- 拥抱向量化运算和广播: 尽量使用 NumPy 提供的向量化函数和运算符进行数组运算,而不是使用 Python 循环。理解广播规则能帮助你更灵活地组合不同形状的数组进行计算。
- 多练习使用
axis
参数: 在聚合函数、合并、分割等操作中,axis
参数控制操作的方向,理解它对于处理多维数据至关重要。 - 查阅官方文档: NumPy 的官方文档(numpy.org)非常详细和全面,是学习和解决问题的宝库。遇到不清楚的地方,查阅文档是最好的方式。
- 动手实践: 理论知识很重要,但最好的学习方式是编写代码、运行代码、修改代码。尝试用 NumPy 解决一些简单的数据处理或数学问题。
- 阅读他人代码: 查看使用 NumPy 的开源项目或教程代码,学习其他人是如何高效使用 NumPy 的。
11. 进阶之路
当你熟练掌握了 NumPy 的基本功能后,可以进一步探索更高级的话题:
- 通用函数 (Ufuncs) 的更多细节: 了解 Ufuncs 的各种方法(如
reduce
,accumulate
,outer
)。 - 结构化数组 (Structured Arrays): 创建包含不同数据类型字段的数组,类似于 C 语言中的结构体或数据库中的行。
- 掩码数组 (Masked Arrays): 处理包含无效或缺失数据的数组。
- 更复杂的线性代数 (
np.linalg
): 特征值分解、奇异值分解等。 - 傅里叶变换 (
np.fft
): 信号处理常用。 - 随机数 (
np.random
) 模块的高级用法: 各种概率分布的采样。 - 性能优化: 了解 NumPy 内部工作原理,如何进一步优化代码。
12. 总结
NumPy 是 Python 数据科学的基石,它以其高性能、内存效率和丰富的功能,为处理数值数据提供了强大的支持。通过本文的学习,你已经了解了 NumPy 的核心概念 ndarray
,学会了如何创建数组、访问元素、进行基本运算、处理数组形状以及执行聚合操作。
学习 NumPy 是掌握更高级数据科学工具(如 Pandas、Scikit-learn)的必经之路。不断练习,将 NumPy 的思想融入你的编程习惯中,你会发现它将极大地提高你的数据处理能力和效率。
现在,是时候打开你的 Python 环境,亲自敲下那些代码,感受 NumPy 的力量了!祝你在 NumPy 的学习旅程中一切顺利!