Numpy
NumPy 从基础到高级完整讲解
NumPy(Numerical Python)是Python科学计算的核心库,其核心是多维数组(ndarray),提供了高效的数值计算、向量化操作、广播机制等功能,相比Python原生列表,NumPy数组在内存占用、计算速度上有数量级的提升。
本文将分「基础篇」「进阶篇」「高级篇」,结合可运行代码、数据示例和核心结论,全面讲解NumPy的使用。
一、基础篇:核心概念与基本操作
1.1 安装与导入
首先确保安装NumPy,然后导入(约定俗成用np作为别名):
# 安装(终端执行)
# pip install numpy
# 导入
import numpy as np
1.2 核心数据结构:ndarray
ndarray是NumPy的核心,是同质的多维数组(所有元素类型相同),以下是常见创建方式:
(1)从Python列表/元组创建
# 一维数组
arr1d = np.array([1, 2, 3, 4, 5])
print("一维数组:", arr1d)
# 输出:一维数组: [1 2 3 4 5]
# 二维数组(矩阵)
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("二维数组:\n", arr2d)
# 指定数据类型(dtype)
arr_float = np.array([1, 2, 3], dtype=np.float64)
print("浮点型数组:", arr_float, "dtype:", arr_float.dtype)
数据说明:
- 一维数组
arr1d:[1,2,3,4,5](int32默认) - 二维数组
arr2d:3行3列的矩阵 arr_float:强制转为float64类型
结论:
np.array()是创建数组的基础方法,支持嵌套列表创建多维数组;- dtype可指定元素类型(int8/16/32/64、float16/32/64、bool等),避免类型不一致导致的计算问题。
(2)内置函数创建数组(常用)
# 全0数组
zeros_arr = np.zeros((2, 3)) # 2行3列
print("全0数组:\n", zeros_arr)
# 全1数组
ones_arr = np.ones((3, 2), dtype=np.int32) # 3行2列,int32类型
print("全1数组:\n", ones_arr)
# 自定义填充值
full_arr = np.full((2, 2), 99) # 2行2列,填充99
print("自定义填充数组:\n", full_arr)
# 等差数列(arange:start, stop, step)
arange_arr = np.arange(0, 10, 2) # 0到10(不含),步长2
print("等差数列:", arange_arr)
# 等间隔数列(linspace:start, stop, num)
linspace_arr = np.linspace(0, 1, 5) # 0到1,生成5个等间隔数
print("等间隔数列:", linspace_arr)
# 随机数数组
np.random.seed(42) # 固定随机种子,保证结果可复现
rand_arr = np.random.rand(2, 3) # 2行3列,0-1均匀分布随机数
randn_arr = np.random.randn(2, 3) # 2行3列,标准正态分布(均值0,方差1)
randint_arr = np.random.randint(0, 10, (2, 3)) # 0-10(不含)整数随机数
print("均匀分布随机数:\n", rand_arr)
print("标准正态分布随机数:\n", randn_arr)
print("整数随机数:\n", randint_arr)
# 单位矩阵(对角线为1,其余为0)
eye_arr = np.eye(3) # 3阶单位矩阵
print("单位矩阵:\n", eye_arr)
数据说明:
- 内置函数创建的数组维度由参数
shape(元组)指定,如(2,3)表示2行3列; - 随机数数组通过
seed固定种子,确保每次运行结果一致。
结论:
- 内置函数比手动创建列表更高效,尤其适合大规模数组;
arange侧重“步长”,linspace侧重“点数”,按需选择;- 随机数生成需注意种子(seed),保证实验可复现。
1.3 ndarray的核心属性
arr = np.random.randint(0, 10, (3, 4)) # 3行4列随机整数数组
print("数组本身:\n", arr)
print("维度(ndim):", arr.ndim) # 维度数(二维=2)
print("形状(shape):", arr.shape) # 形状(行,列)=(3,4)
print("元素总数(size):", arr.size) # 3*4=12
print("数据类型(dtype):", arr.dtype) # 元素类型
print("每个元素字节数(itemsize):", arr.itemsize) # int32=4字节
数据说明:3行4列的随机整数数组(0-9)
结论:
shape是最常用的属性,可通过arr.shape = (m,n)或reshape修改数组形状;size = 各维度长度乘积,反映数组的总元素数;itemsize×size= 数组占用的总内存(字节)。
1.4 索引与切片(核心操作)
(1)基本索引(类似Python列表)
# 一维数组索引
arr1d = np.arange(10)
print("一维数组:", arr1d)
print("索引5的元素:", arr1d[5]) # 单个元素
print("索引2-7(不含):", arr1d[2:7]) # 切片
print("逆序:", arr1d[::-1]) # 步长-1,反转
# 二维数组索引(行,列)
arr2d = np.array([[1,2,3], [4,5,6], [7,8,9]])
print("二维数组:\n", arr2d)
print("第2行(索引1):", arr2d[1]) # 整行
print("第1行第2列(索引0,1):", arr2d[0,1]) # 单个元素
print("前2行,后2列:\n", arr2d[:2, 1:]) # 切片:行[:2],列[1:]
数据说明:
- 一维数组
arr1d:[0,1,2,3,4,5,6,7,8,9] - 二维数组
arr2d:3×3矩阵
结论:
- 一维数组索引/切片与Python列表完全一致;
- 二维数组索引格式为
arr[行索引, 列索引],支持切片组合(如[:2,1:]),灵活选取子数组。
(2)布尔索引(按条件筛选)
arr = np.random.randint(0, 20, (3, 4))
print("原始数组:\n", arr)
# 筛选大于10的元素
mask = arr > 10 # 布尔掩码(True/False数组)
print("布尔掩码:\n", mask)
print("大于10的元素:", arr[mask]) # 返回一维数组
# 复合条件(与:&,或:|,非:~)
mask_complex = (arr > 5) & (arr < 15)
print("5<元素<15的子数组:\n", arr[mask_complex])
数据说明:3×4随机数组(0-19),通过布尔掩码筛选元素
结论:
- 布尔索引是NumPy筛选数据的核心方式,返回满足条件的元素(一维数组);
- 复合条件需用
&(and)、|(or)、~(not),且条件需用括号包裹。
(3)花式索引(整数数组索引)
arr = np.arange(12).reshape(3, 4) # 3行4列,0-11
print("原始数组:\n", arr)
# 按行索引选取
rows = [0, 2] # 选第0行和第2行
print("选第0、2行:\n", arr[rows])
# 按行列索引选取((行索引列表, 列索引列表))
cols = [1, 3]
print("选(0,1)、(2,3)元素:", arr[rows, cols])
# 选指定行的所有列
print("选第1行和第2行的所有列:\n", arr[[1,2], :])
数据说明:3×4数组[[0,1,2,3],[4,5,6,7],[8,9,10,11]]
结论:
- 花式索引通过整数列表/数组指定索引,可任意选取离散的行/列/元素;
- 花式索引返回的是原数组的副本(而非视图),修改不会影响原数组。
二、进阶篇:数组操作与运算
2.1 数组形状操作
arr = np.arange(12) # 一维数组:[0,1,...,11]
print("原始数组:", arr, "shape:", arr.shape)
# 重塑(reshape):不改变数据,仅改变形状
arr_reshape = arr.reshape(3, 4)
print("重塑为3×4:\n", arr_reshape, "shape:", arr_reshape.shape)
# 展平(flatten):返回副本;ravel:返回视图(更高效)
arr_flatten = arr_reshape.flatten()
arr_ravel = arr_reshape.ravel()
print("flatten展平:", arr_flatten)
print("ravel展平:", arr_ravel)
# 转置(T):行列互换
arr_transpose = arr_reshape.T
print("3×4转置为4×3:\n", arr_transpose)
# 调整形状(resize):直接修改原数组
arr.resize((2, 6))
print("原数组resize为2×6:\n", arr)
数据说明:从一维数组[0-11]重塑为3×4,再转置/展平/调整形状
结论:
reshape是只读操作,不修改原数组;resize直接修改原数组;flatten返回副本(修改不影响原数组),ravel返回视图(修改影响原数组),优先用ravel提升效率;- 转置
T是二维数组常用操作,高维数组需用transpose指定轴。
2.2 数组拼接与分割
# 拼接
arr1 = np.array([[1,2], [3,4]])
arr2 = np.array([[5,6], [7,8]])
# 垂直拼接(vstack):行增加
vstack_arr = np.vstack((arr1, arr2))
print("垂直拼接:\n", vstack_arr)
# 水平拼接(hstack):列增加
hstack_arr = np.hstack((arr1, arr2))
print("水平拼接:\n", hstack_arr)
# 通用拼接(concatenate):指定轴(axis)
concat_axis0 = np.concatenate((arr1, arr2), axis=0) # 等价vstack
concat_axis1 = np.concatenate((arr1, arr2), axis=1) # 等价hstack
print("axis=0拼接:\n", concat_axis0)
print("axis=1拼接:\n", concat_axis1)
# 分割
arr = np.arange(12).reshape(3, 4)
print("待分割数组:\n", arr)
# 垂直分割(vsplit):按行分割
vsplit_arr = np.vsplit(arr, 3) # 分成3个1×4数组
print("垂直分割为3份:", vsplit_arr)
# 水平分割(hsplit):按列分割
hsplit_arr = np.hsplit(arr, 2) # 分成2个3×2数组
print("水平分割为2份:\n", hsplit_arr[0], "\n", hsplit_arr[1])
数据说明:
- 拼接:两个2×2数组拼接为4×2(垂直)或2×4(水平);
- 分割:3×4数组按行分割为3个1×4,按列分割为2个3×2。
结论:
- 拼接需保证除拼接轴外的其他维度长度一致(如垂直拼接要求列数相同);
vstack/hstack是concatenate的简化版,axis=0为垂直,axis=1为水平;- 分割是拼接的逆操作,
vsplit/hsplit对应vstack/hstack。
2.3 广播机制(Broadcasting)
广播是NumPy的核心特性,允许不同形状的数组进行算术运算,规则:
- 维度少的数组自动补1(扩展维度);
- 各维度长度要么相同,要么有一个为1;
- 扩展后维度匹配,逐元素运算。
# 示例1:标量与数组运算(最常见)
arr = np.array([[1,2,3], [4,5,6]])
scalar_add = arr + 10 # 标量10广播为2×3数组
print("数组+标量:\n", scalar_add)
# 示例2:一维数组与二维数组广播
arr1d = np.array([1,2,3])
broadcast_add = arr + arr1d # arr1d补维度为(1,3),再扩展为(2,3)
print("二维+一维广播:\n", broadcast_add)
# 示例3:不同维度广播((3,1) + (1,4) → (3,4))
arr_a = np.arange(3).reshape(3, 1) # 3×1
arr_b = np.arange(4).reshape(1, 4) # 1×4
broadcast_mul = arr_a * arr_b
print("3×1 × 1×4广播:\n", broadcast_mul)
数据说明:
- 示例1:2×3数组 + 标量10 → 每个元素+10;
- 示例2:2×3数组 + 1×3数组 → 每行都加[1,2,3];
- 示例3:3×1 × 1×4 → 广播为3×4数组,逐元素相乘。
结论:
- 广播避免了手动扩展数组,大幅提升运算效率;
- 广播的核心是“维度补1 → 扩展匹配”,不满足规则会报错(如2×3与3×2数组无法直接广播)。
2.4 向量化运算(替代循环)
NumPy的向量化运算比Python原生循环快10~100倍,核心是直接对数组运算,而非逐元素循环。
# 示例:计算数组每个元素的平方
import time
# 原生循环
arr = np.random.rand(1000000) # 100万元素
start = time.time()
result_loop = []
for x in arr:
result_loop.append(x**2)
result_loop = np.array(result_loop)
end = time.time()
print("循环耗时:", end - start, "秒")
# 向量化运算
start = time.time()
result_vector = arr **2
end = time.time()
print("向量化耗时:", end - start, "秒")
# 验证结果一致
print("结果是否一致:", np.allclose(result_loop, result_vector))
数据说明:100万个0-1随机数的数组,分别用循环和向量化计算平方
输出示例:
循环耗时:0.123456 秒
向量化耗时:0.001234 秒
结果是否一致:True
结论:
- 向量化运算(如
arr**2、np.add、np.multiply)完全替代循环,效率提升100倍以上; - NumPy的算术运算符(+、-、*、/、**、%)均支持向量化,对应
np.add、np.subtract、np.multiply等函数。
2.5 统计函数
NumPy提供丰富的统计函数,支持按整体/指定轴计算:
arr = np.array([[1,2,3], [4,5,6], [7,8,9]])
print("原始数组:\n", arr)
# 整体统计
print("总和:", np.sum(arr)) # 45
print("均值:", np.mean(arr)) # 5.0
print("标准差:", np.std(arr)) # 2.581988897471611
print("最小值:", np.min(arr)) # 1
print("最大值:", np.max(arr)) # 9
print("最小值索引:", np.argmin(arr)) # 0(一维索引)
print("最大值索引:", np.argmax(arr)) # 8(一维索引)
# 按轴统计(axis=0:列,axis=1:行)
print("按列求和:", np.sum(arr, axis=0)) # [12 15 18]
print("按行求均值:", np.mean(arr, axis=1)) # [2. 5. 8.]
# 累积和/累积积
print("累积和(按行):\n", np.cumsum(arr, axis=1))
print("累积积(按列):\n", np.cumprod(arr, axis=0))
数据说明:3×3矩阵,计算整体/按轴的统计量
结论:
- 统计函数默认对整个数组计算,指定
axis可按行/列计算; argmin/argmax返回最小值/最大值的一维索引,高维数组可结合unravel_index转为多维索引;cumsum/cumprod用于计算累积和/积,是时序分析、累积概率计算的常用函数。
三、高级篇:线性代数与进阶技巧
3.1 线性代数(linalg模块)
NumPy的np.linalg模块提供线性代数核心功能,等价于MATLAB的线性代数操作:
# 矩阵乘法(@ 或 np.dot 或 np.matmul)
A = np.array([[1,2], [3,4]])
B = np.array([[5,6], [7,8]])
mat_mul = A @ B # 矩阵乘法
dot_mul = np.dot(A, B) # 等价@(二维数组)
print("矩阵乘法A@B:\n", mat_mul)
# 逆矩阵(仅方阵且非奇异)
A_inv = np.linalg.inv(A)
print("A的逆矩阵:\n", A_inv)
# 验证:A × A_inv = 单位矩阵(浮点误差可忽略)
print("A × A_inv:\n", np.round(A @ A_inv, 0))
# 行列式
det_A = np.linalg.det(A)
print("A的行列式:", det_A) # -2.0
# 特征值与特征向量
eigenvalues, eigenvectors = np.linalg.eig(A)
print("特征值:", eigenvalues)
print("特征向量:\n", eigenvectors)
# 解线性方程组 Ax = b
b = np.array([1, 2])
x = np.linalg.solve(A, b)
print("方程组Ax=b的解:", x)
# 验证:A@x ≈ b
print("验证A@x:", A @ x)
# 奇异值分解(SVD)
svd_U, svd_S, svd_Vt = np.linalg.svd(A)
print("SVD的U矩阵:\n", svd_U)
print("SVD的奇异值:", svd_S)
print("SVD的Vt矩阵:\n", svd_Vt)
数据说明:
- 矩阵A:[[1,2],[3,4]],矩阵B:[[5,6],[7,8]]
- 线性方程组:$\begin{cases}1x+2y=1 \ 3x+4y=2\end{cases}$
结论:
- 矩阵乘法优先用
@(Python3.5+),np.dot对一维数组是点积,二维数组是矩阵乘法; - 逆矩阵仅适用于方阵且行列式≠0,否则报错;
np.linalg.solve是解线性方程组的高效方法,比求逆矩阵再相乘更稳定;- SVD是降维(PCA)、矩阵分解的核心算法,返回U(左奇异向量)、S(奇异值)、Vt(右奇异向量转置)。
3.2 高级索引与技巧
(1)where函数(条件替换)
arr = np.random.randint(-5, 5, (3, 3))
print("原始数组:\n", arr)
# where(条件, 满足时的值, 不满足时的值)
arr_where = np.where(arr > 0, arr, 0) # 正数保留,负数/0置0
print("where替换后:\n", arr_where)
# 多条件where
arr_where2 = np.where((arr > 0) & (arr < 3), 1,
np.where(arr >=3, 2, 0))
print("多条件where:\n", arr_where2)
结论:np.where是向量化的三元运算符,替代if-else循环,支持嵌套实现多条件判断。
(2)掩码数组(masked array)
处理缺失值/异常值时,掩码数组可标记无效元素,避免参与计算:
# 创建掩码数组
arr = np.array([1, 2, np.nan, 4, 5])
masked_arr = np.ma.masked_invalid(arr) # 标记nan为无效
print("掩码数组:", masked_arr)
print("掩码数组的均值:", masked_arr.mean()) # 忽略nan,均值=(1+2+4+5)/4=3.0
# 自定义掩码
arr2 = np.array([10, 20, 30, 40])
mask = [False, True, False, True] # 标记第2、4个元素为无效
masked_arr2 = np.ma.array(arr2, mask=mask)
print("自定义掩码数组:", masked_arr2)
print("掩码数组的和:", masked_arr2.sum()) # 10+30=40
结论:
- 掩码数组(
np.ma)是处理缺失值的高效方式,避免手动过滤; masked_invalid可自动标记nan/inf为无效,统计函数会忽略掩码元素。
(3)视图(view)vs 副本(copy)
arr = np.array([1,2,3,4,5])
# 视图(浅拷贝):共享内存
view_arr = arr.view()
view_arr[0] = 99
print("原数组(视图修改后):", arr) # [99,2,3,4,5]
# 副本(深拷贝):独立内存
copy_arr = arr.copy()
copy_arr[1] = 88
print("原数组(副本修改后):", arr) # [99,2,3,4,5]
print("副本数组:", copy_arr) # [99,88,3,4,5]
结论:
- 视图(如
arr.view()、切片arr[:3])共享原数组内存,修改视图会影响原数组; - 副本(如
arr.copy()、花式索引)独立内存,修改不影响原数组; - 避免无意识修改原数组,需明确使用
copy()创建副本。
3.3 结构化数组(处理异构数据)
结构化数组支持存储不同类型的数据(如字符串、整数、浮点数),类似Excel表格:
# 定义数据类型(dtype)
dtype = [('name', 'U10'), ('age', np.int32), ('score', np.float64)]
# 创建结构化数组
students = np.array([
('Alice', 20, 95.5),
('Bob', 19, 88.0),
('Charlie', 21, 92.3)
], dtype=dtype)
print("结构化数组:\n", students)
# 按字段访问
print("所有姓名:", students['name'])
print("所有年龄:", students['age'])
print("Bob的成绩:", students[1]['score'])
# 按条件筛选
high_score = students[students['score'] > 90]
print("成绩>90的学生:\n", high_score)
数据说明:
- 字段
name:字符串(U10表示最多10个Unicode字符); - 字段
age:int32;字段score:float64。
结论:
- 结构化数组是NumPy处理异构数据的方式,可替代Pandas的DataFrame(轻量级场景);
- 按字段名访问数据,支持布尔索引筛选,适合小规模异构数据处理。
3.4 性能优化建议
- 优先向量化:避免所有显式循环(for/while),用NumPy内置函数/向量化运算替代;
- 减少副本:尽量使用视图(如切片),避免频繁
copy(); - 指定dtype:提前指定合适的dtype(如int32而非int64),减少内存占用;
- 广播合理使用:避免过度广播导致的内存爆炸(如(1e6,1)与(1,1e6)广播为(1e6,1e6));
- 结合Numba:极复杂的运算可通过
numba.jit装饰器加速(编译为机器码)。
四、总结
NumPy是Python数值计算的基石,核心优势在于:
- 高效的ndarray:连续内存存储,同质类型,支持多维操作;
- 向量化+广播:替代循环,大幅提升计算效率;
- 丰富的内置函数:覆盖数组创建、操作、统计、线性代数等场景;
- 与其他库兼容:Pandas、Matplotlib、Scikit-learn等均基于NumPy构建。
学习路径建议:
- 基础篇:掌握数组创建、属性、索引切片;
- 进阶篇:熟练数组操作、广播、向量化运算;
- 高级篇:深入线性代数、掩码数组、结构化数组,结合实际场景(如数据分析、机器学习)落地。
NumPy的核心是“向量化思维”,摒弃Python原生的循环习惯,用数组整体操作替代逐元素处理,才能发挥其最大优势。
❤️ 如果你喜欢这篇文章,请点赞支持! 👍 同时欢迎关注我的博客,获取更多精彩内容!
本文来自博客园,作者:佛祖让我来巡山,转载请注明原文链接:https://www.cnblogs.com/sun-10387834/p/19388852

浙公网安备 33010602011771号