Python 数据科学基础:NumPy 入门详细教程
NumPy 是 Python 数据科学的核心库,提供高效的多维数组对象和数学函数。本文系统梳理了 NumPy 的基础用法,包括数组创建、增删改查、变形拼接、统计计算、视图与拷贝机制、随机数生成及线性代数运算。重点解析了 axis 参数含义与广播机制原理,辅以代码示例帮助初学者快速掌握数值计算核心技能。

NumPy 是 Python 数据科学的核心库,提供高效的多维数组对象和数学函数。本文系统梳理了 NumPy 的基础用法,包括数组创建、增删改查、变形拼接、统计计算、视图与拷贝机制、随机数生成及线性代数运算。重点解析了 axis 参数含义与广播机制原理,辅以代码示例帮助初学者快速掌握数值计算核心技能。

Python 数据科学基础库主要是三剑客:NumPy、Pandas 以及 Matplotlib。每个库都集成了大量的方法接口,配合使用功能强大。平时虽然一直在用,也看过很多教程,但纸上得来终觉浅,还是需要自己系统梳理总结才能印象深刻。本篇先从 NumPy 开始,对 NumPy 常用的方法进行思维导图式梳理,多数方法仅拉单列表,部分接口辅以解释说明及代码案例。
ndarray 和 ufunc。其中前者是数据结构的基础,后者是接口方法的基础。这部分内容比较基础,仅补充一个个人认为比较有用的 ufunc 加聚合的例子。ufunc 本身属于方法(方法即是类内的函数接口),ufunc 之上还支持 4 个方法:
reduce:聚合方法accumulate:累计聚合reduceat:按指定轴向、指定切片聚合outer:外积当然,后两个用处较少也不易理解,前两个在有些场景下则比较有用:
import numpy as np
arr = np.array([1, 2, 3, 4])
print(np.add.reduce(arr)) # 输出:10 (1+2+3+4)
print(np.add.accumulate(arr)) # 输出:[1 3 6 10]
NumPy 中支持 5 类创建数组的方式:
如列表、元组等。
list_data = [1, 2, 3]
tuple_data = (4, 5, 6)
arr1 = np.array(list_data)
arr2 = np.array(tuple_data)
print(arr1) # [1 2 3]
支持大量方法,例如 ones、zeros、empty 等等。
empty:接收指定大小创建空数组,这里空数组的意义在于未进行数值初始赋值,随机产生,因而速度要更快一些。linspace 和 arange 功能类似,前者创建指定个数的数值,后者按固定步长创建。其中 linspace 默认包含终点值(可以通过 endpoint 参数设置为 false),而 arange 则不含终点。ones_arr = np.ones((2, 3)) # 全 1 数组
zeros_arr = np.zeros((2, 3)) # 全 0 数组
empty_arr = np.empty((2, 3)) # 空数组(未初始化)
linspace_arr = np.linspace(0, 10, 5) # 0 到 10 之间均匀分布 5 个数
arange_arr = np.arange(0, 10, 2) # 0 到 10 步长为 2
例如 .txt, .csv, .npy 等。
# np.loadtxt('data.txt')
# np.save('data.npy', arr)
通常用于处理二进制数据流。
例如 random 随机数包。
以上方法中,最为常用的是方法 1、2、5。
NumPy 提供了与列表类似的增删操作,其中:
append:是在指定维度后面拼接数据,要求相应维度大小匹配。insert:可以在指定维度任意位置插入数据,要求维度大小匹配。delete:删除指定维度下的特定索引对应数据。三种方法需要接收一个 axis 参数,如果未指定,则均会先对目标数组展平至一维数组后再执行相应操作。
arr = np.array([[1, 2], [3, 4]])
# append: 沿 axis=1 追加
appended = np.append(arr, [[5, 6]], axis=1)
print(appended)
# insert: 在 axis=0 的第 1 行插入
inserted = np.insert(arr, 1, [7, 8], axis=0)
print(inserted)
# delete: 删除 axis=1 的第 0 列
deleted = np.delete(arr, 0, axis=1)
print(deleted)
数组变形是指对给定数组重新整合各维度大小的过程,NumPy 封装了 4 类基本的变形操作:转置、展平、尺寸重整和复制。主要方法接口如下:
常用于对给定数组指定维度大小,原数组不变,返回一个具有新形状的新数组;如果想对原数组执行 inplace 变形操作,则可以直接指定其形状为合适维度。
arr = np.arange(6)
reshaped = arr.reshape(2, 3)
print(reshaped)
与 reshape 功能类似,主要有 3 点区别:
resize 面向对象操作时,执行 inplace 操作,调用 np.resize 类方法时则不改变原数组形状;而 reshape 无论如何都不改变原数组形状。resize 变形后的数组大小可以不和原数组一致,会自动根据新尺寸情况进行截断或拼接。resize 可以执行截断,所以要求接收确切的尺寸参数,不允许出现 -1 这样的'非法'数值;而 reshape 中常用 -1 的技巧实现某一维度的自动计算。另外,当 resize 新尺寸参数与原数组大小不一致时,要求操作对象具有原数组的,而不能是 view 或简单赋值。(具体参考 08 视图与拷贝一节)
arr = np.array([1, 2, 3])
resized = np.resize(arr, (2, 3)) # 会自动填充重复元素
print(resized)
ravel 和 flat 功能类似,均返回对数组执行展平后的结果,且不改变原数组形状,区别在于:
arr = np.array([[1, 2], [3, 4]])
print(arr.ravel()) # 返回数组
print(type(arr.flat)) # 返回迭代器
transpose 与 T 均执行转置操作,前者是方法,后者是属性。
arr = np.array([[1, 2], [3, 4]])
print(arr.T)
print(arr.transpose())
tile 和 repeat 方法类似,均为对给定数组执行复制操作,区别在于:
tile 面向整个数组复制,而 repeat 面向数组元素复制。tile 不接收维度参数,而 repeat 需指定维度参数,否则会对数组先展平再复制。arr = np.array([1, 2])
print(np.tile(arr, 2)) # [1 2 1 2]
print(np.repeat(arr, 2)) # [1 1 2 2]
数组拼接也是常用操作之一,主要有 3 类接口:
对给定的多个数组按某一轴进行拼接,要求所有数组具有相同的维度(ndim 相等)、且在非拼接轴大小一致。
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
result = np.concatenate((a, b), axis=0)
print(result)
共 6 个方法:
hstack, column_stack:功能基本一致,均为水平堆叠(axis=1),或者说按列堆叠。唯一的区别在于在处理一维数组时:hstack 按 axis=0 堆叠,且不要求两个一维数组长度一致,堆叠后仍然是一个一维数组;而 column_stack 则会自动将两个一维数组变形为 Nx1 的二维数组,并仍然按 axis=1 堆叠,自然也就要求二者长度一致,堆叠后是一个 Nx2 的二维数组。vstack, row_stack:功能一致,均为垂直堆叠,或者说按行堆叠,axis=0。dstack:主要面向三维数组,执行 axis=2 方向堆叠,输入数组不足 3 维时会首先转换为 3 维,主要适用于图像处理等领域。stack:进行升维堆叠,执行效果与前几种堆叠方式基本不同,要求所有数组必须具有相同尺寸。堆叠后,一维变二维、二维变三维……a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.hstack((a, b))) # 水平
print(np.vstack((a, b))) # 垂直
print(np.stack((a, b))) # 升维
r_[], c_[],效果分别与 row_stack 和 column_stack 类似,但具体语法要求略有不同。另外,虽然不是函数,但第一个参数可以是一个字符串实现特定功能设置。
数组切分可以看做是数组拼接的逆操作,分别对应:
hsplit:水平切分,要求切分后大小相等,维数不变,可以切分一维数组。vsplit:垂直切分,要求切分后大小相等,维数不变,要求至少二维以上。dsplit:纵深切分,要求切分后大小相等,维数不变,至少三维数组。split:通过接收一个 axis 参数实现任意切分,默认 axis=0,若设置 axis=1 或 2 则可分别实现 vstack 和 dstack。array_split:前面 4 个方法均要求实现相同大小的子数组切分,当切分份数无法实现整除时会报错。array_split 则可以适用于近似相等条件下的切分,也接受一个 axis 参数实现指定轴向。arr = np.arange(9).reshape(3, 3)
print(np.hsplit(arr, 3)) # 水平切分为 3 份
print(np.vsplit(arr, 3)) # 垂直切分为 3 份
NumPy 可以很方便的实现基本统计量,而且每种方法均包括对象方法和类方法:
max, argmax:分别返回最大值和最大值对应索引,可接收一个 axis 参数,指定轴线的聚合统计。对于二维及以上数组,若不指定 axis,即 axis=None,此时对数组所有数值求聚合统计。min, argmin:与最大一致。mean, std:分别求均值和标准差,也可接收一个缺省参数 axis 实现特定轴向聚合统计或全局聚合。var, cov:分别求方差和协方差,与均值标准差类似。sort, argsort:分别返回排序后的数组和相应索引,接收一个 axis 参数,默认为 axis=-1,按最后一个轴向,若 axis=None 表示先展平成一维数组后再排序;另外可设置排序算法,如快排、堆排或归并等。arr = np.array([[1, 2, 3], [4, 5, 6]])
print(np.mean(arr, axis=1)) # 每行的均值
print(np.sort(arr, axis=0)) # 每列排序
与列表的操作类似,NumPy 的数组类型也存在深浅拷贝之分:
注:正因为赋值和 view 操作后两个数组的数据共享,所以在前面 resize 试图更改数组形状时可以执行、但更改元素个数时会报错。
a = np.array([1, 2, 3])
b = a # 引用
b_view = a.view()
b_copy = a.copy()
b[0] = 100
print(a) # [100 2 3] (a 变了)
print(b_view) # [100 2 3] (b_view 变了)
print(b_copy) # [1 2 3] (b_copy 没变)
NumPy 提供了一些特殊的常量,值得注意的是 np.newaxis 可以用作是对数组执行升维操作,效果与设置为 None 一致。
arr = np.array([1, 2, 3])
print(arr[np.newaxis, :].shape) # (1, 3)
print(arr[:, np.newaxis].shape) # (3, 1)
Random 是 NumPy 下的一个子包,内置了大量的随机数方法接口,包括绝大部分概率分布接口,常用的主要还是均匀分布和正态分布:
random, rand, uniform,三者功能具有相似性,其中前两者均产生指定个数的 0-1 之间均匀分布,而 uniform 可通过设置参数实现任意区间的均匀分布;当需要产生整数均匀分布时,可用 randint。
print(np.random.rand(2, 2)) # 0-1 均匀分布
print(np.random.uniform(0, 10)) # 0-10 均匀分布
print(np.random.randint(0, 10)) # 0-10 整数
randn, normal,前者生成标准正态分布(均值为 0,方差为 1),后者产生任意正态分布,接收一个 loc 参数作为均值,scale 参数作为标准差。
print(np.random.randn(2, 2)) # 标准正态分布
print(np.random.normal(loc=5, scale=2)) # 均值 5,标准差 2
permutation, shuffle,对给定序列实现随机排列,前者返回一个新数组,后者是 inplace 操作。
seed,因为计算机中的随机数严格讲都是伪随机,需要依赖一个随机数种子来不断生成新的随机数,seed 可以用于固定这个随机种子。当指定随机数种子后,后续的随机将得到固化。
np.random.seed(42)
print(np.random.rand()) # 固定种子后结果可复现
除了随机数包,NumPy 下的另一个常用包是线性代数包,常见的矩阵操作均位于此包下。由于点积 dot() 和向量点积 vdot() 操作使用较为频繁,所以全局可用。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.dot(a, b)) # 点积
print(np.linalg.det(np.eye(3))) # 行列式
由于 NumPy 的基本数据结构是多维数组,很多接口方法均存在维度的问题,按照不同维度执行操作结果往往不同,例如拼接、拆分、聚合统计等,此时一般需要设置一个维度参数,即 axis。由于很多教程因为翻译或语言习惯不同,存在众说纷纭、口径不一的问题,有的说 axis=0 是横轴,有的说是纵向,所以如何理解 axis 的含义可能是很多 NumPy 初学者的常见困扰之一,笔者也是如此。
这一问题困扰了好久,直至一次无意间看到了相关源码中的注释:
如,在 sort 方法中,axis 参数的解释为"Axis along which to sort",翻译过来就是沿着某一轴执行排序。这里的沿着一词用得恰到好处,形象的描述了参数 axis 的作用,即相关操作是如何与轴向建立联系的,在具体解释之前,先介绍下 axis 从小到大的顺序问题。axis 从小到大对应轴的出场顺序先后,或者说变化快慢:axis=0 对应主轴,沿着行变化的方向,可以理解为在多重 for 循环中最外面的一层,对应行坐标,数值变化最慢;而 axis=1 对应次轴,沿着列变化的方向,在多重循环中变化要快于 axis=0 的轴向。类似地,如果有更高维度则依次递增。
至此,再来理解这里 axis 沿着的意义。举个例子,axis=0 代表沿着行变化的方向,那么自然地,切分方法 split(axis=0) 接口对应 vsplit,因为是对行切分,即垂直切分;而 split(axis=1) 接口则对应 hsplit,因为是对列切分,即水平切分;split(axis=2) 则对应 dsplit。类似的,np.sort(axis=0) 必然是沿着行方向排序,也就是分别对每一列执行排序。
想必这样理解,应该不会存在混淆了。
可能困扰 NumPy 初学者的另一个用法是 NumPy 的一大利器:广播机制。广播机制是指执行 ufunc 方法(即对应位置元素 1 对 1 执行标量运算)时,可以确保在数组间形状不完全相同时也可以自动的通过广播机制扩散到相同形状,进而执行相应的 ufunc 方法。
当然,这里的广播机制是有条件的:
条件很简单,即从两个数组的最后维度开始比较,如果该维度满足维度相等或者其中一个大小为 1,则可以实现广播。当然,维度相等时相当于未广播,所以严格的说广播仅适用于某一维度从 1 广播到 N;如果当前维度满足广播要求,则同时前移一个维度继续比较。
为了直观理解这个广播条件,举个例子,下面的情况均满足广播条件:
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([10, 20, 30])
print(a + b) # b 会被广播到与 a 相同的形状
而如下例子则无法完成广播:
a = np.array([[1, 2], [3, 4]])
b = np.array([1, 2, 3])
# print(a + b) # 会抛出 ValueError
本文系统梳理了 NumPy 的核心功能,涵盖了从基础创建到高级运算的各个方面。掌握这些知识将为后续学习 Pandas 数据处理和 Matplotlib 可视化打下坚实基础。建议读者在实际项目中多动手编写代码,加深理解。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online