
Python 数据分析里 NumPy 矩阵运算的那些事儿
记得我第一次接触 NumPy 的时候,完全是一头雾水。那时候刚学 Python 不久,朋友给我推荐了一本《Python数据科学手册》,说这本书讲数据分析讲得特别透。我翻了前几章,满眼的 ndarray、矩阵运算、广播机制,直接把我整不会了。后来硬着头皮把书啃完,又跟着视频敲了不少代码,才慢慢开窍。
现在回头看,NumPy 其实是数据分析的根基。很多高级库比如 Pandas、Scikit-learn 底层都是基于 NumPy 写的。如果你正在用 Python 做数据分析却不了解 NumPy,那就好比盖房子不打地基,心里总是没底的。今天我想把这段时间用 NumPy 做矩阵运算的心得整理一下,说说它到底怎么用,哪些地方容易踩坑,以及怎么避开这些坑。
为什么 NumPy 这么重要
在 NumPy 出现之前,Python 处理大规模数值计算的能力确实有点尴尬。普通的列表 list 倒是能存数据,但做数学运算的时候效率低得吓人。比如你想算两个长度为一万的向量的点积,用纯 Python 写的话,循环一遍就得跑老半天。
NumPy 的核心是 ndarray 这个数据结构,全称 N-dimensional Array,意思是 N 维数组。这东西神奇之处在于,它把数据存在连续的内存块里,然后用 C 语言写的底层代码来做计算。这样一来,同样的向量化操作,NumPy 能比纯 Python 快几十倍甚至上百倍。我在做一个推荐系统的项目时深有体会,同样的算法,用列表实现要跑二十分钟,换成 NumPy 之后两分钟不到就跑完了。
更重要的是,NumPy 提供了非常丰富的矩阵运算函数。转置、求逆、特征值分解、奇异值分解,这些线性代数里的操作,它都有现成的函数可以直接调用。你不用自己从头写算法,节省了大量时间。
创建数组:一切运算的起点
在说矩阵运算之前,我们先聊聊怎么创建 NumPy 数组。这是基础中的基础,但里面也有不少讲究。

最直接的方式是用 np.array() 函数,把 Python 列表转成数组。比如:
import numpy as np
# 创建一维数组
arr1 = np.array([1, 2, 3, 4, 5])
# 创建二维数组(矩阵)
arr2 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr2)
这段代码跑完之后,你会看到一个三行三列的矩阵。输出是这样的:
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | 8 | 9 |

除了从列表创建,NumPy 还提供了很多方便的特殊数组创建函数。np.zeros() 创建全零矩阵,np.ones() 创建全一矩阵,np.eye() 创建单位矩阵,np.arange() 创建等差数列的一维数组,np.linspace() 创建等间距的一维数组。这些函数在初始化参数、生成测试数据的时候特别有用。
我个人的经验是,能用这些内置函数的就尽量用,一来代码更简洁,二来它们底层做了优化,速度比自己写循环创建快得多。有一次我需要生成一个十万行的大矩阵存测试数据,一开始用列表推导式转数组,跑了好几分钟都没出结果。后来换成 np.zeros()>,一秒钟不到就搞定了。
还有一点值得注意,NumPy 默认的数据类型是 float64。如果你处理的是整数数据,可以手动指定 dtype 为 int32 或者 int64,这样能省一半内存。对于大数据集来说,这个优化很关键。
基本矩阵运算:加减乘除与转置
矩阵的加减法是最简单的运算,要求两个矩阵形状完全相同。NumPy 里的做法也很直接,直接用 + - 号就行,系统会自动做元素级别的加减。
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 矩阵加法
C = A + B
# 矩阵减法
D = A - B
print("A + B =")
print(C)
print("A - B =")
print(D)
输出结果:
| A + B = | |
| 6 | 8 |
| 10 | 12 |
矩阵乘法要稍微复杂一点。严格意义上的矩阵乘法,要求第一个矩阵的列数等于第二个矩阵的行数,运算规则是行乘以列再相加。NumPy 里用 @ 符号或者 np.matmul() 函数来做这件事。
# 矩阵乘法
result = A @ B
# 或者用 np.matmul(A, B)
print("A @ B =")
print(result)
这里的结果是一个新的矩阵,第一行第一列的元素是 1*5 + 2*7 = 19,第一行第二列是 1*6 + 2*8 = 22,以此类推。
但要注意,Python 里还有一个 * 符号,这个在 NumPy 里是元素级别的乘法,也就是两个矩阵对应位置相乘,不满足矩阵乘法的数学定义。刚开始用的时候特别容易搞混,我自己也犯过好多次错误。最简单的区分方法是:做真正的矩阵乘法用 @ 或者 np.matmul(),做元素级运算用 *。
转置操作也很常用,把矩阵的行和列互换。在 NumPy 里用 .T 属性就行。比如 A.T 就是 A 的转置。对于高维数组,转置会交换所有轴的顺序,这个在处理图像数据的时候经常会用到。
广播机制:NumPy 的黑魔法
广播机制是 NumPy 最强大的特性之一,也是新手最容易困惑的地方。简单说,它允许不同形状的数组在一定条件下进行运算,自动把小的数组扩展成和大的数组一样的形状。
最常见的例子是用标量(单个数字)乘以矩阵。按理说标量是个 0 维数据,矩阵是二维,完全不搭界。但 NumPy 会自动把标量扩展成和矩阵一样大的数组,每个位置都是那个标量值,然后做元素级乘法。这相当于数学里的标量乘法,每一行每一列的元素都要乘以那个数。
再比如,一个一行三列的矩阵,可以和一个三行一列的矩阵做加法。NumPy 会自动把第一个矩阵复制三份变成三行三列,把第二个矩阵复制三列变成三行三列,然后对应位置相加。这种灵活的扩展机制让代码写起来简洁很多,不用手动写循环或者调用 tile 函数。
不过广播的规则需要牢记,不是所有形状都能兼容。最基本的要求是,从右向左对齐两个数组的形状,对应维度的长度要么相等,要么其中一个为 1。如果不满足这个条件,就会报错。刚开始用的时候,我经常遇到维度不匹配的报错,后来养成了拿到数组先看 shape 的习惯,用 .shape 属性检查一下形状,心裡就有数了。
常用数学函数与统计方法
NumPy 提供了大量的数学函数,涵盖三角函数、指数对数、舍入、求和求积等各个方面。这些函数都是向量化的,传入一个数组,返回一个新数组,对每个元素都做同样的运算。
统计相关的函数在数据分析里特别常用。求和用 np.sum(),平均值用 np.mean(),标准差用 np.std(),方差用 np.var(),最大值用 np.max(),最小值用 np.min()。这些函数可以指定 axis 参数来沿着某个轴进行计算,axis=0 意味着沿着列方向(对每一列做统计),axis=1 意味着沿着行方向(对每一行做统计)。
举个工作中的实际例子。我之前处理用户行为数据,需要计算每个用户的活跃天数、消费总额、平均消费金额这些指标。用 Pandas 的 groupby 当然可以,但用 NumPy 的统计函数配合 reshape 和广播,能做到更高效率的批量计算。特别是当数据量大的时候,NumPy 的向量化操作优势就更明显了。
线性代数操作:矩阵分解与方程求解
这部分算是 NumPy 的高级功能,但真的很实用。np.linalg 模块提供了线性代数相关的函数,包括矩阵求逆、特征值分解、奇异值分解、QR 分解、最小二乘拟合等等。
矩阵求逆用 np.linalg.inv(),但要求矩阵是可逆的,也就是行列式不为零。对于奇异矩阵或者接近奇异的矩阵,这个函数会抛出异常或者给出错误结果。实际应用中,最好先判断矩阵的条件数,用 np.linalg.cond() 看一下,如果条件数很大,说明矩阵接近奇异,求逆的结果会很不稳定。
解线性方程组是工程里经常遇到的问题。给定 Ax = b,要求 x。用 np.linalg.solve(A, b) 比先求逆再相乘更高效,也更数值稳定。特别是当需要对同一个 A 求解不同的 b 时,用 solve 只需要一次 LU 分解,后续求解会快很多。
特征值分解和奇异值分解在降维、推荐系统、图像处理里用得很多。np.linalg.eig() 做特征值分解,返回特征值和特征向量;np.linalg.svd() 做奇异值分解,返回三个矩阵的乘积分解。这两个分解都是数值算法实现的,对于大型矩阵来说计算量不小,但在很多场景下是不可或缺的工具。
实践建议与常见坑点
用 NumPy 久了,或多或少都会踩一些坑。我自己总结了几个最容易出错的地方,分享出来给大家提个醒。
第一个坑是视图和拷贝的区别。对数组进行切片操作的时候,返回的是原数组的视图,不是拷贝。这意味着修改视图里的数据会直接影响原数组。我有次写代码,原始数据被不小心改掉了,调了半天 bug 才找到原因。解决这个问题的方法是,需要独立副本的时候用 .copy() 方法明确拷贝一份。
第二个坑是数据类型的自动转换。NumPy 在运算的时候可能会自动提升数据类型,比如 int32 运算可能变成 float64。这有时候会导致输出类型和预期不符,或者内存占用突然变大。解决办法是在创建数组的时候就指定好 dtype,或者用 .astype() 方法显式转换类型。
第三个坑是维度的问题。二维数组和二维向量(矩阵和向量)有时候容易混淆。NumPy 里有 1D、2D、3D 等不同维度的数组,有时候函数要求输入是二维矩阵,但传进去的是一个一维数组,就会报维度错误。用 np.newaxis 或者 reshape() 可以调整数组的维度,把一维向量转成二维矩阵。
第四个坑是内存管理。大规模数组操作可能会占用很多内存,如果不注意及时释放,会导致内存溢出甚至程序崩溃。用 del 删除不再使用的大数组,或者在不需要的时候让变量离开作用域,有助于释放内存。对于极端大规模的数据,可以考虑使用内存映射文件 np.memmap(),把数据存在磁盘上而不是全部加载到内存里。
写在最后
NumPy 这门手艺,确实是看再多的教程都不如自己动手敲代码。我当年就是看了半天书觉得懂了,一上手还是各种报错。后来逼着自己每天写一点,遇到问题就查文档,慢慢才算是入了门。
如果你正在做数据分析相关的工作,建议把 NumPy 当作必备技能来培养。它不仅仅是一个工具,更重要的是它背后代表的那种向量化的思维方式。用列表循环写的代码和用 NumPy 向量化写的代码,运行效率可能差几十倍,这种差距在实际工作中影响是巨大的。
我平时习惯用 Raccoon - AI 智能助手来辅助学习和调试代码。遇到不太确定的用法,让 AI 帮我生成一段示例代码看看效果,比自己翻文档要快得多。当然,AI 生成的内容还是要自己验证一下,毕竟理论知识和实际应用之间总有差距。
数据分析这条路很长,NumPy 只是起点。后面还有 Pandas、Scikit-learn 这些更高级的库,但它们的很多概念都建立在 NumPy 的基础上。把基础打牢了,后面学什么都快。希望这篇文章对你有帮助,哪怕只是少踩一个坑,那也是值得的。




















