文章目录
创作不易,请各位看官顺手点点关注,不胜感激 。
作为数据科学的基石,NumPy (Numerical Python) 是 Python 中处理数值计算最重要的库。它提供了强大的多维数组对象 ndarray (N-dimensional array),以及用于操作这些数组的工具。理解 NumPy 和 ndarray 是进行高效数据分析、科学计算以及机器学习的基础。
1. 为什么需要 NumPy?
Python 列表 (List) 固然灵活,但在处理大量数值数据时,存在显著的性能瓶颈:
- 性能低下:Python 列表是异构的,可以存储不同类型的数据。这意味着每个元素都需要单独存储其类型信息和值,导致内存占用大,且操作时无法进行连续内存访问优化。
- 功能有限:Python 列表不直接支持像向量加法、矩阵乘法等高级数学运算。你需要编写循环来实现这些操作,这不仅繁琐,而且效率低下。
NumPy 的出现正是为了解决这些问题:
- 高效存储:
ndarray对象将所有元素存储在连续的内存块中,且所有元素必须是同一数据类型,这极大地提高了内存利用率和访问速度。 - 强大的数学运算能力:NumPy 底层由 C 和 Fortran 实现,提供了高度优化的函数,可以对整个数组进行批量操作 (向量化操作),避免了显式的 Python 循环,从而大幅提升计算速度。
- 广播机制 (Broadcasting):NumPy 能够对形状不同的数组执行算术运算,这在处理多维数据时非常方便。
简而言之,NumPy 是 Python 进行高性能科学计算的事实标准。
2. ndarray 对象:NumPy 的核心
ndarray 是 NumPy 库的核心数据结构,它是一个多维同类型数组。
2.1 ndarray 的重要属性
一个 ndarray 对象包含以下重要属性:
ndim: 数组的维数(轴的数量,axes)。shape: 一个元组,表示数组在每个维度上的大小。例如,一个 2x3 的矩阵shape为(2, 3)。size: 数组中元素的总个数,等于shape中所有元素的乘积。dtype: 数组中元素的类型。NumPy 支持多种数据类型,如int32,float64,bool,complex等。所有元素的数据类型必须相同。itemsize: 数组中每个元素占用的字节数。data: 包含实际数组元素的缓冲区。通常不直接使用。
2.2 创建 ndarray
创建 ndarray 的方式有多种:
-
从 Python 列表或元组创建:
np.array()是最常用的方法。import numpy as np # 从列表创建一维数组 arr1d = np.array([1, 2, 3, 4, 5]) print("一维数组:", arr1d) print("维数:", arr1d.ndim) # 1 print("形状:", arr1d.shape) # (5,) print("元素类型:", arr1d.dtype) # int64 (或根据系统而定) # 从嵌套列表创建二维数组 (矩阵) arr2d = np.array([[1, 2, 3], [4, 5, 6]]) print("\n二维数组:\n", arr2d) print("维数:", arr2d.ndim) # 2 print("形状:", arr2d.shape) # (2, 3) print("元素总数:", arr2d.size) # 6 # 创建时指定数据类型 arr_float = np.array([1, 2, 3], dtype=np.float32) print("\n指定 float32 类型数组:", arr_float) print("元素类型:", arr_float.dtype) # float32 -
使用内置函数创建特定形状的数组:
np.zeros(shape): 创建指定形状的全零数组。np.ones(shape): 创建指定形状的全一数组。np.empty(shape): 创建指定形状的空数组(元素值随机,取决于内存状态)。np.full(shape, fill_value): 创建指定形状并填充指定值的数组。np.eye(N): 创建 NxN 的单位矩阵。np.identity(N): 同np.eye(N)。
# 全零数组 zeros_arr = np.zeros((2, 4)) print("\n全零数组:\n", zeros_arr) # 全一数组 ones_arr = np.ones((3, 2), dtype=np.int8) print("\n全一数组 (int8):\n", ones_arr) # 空数组 empty_arr = np.empty((2, 2)) print("\n空数组 (随机值):\n", empty_arr) # 每次运行可能不同 # 填充特定值的数组 full_arr = np.full((3, 3), 7) print("\n填充 7 的数组:\n", full_arr) # 单位矩阵 identity_matrix = np.eye(3) print("\n3x3 单位矩阵:\n", identity_matrix) -
使用序列生成函数创建:
np.arange(start, stop, step): 类似于 Python 的range(),返回一个数组。np.linspace(start, stop, num): 在指定区间内返回均匀分布的num个数字。np.logspace(start, stop, num): 在对数刻度上生成均匀间隔的数字。
# arange arr_range = np.arange(0, 10, 2) # [0, 2, 4, 6, 8] print("\narange:", arr_range) # linspace arr_linspace = np.linspace(0, 1, 5) # 包含 start 和 stop print("\nlinspace (0到1之间5个均匀分布的数):", arr_linspace) -
随机数生成:
numpy.random模块提供了各种生成随机数的函数。np.random.rand(d0, d1, ...): 生成指定形状的 [0, 1) 区间内的浮点数。np.random.randn(d0, d1, ...): 生成标准正态分布 (均值为0,方差为1) 的浮点数。np.random.randint(low, high, size): 生成指定范围内的随机整数。np.random.normal(loc, scale, size): 生成指定均值和标准差的正态分布随机数。
# [0, 1) 均匀分布随机数 rand_arr = np.random.rand(2, 3) print("\n随机 (0-1) 数组:\n", rand_arr) # 标准正态分布随机数 randn_arr = np.random.randn(2, 2) print("\n标准正态分布随机数组:\n", randn_arr) # 1到10之间的随机整数 (5个) randint_arr = np.random.randint(1, 11, size=5) print("\n随机整数数组 (1-10):", randint_arr)
3. ndarray 的索引和切片
NumPy 数组的索引和切片与 Python 列表类似,但更强大,支持多维索引。
3.1 一维数组索引和切片
与 Python 列表完全相同。
arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print("\n原始一维数组:", arr)
print("第一个元素:", arr[0]) # 0
print("第三个到第五个元素:", arr[2:5]) # [2 3 4]
print("从索引5开始到结尾:", arr[5:]) # [5 6 7 8 9]
print("步长为2:", arr[::2]) # [0 2 4 6 8]
print("反转数组:", arr[::-1]) # [9 8 7 6 5 4 3 2 1 0]
3.2 多维数组索引和切片
多维数组的索引和切片使用逗号 , 分隔每个维度的索引。
- 基本索引:
arr[row_index, col_index] - 切片:
arr[row_slice, col_slice]
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("\n原始二维数组:\n", arr2d)
# 访问单个元素 (第1行第2列,索引从0开始)
print("arr2d[0, 1]:", arr2d[0, 1]) # 2
# 访问整行 (第1行)
print("arr2d[0, :]:", arr2d[0, :]) # [1 2 3] (等同于 arr2d[0])
# 访问整列 (第2列)
print("arr2d[:, 1]:", arr2d[:, 1]) # [2 5 8]
# 访问子矩阵 (前两行,后两列)
print("arr2d[:2, 1:]:\n", arr2d[:2, 1:]) # [[2 3], [5 6]]
# 混合索引 (选择第0行和第2行,所有列)
print("arr2d[[0, 2], :]:\n", arr2d[[0, 2], :]) # [[1 2 3], [7 8 9]]
3.3 布尔索引 (Boolean Indexing)
使用布尔数组来选择元素,非常强大。
arr = np.array([10, 20, 30, 40, 50])
# 选出大于 30 的元素
mask = arr > 30
print("\n布尔掩码:", mask) # [False False False True True]
print("大于 30 的元素:", arr[mask]) # [40 50]
# 或者直接写条件
print("等于 20 的元素:", arr[arr == 20]) # [20]
3.4 花式索引 (Fancy Indexing)
使用整数数组来选择任意行或列。
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
print("\n原始二维数组:\n", arr2d)
# 选择第0行、第2行、第1行
print("花式索引选择行 (0, 2, 1):\n", arr2d[[0, 2, 1]])
# 选择第0行和第2行,然后从这些行中选择第1列和第0列
# 注意:这会返回 (len(rows), len(cols)) 形状的数组
print("花式索引选择特定元素 arr2d[[0, 2], [1, 0]]:", arr2d[[0, 2], [1, 0]]) # [2 7] (arr2d[0,1] 和 arr2d[2,0])
4. ndarray 的形状操作
改变数组的形状而不改变其数据。
reshape(shape): 返回一个具有新形状的数组视图(可能不是副本)。resize(shape): 直接修改数组的形状。flatten(): 返回一个一维数组的副本。ravel(): 返回一个一维数组的视图(或副本),通常优先于flatten()。transpose()或T: 数组转置。np.newaxis: 用于增加数组的维度。
arr = np.arange(12)
print("\n原始数组:", arr)
# reshape: 从一维变二维
arr_reshaped = arr.reshape((3, 4))
print("reshape (3x4):\n", arr_reshaped)
# 转置
arr_T = arr_reshaped.T
print("转置:\n", arr_T)
# flatten vs ravel
arr_flat = arr_reshaped.flatten() # 副本
arr_ravel = arr_reshaped.ravel() # 视图
print("flatten:", arr_flat)
print("ravel:", arr_ravel)
# 增加维度 (例如,将一维数组变为二维列向量)
arr_col = arr[:5, np.newaxis] # 或者 arr[:5].reshape(-1, 1)
print("增加维度 (列向量):\n", arr_col)
print("列向量形状:", arr_col.shape) # (5, 1)
# 增加维度 (行向量)
arr_row = arr[np.newaxis, :5] # 或者 arr[:5].reshape(1, -1)
print("增加维度 (行向量):\n", arr_row)
print("行向量形状:", arr_row.shape) # (1, 5)
5. ndarray 的基本运算
NumPy 的强大之处在于其向量化操作,可以直接对整个数组进行元素级的运算,无需显式循环。
5.1 元素级运算
对数组的每个元素执行相同的操作。
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
# 加法
print("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) # [2 4 6]
print("arr1 + 10:", arr1 + 10) # [11 12 13]
# 比较运算
print("arr1 > arr2:", arr1 > arr2) # [False False False]
print("arr1 == arr1:", arr1 == arr1) # [True True True]
5.2 矩阵运算
NumPy 提供了专门的函数进行矩阵运算。
np.dot(a, b)或a @ b(Python 3.5+):矩阵乘法或点积。
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print("\n矩阵 A:\n", A)
print("矩阵 B:\n", B)
# 矩阵乘法
C_dot = np.dot(A, B)
C_at = A @ B
print("矩阵乘法 (A @ B):\n", C_at)
# 元素级乘法 (注意与矩阵乘法的区别)
C_elem_wise = A * B
print("元素级乘法 (A * B):\n", C_elem_wise)
5.3 广播 (Broadcasting)
广播是 NumPy 在执行不同形状数组之间的算术运算时的一种强大机制。当数组的形状不完全匹配时,NumPy 会尝试自动扩展较小的数组以适应较大的数组。
广播规则:
- 如果两个数组的维度数不同,那么将维度较小的数组的形状在前面填充 1,直到它们的维度数相等。
- 在任何一个维度上,如果两个数组的长度相等,或者其中一个数组的长度为 1,那么在这个维度上就兼容。
- 如果两个数组在所有维度上都兼容,它们就能广播。
- 广播之后,每个维度上的大小将是两个输入数组在该维度上的大小的最大值。
matrix = np.array([[1, 2, 3], [4, 5, 6]]) # 形状 (2, 3)
vector = np.array([10, 20, 30]) # 形状 (3,)
# vector 会被广播成 [[10, 20, 30], [10, 20, 30]]
result = matrix + vector
print("\n广播示例 (矩阵 + 向量):\n", result)
scalar = 5
# scalar 会被广播成与 matrix 相同形状的数组
result2 = matrix * scalar
print("广播示例 (矩阵 * 标量):\n", result2)
# 另一个广播示例 (列向量与行向量)
col_vec = np.array([[10], [20]]) # 形状 (2, 1)
row_vec = np.array([1, 2, 3]) # 形状 (3,)
# 结果形状 (2, 3)
# col_vec 广播为 [[10, 10, 10], [20, 20, 20]]
# row_vec 广播为 [[1, 2, 3], [1, 2, 3]]
broadcast_sum = col_vec + row_vec
print("广播示例 (列向量 + 行向量):\n", broadcast_sum)
6. 聚合函数 (Aggregation Functions)
NumPy 提供了许多用于对数组进行统计计算的聚合函数。
sum(): 求和。mean(): 求平均值。median(): 求中位数。min(): 求最小值。max(): 求最大值。std(): 求标准差。var(): 求方差。argmin(),argmax(): 求最小值/最大值的索引。cumsum(): 求累积和。cumprod(): 求累积积。
这些函数可以应用于整个数组,也可以通过指定 axis 参数在特定轴上进行操作。
axis=0: 沿着列进行操作(即对每一列进行操作,结果行数为1)。axis=1: 沿着行进行操作(即对每一行进行操作,结果列数为1)。
data = np.array([[1, 2, 3], [4, 5, 6]])
print("\n原始数据:\n", data)
# 整个数组求和
print("总和:", data.sum()) # 21
# 沿着列求和 (axis=0)
print("列和:", data.sum(axis=0)) # [5 7 9]
# 沿着行求和 (axis=1)
print("行和:", data.sum(axis=1)) # [6 15]
# 平均值
print("平均值:", data.mean()) # 3.5
print("列平均值:", data.mean(axis=0)) # [2.5 3.5 4.5]
# 最大值和最小值
print("最大值:", data.max())
print("最小值:", data.min())
# 最大值的索引
print("最大值索引:", data.argmax()) # 5 (元素6的索引)
print("列最大值索引:", data.argmax(axis=0)) # [1 1 1] (每列最大值所在的行索引)
# 累积和
print("累积和:\n", data.cumsum()) # [ 1 3 6 10 15 21] (展平后)
print("列累积和:\n", data.cumsum(axis=0)) # [[1 2 3], [5 7 9]]
7. 深入理解 NumPy 的视图 (View) 与副本 (Copy)
这是 NumPy 中一个非常重要的概念,因为它会影响你对数据修改的行为。
- 视图 (View):当你对数组进行切片操作时(例如
arr[1:5]),NumPy 通常会返回原始数组的一个视图。视图和原始数组共享底层数据。这意味着如果你修改了视图中的元素,原始数组中的对应元素也会被修改,反之亦然。这提高了效率,避免了不必要的内存复制。 - 副本 (Copy):当你明确调用
copy()方法,或者进行一些会强制创建新内存空间的操作时(例如花式索引、高级数学运算的结果),NumPy 会返回一个副本。副本与原始数组的数据是独立的,修改副本不会影响原始数组。
original_arr = np.arange(10)
print("\n原始数组:", original_arr) # [0 1 2 3 4 5 6 7 8 9]
# 创建一个视图
view_arr = original_arr[2:6]
print("视图数组:", view_arr) # [2 3 4 5]
# 修改视图中的元素
view_arr[0] = 99
print("修改视图后,视图数组:", view_arr) # [99 3 4 5]
print("修改视图后,原始数组:", original_arr) # [0 1 99 4 5 6 7 8 9] (原始数组受影响)
# 创建一个副本
copy_arr = original_arr[2:6].copy()
print("\n副本数组:", copy_arr) # [99 4 5 6] (注意这里是原始数组修改后的值)
# 修改副本中的元素
copy_arr[0] = 100
print("修改副本后,副本数组:", copy_arr) # [100 4 5 6]
print("修改副本后,原始数组:", original_arr) # [0 1 99 4 5 6 7 8 9] (原始数组不受影响)
# 通过 np.array() 从现有数组创建新数组通常也是副本
another_copy = np.array(original_arr)
another_copy[0] = -1
print("\n从数组创建的副本:", another_copy)
print("原始数组 (未变):", original_arr)
理解视图和副本对于避免程序中潜在的意外行为至关重要。
总结
NumPy 是 Python 数据科学生态系统中不可或缺的库。它的 ndarray 对象提供了高效的数据存储和强大的数值运算能力,通过向量化操作、广播机制以及灵活的索引切片,极大地简化了科学计算任务。掌握 NumPy 是你迈向高级数据分析和机器学习的第一步。
练习题
尝试独立完成以下练习题,并通过答案进行对照:
-
数组创建与属性:
- 创建一个 3x3 的全零矩阵,数据类型为
float32。 - 创建一个 5 维的随机整数数组,每个维度大小为 2,整数范围在 10 到 50 之间。
- 打印这两个数组的
ndim,shape,size,dtype属性。
- 创建一个 3x3 的全零矩阵,数据类型为
-
索引与切片:
- 创建一个形状为 (4, 5) 的数组,包含从 1 到 20 的整数。
- 获取第 2 行(索引为 1)的所有元素。
- 获取所有行中第 3 列(索引为 2)的元素。
- 获取前 2 行和后 3 列组成的子矩阵。
- 使用布尔索引,选出数组中所有大于 15 的元素。
- 使用花式索引,选出第 0, 2, 3 行。
-
形状操作:
- 创建一个包含 1 到 25 的一维数组。
- 将其重塑为 5x5 的二维矩阵。
- 对这个 5x5 矩阵进行转置。
- 将其展平回一维数组。
- 创建一个新的包含 3 个元素的数组,通过
np.newaxis将其转换为列向量和行向量,并打印其形状。
-
基本运算与广播:
- 创建两个 3x3 的随机整数矩阵 A 和 B (范围 1-10)。
- 计算 A 和 B 的元素级加法、减法、乘法、除法。
- 计算 A 和 B 的矩阵乘法。
- 创建一个 3x1 的矩阵
col_vector,值为[[10], [20], [30]]。 - 计算 A 和
col_vector的加法(观察广播效果)。
-
聚合函数:
- 创建一个 4x4 的随机整数矩阵(范围 1-100)。
- 计算整个矩阵的平均值、最大值和标准差。
- 计算每列的和。
- 计算每行的最小值。
- 找出整个矩阵中最大值和最小值的索引。
练习题答案
import numpy as np
print("--- 练习题答案 ---")
# 1. 数组创建与属性
print("\n--- 练习 1: 数组创建与属性 ---")
zeros_matrix = np.zeros((3, 3), dtype=np.float32)
print("3x3 全零矩阵 (float32):\n", zeros_matrix)
print("ndim:", zeros_matrix.ndim)
print("shape:", zeros_matrix.shape)
print("size:", zeros_matrix.size)
print("dtype:", zeros_matrix.dtype)
random_5d_arr = np.random.randint(10, 51, size=(2, 2, 2, 2, 2))
print("\n5维随机整数数组 (10-50):\n", random_5d_arr)
print("ndim:", random_5d_arr.ndim)
print("shape:", random_5d_arr.shape)
print("size:", random_5d_arr.size)
print("dtype:", random_5d_arr.dtype)
# 2. 索引与切片
print("\n--- 练习 2: 索引与切片 ---")
arr_20 = np.arange(1, 21).reshape((4, 5))
print("原始数组 (4x5):\n", arr_20)
print("\n第 2 行 (索引为 1) 的所有元素:", arr_20[1, :]) # 或者 arr_20[1]
print("所有行中第 3 列 (索引为 2) 的元素:", arr_20[:, 2])
print("前 2 行和后 3 列的子矩阵:\n", arr_20[:2, 2:])
greater_than_15 = arr_20[arr_20 > 15]
print("大于 15 的元素:", greater_than_15)
selected_rows = arr_20[[0, 2, 3]]
print("选择第 0, 2, 3 行:\n", selected_rows)
# 3. 形状操作
print("\n--- 练习 3: 形状操作 ---")
arr_1_to_25 = np.arange(1, 26)
print("原始一维数组:", arr_1_to_25)
matrix_5x5 = arr_1_to_25.reshape((5, 5))
print("重塑为 5x5 矩阵:\n", matrix_5x5)
transposed_matrix = matrix_5x5.T
print("转置后的矩阵:\n", transposed_matrix)
flattened_arr = transposed_matrix.flatten() # 或 .ravel()
print("展平回一维数组:", flattened_arr)
new_arr = np.array([1, 2, 3])
col_vec = new_arr[:, np.newaxis]
row_vec = new_arr[np.newaxis, :]
print("\n原始小数组:", new_arr)
print("列向量:\n", col_vec)
print("列向量形状:", col_vec.shape)
print("行向量:\n", row_vec)
print("行向量形状:", row_vec.shape)
# 4. 基本运算与广播
print("\n--- 练习 4: 基本运算与广播 ---")
A = np.random.randint(1, 11, size=(3, 3))
B = np.random.randint(1, 11, size=(3, 3))
print("矩阵 A:\n", A)
print("矩阵 B:\n", B)
print("\nA + B (元素级加法):\n", A + B)
print("A - B (元素级减法):\n", A - B)
print("A * B (元素级乘法):\n", A * B)
print("A / B (元素级除法):\n", A / B)
print("\nA @ B (矩阵乘法):\n", A @ B)
col_vector = np.array([[10], [20], [30]])
print("\n列向量:\n", col_vector)
print("A + col_vector (广播加法):\n", A + col_vector)
# 5. 聚合函数
print("\n--- 练习 5: 聚合函数 ---")
data_matrix = np.random.randint(1, 101, size=(4, 4))
print("原始数据矩阵:\n", data_matrix)
print("\n整个矩阵的平均值:", data_matrix.mean())
print("整个矩阵的最大值:", data_matrix.max())
print("整个矩阵的标准差:", data_matrix.std())
print("\n每列的和:\n", data_matrix.sum(axis=0))
print("每行的最小值:\n", data_matrix.min(axis=1))
max_index = data_matrix.argmax()
min_index = data_matrix.argmin()
print(f"\n整个矩阵中最大值的展平索引: {max_index} (值为 {data_matrix.flatten()[max_index]})")
print(f"整个矩阵中最小值的展平索引: {min_index} (值为 {data_matrix.flatten()[min_index]})")
# 如果想获取多维索引
max_multi_index = np.unravel_index(max_index, data_matrix.shape)
min_multi_index = np.unravel_index(min_index, data_matrix.shape)
print(f"整个矩阵中最大值的多维索引: {max_multi_index}")
print(f"整个矩阵中最小值的多维索引: {min_multi_index}")
3973

被折叠的 条评论
为什么被折叠?



