当前位置: 首页 > article >正文

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.2 ndarray解剖课:多维数组的底层实现

在这里插入图片描述

1.2 《ndarray解剖课:多维数组的底层实现》

内容介绍

NumPy 的 ndarray 是其核心数据结构,用于高效处理多维数组。在这篇文章中,我们将深入解析 ndarray 的底层实现,探讨其内存结构、维度、数据类型、步长等关键概念,并通过实验验证这些概念的实际应用。

1.2.1 ndarray与Python列表的核心差异

ndarray 和 Python 列表是两种不同的数据结构,它们在内存布局和性能上有显著的差异。下面是 ndarray 和 Python 列表的核心差异对比表:

特性ndarrayPython 列表
内存布局连续的内存块(固定大小)动态分配的内存(指向对象的指针)
数据类型统一的数据类型(dtype)混合的数据类型(可以包含任意类型的对象)
访问速度高效的向量化操作较慢的迭代访问
修改成本低(视图和副本)高(需要重新分配内存)
支持的运算广泛的数学和科学计算功能有限的列表操作
数据对齐自动对齐(通过步长)无对齐
计算性能高(利用C/C++实现)低(纯Python实现)
文件读写支持 .npy 和 .npz 文件格式不支持二进制文件格式,需要额外的库支持
集成性与 Pandas、Scikit-learn 等科学计算库高度集成与标准库高度集成,但与其他科学计算库集成度较低
1.2.2 ndarray内存结构3D示意图

为了更好地理解 ndarray 的内存结构,我们绘制一个 3D 示意图,展示 ndarray 如何在内存中存储多维数组。

元数据
内存块
3, 3
维度 (shape)
8 (64-bit float)
数据类型 (dtype)
(24, 8)
步长 (strides)
元素1
数据
元素2
元素3
元素4
元素5
元素6
元素7
元素8
元素9
NumPy ndarray
内存块
元数据

内存布局示意图(三维数组示例)

内存地址0x10000x10040x10080x100C0x10100x1014
三维索引[0,0,0][0,0,1][0,1,0][0,1,1][1,0,0][1,0,1]
二维展开[0,0][0,1][1,0][1,1][2,0][2,1]
一维展开012345

内存布局验证实验

import numpy as np

# 创建基础数组
base_arr = np.arange(6, dtype=np.int32)
print(f"原始数组ID: {id(base_arr)}")  # 输出原始数组内存地址

# 创建视图
view_arr = base_arr[::2]  # 步长切片创建视图
print(f"视图数组ID: {id(view_arr)}")  # 地址不同但共享数据

# 创建副本
copy_arr = base_arr.copy()  # 完整内存复制
print(f"副本数组ID: {id(copy_arr)}")  # 全新内存地址

# 修改视图影响原始数组
view_arr[0] = 100
print("修改视图后的原始数组:", base_arr)  # 输出[100  1   2   3   4   5]
1.2.3 维度(shape)、数据类型(dtype)、步长(strides)的关联关系

ndarray 的三个关键属性是 shape(维度)、dtype(数据类型)和 strides(步长)。它们之间的关系如下:

  • shape:表示数组的形状,即每个维度的大小。例如,shape=(3, 3) 表示一个 3x3 的二维数组。
  • dtype:表示数组中每个元素的数据类型。例如,dtype=np.float64 表示数组中的元素是 64 位浮点数。
  • strides:表示在内存中从一个元素移动到下一个元素所需的字节数。例如,在一个 shape=(3, 3)dtype=np.float64 的数组中,步长 strides=(24, 8) 表示从一个行到下一个行需要移动 24 个字节,从一个列到下一个列需要移动 8 个字节。

步长计算公式推导

对于形状为 ( d 1 , d 2 , . . . , d n ) (d_1,d_2,...,d_n) (d1,d2,...,dn)的数组,第 k k k维步长:
s t r i d e k = ( ∏ i = k + 1 n d i ) × i t e m s i z e stride_k = \left( \prod_{i=k+1}^{n} d_i \right) \times itemsize stridek=(i=k+1ndi)×itemsize

示例:三维数组(2,3,4),数据类型int32(4字节)

axis0_stride = 3*4*4 = 48 字节
axis1_stride = 4*4 = 16 字节
axis2_stride = 4 字节
1.2.4 不同初始化方式的内存分配对比(zeros vs empty)

NumPy 提供了多种初始化数组的方法,其中 np.zerosnp.empty 是两个常用的方法。我们将通过实验对比它们的内存分配方式。

import numpy as np

# 创建一个 3x3 的零数组
zeros_array = np.zeros((3, 3), dtype=np.float64)
print("零数组:")
print(zeros_array)

# 创建一个 3x3 的未初始化数组
empty_array = np.empty((3, 3), dtype=np.float64)
print("未初始化数组:")
print(empty_array)

# 验证两个数组的内存地址
print("零数组的内存地址:", id(zeros_array))
print("未初始化数组的内存地址:", id(empty_array))

# 验证两个数组的相同元素是否共享内存
a = zeros_array[0, 0]
b = empty_array[0, 0]
print("零数组的首元素内存地址:", id(a))
print("未初始化数组的首元素内存地址:", id(b))

注释:

# 导入 NumPy 库,并将其别名为 np
import numpy as np

# 创建一个 3x3 的零数组
# np.zeros 是 NumPy 中用于创建全零数组的函数
# 传入数组的形状和数据类型作为参数
zeros_array = np.zeros((3, 3), dtype=np.float64)
print("零数组:")  # 打印零数组
print(zeros_array)

# 创建一个 3x3 的未初始化数组
# np.empty 是 NumPy 中用于创建未初始化数组的函数
# 传入数组的形状和数据类型作为参数
empty_array = np.empty((3, 3), dtype=np.float64)
print("未初始化数组:")  # 打印未初始化数组
print(empty_array)

# 验证两个数组的内存地址
# id() 函数用于获取对象的内存地址
print("零数组的内存地址:", id(zeros_array))
print("未初始化数组的内存地址:", id(empty_array))

# 验证两个数组的相同元素是否共享内存
# 获取零数组和未初始化数组的首元素
a = zeros_array[0, 0]
b = empty_array[0, 0]
print("零数组的首元素内存地址:", id(a))
print("未初始化数组的首元素内存地址:", id(b))
1.2.5 数组元属性操作实验(shape修改的边界条件)

ndarrayshape 属性可以动态修改,但有一些边界条件需要遵守。我们将通过实验验证这些边界条件。

import numpy as np

# 创建一个 3x3 的数组
array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float64)
print("原始数组:")
print(array)

# 修改数组的形状为 1x9
array.shape = (1, 9)
print("修改后的数组(1x9):")
print(array)

# 修改数组的形状为 9x1
array.shape = (9, 1)
print("修改后的数组(9x1):")
print(array)

# 尝试修改数组的形状为 4x3
try:
    array.shape = (4, 3)
except ValueError as e:
    print("尝试修改形状为 4x3 时的错误:", e)

# 尝试修改数组的形状为 3x3x3
try:
    array.shape = (3, 3, 3)
except ValueError as e:
    print("尝试修改形状为 3x3x3 时的错误:", e)

# 修改数组的形状为 3x3
array.shape = (3, 3)
print("恢复数组形状为 3x3:")
print(array)

注释:

# 导入 NumPy 库,并将其别名为 np
import numpy as np

# 创建一个 3x3 的数组
# np.array 是 NumPy 中用于创建数组的函数
# 传入二维列表,每个子列表代表数组的一行,指定数据类型为 64 位浮点数
array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float64)
print("原始数组:")  # 打印原始数组
print(array)

# 修改数组的形状为 1x9
# .shape 属性用于获取或设置数组的形状
array.shape = (1, 9)
print("修改后的数组(1x9):")  # 打印修改后的数组
print(array)

# 修改数组的形状为 9x1
array.shape = (9, 1)
print("修改后的数组(9x1):")  # 打印修改后的数组
print(array)

# 尝试修改数组的形状为 4x3
# 这将导致 ValueError,因为数组的总元素数(9)不等于目标形状的总元素数(12)
try:
    array.shape = (4, 3)
except ValueError as e:
    print("尝试修改形状为 4x3 时的错误:", e)

# 尝试修改数组的形状为 3x3x3
# 这将导致 ValueError,因为数组的总元素数(9)不等于目标形状的总元素数(27)
try:
    array.shape = (3, 3, 3)
except ValueError as e:
    print("尝试修改形状为 3x3x3 时的错误:", e)

# 修改数组的形状为 3x3
# 成功修改回原形状
array.shape = (3, 3)
print("恢复数组形状为 3x3:")  # 打印恢复后的数组
print(array)

总结

通过这篇文章,我们深入解析了 NumPy 的 ndarray 的底层实现,探讨了其内存结构、维度、数据类型、步长等关键概念,并通过实验验证了这些概念的实际应用。希望这些内容能帮助你更好地理解和使用 NumPy。

参考文献或资料

参考资料名称链接
NumPy 官方文档https://numpy.org/doc/
Python 官方文档https://docs.python.org/3/
NumPy 入门指南https://numpy.org/devdocs/user/quickstart.html
NumPy 源码分析https://github.com/numpy/numpy
NumPy 速查表https://www.kaggle.com/learn/overview
NumPy 实战案例https://www.tensorflow.org/tutorials/quickstart/beginner
NumPy 书籍推荐https://www.springer.com/gp/book/9781484242452
NumPy 视频教程https://www.youtube.com/watch?v=QUT1VHiLmmI
NumPy 交互式学习https://colab.research.google.com/
Python 内存管理https://docs.python.org/3/c-api/memory.html
C 语言内存管理https://en.wikipedia.org/wiki/C_memory_allocation
数据结构与算法https://www.geeksforgeeks.org/
深度学习中的数组操作https://pytorch.org/tutorials/beginner/basics/tensorqs_tutorial.html
科学计算库对比https://www.tensorflow.org/compare
高效计算技术https://en.wikipedia.org/wiki/High-performance_computing
编程社区讨论https://stackoverflow.com/questions/tagged/numpy

希望这篇文章能帮助你在 NumPy 的学习和使用中更进一步。如果有任何问题或需要进一步的帮助,欢迎留言讨论!


http://www.kler.cn/a/518912.html

相关文章:

  • 【精选】基于数据挖掘的招聘信息分析与市场需求预测系统 职位分析、求职者趋势分析 职位匹配、人才趋势、市场需求分析数据挖掘技术 职位需求分析、人才市场趋势预测
  • Flutter中PlatformView在鸿蒙中的使用
  • debian12.9编译freeswitch1.10.12【默认安装】
  • 【Flask】在Flask应用中使用Flask-Limiter进行简单CC攻击防御
  • RV1126画面质量四:GOP改善画质
  • 红黑树实现
  • 「 机器人 」扑翼飞行器混合控制策略缺点浅谈
  • 在centos下使用containerd管理容器:5分钟从docker转型到containerd
  • go并发原语源码系列(二)sync.WaitGroup
  • How to learn html?基于chatGLM-b生成示例(仅供参考)
  • 【C++】类与对象初级应用篇:打造自定义日期类与日期计算器(2w5k字长文附源码)
  • 双注意力模块DAB详解及代码复现
  • 05.KNN算法总结
  • 重构(2)判断型函数和判断型变量的命名
  • 蓝桥杯单片机(九)定时器的进阶综合案例
  • CAPL高级应用
  • 深入理解 Window 对象:属性与方法详解
  • 模块初阶学习
  • S4 HANA更改Tax base Amount的字段控制
  • NR_shell运行流程简析
  • Pyecharts之特殊图表的独特展示
  • fatal: unable to access ‘https://github
  • ### 2.5.3 二叉树的基本操作
  • 租赁管理系统如何优化商业资产管理与提升租赁效率探讨
  • 声明,这些内容和我无关
  • 【Axure 模版素材】Arco Design组件库 - AxureMost