NumPy 数据处理:实用指南
NumPy 数据处理是使用 NumPy 库加载、重塑、清理和规约数值数组的实践,其核心“ndarray”对象将同质数据存储在固定大小的连续内存块中。自 2006 年发布以来,NumPy 已成为 SciPy、pandas、scikit-learn 和大多数科学 Python 的基础,因此您在这里养成的习惯会影响到各个方面。
要点
ndarray是平面内存缓冲区上的跨步视图;理解步幅可以解释为什么“重塑”、切片和“转置”很便宜,而“复制”和花哨的索引则不然。- 向量化不仅仅是风格上的——它将循环从解释的 Python 转移到编译的 C,并且对于算术密集型工作来说,加速通常是一到两个数量级。
- 对于大型模拟数组,内存布局(“C”与“F”顺序)和数据类型选择通常比算法选择更重要;包含 10^8 个元素的 float64 数组在进行任何复制之前已消耗 800 MB。
- 对于不适合 RAM 的数组,“numpy.memmap”和 HDF5 支持的访问 (h5py) 让您可以分块进行处理,而无需重写用于 numpy 数据处理的分析代码。
- 再现性取决于显式控制 RNG(
np.random.default_rng(seed))、固定版本以及记录数据类型和形状以及结果。
NumPy 实际上是什么(以及为什么它对处理很重要)
NumPy 的中心抽象是一个类型化的多维数组,具有形状、数据类型和一组描述要跳过多少字节以到达沿每个轴的下一个元素的步幅。这种设计决策(将逻辑形状与物理布局分开)使 NumPy 既快速又灵活。一个 (1000, 1000) float64 数组占用 8 MB 的连续内存;转置它不会移动单个字节,它只会重写步长元数据。切片的行为方式相同:“a[::2]”返回视图,而不是副本。
numpy 数据处理的实际含义是,您需要知道何时持有视图以及何时持有其副本。视图很便宜并且共享内存,因此修改视图会修改原数组。副本是安全的,但会使您的峰值内存加倍。 NumPy 自己的关于数组视图和副本 的文档是权威参考,值得仔细阅读一次,而不是稍后调试静默别名错误。
Dtype 值得同等关注。默认情况下,模拟输出通常以 float64 形式到达,但原子坐标的分子动力学轨迹很少需要超过 float32 来进行可视化,并且整数计数(原子索引、帧编号)应为 int32 或 int64,而不是 float。将 dtype 减半可以使内存流量减半,这对于内存带宽限制的操作通常是主要成本。
核心数据处理流程
用于科学工作的典型 NumPy 数据处理管道有五个步骤:摄取、检查、清理、转换和减少。每个步骤都有惯用的 NumPy 工具,跳过检查步骤是下游错误的最常见来源。
摄取。 np.loadtxt 和 np.genfromtxt 处理纯文本,但对于大文件来说速度很慢,因为它们在 Python 中逐行解析。 np.load 和 .npy/.npz 是快速二进制路径。对于 HDF5(由 HDF Group 维护的高性能计算事实标准),请使用 h5py 或 PyTables,它们将数据集公开为支持部分读取的类似数组的对象。
Related: — Project-based data-science paths with a guided terminal and real datasets.
检查。 在进行任何算术运算之前,请检查 arr.shape、arr.dtype 和 np.isnan(arr).any()。快速的“arr.min()”、“arr.max()”和“arr.mean()”揭示了单位错误和哨兵值(常见的是“-9999”,用作环境数据集中缺失数据的标记)。这个三行检查发现的错误比任何测试套件都多。
清理。 NumPy 中的缺失值由浮点数的“np.nan”表示,NaN 通过设计进行算术传播。使用“np.nanmean”、“np.nanstd”和“np.nansum”来忽略它们,或者使用“np.isnan”来显式屏蔽它们。请注意,整数数组不能包含 NaN——这是读取包含空白的 CSV 数据时的常见陷阱。
变换。 重塑、广播和轴方向操作都在这里进行。 arr.reshape(-1, 3) 将平面坐标流转换为 xyz 三元组; arr - arr.mean(axis=0) 将每列居中; np.einsum 表示张量收缩,否则需要嵌套循环。
Worth a look: — One subscription for university-backed Python and data-science certificates.
规约。 沿轴使用 sum、mean、std、argmin 和 percentile 进行聚合。对于分组规约,“np.add.at”或“np.bincount”处理需要按索引而不是按连续块累积的情况。
矢量化、广播和循环成本
矢量化意味着表达对整个数组的操作,因此 NumPy 将其分派到编译循环。典型示例:使用 Python 双循环计算 10,000 个点之间的成对距离,需要 10^8 次解释迭代;广播形式 np.sqrt(((a[:, None, :] - b[None, :, :])**2).sum(-1)) 在 C 中执行相同的工作,但代价是实例化一个大型中间数组。
广播规则是使 numpy 数据处理变得简洁的机制。 NumPy 从右侧对齐形状并拉伸大小为 1 的维度。“(N, 3)”数组减去“(3,)”数组会从每一行中减去向量。 “(N, 1)”数组乘以“(1, M)”数组会生成“(N, M)”。这些规则记录在 NumPy 广播指南 中,并且熟练掌握这些规则会从数字代码中删除大部分“for”循环。
权衡是内存。广播可以创建比输入大得多的临时数据。当这成为瓶颈时,对计算进行分块或使用带有“optimize=True”的“np.einsum”,这会消除一些中间产物。对于真正抵抗矢量化的循环限制问题,Numba 的“@njit”装饰器将 Python 循环编译为机器代码,并且通常是实用的替代方案。
比较:为工作选择正确的工具
| 任务 | 惯用的 NumPy | 何时该去寻找其他东西? |
|---|---|---|
| 加载 50 GB 轨迹 | np.memmap 或 h5py 分块读取 | 用于并行/云访问的 Dask 或 Zarr |
| 分组聚合 | np.bincount、np.add.at | pandas groupby 用于标记的混合类型数据 |
| 成对距离 | 广播 + einsum | SciPy cdist/pdist (内存优化) |
| 稀疏矩阵 | np.zeros(密集) | SciPy“稀疏”——密集浪费 90% 以上的内存 |
| 自定义元素数学 | 矢量化 ufunc | 当逻辑有分支时,Numba 或 Cython |
| 可重复采样 | np.random.default_rng(种子) | —(这是正确的现代 API) |
numpy 数据处理的模式:NumPy 是正确的默认值,但密集数组是稀疏或标记数据的错误表示,单机数组是内存外数据的错误表示。
内存、数据类型和核外处理
NumPy 科学工作和 numpy 数据处理中的绑定约束通常是内存,而不是 CPU。三种技术可以回答这个问题。
首先,慎重选择数据类型。 np.float32 与 float64 相比,内存减少了一半,并且通常足以满足中间结果; np.int8 或 np.uint16 涵盖了大多数索引和标签数组。 NumPy 在混合运算中会默默向上转换,因此在算术运算后使用 arr.dtype 进行验证。
其次,使用内存映射数组。 np.memmap 将磁盘上的文件映射到地址空间,让您可以像在 RAM 中一样对 100 GB 数组进行切片,并且操作系统仅在所需的块中进行分页。这对于顺序访问模式很有效,但对于整个阵列的随机访问则效果不佳。
第三,分块处理。以 10,000 行为单位读取 HDF5 数据集并累积运行平均值或直方图,无论文件大小如何,都可以保持峰值内存有限。这是流式测序读取的生物信息学管道和减少多十年模型输出的气候工作流程中的标准模式。
一个微妙之处:“arr.copy()”和花哨的索引(“arr[idx_array]”)都分配。在块的紧密循环中,这些分配支配着执行时间。通过 np.copyto 或 ufuncs 的 out= 参数重用预先分配的输出缓冲区可以避免频繁分配内存。
再现性和出处
numpy 数据处理中的可重复数值工作需要控制 NumPy 直接涉及的三个方面:随机性、数据类型和版本。
随机性:不鼓励使用旧全局状态“np.random.seed”。现代 API 是“rng = np.random.default_rng(seed)”,它返回一个隔离的“Generator”,其状态不会在函数之间泄漏。当并行工作程序每个都需要独立的、可复制的流时,这一点很重要。
Dtype:记录您保存的每个数组的数据类型。以 float32 计算的结果和以 float64 计算的结果在最后几位数字上有所不同,比较结果的审阅者应该知道使用了哪一个。使用“np.save”保存会保留数据类型;另存为 CSV 不会执行此操作。
版本:NumPy 的行为在不同版本中发生了变化,影响了结果 - 例如,Windows 上的默认整数类型以及某些归约的处理。将 NumPy 固定在环境文件中并将版本保存在输出元数据中是可重现研究工具的常见做法。 NumPy 发行说明 记录了这些更改。
对于出处,将输入的形状、数据类型和哈希值存储在结果旁边。像“numpy.testing.assert_allclose”这样具有显式容差的工具使回归测试变得有意义而不是脆弱。
将 NumPy 与更广泛的堆栈集成
NumPy 在 numpy 数据处理中很少独立。 pandas 用带标签的轴包装 NumPy 数组,当您的数据具有异构列或有意义的行标签时,它是正确的工具;当数据类型对齐时,使用 .to_numpy() 进行转换是零拷贝。 SciPy 建立在 NumPy 的基础上,用于线性代数、优化和信号处理——对于基本解决之外的任何问题,“scipy.linalg”通常比“numpy.linalg”更受青睐。 scikit-learn 始终使用并返回 NumPy 数组。 Matplotlib 直接绘制它们。
互操作性契约是数组接口,形式化为Python Array API 标准,它允许 CuPy、JAX 和 PyTorch 等库公开与 NumPy 兼容的 API。在这个子集上编写分析代码可以使其以最小的修改移植到 GPU——当模拟超过工作站的大小时,这是一个真正的优势。
需要注意的是:这些库之间的隐式转换会复制数据。使用 CuPy 的“cupy.asarray”通过 PCIe 传输将 NumPy 数组移动到 GPU;将数据驻留在单个设备上并分组传输比每次操作的往返要快得多。
资料来源和进一步阅读
- 数据处理 — 维基百科:数据处理是收集和操作数字数据以产生有意义的信息。数据处理是信息处理的一种形式,…
常见问题
什么是 NumPy 数据处理?
NumPy 数据处理涉及使用 NumPy 库的“ndarray”来加载、清理、转换和减少数值数据。它涵盖了从文件中读取数组、使用 NaN 感知函数处理缺失值、重塑和广播以及沿轴聚合。由于大多数科学 Python 库都依赖于 NumPy,因此它是数值管道的基础层。
NumPy 的数据处理速度比 pandas 快吗?
NumPy 对于同构数值数组更快,因为它直接在连续内存上运行,没有索引或数据类型调度开销。 pandas 编写速度更快,更适合混合类型标记的表格数据。对于大型数值计算,使用“.to_numpy()”将 pandas DataFrame 转换为 NumPy 数组并进行处理是一种常见的优化。
如何处理 NumPy 中的缺失数据?
浮点数组将缺失值表示为“np.nan”,NumPy 提供“np.nanmean”、“np.nanstd”、“np.nansum”和“np.isnan”来处理它们。整数数组无法存储 NaN,因此要么转换为浮点数,要么使用哨兵值加布尔掩码。通过“np.ma”的屏蔽数组提供了一种更结构化的替代方案。
NumPy 可以处理大于 RAM 的数据吗?
是的,使用 np.memmap 将磁盘文件映射到地址空间,或者使用 h5py 读取块中的 HDF5 数据集。两种方法都保持峰值内存有限。对于跨机器的并行或分布式处理,Dask 和 Zarr 将相同的阵列模型扩展到单个节点之外。
NumPy 中的视图和副本有什么区别?
视图共享原始数组的内存缓冲区,并且仅修改形状或步幅元数据,因此它很便宜,但突变会传播。副本分配新的内存并且是独立的。切片和“重塑”通常返回视图;花式索引和“.copy()”返回副本。使用 arr.base 检查数组是否是视图。
如何使 NumPy 结果可重现?
使用“np.random.default_rng(seed)”而不是旧版全局“np.random.seed”,在您的环境中固定 NumPy 版本,并使用输出记录 dtype 和 shape。使用“np.save”而不是 CSV 保留数组以准确保留数据类型。对于测试,请与“np.testing.assert_allclose”和显式容差进行比较。
Frequently asked questions
什么是 NumPy 数据处理?
NumPy 数据处理涉及使用 NumPy 库的 ndarray 来加载、清理、转换和减少数值数据。它涵盖了从文件中读取数组、使用 NaN 感知函数处理缺失值、重塑和广播以及沿轴聚合。由于大多数科学 Python 库都依赖于 NumPy,因此它是数值管道的基础层。
NumPy 的数据处理速度比 pandas 快吗?
NumPy 对于同构数值数组更快,因为它直接在连续内存上运行,没有索引或数据类型调度开销。 pandas 编写速度更快,更适合混合类型标记的表格数据。对于大型数值计算,使用 .to_numpy() 将 pandas DataFrame 转换为 NumPy 数组并进行处理是一种常见的优化。
如何处理 NumPy 中缺失的数据?
浮点数组将缺失值表示为 np.nan,NumPy 提供 np.nanmean、np.nanstd、np.nansum 和 np.isnan 来处理它们。整数数组无法存储 NaN,因此要么转换为浮点数,要么使用哨兵值加布尔掩码。通过 np.ma 的屏蔽数组提供了一种更加结构化的替代方案。
NumPy 可以处理大于 RAM 的数据吗?
是的,使用 np.memmap 将磁盘文件映射到地址空间,或者使用 h5py 读取块中的 HDF5 数据集。两种方法都保持峰值内存有限。对于跨机器的并行或分布式处理,Dask 和 Zarr 将相同的阵列模型扩展到单个节点之外。
NumPy 中的视图和副本有什么区别?
视图共享原始数组的内存缓冲区,并且仅修改形状或步幅元数据,因此它很便宜,但突变会传播。副本分配新的内存并且是独立的。切片和重塑通常会返回视图;奇特的索引和 .copy() 返回副本。使用 arr.base 检查数组是否是视图。
如何使 NumPy 结果可重现?
使用 np.random.default_rng(seed) 而不是旧版全局 np.random.seed,在您的环境中固定 NumPy 版本,并使用输出记录 dtype 和 shape。使用 np.save 而不是 CSV 保留数组以准确保留数据类型。对于测试,请与 np.testing.assert_allclose 和显式容差进行比较。
Learn Python by coding in your browser
Interactive Python and data-science courses you code directly in the browser