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 的优势:
- 性能:比纯 Python 快 10-100 倍
- 内存效率:连续内存存储,减少内存碎片
- 向量化操作:对整个数组进行操作,无需循环
- 丰富的数学函数:线性代数、统计、傅里叶变换等
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,则可以广播
- 如果两个数组在所有维度上都兼容,则可以广播
广播示例
# 示例 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 的核心特性
- 同质多维数组:所有元素类型相同,支持多维
- 高效性能:连续内存存储,向量化操作
- 丰富的操作:数学运算、线性代数、统计函数
- 广播机制:灵活的形状兼容性
- 内存效率:比 Python 列表更节省内存
关键要点
- 向量化操作:始终优先使用 NumPy 的向量化操作,避免 Python 循环
- 数据类型选择:根据需求选择合适的数据类型,平衡精度和内存
- 视图 vs 副本:理解切片返回视图,需要独立数据时使用
copy() - 广播规则:掌握广播机制,可以写出更简洁高效的代码
- 内存管理:注意大型数组的内存使用,及时释放不需要的数据
应用领域
- 数据科学:数据处理、统计分析
- 机器学习:特征工程、模型训练
- 科学计算:数值计算、仿真
- 图像处理:图像操作、计算机视觉
- 信号处理:音频、视频处理
掌握 NumPy 和 ndarray 是进入数据科学和机器学习领域的基础,它为你提供了高效、灵活的数值计算能力。通过本文的学习,你应该能够:
- ✅ 理解 ndarray 的核心概念和特性
- ✅ 熟练创建和操作多维数组
- ✅ 掌握索引、切片和布尔索引
- ✅ 使用向量化操作和广播机制
- ✅ 进行数学运算和线性代数操作
- ✅ 优化代码性能
- ✅ 在实际项目中应用 NumPy
继续学习建议:
- 深入学习 Pandas(基于 NumPy 的数据分析库)
- 学习 Matplotlib(数据可视化)
- 探索 SciPy(科学计算扩展)
- 了解 NumPy 的底层实现(C 扩展)