NumPy库简介及高效使用指南
一、为什么要用NumPy库?
Python原生的数据结构和语法虽然也能实现许多功能,但效率较低。例如,当我们要求100万个数的倒数时,使用Python原生的for循环实现耗时较长。我们通过%timeit
测试得出,使用Python原生for循环实现平均每次运行时间为145毫秒;而使用NumPy库的向量化操作实现相同功能,平均每次运行时间仅为5.8毫秒左右,是前者的大约25倍。
NumPy库之所以高效,主要有以下几个原因:
- 底层使用C语言编写,C语言是编译型语言,执行速度快。
- NumPy数组是连续单一类型存储,这使得NumPy数组在内存中是连续存储的,而Python列表是分散存储的,因此NumPy数组的运行更加高效。
- Python语言执行时有线程锁,无法实现真正的多线程并行运算;而C语言不受此限制,因此在并行计算方面,NumPy库更胜一筹。
二、NumPy数组的创建
(一)从现有数据创建
-
从列表创建
可以直接将Python列表传入
np.array()
函数来创建NumPy数组,并且可以在创建时指定数据类型,如np.array([1, 2, 3], dtype=float)
。 -
创建特殊数组
np.zeros(5)
:创建长度为5,数值都为0的数组。np.ones((2, 4), dtype=float)
:创建2行4列,数值都为1的浮点型数组。np.full((3, 5), 8.8)
:创建3行5列,数值都为8.8的数组。np.eye(3)
:创建3×3的单位矩阵。np.arange(1, 15, 2)
:创建从1开始到15(不包括15),步长为2的线性序列数组。np.linspace(0, 1, 4)
:创建0到1之间平均分为3份的等差数列,共4个数值。np.logspace(0, 9, 10)
:创建10个数值构成的等比数列,数值范围从10的0次方到10的9次方。
(二)创建随机数组
np.random.random((3, 3))
:创建3×3的数组,数值为0到1之间的均匀分布随机数。np.random.normal(0, 1, (3, 3))
:创建3×3的数组,数值为均值为0,标准差为1的正态分布随机数。np.random.randint(0, 10, (3, 3))
:创建3×3的数组,数值为0到10(不包括10)之间的随机整数。np.random.permutation([1, 4, 9, 12, 7])
:对数组进行随机重排,不改变原数组。np.random.shuffle(x)
:对数组元素进行随机重排,改变原数组。np.random.choice(np.arange(10, 24), size=(4, 3), p=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4])
:从数组中进行随机采样,p
参数为采样概率。
三、NumPy数组的属性与操作
(一)属性
- 形状:
x.shape
,如(3, 4)
表示3行4列。 - 维度:
x.ndim
,如2表示二维数组。 - 大小:
x.size
,表示元素个数。 - 数据类型:
x.dtype
,如float64
。
(二)索引与切片
- 对于一维数组,索引与Python列表类似,可以使用正向索引和负向索引。
- 对于多维数组,例如
x[0, 0]
表示访问二维数组第0行第0列元素。 - 切片操作与列表类似,但需要分别对行和列设置,如
x[:2, :3]
表示取前两行前三列。 - 切片得到的是视图,若要得到副本需使用
copy()
方法。
(三)变形
- 使用
reshape()
函数改变数组形状,如x.reshape(3, 4)
将一维数组变为3行4列二维数组,但前后元素个数需相等。 - 将一维数组变为行向量或列向量可使用
x.reshape(1, -1)
或x.reshape(-1, 1)
,也可使用x[np.newaxis, :]
或x[:, np.newaxis]
。 - 将多维数组变为一维数组可使用
flatten()
、ravel()
或reshape(-1)
,其中flatten()
返回副本,ravel()
返回视图。
(四)拼接与分裂
- 水平拼接:
np.hstack([x1, x2])
或np.c_[x1, x2]
。 - 垂直拼接:
np.vstack([x1, x2])
或np.r_[x1, x2]
。 - 分裂:
np.split(x, [2, 7])
将一维数组在第2个元素和第7个元素前分割,np.hsplit(x, [2, 4])
和np.vsplit(x, [2, 4])
分别对二维数组进行水平和垂直分割。
四、NumPy数组的运算
(一)向量化运算
- 数组与数字的加减乘除等运算符作用于每个元素,如
x + 5
。 - 数组间的加减乘除等运算符作用于对应位置元素,如
x1 + x2
。 - 还支持取反、乘方、求整数商、求余数、求绝对值、三角函数、反三角函数、指数运算、对数运算等向量化操作。
(二)矩阵运算
- 转置:
x.T
。 - 矩阵乘法:
x.dot(y)
或np.dot(x, y)
。
(三)广播运算
- 当两个数组形状不匹配时,较小数组会沿着维度为1的方向扩展,使两者形状相同后进行运算。如
x + 5
,5会被扩展成与x形状相同的数组,每个元素都加5。
(四)比较运算与掩码
- 比较运算结果为布尔数组,如
x > 5
。 - 可对布尔数组进行求和、判断是否全为True/False、判断是否存在True/False等操作。
- 可将布尔数组作为掩码筛选出符合条件的元素,如
x[x > 5]
。
(五)花哨索引
- 对于一维数组,可通过索引数组取出多个指定位置元素,如
x[[1, 5, 8]]
。 - 对于多维数组,可通过行索引和列索引数组组合取出多个指定位置元素,如
x[[0, 1, 2], [1, 3, 0]]
。
五、NumPy通用函数
- 排序:
np.sort(x)
临时排序,x.sort()
永久排序,np.argsort(x)
获取排序后的索引。 - 最大最小值:
np.max(x)
、np.min(x)
,np.argmax(x)
、np.argmin(x)
获取最大最小值索引。 - 求和求积:
x.sum()
、np.sum(x)
,x.prod()
、np.prod(x)
,可按行axis=1
或按列axis=0
求和求积。 - 统计函数:
np.median(x)
求中位数,np.mean(x)
求均值,np.var(x)
求方差,np.std(x)
求标准差。
习题
1. 为什么NumPy库(n派库)比Python原生的for循环更高效?
答案:NumPy库比Python原生的for循环更高效的原因在于其底层是用C语言编写的,C语言是一种编译型语言,执行速度快;NumPy数组在内存中是连续单一类型存储,避免了类型检查和函数查找的时间开销;此外,Python有线程锁,无法实现真正的多线程并行运算,而C语言不受此限制,因此NumPy在并行计算方面更胜一筹。
2. NumPy数组的创建有哪两种方式?
答案:NumPy数组的创建有两种方式:一种是从Python列表开始创建,使用np.array()
方法;另一种是从无到有创建特定的数组,如全零数组np.zeros()
、全一数组np.ones()
、自定义值填充数组np.full()
、单位矩阵np.eye()
、线性序列数组np.arange()
、等差数列np.linspace()
、等比数列np.logspace()
、随机数组np.random.random()
、正态分布随机数组np.random.normal()
等。
3. NumPy数组的属性有哪些?
答案:NumPy数组的属性包括形状shape
、维度ndim
、大小size
、数据类型dtype
。
4. 如何对NumPy数组进行索引和切片?
答案:NumPy数组的索引和切片与Python列表类似,但需要分别对行和列进行操作。例如,x[0, 0]
表示访问二维数组的第一行第一列元素。切片操作如x[:2, :3]
表示取前两行前三列。需要注意的是,切片返回的是视图而非副本,若要获得副本,需使用x[:2, :3].copy()
。
5. 什么是NumPy数组的广播机制?
答案:NumPy数组的广播机制是指当两个数组在形状的维度上不匹配时,较小的数组会在维度为一的维度上进行扩展,使其形状与较大的数组匹配,从而可以进行逐元素运算。例如,一个形状为(3, 1)
的数组和一个形状为(1, 3)
的数组相加时,会扩展成(3, 3)
进行运算。
6. NumPy数组的四大运算包括哪些?
答案:NumPy数组的四大运算包括向量化的运算、矩阵化的运算、广播运算和比较运算与掩码。向量化的运算指对数组中所有元素进行逐元素运算;矩阵化的运算包括矩阵转置和矩阵乘法;广播运算是指形状不同的数组通过扩展进行逐元素运算;比较运算与掩码是指通过比较运算生成布尔数组,并用布尔数组作为掩码进行数据筛选。
7. 如何对NumPy数组进行排序和统计?
答案:可以使用np.sort()
对数组进行排序,argsort()
获取排序后的索引。统计操作包括求最大值np.max()
、最小值np.min()
、求和np.sum()
、求积np.prod()
、求中位数np.median()
、求均值np.mean()
、求方差np.var()
、求标准差np.std()
等。
8. 什么是NumPy数组的花哨索引?
答案:花哨索引是指通过一个或多个索引数组来选择数组中的元素。例如,x[[0, 2, 4]]
会选择一维数组中的第0、2、4个元素。对于多维数组,可以同时使用行索引和列索引来选择元素,如x[[0, 1], [1, 3]]
会选择(0,1)和(1,3)位置的元素。
9. NumPy数组的拼接和分裂有哪些方法?
答案:拼接方法包括水平拼接np.hstack()
或np.c_[]
,垂直拼接np.vstack()
或np.r_[]
。分裂方法包括np.split()
、np.hsplit()
(水平分裂)、np.vsplit()
(垂直分裂)。这些方法可以根据指定的索引或位置对数组进行分割或组合。
markdown
习题1:创建随机数组并获取属性
题目: 创建一个4x6的二维数组,元素由0至1之间的均匀分布随机数构成,并输出该数组的形状、大小、维度以及数据类型。
答案: 使用np.random.random
函数创建4x6的数组,其shape为(4, 6),size为24,维度为2,数据类型为float64。
习题2:数组形状变换
题目: 将习题1中的二维数组分别转化为3x8的数组和平摊为一维数组。
答案: 使用reshape
函数,3x8的数组可以通过reshape(3, 8)
获得,一维数组可以通过reshape(-1)
获得。
习题3:数组拼接
题目: 创建两个4x4的二维数组,元素为整数随机取自区间0到10。将这两个数组分别进行水平拼接和垂直拼接。
答案: 使用np.hstack
进行水平拼接,使用np.vstack
进行垂直拼接。也可以使用np.concatenate
函数,指定axis参数为1(水平)或0(垂直)。
习题4:创建特殊结构数组
题目: 创建一个16x16的数组,其中数组的外围均为1,内部均为0。
答案: 首先创建一个全零的16x16数组,然后通过切片赋值的方式将第一行、第一列、最后一行和最后一列设置为1。
习题5:统计分析与数据处理
题目: 创建一个由10,000个元素构成的一维数组,元素随机取值0至100。求该数组元素的均值、方差和标准差,并对其进行标准化处理。
答案: 使用np.mean
、np.var
和np.std
函数分别计算均值、方差和标准差。标准化处理公式为(x - np.mean(x)) / np.std(x)
。
习题6:成绩筛选与排序
题目: 创建一个由30个元素构成的一维数组,元素随机取自80至100。筛选出大于等于95分的成绩,小于等于85分的成绩,以及大于85分且小于95分的成绩,并对所有成绩进行排序。
答案: 使用布尔索引筛选成绩,如x[x >= 95]
表示大于等于95分的成绩,x[x <= 85]
表示小于等于85分的成绩,x[(x > 85) & (x < 95)]
表示大于85分且小于95分的成绩。使用np.sort
函数对成绩进行排序。
习题7:矩阵运算性能比较
题目: 构建两个3x3的二维列表A和B,分别实现逐元素乘法运算和矩阵乘法,并比较两种运算的性能。
答案: 使用Python的for循环手工实现逐元素乘法运算,并记录时间。使用np.multiply
实现逐元素乘法,使用np.dot
实现矩阵乘法,并比较两者的时间开销。通常情况下,NumPy提供的矩阵运算速度远快于手工实现。