NumPy - 科学计算基础库
NumPy(Numerical Python)是 Python 科学计算的核心库,提供了高性能的多维数组对象和各种工具来处理这些数组。它是几乎所有数据科学和机器学习库的基础。
什么是 NumPy?
NumPy 是 Python 中用于科学计算的基础包,主要特点包括:
- ndarray:高效的多维数组对象
- 向量化运算:无需编写循环即可对整个数组进行操作
- 广播机制:不同形状的数组之间可以进行运算
- 集成 C/C++/Fortran 代码:提供高性能计算能力
- 丰富的数学函数库:线性代数、傅里叶变换、随机数生成等
安装 NumPy
# 使用 pip 安装
pip install numpy
# 使用 conda 安装(推荐用于数据科学)
conda install numpy
基础概念
导入 NumPy
import numpy as np
按照惯例,NumPy 通常被导入为 np,这是社区广泛接受的命名约定。
为什么使用 NumPy 而不是 Python 列表?
import numpy as np
import time
# Python 列表
python_list = list(range(1000000))
# NumPy 数组
numpy_array = np.arange(1000000)
# 性能对比:每个元素乘以 2
start = time.time()
python_result = [x * 2 for x in python_list]
print(f"Python 列表耗时: {time.time() - start:.4f} 秒")
start = time.time()
numpy_result = numpy_array * 2
print(f"NumPy 数组耗时: {time.time() - start:.4f} 秒")
# 通常 NumPy 比 Python 列表快 10-100 倍
NumPy 的优势:
- 性能:NumPy 数组在内存中是连续存储的,操作由预编译的 C 代码执行
- 内存效率:NumPy 数组比 Python 列表占用更少的内存
- 功能丰富:提供了大量数学函数和线性代数运算
- 向量化:无需显式循环即可对整个数组进行操作
创建数组
从 Python 列表创建
import numpy as np
# 一维数组
a = np.array([1, 2, 3, 4, 5])
print(a) # [1 2 3 4 5]
print(type(a)) # <class 'numpy.ndarray'>
# 二维数组(矩阵)
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(b)
# [[1 2 3]
# [4 5 6]
# [7 8 9]]
# 指定数据类型
c = np.array([1, 2, 3], dtype=np.float32)
print(c) # [1. 2. 3.]
print(c.dtype) # float32
数据类型(dtype):
| 类型 | 说明 | 范围 |
|---|---|---|
int8 | 8位有符号整数 | -128 ~ 127 |
int16 | 16位有符号整数 | -32768 ~ 32767 |
int32 | 32位有符号整数 | -2³¹ ~ 2³¹-1 |
int64 | 64位有符号整数 | -2⁶³ ~ 2⁶³-1 |
uint8 | 8位无符号整数 | 0 ~ 255 |
float32 | 单精度浮点数 | ~6-7位有效数字 |
float64 | 双精度浮点数 | ~15-16位有效数字 |
bool | 布尔值 | True/False |
complex64 | 复数 | 两个32位浮点数 |
使用内置函数创建数组
import numpy as np
# zeros() - 全零数组
zeros = np.zeros((3, 4)) # 3行4列的全零数组
print(zeros)
# [[0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 0. 0. 0.]]
# ones() - 全一数组
ones = np.ones((2, 3, 4)) # 2个3x4的三维数组
print(ones.shape) # (2, 3, 4)
# full() - 填充指定值
full = np.full((2, 3), 7) # 填充7
print(full)
# [[7 7 7]
# [7 7 7]]
# eye() - 单位矩阵
identity = np.eye(3) # 3x3单位矩阵
print(identity)
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# arange() - 类似 range()
arr = np.arange(0, 10, 2) # 从0到10,步长2
print(arr) # [0 2 4 6 8]
# linspace() - 等间距数组
lin = np.linspace(0, 1, 5) # 0到1之间5个等间距数
print(lin) # [0. 0.25 0.5 0.75 1. ]
# logspace() - 对数间距数组
log = np.logspace(0, 2, 5) # 10⁰到10²之间5个数
print(log) # [ 1. 3.16227766 10. 31.6227766 100. ]
随机数组
import numpy as np
# 设置随机种子(确保可重复)
np.random.seed(42)
# rand() - 0-1之间的均匀分布
random_arr = np.random.rand(3, 3)
print(random_arr)
# randn() - 标准正态分布(均值为0,标准差为1)
normal_arr = np.random.randn(3, 3)
# randint() - 随机整数
random_int = np.random.randint(1, 100, size=(3, 3)) # 1-99的随机整数
# choice() - 从给定数组中随机选择
choices = np.random.choice([1, 2, 3, 4, 5], size=3, replace=False)
# shuffle() - 原地打乱数组
arr = np.array([1, 2, 3, 4, 5])
np.random.shuffle(arr)
print(arr) # 顺序被打乱
数组属性
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(f"数组形状: {arr.shape}") # (2, 3) - 2行3列
print(f"数组维度: {arr.ndim}") # 2 - 二维数组
print(f"元素总数: {arr.size}") # 6
print(f"数据类型: {arr.dtype}") # int64
print(f"每个元素字节数: {arr.itemsize}") # 8
print(f"总字节数: {arr.nbytes}") # 48
数组索引和切片
一维数组索引
import numpy as np
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[::2]) # [10 30 50] - 每隔一个元素
print(arr[::-1]) # [50 40 30 20 10] - 反转数组
多维数组索引
import numpy as np
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 获取元素
print(arr2d[0, 0]) # 1 - 第0行第0列
print(arr2d[1, 2]) # 6 - 第1行第2列
# 获取行
print(arr2d[0]) # [1 2 3] - 第0行
print(arr2d[0, :]) # [1 2 3] - 第0行(显式)
# 获取列
print(arr2d[:, 1]) # [2 5 8] - 第1列
# 子数组
print(arr2d[0:2, 1:3])
# [[2 3]
# [5 6]]
布尔索引(条件筛选)
import numpy as np
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# 筛选大于5的元素
print(arr[arr > 5]) # [ 6 7 8 9 10]
# 筛选偶数
print(arr[arr % 2 == 0]) # [ 2 4 6 8 10]
# 复合条件
print(arr[(arr > 3) & (arr < 8)]) # [4 5 6 7]
# 使用 where 获取索引
indices = np.where(arr > 5)
print(indices) # (array([5, 6, 7, 8, 9]),)
# 条件赋值
arr_copy = arr.copy()
arr_copy[arr_copy > 5] = 0
print(arr_copy) # [1 2 3 4 5 0 0 0 0 0]
花式索引
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
# 使用索引数组
indices = [0, 2, 4]
print(arr[indices]) # [10 30 50]
# 二维数组花式索引
arr2d = np.array([[1, 2], [3, 4], [5, 6]])
print(arr2d[[0, 2]]) # [[1 2] [5 6]]
# 同时指定行和列
print(arr2d[[0, 1, 2], [0, 1, 0]]) # [1 4 5]
数组变形
import numpy as np
arr = np.arange(12)
print(f"原始数组: {arr}") # [ 0 1 2 3 4 5 6 7 8 9 10 11]
# reshape() - 改变形状(不复制数据)
reshaped = arr.reshape(3, 4)
print(reshaped)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# -1 表示自动计算该维度
reshaped2 = arr.reshape(2, -1) # 2行,列数自动计算
print(reshaped2.shape) # (2, 6)
# ravel() / flatten() - 展平数组
flat = reshaped.ravel() # 返回视图
flat2 = reshaped.flatten() # 返回副本
# transpose() / T - 转置
transposed = reshaped.T
print(transposed)
# [[ 0 4 8]
# [ 1 5 9]
# [ 2 6 10]
# [ 3 7 11]]
# resize() - 改变形状并填充或截断
arr.resize(3, 5) # 原地修改
print(arr)
# [[ 0 1 2 3 4]
# [ 5 6 7 8 9]
# [10 11 0 0 0]]
数组运算
算术运算
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 元素级运算
print(a + b) # [5 7 9]
print(a - b) # [-3 -3 -3]
print(a * b) # [ 4 10 18] - 元素相乘
print(a / b) # [0.25 0.4 0.5 ]
print(a ** 2) # [1 4 9]
print(a % 2) # [1 0 1]
# 标量运算
print(a + 10) # [11 12 13]
print(a * 2) # [2 4 6]
print(a / 2) # [0.5 1. 1.5]
# 通用函数(ufunc)
print(np.sqrt(a)) # [1. 1.41421356 1.73205081]
print(np.exp(a)) # [ 2.71828183 7.3890561 20.08553692]
print(np.log(a + 1)) # [0.69314718 1.09861229 1.38629436]
print(np.sin(a)) # 正弦函数
矩阵运算
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# 矩阵乘法(点积)
print(np.dot(a, b))
# [[19 22]
# [43 50]]
# 或使用 @ 运算符(Python 3.5+)
print(a @ b)
# 转置
print(a.T)
# 逆矩阵
print(np.linalg.inv(a))
# [[-2. 1. ]
# [ 1.5 -0.5]]
# 行列式
print(np.linalg.det(a)) # -2.0
# 特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eig(a)
print(f"特征值: {eigenvalues}")
print(f"特征向量:\n{eigenvectors}")
# 解线性方程组 ax = b
a = np.array([[3, 1], [1, 2]])
b = np.array([9, 8])
x = np.linalg.solve(a, b)
print(x) # [2. 3.]
聚合运算
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 基本聚合
print(np.sum(arr)) # 45
print(np.mean(arr)) # 5.0
print(np.std(arr)) # 标准差
print(np.var(arr)) # 方差
print(np.min(arr)) # 1
print(np.max(arr)) # 9
print(np.argmin(arr)) # 0 - 最小值的索引
print(np.argmax(arr)) # 8 - 最大值的索引
# 按轴聚合
print(np.sum(arr, axis=0)) # [12 15 18] - 按列求和
print(np.sum(arr, axis=1)) # [ 6 15 24] - 按行求和
print(np.mean(arr, axis=0)) # [4. 5. 6.] - 按列求平均
# 累积运算
print(np.cumsum(arr)) # [ 1 3 6 10 15 21 28 36 45] - 累积和
print(np.cumprod(arr)) # 累积积
广播机制
广播是 NumPy 中用于处理不同形状数组之间运算的机制。
import numpy as np
# 标量广播
a = np.array([1, 2, 3])
print(a + 10) # [11 12 13]
# 一维数组广播到二维
a = np.array([[1, 2, 3], [4, 5, 6]]) # shape: (2, 3)
b = np.array([10, 20, 30]) # shape: (3,)
print(a + b)
# [[11 22 33]
# [14 25 36]]
# 列向量广播
c = np.array([[10], [20]]) # shape: (2, 1)
print(a + c)
# [[11 12 13]
# [24 25 26]]
# 广播规则:
# 1. 如果两个数组维度不同,会在较小数组的形状前面补1
# 2. 如果两个数组在某个维度上大小相同,或其中一个为1,则可以广播
# 3. 否则报错
数组拼接和分割
拼接数组
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# vstack() - 垂直拼接(按行)
print(np.vstack((a, b)))
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
# hstack() - 水平拼接(按列)
print(np.hstack((a, b)))
# [[1 2 5 6]
# [3 4 7 8]]
# concatenate() - 通用拼接
print(np.concatenate((a, b), axis=0)) # 垂直拼接
print(np.concatenate((a, b), axis=1)) # 水平拼接
# stack() - 在新轴上拼接
print(np.stack((a, b), axis=0).shape) # (2, 2, 2)
print(np.stack((a, b), axis=2).shape) # (2, 2, 2)
分割数组
import numpy as np
arr = np.arange(12).reshape(3, 4)
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# vsplit() - 垂直分割
parts = np.vsplit(arr, 3) # 分成3份
for part in parts:
print(part)
# [[0 1 2 3]]
# [[4 5 6 7]]
# [[ 8 9 10 11]]
# hsplit() - 水平分割
parts = np.hsplit(arr, 2) # 分成2份
for part in parts:
print(part)
# [[0 1]
# [4 5]
# [8 9]]
# [[ 2 3]
# [ 6 7]
# [10 11]]
# split() - 通用分割
parts = np.split(arr, [1, 2], axis=0) # 在第1行和第2行处分割
文件操作
保存和加载数组
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
# 保存为 .npy 文件(NumPy 二进制格式)
np.save('array.npy', arr)
# 加载 .npy 文件
loaded = np.load('array.npy')
print(loaded)
# 保存多个数组
np.savez('arrays.npz', a=arr, b=arr * 2)
# 加载多个数组
data = np.load('arrays.npz')
print(data['a'])
print(data['b'])
# 保存为文本文件
np.savetxt('array.txt', arr, delimiter=',', fmt='%d')
# 从文本文件加载
loaded_txt = np.loadtxt('array.txt', delimiter=',')
print(loaded_txt)
实用技巧
条件赋值
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
# np.where() - 条件赋值
result = np.where(arr > 3, arr * 2, arr)
print(result) # [1 2 3 8 10]
# np.clip() - 限制范围
clipped = np.clip(arr, 2, 4)
print(clipped) # [2 2 3 4 4]
# np.select() - 多条件选择
conditions = [arr < 2, arr < 4, arr >= 4]
choices = ['small', 'medium', 'large']
result = np.select(conditions, choices)
print(result) # ['small' 'medium' 'medium' 'large' 'large']
去重和计数
import numpy as np
arr = np.array([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
# 去重
unique = np.unique(arr)
print(unique) # [1 2 3 4]
# 去重并计数
unique, counts = np.unique(arr, return_counts=True)
print(dict(zip(unique, counts))) # {1: 1, 2: 2, 3: 3, 4: 4}
# 去重并返回索引
unique, indices = np.unique(arr, return_index=True)
print(indices) # [0 1 3 6]
排序
import numpy as np
arr = np.array([3, 1, 4, 1, 5, 9, 2, 6])
# 返回排序后的副本
sorted_arr = np.sort(arr)
print(sorted_arr) # [1 1 2 3 4 5 6 9]
# 原地排序
arr_copy = arr.copy()
arr_copy.sort()
# 返回排序索引
indices = np.argsort(arr)
print(indices) # [1 3 6 0 2 4 7 5]
print(arr[indices]) # [1 1 2 3 4 5 6 9]
# 按列排序
arr2d = np.array([[3, 1], [1, 4], [5, 2]])
sorted_by_col = arr2d[arr2d[:, 0].argsort()]
print(sorted_by_col)
# [[1 4]
# [3 1]
# [5 2]]
实际应用示例
示例 1:图像处理基础
import numpy as np
# 创建模拟图像(100x100 灰度图)
image = np.random.rand(100, 100)
# 图像反转
inverted = 1 - image
# 裁剪
cropped = image[25:75, 25:75]
# 调整大小(简单最近邻)
resized = image[::2, ::2] # 缩小为原来的一半
# 添加噪声
noisy = image + np.random.normal(0, 0.1, image.shape)
noisy = np.clip(noisy, 0, 1)
示例 2:数据统计分析
import numpy as np
# 生成模拟数据
data = np.random.randn(1000) * 10 + 50 # 均值为50,标准差为10
# 描述性统计
print(f"均值: {np.mean(data):.2f}")
print(f"中位数: {np.median(data):.2f}")
print(f"标准差: {np.std(data):.2f}")
print(f"方差: {np.var(data):.2f}")
print(f"最小值: {np.min(data):.2f}")
print(f"最大值: {np.max(data):.2f}")
print(f"25%分位数: {np.percentile(data, 25):.2f}")
print(f"75%分位数: {np.percentile(data, 75):.2f}")
# 相关性分析
x = np.random.randn(100)
y = 2 * x + np.random.randn(100) * 0.5 # y 与 x 相关
correlation = np.corrcoef(x, y)
print(f"相关系数:\n{correlation}")
示例 3:向量化计算
import numpy as np
# 计算欧几里得距离(向量化版本)
def euclidean_distance_vectorized(points1, points2):
"""
计算两组点之间的欧几里得距离
points1: (N, D) 数组
points2: (M, D) 数组
返回: (N, M) 距离矩阵
"""
# 利用广播机制:(N, 1, D) - (1, M, D) = (N, M, D)
diff = points1[:, np.newaxis, :] - points2[np.newaxis, :, :]
distances = np.sqrt(np.sum(diff ** 2, axis=2))
return distances
# 示例
points_a = np.array([[0, 0], [1, 1]])
points_b = np.array([[0, 1], [1, 0], [2, 2]])
distances = euclidean_distance_vectorized(points_a, points_b)
print(distances)
# [[1. 1. 2.82842712]
# [1. 1. 1.41421356]]
小结
NumPy 是 Python 科学计算的基石,掌握 NumPy 对于数据分析和机器学习至关重要。
核心概念:
- ndarray:高效的多维数组对象
- 向量化运算:避免显式循环,提高性能
- 广播机制:不同形状数组之间的运算规则
- 索引和切片:灵活的数据访问方式
- 通用函数(ufunc):元素级运算函数
常用功能:
- 数组创建:
array(),zeros(),ones(),arange(),linspace() - 数组变形:
reshape(),ravel(),transpose() - 数组运算:
+,-,*,/,@,np.dot() - 聚合运算:
sum(),mean(),std(),min(),max() - 线性代数:
np.linalg模块 - 随机数:
np.random模块
练习
- 创建一个 5x5 的矩阵,对角线为1,其余为0(不使用 eye 函数)
- 实现一个函数,计算两个数组的皮尔逊相关系数
- 使用 NumPy 实现矩阵的奇异值分解(SVD)
- 创建一个函数,对图像进行高斯模糊处理
- 实现一个向量化版本的 K-近邻算法