办公小浣熊
Raccoon - AI 智能助手

Python 分析数据如何使用 NumPy 进行矩阵运算

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 的基础上。把基础打牢了,后面学什么都快。希望这篇文章对你有帮助,哪怕只是少踩一个坑,那也是值得的。

小浣熊家族 Raccoon - AI 智能助手 - 商汤科技

办公小浣熊是商汤科技推出的AI办公助手,办公小浣熊2.0版本全新升级

代码小浣熊办公小浣熊