跳到主要内容

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 的优势

  1. 性能:NumPy 数组在内存中是连续存储的,操作由预编译的 C 代码执行
  2. 内存效率:NumPy 数组比 Python 列表占用更少的内存
  3. 功能丰富:提供了大量数学函数和线性代数运算
  4. 向量化:无需显式循环即可对整个数组进行操作

创建数组

从 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)

类型说明范围
int88位有符号整数-128 ~ 127
int1616位有符号整数-32768 ~ 32767
int3232位有符号整数-2³¹ ~ 2³¹-1
int6464位有符号整数-2⁶³ ~ 2⁶³-1
uint88位无符号整数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 对于数据分析和机器学习至关重要。

核心概念

  1. ndarray:高效的多维数组对象
  2. 向量化运算:避免显式循环,提高性能
  3. 广播机制:不同形状数组之间的运算规则
  4. 索引和切片:灵活的数据访问方式
  5. 通用函数(ufunc):元素级运算函数

常用功能

  • 数组创建:array(), zeros(), ones(), arange(), linspace()
  • 数组变形:reshape(), ravel(), transpose()
  • 数组运算:+, -, *, /, @, np.dot()
  • 聚合运算:sum(), mean(), std(), min(), max()
  • 线性代数:np.linalg 模块
  • 随机数:np.random 模块

练习

  1. 创建一个 5x5 的矩阵,对角线为1,其余为0(不使用 eye 函数)
  2. 实现一个函数,计算两个数组的皮尔逊相关系数
  3. 使用 NumPy 实现矩阵的奇异值分解(SVD)
  4. 创建一个函数,对图像进行高斯模糊处理
  5. 实现一个向量化版本的 K-近邻算法

参考资源