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/hstackconcatenate的简化版,axis=0为垂直,axis=1为水平;
  • 分割是拼接的逆操作,vsplit/hsplit对应vstack/hstack

2.3 广播机制(Broadcasting)

广播是NumPy的核心特性,允许不同形状的数组进行算术运算,规则:

  1. 维度少的数组自动补1(扩展维度);
  2. 各维度长度要么相同,要么有一个为1;
  3. 扩展后维度匹配,逐元素运算。
# 示例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**2np.addnp.multiply)完全替代循环,效率提升100倍以上;
  • NumPy的算术运算符(+、-、*、/、**、%)均支持向量化,对应np.addnp.subtractnp.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 性能优化建议

  1. 优先向量化:避免所有显式循环(for/while),用NumPy内置函数/向量化运算替代;
  2. 减少副本:尽量使用视图(如切片),避免频繁copy()
  3. 指定dtype:提前指定合适的dtype(如int32而非int64),减少内存占用;
  4. 广播合理使用:避免过度广播导致的内存爆炸(如(1e6,1)与(1,1e6)广播为(1e6,1e6));
  5. 结合Numba:极复杂的运算可通过numba.jit装饰器加速(编译为机器码)。

四、总结

NumPy是Python数值计算的基石,核心优势在于:

  1. 高效的ndarray:连续内存存储,同质类型,支持多维操作;
  2. 向量化+广播:替代循环,大幅提升计算效率;
  3. 丰富的内置函数:覆盖数组创建、操作、统计、线性代数等场景;
  4. 与其他库兼容:Pandas、Matplotlib、Scikit-learn等均基于NumPy构建。

学习路径建议:

  • 基础篇:掌握数组创建、属性、索引切片;
  • 进阶篇:熟练数组操作、广播、向量化运算;
  • 高级篇:深入线性代数、掩码数组、结构化数组,结合实际场景(如数据分析、机器学习)落地。

NumPy的核心是“向量化思维”,摒弃Python原生的循环习惯,用数组整体操作替代逐元素处理,才能发挥其最大优势。

posted @ 2026-01-08 14:37  佛祖让我来巡山  阅读(58)  评论(0)    收藏  举报

佛祖让我来巡山博客站 - 创建于 2018-08-15

开发工程师个人站,内容主要是网站开发方面的技术文章,大部分来自学习或工作,部分来源于网络,希望对大家有所帮助。

Bootstrap中文网