行莫
行莫
发布于 2025-11-16 / 4 阅读
0
0

NumPy 框架全面介绍:深入理解 ndarray 的核心

NumPy 框架全面介绍:深入理解 ndarray 的核心

引言

NumPy(Numerical Python)是 Python 科学计算的基础库,也是数据科学、机器学习、深度学习等领域的核心工具。NumPy 的核心是 ndarray(N-dimensional array,N 维数组),这是一个高效的多维数组对象,为 Python 提供了强大的数值计算能力。

本文将深入探讨 NumPy 框架,特别详细地讲解 ndarray 的概念、特性、操作和应用,帮助你全面掌握这个数据科学领域最重要的工具之一。


第一部分:NumPy 简介

什么是 NumPy?

NumPy 是一个开源的 Python 科学计算库,提供了:

  • 高性能的多维数组对象(ndarray)
  • 丰富的数学函数库
  • 线性代数、傅里叶变换等科学计算工具
  • 与 C/C++/Fortran 代码的集成能力

为什么需要 NumPy?

问题:Python 列表的性能限制

# Python 列表的性能问题
import time

# 使用 Python 列表
python_list1 = list(range(1000000))
python_list2 = list(range(1000000))

start = time.time()
result = [a + b for a, b in zip(python_list1, python_list2)]
python_time = time.time() - start
print(f"Python 列表耗时: {python_time:.4f} 秒")

# 使用 NumPy 数组
import numpy as np
numpy_array1 = np.array(range(1000000))
numpy_array2 = np.array(range(1000000))

start = time.time()
result = numpy_array1 + numpy_array2
numpy_time = time.time() - start
print(f"NumPy 数组耗时: {numpy_time:.4f} 秒")
print(f"NumPy 比 Python 列表快 {python_time/numpy_time:.1f} 倍")

输出示例:

Python 列表耗时: 0.1234 秒
NumPy 数组耗时: 0.0012 秒
NumPy 比 Python 列表快 102.8 倍

NumPy 的优势:

  1. 性能:比纯 Python 快 10-100 倍
  2. 内存效率:连续内存存储,减少内存碎片
  3. 向量化操作:对整个数组进行操作,无需循环
  4. 丰富的数学函数:线性代数、统计、傅里叶变换等

NumPy 的安装

# 使用 pip 安装
pip install numpy

# 使用 conda 安装
conda install numpy

# 验证安装
python -c "import numpy as np; print(np.__version__)"

第二部分:ndarray 核心概念

什么是 ndarray?

ndarray(N-dimensional array)是 NumPy 的核心数据结构,表示一个同质的多维数组

关键特性:

  • 同质:数组中所有元素必须是相同的数据类型
  • 多维:可以是一维、二维、三维或更高维度
  • 固定大小:创建后大小不可改变(但可以创建新数组)
  • 连续内存:数据在内存中连续存储,提高访问效率

ndarray 的基本属性

import numpy as np

# 创建一个数组
arr = np.array([1, 2, 3, 4, 5])

# 查看数组的基本属性
print(f"数组维度: {arr.ndim}")           # 1(一维数组)
print(f"数组形状: {arr.shape}")          # (5,)(5 个元素)
print(f"数组大小: {arr.size}")            # 5(元素总数)
print(f"数据类型: {arr.dtype}")          # int64(64 位整数)
print(f"元素大小: {arr.itemsize}")       # 8(每个元素 8 字节)
print(f"总内存: {arr.nbytes}")           # 40(5 * 8 字节)
print(f"数据指针: {arr.data}")           # 内存地址

ndarray 的内存布局

ndarray 在内存中的存储:

一维数组 [1, 2, 3, 4, 5]:
内存布局:| 1 | 2 | 3 | 4 | 5 |
         连续的内存块

二维数组 [[1, 2, 3], [4, 5, 6]]:
内存布局:| 1 | 2 | 3 | 4 | 5 | 6 |
         按行存储(C 风格)或按列存储(Fortran 风格)

优势:

  • 缓存友好:连续内存访问速度快
  • 向量化操作:CPU 可以并行处理多个元素
  • 内存效率:没有 Python 对象的额外开销

第三部分:创建 ndarray

1. 从 Python 列表创建

import numpy as np

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

# 二维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2d)
# [[1 2 3]
#  [4 5 6]]

# 三维数组
arr3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(arr3d)
# [[[1 2]
#   [3 4]]
#  [[5 6]
#   [7 8]]]

2. 使用 NumPy 内置函数创建

2.1 创建全零数组

# 一维全零数组
zeros_1d = np.zeros(5)
print(zeros_1d)  # [0. 0. 0. 0. 0.]

# 二维全零数组
zeros_2d = np.zeros((3, 4))
print(zeros_2d)
# [[0. 0. 0. 0.]
#  [0. 0. 0. 0.]
#  [0. 0. 0. 0.]]

# 指定数据类型
zeros_int = np.zeros((3, 4), dtype=int)
print(zeros_int)
# [[0 0 0 0]
#  [0 0 0 0]
#  [0 0 0 0]]

2.2 创建全一数组

# 全一数组
ones_2d = np.ones((2, 3))
print(ones_2d)
# [[1. 1. 1.]
#  [1. 1. 1.]]

# 指定数据类型
ones_int = np.ones((2, 3), dtype=int)

2.3 创建空数组(未初始化)

# 空数组(值未定义,可能是任意值)
empty_arr = np.empty((3, 3))
print(empty_arr)  # 值不确定,但分配了内存

2.4 创建单位矩阵

# 3x3 单位矩阵
identity = np.eye(3)
print(identity)
# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]

# 非方阵单位矩阵
identity_rect = np.eye(3, 4)
print(identity_rect)
# [[1. 0. 0. 0.]
#  [0. 1. 0. 0.]
#  [0. 0. 1. 0.]]

2.5 创建范围数组

# arange:类似 range,但返回数组
arr1 = np.arange(0, 10, 2)  # [0, 2, 4, 6, 8]
arr2 = np.arange(10)        # [0 1 2 3 4 5 6 7 8 9]

# linspace:创建等间距数组
arr3 = np.linspace(0, 1, 5)  # [0.   0.25 0.5  0.75 1.  ]

# logspace:创建对数等间距数组
arr4 = np.logspace(0, 2, 3)  # [  1.  10. 100.]

2.6 创建随机数组

# 均匀分布随机数 [0, 1)
random_arr = np.random.random((3, 3))

# 标准正态分布随机数
normal_arr = np.random.randn(3, 3)

# 指定范围的随机整数
randint_arr = np.random.randint(0, 10, (3, 3))

# 从指定数组随机选择
choice_arr = np.random.choice([1, 2, 3, 4, 5], size=(3, 3))

3. 从文件创建

# 从文本文件读取
data = np.loadtxt('data.txt')

# 从 CSV 文件读取
data = np.genfromtxt('data.csv', delimiter=',')

# 保存数组到文件
np.savetxt('output.txt', arr)

4. 数组形状操作

# 重塑数组形状
arr = np.arange(12)
reshaped = arr.reshape(3, 4)  # 3x4 数组
print(reshaped)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# 展平数组
flattened = reshaped.flatten()  # 一维数组

# 转置
transposed = reshaped.T  # 4x3 数组

# 改变数组大小(会创建新数组)
resized = np.resize(arr, (3, 5))  # 3x5,会重复元素

第四部分:ndarray 的数据类型

NumPy 支持的数据类型

NumPy 提供了丰富的数据类型,比 Python 内置类型更精确:

# 整数类型
arr_int8 = np.array([1, 2, 3], dtype=np.int8)    # 8 位整数 (-128 到 127)
arr_int16 = np.array([1, 2, 3], dtype=np.int16)  # 16 位整数
arr_int32 = np.array([1, 2, 3], dtype=np.int32)  # 32 位整数
arr_int64 = np.array([1, 2, 3], dtype=np.int64)  # 64 位整数

# 无符号整数
arr_uint8 = np.array([1, 2, 3], dtype=np.uint8)  # 8 位无符号整数 (0 到 255)

# 浮点数类型
arr_float32 = np.array([1.0, 2.0, 3.0], dtype=np.float32)  # 32 位浮点数
arr_float64 = np.array([1.0, 2.0, 3.0], dtype=np.float64)  # 64 位浮点数(默认)

# 复数类型
arr_complex = np.array([1+2j, 3+4j], dtype=np.complex128)

# 布尔类型
arr_bool = np.array([True, False, True], dtype=bool)

# 字符串类型
arr_str = np.array(['hello', 'world'], dtype='U10')  # Unicode 字符串,最大长度 10

数据类型转换

# 创建数组
arr = np.array([1, 2, 3, 4, 5])

# 转换数据类型
arr_float = arr.astype(float)      # 转换为浮点数
arr_int = arr.astype(int)           # 转换为整数
arr_str = arr.astype(str)           # 转换为字符串

# 查看数据类型
print(arr.dtype)        # int64
print(arr_float.dtype)  # float64

为什么数据类型很重要?

# 内存占用对比
arr_int32 = np.array([1, 2, 3], dtype=np.int32)
arr_int64 = np.array([1, 2, 3], dtype=np.int64)

print(f"int32 内存: {arr_int32.nbytes} 字节")  # 12 字节
print(f"int64 内存: {arr_int64.nbytes} 字节")  # 24 字节

# 对于大型数组,选择合适的类型可以节省大量内存
large_arr_int32 = np.zeros((1000, 1000), dtype=np.int32)
large_arr_int64 = np.zeros((1000, 1000), dtype=np.int64)

print(f"int32 数组内存: {large_arr_int32.nbytes / 1024 / 1024:.2f} MB")  # 3.81 MB
print(f"int64 数组内存: {large_arr_int64.nbytes / 1024 / 1024:.2f} MB")  # 7.63 MB

第五部分:ndarray 索引和切片

一维数组索引

arr = np.array([10, 20, 30, 40, 50])

# 基本索引
print(arr[0])    # 10(第一个元素)
print(arr[-1])   # 50(最后一个元素)

# 切片(返回视图,不复制数据)
print(arr[1:4])      # [20 30 40]
print(arr[:3])       # [10 20 30]
print(arr[2:])       # [30 40 50]
print(arr[::2])      # [10 30 50](步长为 2)

# 布尔索引
mask = arr > 25
print(mask)          # [False False  True  True  True]
print(arr[mask])     # [30 40 50]
print(arr[arr > 25]) # [30 40 50](简化写法)

# 花式索引(Fancy Indexing)
indices = [0, 2, 4]
print(arr[indices])  # [10 30 50]

二维数组索引

arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 基本索引
print(arr[0, 0])     # 1(第 0 行,第 0 列)
print(arr[1, 2])     # 6(第 1 行,第 2 列)

# 行索引
print(arr[0])        # [1 2 3](第 0 行)
print(arr[1, :])     # [4 5 6](第 1 行,所有列)

# 列索引
print(arr[:, 0])     # [1 4 7](所有行,第 0 列)
print(arr[:, 1])     # [2 5 8](所有行,第 1 列)

# 切片
print(arr[0:2, 1:3])  # [[2 3]
                      #  [5 6]]

# 布尔索引
mask = arr > 5
print(mask)
# [[False False False]
#  [False False  True]
#  [ True  True  True]]
print(arr[mask])     # [6 7 8 9]

# 条件索引
print(arr[arr > 5])  # [6 7 8 9]

多维数组索引

arr = np.arange(24).reshape(2, 3, 4)
print(arr.shape)  # (2, 3, 4)

# 三维数组索引
print(arr[0, 0, 0])    # 0
print(arr[1, 2, 3])    # 23

# 切片
print(arr[0, :, :])    # 第 0 个二维切片
print(arr[:, 0, :])    # 所有二维切片的第 0 行
print(arr[:, :, 0])    # 所有二维切片的第 0 列

视图 vs 副本

重要概念: NumPy 的切片返回视图(view),而不是副本(copy)。

arr = np.array([1, 2, 3, 4, 5])

# 切片创建视图
view = arr[1:4]
view[0] = 99

print(arr)   # [ 1 99  3  4  5](原数组也被修改!)
print(view)  # [99  3  4]

# 创建副本
copy = arr[1:4].copy()
copy[0] = 100

print(arr)   # [ 1 99  3  4  5](原数组不变)
print(copy)  # [100   3   4]

何时使用副本:

  • 需要独立修改数据时
  • 避免意外修改原数组时

第六部分:ndarray 的数学运算

1. 元素级运算(向量化操作)

NumPy 的核心优势之一是向量化操作,可以对整个数组进行操作,无需循环。

arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([6, 7, 8, 9, 10])

# 加法(元素级)
result = arr1 + arr2  # [ 7  9 11 13 15]

# 减法
result = arr2 - arr1  # [5 5 5 5 5]

# 乘法(元素级,不是矩阵乘法)
result = arr1 * arr2  # [ 6 14 24 36 50]

# 除法
result = arr2 / arr1  # [6.  3.5 2.66666667 2.25 2.]

# 幂运算
result = arr1 ** 2    # [ 1  4  9 16 25]

# 取模
result = arr2 % arr1  # [0 1 2 1 0]

2. 标量与数组运算

arr = np.array([1, 2, 3, 4, 5])

# 标量加法
result = arr + 10     # [11 12 13 14 15]

# 标量乘法
result = arr * 2      # [ 2  4  6  8 10]

# 标量除法
result = arr / 2      # [0.5 1.  1.5 2.  2.5]

# 标量幂
result = arr ** 2     # [ 1  4  9 16 25]

3. 通用函数(ufunc)

NumPy 提供了大量的通用函数(universal functions),可以对数组进行逐元素操作。

3.1 数学函数

arr = np.array([1, 2, 3, 4, 5])

# 三角函数
angles = np.array([0, np.pi/2, np.pi])
sin_values = np.sin(angles)
cos_values = np.cos(angles)

# 指数和对数
exp_values = np.exp(arr)        # e^x
log_values = np.log(arr)        # 自然对数
log10_values = np.log10(arr)    # 以 10 为底的对数
sqrt_values = np.sqrt(arr)      # 平方根

# 绝对值
abs_values = np.abs([-1, -2, 3, -4])  # [1 2 3 4]

# 向上/向下取整
ceil_values = np.ceil([1.2, 2.7, 3.1])  # [2. 3. 4.]
floor_values = np.floor([1.2, 2.7, 3.1])  # [1. 2. 3.]

3.2 统计函数

arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

# 基本统计
print(np.sum(arr))        # 55(总和)
print(np.mean(arr))       # 5.5(平均值)
print(np.std(arr))        # 2.8722813232690143(标准差)
print(np.var(arr))        # 8.25(方差)
print(np.min(arr))        # 1(最小值)
print(np.max(arr))        # 10(最大值)
print(np.median(arr))     # 5.5(中位数)

# 百分位数
print(np.percentile(arr, 25))  # 3.25(25% 分位数)
print(np.percentile(arr, 75))  # 7.75(75% 分位数)

# 多维数组统计(指定轴)
arr_2d = np.array([[1, 2, 3], [4, 5, 6]])
print(np.sum(arr_2d, axis=0))   # [5 7 9](沿列求和)
print(np.sum(arr_2d, axis=1))   # [6 15](沿行求和)

4. 矩阵运算

# 矩阵乘法(使用 @ 或 dot)
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

# 矩阵乘法
result1 = A @ B
result2 = np.dot(A, B)
# [[19 22]
#  [43 50]]

# 内积
vec1 = np.array([1, 2, 3])
vec2 = np.array([4, 5, 6])
dot_product = np.dot(vec1, vec2)  # 32

# 外积
outer_product = np.outer(vec1, vec2)
# [[ 4  5  6]
#  [ 8 10 12]
#  [12 15 18]]

5. 线性代数运算

import numpy.linalg as la

A = np.array([[1, 2], [3, 4]])

# 矩阵转置
A_T = A.T
# [[1 3]
#  [2 4]]

# 矩阵逆
A_inv = la.inv(A)
# [[-2.   1. ]
#  [ 1.5 -0.5]]

# 行列式
det = la.det(A)  # -2.0

# 特征值和特征向量
eigenvalues, eigenvectors = la.eig(A)
print(f"特征值: {eigenvalues}")
print(f"特征向量:\n{eigenvectors}")

# 矩阵的秩
rank = la.matrix_rank(A)  # 2

# 求解线性方程组 Ax = b
b = np.array([5, 11])
x = la.solve(A, b)  # [1. 2.]

第七部分:广播(Broadcasting)

什么是广播?

广播是 NumPy 的一个强大特性,允许不同形状的数组进行数学运算。

广播规则

  1. 如果两个数组的维度数不同,维度较小的数组会在前面补 1
  2. 如果两个数组在某个维度上的大小相同,或者其中一个为 1,则可以广播
  3. 如果两个数组在所有维度上都兼容,则可以广播

广播示例

# 示例 1:标量与数组
arr = np.array([[1, 2, 3], [4, 5, 6]])
result = arr + 10  # 标量 10 被广播到整个数组
# [[11 12 13]
#  [14 15 16]]

# 示例 2:不同形状的数组
arr1 = np.array([[1, 2, 3], [4, 5, 6]])  # 形状: (2, 3)
arr2 = np.array([10, 20, 30])            # 形状: (3,)
# arr2 被广播为 (1, 3),然后扩展为 (2, 3)
result = arr1 + arr2
# [[11 22 33]
#  [14 25 36]]

# 示例 3:列向量与行向量
col = np.array([[1], [2], [3]])  # 形状: (3, 1)
row = np.array([10, 20, 30])     # 形状: (3,)
# row 被广播为 (1, 3),然后扩展为 (3, 3)
result = col + row
# [[11 21 31]
#  [12 22 32]
#  [13 23 33]]

广播的维度扩展过程

# 详细理解广播过程
arr1 = np.array([[1, 2, 3], [4, 5, 6]])  # 形状: (2, 3)
arr2 = np.array([10, 20, 30])            # 形状: (3,)

# 步骤 1:arr2 的形状从 (3,) 变为 (1, 3)
# 步骤 2:arr2 沿第 0 轴扩展为 (2, 3)
# 步骤 3:执行元素级运算

# 可视化:
# arr1:        arr2 (扩展后):
# [[1 2 3]     [[10 20 30]
#  [4 5 6]]     [10 20 30]]

广播的应用场景

# 1. 归一化数据
data = np.random.randn(5, 3)
mean = data.mean(axis=0)      # 每列的平均值
std = data.std(axis=0)        # 每列的标准差
normalized = (data - mean) / std  # 广播归一化

# 2. 添加偏置项
weights = np.random.randn(10, 5)
bias = np.random.randn(5)
output = weights + bias  # 每行都加上偏置

# 3. 图像处理
image = np.random.randn(100, 100, 3)  # RGB 图像
brightness = np.array([0.1, 0.2, 0.3])  # RGB 亮度调整
brightened = image + brightness  # 广播到每个像素

第八部分:ndarray 的高级操作

1. 数组拼接

arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6], [7, 8]])

# 垂直拼接(沿行方向)
vstack = np.vstack([arr1, arr2])
# [[1 2]
#  [3 4]
#  [5 6]
#  [7 8]]

# 水平拼接(沿列方向)
hstack = np.hstack([arr1, arr2])
# [[1 2 5 6]
#  [3 4 7 8]]

# 通用拼接
concatenated = np.concatenate([arr1, arr2], axis=0)  # 垂直
concatenated = np.concatenate([arr1, arr2], axis=1)  # 水平

2. 数组分割

arr = np.arange(12).reshape(3, 4)

# 垂直分割
vsplit = np.vsplit(arr, 3)  # 分成 3 个数组

# 水平分割
hsplit = np.hsplit(arr, 2)  # 分成 2 个数组

# 通用分割
split = np.split(arr, 2, axis=0)  # 沿行分割
split = np.split(arr, 2, axis=1)  # 沿列分割

3. 数组复制和视图

arr = np.array([1, 2, 3, 4, 5])

# 浅拷贝(视图)
view = arr.view()
view[0] = 99
print(arr)   # [99  2  3  4  5](原数组也被修改)

# 深拷贝(副本)
copy = arr.copy()
copy[0] = 100
print(arr)   # [99  2  3  4  5](原数组不变)
print(copy)  # [100   2   3   4   5]

4. 数组排序

arr = np.array([3, 1, 4, 1, 5, 9, 2, 6])

# 排序(返回新数组)
sorted_arr = np.sort(arr)  # [1 1 2 3 4 5 6 9]

# 原地排序
arr.sort()  # 修改原数组

# 获取排序索引
indices = np.argsort(arr)  # [1 3 6 0 2 4 7 5]

# 多维数组排序
arr_2d = np.array([[3, 1], [2, 4]])
sorted_2d = np.sort(arr_2d, axis=1)  # 每行排序

5. 数组搜索

arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

# 查找元素索引
index = np.where(arr == 5)  # (array([4]),)

# 查找满足条件的元素索引
indices = np.where(arr > 5)  # (array([5, 6, 7, 8, 9]),)

# 查找最大值/最小值的索引
max_idx = np.argmax(arr)  # 9
min_idx = np.argmin(arr)  # 0

# 查找非零元素索引
nonzero = np.nonzero([0, 1, 0, 2, 0, 3])  # (array([1, 3, 5]),)

6. 数组去重

arr = np.array([1, 2, 2, 3, 3, 3, 4, 5])

# 去重
unique = np.unique(arr)  # [1 2 3 4 5]

# 去重并返回索引
unique, indices = np.unique(arr, return_index=True)

# 去重并返回计数
unique, counts = np.unique(arr, return_counts=True)

第九部分:ndarray 的性能优化

1. 向量化操作 vs 循环

import numpy as np
import time

# 创建大型数组
arr = np.random.randn(1000000)

# 方法 1:使用循环(慢)
start = time.time()
result1 = []
for x in arr:
    result1.append(x * 2)
time1 = time.time() - start

# 方法 2:使用 NumPy 向量化(快)
start = time.time()
result2 = arr * 2
time2 = time.time() - start

print(f"循环耗时: {time1:.4f} 秒")
print(f"向量化耗时: {time2:.4f} 秒")
print(f"速度提升: {time1/time2:.1f} 倍")

2. 内存布局优化

# C 风格(行优先)vs Fortran 风格(列优先)
arr_c = np.array([[1, 2, 3], [4, 5, 6]], order='C')
arr_f = np.array([[1, 2, 3], [4, 5, 6]], order='F')

# 对于行遍历,C 风格更快
# 对于列遍历,F 风格更快

3. 避免不必要的复制

# 使用视图而不是副本
arr = np.arange(1000000)

# 不好的做法:创建副本
subset = arr[100:200].copy()  # 如果不需要修改原数组,不需要 copy

# 好的做法:使用视图
subset = arr[100:200]  # 更高效

4. 使用适当的数据类型

# 如果不需要高精度,使用 float32 而不是 float64
arr_float32 = np.array([1.0, 2.0, 3.0], dtype=np.float32)  # 节省一半内存
arr_float64 = np.array([1.0, 2.0, 3.0], dtype=np.float64)

第十部分:ndarray 的实际应用

1. 图像处理

import numpy as np
from PIL import Image

# 将图像转换为 NumPy 数组
img = Image.open('image.jpg')
img_array = np.array(img)  # 形状: (height, width, channels)

# 图像操作
# 调整亮度
brightened = img_array + 50
brightened = np.clip(brightened, 0, 255)  # 限制在 [0, 255]

# 转换为灰度图
gray = np.mean(img_array, axis=2).astype(np.uint8)

# 图像翻转
flipped = np.flip(img_array, axis=1)  # 水平翻转

# 调整大小
resized = img_array[::2, ::2]  # 缩小到 1/4

2. 数据分析

# 模拟销售数据
sales_data = np.random.randint(100, 1000, (365, 5))  # 365 天,5 个产品

# 计算每日总销售额
daily_total = np.sum(sales_data, axis=1)

# 计算每个产品的平均销售额
product_avg = np.mean(sales_data, axis=0)

# 找出销售额最高的日期
best_day = np.argmax(daily_total)

# 计算每个产品的增长率
growth_rate = (sales_data[-1] - sales_data[0]) / sales_data[0] * 100

3. 科学计算

# 数值积分(使用梯形法则)
def integrate_trapezoidal(func, a, b, n=1000):
    x = np.linspace(a, b, n+1)
    y = func(x)
    h = (b - a) / n
    return h * (np.sum(y) - (y[0] + y[-1]) / 2)

# 使用示例
result = integrate_trapezoidal(np.sin, 0, np.pi)
print(f"∫sin(x)dx from 0 to π ≈ {result:.6f}")  # 应该接近 2.0

# 求解微分方程(欧拉法)
def euler_method(f, y0, t_span, dt):
    t = np.arange(t_span[0], t_span[1] + dt, dt)
    y = np.zeros_like(t)
    y[0] = y0
    for i in range(len(t) - 1):
        y[i+1] = y[i] + dt * f(t[i], y[i])
    return t, y

4. 机器学习数据预处理

# 特征标准化
def standardize(X):
    mean = np.mean(X, axis=0)
    std = np.std(X, axis=0)
    return (X - mean) / std

# 特征归一化(到 [0, 1])
def normalize(X):
    min_val = np.min(X, axis=0)
    max_val = np.max(X, axis=0)
    return (X - min_val) / (max_val - min_val)

# 创建批次数据
def create_batches(X, y, batch_size):
    n_samples = X.shape[0]
    indices = np.random.permutation(n_samples)
    for i in range(0, n_samples, batch_size):
        batch_indices = indices[i:i+batch_size]
        yield X[batch_indices], y[batch_indices]

第十一部分:ndarray 与其他库的集成

1. 与 Pandas 集成

import pandas as pd
import numpy as np

# Pandas DataFrame 转 NumPy 数组
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
arr = df.values  # 或 df.to_numpy()

# NumPy 数组转 Pandas DataFrame
arr = np.array([[1, 2, 3], [4, 5, 6]])
df = pd.DataFrame(arr, columns=['A', 'B', 'C'])

2. 与 Matplotlib 集成

import matplotlib.pyplot as plt
import numpy as np

# 创建数据
x = np.linspace(0, 10, 100)
y = np.sin(x)

# 绘图
plt.plot(x, y)
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Sin Wave')
plt.show()

# 2D 图像
data = np.random.randn(100, 100)
plt.imshow(data, cmap='viridis')
plt.colorbar()
plt.show()

3. 与 SciPy 集成

from scipy import optimize, integrate
import numpy as np

# 优化
def objective(x):
    return x**2 + 10*np.sin(x)

result = optimize.minimize(objective, x0=0)
print(f"最小值: {result.x}")

# 积分
result = integrate.quad(lambda x: np.exp(-x**2), -np.inf, np.inf)
print(f"积分结果: {result[0]}")

第十二部分:ndarray 最佳实践

1. 内存管理

# 及时释放不需要的数组
large_arr = np.random.randn(10000, 10000)
# 使用完后
del large_arr  # 释放内存

# 使用上下文管理器
with np.errstate(divide='ignore'):
    result = 1 / arr  # 忽略除零警告

2. 错误处理

# 检查数组形状
if arr.shape != (3, 4):
    raise ValueError("数组形状不正确")

# 检查数据类型
if arr.dtype != np.float64:
    arr = arr.astype(np.float64)

# 处理 NaN 和 Inf
arr = np.array([1, 2, np.nan, 4, np.inf])
clean_arr = arr[np.isfinite(arr)]  # 移除 NaN 和 Inf

3. 代码优化

# 好的做法:使用向量化操作
result = arr1 + arr2  # 快

# 不好的做法:使用循环
result = []
for a, b in zip(arr1, arr2):
    result.append(a + b)  # 慢

# 好的做法:使用广播
result = arr + np.array([1, 2, 3])  # 利用广播

# 不好的做法:手动扩展
result = arr + np.tile([1, 2, 3], (arr.shape[0], 1))  # 浪费内存

4. 文档和注释

def normalize_array(arr, axis=0):
    """
    标准化数组。
    
    参数:
        arr: ndarray,输入数组
        axis: int,沿哪个轴计算均值和标准差
    
    返回:
        ndarray,标准化后的数组
    """
    mean = np.mean(arr, axis=axis, keepdims=True)
    std = np.std(arr, axis=axis, keepdims=True)
    return (arr - mean) / std

总结

NumPy 的 ndarray 是 Python 科学计算的基础,具有以下核心特点:

ndarray 的核心特性

  1. 同质多维数组:所有元素类型相同,支持多维
  2. 高效性能:连续内存存储,向量化操作
  3. 丰富的操作:数学运算、线性代数、统计函数
  4. 广播机制:灵活的形状兼容性
  5. 内存效率:比 Python 列表更节省内存

关键要点

  • 向量化操作:始终优先使用 NumPy 的向量化操作,避免 Python 循环
  • 数据类型选择:根据需求选择合适的数据类型,平衡精度和内存
  • 视图 vs 副本:理解切片返回视图,需要独立数据时使用 copy()
  • 广播规则:掌握广播机制,可以写出更简洁高效的代码
  • 内存管理:注意大型数组的内存使用,及时释放不需要的数据

应用领域

  • 数据科学:数据处理、统计分析
  • 机器学习:特征工程、模型训练
  • 科学计算:数值计算、仿真
  • 图像处理:图像操作、计算机视觉
  • 信号处理:音频、视频处理

掌握 NumPy 和 ndarray 是进入数据科学和机器学习领域的基础,它为你提供了高效、灵活的数值计算能力。通过本文的学习,你应该能够:

  1. ✅ 理解 ndarray 的核心概念和特性
  2. ✅ 熟练创建和操作多维数组
  3. ✅ 掌握索引、切片和布尔索引
  4. ✅ 使用向量化操作和广播机制
  5. ✅ 进行数学运算和线性代数操作
  6. ✅ 优化代码性能
  7. ✅ 在实际项目中应用 NumPy

继续学习建议:

  • 深入学习 Pandas(基于 NumPy 的数据分析库)
  • 学习 Matplotlib(数据可视化)
  • 探索 SciPy(科学计算扩展)
  • 了解 NumPy 的底层实现(C 扩展)

参考资料


评论