
你是不是也有这种感觉NumPy 基础语法都看过了np.array会创建了但真正处理数据时还是绕来绕去数组维度怎么改都别扭合并、分割总要临时查文档统计结果又总想用 Python 循环写。如果是这样今天这篇就是帮你把“会用”变成“用熟”。这是「100天精通Python」系列的第 50 天主题是 NumPy 进阶。我们不再讲np.array([1, 2, 3])这种入门用法而是把数组操作和常用方法完整过一遍形状怎么变、数组怎么拼、怎么拆、统计函数怎么用、广播机制怎么理解最后再用timeit实际对比一次 Python 循环和 NumPy 向量化的性能差距。先给一个快速结论NumPy 的核心价值不是“能存数组”而是“在数组上做高效计算”。它底层用 C 语言实现配合向量化操作和广播机制同样一个求和任务比纯 Python 循环快几十倍甚至上百倍。这篇看完你会得到一个完整的数组操作工具箱后面做数据分析、机器学习预处理会顺手很多。1. 核心能力速览先看一张速览表快速确认 NumPy 到底覆盖哪些能力以及你需要在什么环境下使用它。能力项说明项目类型Python 第三方数值计算库核心功能数组创建、形状操作、合并分割、统计计算、向量化运算、广播机制输入数据Python 列表、元组、NumPy 数组、文件读取数据依赖环境Python 3.8 及以上支持 Windows / Linux / macOS安装方式pip install numpy或conda install numpy是否支持批量处理支持向量化运算天然适合批量数据典型场景数据分析、科学计算、机器学习预处理、图像数据处理学习建议先掌握ndarray属性再学操作最后用广播优化代码这份表格里最值得关注的是“向量化运算”和“广播机制”。很多初学者会用 NumPy 创建数组但真正写业务逻辑时仍然用 Python 循环逐个处理元素这会丢掉 NumPy 最大的性能优势。后面的章节会专门演示这个问题。2. 适用场景与使用边界NumPy 适合什么场景首先是任意形式的数值型数据批处理例如一组商品价格、传感器读数、模型的特征矩阵。其次是与线性代数相关的矩阵运算、点积、转置这是机器学习算法的基础。再就是数据清洗里的常见操作过滤异常值、归一化、排序、统计摘要。只要你的核心数据是“多维数值数组”NumPy 就是最顺手的工具。但 NumPy 也有明显的使用边界。如果你要处理的是复杂字符串文本、JSON 嵌套结构、关系型数据建模直接用 Python 字符串方法和 pandas 会更合适没必要硬套 NumPy。另外NumPy 数组要求所有元素类型一致不要把用户名、日期、分数混在同一个数组里。遇到这种数据更快的方式是先转成 pandas DataFrame。还有一个很重要的提醒如果在真实业务中处理用户数据、交易数据等敏感信息要先确认数据来源是否合规、是否取得授权。数据合规不是技术细节是使用前提。3. 环境准备与安装NumPy 安装很简单但很多人会卡在环境问题上。最常见的情况是机器上有多个 Python 版本pip install numpy装到了 A 目录python命令却指向 B 目录。所以第一步永远是确认 Python 环境。python --version pip --version确认版本后直接安装pip install numpy更稳妥的做法是先在项目目录创建虚拟环境避免污染全局环境python -m venv numpy-env # Windows numpy-env\Scripts\activate # Linux / macOS source numpy-env/bin/activate pip install numpy安装完成后打开 Python 交互环境验证import numpy as np print(np.__version__)如果能正常输出版本号比如2.1.0说明安装成功。如果提示ModuleNotFoundError: No module named numpy多半是装到了其他 Python 环境可以用pip show numpy查看安装位置再确认当前python指向哪里。4. 数组操作进阶从这一节开始进入正题。先建立基础数组然后逐个演示形状变换、转置、合并、分割、增删元素。4.1 创建数组并查看属性操作数组之前先养成一个习惯打印shape、ndim、dtype。很多形状错误都是因为没先确认这三项。import numpy as np a np.array([1, 2, 3, 4, 5]) # 一维数组 b np.zeros((2, 3)) # 两行三列全 0 c np.ones((2, 3)) # 两行三列全 1 d np.arange(10) # 0 到 9 e np.linspace(0, 1, 5) # 0 到 1 之间均匀取 5 个数 print(a:, a.shape, a.ndim, a.dtype) print(d:, d) print(e:, e)运行结果a: (5,) 1 int64 d: [0 1 2 3 4 5 6 7 8 9] e: [0. 0.25 0.5 0.75 1. ]这里的shape是维度元组ndim是轴数量dtype是元素类型。处理大数组时这三个属性就是你的“导航仪”。4.2 改变形状reshape、resize、ravel、flattenreshape是最常用的形状变换方法。它不修改原数组而是返回一个新视图。arr np.arange(12) print(arr.reshape(3, 4))输出[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]]如果不想手动计算行数或列数可以用-1让 NumPy 自动推断print(arr.reshape(-1, 4)) # 4 列行数自动计算 print(arr.reshape(3, -1)) # 3 行列数自动计算需要注意reshape要求元素总数完全一致。如果arr有 12 个元素你不能把它变成(5, 2)因为 12 不能被分成 5 行 2 列。这时可以先arr.size确认元素个数。再看ravel和flatten它们都是把多维数组变成一维m np.arange(6).reshape(2, 3) print(m.ravel()) # 返回视图可能修改原数组 print(m.flatten()) # 返回拷贝修改不影响原数组两者的关键区别是flatten返回全新数组ravel在可能的情况下返回视图。如果你不确定代码会不会互相影响直接用flatten()最安全。resize与reshape不同它可以改变数组总元素数量元素不足时会重复原数组数据r np.array([1, 2, 3]) print(np.resize(r, (2, 3)))输出[[1 2 3] [1 2 3]]4.3 转置与轴交换转置在矩阵运算里非常常见。一维数组的T没有实际效果二维及以上就很直观m np.arange(6).reshape(2, 3) print(原数组:) print(m) print(转置 T:) print(m.T) print(transpose(1, 0):) print(m.transpose(1, 0)) print(swapaxes(0, 1):) print(m.swapaxes(0, 1))输出原数组: [[0 1 2] [3 4 5]] 转置 T: [[0 3] [1 4] [2 5]]对于三维数组transpose可以指定轴的顺序。轴的理解是 NumPy 进阶的重要分水岭axis0通常表示第一维axis1表示第二维以此类推。之后合并、分割、统计全部和轴有关。three np.arange(24).reshape(2, 3, 4) print(three.transpose(2, 0, 1).shape) # 轴顺序从 (0,1,2) 变成 (2,0,1)输出(4, 2, 3)4.4 合并数组合并操作可以分为两类沿已有轴拼接、新增轴堆叠。concatenate是最基础的拼接方式用axis指定沿哪个轴合并a np.array([[1, 2], [3, 4]]) b np.array([[5, 6], [7, 8]]) # 沿 axis0也就是“行方向”拼接结果行数增加 print(np.concatenate([a, b], axis0)) # 沿 axis1也就是“列方向”拼接结果列数增加 print(np.concatenate([a, b], axis1))输出[[1 2] [3 4] [5 6] [7 8]][[1 2 5 6] [3 4 7 8]]vstack和hstack是更直观的封装vstack垂直堆叠hstack水平堆叠不需要再写axis。print(np.vstack([a, b])) print(np.hstack([a, b]))如果想把两个数组放进一个新的维度用stackc np.stack([a, b], axis0) print(c.shape) # (2, 2, 2)这个操作在处理批量样本时很常用。例如你有多个二维矩阵想组成一个三维批量张量就可以用stack。4.5 分割数组有合并就有分割。split按指定位置切分数组arr np.arange(10) parts np.split(arr, [2, 5, 8]) for p in parts: print(p)输出[0 1] [2 3 4] [5 6 7] [8 9]这里[2, 5, 8]是分割点的下标表示切在索引 2、5、8 位置最终切出 4 段。如果不想指定位置只想切成 N 份可以用array_splitprint(np.array_split(arr, 3))输出[array([0, 1, 2, 3]), array([4, 5, 6, 7]), array([8, 9])]二维数组可以用vsplit和hsplit按行、按列分割m np.arange(12).reshape(3, 4) print(np.vsplit(m, 3)) # 按行分 3 份 print(np.hsplit(m, 2)) # 按列分 2 份分割在交叉验证、分批次生成测试集、按批次处理数据时特别有用。批量任务里先split再逐段处理是控制内存占用的常见手段。4.6 增删元素NumPy 的insert、append、delete返回新数组不会修改原数组。arr np.array([1, 2, 3]) # 在索引 1 处插入 99 print(np.insert(arr, 1, 99)) # 末尾追加多个元素 print(np.append(arr, [4, 5])) # 删除索引 0 print(np.delete(arr, 0))输出[ 1 99 2 3] [1 2 3 4 5] [2 3]对于二维数组insert和delete需要指定axis否则结果会先扁平化再操作m np.array([[1, 2], [3, 4]]) # 在第二列前插入一列 print(np.insert(m, 1, 99, axis1)) # 删除第一行 print(np.delete(m, 0, axis0))输出[[ 1 99 2] [ 3 99 4]][[3 4]]这里要特别注意np.insert如果不写axis默认会先把数组拉成一位再插入返回的 shape 很可能不是你以为的那样。遇到这种问题先打印.shape。5. 常用方法与统计函数数组操作解决的是“怎么组织数据”统计方法解决的是“怎么描述数据”。5.1 数学统计方法NumPy 内置的统计方法覆盖了大多数日常需求arr np.array([[1, 2, 3], [4, 5, 6]]) print(全部元素求和:, arr.sum()) print(沿 axis0 求和:, arr.sum(axis0)) print(沿 axis1 求和:, arr.sum(axis1)) print(全局均值:, arr.mean()) print(每列均值:, arr.mean(axis0)) print(标准差:, arr.std()) print(方差:, arr.var()) print(全局最小值:, arr.min()) print(全局最大值:, arr.max()) print(每行最大值下标:, arr.argmax(axis1)) print(累加和:, arr.cumsum())输出全部元素求和: 21 沿 axis0 求和: [5 7 9] 沿 axis1 求和: [ 6 15] 全局均值: 3.5 每列均值: [2.5 3.5 4.5] 标准差: 1.707825127659933 方差: 2.9166666666666665 全局最小值: 1 全局最大值: 6 每行最大值下标: [2 2] 累加和: [ 1 3 6 10 15 21]axis是这里最容易混淆的地方。你可以记住一句话axis0是沿着“行方向”移动结果会把每一列压缩成一个数所以输出长度等于列数axis1是沿着“列方向”移动结果会把每一行压缩成一个数所以输出长度等于行数。不确定时拿一个 2x3 的小数组打印验证比死记硬背更有效。5.2 条件选择np.wherenp.where是数据清洗的利器。它有两种用法只传条件时返回满足条件的下标传三个参数时做条件替换。arr np.array([10, 23, 35, 42, 57, 68]) # 大于 30 的保留原值否则替换为 0 filtered np.where(arr 30, arr, 0) print(filtered) # 返回满足条件的下标 idx np.where(arr 30) print(idx) print(arr[idx])输出[ 0 0 35 42 57 68] (array([2, 3, 4, 5]),) [35 42 57 68]在批量任务里np.where经常用来替换异常值、标记缺失值、按阈值拆分数据比 Python 列表推导式更简洁。5.3 排序sort 与 argsortnp.sort返回排序后的数组np.argsort返回排序后的索引。后者在实际业务中很有用因为你可以用索引去对齐另一组数据。scores np.array([88, 72, 95, 60, 100]) print(排序结果:, np.sort(scores)) print(排序索引:, np.argsort(scores)) # 用索引取回原数组排序结果 print(按索引取值:, scores[np.argsort(scores)])输出排序结果: [ 60 72 88 95 100] 排序索引: [3 1 0 2 4]请注意np.sort默认不修改原数组。如果你要原地排序使用scores.sort()。排序方向可以用np.sort(scores)[::-1]或者np.sort(scores)[-N:]取前 N 个最大值。5.4 去重uniquenp.unique返回排序后的去重值并且可以用return_countsTrue统计每个值出现次数nums np.array([3, 1, 3, 2, 1, 5, 5, 5]) values, counts np.unique(nums, return_countsTrue) print(去重值:, values) print(出现次数:, counts)输出去重值: [1 2 3 5] 出现次数: [2 1 2 3]这个函数在统计分类标签分布时非常方便。比如给一个模型的预测结果做类别分布检查一行代码就能拿到每个类别的频次。5.5 广播机制广播机制是 NumPy 进阶里最值得理解的概念也是很多报错的来源。它的作用是在不复制数据的情况下让不同 shape 的数组参与运算。a np.array([[1, 2, 3], [4, 5, 6]]) b np.array([10, 20, 30]) print(a b)输出[[11 22 33] [14 25 36]]这里的b是一维数组但 NumPy 自动把它广播到与a相同的形状再做加法。广播规则可以简化为从最后一个维度往前对齐两个维度要么相等要么其中一个是 1否则无法广播。# 可以广播的情况(2, 3) 与 (3,) a1 np.ones((2, 3)) b1 np.ones((3,)) print((a1 b1).shape) # (2, 3) # 无法广播的情况(2, 3) 与 (2,) a2 np.ones((2, 3)) b2 np.ones((2,)) try: print(a2 b2) except ValueError as e: print(广播失败:, e)输出(2, 3) 广播失败: operands could not be broadcast together with shapes (2,3) (2,)理解广播之后很多代码可以去掉无意义的for循环直接用一维数组和二维数组做运算代码更短、性能更高。6. 性能对比Python 循环 vs NumPy 向量化前面反复说“用向量化代替循环”这里直接给一段可运行的对比脚本。你可以把它拿到自己机器上跑一遍观察差距。import timeit import numpy as np size 1000000 data list(range(size)) arr np.array(data) python_time timeit.timeit(lambda: sum(data), number50) numpy_time timeit.timeit(lambda: arr.sum(), number50) print(fPython 循环: {python_time:.4f}s) print(fNumPy 向量化: {numpy_time:.4f}s) print(f差距倍数: {python_time / numpy_time:.1f}x)不同机器上的绝对时间不同但趋势非常一致纯 Python 内置sum遍历 100 万个元素需要循环 100 万次而 NumPy 的arr.sum()底层用 C 语言连续内存计算通常快几十倍。原因在于 Python 的循环每一步都要经过解释器而 NumPy 把计算下沉到了编译好的 C 层。这不是说 Python 循环不能写而是要分场景小数据量无所谓几万到几百万规模的数据就应该用向量化。编写批量处理逻辑时优先思考“能不能用数组方法一次算完”而不是“怎么遍历每个元素”。7. 常见问题与排查方法NumPy 使用中最常见的坑主要集中在环境安装、维度匹配、视图修改这几类。问题现象可能原因排查与解决pip install numpy安装失败Python 环境不对、权限不够、网络源问题先python --version确认版本用虚拟环境安装必要时换国内镜像源ModuleNotFoundError: No module named numpy装到了另一个 Python 环境用pip show numpy查看安装位置确认当前 Python 的 site-packages 路径广播运算报shapes not aligned数组维度不匹配打印两个数组的.shape手动reshape或添加维度结果 shape 和预期不一致axis参数理解反了拿二维小数组验证axis0和axis1的输出理解后再应用到实际数据修改新数组后原数组也变了视图与拷贝未区分例如ravel()用.flatten()或.copy()显式创建独立数组数值结果溢出出现负数默认dtype位数不够创建数组时指定dtypenp.int64或dtypenp.float64reshape报 cannot reshape array元素总数与目标形状不一致先arr.size确认元素总数用-1自动推断一个维度API 调用返回数据再转 NumPy 形状不对数据源嵌套层级比预期深用.shape、.ndim观察必要时np.squeeze去掉长度为 1 的维度8. 最佳实践与使用建议写 NumPy 代码时我建议养成下面这些习惯能省下大量调试时间。第一写代码前先打印shape和dtype。尤其是从 CSV、数据库、外部 API 拿到数据后第一件事不是开始计算而是确认数据长什么样。这一步能避免一半以上的维度错误。第二能用向量化就不用循环。如果你的代码里出现for i in range(len(arr))停一下看看能不能改成np.where、arr.sum(axis1)、np.sort这类数组级方法。性能提升不是一点半点。第三明确axis的含义。这是 NumPy 初学者最混淆的概念。拿一个 2x3 的小数组分别执行sum(axis0)和sum(axis1)把输出打印出来看一次记忆比背文档更牢。第四区分视图和拷贝。reshape、ravel可能存在视图语义修改视图会影响原数组。不想被影响就用.copy()。这个点在高并发、批处理脚本里尤其重要否则可能出现“某个数组不知何时被改了”的诡异问题。第五合理指定dtype。整数和浮点数的精度直接影响统计结果。做归一化、均值、方差这类计算时优先保证数据是浮点类型避免整数除法带来的精度损失。第六涉及真实业务数据时先确认数据授权和隐私合规。NumPy 只负责计算数据从哪来、能不能用、能不能商用是使用者必须自己确认的事。9. 综合示例一次完整的数据处理流程把上面的知识点串起来模拟一个数据处理任务有一批测试数据先清洗异常值再做归一化然后输出统计摘要最后取出排序后的前 10 个最大值。import numpy as np # 模拟 1000 条测试数据均值 50标准差 15 data np.random.normal(loc50, scale15, size1000).round(2) print(原始数据 shape:, data.shape) # 1. 清洗把小于 0 的值替换为 0 data_clean np.where(data 0, 0, data) # 2. 归一化min-max 缩放到 [0, 1] min_val data_clean.min() max_val data_clean.max() data_norm (data_clean - min_val) / (max_val - min_val) # 3. 统计摘要 print(均值:, data_norm.mean()) print(标准差:, data_norm.std()) print(中位数:, np.median(data_norm)) print(最小值:, data_norm.min()) print(最大值:, data_norm.max()) # 4. 排序并取最大的 10 个值 top10 np.sort(data_norm)[-10:] print(最大的 10 个值:, top10)这段代码覆盖了本文的大部分重点np.random.normal创建模拟数据np.where清洗异常值数组运算做归一化mean、std、median做统计np.sort排序后切片取 Top N。运行后你会看到归一化后的数据均值大致在 0.5 附近标准差在 0.2 左右。这就是一套典型的“数据预处理 统计摘要”流程后面接 pandas 做进一步分析或直接喂给机器学习模型都是这个套路。到这里NumPy 的数组操作和常用方法已经过了一遍。建议先收藏这篇文章然后打开 Jupyter Notebook 或 Python 脚本把每个代码块亲手跑一遍。下一阶段可以继续学 pandas把 NumPy 数组放进 DataFrame 里做更贴近业务的数据分析。