跳到主要内容

NumPy 基础

NumPy(Numerical Python)是 Python 数据分析的基础库,提供了高性能的多维数组对象和用于处理这些数组的工具。本章将详细介绍 NumPy 的核心概念和使用方法。

为什么需要 NumPy?

传统 Python 列表的局限性

# Python 列表 - 存储整数
numbers = [1, 2, 3, 4, 5]

# 对每个元素加1 - 需要循环
result = []
for n in numbers:
result.append(n + 1)

# 或者使用列表推导式
result = [n + 1 for n in numbers]

NumPy 的优势

import numpy as np

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

# 向量化操作 - 直接加1
result = arr + 1 # [2, 3, 4, 5, 6]

# 性能对比
import time

# Python 列表
start = time.time()
numbers = list(range(1000000))
result = [n + 1 for n in numbers]
print(f"Python 列表: {time.time() - start:.4f}秒")

# NumPy 数组
start = time.time()
arr = np.arange(1000000)
result = arr + 1
print(f"NumPy 数组: {time.time() - start:.4f}秒")

NumPy 比纯 Python 快 10-100 倍!

创建数组

从列表创建

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]]

# 指定数据类型
arr_int = np.array([1, 2, 3], dtype=np.int32)
arr_float = np.array([1.0, 2.0, 3.0], dtype=np.float64)

使用内置函数创建

# arange - 类似于 range
arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
arr = np.arange(0, 10, 2) # [0 2 4 6 8]

# linspace - 创建等间距数组
arr = np.linspace(0, 10, 5) # [ 0. 2.5 5. 7.5 10. ]

# zeros - 全零数组
arr = np.zeros(5) # [0. 0. 0. 0. 0.]
arr = np.zeros((3, 4)) # 3x4 全零矩阵

# ones - 全一数组
arr = np.ones(5) # [1. 1. 1. 1. 1.]

# full - 指定值的数组
arr = np.full(5, 7) # [7 7 7 7 7]
arr = np.full((3, 3), -1) # 3x3 全 -1 矩阵

# eye - 单位矩阵
arr = np.eye(3) # 3x3 单位矩阵

# random - 随机数组
arr = np.random.rand(5) # [0, 1) 均匀分布
arr = np.random.randn(5) # 标准正态分布
arr = np.random.randint(0, 10, 5) # [0, 10) 整数随机

数组属性

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

print(arr.ndim) # 维度数量: 2
print(arr.shape) # 形状: (2, 3)
print(arr.size) # 元素总数: 6
print(arr.dtype) # 数据类型: int64
print(arr.itemsize) # 每个元素字节数: 8
print(arr.nbytes) # 总字节数: 48

数组索引和切片

基本索引

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

# 单个索引
print(arr[0]) # 第一个元素: 1
print(arr[-1]) # 最后一个元素: 5

# 切片
print(arr[1:4]) # [2 3 4]
print(arr[:3]) # [1 2 3]
print(arr[::2]) # [1 3 5] - 步长为2

# 多维数组索引
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

print(arr2d[0]) # 第一行: [1 2 3]
print(arr2d[0, 0]) # 第一个元素: 1
print(arr2d[1:, :2]) # 行索引1及以后,列索引0-1

高级索引

NumPy 提供了两种高级索引方式:整数数组索引和布尔索引。与基本切片不同,高级索引总是返回数据的副本,而不是视图。

整数数组索引

整数数组索引允许使用整数数组选择任意位置的元素:

# 一维数组的整数索引
arr = np.array([10, 20, 30, 40, 50])
indices = np.array([0, 2, 4])
print(arr[indices]) # [10 30 50]

# 使用列表作为索引
print(arr[[0, 2, 4]]) # [10 30 50]

# 允许重复索引
print(arr[[0, 0, 2, 2]]) # [10 10 30 30]

# 二维数组的整数索引
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 选择特定位置的元素:选 (0,0), (1,2), (2,1)
rows = np.array([0, 1, 2])
cols = np.array([0, 2, 1])
print(arr2d[rows, cols]) # [1 6 8]

# 使用 np.ix_ 创建索引网格
# 选择第0行和第2行,第0列和第2列的交叉区域
rows = [0, 2]
cols = [0, 2]
print(arr2d[np.ix_(rows, cols)])
# [[1 3]
# [7 9]]

# 整数索引结合切片
print(arr2d[1:, [0, 2]]) # 第1行之后,第0和第2列
# [[4 6]
# [7 9]]

布尔索引

布尔索引使用布尔数组选择满足条件的元素:

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

# 使用布尔数组
mask = np.array([True, False, True, False, True, False, True, False, True])
print(arr[mask]) # [1 3 5 7 9]

# 直接使用条件表达式
print(arr[arr > 5]) # [6 7 8 9]
print(arr[arr % 2 == 0]) # [2 4 6 8] - 偶数

# 组合条件(需要用 & | ~,不能用 and or not)
print(arr[(arr > 3) & (arr < 8)]) # [4 5 6 7]
print(arr[(arr < 3) | (arr > 7)]) # [1 2 8 9]
print(arr[~(arr > 5)]) # [1 2 3 4 5] - 不大于5的元素

# 二维数组的布尔索引
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 选择所有大于5的元素
print(arr2d[arr2d > 5]) # [6 7 8 9] - 返回一维数组

# 按行或列条件筛选
# 选择第二列大于5的行
print(arr2d[arr2d[:, 1] > 5])
# [[7 8 9]]

# np.where 与布尔索引结合
indices = np.where(arr > 5)
print(indices) # (array([5, 6, 7, 8]),)
print(arr[indices]) # [6 7 8 9]

花式索引技巧

# 使用 take 和 put
arr = np.array([10, 20, 30, 40, 50])
# take:按索引取值
print(np.take(arr, [0, 2, 4])) # [10 30 50]

# put:按索引赋值
arr_copy = arr.copy()
np.put(arr_copy, [0, 2, 4], [-1, -2, -3])
print(arr_copy) # [-1 20 -2 40 -3]

# choose:从多个数组中选择
choices = np.array([[1, 2, 3], [10, 20, 30], [100, 200, 300]])
indices = np.array([0, 1, 2])
print(np.choose(indices, choices)) # [1 20 300]

# select:多条件选择(类似 case-when)
conditions = [arr < 20, arr < 40, arr >= 40]
choices_list = [arr * 10, arr * 2, arr]
print(np.select(conditions, choices_list)) # [100 40 60 80 50]

索引返回视图还是副本

这是一个重要的概念:基本切片返回视图,高级索引返回副本。

arr = np.arange(10)

# 基本切片返回视图(修改会影响原数组)
view = arr[2:5]
view[:] = 100
print(arr) # [0 1 100 100 100 5 6 7 8 9] - 原数组被修改

arr = np.arange(10)

# 高级索引返回副本(修改不影响原数组)
copy = arr[[2, 3, 4]]
copy[:] = 100
print(arr) # [0 1 2 3 4 5 6 7 8 9] - 原数组未修改

# 判断是否共享内存
arr = np.arange(10)
view = arr[2:5]
print(np.shares_memory(arr, view)) # True

copy = arr[[2, 3, 4]]
print(np.shares_memory(arr, copy)) # False

数组运算

算术运算

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

# 基本运算
print(arr + 1) # [2 3 4 5 6]
print(arr - 1) # [0 1 2 3 4]
print(arr * 2) # [2 4 6 8 10]
print(arr / 2) # [0.5 1. 1.5 2. 2.5]
print(arr ** 2) # [ 1 4 9 16 25]
print(arr % 2) # [1 0 1 0 1]

# 数组之间的运算
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(a + b) # [5 7 9]
print(a * b) # [ 4 10 18]

聚合函数

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

# 统计函数
print(arr.sum()) # 求和: 15
print(arr.mean()) # 平均值: 3.0
print(arr.std()) # 标准差: 1.41421356
print(arr.var()) # 方差: 2.0
print(arr.min()) # 最小值: 1
print(arr.max()) # 最大值: 5

# 找到最大值/最小值的索引
print(arr.argmin()) # 0
print(arr.argmax()) # 4

# 多维数组的聚合
arr2d = np.array([[1, 2, 3], [4, 5, 6]])

print(arr2d.sum()) # 所有元素求和: 21
print(arr2d.sum(axis=0)) # 按列求和: [5 7 9]
print(arr2d.sum(axis=1)) # 按行求和: [6 15]

广播机制

广播是 NumPy 最强大的特性之一,它允许不同形状的数组进行算术运算。广播的核心思想是让较小的数组在运算时"扩展"到与较大数组相同的形状,但这种扩展只是概念上的,实际并不会复制数据,因此非常高效。

广播规则

根据 NumPy 官方文档,广播遵循以下规则:

规则一:从右向左比较维度

当操作两个数组时,NumPy 从最右侧(尾部)维度开始逐个比较形状,向左推进。

规则二:维度兼容条件

两个维度兼容当且仅当满足以下条件之一:

  • 它们相等
  • 其中一个为 1

如果条件不满足,会抛出 ValueError: operands could not be broadcast together 异常。

规则三:结果形状

结果数组的每个维度取两个输入数组对应维度的最大值。

# 理解广播规则
import numpy as np

# 示例1:标量与数组
arr = np.array([1, 2, 3])
# 形状: (3,)
# 标量可以视为形状为 () 的数组
# () 与 (3,) 比较:标量扩展为 (3,)
result = arr + 5 # [6 7 8]

# 示例2:一维与二维数组
a = np.array([[1], [2], [3]]) # 形状 (3, 1)
b = np.array([1, 2, 3]) # 形状 (3,)
# 广播过程:
# a: (3, 1) b: (3,) -> 先补齐 -> b: (1, 3)
# a: (3, 1) b: (1, 3)
# 最终形状: (3, 3)
result = a + b
print(result)
# [[2 3 4]
# [3 4 5]
# [4 5 6]]

广播形状对照表

理解广播规则的最好方式是看具体例子:

# 兼容的形状示例
# A (2d array): 5 x 4
# B (1d array): 4
# Result (2d array): 5 x 4

# A (3d array): 15 x 3 x 5
# B (3d array): 15 x 1 x 5
# Result (3d array): 15 x 3 x 5

# A (4d array): 8 x 1 x 6 x 1
# B (3d array): 7 x 1 x 5
# Result (4d array): 8 x 7 x 6 x 5

# 不兼容的形状示例
# A (1d array): 3
# B (1d array): 4 # 尾部维度不匹配!

# A (2d array): 2 x 1
# B (3d array): 8 x 4 x 3 # 倒数第二维不匹配!

实际应用示例

# 归一化数据(每列减去均值)
data = np.random.randn(100, 5) # 100个样本,5个特征
means = data.mean(axis=0) # 形状 (5,)
# data (100, 5) 与 means (5,) 广播
normalized = data - means

# 给图像的每个通道乘以不同系数
image = np.random.rand(256, 256, 3) # RGB图像
scales = np.array([1.0, 0.8, 0.6]) # R, G, B 缩放系数
# image (256, 256, 3) 与 scales (3,) 广播
scaled_image = image * scales

# 使用 newaxis 扩展维度实现外积
a = np.array([1, 2, 3]) # 形状 (3,)
b = np.array([10, 20, 30]) # 形状 (3,)
# a[:, np.newaxis] 形状变为 (3, 1)
# b 形状为 (3,),广播后变为 (1, 3)
outer = a[:, np.newaxis] + b # 结果形状 (3, 3)
print(outer)
# [[11 21 31]
# [12 22 32]
# [13 23 33]]

广播的注意事项

# 广播虽然方便,但在处理大数据时需要注意内存
# 有时广播会产生非常大的中间数组

# 示例:计算大量观测与多个码本的距离
observations = np.random.rand(10000, 3) # 10000个观测点
codes = np.random.rand(100, 3) # 100个码本点

# 方法1:广播(内存消耗大)
# diff 形状为 (100, 10000, 3),占用大量内存
diff = codes[:, np.newaxis, :] - observations # 广播产生大数组

# 方法2:循环计算(内存友好)
distances = []
for code in codes:
dist = np.sqrt(np.sum((observations - code) ** 2, axis=1))
distances.append(dist)

数组操作

改变形状

arr = np.arange(12)
print(arr.reshape(3, 4)) # 3x4 矩阵
print(arr.reshape(2, -1)) # 自动计算列数: 2x6

# flatten 和 ravel - 展平数组
arr2d = np.array([[1, 2], [3, 4]])
print(arr2d.flatten()) # [1 2 3 4] - 返回副本
print(arr2d.ravel()) # [1 2 3 4] - 返回视图(如果可能)

# transpose - 转置
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2d.T) # [[1 4], [2 5], [3 6]]

数组拼接

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# concatenate - 拼接
print(np.concatenate([a, b])) # [1 2 3 4 5 6]

# vstack - 垂直堆叠
print(np.vstack([a, b])) # [[1 2 3]
# [4 5 6]]

# hstack - 水平堆叠
print(np.hstack([a, b])) # [1 2 3 4 5 6]

数组分割

arr = np.arange(12)

# split - 分割
print(np.split(arr, 3)) # 分成3份: [0 1 2 3], [4 5 6 7], [8 9 10 11]
print(np.split(arr, [3, 7])) # 在索引3和7处分割: [0 1 2], [3 4 5 6], [7 8 9 10 11]

arr2d = np.arange(16).reshape(4, 4)
# hsplit - 水平分割
print(np.hsplit(arr2d, 2)) # 分成2个4x2矩阵
# vsplit - 垂直分割
print(np.vsplit(arr2d, 2)) # 分成2个2x4矩阵

条件运算

where 函数

np.where 是 NumPy 中实现条件选择的核心函数,它类似于三元运算符的向量化版本。

# np.where(condition, x, y) - 条件选择
arr = np.array([1, 2, 3, 4, 5])
result = np.where(arr > 3, arr, 0)
print(result) # [0 0 0 4 5]

# 只提供条件,返回索引
indices = np.where(arr > 3)
print(indices) # (array([3, 4]),)

# 多维数组的 where
arr2d = np.array([[1, 2], [3, 4]])
# 找出所有大于2的元素的位置
rows, cols = np.where(arr2d > 2)
print(f"行索引: {rows}, 列索引: {cols}") # 行索引: [1 1], 列索引: [0 1]

# 条件替换:将负数替换为0
data = np.array([-1, 2, -3, 4, -5])
positive = np.where(data < 0, 0, data)
print(positive) # [0 2 0 4 0]

逻辑函数

NumPy 提供了丰富的逻辑函数用于构建复杂条件:

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

# 基本逻辑判断
print(np.any(arr > 3)) # True - 任意元素满足
print(np.all(arr > 0)) # True - 所有元素满足

# 逻辑运算
print(np.logical_and(arr > 1, arr < 5)) # [False True True True False]
print(np.logical_or(arr < 2, arr > 4)) # [True False False False True]
print(np.logical_not(arr > 3)) # [True True True False False]

# 比较 between
print(np.logical_and(arr >= 2, arr <= 4)) # [False True True True False]

# 使用 & | ~ 进行元素级逻辑运算(需要括号)
print((arr > 1) & (arr < 5)) # [False True True True False]
print((arr < 2) | (arr > 4)) # [True False False False True]
print(~(arr > 3)) # [True True True False False]

select 和 piecewise

对于多条件选择,可以使用 np.selectnp.piecewise

# 多条件选择
scores = np.array([85, 92, 78, 65, 55])

conditions = [
scores >= 90, # 优秀
scores >= 80, # 良好
scores >= 60, # 及格
scores < 60 # 不及格
]
choices = ['优秀', '良好', '及格', '不及格']

grades = np.select(conditions, choices, default='未知')
print(grades) # ['良好' '优秀' '及格' '及格' '不及格']

# piecewise:分段函数
x = np.linspace(-5, 5, 11)
# x < 0 时取 -x,x >= 0 时取 x(即绝对值函数)
y = np.piecewise(x, [x < 0, x >= 0], [lambda x: -x, lambda x: x])
print(y) # [5. 4. 3. 2. 1. 0. 1. 2. 3. 4. 5.]

通用函数(ufunc)

通用函数(Universal Functions,简称 ufunc)是 NumPy 中对数组进行元素级运算的函数。它们是 NumPy 高效计算的核心。

常用数学 ufunc

import numpy as np

arr = np.array([1, 4, 9, 16, 25])

# 一元数学函数
print(np.sqrt(arr)) # [1. 2. 3. 4. 5.] - 平方根
print(np.square(arr)) # [ 1 16 81 256 625] - 平方
print(np.exp(arr)) # e^x
print(np.log(arr)) # 自然对数
print(np.log10(arr)) # 以10为底的对数
print(np.abs(arr)) # 绝对值

# 三角函数
angles = np.array([0, np.pi/6, np.pi/4, np.pi/3, np.pi/2])
print(np.sin(angles)) # 正弦
print(np.cos(angles)) # 余弦
print(np.tan(angles)) # 正切

# 反三角函数
values = np.array([0, 0.5, 1])
print(np.arcsin(values)) # 反正弦
print(np.arccos(values)) # 反余弦

# 取整函数
arr = np.array([-2.5, -1.5, 0.5, 1.5, 2.5])
print(np.round(arr)) # [ -2. -2. 0. 2. 2.] - 四舍五入
print(np.floor(arr)) # [ -3. -2. 0. 1. 2.] - 向下取整
print(np.ceil(arr)) # [ -2. -1. 1. 2. 3.] - 向上取整
print(np.trunc(arr)) # [ -2. -1. 0. 1. 2.] - 截断

# 符号函数
print(np.sign(arr)) # [-1. -1. 1. 1. 1.] - 返回符号

二元数学 ufunc

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 算术运算
print(np.add(a, b)) # [5 7 9] - 加法
print(np.subtract(a, b)) # [-3 -3 -3] - 减法
print(np.multiply(a, b)) # [4 10 18] - 乘法
print(np.divide(a, b)) # [0.25 0.4 0.5] - 除法
print(np.power(a, b)) # [1 32 729] - 幂运算

# 其他运算
print(np.mod(b, a)) # [0 1 0] - 取模
print(np.fmod(b, a)) # [0 1 0] - 浮点取模
print(np.maximum(a, b)) # [4 5 6] - 元素级最大值
print(np.minimum(a, b)) # [1 2 3] - 元素级最小值
print(np.copysign(a, b)) # [1. 2. 3.] - 复制符号

ufunc 方法

每个 ufunc 都有四个重要的方法:reduceaccumulatereduceatouter

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

# reduce:沿指定轴归约
print(np.add.reduce(arr)) # 15 - 相当于 sum
print(np.multiply.reduce(arr)) # 120 - 相当于 prod

# accumulate:累积运算
print(np.add.accumulate(arr)) # [1 3 6 10 15] - 累积和
print(np.multiply.accumulate(arr)) # [1 2 6 24 120] - 累积积

# outer:外积运算
a = np.array([1, 2, 3])
b = np.array([10, 20])
print(np.add.outer(a, b))
# [[11 21]
# [12 22]
# [13 23]]

# reduceat:在指定位置分段归约
print(np.add.reduceat(arr, [0, 2, 4]))
# [3, 7, 5] # arr[0:2]的和, arr[2:4]的和, arr[4:]的和

自定义 ufunc

可以使用 np.frompyfunc 创建自定义 ufunc:

# 定义一个普通 Python 函数
def my_func(x):
return x ** 2 + 2 * x + 1

# 创建 ufunc
my_ufunc = np.frompyfunc(my_func, 1, 1) # 1个输入,1个输出

arr = np.array([1, 2, 3, 4, 5])
print(my_ufunc(arr)) # [4 9 16 25 36]

# 使用 np.vectorize(更灵活)
my_vec = np.vectorize(my_func)
print(my_vec(arr)) # [4 9 16 25 36]

# 支持多输出的 ufunc
def minmax(x):
return x.min(), x.max()

minmax_ufunc = np.frompyfunc(minmax, 1, 2)
result = minmax_ufunc(np.array([[1, 2, 3], [4, 5, 6]]))
print(result) # (array([1, 4]), array([3, 6]))

线性代数操作

NumPy 的 numpy.linalg 模块提供了完整的线性代数运算功能,底层依赖 BLAS 和 LAPACK 库实现高效计算。

矩阵乘法

import numpy as np

# 创建矩阵
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

# 使用 @ 运算符(推荐)
C = A @ B # 矩阵乘法
print(C)
# [[19 22]
# [43 50]]

# 使用 np.matmul
C = np.matmul(A, B)

# 使用 np.dot
C = np.dot(A, B)

# 区别:对于高维数组
# @ 和 matmul 保留前导维度,只对最后两维做矩阵乘法
# dot 会进行更复杂的广播
A_3d = np.random.rand(2, 3, 4)
B_3d = np.random.rand(2, 4, 5)
C_3d = A_3d @ B_3d # 结果形状 (2, 3, 5)

# 点积(向量内积)
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
dot_product = np.dot(v1, v2) # 1*4 + 2*5 + 3*6 = 32
print(dot_product) # 32

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

矩阵分解

# 创建对称正定矩阵用于示例
A = np.array([[4, 2], [2, 3]], dtype=float)

# Cholesky 分解(适用于对称正定矩阵)
L = np.linalg.cholesky(A)
print("Cholesky 分解:")
print("L =", L)
print("L @ L.T =", L @ L.T) # 应该等于 A

# QR 分解
A = np.array([[1, 2], [3, 4], [5, 6]], dtype=float)
Q, R = np.linalg.qr(A)
print("\nQR 分解:")
print("Q =", Q)
print("R =", R)
print("Q @ R =", Q @ R) # 应该等于 A

# 奇异值分解(SVD)
A = np.array([[1, 2, 3], [4, 5, 6]], dtype=float)
U, S, Vt = np.linalg.svd(A)
print("\nSVD 分解:")
print("U =", U) # 左奇异向量
print("S =", S) # 奇异值
print("Vt =", Vt) # 右奇异向量(转置)
# 重构原矩阵
reconstructed = U @ np.diag(S) @ Vt
print("重构矩阵:", reconstructed)

特征值和特征向量

# 创建方阵
A = np.array([[4, -2], [1, 1]], dtype=float)

# 计算特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eig(A)
print("特征值:", eigenvalues)
print("特征向量:")
print(eigenvectors)

# 验证:A @ v = λ * v
for i in range(len(eigenvalues)):
v = eigenvectors[:, i]
lambda_v = eigenvalues[i]
print(f"验证 A @ v{i} = λ{i} * v{i}:")
print(f" A @ v = {A @ v}")
print(f" λ * v = {lambda_v * v}")

# 对于对称矩阵,使用 eigh 更高效且保证实特征值
A_sym = np.array([[4, 2], [2, 3]], dtype=float)
eigenvalues, eigenvectors = np.linalg.eigh(A_sym)
print("\n对称矩阵特征值:", eigenvalues)

求解线性方程组

# 求解 Ax = b
A = np.array([[3, 1], [1, 2]], dtype=float)
b = np.array([9, 8], dtype=float)

# 方法1:使用 solve
x = np.linalg.solve(A, b)
print("解 x =", x) # [2. 3.]
print("验证 A @ x =", A @ x) # 应该等于 b

# 方法2:使用矩阵求逆(不推荐,数值稳定性差)
x = np.linalg.inv(A) @ b
print("使用逆矩阵求解 x =", x)

# 最小二乘法(超定方程组)
# 当方程数大于未知数时
A = np.array([[1, 1], [1, 2], [1, 3]], dtype=float)
b = np.array([1, 2, 2.5], dtype=float)
x, residuals, rank, s = np.linalg.lstsq(A, b, rcond=None)
print("\n最小二乘解:", x)
print("残差:", residuals)

矩阵属性计算

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

# 行列式
det = np.linalg.det(A)
print("行列式:", det) # -2.0

# 矩阵的秩
rank = np.linalg.matrix_rank(A)
print("秩:", rank) # 2

# 矩阵的范数
norm_fro = np.linalg.norm(A, 'fro') # Frobenius 范数
norm_2 = np.linalg.norm(A, 2) # 2-范数(最大奇异值)
norm_1 = np.linalg.norm(A, 1) # 1-范数(列和最大值)
print(f"Frobenius范数: {norm_fro}")
print(f"2-范数: {norm_2}")
print(f"1-范数: {norm_1}")

# 条件数(衡量矩阵病态程度)
cond = np.linalg.cond(A)
print("条件数:", cond)

# 迹(对角线元素之和)
trace = np.trace(A)
print("迹:", trace) # 5.0

# 伪逆(Moore-Penrose 伪逆)
pinv = np.linalg.pinv(A)
print("伪逆:")
print(pinv)

批量矩阵运算

NumPy 支持对矩阵堆栈进行批量运算:

# 创建矩阵堆栈
batch_A = np.random.rand(5, 3, 3) # 5 个 3x3 矩阵
batch_b = np.random.rand(5, 3) # 5 个长度为 3 的向量

# 批量求解线性方程组
solutions = np.linalg.solve(batch_A, batch_b)
print("批量解形状:", solutions.shape) # (5, 3)

# 批量计算行列式
determinants = np.linalg.det(batch_A)
print("批量行列式形状:", determinants.shape) # (5,)

# 批量矩阵乘法
batch_B = np.random.rand(5, 3, 4)
result = batch_A @ batch_B
print("批量矩阵乘法结果形状:", result.shape) # (5, 3, 4)

结构化数组

结构化数组允许数组中每个元素包含多个命名字段,类似于数据库表或 C 语言的结构体。

创建结构化数组

import numpy as np

# 方法1:使用 dtype 指定字段
employees = np.array([
('张三', 25, 10000.0),
('李四', 30, 15000.0),
('王五', 35, 20000.0)
], dtype=[('name', 'U10'), ('age', 'i4'), ('salary', 'f8')])

print(employees)
# [('张三', 25, 10000.) ('李四', 30, 15000.) ('王五', 35, 20000.)]

# 方法2:使用字典定义类型
dtype_dict = {
'names': ['name', 'age', 'salary'],
'formats': ['U10', 'i4', 'f8']
}
employees = np.array([
('张三', 25, 10000.0),
('李四', 30, 15000.0)
], dtype=dtype_dict)

# 方法3:使用逗号分隔的类型字符串
arr = np.zeros(3, dtype='U10, i4, f8')
arr[:] = [('A', 1, 1.0), ('B', 2, 2.0), ('C', 3, 3.0)]
print(arr)

访问和修改字段

# 创建结构化数组
employees = np.array([
('张三', 25, 10000.0),
('李四', 30, 15000.0),
('王五', 35, 20000.0)
], dtype=[('name', 'U10'), ('age', 'i4'), ('salary', 'f8')])

# 访问单个字段(返回视图)
print("所有姓名:", employees['name'])
# ['张三' '李四' '王五']
print("所有年龄:", employees['age'])
# [25 30 35]

# 访问多个字段
print(employees[['name', 'salary']])
# [('张三', 10000.) ('李四', 15000.) ('王五', 20000.)]

# 访问单个元素
print("第一个员工:", employees[0])
# ('张三', 25, 10000.)

# 访问元素的单个字段
print("第一个员工的姓名:", employees[0]['name'])
# '张三'

# 修改字段值
employees['age'] = employees['age'] + 1 # 所有年龄加1
employees['salary'] *= 1.1 # 工资涨10%
print("修改后:")
print(employees)

# 修改单个元素
employees[0] = ('赵六', 28, 18000.0)
print("修改第一个员工后:")
print(employees)

嵌套和子数组类型

# 包含子数组的结构化类型
points = np.array([
((1, 2, 3), 100),
((4, 5, 6), 200),
((7, 8, 9), 300)
], dtype=[('coords', 'i4', (3,)), ('value', 'i4')])

print("坐标:", points['coords'])
# [[1 2 3]
# [4 5 6]
# [7 8 9]]
print("第一个点的x坐标:", points[0]['coords'][0]) # 1

# 嵌套结构化类型
nested_dtype = [
('name', 'U10'),
('info', [('age', 'i4'), ('city', 'U10')])
]
people = np.array([
('张三', (25, '北京')),
('李四', (30, '上海'))
], dtype=nested_dtype)

print("年龄:", people['info']['age'])
# [25 30]

记录数组(recarray)

记录数组是结构化数组的子类,允许通过属性访问字段:

# 创建记录数组
rec = np.rec.array([
('张三', 25, 10000.0),
('李四', 30, 15000.0)
], dtype=[('name', 'U10'), ('age', 'i4'), ('salary', 'f8')])

# 通过属性访问(更方便)
print("姓名:", rec.name)
# ['张三' '李四']
print("年龄:", rec.age)
# [25 30]

# 也可以通过索引访问
print("第一个员工的工资:", rec[0].salary)
# 10000.0

# 从结构化数组转换为记录数组
structured = np.array([('A', 1), ('B', 2)], dtype='U10, i4')
record_arr = structured.view(np.recarray)
print("通过属性访问:", record_arr.f0) # ['A' 'B']
print("通过属性访问:", record_arr.f1) # [1 2]

结构化数组的实际应用

# 示例:存储和操作学生成绩数据
students = np.array([
(1, '张三', 85, 90, 88),
(2, '李四', 92, 88, 95),
(3, '王五', 78, 85, 82),
(4, '赵六', 88, 92, 90)
], dtype=[('id', 'i4'), ('name', 'U10'), ('chinese', 'f4'),
('math', 'f4'), ('english', 'f4')])

# 计算总分和平均分
chinese = students['chinese']
math = students['math']
english = students['english']

total = chinese + math + english
average = total / 3

print("总分:", total)
print("平均分:", average)

# 找出总分最高的学生
max_idx = np.argmax(total)
print(f"总分最高: {students[max_idx]['name']}, 总分: {total[max_idx]}")

# 筛选数学成绩大于90的学生
high_math = students[students['math'] > 90]
print("数学成绩大于90的学生:")
for s in high_math:
print(f" {s['name']}: {s['math']}")

# 按总分排序
sorted_indices = np.argsort(total)[::-1]
print("成绩排名:")
for i, idx in enumerate(sorted_indices):
print(f" 第{i+1}名: {students[idx]['name']}, 总分: {total[idx]}")

实践示例

示例:计算股票收益率

# 模拟股票价格数据
prices = np.array([100, 102, 101, 105, 107, 106, 110, 108, 112, 115])

# 计算日收益率
daily_returns = (prices[1:] - prices[:-1]) / prices[:-1]
print("日收益率:", daily_returns)

# 计算统计指标
print("平均收益率:", np.mean(daily_returns))
print("收益率标准差:", np.std(daily_returns))
print("最大收益率:", np.max(daily_returns))
print("最小收益率:", np.min(daily_returns))

# 计算累计收益
cumulative_returns = (prices / prices[0]) - 1
print("累计收益:", cumulative_returns)

示例:图像处理基础

# 创建一个简单的图像矩阵(灰度图像)
# 使用随机值模拟噪点图像
image = np.random.randint(0, 256, (100, 100), dtype=np.uint8)

# 调整亮度 - 所有像素值增加50
brightened = np.clip(image + 50, 0, 255).astype(np.uint8)

# 创建对比度增强
mean_value = np.mean(image)
contrasted = np.clip((image - mean_value) * 1.5 + mean_value, 0, 255).astype(np.uint8)

# 提取红色通道(模拟RGB图像)
# 假设有一个RGB图像
rgb_image = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
red_channel = rgb_image[:, :, 0]
green_channel = rgb_image[:, :, 1]
blue_channel = rgb_image[:, :, 2]

小结

本章我们学习了:

  1. NumPy 的优势:向量化操作,高性能计算
  2. 创建数组:从列表创建、内置函数创建
  3. 数组属性:维度、形状、数据类型
  4. 索引和切片:基本索引、高级索引、布尔索引
  5. 数组运算:算术运算、聚合函数
  6. 广播机制:广播规则、形状兼容条件、实际应用
  7. 数组操作:形状改变、拼接、分割
  8. 条件运算:where、逻辑函数、select、piecewise
  9. 通用函数(ufunc):数学函数、ufunc 方法、自定义 ufunc
  10. 线性代数:矩阵乘法、分解、特征值、求解方程组
  11. 结构化数组:创建、字段访问、记录数组

练习

基础练习

  1. 创建一个 5x5 的随机整数数组(1-100)
  2. 计算数组中所有偶数的平均值
  3. 将数组中所有大于50的值替换为100
  4. 创建一个 3x4 的数组,计算每行每列的和
  5. 使用布尔索引找出数组中所有在 20-80 范围内的值

广播练习

  1. 创建一个形状为 (3, 1) 的数组和一个形状为 (1, 4) 的数组,使用广播计算它们的和
  2. 给定一个 10x5 的数据矩阵,每列减去该列的均值(使用广播实现)

ufunc 练习

  1. 使用 np.add.reduce 计算数组元素的累积和
  2. 创建一个自定义 ufunc,实现 f(x) = x^2 + 2x + 1 的向量化计算

线性代数练习

  1. 创建两个 3x3 矩阵,计算它们的矩阵乘法、行列式和逆矩阵
  2. 求解线性方程组:2x + y = 5, 3x - 2y = 4

结构化数组练习

  1. 创建一个存储学生信息(姓名、年龄、三门成绩)的结构化数组,计算每个学生的平均分

参考资源

下一步

现在你已经掌握了 NumPy 的基础,接下来让我们学习 Pandas 基础,这是数据分析的核心工具!