NumPy 科学计算完全指南:从数组创建到广播机制

NumPy 科学计算完全指南:从数组创建到广播机制

NumPy 是 Python 数据分析和机器学习的基石------Pandas、SciPy、Matplotlib、scikit-learn 全部构建在 NumPy 之上。本文以知识点解析 + 代码实例 为主线,所有代码输出均为真实运行结果,可直接复制验证。


文章目录

  • [NumPy 科学计算完全指南:从数组创建到广播机制](#NumPy 科学计算完全指南:从数组创建到广播机制)
    • [一、NumPy 简介与安装](#一、NumPy 简介与安装)
      • [1.1 什么是 NumPy?](#1.1 什么是 NumPy?)
      • [1.2 Python 数据分析生态圈](#1.2 Python 数据分析生态圈)
      • [1.3 安装与验证](#1.3 安装与验证)
    • [二、ndarray:NumPy 的核心对象](#二、ndarray:NumPy 的核心对象)
      • [2.1 创建数组](#2.1 创建数组)
      • [2.2 数据类型 dtype](#2.2 数据类型 dtype)
      • [2.3 五大核心属性](#2.3 五大核心属性)
      • [2.4 维度变换](#2.4 维度变换)
        • [升维:reshape 与 resize](#升维:reshape 与 resize)
        • [降维:ravel 与 flatten](#降维:ravel 与 flatten)
        • 转置与压缩
    • 三、生成数列:arange、linspace、logspace
      • [3.1 arange:按步长生成(左闭右开)](#3.1 arange:按步长生成(左闭右开))
      • [3.2 linspace:按个数生成(左右都闭)](#3.2 linspace:按个数生成(左右都闭))
      • [3.3 logspace:等比数列](#3.3 logspace:等比数列)
    • 四、特殊数组创建
    • 五、数组索引与切片
      • [5.1 一维数组索引](#5.1 一维数组索引)
      • [5.2 二维数组索引](#5.2 二维数组索引)
      • [5.3 三维数组索引](#5.3 三维数组索引)
      • [5.4 布尔索引](#5.4 布尔索引)
      • [5.5 花式索引](#5.5 花式索引)
    • 六、数组元素操作
      • [6.1 修改元素](#6.1 修改元素)
      • [6.2 np.delete:删除元素](#6.2 np.delete:删除元素)
      • [6.3 np.where:条件筛选](#6.3 np.where:条件筛选)
      • [6.4 np.unique:去重](#6.4 np.unique:去重)
    • 七、数组组合与切割
      • [7.1 组合](#7.1 组合)
      • [7.2 切割](#7.2 切割)
    • [八、算术运算与 ufunc](#八、算术运算与 ufunc)
      • [8.1 元素级运算](#8.1 元素级运算)
      • [8.2 ufunc:通用函数](#8.2 ufunc:通用函数)
      • [8.3 数组与标量运算](#8.3 数组与标量运算)
    • 九、矩阵运算
      • [9.1 矩阵乘法](#9.1 矩阵乘法)
      • [9.2 矩阵求逆](#9.2 矩阵求逆)
    • 十、广播机制(Broadcasting)
      • [10.1 广播规则](#10.1 广播规则)
      • [10.2 三种典型情况](#10.2 三种典型情况)
      • [10.3 用 np.newaxis 手动扩展维度](#10.3 用 np.newaxis 手动扩展维度)
    • 十一、随机模块
      • [11.1 随机种子](#11.1 随机种子)
      • [11.2 三种常用分布](#11.2 三种常用分布)
    • 十二、统计函数
      • [12.1 keepdims:保持维度](#12.1 keepdims:保持维度)
      • [12.2 bincount 与 histogram](#12.2 bincount 与 histogram)
    • 十三、深拷贝与浅拷贝
    • 十四、文件读写
    • 十五、多项式函数
    • 十六、总结

一、NumPy 简介与安装

1.1 什么是 NumPy?

NumPy(Numerical Python)是 Python 的开源数值计算扩展库,底层用 C 语言实现,核心功能包括:

  • N 维数组对象(ndarray):存储单一数据类型的多维数组
  • 通用函数(ufunc):对数组每个元素进行运算,速度极快
  • 线性代数、傅里叶变换、随机数生成
  • 与 C++/Fortran 无缝结合

🔰 为什么不用 Python 原生列表?

Python 列表的本质是一个"指针数组"------每个元素存储的是对象的内存地址 ,而不是值本身。比如 [1, 2, 3] 在内存中不是连续的三个整数,而是三个指针,每个指针分别指向一个 Python 整数对象。这样做的好处是灵活(可以混合存储不同类型),但代价是:

  1. 内存浪费:每个 int 对象本身就要占 28 字节,而 NumPy 的 int64 只要 8 字节
  2. 缓存不友好:数据散落在内存各处,CPU 缓存命中率低
  3. 运算慢:两个列表相加需要逐个解引用、类型检查、创建新对象

NumPy 的 ndarray 直接在连续内存块 中存储原始数据(就是纯粹的数字),没有指针间接层。对大规模数值计算,速度可快 10~100 倍。这就是为什么几乎所有 Python 数据科学库(Pandas、scikit-learn、TensorFlow)底层都依赖 NumPy。

1.2 Python 数据分析生态圈

功能 依赖关系
NumPy N 维数组、矩阵运算 基础库
SciPy 积分、优化、插值、信号处理 依赖 NumPy
Pandas 数据分析、表格操作 基于 NumPy
Matplotlib 绘图 依赖 NumPy
scikit-learn 机器学习 基于 NumPy/SciPy

1.3 安装与验证

python 复制代码
import numpy as np
print(np.__version__)  # 查看版本

运行输出:

复制代码
2.4.6

二、ndarray:NumPy 的核心对象

NumPy 最核心的东西就一个------ndarray(N-dimensional array,N维数组)。它类似 R 语言的向量和矩阵,但更强大。

ndarray 有两个基本特征:所有元素必须是相同数据类型 (这保证了内存连续和高效运算),以及支持任意维度(从一维向量到 N 维张量)。这两个特征使得 ndarray 在处理大规模矩阵运算时远比 Python 列表高效。

2.1 创建数组

从列表创建

最直接的创建方式是将 Python 列表传给 np.array()。列表的嵌套层数决定了数组的维度:一层括号是一维,两层是二维,三层是三维。

观察输出可以发现一个重要区别:Python 列表打印时元素之间有逗号 [1, 2, 3],而 NumPy 数组没有逗号 [1 2 3]------这是快速辨认两种类型的方法。

python 复制代码
import numpy as np

# 一维数组
v = np.array([1, 2, 3, 4, 5])
print(v)

# 二维数组(多个一维数组构成)
m = np.array([[1,2,3,4,5],
              [1,2,3,4,5],
              [1,2,3,4,5]])
print(m)

# 三维数组(多个二维数组构成)
z = np.array([[ [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5] ],
              [ [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5] ],
              [ [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5] ]])

运行输出:

复制代码
一维数组:
[1 2 3 4 5]
type: <class 'numpy.ndarray'>, shape: (5,), ndim: 1, size: 5, dtype: int64

二维数组:
[[1 2 3 4 5]
 [1 2 3 4 5]
 [1 2 3 4 5]]
type: <class 'numpy.ndarray'>, shape: (3, 5), ndim: 2, size: 15

三维数组 shape=(3, 3, 5), ndim=3, size=45

🔰 类型规则 :ndarray 中所有元素必须是相同类型 。如果混合传入不同类型,NumPy 按优先级自动"向上转型":str > float > int。例如 np.array([1, 2.5, 'hello']) 中有字符串,所以全部转为字符串 ['1' '2.5' 'hello']。这是 ndarray 与 Python 列表最大的区别------列表可以 [1, 2.5, 'hello'] 混合存储,ndarray 不行。

这么做的原因是性能:相同类型意味着每个元素占用相同字节数,CPU 可以直接用指针算术定位第 N 个元素,不需要类型检查。

从无到有创建

除了从列表转换,NumPy 还提供了一系列工厂函数直接生成数组。这些函数不需要你先手写数据,而是按规则自动填充------这在初始化模型参数、生成坐标轴、创建测试数据时非常常用。

python 复制代码
# empty:创建未初始化的数组(内存中的随机值)
print(np.empty((2, 3)))

# arange:等差数列(左闭右开)
print(np.arange(0, 9, 3))  # [0 3 6]

# linspace:等差数列(左右都闭)
print(np.linspace(0, 1, 21))

# logspace:等比数列
print(np.logspace(0, 3, 4, base=10))  # [1, 10, 100, 1000]

运行输出:

复制代码
empty((2,3)):
[[ 0. nan  0.]
 [nan  0. nan]]

arange(0,9,3): [0 3 6]
linspace(0,1,21): [0.  0.05 0.1 ... 0.95 1.]
logspace(0,3,4,base=10): [   1.   10.  100. 1000.]

💡 empty 的用途 :当你只需要一块"空地"稍后填充数据时,emptyzeros 快------因为它不初始化,直接返回内存里的旧值。但要注意:empty 返回的值是不可预测的垃圾值 (看到 nan 是因为那块内存恰好存的是 NaN),在用于计算前必须手动赋值,否则会得到错误结果。

2.2 数据类型 dtype

Python 原生只有 intfloatstr 三种数值类型,而 NumPy 提供了极其精细的类型系统。为什么需要这么多类型?因为不同的类型占用的内存不同 ------处理 100 万个数据时,用 int8(1字节)代替 int64(8字节)可以节省 7 倍内存。在深度学习、图像处理、嵌入式设备中,合理选择数据类型可以显著降低内存消耗和加速计算。

类型 说明 示例
int8/16/32/64 不同长度的有符号整数 int64 占 8 字节
uint8/16/32/64 无符号整数(只能≥0) uint8 范围 0~255
float16/32/64 不同精度的浮点数 float64 = Python float
bool 布尔 True/False
str_ 定长字符串 'S10' 表示长度10
complex64/128 复数 1+2j

类型转换 astype()

python 复制代码
arr = np.array([1.6, 2.3, 3.8, 4.1])
print(f"原数组: {arr}, dtype={arr.dtype}")
print(f"astype(int): {arr.astype(int)}")    # 截断小数
print(f"astype(str): {arr.astype(str)}")

运行输出:

复制代码
原数组: [1.6 2.3 3.8 4.1], dtype=float64
astype(int): [1 2 3 4]           ← 直接截断小数部分(不是四舍五入!)
astype(str): ['1.6' '2.3' '3.8' '4.1']

🔰 注意astype(int)截断 不是四舍五入------1.6 变 1,3.8 变 3。这和 C 语言的类型转换行为一致:直接丢弃小数部分。如果要四舍五入,需要先用 np.round() 再转:np.round(arr).astype(int) 会把 1.6→2、3.8→4。

这个行为在数据预处理中容易踩坑------比如你想把连续的成绩分转换为整数分,截断会让 89.9 变成 89 而不是 90。

2.3 五大核心属性

理解数组的属性是后续一切操作的基础------shape 告诉你数组"长什么样",ndim 告诉你"几维",size 告诉你"多少个元素",dtype 告诉你"什么类型"。这四个属性在调试代码、检查数据维度是否匹配时最常用。

属性 含义 一维示例 二维示例 三维示例
shape 形状(各维度大小) (5,) (3, 5) (3, 3, 5)
ndim 维度数(轴数/秩) 1 2 3
size 元素总个数 5 15 45
dtype 元素数据类型 int64 int64 int64
type() Python 对象类型 numpy.ndarray numpy.ndarray numpy.ndarray

🔰 轴(axis)的概念 :NumPy 中每个维度称为一个"轴"(axis),轴的数量称为"秩"(rank)。三维数组有三个轴:第0轴(层)、第1轴(行)、第2轴(列)。从外到内编号。这个概念非常重要------后续所有按维度操作的函数(如 sum(axis=0)concatenate(axis=1))都是基于这个编号系统的。

怎么理解轴? 想象一本有 2 页、每页 4 行 6 列的表格:第 0 轴选"翻到第几页",第 1 轴选"看第几行",第 2 轴选"看第几列"。axis=0 操作就是"跨页"汇总,axis=1 操作就是"跨行"汇总。

2.4 维度变换

维度变换是机器学习中极其常用的操作------例如卷积神经网络在全连接层之前需要把多维特征"展平"为一维;图像数据通常是三维的 (H, W, C),批量处理时需要升为四维 (B, H, W, C)。掌握 reshaperavelflatten 是进行深度学习数据处理的基本功。

升维:reshape 与 resize
python 复制代码
v = np.array([1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8])

# 一维变二维,-1表示自动计算
r1 = v.reshape(4, -1)
print(r1)

# 一维变三维
r2 = v.reshape(2, 2, 4)
print(r2)

运行输出:

复制代码
reshape(4,-1) 升为二维:
[[1 2 3 4]
 [5 6 7 8]
 [1 2 3 4]
 [5 6 7 8]]

reshape(2,2,4) 二维升三维:
[[[1 2 3 4]
  [5 6 7 8]]
 [[1 2 3 4]
  [5 6 7 8]]]

reshape vs resize

方法 返回值 是否修改原数组 用法
reshape() 返回新数组 ❌ 不修改 new = arr.reshape(2,3)
resize() 无返回值 ✅ 直接修改原数组 arr.resize(2,3)

💡 -1 的妙用 :不确定某维大小时写 -1,NumPy 自动计算。16个元素 reshape(4, -1) → 自动算出 4 列。原理很简单:元素总数 = 各维乘积,已知总数 16 和其中一维 4,另一维必然是 16÷4=4。但只能有一个维度写 -1,否则 NumPy 无法判断。

reshape 的限制 :元素总数必须不变。16 个元素可以 reshape(4,4)reshape(2,8),但不能 reshape(3,5)------15≠16 会报错。

降维:ravel 与 flatten

降维是将高维数组"拍平"为一维数组的操作。这在机器学习中非常常用------例如将 28×28 的图像展平为 784 维向量输入到全连接层。NumPy 提供了两个看起来效果一样、但内存行为完全不同的函数。

python 复制代码
v = np.array([1,2,3,4,5,6,7,8]).reshape(2,2,2)

# 降为一维
print(v.ravel())     # 返回视图
print(v.flatten())   # 返回副本

运行输出:

复制代码
ravel() 降为一维: [1 2 3 4 5 6 7 8]
flatten() 降为一维: [1 2 3 4 5 6 7 8]

ravel vs flatten

方法 返回的是 修改返回值会影响原数组吗
ravel() 原数组的视图 ✅ 会影响(共享内存)
flatten() 原数组的副本 ❌ 不影响(独立内存)

💡 记忆:flatten = flat + copy(拍平并复制),ravel 像拉拉链展开(可能共享数据)。

实际选择建议 :大多数情况下用 ravel() 就够了------它更快(不复制数据)。但如果你后续要修改展平后的数组且不想影响原数组,必须用 flatten()。记住这个原则:需要独立修改 → flatten,只读不改 → ravel

转置与压缩

转置(.T)是矩阵运算的基础操作------行变列、列变行。在机器学习中,特征矩阵经常需要在 (samples, features) 和 (features, samples) 之间转置以匹配不同函数的输入要求。

squeeze 则是一个"清理工具"------它去掉所有长度为 1 的维度。这在深度学习框架中特别常见,因为模型输出往往带有冗余的 batch 维度,如 shape (1, 1, 10)squeeze() 变成 (10,),更方便后续处理。

python 复制代码
# 转置:行变列、列变行
a = np.arange(6).reshape(2, 3)
print(a)
print(a.T)  # 或 a.transpose()

# squeeze:去掉长度为1的维度
b = np.array([[[1,2,3]]])  # shape=(1,1,3)
print(b.shape)              # (1, 1, 3)
print(b.squeeze().shape)    # (3,)

运行输出:

复制代码
a:
[[0 1 2]
 [3 4 5]]
a.T:
[[0 3]
 [1 4]
 [2 5]]

b.shape: (1, 1, 3)
b.squeeze().shape: (3,)

🔰 squeeze 的用途 :深度学习中模型输出 shape 可能是 (batch_size, 1, num_classes),用 squeeze 去掉中间的 1 维度变成 (batch_size, num_classes)


三、生成数列:arange、linspace、logspace

在数据分析中经常需要生成等差或等比数列------画图时生成 x 轴坐标点、划分区间边界、设置学习率衰减序列等。NumPy 提供了三个函数来满足不同需求。

3.1 arange:按步长生成(左闭右开)

python 复制代码
print(np.arange(0, 9, 3))

运行输出:

复制代码
[0 3 6]

类似 Python 的 range(),但返回的是 NumPy 数组。不包含终点 9 ------这是初学者最容易踩的坑:arange(0, 9, 3) 生成 [0, 3, 6] 而不是 [0, 3, 6, 9]。原因和 Python range 一样:左闭右开区间,终点不包含在内。

3.2 linspace:按个数生成(左右都闭)

python 复制代码
print(np.linspace(0, 1, 21))

运行输出:

复制代码
[0.   0.05 0.1  0.15 0.2  0.25 0.3  0.35 0.4  0.45 0.5  0.55 0.6  0.65 0.7  0.75 0.8  0.85 0.9  0.95 1.  ]

0, 1 之间均匀取 21 个点,包含终点 1。步长 = (1-0)/(21-1) = 0.05。

linspacearange 的核心区别是:你指定想要多少个点 ,而不是步长。NumPy 自动计算步长来均匀分布。这在画图时特别有用------np.linspace(0, 2*np.pi, 100) 可以在 0 到 2π 之间取 100 个点来画平滑的正弦曲线。

3.3 logspace:等比数列

python 复制代码
print(np.logspace(0, 3, 4, base=10))

运行输出:

复制代码
[   1.   10.  100. 1000.]

生成 10^0=1, 10^1=10, 10^2=100, 10^3=1000,是以 10 为底的等比数列。

logspace 的参数是指数 ,不是实际值。logspace(0, 3, 4) 的意思是:在 10^0 到 10^3 之间取 4 个等距的指数 值(0, 1, 2, 3),然后计算 10 的这些幂次。这在机器学习调参时非常有用------比如要在 0.0011000 之间搜索正则化参数,直接写 np.logspace(-3, 3, 7) 就能得到 [0.001, 0.01, 0.1, 1, 10, 100, 1000],比手动写方便得多。

函数 区间 控制方式 典型场景
arange(start, end, step) 左闭右开 指定步长 生成 0,3,6
linspace(start, end, num) 左右都闭 指定个数 画图取坐标
logspace(start, end, num, base) 左右都闭 指定个数 生成 10的幂次

四、特殊数组创建

在实际开发中,我们经常需要初始化一块特定形状和值的数组------比如神经网络的权重初始化为零、创建单位矩阵做线性代数运算、生成全 1 数组做掩码。NumPy 提供了一组工厂函数来快速完成这些任务。

python 复制代码
import numpy as np

# 全为0
print(np.zeros(5))          # 一维
print(np.zeros((2,2)))      # 二维

# 全为1
print(np.ones(5))
print(np.ones((2,2)))

# 全为指定值
print(np.full((2,2), 2))   # 全部填充2

# 单位矩阵(对角线为1)
print(np.eye(5, 7))

运行输出:

复制代码
np.zeros(5): [0. 0. 0. 0. 0.]
np.zeros((2,2)):
[[0. 0.]
 [0. 0.]]
np.ones(5): [1. 1. 1. 1. 1.]
np.full((2,2), 2):
[[2 2]
 [2 2]]
np.eye(5,7):
[[1. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0.]]
函数 作用 常用场景
np.zeros(shape) 全0数组 初始化权重、占位
np.ones(shape) 全1数组 掩码、计数
np.full(shape, val) 全指定值 自定义填充
np.eye(n, m) 单位矩阵 线性代数
np.empty(shape) 未初始化 追求速度的场景

🔰 注意zerosones 多维形状必须用元组 ((2,2)) 传入,不能写 np.zeros(2,2)。原因:np.zeros(5) 中的 5 被当作一维形状;如果写 np.zeros(2,2),NumPy 会以为第二个 2 是 dtype 参数而报错。记住:多维形状永远是元组


五、数组索引与切片

索引和切片是数组操作的核心------数据分析中几乎每一步都涉及"取某些行、选某些列、筛选满足条件的元素"。NumPy 的索引系统比 Python 列表强大得多:除了基本的整数索引和切片,还支持布尔索引(按条件筛选)和花式索引(按位置列表选取)。

5.1 一维数组索引

一维数组的索引与 Python 列表基本一致------用 [index] 取单个元素,用 [start:stop] 切片。但 NumPy 额外支持用列表做索引来选取多个不连续的元素,这是 Python 列表做不到的。

python 复制代码
array1 = np.arange(1, 9)  # [1 2 3 4 5 6 7 8]

# 选取
print(array1[1])              # 单个元素 → 2
print(array1[[1, 3, 5]])      # 不连续多个 → [2 4 6]
print(array1[0:6])            # 切片 → [1 2 3 4 5 6]

# 修改
array1[0] = 10
array1[[1, 3, 5]] = 20        # 批量修改
array1[0:6] = 100             # 切片修改

运行输出:

复制代码
原数组: [1 2 3 4 5 6 7 8]
array1[0]=10 后:        [10  2  3  4  5  6  7  8]
array1[[1,3,5]]=20 后:  [10 20  3 20  5 20  7  8]
array1[0:6]=100 后:     [100 100 100 100 100 100   7   8]

💡 技巧 :用列表 做索引可以选取不连续的元素,如 array1[[1,3,5]] 同时选第2、4、6个。这在数据预处理中非常实用------比如你知道第 0、3、5 列是特征列,其他是标签,就可以用 data[:, [0,3,5]] 一步选出。

5.2 二维数组索引

二维数组索引是 NumPy 学习的关键里程碑。掌握后,三维及更高维的索引逻辑完全一样------只是多加一个维度而已。

核心规则:逗号分隔行和列 。逗号前是行索引,逗号后是列索引,: 表示"全部"。

python 复制代码
array1 = np.arange(24).reshape(4, 6)

# 选取某个元素
a = array1[1, 4]          # 第2行第5列 → 10

# 选取某行
b = array1[3, :]          # 第4行全部

# 选取某些行(连续/不连续)
c = array1[0:2, :]        # 第1~2行
d = array1[[0, 2], :]     # 第1行和第3行

# 选取某列
e = array1[:, 3]          # 第4列全部

# 选取某些列
f = array1[:, 0:3]        # 第1~3列
g = array1[:, [0, 3]]     # 第1列和第4列

运行输出:

复制代码
原数组:
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]

array1[1,4] = 10                     ← 第2行第5列
array1[3,:] = [18 19 20 21 22 23]    ← 第4行全部
array1[0:2,:] =                      ← 连续行
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]
array1[[0,2],:] =                    ← 不连续行
[[ 0  1  2  3  4  5]
 [12 13 14 15 16 17]]
array1[:,3] = [ 3  9 15 21]          ← 第4列全部
array1[:,[0,3]] =                    ← 不连续列
[[ 0  3]
 [ 6  9]
 [12 15]
 [18 21]]

索引速查表:

写法 含义
[1, 4] 第2行第5列(单个元素)
[1, :] 第2行全部
[:, 4] 第5列全部
[0:2, :] 第1~2行
[:, 0:3] 第1~3列
[[0,2], :] 第1行和第3行
[:, [0,3]] 第1列和第4列

修改元素:

python 复制代码
array1[1, 4] = 100        # 改单个
array1[3, :] = 100        # 改整行
array1[[0, 2], :] = 50    # 批量改行
复制代码
array1[1,4]=100 后:
[[  0   1   2   3   4   5]
 [  6   7   8   9 100  11]
 [ 12  13  14  15  16  17]
 [ 18  19  20  21  22  23]]

array1[[0,2],:]=50 后:
[[ 50  50  50  50  50  50]
 [  6   7   8   9 100  11]
 [ 50  50  50  50  50  50]
 [100 100 100 100 100 100]]

5.3 三维数组索引

三维数组索引看起来吓人,但逻辑很简单------只是在二维的基础上多了一层"层"的概念。你可以把三维数组想象成一本书:第0轴是"第几页",第1轴是"第几行",第2轴是"第几列"。逗号前是页,中间是行,最后是列。

python 复制代码
array1 = np.arange(48).reshape(2, 4, 6)

a = array1[1, 0, 0]       # 第2层、第1行、第1列 → 24
b = array1[0, 1, :]       # 第1层、第2行全部
c = array1[0, 1:3, :]     # 第1层、第2~3行
e = array1[1, :, 1]       # 第2层、所有行、第2列

运行输出:

复制代码
原数组 shape=(2, 4, 6):
[[[ 0  1  2  3  4  5]      ← 第1层
  [ 6  7  8  9 10 11]
  [12 13 14 15 16 17]
  [18 19 20 21 22 23]]
 [[24 25 26 27 28 29]      ← 第2层
  [30 31 32 33 34 35]
  [36 37 38 39 40 41]
  [42 43 44 45 46 47]]]

array1[1,0,0] = 24                  ← 第2层第1行第1列
array1[0,1,:] = [ 6  7  8  9 10 11] ← 第1层第2行
array1[0,1:3,:] =
[[ 6  7  8  9 10 11]
 [12 13 14 15 16 17]]
array1[1,:,1] = [25 31 37 43]      ← 第2层所有行第2列

🔰 三维记忆法 :把三维数组想象成一叠扑克牌------第一个索引选第几 (层),第二个选第几 ,第三个选第几

5.4 布尔索引

布尔索引是 NumPy 最强大的功能之一------它让你用条件表达式 直接筛选数组元素,不需要写 for 循环。工作原理分两步:第一步,条件表达式(如 arr > 4)生成一个布尔数组(True/False);第二步,用这个布尔数组作为索引,NumPy 返回所有 True 位置的元素。

这种方式叫做"向量化操作"------它一次性处理整个数组,而不是逐元素遍历,速度比 for 循环快几十倍。

python 复制代码
arr = np.array([3, 5, 2, 8, 1, 9, 4])
mask = arr > 4                # 生成布尔数组
print(mask)                   # [False  True False  True False  True False]
print(arr[mask])              # [5 8 9]

# 直接修改满足条件的元素
arr[arr > 4] = 0
print(arr)                    # [3 0 2 0 1 0 4]

运行输出:

复制代码
原数组: [3 5 2 8 1 9 4]
arr > 4: [False  True False  True False  True False]
arr[arr>4]: [5 8 9]
arr[arr>4]=0: [3 0 2 0 1 0 4]

💡 应用场景 :数据清洗中把异常值替换为0或均值,如 data[data > 999] = np.nan

5.5 花式索引

花式索引(Fancy Indexing)是用整数列表/数组 作为索引来选取任意位置的元素。与切片不同,花式索引可以选取不连续的行或列,而且可以自由组合。但花式索引有一个容易混淆的陷阱:当传入两个列表时,它选取的是"坐标对"而不是"行列子矩阵"。

python 复制代码
arr = np.arange(16).reshape(4, 4)

# 选第2行和第4行
print(arr[[1, 3]])

# 选 (1,2) 和 (3,1) 两个位置的元素
print(arr[[1, 3], [2, 1]])    # [6 13]

# 用 np.ix_ 选取行列子矩阵
print(arr[np.ix_([0, 2], [1, 3])])

运行输出:

复制代码
原数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]

arr[[1,3]] 选第2和4行:
[[ 4  5  6  7]
 [12 13 14 15]]

arr[[1,3],[2,1]] 选(1,2)和(3,1): [ 6 13]

arr[np.ix_([0,2],[1,3])]:
[[ 1  3]
 [ 9 11]]

🔰 易错点arr[[1,3],[2,1]] 选的是两个点 (1,2) 和 (3,1),而不是行列子矩阵。要选子矩阵用 np.ix_()
💡 切片 vs 花式索引的内存差异 :切片产生的新数组与原数组共享存储空间 ;用列表索引会创建副本


六、数组元素操作

6.1 修改元素

前面已展示过用索引修改元素,这里补充 np.deletenp.where

6.2 np.delete:删除元素

删除元素在数据预处理中很常用------比如删除不需要的特征列、删除异常数据行。np.delete 接受三个参数:要操作的数组、要删除的索引位置、沿哪个轴操作。注意 delete 不会修改原数组,而是返回一个新数组。

python 复制代码
arr = np.arange(12).reshape(3, 4)

# 删行
print(np.delete(arr, 0, axis=0))  # 删第1行

# 删列
print(np.delete(arr, 1, axis=1))  # 删第2列

运行输出:

复制代码
原数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

delete行(axis=0) 删第1行:
[[ 4  5  6  7]
 [ 8  9 10 11]]

delete列(axis=1) 删第2列:
[[ 0  2  3]
 [ 4  6  7]
 [ 8 10 11]]

6.3 np.where:条件筛选

np.where 是 NumPy 中最灵活的函数之一,它有完全不同的两种用法。理解它的关键在于参数个数:三个参数时,它是一个"条件赋值"操作(满足条件取 x,否则取 y);一个参数时,它是一个"条件查找"操作(返回满足条件的元素位置)。

python 复制代码
arr = np.array([3, -1, 5, -2, 8, -3])

# 用法1:where(条件, x, y) --- 满足条件输出x,否则输出y
print(np.where(arr > 0, arr, 0))   # 正数保留,负数变0 → [3 0 5 0 8 0]

# 用法2:where(条件) --- 返回满足条件的元素索引
print(np.where(arr < 0))           # → [1 3 5]

运行输出:

复制代码
原数组: [ 3 -1  5 -2  8 -3]
np.where(arr>0, arr, 0): [3 0 5 0 8 0]
np.where(arr<0) 负数位置: [1 3 5]

💡 np.where 的两种用法

  • 三个参数 where(cond, x, y):相当于 if cond then x else y,逐元素
  • 一个参数 where(cond):返回满足条件的索引位置

6.4 np.unique:去重

np.unique 看起来只是去重,但它通过可选参数可以同时返回多个信息:去重后的值、每个值首次出现的位置、反向索引(用去重结果重建原数组)、每个值的出现频次。一行代码就能完成"值频统计"------这在分析分类数据时极其有用。

python 复制代码
arr = np.array([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5])
unique_vals, indices, inverse, counts = np.unique(
    arr, return_index=True, return_inverse=True, return_counts=True)

print(f"去重后: {unique_vals}")           # [1 2 3 4 5 6 9]
print(f"首次出现索引: {indices}")          # [1 6 0 2 4 7 5]
print(f"重构数组: {unique_vals[inverse]}")  # 还原回原数组
print(f"各值出现次数: {counts}")           # [2 1 2 1 3 1 1]

运行输出:

复制代码
去重后: [1 2 3 4 5 6 9]
首次出现索引: [1 6 0 2 4 7 5]
重构数组: [3 1 4 1 5 9 2 6 5 3 5]
各值出现次数: [2 1 2 1 3 1 1]

🔰 unique 的四个返回值:去重结果 + 首次出现位置 + 反向索引(用去重结果还原原数组) + 各值频次。非常实用------一行代码完成"值频统计"。


七、数组组合与切割

在实际项目中,数据往往分散在多个数组中------比如特征和标签是分开的、训练集和验证集需要拼接、一个大数据集需要分成多个子集。NumPy 提供了组合(拼接)和切割(拆分)两类函数。

组合是将多个小数组合并为一个大数组;切割是将一个大数组拆分为多个小数组。两者互为逆操作。

7.1 组合

python 复制代码
array1 = np.arange(9).reshape(3, 3)
array2 = 2 * array1

# 水平组合(左右拼)
print(np.hstack((array1, array2)))

# 垂直组合(上下拼)
print(np.vstack((array2, array1)))

# concatenate:axis=1 水平,axis=0 垂直
print(np.concatenate((array1, array2), axis=1))

运行输出:

复制代码
array1:               array2:
[[0 1 2]              [[ 0  2  4]
 [3 4 5]               [ 6  8 10]
 [6 7 8]]              [12 14 16]]

hstack水平组合:
[[ 0  1  2  0  2  4]
 [ 3  4  5  6  8 10]
 [ 6  7  8 12 14 16]]

vstack垂直组合:
[[ 0  2  4]
 [ 6  8 10]
 [12 14 16]
 [ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]]
函数 方向 等价写法
np.hstack((a, b)) 水平(左右) np.concatenate((a,b), axis=1)
np.vstack((a, b)) 垂直(上下) np.concatenate((a,b), axis=0)

7.2 切割

切割是组合的逆操作------将一个大数组拆成多个小数组。在机器学习中,交叉验证时需要把数据集等分成 K 份;在处理批量数据时需要把大矩阵分成小块。NumPy 提供了 hsplit(水平切)和 vsplit(垂直切),以及更灵活的 array_split(不要求等分)。

python 复制代码
array1 = np.arange(16).reshape(4, 4)

# 等分切割
print(np.hsplit(array1, 2))    # 水平2等分
print(np.vsplit(array1, 2))    # 垂直2等分

# 强制切割(不等分也能切)
print(np.array_split(array1, 3, axis=1))  # 水平3等分
print(np.array_split(array1, 3, axis=0))  # 垂直3等分

运行输出:

复制代码
原数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]

hsplit(array1, 2) 水平2等分:
  部分0:          部分1:
[[ 0  1]        [[ 2  3]
 [ 4  5]         [ 6  7]
 [ 8  9]         [10 11]
 [12 13]]        [14 15]]

vsplit(array1, 2) 垂直2等分:
  部分0:          部分1:
[[0 1 2 3]      [[ 8  9 10 11]
 [4 5 6 7]]      [12 13 14 15]]

array_split(array1, 3, axis=0) 强制垂直3等分:
  部分0(2行)     部分1(1行)      部分2(1行)
[[0 1 2 3]     [[ 8  9 10 11]]  [[12 13 14 15]]
 [4 5 6 7]]

🔰 hsplit/vsplit vs array_split:前者要求能整除,不能整除会报错;后者自动不等分分配。
💡 为什么"水平切割"的线是竖着的?"垂直切割"的线是横着的?

这是初学者最容易混淆的地方!之所以看起来"反直觉",是因为"水平/垂直"描述的是切割后子数组的排列方向,而不是刀落下的方向。

函数 名字含义 刀的方向 结果排列 类比
hsplit 水平切割 Horizontal = 结果左右排列 竖着切 部分0在左,部分1在右 把法棍面包切成左右两段,刀竖着切下去
vsplit 垂直切割 Vertical = 结果上下堆叠 横着切 部分0在上,部分1在下 把高楼拦腰截断,刀横着切过去

记忆法:不要记"线的方向",要记"分完之后的样子":

  • hsplit → 结果左右排排坐 → 刀竖着
  • vsplit → 结果上下叠罗汉 → 刀横着

对应到底层 API:hsplit 等价于 split(axis=1)(沿列轴切),vsplit 等价于 split(axis=0)(沿行轴切)。


八、算术运算与 ufunc

NumPy 的算术运算遵循一个核心原则:元素级运算 (element-wise)。也就是说,两个数组的 +-*/ 都是对应位置的元素逐一运算,而不是矩阵运算。这与数学中的矩阵运算有本质区别,初学者尤其要注意 *np.dot() 的差异。

8.1 元素级运算

python 复制代码
array1 = np.arange(1, 5).reshape(2, 2)  # [[1,2],[3,4]]
array2 = 2 * array1                       # [[2,4],[6,8]]

print(array1 + array2)    # 加
print(array1 - array2)    # 减
print(array1 * array2)    # 乘(元素级)
print(array1 / array2)    # 除
print(array2 % array1)    # 取余
print(array1 // array2)   # 取整

运行输出:

复制代码
array1:       array2:
[[1 2]        [[2 4]
 [3 4]]       [6 8]]

加法: [[ 3  6]     减法: [[-1 -2]    乘法: [[ 2  8]     除法: [[0.5 0.5]
       [ 9 12]]         [-3 -4]]           [18 32]]          [0.5 0.5]]
取余:  [[0 0]          取整:
        [0 0]]          [[0 0]
                        [0 0]]

⚠️ 重要 :NumPy 的 *元素级乘法 (对应位置相乘),不是矩阵乘法!矩阵乘法要用 np.dot()。这是初学者最常犯的错误------在数学中 A×B 通常指矩阵乘法,但在 NumPy 中 A*B 只是逐元素相乘。两者结果完全不同,后续第九章会详细演示矩阵乘法。

8.2 ufunc:通用函数

NumPy 中每一个运算符(+-*/)背后都对应一个 ufunc (universal function,通用函数)。ufunc 的特点是:它对数组中每个元素独立执行相同的运算,底层用 C 语言实现,避免了 Python 循环的开销。

ufunc 不仅比 Python for 循环快几十倍,还支持"广播"(下一章详解)------即使两个数组形状不同,ufunc 也能自动处理。

python 复制代码
array1 = np.arange(1, 5).reshape(2, 2)  # [[1,2],[3,4]]
array2 = 2 * array1                       # [[2,4],[6,8]]

# 这些 ufunc 写法与运算符完全等价
np.add(array1, array2)       # 等同于 array1 + array2
np.subtract(array1, array2) # 等同于 array1 - array2
np.multiply(array1, array2) # 等同于 array1 * array2
np.divide(array1, array2)   # 等同于 array1 / array2

运行输出:

复制代码
np.add:       [[ 3  6]     np.subtract:  [[-1 -2]    np.multiply:  [[ 2  8]    np.divide:
               [ 9 12]]                   [-3 -4]]                 [18 32]]      [[0.5 0.5]
                                                                                   [0.5 0.5]]

🔰 ufunc vs math 库np.sin 对数组操作时比 math.sin 快很多(C 层并行);但对单个数值,math.sin 更快(调用开销小)。

8.3 数组与标量运算

当一个 NumPy 数组与一个标量(单个数字)进行运算时,标量会被"广播"到数组的每个元素上。这种机制使得你可以像写数学公式一样写代码------a * 3 + b / 2 会自动对每个元素生效,不需要写 for 循环。

这也是 NumPy 与 Python 列表行为完全不同 的地方------列表的 * 是重复,NumPy 的 * 是逐元素乘法。这个差异在从列表迁移到 NumPy 时最容易踩坑。

python 复制代码
a = np.array([1, 2, 3])
print(a * 3)    # 每个元素乘3
print(a + 10)   # 每个元素加10

运行输出:

复制代码
a * 3 = [3 6 9]
a + 10 = [11 12 13]

💡 与 Python 列表的区别 :Python 列表 [1,2,3] * 3 会把列表重复成 [1,2,3,1,2,3,1,2,3];而 NumPy 数组 np.array([1,2,3]) * 3 是每个元素乘 3。

python 复制代码
# Python 列表的 * 是重复
print([1, 2, 3] * 3)

运行输出:

复制代码
[1, 2, 3, 1, 2, 3, 1, 2, 3]

九、矩阵运算

矩阵运算是线性代数的基础。在机器学习中,神经网络的前向传播本质上就是一系列矩阵乘法:输出 = 权重矩阵 × 输入向量 + 偏置。理解矩阵乘法与元素级乘法的区别是使用 NumPy 做科学计算的关键。

9.1 矩阵乘法

矩阵乘法的规则是"行乘列再求和"------结果矩阵的第 i 行第 j 列等于左矩阵第 i 行与右矩阵第 j 列的对应元素乘积之和。这与元素级乘法(同位置相乘)完全不同,初学者务必通过下面的输出对比来区分。

python 复制代码
a = np.arange(4).reshape(2, 2)  # [[0,1],[2,3]]
b = a.copy()

# 矩阵乘法(点乘)------ 两种写法等价
print(a.dot(b))
print(np.dot(a, b))

运行输出:

复制代码
a:           b:
[[0 1]       [[0 1]
 [2 3]]      [2 3]]

a.dot(b) 矩阵乘法:
[[ 2  3]
 [ 6 11]]

🔰 矩阵乘法规则C[i,j] = Σ A[i,k] × B[k,j],即"行乘列再求和"。

例如 C[0,0] = 0×0 + 1×2 = 2C[1,1] = 2×1 + 3×3 = 11

9.2 矩阵求逆

矩阵求逆是线性代数中的核心操作。逆矩阵的定义是:如果 A × A⁻¹ = I(单位矩阵),则 A⁻¹ 是 A 的逆矩阵。这在解线性方程组 Ax=b 时非常关键------解就是 x = A⁻¹ × b。在机器学习中,线性回归的解析解(最小二乘法)就涉及矩阵求逆。

python 复制代码
a3 = np.linalg.inv(a)
print(a3)
print(a.dot(a3))  # 验证:矩阵 × 逆 = 单位阵

运行输出:

复制代码
np.linalg.inv(a) 矩阵求逆:
[[-1.5  0.5]
 [ 1.   0. ]]

a.dot(inv(a)) 验证:
[[1. 0.]
 [0. 1.]]   ← 确实是单位矩阵!

🔰 注意 :不是所有矩阵都有逆。不可逆矩阵(奇异矩阵)np.linalg.inv 会给出"伪逆"(Moore-Penrose 伪逆),但仍可计算。

运算 函数 公式
矩阵乘法 a.dot(b)np.dot(a,b) Ci,j = Σ Ai,k × Bk,j
矩阵求逆 np.linalg.inv(a) A × A⁻¹ = I
元素级乘法 a * b Ai,j × Bi,j
转置 a.Ta.transpose() 行变列、列变行

十、广播机制(Broadcasting)

广播是 NumPy 最强大也最容易困惑的特性之一。它允许形状不同的数组进行运算,自动将小数组"拉伸"到大数组的形状,而不需要真正复制数据。

为什么要叫"广播"?想象电视台发送信号------一个发射塔发出信号,所有接收设备都能收到。NumPy 中也是如此:一个小数组(如标量或一维向量)的运算效果会"广播"到整个大数组的每个元素上。

广播的核心价值在于避免显式复制数据------你不需要用 for 循环或 tile 函数手动扩展小数组的形状,NumPy 在底层通过"步长"机制虚拟地完成了扩展,既简洁又高效。

10.1 广播规则

最右边的维度开始向左比较,满足以下条件之一即可广播:

  1. 两个维度相等
  2. 其中一个维度为 1

🔰 三步判断法

  1. 从右到左逐个维度比较
  2. 维度相同 → OK
  3. 维度不同但有一个是 1 → 那个为 1 的被"拉伸"
  4. 维度不同且都不为 1 → 报错!

10.2 三种典型情况

情况1:标量 + 数组(低维有1)

python 复制代码
a = np.array([[1, 2, 3],
              [4, 5, 6]])
print(a + 10)

运行输出:

复制代码
[[11 12 13]
 [14 15 16]]

标量 10 被"拉伸"成 [[10,10,10],[10,10,10]] 再相加。

情况2:(4,3) + (3,)(后缘相符)

python 复制代码
a = np.array([[0, 0, 0],
              [10, 10, 10],
              [20, 20, 20],
              [30, 30, 30]])
b = np.array([1, 2, 3])
print(a + b)

运行输出:

复制代码
a shape=(4, 3)
b shape=(3,)
a + b =
[[ 1  2  3]
 [11 12 13]
 [21 22 23]
 [31 32 33]]

b 的 shape (3,) 与 a 的最后一维 3 相等,所以 b 被复制 4 行 变成 (4,3)

情况3:(4,1) + (3,)(后缘不符但低维有1)

python 复制代码
a2 = np.array([[0], [10], [20], [30]])  # shape (4,1)
b2 = np.array([1, 2, 3])                 # shape (3,)
print(a2 + b2)

运行输出:

复制代码
a2 shape=(4, 1), b2 shape=(3,)
a2 + b2 =
[[ 1  2  3]
 [11 12 13]
 [21 22 23]
 [31 32 33]]

a2 的 1 先扩展为 3(→ shape (4,3)),b2 也扩展为 (4,3),然后相加。

💡 广播的本质:不真正复制数据,而是在内存中用"步长"技巧实现虚拟复制,非常高效。

10.3 用 np.newaxis 手动扩展维度

python 复制代码
a = np.array([1, 2, 3])          # shape (3,)
print(a[:, np.newaxis].shape)     # (3, 1) --- 插入新维度
print(a[np.newaxis, :].shape)     # (1, 3)

# np.newaxis 和 None 等价
print(a[:, None].shape)           # (3, 1)

# 看实际数组变化
print(a[:, np.newaxis])           # 变成列向量
print(a[np.newaxis, :])           # 变成行向量

运行输出:

复制代码
a.shape = (3,)
a[:, np.newaxis].shape = (3, 1)
a[np.newaxis, :].shape = (1, 3)
a[:, None].shape = (3, 1)

a[:, np.newaxis] =
[[1]
 [2]
 [3]]

a[np.newaxis, :] =
[[1 2 3]]

🔰 np.newaxisNone 的别名,用于在指定位置插入宽度为 1 的新维度。在手动触发广播时非常有用。


十一、随机模块

11.1 随机种子

随机种子(seed)控制随机数生成器的起始状态。设定相同的种子后,每次运行产生的"随机"数完全一样------这在科学实验中非常重要:别人复现你的实验时需要得到相同的数据和结果。深度学习框架(PyTorch、TensorFlow)中也有类似机制。

要注意一个陷阱:seed 只对其后的第一次随机函数调用生效。如果后续再调用其他随机函数,得到的是新的随机数(不再是种子固定的值)。

python 复制代码
np.random.seed(1000)
r1 = np.random.randint(0, 10)
print(r1)

运行输出:

复制代码
3

🔰 种子的作用 :设了种子后每次运行产生的随机数完全一样。调试模型、复现实验时必用。种子只对其后第一次随机函数生效。

11.2 三种常用分布

NumPy 的随机模块支持多种概率分布。最常用的三种是:randint(均匀分布的整数,每个值出现概率相等)、rand(0~1 之间的均匀分布浮点数)、normal(正态分布/高斯分布,数据集中在均值附近,越远越稀疏)。

理解分布的关键在于知道什么场景用什么:模拟掷骰子用 randint,生成随机概率用 rand,模拟身高体重等自然现象用 normal。下图展示了三种分布的频次直方图------均匀分布是"平的",正态分布是"钟形的"。

python 复制代码
# 均匀分布整数 [0, 10)
print(np.random.randint(0, 10, size=(5, 5)))

# (0,1)均匀分布浮点
print(np.random.rand(5, 5))

# 正态分布 N(5, 10)  --- 均值5, 标准差10
print(np.random.normal(5, 10, size=(5, 5)))

运行输出:

复制代码
randint(0,10,size=(5,5)):
[[3 3 3 6 4]
 [7 2 4 4 2]
 [4 1 2 3 9]
 [0 5 2 1 0]
 [6 8 9 6 1]]

rand(5,5):
[[0.5414 0.4826 0.4765 0.4460 0.8161]
 [0.2744 0.0620 0.9603 0.4581 0.7579]
 ...]

normal(5,10,size=(5,5)):
[[  2.07  -1.08  11.77  27.88   8.29]
 [  5.03   3.73 -15.29  -1.84   9.94]
 ...]
函数 分布 参数说明
np.random.randint(low, high, size) 均匀分布整数 [low, high) 左闭右开
np.random.rand(d0, d1, ...) 均匀分布浮点 [0, 1) 区间
np.random.normal(loc, scale, size) 正态分布 loc=均值, scale=标准差

💡 参数格式差异randintnormalsize=(5,5) 传形状;rand 直接用 rand(5,5) 传维度。


十二、统计函数

统计函数用于从数据中提取摘要信息------方差衡量数据波动程度、均值反映数据中心位置、中位数不受极端值影响。在数据分析中,先对数据做统计描述(均值、标准差、分位数)是最基本的第一步,帮助了解数据的整体分布和异常情况。

NumPy 的统计函数大部分支持 axis 参数,可以按行或按列分别计算------这在处理表格数据时非常实用,比如按行算每个样本的特征总和,按列算每个特征的均值。

python 复制代码
np.random.seed(42)
array1 = np.random.normal(size=(3, 3))

print(array1.var())           # 方差
print(array1.std())            # 标准差
print(array1.mean())           # 均值
print(array1.sum())            # 求和
print(np.median(array1))       # 中位数

# 按行/列求和
print(array1.sum(axis=1))     # 行求和
print(array1.sum(axis=0))     # 列求和

运行输出:

复制代码
数组:
[[ 0.4967 -0.1383  0.6477]
 [ 1.5230 -0.2342 -0.2341]
 [ 1.5792  0.7674 -0.4695]]

方差 var():   0.521639
标准差 std():  0.722246
均值 mean():  0.437561
求和 sum():   3.938051
中位数 median(): 0.496714

行求和 sum(axis=1): [1.006  1.055  1.877]
列求和 sum(axis=0): [3.599  0.395 -0.056]
函数 作用 公式
var() 方差 σ² = Σ(xi - μ)² / N
std() 标准差 σ = √(方差)
mean() 均值 μ = Σxi / N
sum() 求和 Σxi
np.median() 中位数 排序后取中间值
sum(axis=1) 行求和 每行所有列相加
sum(axis=0) 列求和 每列所有行相加

🔰 axis 记忆法axis=0 沿行方向操作(行消失→列求和);axis=1 沿列方向操作(列消失→行求和)。"axis=那个维度,那个维度就消失了"。

12.1 keepdims:保持维度

默认情况下,sum(axis=1) 会把结果"压扁"------二维数组按行求和后变成一维。但有时我们需要保持维度不变(结果是二维),这样后续可以继续用广播与其他二维数组运算。keepdims=True 就是为此设计的。

python 复制代码
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(f"sum(axis=1): {arr.sum(axis=1)}, shape={arr.sum(axis=1).shape}")
print(f"sum(axis=1, keepdims=True):\n{arr.sum(axis=1, keepdims=True)}, shape={arr.sum(axis=1, keepdims=True).shape}")

运行输出:

复制代码
原数组 shape=(2, 3):
[[1 2 3]
 [4 5 6]]

sum(axis=1): [ 6 15], shape=(2,)
sum(axis=1, keepdims=True):
[[ 6]
 [15]], shape=(2, 1)

💡 keepdims 用途 :默认 sum 后维度降低,设 keepdims=True 保持原维度数,方便后续广播运算。

12.2 bincount 与 histogram

bincounthistogram 都是"数频次"的工具,但适用场景不同:bincount 专门用于非负整数 数组,返回每个整数出现多少次,适合统计分类标签分布;histogram 更通用,可以将任意连续值划分到若干个区间(bin)中统计,适合分析数据分布。

python 复制代码
arr = np.array([0, 1, 1, 3, 2, 1, 0, 3, 3, 2])
print(np.bincount(arr))                          # 各值出现次数

hist, edges = np.histogram(arr, bins=4)
print(f"hist={hist}, edges={edges}")

运行输出:

复制代码
原数组: [0 1 1 3 2 1 0 3 3 2]
bincount: [2 3 2 3]           ← 0出现2次, 1出现3次, 2出现2次, 3出现3次
histogram bins=4: hist=[2 3 2 3], edges=[0. 0.75 1.5 2.25 3.]
函数 作用
np.bincount(arr) 统计非负整数各值出现次数
np.histogram(arr, bins) 直方图统计,返回频次和边界

十三、深拷贝与浅拷贝

这是 NumPy 中最容易踩坑的地方之一。很多初学者发现"我只是复制了一份,怎么改副本把原件也改了?"原因在于 NumPy 的"赋值"操作不复制数据------它只是给同一块内存贴了另一个名字。只有显式调用 .copy() 才会真正复制数据。

理解这一点对于避免隐蔽 bug 非常重要------在数据预处理流水线中,如果一个函数"不小心"修改了传入的数组,而调用者还在使用"原件",就会产生难以排查的错误。

python 复制代码
array1 = np.array([1, 2, 3])

# 赋值(浅拷贝)
array2 = array1
array2[0] = 100
print(array1)   # [100   2   3]  ← array1 也变了!

# copy(深拷贝)
array3 = array1.copy()
array3[0] = 10
print(array1)   # [100   2   3]  ← array1 不变

运行输出:

复制代码
赋值后 array2: [100   2   3]
赋值后 array1: [100   2   3]  ← array1也被改了!

深拷贝后 array3: [10  2  3]
深拷贝后 array1: [100   2   3]  ← array1不变
操作 是否共享内存 修改B会影响A吗
B = A(赋值) ✅ 共享 ✅ 会影响
B = A.copy()(深拷贝) ❌ 独立 ❌ 不影响
B = A[:](切片) ✅ 共享 ✅ 会影响

💡 记忆= 只是给数据贴了个新标签,两个名字指向同一块内存;.copy() 是真正复制了一份新数据。这与列表的切片行为一致------切片产生的是视图而非副本。


十四、文件读写

数据分析的第一步通常是"把文件读进来"。NumPy 提供了简单的 loadtxt/savetxt 来读写纯文本文件。虽然 Pandas 的 read_csv 功能更强大(支持表头、混合类型),但 NumPy 的文件读写函数对于纯数值数据来说更轻量、更快。

loadtxt 默认按空格分隔,加 delimiter 参数可以指定分隔符。注意它要求每行有相同数量的值 ------如果文件中有缺失值或文本列,loadtxt 会报错,这时应该用 Pandas。

python 复制代码
import numpy as np

# 从txt文件加载数据
data = np.loadtxt('datingTestSet2.txt', delimiter='\t')
print(data.shape)
print(data[:5])

# 将数组保存到txt文件
array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
np.savetxt('array.txt', array)

运行输出:

复制代码
loadtxt('datingTestSet2.txt', delimiter='\t'):
shape: (1000, 4)
前5行:
[[40920.           8.326976     0.953952     3.      ]
 [14488.           7.153469     1.673904     2.      ]
 [26052.           1.441871     0.805124     1.      ]
 [75136.          13.147394     0.428964     1.      ]
 [38344.           1.669788     0.134296     1.      ]]

保存数组到 array.txt:
[[1 2 3]
 [4 5 6]
 [7 8 9]]
函数 作用 参数说明
np.loadtxt(fname, delimiter) 从文本文件加载数据 delimiter 指定分隔符
np.savetxt(fname, array) 将数组保存为文本文件 默认空格分隔

🔰 loadtxt 假定每行有相同数量的值。delimiter='\t' 表示用制表符分隔。


十五、多项式函数

多项式在科学计算中无处不在------泰勒展开用多项式逼近复杂函数、信号处理用多项式做滤波器设计、数据拟合用多项式做回归。NumPy 的 poly1d 类封装了多项式的常见操作:求值、求导、求积分、求根,让多项式运算像写数学公式一样直观。

python 复制代码
import numpy as np

# 创建多项式:x² + 2x + 3
p = np.poly1d([1, 2, 3])
print(p)

# 求值
print(f"p(2) = {p(2)}")  # 1*4 + 2*2 + 3 = 11

# 求导
print(f"导数: {p.deriv()}")  # 2x + 2

# 求积分
print(f"积分: {p.integ()}")  # (1/3)x³ + x² + 3x

# 求根
print(f"根: {np.roots(p)}")  # x² + 2x + 3 = 0 的解

运行输出:

复制代码
poly1d([1,2,3]) = 
   2
1 x + 2 x + 3

p(2) = 11
导数: 2 x + 2
积分:         3     2
       0.3333 x + 1 x + 3 x
根: [-1.+1.41421356j -1.-1.41421356j]

🔰 系数规则np.poly1d([1, 2, 3]) 中,a[0] 是最高次系数,a[-1] 是常数项。所以 [1,2,3]1x² + 2x + 3。这个顺序与直觉可能相反------我们习惯从低次往高次写,但 NumPy 从高次往低次排。
💡 多项式拟合np.polyfit(x, y, deg) 可以用最小二乘法拟合多项式曲线------给定一组数据点 (x, y) 和多项式次数 deg,返回最佳拟合的系数。例如 np.polyfit(x, y, 2) 拟合二次曲线 ax² + bx + c。这在简单回归分析中很常用,但更复杂的拟合建议用 scikit-learn。


十六、总结

易混淆点速查

易混淆 区别
reshape vs resize reshape 不改原数组返回新数组,resize 直接修改原数组
ravel vs flatten ravel 返回视图(共享内存),flatten 返回副本(独立)
arange vs linspace arange 左闭右开按步长,linspace 左右都闭按个数
* vs dot * 元素级相乘,dot 矩阵乘法
= vs copy = 共享内存,copy 独立内存
split vs array_split split 要求整除,array_split 不要求
axis=0 vs axis=1 axis=0 沿行方向(行消失→列求和),axis=1 沿列方向(列消失→行求和)
astype(int) 截断小数,不是四舍五入
花式索引 [[1,3],[2,1]] 选的是两个 不是子矩阵,子矩阵用 np.ix_()
切片 a[:] 产生视图(共享内存),修改会影响原数组

知识体系总览

复制代码
NumPy
├── 核心对象:ndarray(N维数组)+ ufunc(通用函数)
├── 创建:array / zeros / ones / full / eye / empty / arange / linspace / logspace
├── 属性:shape / ndim / size / dtype
├── 变换:reshape / resize / ravel / flatten / T / squeeze / astype
├── 索引:基本[行,列] / 布尔[条件] / 花式[[列表]]
├── 操作:delete / where / unique
├── 组合:hstack / vstack / concatenate
├── 切割:hsplit / vsplit / array_split
├── 运算:+ - * / % //(元素级)/ dot(矩阵乘法)/ inv(求逆)
├── 广播:形状不同自动拉伸,规则=从右到左逐维比较
├── 随机:randint / rand / normal / seed
├── 统计:var / std / mean / sum / median / bincount / histogram
├── 拷贝:=(浅)/ copy(深)
├── 读写:loadtxt / savetxt
└── 多项式:poly1d / roots / polyfit / deriv / integ
相关推荐
深蓝海拓1 小时前
基于QtPy (PySide6) 的PLC-HMI工程实战记录(七)创建适合HMI项目的数字输入/显示类部件
python
MartinYeung51 小时前
[论文学习]MPIB:医疗提示注入基准——针对LLM临床安全性的系统性评估
人工智能·python·学习
用户8356290780511 小时前
使用 Python 为 PowerPoint 演示文稿添加评论
后端·python
阿图灵2 小时前
OpenCV 绘图五件套:画圆、文本、线段、矩形与椭圆
图像处理·人工智能·python·opencv·计算机视觉·绘图
web行路人2 小时前
AI全栈之旅 · 第一周总结
python
slacker-kian2 小时前
HuggingFace API加载模型超时:用 ModelScopeAPI 替代
人工智能·python·transformer·huggingface·blip·modelscope·blipprocessor
七夜zippoe2 小时前
DolphinDB 高可用部署实战:从容灾设计到故障自动转移
开发语言·python·高可用·容灾·dolphindb
l1258652 小时前
# LangGraph Deep Research Agent 全流程设计:多轮研究、人机协同与真实来源管理
数据库·人工智能·python·算法·自然语言处理·oracle·langchain
青少儿编程课堂3 小时前
图形化编程实战:智能交通灯调度台,一个作品讲透循环、条件与广播
c++·python·算法·bfs·信息学竞赛