NumPy进阶指南:数组操作与向量化批量任务实战 📅 发布时间:2026/9/1 16:09:44 👁 浏览次数: 今天是《100天精通Python》系列的第 50 天主题是 numpy 进阶数组操作和常用方法。前面 49 天我们从 Python 基础语法、流程控制、函数、文件读写、面向对象一路走过来已经具备写 Python 脚本的能力。但从今天起要进入真正的数据处理阶段。无论是数据分析、机器学习、量化回测还是图像处理底层几乎都是 numpy 的多维数组。如果你打算继续学 pandas、scikit-learn、OpenCVnumpy 是绕不开的底子。很多同学学 numpy 只停留在“用 np.array() 创建一个数组、打印 shape”这一步真正要处理数据时还是用 for 循环逐个元素计算代码长、速度慢还容易在索引上出错。这篇文章把这些问题补齐数组创建、形状操作、拼接拆分、统计排序、布尔索引、广播机制以及最重要的“向量化批量任务”到底怎么用。全文按“环境准备 - 核心操作 - 常用方法 - 批量任务 - 性能观察 - 问题排查”的顺序展开每个知识点都带可运行的代码。建议打开 Jupyter Notebook 或 VS Code 跟着敲一遍遇到报错先看第 11 节的排查表。1. 核心能力速览能力项说明项目类型Python 科学计算第三方库核心对象ndarray 多维数组主要功能数组创建、形状变换、拼接拆分、统计聚合、排序去重、条件筛选、广播运算安装方式pip install numpy运行环境Windows / Linux / macOS 均可纯 CPU 可运行无独立显卡要求推荐 Python 版本Python 3.9 及以上具体以 numpy 当前版本官方支持范围为准是否支持批量任务支持向量化运算可一次处理整批数据是否提供 API提供完整 Python 函数式 API支持 np.save / np.load 数据持久化典型应用数据预处理、科学计算、机器学习特征工程、图像像素运算学习门槛需要掌握 Python 基础语法了解列表和循环即可开始2. 适用场景与使用边界numpy 适合三类人第一类是刚入门数据处理想用 Python 替代 Excel 做批量计算的开发者第二类是准备学机器学习需要处理特征矩阵和标签向量的同学第三类是写自动化脚本时经常面对大量数字、日志、传感器数据的工程师。先说能解决的问题。numpy 最核心的价值是“批量”一个数组里几千万元素求和、求均值、筛选、归一化一行代码完成不用写循环。配合 pandas 做表格处理、matplotlib 做可视化、scikit-learn 做建模numpy 就是所有这些工具的数据底座。再看边界。numpy 不是万能的下面这几种情况不建议硬用处理带列名、带多种类型的表格数据优先选 pandas而不是 numpy 二维数组。需要频繁从磁盘读写结构化数据优先用数据库或 Parquet、CSV 等格式不要只用 np.save。数据量极大且需要 GPU 加速numpy 默认跑 CPU可以考虑 cupy但那是另一个技术栈。复杂的字符串处理、正则匹配用 Python 标准库和 re 模块更顺手。另外提醒一点numpy 处理的数据来源要合规尤其是爬虫抓来的数据、用户隐私数据、有版权的内容在本地分析、二次加工和对外发布前必须确认授权范围。这是工程习惯不是额外负担。3. 环境准备与 numpy 安装numpy 是第三方库先确认 Python 环境再安装。打开终端或命令行检查 Python 和 pippython --version pip --version如果 Python 已经装好直接安装 numpypip install numpy国内网络环境慢的话可以指定清华镜像源pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后进入 Python 环境验证import numpy as np print(np.__version__)能打印出版本号就说明安装成功。比如输出 1.26.4 或 2.1.0不同版本 API 略有差异但下面代码在主流版本上都能跑。如果你同时管理多个项目建议把 numpy 装进虚拟环境避免和系统环境互相干扰python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate pip install numpy还有一个常见问题如果你用的是刚发布的新版本 Python而 numpy 还没有对应的预编译包pip install numpy可能会触发源码编译报错里会出现 “error: failed to build numpy when getting requirements to build wheel”。后面第 11 节会专门讲怎么处理。稳妥的做法是用当前稳定版 Python 配最新版 numpy。4. 数组创建与基础属性先从一个最简单的数组开始。import numpy as np arr1 np.array([1, 2, 3, 4]) print(type(arr1)) print(arr1.shape) print(arr1.dtype) print(arr1.ndim) print(arr1.size)输出含义type(arr1)是class numpy.ndarray也就是 numpy 的数组对象。shape是形状一维数组返回(4,)。dtype是元素类型这里是int64或int32取决于平台。ndim是维度数一维数组是 1。size是元素总个数这里是 4。创建二维数组也很直观arr2 np.array([[1, 2, 3], [4, 5, 6]]) print(arr2.shape) # (2, 3) print(arr2.ndim) # 2除了从列表创建numpy 提供了一批内置的创建函数。# 全零、全一、单位矩阵 zeros np.zeros((2, 3)) ones np.ones((3, 2)) eye np.eye(3) # 等差数组 arange_arr np.arange(0, 10, 2) # [0 2 4 6 8] linspace_arr np.linspace(0, 1, 5) # [0. 0.25 0.5 0.75 1. ] # 随机数组 rng np.random.default_rng(42) rand_arr rng.random((2, 3)) # 0-1 均匀分布 rand_int rng.integers(0, 100, (3, 3)) # 0-99 整数这里注意np.arange和np.linspace的区别arange指定步长linspace指定个数。linspace(0, 1, 5)会在 0 到 1 之间均匀生成 5 个数做坐标轴、时间序列时非常常用。写代码时建议用np.random.default_rng(42)而不是老的np.random.rand()前者可复现性更好也更符合新版本推荐写法。5. 数组形状操作reshape、flatten、转置维度调整是 numpy 使用频率最高的操作之一。5.1 reshape 重塑形状a np.arange(12) print(a) b a.reshape(3, 4) print(b) c a.reshape(2, 2, 3) print(c)reshape不会改变数据本身只是把数组从一种形状映射到另一种形状前提是元素总数一致。12可以变成(3, 4)、(2, 2, 3)、(4, 3)但不能变成(5, 2)因为元素个数对不上。不确定某个维度大小时用-1让 numpy 自动推断d a.reshape(-1, 4) print(d.shape) # (3, 4)这里-1表示“剩下的维度自己算”对快速变形很有用。5.2 ravel 与 flatten 展平m np.arange(6).reshape(2, 3) print(m) print(m.ravel()) # 展平为一维 print(m.flatten()) # 展平为一维两个方法都返回一维数组区别在于ravel在可能的情况下返回原数组的视图修改它可能影响原数组flatten总是返回一份拷贝改它不会影响原数组。对初学者拿不准时就用flatten()。5.3 转置m np.array([[1, 2, 3], [4, 5, 6]]) print(m.T) # 快速转置 print(m.transpose(1, 0)) # 等价写法二维数组转置就是行列互换。做矩阵运算、特征处理时经常用到。reshape和transpose的区别一定要搞清楚reshape只改形状不换数据顺序transpose会改变数据在维度的排列方向两者结果有时看起来一样语义完全不同。6. 数组拼接、拆分与元素增删实际处理数据时经常要把多个数组合并或者把一个数组按规则切开。6.1 拼接a np.array([[1, 2], [3, 4]]) b np.array([[5, 6], [7, 8]]) # 按行拼接上下叠 print(np.concatenate([a, b], axis0)) # 按列拼接左右并 print(np.concatenate([a, b], axis1)) # 上下堆叠 print(np.vstack([a, b])) # 左右堆叠 print(np.hstack([a, b])) # 新增维度堆叠 print(np.stack([a, b], axis0))concatenate是通用拼接函数vstack和hstack是垂直/水平堆叠的快捷方式。stack和前面几个不同它会增加一个维度把两个(2, 2)数组变成(2, 2, 2)或(2, 2, 2)常用于批量给模型准备输入数据。6.2 拆分arr np.arange(10) part1, part2, part3 np.split(arr, [2, 7]) print(part1) # [0 1] print(part2) # [2 3 4 5 6] print(part3) # [7 8 9]np.split(arr, [2, 7])表示在索引 2 和索引 7 处切开得到三段。如果每个子数组长度不要求相等用np.array_splitparts np.array_split(np.arange(10), 3) for p in parts: print(p)array_split适合把数据平均分成 N 份比如 10 条数据分 3 份结果是 4、3、3。6.3 增删元素arr np.array([1, 2, 3]) print(np.append(arr, [4, 5])) # [1 2 3 4 5] print(np.insert(arr, 1, 99)) # [1 99 2 3] print(np.delete(arr, [0, 2])) # [2]注意np.append、np.insert、np.delete都不会修改原数组而是返回一个新数组。如果在一个大循环里反复append性能和内存都很差正确做法是先收集到列表最后一次性转成 numpy 数组。7. 统计、排序与去重等常用方法这一节是数据处理最常用的工具箱。7.1 聚合统计arr np.array([[1, 2, 3], [4, 5, 6]]) print(arr.sum()) # 21 print(arr.sum(axis0)) # [5 7 9] print(arr.sum(axis1)) # [6 15] print(arr.mean()) # 3.5 print(arr.std()) # 标准差 print(arr.var()) # 方差 print(arr.min()) # 1 print(arr.max()) # 6 print(arr.argmin()) # 0最小值索引 print(arr.argmax()) # 5最大值索引这里最容易搞混的是axis。记住一条规则axis0表示沿着第 0 维方向聚合相当于把每一列“压扁”axis1表示沿着第 1 维方向聚合相当于把每一行“压扁”。对形状(2, 3)的数组sum(axis0)结果形状是(3,)对应每列的和。sum(axis1)结果形状是(2,)对应每行的和。不确定的时候先在小数组上打印结果对比一下。7.2 排序scores np.array([88, 65, 92, 73, 59]) print(np.sort(scores)) # 升序 [59 65 73 88 92] print(np.argsort(scores)) # 排序后原索引 [4 1 3 0 2]np.sort返回排序后的数组np.argsort返回排序后的索引位置。argsort在需要“排名”或“按另一列顺序重排”时很有用。二维数组排序要指定 axism np.array([[3, 1, 2], [6, 5, 4]]) print(np.sort(m, axis1)) # 每行排序7.3 去重tags np.array([a, b, a, c, b, a]) values, counts np.unique(tags, return_countsTrue) print(values) # [a b c] print(counts) # [3 2 1]return_countsTrue可以在去重的同时返回每个值的出现次数做类别统计非常方便比手动写循环快得多。7.4 其他常用方法# 累加 print(np.cumsum(np.array([1, 2, 3, 4]))) # [1 3 6 10] # 裁剪把数据限制在 [low, high] 区间 print(np.clip(np.array([1, 5, 9]), 2, 7)) # [2 5 7] # 四舍五入 print(np.round(np.array([1.234, 5.678]), 2)) # 是否存在满足条件 / 是否全部满足 print(np.any(np.array([1, 2, 3]) 2)) print(np.all(np.array([1, 2, 3]) 0))np.clip在归一化、处理异常值时很好用比如把用户评分限制在 1 到 5 之间一行搞定。8. 布尔索引、条件筛选与 np.wherePython 列表做筛选一般要写循环numpy 可以直接用布尔数组做索引这是 numpy 最有吸引力的特性之一。arr np.array([10, 25, 30, 45, 60]) mask arr 30 print(mask) # [False False False True True] print(arr[mask]) # [45 60] # 多条件组合 print(arr[(arr 20) (arr 50)]) # [25 30 45] # 把偶数元素改成 -1 arr[arr % 2 0] -1 print(arr) # [-1 25 -1 45 -1]几个要点布尔索引返回的是原数组的拷贝不是视图修改结果不会影响原数组。多条件组合必须用、|、~并且每个条件都要加括号不能用 Python 的and、or。布尔索引的速度非常快因为它只在 C 层做了一次遍历。条件判断后需要替换值时用np.wherescores np.array([55, 78, 92, 43, 88]) result np.where(scores 60, pass, fail) print(result) # [fail pass pass fail pass]np.where(条件, 满足时取值, 不满足时取值)是批量处理里出现频率极高的函数。比如把缺失值填 0、把负值归零、给数据打分都可以用 where 完成。9. 广播机制与向量化批量任务广播是 numpy 高阶操作的核心也是新手最容易报错的点。广播的规则可以简化成三条两个数组比较维度时从最后一个维度开始往前比。每个维度要么相等要么其中一个是 1。如果两个维度都不相等且都不是 1就无法广播报 ValueError。最常见的场景是“每行减去该行的均值”也就是按行或者按列做标准化。data np.random.default_rng(42).random((5, 3)) print(原始数据:) print(data) mean data.mean(axis0) std data.std(axis0) print(每列均值:, mean) print(每列标准差:, std) # 一行完成标准化形状 (5,3) 与 (3,) 广播 normalized (data - mean) / std print(标准化后:) print(normalized) # 验证每列均值接近 0标准差接近 1 print(normalized.mean(axis0)) print(normalized.std(axis0))这里data的形状是(5, 3)mean的形状是(3,)。numpy 会把(3,)自动补成(1, 3)再沿着第 0 维扩展到(5, 3)于是每一行都减去了同一列均值。这个过程就是广播。再看一个批量归一化的例子。假设你有 10000 个样本每个样本 20 个特征需要对所有样本做标准化samples np.random.default_rng(0).random((10000, 20)) mean samples.mean(axis0) std samples.std(axis0) # 一次计算所有样本不需要 for 循环 samples_norm (samples - mean) / std print(samples_norm.shape)这就是 numpy 处理批量任务的典型写法先分析数据形状再用广播去掉循环。无论是 100 条还是 10000 条数据代码是一样的计算都在 C 层完成。10. 性能观察向量化比循环快多少很多同学知道 numpy 快但不知道快多少。用 Python 自带的timeit可以直接对比。import timeit import numpy as np n 1_000_000 rng np.random.default_rng(0) arr rng.random(n) def loop_sum(): total 0 for x in arr: total x return total def numpy_sum(): return arr.sum() print(Python 循环耗时:, timeit.timeit(loop_sum, number5)) print(numpy 耗时:, timeit.timeit(numpy_sum, number5))在自己机器上跑一下大概率会看到 numpy 方案比纯 Python 循环快一个数量级以上。数据量越大、运算越复杂差距越明显。原因很简单Python 循环每处理一个元素都要做一次类型解释和边界检查而 numpy 把数据存在连续内存里底层调用的是编译好的 C/Fortran 代码一次处理一整块数据。所以写 numpy 的正确思路是“从单元素操作切换到整数组操作”尽量少写 for 循环。性能优化时的几个观察点数据量大时优先检查是否用了向量化写法而不是急着上多线程。reshape默认返回视图不复制数据内存开销小flatten会复制大数组慎用。反复在循环里拼接数组会造成大量内存拷贝先收集再一次性转数组。如果机器内存紧张处理超大数组时尽量用dtype更紧凑的类型比如float32代替float64。11. 常见问题与排查方法下面是 numpy 学习和使用中最高频的一批问题。问题现象可能原因排查方式解决方案pip install numpy 报 error: failed to build numpy when getting requirements to build wheel网络差、pip 版本旧、或当前 Python 版本没有对应预编译包触发了源码编译查看完整报错确认是否在编译先pip install --upgrade pip再用镜像源安装优先使用稳定版 Python 搭配新版 numpyimport numpy 报 ModuleNotFoundErrornumpy 未安装或安装到了另一个 Python 环境运行pip list查看环境在虚拟环境里执行pip install numpy确认 activate 了正确环境广播时报 ValueError: operands could not be broadcast together两个数组形状不兼容打印两个数组的 shape用 reshape 把需要广播的一方补成 1 的维度arr.sum()和arr.sum(axis1)结果不同axis 理解不对在 2x3 小数组上打印两种结果记住 axis0 压扁列axis1 压扁行修改 reshape 后的数组原数组也跟着变了reshape 返回的是视图共享内存用np.shares_memory(a, b)检查需要独立数据时用copy()整数数组做除法结果全变成 0整数数组不支持浮点除法结果自动截断打印 dtype 确认先arr.astype(float)再运算数组元素很多print 显示不完整numpy 默认省略显示打印 shape 确认用np.set_printoptions(thresholdsys.maxsize)或切片查看使用老版本 numpy 代码报 AttributeErrornumpy 2.x 移除了一些旧 API如 np.float_查看报错中的属性名升级代码写法或按项目要求锁定 numpy 1.x 版本如果安装时遇到编译报错最直接的解决方案是换用预编译 wheel。Windows 用户建议使用官方 Python 安装包不要使用来源不明的 Python 发行版Linux 用户可以通过pip install --only-binary :all: numpy强制只用二进制包安装。12. 最佳实践与下一步学习最后给出几条工程化建议。第一操作数组前先确认 shape。大约一半的 numpy 报错都是形状问题。养成习惯拿到数据先print(data.shape)再做计算能省大量排查时间。第二把“对单个元素思考”改成“对整个数组思考”。遇到循环先停一下问自己这个操作能不能用 numpy 的一次函数调用完成能就写向量化版本不能再考虑循环。第三保留最小可运行示例。工作中遇到 numpy 问题先用几行代码把问题复现出来再贴给同事或搜索引擎这样定位更快。第四批量任务一定要有日志和验证步骤。比如批量归一化之后检查每一列均值是否接近 0、标准差是否接近 1批量筛选之后检查筛选前后总数是否对得上。第五随机实验要固定种子。用np.random.default_rng(42)而不是每次随机结果才能复现写文章、做报告、调参都更可靠。第六中间结果及时落盘。需要多次使用的数组用np.save(data.npy, arr)保存下次np.load(data.npy)直接读比每次重新计算快得多也避免内存占用过高。今天的内容先到这里。第 51 天建议做一个练习生成 1000 名学生的成绩表用今天学的聚合、筛选、排序、归一化方法完成一轮完整的数据处理再把结果用np.save保存下来。这份练习做完再进入 pandas 会轻松很多。建议收藏这篇文章写代码时随时回来查 API。