1.5 聚合统计与矩阵运算
掌握了逐元素的运算后,还需要把整个数组"浓缩"成有意义的统计量:总和、均值、最值、标准差,以及按某个轴分别统计。这是数据分析的第一步。除此之外,NumPy 还提供完整的线性代数子模块 numpy.linalg,用于矩阵乘法、求逆、特征值、奇异值分解等运算。本节将系统讲解聚合函数、多轴归约、累积与差分,以及 linalg 子模块的常用功能。
1.5.1 归约运算(聚合函数)
基础聚合
聚合函数把整个数组归约为一个标量:
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]])
print(np.sum(a)) # 21,所有元素求和
print(np.prod(a)) # 720,所有元素求积
print(np.mean(a)) # 3.5,均值
print(np.var(a)) # 2.9166666666666665,方差
print(np.std(a)) # 1.707825127659933,标准差
print(np.min(a)) # 1
print(np.max(a)) # 6
print(np.ptp(a)) # 5,极差 max - minargmin 与 argmax
np.argmin/np.argmax 返回最值所在位置的索引:
a = np.array([3, 1, 4, 1, 5])
print(np.argmax(a)) # 4,最大值 5 的位置
print(np.argmin(a)) # 1,最小值 1 的第一个位置median、percentile、quantile
import numpy as np
data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(np.median(data)) # 5.5,中位数
print(np.percentile(data, 25))# 3.25,第 25 百分位数
print(np.percentile(data, 75))# 7.75,第 75 百分位数
print(np.quantile(data, 0.5)) # 5.5,等价于中位数百分位数把数据从小到大分成 100 份,percentile(data, p) 返回第 p 百分位的值。quantile 使用 0~1 的比例,与 percentile 本质相同。
average 加权平均
np.average 支持权重参数,用于加权平均:
import numpy as np
scores = np.array([80, 90, 70])
weights = np.array([0.2, 0.5, 0.3])
print(np.average(scores)) # 80.0,普通平均
print(np.average(scores, weights=weights)) # 82.0,加权平均1.5.2 多轴归约(axis 参数)与 keepdims
axis 参数
聚合函数的 axis 参数指定沿哪个轴归约。对二维数组:
axis=0沿行方向归约(逐列统计)axis=1沿列方向归约(逐行统计)
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]])
print(np.sum(a, axis=0)) # [5 7 9],每列之和
print(np.sum(a, axis=1)) # [ 6 15],每行之和
print(np.mean(a, axis=0)) # [2.5 3.5 4.5]
print(np.max(a, axis=1)) # [3 6]
print(np.argmax(a, axis=1))# [2 2],每行最大值的位置axis 是数据分析的核心参数,pandas 的分组聚合、数据透视都基于类似思想。
多轴归约
高维数组可以同时沿多个轴归约,传入轴元组:
import numpy as np
a = np.arange(24).reshape(2, 3, 4)
print(a.sum(axis=(0, 1))) # 沿轴 0、1 归约,剩轴 2
# [60 66 72 78]
print(a.sum(axis=0).shape) # (3, 4)keepdims 保持维度
keepdims=True 让归约后保持原维度数(长度 1),方便与广播配合:
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a.sum(axis=1)) # [6 15],一维
print(a.sum(axis=1, keepdims=True))# [[6] [15]],仍是二维keepdims=True 的结果形状是 (2, 1),可以直接与 a 广播运算,实现"每行除以行和"这类操作。
1.5.3 累积运算
累积函数沿某个轴逐步累加/累乘,返回与输入等长的数组:
import numpy as np
a = np.array([1, 2, 3, 4])
print(np.cumsum(a)) # [1 3 6 10],逐步累加
print(np.cumprod(a)) # [1 2 6 24],逐步累乘
b = np.array([[1, 2, 3], [4, 5, 6]])
print(np.cumsum(b, axis=0)) # 沿行累积
# [[1 2 3]
# [5 7 9]]
print(np.cumsum(b, axis=1)) # 沿列累积
# [[ 1 3 6]
# [ 4 9 15]]忽略 NaN 的累积
nancumsum/nancumprod 忽略 NaN,NaN 位置的累计值保持前一个值:
import numpy as np
a = np.array([1.0, np.nan, 3.0, 4.0])
print(np.nancumsum(a)) # [1. 1. 4. 8.],NaN 被跳过
print(np.cumsum(a)) # [1. nan nan nan],普通累积被 NaN 污染1.5.4 差分与梯度
import numpy as np
a = np.array([1, 3, 6, 10, 15])
print(np.diff(a)) # [2 3 4 5],相邻元素之差
print(np.diff(a, n=2)) # [1 1 1],二阶差分
x = np.array([0.0, 1.0, 2.0, 3.0])
y = np.array([0.0, 1.0, 4.0, 9.0])
print(np.gradient(y)) # [1. 2. 4. 6.],数值梯度np.diff 计算相邻差值,n 参数指定差分阶数。np.gradient 计算数值梯度(一阶导数),可用于求导、检测变化趋势。
1.5.5 线性代数子模块(numpy.linalg)
点积:dot、matmul、@
三种点积写法,行为在二维数组时一致:
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]]
print(np.matmul(a, b))
# [[19 22]
# [43 50]]
print(a @ b) # @ 运算符等价于 matmul
# [[19 22]
# [43 50]]@ 是 Python 3.5+ 引入的矩阵乘法运算符,推荐日常使用。dot 对一维数组计算内积,matmul 对一维数组的行为与之不同,矩阵运算优先用 @ 或 matmul。
内积/外积/叉积
import numpy as np
u = np.array([1, 2, 3])
v = np.array([4, 5, 6])
print(np.inner(u, v)) # 32,内积 = 1*4 + 2*5 + 3*6
print(np.dot(u, v)) # 32,一维时与 inner 相同
print(np.outer(u, v))
# [[ 4 5 6]
# [ 8 10 12]
# [12 15 18]]
a = np.array([1, 0, 0])
b = np.array([0, 1, 0])
print(np.cross(a, b)) # [0 0 1],叉积,结果垂直于两向量矩阵转置共轭(.H)
.T 转置,.conj() 取共轭,.H 对复数矩阵做共轭转置:
import numpy as np
c = np.array([[1 + 2j, 3 - 1j], [2j, 4 + 1j]])
print(c.T) # 转置
print(c.conj()) # 共轭
print(c.conj().T) # 共轭转置,等于 c 的 Hermitian 伴随NumPy 里 .H 属性可以通过对复数矩阵取共轭转置得到,等价于 c.conj().T。
矩阵逆与伪逆
import numpy as np
a = np.array([[1, 2], [3, 4]])
print(np.linalg.inv(a))
# [[-2. 1. ]
# [ 1.5 -0.5]]
# 验证 A @ A^-1 = I
print(np.round(a @ np.linalg.inv(a), 10))
# [[1. 0.]
# [0. 1.]]
# 伪逆:对不可逆或非方阵也有效
b = np.array([[1, 2, 3], [4, 5, 6]])
print(np.linalg.pinv(b))inv 只对可逆方阵有效,不可逆时抛 LinAlgError。pinv 计算 Moore-Penrose 伪逆,对任意形状矩阵可用,常用于最小二乘问题。
行列式与秩
import numpy as np
a = np.array([[1, 2], [3, 4]])
print(np.linalg.det(a)) # -2.0000000000000004,行列式
print(np.linalg.matrix_rank(a)) # 2,秩
# 奇异矩阵行列式为 0,秩小于阶数
b = np.array([[1, 2], [2, 4]])
print(np.linalg.det(b)) # 0.0
print(np.linalg.matrix_rank(b)) # 1行列式为 0 的矩阵不可逆;矩阵的秩表示线性无关的行或列的最大个数。
特征值与特征向量
import numpy as np
a = np.array([[2, 0], [0, 3]])
eigvals = np.linalg.eigvals(a) # 只求特征值
print(eigvals) # [2. 3.]
eigvals2, eigvecs = np.linalg.eig(a) # 特征值和特征向量
print(eigvals2) # [2. 3.]
print(eigvecs) # 特征向量组成的矩阵
# [[1. 0.]
# [0. 1.]]eig 返回两个值:特征值数组和特征向量矩阵(每列是一个特征向量)。eigvals 只返回特征值。特征值与特征向量满足 A @ v = λ * v。
奇异值分解(SVD)
np.linalg.svd 把矩阵分解为 A = U Σ V^T:
import numpy as np
a = np.array([[1, 2], [3, 4], [5, 6]])
u, s, vh = np.linalg.svd(a)
print(u.shape) # (3, 3),左奇异向量
print(s) # 奇异值数组,按降序排列
print(vh.shape) # (2, 2),右奇异向量(转置)SVD 是矩阵分解中最通用的方法,对任意矩阵都适用,是主成分分析、推荐系统、降维等算法的数学基础。
QR 分解
np.linalg.qr 把矩阵分解为 A = Q R,Q 是正交矩阵,R 是上三角矩阵:
import numpy as np
a = np.array([[1, 2], [3, 4], [5, 6]])
q, r = np.linalg.qr(a)
print(q.shape) # (3, 2)
print(r.shape) # (2, 2)Cholesky 分解
np.linalg.cholesky 把正定对称矩阵分解为 A = L L^T,L 是下三角矩阵:
import numpy as np
a = np.array([[4, 2], [2, 3]])
l = np.linalg.cholesky(a)
print(l)
# [[1.41421356 0. ]
# [1.41421356 1. ]]Cholesky 分解要求矩阵对称正定,常用于多元正态分布抽样、协方差矩阵求逆。
解线性方程组:solve
np.linalg.solve(A, b) 求解 Ax = b:
import numpy as np
# 2x + y = 5
# x + 3y = 6
a = np.array([[2, 1], [1, 3]])
b = np.array([5, 6])
x = np.linalg.solve(a, b)
print(x) # [1.8 1.4],x=1.8, y=1.4
# 验证
print(a @ x) # [5. 6.]最小二乘解:lstsq
np.linalg.lstsq 求解最小二乘问题,在无解或多解时给出最优近似解:
import numpy as np
# 用直线 y = a + b*x 拟合数据
x_data = np.array([0, 1, 2, 3])
y_data = np.array([1, 3, 5, 7])
A = np.vstack([np.ones_like(x_data), x_data]).T
print(A.shape) # (4, 2)
coeff, residuals, rank, sv = np.linalg.lstsq(A, y_data, rcond=None)
print(coeff) # [1. 2.],截距 1,斜率 2范数与条件数
import numpy as np
v = np.array([3, 4])
print(np.linalg.norm(v)) # 5.0,默认 L2 范数
print(np.linalg.norm(v, ord=1)) # 7.0,L1 范数
print(np.linalg.norm(v, ord=np.inf)) # 4.0,无穷范数
a = np.array([[1, 2], [3, 4]])
print(np.linalg.cond(a)) # 条件数norm 计算向量或矩阵的范数,cond 计算矩阵的条件数,衡量矩阵对误差的敏感程度。
1.5.6 张量操作
np.tensordot
np.tensordot 在指定轴上做张量缩并,是通用化的矩阵乘法:
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
print(np.tensordot(a, b, axes=1))
# [[19 22]
# [43 50]],等价于 a @ b
c = np.arange(6).reshape(2, 3)
d = np.arange(12).reshape(3, 4)
print(np.tensordot(c, d, axes=1).shape) # (2, 4)np.einsum
np.einsum 用爱因斯坦求和记号描述张量运算,表达紧凑、功能强大:
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# 矩阵乘法: ij,jk -> ik
print(np.einsum("ij,jk->ik", a, b))
# [[19 22]
# [43 50]]
# 对角线: ii -> i
print(np.einsum("ii->i", a)) # [1 4]
# 转置: ij -> ji
print(np.einsum("ij->ji", a))
# [[1 3]
# [2 4]]
# 求迹: ii ->
print(np.einsum("ii->", a)) # 5einsum 的字符串中,逗号分隔各输入的下标,-> 后是输出下标,重复下标表示求和。掌握 einsum 可以写出极其简洁的高维张量运算。
练习题
第1题 概念理解
写出下面代码的输出结果,并解释 axis 参数与 keepdims 的作用。
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]])
print(np.sum(a, axis=0))
print(np.sum(a, axis=1))
print(np.mean(a, axis=0, keepdims=True).shape)参考答案
[5 7 9]
[6 15]
(1, 3)axis=0 逐列统计,axis=1 逐行统计。keepdims=True 保持维度数,结果形状从 (3,) 变成 (1, 3)。
第2题 代码编写
给定成绩矩阵 scores = np.array([[85, 90, 78], [60, 75, 80], [92, 88, 95]])(每行是一个学生,每列是一门课)。计算每个学生的平均分、每门课的平均分,以及全班总分。
参考答案
import numpy as np
scores = np.array([[85, 90, 78], [60, 75, 80], [92, 88, 95]])
print(scores.mean(axis=1)) # [84.33333333 71.66666667 91.66666667],每学生平均
print(scores.mean(axis=0)) # [79. 84.33333333 84.33333333],每科平均
print(scores.sum()) # 743,全班总分第3题 进阶练习
用 numpy.linalg 求解方程组:
3x + 2y = 12
x - y = 1用 solve 求 x、y,并用 eig 求系数矩阵的特征值和特征向量,说明特征值含义。
参考答案
import numpy as np
a = np.array([[3, 2], [1, -1]])
b = np.array([12, 1])
x = np.linalg.solve(a, b)
print(x) # [2.8 1.8],x=2.8, y=1.8
eigvals, eigvecs = np.linalg.eig(a)
print(eigvals) # 特征值
print(eigvecs) # 特征向量特征值 λ 满足 A v = λ v,表示矩阵在该特征向量方向上只做伸缩,不改变方向。
第4题 项目实践
命令行任务管理器要对任务的完成度数据做汇总统计。假设 10 个任务的完成度 completion = np.array([0.2, 0.5, 0.9, 0.1, 0.6, 0.8, 0.3, 0.7, 0.4, 1.0])。计算完成度的均值、中位数、标准差、最大值与最小值的差值,并找出完成度最高的任务位置。
参考答案
import numpy as np
completion = np.array([0.2, 0.5, 0.9, 0.1, 0.6, 0.8, 0.3, 0.7, 0.4, 1.0])
print(np.mean(completion)) # 0.55
print(np.median(completion)) # 0.55
print(np.std(completion)) # 0.275681
print(np.ptp(completion)) # 0.9
print(np.argmax(completion)) # 9,完成度最高的位置常见错误
错误 1 · 忘记 axis 导致整个数组聚合
原因:聚合函数不传 axis 时对整个数组归约成一个标量,而期望的是按行或按列统计。
解决:需要分轴统计时明确传 axis;不确定结果维度时检查返回数组的 shape。
错误 2 · LinAlgError: Singular matrix
原因:对奇异矩阵(行列式为 0,不可逆)调用 np.linalg.inv。
解决:先检查 np.linalg.det(a) 是否为 0,或改用 np.linalg.pinv 计算伪逆。
错误 3 · 用 * 做矩阵乘法
原因:对矩阵使用 a * b 得到的是逐元素相乘,而非矩阵乘法,结果与预期不符。
解决:矩阵乘法用 a @ b 或 np.matmul(a, b),* 只用于逐元素运算。
错误 4 · NaN 污染聚合结果
原因:数据含 NaN 时,sum/mean/std 等聚合结果变为 NaN。
解决:使用 np.nansum、np.nanmean 等忽略 NaN 的版本,或先用 np.isnan 清洗数据。