002-NumPy数组基础

002-NumPy数组基础

学习目标

通过本章节的学习,你将能够:

  • 理解ndarray对象的核心概念
  • 掌握数组与Python列表的区别
  • 了解数组的内存布局和存储机制
  • 学会创建和操作基本数组
  • 理解数组的维度概念

1. ndarray对象简介

1.1 什么是ndarray

ndarray(N-dimensional array)是NumPy的核心数据结构,它是一个:

  • 同质化数组:所有元素具有相同的数据类型
  • 多维数组:支持任意维度的数组
  • 高效存储:使用连续的内存布局
  • 向量化运算:支持整个数组的批量操作
ndarray对象
数据缓冲区
数据类型dtype
形状shape
步长strides
连续内存块
int32, float64, bool等
各维度大小
内存访问步长
高效访问
类型安全
多维结构
灵活布局

1.2 数组vs列表对比

# 文件路径: examples/array_vs_list.py
import numpy as np
import sys

def compare_array_list():
    """比较NumPy数组和Python列表"""
    print("NumPy数组 vs Python列表对比")
    print("=" * 50)
    
    # 创建相同数据的列表和数组
    python_list = [1, 2, 3, 4, 5]
    numpy_array = np.array([1, 2, 3, 4, 5])
    
    print("1. 数据类型")
    print(f"Python列表: {type(python_list)}")
    print(f"NumPy数组:  {type(numpy_array)}")
    
    print("\n2. 元素类型")
    print(f"列表元素类型: {type(python_list[0])}")
    print(f"数组元素类型: {numpy_array.dtype}")
    
    print("\n3. 内存使用")
    print(f"列表内存: {sys.getsizeof(python_list)} bytes")
    print(f"数组内存: {numpy_array.nbytes} bytes")
    
    print("\n4. 运算支持")
    try:
        # 列表运算(会报错)
        # list_result = python_list * 2  # 这会重复列表
        print("列表乘法: [1, 2, 3] * 2 =", [1, 2, 3] * 2)
        
        # 数组运算(向量化)
        array_result = numpy_array * 2
        print("数组乘法: array * 2 =", array_result)
    except Exception as e:
        print(f"运算错误: {e}")
    
    print("\n5. 性能对比")
    import time
    
    # 大数据量测试
    size = 1000000
    large_list = list(range(size))
    large_array = np.arange(size)
    
    # 列表求和
    start = time.time()
    list_sum = sum(large_list)
    list_time = time.time() - start
    
    # 数组求和
    start = time.time()
    array_sum = np.sum(large_array)
    array_time = time.time() - start
    
    print(f"列表求和时间: {list_time:.6f}秒")
    print(f"数组求和时间: {array_time:.6f}秒")
    print(f"性能提升: {list_time/array_time:.1f}倍")

if __name__ == "__main__":
    compare_array_list()

1.3 数组的优势

特性Python列表NumPy数组
数据类型混合类型同质类型
内存效率较低高效
计算速度较慢快速
向量化运算不支持支持
多维支持需要嵌套原生支持
数学函数有限丰富
广播机制支持

2. 数组的维度概念

2.1 维度层次结构

数组维度
0维 标量
1维 向量
2维 矩阵
3维 张量
N维 高维张量
单个数值
shape: ()
一维数组
shape: (n,)
二维数组
shape: (m,n)
三维数组
shape: (l,m,n)
多维数组
shape: (d1,d2,...,dn)

2.2 各维度示例

# 文件路径: examples/array_dimensions.py
import numpy as np

def demonstrate_dimensions():
    """演示不同维度的数组"""
    print("NumPy数组维度演示")
    print("=" * 40)
    
    # 0维数组(标量)
    scalar = np.array(42)
    print("0维数组(标量):")
    print(f"值: {scalar}")
    print(f"形状: {scalar.shape}")
    print(f"维度: {scalar.ndim}")
    print(f"大小: {scalar.size}")
    print()
    
    # 1维数组(向量)
    vector = np.array([1, 2, 3, 4, 5])
    print("1维数组(向量):")
    print(f"值: {vector}")
    print(f"形状: {vector.shape}")
    print(f"维度: {vector.ndim}")
    print(f"大小: {vector.size}")
    print()
    
    # 2维数组(矩阵)
    matrix = np.array([[1, 2, 3], 
                       [4, 5, 6]])
    print("2维数组(矩阵):")
    print(f"值:\n{matrix}")
    print(f"形状: {matrix.shape}")
    print(f"维度: {matrix.ndim}")
    print(f"大小: {matrix.size}")
    print()
    
    # 3维数组(张量)
    tensor = np.array([[[1, 2], [3, 4]], 
                       [[5, 6], [7, 8]]])
    print("3维数组(张量):")
    print(f"值:\n{tensor}")
    print(f"形状: {tensor.shape}")
    print(f"维度: {tensor.ndim}")
    print(f"大小: {tensor.size}")
    print()
    
    # 可视化3维数组结构
    print("3维数组结构解析:")
    print(f"第0个2D切片:\n{tensor[0]}")
    print(f"第1个2D切片:\n{tensor[1]}")

if __name__ == "__main__":
    demonstrate_dimensions()

2.3 形状(Shape)详解

# 文件路径: examples/array_shape.py
import numpy as np

def explore_array_shape():
    """探索数组形状的概念"""
    print("数组形状(Shape)详解")
    print("=" * 30)
    
    # 不同形状的数组
    arrays = [
        np.array([1, 2, 3]),                    # (3,)
        np.array([[1, 2, 3]]),                  # (1, 3)
        np.array([[1], [2], [3]]),              # (3, 1)
        np.array([[1, 2], [3, 4], [5, 6]]),     # (3, 2)
        np.array([[[1, 2]], [[3, 4]]]),         # (2, 1, 2)
    ]
    
    for i, arr in enumerate(arrays, 1):
        print(f"数组{i}:")
        print(f"值: {arr if arr.ndim <= 2 else '3D数组'}")
        if arr.ndim > 2:
            print(f"详细值:\n{arr}")
        print(f"形状: {arr.shape}")
        print(f"维度: {arr.ndim}")
        print(f"总元素数: {arr.size}")
        print("-" * 20)
    
    # 形状的含义
    print("\n形状含义解释:")
    arr_2d = np.array([[1, 2, 3, 4], 
                       [5, 6, 7, 8], 
                       [9, 10, 11, 12]])
    print(f"2D数组:\n{arr_2d}")
    print(f"形状 {arr_2d.shape} 表示:")
    print(f"  - 第0维(行): {arr_2d.shape[0]} 行")
    print(f"  - 第1维(列): {arr_2d.shape[1]} 列")
    
    arr_3d = np.array([[[1, 2], [3, 4]], 
                       [[5, 6], [7, 8]], 
                       [[9, 10], [11, 12]]])
    print(f"\n3D数组形状 {arr_3d.shape} 表示:")
    print(f"  - 第0维(深度): {arr_3d.shape[0]} 个2D数组")
    print(f"  - 第1维(行): {arr_3d.shape[1]} 行")
    print(f"  - 第2维(列): {arr_3d.shape[2]} 列")

if __name__ == "__main__":
    explore_array_shape()

3. 数组的内存布局

3.1 内存存储方式

2D数组 [[1,2,3],[4,5,6]]
行优先 C-order
列优先 F-order
内存: [1,2,3,4,5,6]
内存: [1,4,2,5,3,6]
连续行存储
连续列存储

3.2 内存布局示例

# 文件路径: examples/memory_layout.py
import numpy as np

def explore_memory_layout():
    """探索数组的内存布局"""
    print("数组内存布局探索")
    print("=" * 30)
    
    # 创建2D数组
    arr = np.array([[1, 2, 3], 
                    [4, 5, 6]])
    print(f"原始数组:\n{arr}")
    print(f"形状: {arr.shape}")
    print(f"数据类型: {arr.dtype}")
    print(f"总字节数: {arr.nbytes}")
    print(f"每个元素字节数: {arr.itemsize}")
    
    # 内存相关属性
    print("\n内存属性:")
    print(f"是否C连续: {arr.flags['C_CONTIGUOUS']}")
    print(f"是否F连续: {arr.flags['F_CONTIGUOUS']}")
    print(f"步长(strides): {arr.strides}")
    
    # 步长的含义
    print("\n步长含义:")
    print(f"行步长: {arr.strides[0]} bytes (移动到下一行需要跳过的字节数)")
    print(f"列步长: {arr.strides[1]} bytes (移动到下一列需要跳过的字节数)")
    
    # 不同内存布局的数组
    print("\n不同内存布局:")
    
    # C-order (行优先)
    arr_c = np.array([[1, 2, 3], [4, 5, 6]], order='C')
    print(f"C-order数组步长: {arr_c.strides}")
    
    # F-order (列优先)
    arr_f = np.array([[1, 2, 3], [4, 5, 6]], order='F')
    print(f"F-order数组步长: {arr_f.strides}")
    
    # 内存视图
    print("\n内存数据查看:")
    print(f"C-order原始字节: {arr_c.tobytes()}")
    print(f"F-order原始字节: {arr_f.tobytes()}")

def demonstrate_strides():
    """演示步长的工作原理"""
    print("\n步长工作原理演示")
    print("=" * 30)
    
    arr = np.array([[10, 20, 30], 
                    [40, 50, 60]], dtype=np.int32)
    
    print(f"数组:\n{arr}")
    print(f"步长: {arr.strides}")
    print(f"数据类型大小: {arr.itemsize} bytes")
    
    # 手动计算元素位置
    print("\n元素内存位置计算:")
    base_address = arr.__array_interface__['data'][0]
    
    for i in range(arr.shape[0]):
        for j in range(arr.shape[1]):
            offset = i * arr.strides[0] + j * arr.strides[1]
            print(f"arr[{i},{j}] = {arr[i,j]}, 内存偏移: {offset} bytes")

if __name__ == "__main__":
    explore_memory_layout()
    demonstrate_strides()

4. 数组的基本属性

4.1 核心属性总览

# 文件路径: examples/array_attributes.py
import numpy as np

def explore_array_attributes():
    """探索数组的各种属性"""
    print("NumPy数组属性详解")
    print("=" * 40)
    
    # 创建示例数组
    arr = np.array([[1, 2, 3, 4], 
                    [5, 6, 7, 8], 
                    [9, 10, 11, 12]], dtype=np.float64)
    
    print(f"示例数组:\n{arr}")
    print("\n=== 基本属性 ===")
    
    # 形状和维度
    print(f"shape (形状):     {arr.shape}")
    print(f"ndim (维度数):    {arr.ndim}")
    print(f"size (元素总数):  {arr.size}")
    
    # 数据类型
    print(f"\ndtype (数据类型): {arr.dtype}")
    print(f"itemsize (元素字节数): {arr.itemsize}")
    print(f"nbytes (总字节数): {arr.nbytes}")
    
    # 内存信息
    print(f"\n=== 内存信息 ===")
    print(f"strides (步长):   {arr.strides}")
    print(f"flags (标志):")
    for flag, value in arr.flags.items():
        print(f"  {flag}: {value}")
    
    # 数据缓冲区信息
    print(f"\n=== 缓冲区信息 ===")
    print(f"data: {arr.data}")
    print(f"base: {arr.base}")
    
    # 实用属性
    print(f"\n=== 实用属性 ===")
    print(f"T (转置):")
    print(arr.T)
    print(f"\nflat (扁平迭代器): {list(arr.flat)}")
    print(f"real (实部): {arr.real}")
    print(f"imag (虚部): {arr.imag}")

def compare_dtypes():
    """比较不同数据类型的数组"""
    print("\n\n数据类型对比")
    print("=" * 30)
    
    data = [1, 2, 3, 4, 5]
    dtypes = [np.int8, np.int32, np.int64, np.float32, np.float64]
    
    for dtype in dtypes:
        arr = np.array(data, dtype=dtype)
        print(f"{dtype.__name__:>8}: itemsize={arr.itemsize}, nbytes={arr.nbytes}")

if __name__ == "__main__":
    explore_array_attributes()
    compare_dtypes()

4.2 属性详细说明

属性说明示例
shape数组各维度的大小(3, 4)
ndim数组的维度数2
size数组元素总数12
dtype元素数据类型float64
itemsize每个元素的字节数8
nbytes数组总字节数96
strides各维度的步长(32, 8)
flags内存布局标志C_CONTIGUOUS: True
data数据缓冲区<memory at 0x...>
base基础数组对象None 或 数组对象

5. 数组的基本操作

5.1 数组访问

# 文件路径: examples/array_access.py
import numpy as np

def demonstrate_array_access():
    """演示数组访问方法"""
    print("数组访问方法演示")
    print("=" * 30)
    
    # 1维数组访问
    arr_1d = np.array([10, 20, 30, 40, 50])
    print(f"1D数组: {arr_1d}")
    print(f"第一个元素: arr_1d[0] = {arr_1d[0]}")
    print(f"最后一个元素: arr_1d[-1] = {arr_1d[-1]}")
    print(f"切片: arr_1d[1:4] = {arr_1d[1:4]}")
    
    # 2维数组访问
    arr_2d = np.array([[1, 2, 3], 
                       [4, 5, 6], 
                       [7, 8, 9]])
    print(f"\n2D数组:\n{arr_2d}")
    print(f"元素访问: arr_2d[1, 2] = {arr_2d[1, 2]}")
    print(f"行访问: arr_2d[1] = {arr_2d[1]}")
    print(f"列访问: arr_2d[:, 1] = {arr_2d[:, 1]}")
    print(f"子矩阵: arr_2d[0:2, 1:3] =\n{arr_2d[0:2, 1:3]}")
    
    # 3维数组访问
    arr_3d = np.array([[[1, 2], [3, 4]], 
                       [[5, 6], [7, 8]]])
    print(f"\n3D数组:\n{arr_3d}")
    print(f"元素访问: arr_3d[1, 0, 1] = {arr_3d[1, 0, 1]}")
    print(f"2D切片: arr_3d[0] =\n{arr_3d[0]}")

def demonstrate_array_modification():
    """演示数组修改"""
    print("\n\n数组修改演示")
    print("=" * 20)
    
    arr = np.array([[1, 2, 3], 
                    [4, 5, 6], 
                    [7, 8, 9]])
    print(f"原始数组:\n{arr}")
    
    # 单个元素修改
    arr[1, 1] = 99
    print(f"\n修改arr[1,1]=99后:\n{arr}")
    
    # 行修改
    arr[0] = [10, 20, 30]
    print(f"\n修改第0行后:\n{arr}")
    
    # 列修改
    arr[:, 2] = [100, 200, 300]
    print(f"\n修改第2列后:\n{arr}")
    
    # 切片修改
    arr[1:3, 0:2] = [[11, 22], [33, 44]]
    print(f"\n修改子矩阵后:\n{arr}")

if __name__ == "__main__":
    demonstrate_array_access()
    demonstrate_array_modification()

5.2 数组复制

# 文件路径: examples/array_copy.py
import numpy as np

def demonstrate_array_copy():
    """演示数组复制的不同方式"""
    print("数组复制方式对比")
    print("=" * 30)
    
    original = np.array([[1, 2, 3], [4, 5, 6]])
    print(f"原始数组:\n{original}")
    
    # 1. 赋值(共享内存)
    assignment = original
    print(f"\n1. 赋值操作")
    print(f"assignment is original: {assignment is original}")
    print(f"共享内存: {np.shares_memory(original, assignment)}")
    
    # 修改赋值变量
    assignment[0, 0] = 999
    print(f"修改assignment后,原数组: \n{original}")
    
    # 恢复原始值
    original[0, 0] = 1
    
    # 2. 视图(共享数据)
    view = original.view()
    print(f"\n2. 视图操作")
    print(f"view is original: {view is original}")
    print(f"共享内存: {np.shares_memory(original, view)}")
    print(f"view.base is original: {view.base is original}")
    
    # 修改视图
    view[0, 1] = 888
    print(f"修改view后,原数组: \n{original}")
    
    # 恢复原始值
    original[0, 1] = 2
    
    # 3. 深拷贝(独立内存)
    deep_copy = original.copy()
    print(f"\n3. 深拷贝操作")
    print(f"deep_copy is original: {deep_copy is original}")
    print(f"共享内存: {np.shares_memory(original, deep_copy)}")
    
    # 修改深拷贝
    deep_copy[0, 2] = 777
    print(f"修改deep_copy后,原数组: \n{original}")
    print(f"deep_copy: \n{deep_copy}")
    
    # 4. 切片(视图)
    slice_view = original[0:1, :]
    print(f"\n4. 切片操作")
    print(f"切片结果: {slice_view}")
    print(f"共享内存: {np.shares_memory(original, slice_view)}")
    
    # 修改切片
    slice_view[0, 0] = 666
    print(f"修改切片后,原数组: \n{original}")

def memory_efficiency_demo():
    """演示内存效率"""
    print("\n\n内存效率演示")
    print("=" * 20)
    
    # 创建大数组
    large_array = np.random.random((1000, 1000))
    print(f"大数组内存: {large_array.nbytes / 1024 / 1024:.2f} MB")
    
    # 视图不占用额外内存
    view = large_array[::2, ::2]  # 每隔一个元素取样
    print(f"视图形状: {view.shape}")
    print(f"视图是否共享内存: {np.shares_memory(large_array, view)}")
    
    # 拷贝占用额外内存
    copy = large_array[::2, ::2].copy()
    print(f"拷贝形状: {copy.shape}")
    print(f"拷贝是否共享内存: {np.shares_memory(large_array, copy)}")
    print(f"拷贝内存: {copy.nbytes / 1024 / 1024:.2f} MB")

if __name__ == "__main__":
    demonstrate_array_copy()
    memory_efficiency_demo()

6. 数组与标量的运算

6.1 向量化运算

# 文件路径: examples/vectorized_operations.py
import numpy as np

def demonstrate_vectorized_operations():
    """演示向量化运算"""
    print("向量化运算演示")
    print("=" * 30)
    
    arr = np.array([[1, 2, 3], 
                    [4, 5, 6]])
    print(f"原始数组:\n{arr}")
    
    # 数组与标量运算
    print("\n数组与标量运算:")
    print(f"arr + 10:\n{arr + 10}")
    print(f"arr * 2:\n{arr * 2}")
    print(f"arr ** 2:\n{arr ** 2}")
    print(f"arr / 2:\n{arr / 2}")
    
    # 数学函数
    print("\n数学函数:")
    print(f"np.sqrt(arr):\n{np.sqrt(arr)}")
    print(f"np.sin(arr):\n{np.sin(arr)}")
    print(f"np.exp(arr):\n{np.exp(arr)}")
    
    # 比较运算
    print("\n比较运算:")
    print(f"arr > 3:\n{arr > 3}")
    print(f"arr == 5:\n{arr == 5}")
    
    # 逻辑运算
    print("\n逻辑运算:")
    condition1 = arr > 2
    condition2 = arr < 6
    print(f"(arr > 2) & (arr < 6):\n{condition1 & condition2}")

def compare_with_loops():
    """比较向量化运算与循环的性能"""
    print("\n\n向量化 vs 循环性能对比")
    print("=" * 30)
    
    import time
    
    # 创建大数组
    size = 1000000
    arr = np.random.random(size)
    
    # 向量化运算
    start = time.time()
    result_vectorized = arr ** 2 + np.sin(arr)
    vectorized_time = time.time() - start
    
    # 循环运算
    start = time.time()
    result_loop = np.zeros(size)
    for i in range(size):
        result_loop[i] = arr[i] ** 2 + np.sin(arr[i])
    loop_time = time.time() - start
    
    print(f"向量化运算时间: {vectorized_time:.6f}秒")
    print(f"循环运算时间: {loop_time:.6f}秒")
    print(f"性能提升: {loop_time/vectorized_time:.1f}倍")
    
    # 验证结果一致性
    print(f"结果一致性: {np.allclose(result_vectorized, result_loop)}")

if __name__ == "__main__":
    demonstrate_vectorized_operations()
    compare_with_loops()

实践练习

练习1:数组属性探索

创建不同维度的数组,探索它们的各种属性。

# 练习提示
# TODO: 创建以下数组并分析其属性
# 1. 1D数组: [1, 2, 3, 4, 5]
# 2. 2D数组: 3x4的矩阵
# 3. 3D数组: 2x3x4的张量
# 分析每个数组的shape, ndim, size, dtype, strides等属性

练习2:内存布局实验

比较C-order和F-order数组的性能差异。

# 练习提示
# TODO: 实现以下功能
# 1. 创建相同数据的C-order和F-order数组
# 2. 比较行访问和列访问的性能
# 3. 分析内存布局对性能的影响

练习3:向量化运算应用

实现一个简单的图像处理函数,使用向量化运算。

# 练习提示
# TODO: 实现以下功能
# 1. 创建一个模拟的灰度图像(2D数组)
# 2. 实现亮度调整(加法运算)
# 3. 实现对比度调整(乘法运算)
# 4. 实现图像反转(255 - 像素值)

总结

通过本章节的学习,你已经:

理解了ndarray的核心概念:同质化、多维、高效的数组对象
掌握了数组与列表的区别:性能、功能、内存效率等方面
学会了数组的维度概念:从0维标量到N维张量
了解了内存布局机制:C-order、F-order、步长等概念
掌握了数组的基本操作:访问、修改、复制等
体验了向量化运算的威力:高效的批量计算

关键要点

  1. ndarray是NumPy的核心:理解其特性是使用NumPy的基础
  2. 向量化运算是关键优势:避免显式循环,提高性能
  3. 内存布局影响性能:了解步长和连续性概念
  4. 视图vs拷贝的区别:合理使用可以节省内存

下一步学习

在下一章节中,我们将学习NumPy数组的创建方法,包括:

  • 各种数组创建函数
  • 从现有数据创建数组
  • 特殊数组的生成
  • 数组初始化的最佳实践

💡 学习建议: 多动手实践本章的代码示例,特别是内存布局和向量化运算的部分。

下一步

003-数组创建方法 - 学习创建NumPy数组的各种方法


更新记录:

  • 2024-01-15: 创建文档
  • 版本: v1.0.0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lvjesus

码力充电

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值