数据科学家 NumPy:实用指南
面向数据科学家的 NumPy 是用于数值计算的基本 Python 库:它提供了 ndarray,这是一种固定类型的连续内存块,支持跨 N 维的向量化操作。 NumPy 于 2006 年发布,现已提供 2.x 版本。它是 pandas、SciPy、scikit-learn 以及您将接触到的几乎所有科学 Python 堆栈的基础。
要点
- ndarray 是连续缓冲区上的类型化、步幅视图 - 了解跨步和数据类型可以解释在数据科学中使用 NumPy 时您会遇到的大多数性能和内存惊喜。
- 在典型的数值工作负载中,矢量化比 Python 循环快一到两个数量级,但前提是操作映射到 NumPy 的编译内核上。
- 广播是一组对齐规则,而不是魔术:尺寸从右到左进行比较,并且必须等于或 1。
- NumPy 2.0 更改了 Windows 上的默认整数类型并收紧了提升规则,因此在 1.26 上默默工作的代码可以在 2.x 上更改 dtype 或提升。
- 对于表格工作,pandas 通常是正确的层; NumPy 是数组、线性代数、信号处理和自定义管道的数字核心的正确层。
- 内存布局(C 顺序与 Fortran 顺序)和复制与视图语义决定 10 GB 数组是否适合 RAM 或悄悄翻倍。
数据科学中的 NumPy 到底是什么?
对于数据科学家方法的 numpy,最好将其理解为堆栈其余部分下方的数组基底。当您调用 pandas.DataFrame.to_numpy() 、训练 scikit-learn 估计器或使用 h5py 读取 HDF5 文件块时,数据会存放在 ndarray 中。这种单一对象类型(一种数据类型、一种形状、一个内存缓冲区)使下游库变得快速且可预测。
该库的范围比新来者预期的要窄。 NumPy 不做标记数据、缺失值语义或分组聚合;pandas 负责这些。 NumPy 不进行优化、插值或稀疏线性代数; SciPy 负责这些。 NumPy 执行密集 N 维数组、逐元素数学运算、广播、归约、索引、随机数生成以及 BLAS 和 LAPACK 的线性代数接口。知道边界的位置可以防止在 NumPy 中重新实现 pandas 的常见错误。
ndarray:解释一切的三个属性
ndarray 由三项描述:“shape”、“dtype”和“strides”。形状是逻辑维度。 Dtype 修复了每个元素的解释 — float64、int32、complex128、datetime64[ns] 或结构化记录。步幅给出每个轴的步长字节偏移量。
步幅是 arr[::2] 和 arr.T 是免费的原因。使用步骤进行切片或转置不会移动数据;它返回一个在同一缓冲区上具有不同步幅的新视图。重塑 C 连续数组同样是一种视图。相比之下,花式索引(arr[[0, 5, 9]])和布尔掩码总是分配一个新数组。在处理多 GB 数组的管道中,视图和副本之间的区别就是运行成功与内存交换之间的区别。
使用 numpy 执行数据科学家任务的一个实用习惯是:在任何重要的索引操作之后,检查“arr.base is None”以查看您是否拥有内存,并检查“arr.flags[‘C_CONTIGUOUS’]”以查看布局是否是下游 C 或 Fortran 例程所期望的。 NumPy 自己的内部内存布局文档是这里的权威参考。
Related: — Project-based data-science paths with a guided terminal and real datasets.
NumPy 在数据科学中的使用:它真正赢得一席之地的地方
对于数据科学家的 numpy 工作流程,使用 NumPy 集群分为五个重复作业。
大规模数值预处理。 标准化特征、对数转换计数、裁剪离群值和计算成对距离都是按元素或归约操作。在 ndarray 上执行这些操作可以避免每行的 Python 开销。
线性代数。 最小二乘、通过 SVD 进行的 PCA、协方差估计以及通过“numpy.linalg”求解密集系统,它调用 MATLAB 和 R 使用的相同 BLAS/LAPACK 库。经过良好调整的 OpenBLAS 或 MKL 构建可以比同一台机器上的原始构建快几倍。
Worth a look: — One subscription for university-backed Python and data-science certificates.
随机模拟。 numpy.random.default_rng()(NumPy 1.17 中引入的 Generator API)提供了比传统 RandomState 更可重现、统计上表现更好的流。蒙特卡罗工作、引导重采样和排列测试都在这里进行。
与二进制格式接口。 HDF5、NetCDF、Zarr 和内存映射原始文件都公开类似数组的接口。 np.memmap 允许您通过从磁盘分页来处理大于 RAM 的阵列。
库之间的粘合剂。 pandas、PyTorch 和 xarray 之间的转换通常通过 NumPy。缓冲协议意味着这些转换通常是零拷贝的。
NumPy 对数据科学重要吗?诚实的回答
NumPy 对于数据科学家来说很重要,因为它是一种依赖项,而不是因为它始终是您编写的接口。一名正在工作的数据科学家可能会几个月不直接输入“import numpy as np”,但每个 pandas 操作、每个 scikit-learn 拟合以及每个 matplotlib 绘图都在下面执行 NumPy 代码。
重要性是结构性的。 NumPy 定义了生态系统其他部分都同意的数组 API——该规范现已正式化为 Python 数组 API 标准,它允许 CuPy、JAX 和 PyTorch 等库公开兼容的接口。因此,学习 NumPy 不再是记忆函数,而是更多地学习心理模型,该模型将转移到您以后将使用的每个数组库。
NumPy“不是”答案:大量字符串 ETL、跨异构表的联接、具有不规则时间戳的时间序列重采样,以及任何需要对不适合内存的数据集进行惰性评估的内容。在这些情况下,请使用 pandas、Polars、DuckDB 或 Dask。
矢量化、广播和有效的规则
广播从右侧比较形状。如果两个维度相等或者其中一个维度为 1,则两个维度兼容; size-1 维度被拉伸而不复制。 “(1000, 3)”特征矩阵减去“(3,)”均值向量即可。 (1000, 3) 矩阵减去 (1000,) 向量会引发,因为尾随维度 3 和 1000 不一致 - 并且修复几乎总是 mean[:, None],而不是循环。这是 numpy 中数据科学家工作流程的一个关键概念。
实际代码中会重复出现三种故障模式:
- 意外的外部产品。
a[:, None] * b[None, :]在两个 100k 元素向量上分配 10^10 个浮点数。即 float64 时 80 GB。将其切成块,或使用立即减少的配方。 - 整数溢出。
np.int32算术静默换行。对 int32 中的大量计数求和是负总数的典型来源。 - 视图上的就地操作。
arr[::2] += 1修改父缓冲区。这通常是您想要的,但偶尔也会出现损坏缓存数组的错误。
选择正确的工具:NumPy 与替代工具
| 任务 | 最佳首选 | 为什么 |
|---|---|---|
| 标签表格数据、连接、groupby | pandas 或 Polars | 索引对齐和缺失值语义 |
| 密集数值数组、线性代数 | NumPy | 直接 BLAS/LAPACK 访问,开销最小 |
| 大于 RAM 的数组 | Dask、Zarr 或 np.memmap | 分块或分页执行 |
| GPU 加速数组数学 | CuPy 或 JAX | NumPy 兼容 API,设备执行 |
| 稀疏矩阵 | SciPy 稀疏 | 内存规模与非零 |
| 研究代码的 Autodiff 和 JIT | JAX | 数组程序的函数转换 |
决策规则:如果您的数据具有有意义的行标签和混合列类型,请从 pandas 开始。如果它是同构数字块并且您关心吞吐量,请从 NumPy 开始——这是数据科学家 numpy 的基本工具。如果它不适合内存,请从分块框架开始,然后将其放入每个块内的 NumPy。
切实有效的绩效实践
将 dtype 与问题相匹配。 float32 将内存减半,并且可以以 2:1 FP32:FP64 比率使硬件上的吞吐量加倍,但代价是大约七位小数位的精度。对于迭代求解器和长时间模拟,该误差会累积;对于面向显示的预处理,通常就可以了。
预分配并完成。 在循环中使用 np.append 扩展数组会重新分配每次迭代。将输出分配一次并分块分配。
使用 out= 避免临时变量。 np.multiply(a, b, out=c) 写入现有内存。在大型数组的紧密循环中,这消除了分配压力并改善了缓存行为。
更喜欢减少而不是物化。 np.einsum 和 np.dot 表达收缩而不构建中间数组。 (a[:, None] * b[None, :]).sum(axis=1) 和 a * b.sum() 使用截然不同的内存配置文件计算相同的结果。
知道何时离开 NumPy。 对于具有分支的元素函数,Numba 或 Cython 可以击败矢量化 NumPy,因为它们完全避免了临时数组。对于任何在行上使用 Python 级循环的东西,循环才是问题所在,而不是 NumPy。
NumPy 2.x:发生了什么变化及其重要性
NumPy 2.0 于 2024 年 6 月发布,是自 2006 年以来的第一个主要版本升级。三项更改影响了数据科学家社区 numpy 的工作代码。 Windows 上的默认整数类型从“int32”更改为“int64”,与 Linux 和 macOS 保持一致。 NEP 50 加强了类型提升,以便 Python 标量不再以令人惊讶的方式向上转换数组 - np.float32(1) + 1.0 现在保留 float32。 C API 被重新组织,这破坏了二进制兼容性:必须重新构建针对 1.x 编译的扩展。
对于大多数分析代码来说,迁移是顺利的,但是依赖于隐式向上转换的数字代码可能会更改最后位的结果。在升级生产环境之前针对 2.x 运行测试套件,并将版本固定在可重现的研究工件中。 NumPy 发行说明记录了每一项更改。
实验室组的再现性说明
对于数据科学家来说,numpy 的可重复数值工作不仅仅取决于种子。记录 NumPy 版本、BLAS 实现(OpenBLAS、MKL 和 Accelerate 对于同一操作给出不同的最后一位结果)、线程计数以及提供已发布数据的每个数组的 dtype。 np.show_config() 打印构建详细信息。
浮点求和不具有关联性,因此并行归约可能会因运行而异。如果结果必须位相同,请在单个线程上使用“np.sum”和成对求和,或显式使用 Kahan 求和。对于共享实验室管道,将 NumPy 固定在锁定文件中并将锁定文件与数据一起存储。
资料来源和进一步阅读
- 数据科学 — 维基百科:数据科学是一个跨学科的学术领域,它使用统计学、科学计算、科学方法、处理、科学可视化、算法…
常见问题
NumPy 在数据科学中的用途是什么?
NumPy 提供了大多数科学 Python 库所构建的 N 维数组和向量化运算。数据科学家使用 numpy 执行数据科学家任务,例如数值预处理、线性代数、随机模拟,以及作为 pandas、scikit-learn、PyTorch 和绘图库之间的交换格式。直接使用在自定义管道中很常见;间接使用是普遍存在的。
如果我主要使用 pandas,NumPy 对数据科学重要吗?
是的,因为 pandas 将数字列存储为 NumPy 数组并将其数学委托给 NumPy。了解数据类型、视图与副本以及广播可以解释大多数 pandas 的性能和内存行为。无需直接编写 NumPy 即可提高工作效率,但如果您了解底层,则调试速度会更快。
我应该先学习 NumPy 还是 pandas?
如果您的工作涉及模拟、信号、图像或自定义数值算法,请首先学习 NumPy。如果您的工作是带有标签列和混合类型的表格分析,请首先学习 pandas。在实践中,几个小时的 NumPy(数组、索引、广播、归约)立即让 pandas 不再那么神秘。
与纯 Python 循环相比,NumPy 有多快?
矢量化 NumPy 操作通常比相同数据上的等效 Python 循环快一到两个数量级,因为内部循环在编译的 C 中执行,没有每个元素解释器的开销。当操作无法向量化时、当数组足够小以至于调用开销占主导地位时、或者当向量化形式分配较大的临时变量时,差距会缩小或逆转。
NumPy 是否处理缺失数据?
NumPy 有用于浮点数的“np.nan”和“np.ma”掩码数组,但两者都没有跨数据类型提供 pandas 风格的缺失值语义。从 NumPy 1.24 开始,“np.nan”仅对浮点和复数类型有效,整数数组无法容纳它。对于真正的缺失数据处理,请使用 pandas 可空数据类型或专用框架。
NumPy 2.0 中的哪些变化可能会破坏我的代码?
NumPy 2.0 将 Windows 默认整数更改为 int64,采用了 NEP 50 提升规则,因此 Python 标量不再向上转换数组,并重新组织了 C API,打破了与针对 1.x 构建的扩展的二进制兼容性。大多数分析代码运行不变,但对 dtype 提升敏感的数值代码应重新测试。
Frequently asked questions
NumPy 在数据科学中的用途是什么?
NumPy 提供了大多数科学 Python 库所构建的 N 维数组和向量化运算。数据科学家使用 numpy 执行数据科学家任务,例如数值预处理、线性代数、随机模拟,以及作为 pandas、scikit-learn、PyTorch 和绘图库之间的交换格式。直接使用在自定义管道中很常见;间接使用是普遍存在的。
如果我主要使用 pandas,NumPy 对数据科学重要吗?
是的,因为 pandas 将数字列存储为 NumPy 数组并将其数学委托给 NumPy。了解数据类型、视图与副本以及广播可以解释大多数 pandas 的性能和内存行为。无需直接编写 NumPy 即可提高工作效率,但如果您了解底层,则调试速度会更快。
我应该先学习 NumPy 还是 pandas?
如果您的工作涉及模拟、信号、图像或自定义数值算法,请首先学习 NumPy。如果您的工作是带有标签列和混合类型的表格分析,请首先学习 pandas。在实践中,几个小时的 NumPy(数组、索引、广播、归约)立即让 pandas 不再那么神秘。
与纯 Python 循环相比,NumPy 的速度有多快?
矢量化 NumPy 操作通常比相同数据上的等效 Python 循环快一到两个数量级,因为内部循环在编译的 C 中执行,没有每个元素解释器的开销。当操作无法向量化时、当数组足够小以至于调用开销占主导地位时、或者当向量化形式分配较大的临时变量时,差距会缩小或逆转。
NumPy 是否处理缺失数据?
NumPy 有 np.nan 用于浮点数和 np.ma 掩码数组,但两者都没有提供跨数据类型的 pandas 风格的缺失值语义。从 NumPy 1.24 开始,np.nan 仅对浮点和复数类型有效,整数数组无法容纳它。对于真正的缺失数据处理,请使用 pandas 可空数据类型或专用框架。
NumPy 2.0 中的哪些变化可能会破坏我的代码?
NumPy 2.0 将 Windows 默认整数更改为 int64,采用了 NEP 50 提升规则,因此 Python 标量不再向上转换数组,并重新组织了 C API,打破了与针对 1.x 构建的扩展的二进制兼容性。大多数分析代码运行不变,但对 dtype 提升敏感的数字代码应重新测试。
Learn Python by coding in your browser
Interactive Python and data-science courses you code directly in the browser