ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Numpy核心操作与避坑指南:从数组创建到矩阵运算

Numpy核心操作与避坑指南:从数组创建到矩阵运算 做数据分析的人基本都绕不开 Numpy。不管是做数据清洗、特征工程、统计建模还是后面接 Pandas、Scikit-learn、Spark底层都离不开这个库。你说它是数据分析的第一块地基一点都不夸张。这篇是“数据分析系列”的第二篇专门把 Numpy 里那些平时最常用、面试也常问、但文档里翻起来又很零散的东西整理成一条线从数组创建、索引切片、向量化计算、广播机制到矩阵运算、逆矩阵求解、批量三维数组相乘再到和 Pandas 配合的实战案例最后把安装报错、版本不匹配、trapz属性找不到这类坑一次性讲清楚。这篇文章适合刚入门 Python 数据分析的新手也适合用过一段时间但总觉得自己对 Numpy 的理解停留在“会用几个函数”层面的朋友。我会把每个操作背后的“为什么这样做”也讲明白而不是只丢给你一堆 API 用法。1. 数据分析第二篇为什么 Numpy 是绕不开的基石1.1 Numpy 是什么以及它到底解决了什么问题Numpy 的全称是 Numerical Python核心是提供了一个高性能的多维数组对象也就是ndarray。很多人第一次接触它时觉得“这不就是个数组吗Python 列表也能干这事”但真正到数据量大起来、计算复杂起来的时候就知道差距在哪了。Python 原生列表里的每个元素都是独立的 Python 对象光内存开销就大得离谱而且做循环计算时解释器的每一次迭代都有额外开销。Numpy 的数组在内存里是连续存放的元素类型统一底层计算用 C 语言实现再加上向量化运算能力能把原本要写 for 循环的批量计算直接变成对整个数组的操作。举个例子你要算 1000 万个数的正弦值。用原生 Python 列表写个循环挨个算在我的测试机器上大概要 5 秒多而用 Numpy 直接np.sin(arr)耗时大约是 30 毫秒差了将近 200 倍。这个差距不是靠优化代码风格能补回来的而是数据结构和计算模型层面的差距。1.2 与 Python 列表的对比真实性能测试我实际跑过一段简单的对比代码如下import numpy as np import math import time # 生成 1000 万个点 size 10_000_000 python_list list(range(size)) # 用原生 Python 列表算正弦 start time.time() result_list [math.sin(x) for x in python_list] print(Python list: , time.time() - start) # 用 Numpy 数组算正弦 arr np.arange(size, dtypenp.float64) start time.time() result_np np.sin(arr) print(Numpy array: , time.time() - start)在我笔记本上的结果是 Python 列表耗时 5.6 秒左右Numpy 数组耗时 0.035 秒左右。所以 Numpy 快的主要原因有三点第一数组内存连续缓存命中率高第二底层通过向量化指令和 C 语言实现没有逐元素解释执行的过程第三很多操作在 Numpy 内部已经用多线程或底层 BLAS、LAPACK 库优化过了。这也是为什么学 Numpy 时第一件事要纠正一个思维习惯别再用 for 循环去遍历数组做逐元素计算要想着“一次操作整个数组”。2. 上手 Numpy安装、数组创建与最常用的维度操作2.1 环境准备安装与验证Numpy 的安装非常简单常规情况下pip install numpy就能装好。但有几种情况容易出问题我先说正常的验证方式pip install numpy python -c import numpy as np; print(np.__version__)能打印出版本号就说明环境没问题。如果你用的是 Anaconda直接用conda install numpy也行。安装过程中最常见的报错是Installing backend dependencies...卡住不动这个我在文章第 6 节单独讲。这里先提醒一点如果是在公司内网或网络受限的环境里pip 默认源下载很慢建议配置国内镜像源比如清华、阿里云的 PyPI 镜像。装完之后最好确认一下是否和你本机 Python 版本匹配尤其是 Windows 上容易出现 32 位 / 64 位不匹配的问题。2.2 数组创建的四种常用方式Numpy 最常用的创建方式我总结成四类从 Python 列表创建np.array([[1, 2, 3], [4, 5, 6]])这种方式最直观适合小数据量或测试。全 0 全 1 数组np.zeros((3, 4))、np.ones((2, 5))经常用来初始化参数或占位。等差数列np.arange(0, 10, 2)和np.linspace(0, 1, 5)。arange适合整数序列linspace适合需要指定个数的等间隔浮点数序列。随机数组np.random.rand(3, 3)、np.random.randint(0, 10, size(3, 3))、np.random.normal(0, 1, 1000)。我特别推荐新手多用np.random系列来练手因为随机数据能模拟很多真实场景比如生成一组正态分布的数据后面做统计和可视化时非常方便。创建数组时最关键的一点是数据类型。Numpy 数组要求元素类型统一所以创建时可以通过dtype指定例如arr np.array([1, 2, 3], dtypenp.float64)如果你不指定默认会根据输入内容推断类型。数据分析和建模时混合类型常常会引发精度或计算问题所以养成显式指定dtype的习惯很有必要。2.3 索引、切片与维度变换Numpy 的索引切片和 Python 列表类似但多维度数组的写法略有不同arr np.arange(12).reshape(3, 4) # [[ 0 1 2 3] # [ 4 5 6 7] # [ 8 9 10 11]] print(arr[0]) # 第一行 print(arr[:, 1]) # 第二列 print(arr[1:, :2]) # 从第二行开始的两行取前两列这里有一个很多人踩过的坑切片出来的是视图不是副本。如果你修改切片后的数组原数组也会变。如果需要独立的副本必须显式调用.copy()。维度变换最常用的几个方法reshape改变形状比如(3, 4)变成(12,)。ravel/flatten把多维数组拉平。ravel返回视图可能flatten返回副本。transpose/.T转置交换维度顺序。d新增维度。arr[:, np.newaxis]可以把一维数组变成列向量。维度操作多练习几遍就会形成肌肉记忆。尤其是做矩阵计算时经常因为行向量和列向量的形状问题导致广播结果不对这一点在下一节会详细说。3. 向量化计算、广播机制与统计函数这是 Numpy 的深水区3.1 向量化计算的正确打开方式Numpy 的向量化意味着你可以直接对整个数组执行数学运算比如arr np.array([1, 2, 3, 4]) print(arr 1) # 每个元素加 1 print(arr * 2) # 每个元素乘 2 print(arr ** 2) # 每个元素平方 print(arr 2) # 布尔判断返回 True/False 数组不仅普通的加减乘除函数也可以直接作用于数组np.sqrt(arr)、np.exp(arr)、np.log(arr)、np.sin(arr)。这些运算内部都经过了高度优化。在数据分析项目里最常见的场景是对某一列数据做标准化data np.array([2.0, 4.0, 6.0, 8.0, 10.0]) mean data.mean() std data.std() scaled (data - mean) / std这就是向量化的典型写法一行代码完成批量操作不用写循环。3.2 广播机制不同形状的数组怎么运算广播是 Numpy 的核心机制也是新手最容易懵的地方。简单说当两个数组形状不一样时Numpy 会尽量把较小数组的维度“扩展”到和大数组一致然后再做逐元素运算。来看一个实际例子A np.arange(12).reshape(3, 4) b np.array([10, 20, 30, 40]) print(A b)这里A的形状是(3, 4)b的形状是(4,)。Numpy 会把b沿行方向扩展成 3 行相同的[10, 20, 30, 40]然后逐个相加。这个机制让代码写起来极其简洁。但广播也有规则不是随便什么形状都能算。规则很简单从最后一个维度往前比对如果两个维度相等或者其中一个为 1或者其中一个维度缺失就可以继续广播否则就会报ValueError: operands could not be broadcast together。实际操作中我建议养成一个习惯不确定形状能不能广播时先打印两个数组的shape用脑子过一遍规则再决定是reshape还是np.newaxis扩展维度。很多线上事故其实是形状不对但代码没报错结果算了半天得出一个完全错误的中间结果。3.3 统计函数与缺失值处理数据分析离不开统计描述。Numpy 的统计函数很全最常用的包括arr.sum()、arr.mean()、arr.std()、arr.var()arr.min()、arr.max()、arr.argmin()、arr.argmax()np.median()、np.percentile()、np.quantile()所有聚合类函数都可以指定axis参数这是多维数组统计的关键arr np.arange(12).reshape(3, 4) print(arr.sum(axis0)) # 对每一列求和返回 4 个值 print(arr.sum(axis1)) # 对每一行求和返回 3 个值axis0对应“沿着行方向移动结果里保留列的维度”理解成“纵向聚合”axis1是横向聚合。初期记不住没关系多做几次或者干脆每个 axis 都打出来看看结果形状。缺失值处理上Numpy 原生的np.nan会出现“一个 nan 拖垮整组统计”的情况。比如arr np.array([1.0, 2.0, np.nan, 4.0]) print(arr.mean()) # nan这时候需要用 Numpy 的专用版本np.nanmean(arr)、np.nanstd(arr)、np.nansum(arr)。它们会自动忽略 NaN 值。在实际数据清洗之前建议先用np.isnan(arr).any()检查有没有缺失。3.4 随机数模块模拟与抽样数据分析中随机数经常用来做模拟、抽样、划分训练集和测试集。np.random模块里的几个常用函数np.random.seed(42) arr1 np.random.rand(1000) # 均匀分布 [0, 1) arr2 np.random.randn(1000) # 标准正态分布 arr3 np.random.randint(0, 100, 50) # 整数随机数 arr4 np.random.normal(5, 2, 1000) # 均值为 5、标准差为 2 的正态分布设置seed的目的很简单让随机结果可复现。我以前做项目时吃过亏一个实验跑出来的结果每次都不一样排查了半天才发现是忘了设随机种子。做任何涉及到随机过程的实验第一步先固定种子。随机抽样也是常用场景。要对一个数组随机抽取 20% 的样本可以这样data np.arange(100) idx np.random.choice(len(data), size20, replaceFalse) sample data[idx]4. 矩阵运算、逆矩阵求解与三维数组相乘的实际玩法4.1 np.dot、np.matmul、 运算符到底怎么选矩阵乘法是线性代数里的基本功Numpy 里至少有三个长得差不多的写法。我直接说结论np.dot(A, B)对二维数组执行矩阵乘法对一维数组执行点积。np.matmul(A, B)矩阵乘法规则更严格。A BPython 3.5 之后推荐的矩阵乘法运算符等价于np.matmul。np.multiply(A, B)逐元素乘法不是矩阵乘法。这个区别必须搞清楚否则做三维数组乘法时会乱成一团。简单记法和matmul就是标准的矩阵乘法*在 Numpy 里默认是逐元素相乘只有两个都恰好是二维数组时结果巧合等于矩阵点乘实际是 element-wise不是矩阵乘法。4.2 求逆矩阵的三种方式以及什么时候不能用求逆矩阵是解线性方程组、统计计算、最小二乘法里的常见需求。最直接的写法是A np.array([[1, 2], [3, 4]]) A_inv np.linalg.inv(A)但要注意inv要求矩阵必须是方阵且满秩。如果行列式接近 0矩阵是奇异的逆矩阵不存在代码会抛LinAlgError: Singular matrix。遇到不可逆或接近奇异的情况我一般改用np.linalg.lstsq求最小二乘解或者用np.linalg.pinv求伪逆尤其在数据处理和建模场景里更实用。第三种方式是用np.linalg.solve代替显式求逆。解方程Ax b时不要写成x np.linalg.inv(A) b直接写x np.linalg.solve(A, b)。原因是solve使用 LU 分解数值更稳定速度更快也避免了求逆引入的额外误差。4.3 三维数组相乘到底怎么理解热点词里有一个“Numpy 三维数组相乘”这个问题确实容易把人绕晕。三维数组的矩阵乘法重点在于理解它对最后一维的处理方式。举个例子假设你有一个形状为(N, 2, 2)的数组里面的含义是 N 个 2x2 矩阵。你想让每一个小矩阵乘以同一个向量b [1, 2]维度是(2,)。如果直接写N 5 matrices np.random.rand(N, 2, 2) b np.array([1.0, 2.0]) result matrices b这时的计算规则是matrices的形状(5, 2, 2)看成“5 个(2, 2)的矩阵”b的形状(2,)广播为(5, 2, 2)的最后一维最终结果形状是(5, 2)。也就是说每个小矩阵分别与b做矩阵乘法得到(2,)的向量再堆叠起来。如果你想让每个小矩阵乘以不同的向量也就是批量矩阵乘以批量向量可以把向量数组的形状设为(N, 2, 1)然后把对应位置的小矩阵和对应位置的向量相乘vectors np.random.rand(N, 2, 1) result matrices vectors # 结果形状 (N, 2, 1)这里matrices vectors会按第 0 维一一对应每个(2,2)矩阵乘对应的(2,1)向量。更高阶的批量矩阵乘法在深度学习和批量数据处理中特别常见比如一次处理多个样本的线性变换。理解了这层逻辑后面读别人代码时就不会被(..., n, k) (..., k, m)这种写法吓到。5. 和 Pandas 配合的实战从模拟数据到统计结果5.1 为什么 Numpy 和 Pandas 是搭档而不是竞争对手Pandas 底层大量使用了 Numpy 数组DataFrame 的每一列本质上是 Numpy 数组的封装。所以学数据分析时Numpy 和 Pandas 是先后关系而不是替代关系。Pandas 长于表格操作、分组聚合、合并连接、时间序列而 Numpy 长于高性能数值计算和矩阵运算。你用 Pandas 处理完数据转化成一维或二维数值结构后往往还是要用 Numpy 做统计、线代或科学计算。5.2 实战案例生成模拟销售数据并按周统计我拿一个最简单的场景演示假设你有 30 天的销售数据临时没有现成的 CSV用 Numpy 生成一份模拟数据然后做统计分析。import numpy as np np.random.seed(2025) sales np.random.normal(loc500, scale80, size30).round(2) # 随机把 5 个数据变成缺失 missing_idx np.random.choice(30, size5, replaceFalse) sales[missing_idx] np.nan先检查缺失情况print(f缺失数量: {np.isnan(sales).sum()}) print(f整体均值: {np.nanmean(sales):.2f}) print(f整体标准差: {np.nanstd(sales):.2f})接着把数据按照每 7 天一周来分组统计。Numpy 提供了非常优雅的切分操作用reshape就能实现sales_7d sales[:28].reshape(4, 7) # 取前 28 天分成 4 周 week_sum np.nansum(sales_7d, axis1) week_mean np.nanmean(sales_7d, axis1)如果你还想知道这 30 天里最高的一天出现在什么时候用argmaxbest_day np.nanargmax(sales) print(f销量最高的一天是第 {best_day 1} 天)这种代码风格就是数据分析和普通 Python 写法的分水岭。你不需要一个数据一个数据处理而是用数组思维一下子算完整列。5.3 可视化之前的数据准备直方图与累积分布在用 Matplotlib 画图之前Numpy 也常用来设置分箱和统计频数。np.histogram直接返回频数和分箱边界counts, bin_edges np.histogram(sales, bins10)如果想计算数据的百分位排名用np.percentilep25 np.nanpercentile(sales, 25) p50 np.nanpercentile(sales, 50) p90 np.nanpercentile(sales, 90)这几个操作在做异常值判断、销售目标预测、容量规划时非常实用。比如判断某天销量是否处于前 10% 水平直接用sales np.nanpercentile(sales, 90)就能得到布尔数组。6. 常见问题与排查技巧安装、版本、trapz 属性问题一次说清6.1 安装时卡在“Installing backend dependencies”不少人在全新环境里执行pip install numpy时会看到卡在Installing backend dependencies很长时间然后报错或一直不动。这个问题的实质是 pip 在安装 numpy 的构建依赖比如 setuptools、wheel、Cython 等或者因为网络原因无法下载这些依赖包。解决办法我试过几种按推荐顺序排列先升级 pip 再装pip install --upgrade pip然后重新pip install numpy。换国内镜像源pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple。干脆用预编译的 wheel 包直接去 PyPI 或 Python 官网下载对应系统的.whl文件安装。如果还是慢检查是不是公司网络限制了 PyPI必要时配置代理或换 Anaconda 的 conda 通道。6.2 ModuleNotFoundError: No module named numpy这个报错最常见的原因是当前 Python 环境和安装 numpy 的环境不是同一个。尤其在使用 VS Code 或 Jupyter 时很容易出现“终端里 pip 装好了但 Jupyter 里 import numpy 却失败”的情况。排查思路如下在终端里执行python -c import numpy; print(numpy.__file__)确认当前环境能不能找到。在 Jupyter 里执行import sys; print(sys.executable)看当前 Python 解释器路径。对比两者路径是否一致如果不一致就统一环境或者给 Jupyter 安装ipykernel并选择正确的 kernel。也有可能是把项目文件命名成了numpy.py导致 import 时导入了自己写的文件而不是真正的库。这个坑虽然低级但我见过不止一次。6.3 Numpy 版本不匹配trapz 到底去哪了Numpy 2.0 里做了一件让很多人不习惯的事把np.trapz改名为np.trapezoid旧名字暂时保留但提示弃用。所以如果你在较新版本里直接写np.trapz不会再像以前那样无脑可用而某些下架较早、版本更旧的 Numpy 里又因为历史原因没有trapz这个属性于是网上就出现了大量“module numpy has no attribute trapz”的报错。遇到这种情况先打印一下版本号import numpy as np print(np.__version__)如果是新版本要么改用np.trapezoid要么用旧的np.trapz并在代码里加一个兼容判断trapz_func getattr(np, trapezoid, np.trapz)这行代码的意思是如果np.trapezoid存在就取它如果不存在退回旧版本里的np.trapz。这样代码在新旧版本里都能跑。6.4 其他容易踩的坑数据类型、视图复制、广播歧义除了安装和版本问题日常使用里还有几个坑需要提前预防。数据类型不匹配Numpy 数组的类型推断有时会带来精度损失比如np.array([1, 2, 3])默认是int64如果你往里面塞浮点数会被截断。视图 vs 副本arr_slice arr[:]修改arr_slice会改变arr。不需要改原数据时一定要.copy()。广播歧义不报错形状可以广播但语义上可能不是你想要的结果。比如形状(3, 1)和(1, 4)的数组相加结果会变成(3, 4)如果没有意识到这个扩张很容易在特征交叉时得到意料之外的矩阵。axis搞错方向sum、mean、max 系列函数的axis用反会直接导致统计结果错误而且这种错误往往不报异常属于最难排查的一类。建议每写一个聚合操作先打印result.shape验证。我把这几个典型问题整理成一张速查表报错或现象常见原因解决思路No module named numpy环境不一致检查sys.executable和 pip 安装路径pip 卡在Installing backend dependencies网络问题或缺少构建依赖升级 pip、换镜像源、装 wheelmodule numpy has no attribute trapzNumpy 2.0 改名用np.trapezoid或getattr兼容切片修改影响原数组视图机制显式.copy()Singular matrix报错矩阵不可逆改用pinv、lstsq或重新检查数据广播结果形状不对shape理解不到位打印形状用np.newaxis调整维度7. 我的一点实操体会从第一次接触 Numpy 到现在我最大的感受是学 Numpy 不是在背函数而是在换一种思维方式。你在 Python 里习惯了一个一个处理元素但 Numpy 要求你学会“整片整片处理数组”。刚开始很别扭但一旦习惯之后写代码的速度和分析问题的速度都会上一个台阶。如果让我给刚入门数据分析的人一个练习路径我会建议先把np.array、np.arange、reshape、random、sum/mean/std、、nanmean这几个核心操作练熟不要一开始就去钻花式索引和广播的冷门边界。然后拿一个实际的小数据比如自己近一个月的支出记录去跑一遍缺失值处理、分组统计、百分位分析再做一次可视化输入。等这一步走通了再回头啃linalg、广播和批量矩阵乘法就会觉得都是水到渠成的事。Numpy 后面还有太多值得聊的东西比如数组的内存布局、strides 原理、自定义 ufunc、和 Cython/C 的扩展接口这些等你真正把基础玩到滚瓜烂熟之后自然会知道该往哪个方向探索。不过在那之前把今天这篇里头的内容吃透已经足够应对绝大多数数据分析工作了。
返回列表