前言
使用numpy数组可以使我们利用简单的数组表达式完成多种数据操作任务,而无须写些大量循环(避免循环是核心思想)。这种利用数组表达式来替代显示循环的方法,称为向量化。通常,向量化的数组操作会比纯Python快上一到两个数量级(甚至更多),这对所有种类的数值计算产生了最大影响。
PS: 这篇文章是我写的numpy知识总结的一部分,完整知识总结入口在这篇文章,在这篇文章里我搭建了numpy的基础知识框架,非常适合入门。
1.数组编程威力的证明
以一个列子说明数组编程的威力:
计算函数 f = sqrt(x^2 + y^2),其中x和y均为一维数组。
Python编程思路:通过写双重for循环计算,时间复杂度
O
(
n
2
)
O(n^2)
O(n2)。
面向数组编程思路:将两个数组生成一个二维矩阵,然后利用矩阵的逐元素计算避免循环。
【例1】两种思路的代码运行时间比较
#数组规模为个位数时
In [406]: x
Out[406]: [0, 1, 2, 3, 4]
In [407]: y
Out[407]: [0, 1, 2, 3, 4, 5, 6]
In [408]: %load test.py #魔法函数,加载写好的代码
In [409]: # %load test.py
...: import math
...:
...: def f(x,y):
...: temp = []
...: for i in x:
...: for j in y:
...: temp.append(math.sqrt(math.pow(i,2) + math.pow(j,2)))
...: return temp
In [410]: %time f(x,y)
CPU times: user 73 µs, sys: 59 µs, total: 132 µs
Wall time: 136 µs
In [411]: xs,ys = np.meshgrid(x,y)
In [413]: %time np.sqrt(xs ** 2 + ys ** 2)
CPU times: user 71 µs, sys: 33 µs, total: 104 µs
Wall time: 109 µs
#数组规模为千位数时
In [415]: x = list(_ for _ in range(999))
In [416]: y = list(_ for _ in range(1001))
In [417]: %time f(x,y)
CPU times: user 384 ms, sys: 11 ms, total: 395 ms
Wall time: 394 ms
In [418]: xs, ys = np.meshgrid(x,y)
In [419]: %time np.sqrt(xs ** 2 + ys ** 2)
CPU times: user 10.1 ms, sys: 11.5 ms, total: 21.5 ms
Wall time: 22.6 ms
首先需要说明,两个方法计算出的结果是相同的,因为太长,这里没有贴。以下是时间对比:
思路 | 数据规模 | total time |
---|---|---|
Python | 个位 | 132 µs |
数组 | 个位 | 104 µs |
Python | 千位 | 395 ms |
数组 | 千位 | 21.5 ms |
随着数据规模的增大,数组方法的执行效率优势非常明显,我们做数据处理时,十万和百万级的数据非常多,在这种情况下,面向数组编程的优势显而易见。
对meshgrid函数的解释:在这个例子中,显然函数是一个二元函数,那么将这个函数是在三维空间里的,x,y构成一个平面,meshgrid函数的作用就是将这个平面上x,y对应的点用一个二维矩阵表示出来,xs和ys在相同位置的元素对应了平面上的一个点对,所以对其进行的计算结果与for循环计算的结果相同。得到的xs, ys就如下面例子所示,xs是原数组为行的简单复制,ys是原数组变为列后的简单复制。xs的第一列和ys的第一列就对应了平面上的点(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),依此类推整个矩阵也就是数组x和y一一对应计算的所有数值对,对这些数值对的计算也就等同于双重for循环的计算。
【例2】解释meshgrid函数
In [42]: x = np.arange(5)
In [43]: y = np.arange(6)
In [44]: xs, ys = np.meshgrid(x,y)
In [45]: xs
Out[45]:
array([[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]])
In [46]: ys
Out[46]:
array([[0, 0, 0, 0, 0],
[1, 1, 1, 1, 1],
[2, 2, 2, 2, 2],
[3, 3, 3, 3, 3],
[4, 4, 4, 4, 4],
[5, 5, 5, 5, 5]])
注:这里没有加入meshgrid的测试时间,其时间消耗很少,可忽略不计。如果x,y本身是ndarray的话,运算效率可提升约40%。
就像meshgrid函数一样,numpy还提供了很多的函数用以支持面向数组的编程,大致可以分为以下几类:统计方法、排序、布尔值数组处理、唯一值及集合运算、数组版三元表达式where。
2. 统计方法(sum、mean等)
方法 | 描述 | 方法 | 描述 |
---|---|---|---|
sum | 沿着轴向计算所有元素的累积和,0长度的数组,累积和为0 | mean | 数学平均,0长度的数组平均值为NaN |
min, max | 最小值和最大值 | argmin, argmax | 最小值和最大值的位置 |
cumsum | 从0开始到当前元素的元素累和 | cumprod | 从1开始到当前元素的元素累积 |
std, var | 标准差和方差,可以选择自由度调整(默认分母是n) |
注:表格中的方法都是数组的方法,可以通过数组直接调用。
【例】部分统计方法示例
In [450]: arr = np.arange(12).reshape(4,3)
In [451]: arr
Out[451]:
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
#计算总和
In [453]: arr.sum()
Out[453]: 36
#计算0轴向
In [454]: arr.sum(0)
Out[454]: array([18, 22, 26])
#计算1轴向
In [455]: arr.sum(1)
Out[455]: array([ 3, 12, 21, 30])
In [458]: arr.cumsum(0)
Out[458]:
array([[ 0, 1, 2],
[ 3, 5, 7],
[ 9, 12, 15],
[18, 22, 26]])
In [459]: arr.cumsum(1)
Out[459]:
array([[ 0, 1, 3],
[ 3, 7, 12],
[ 6, 13, 21],
[ 9, 19, 30]])
3. 排序
numpy可以使用sort方法按位置排序,对于多维数组,需要传入axis值。sort方法有两种使用方式,一种改变原数组,一种创建副本。
arr.sort() //改变原数组元素顺序
np.sort(arr) //生成一个排序后的副本,不改变原数组顺序
【例】sort方法使用示例
In [467]: arr
Out[467]: array([0.19008705, 0.46913444, 0.33954087, 0.7044972 , 0.2260952 ])
In [468]: np.sort(arr)
Out[468]: array([0.19008705, 0.2260952 , 0.33954087, 0.46913444, 0.7044972 ])
#使用np.sort之后原数组元素顺序未变
In [469]: arr
Out[469]: array([0.19008705, 0.46913444, 0.33954087, 0.7044972 , 0.2260952 ])
In [470]: arr.sort()
#使用arr.sort之后原数组元素顺序改变
In [471]: arr
Out[471]: array([0.19008705, 0.2260952 , 0.33954087, 0.46913444, 0.7044972 ])
4. 布尔值数组处理
对布尔值数组,numpy也提供了处理函数,常见操作需求及其函数如下。
操作 | 函数 |
---|---|
计算True的个数 | arr.sum(),布尔值会强制为1(True)或0(Flase),所以通过求和即可计算 |
是否全为True | arr.all() |
至少一个为True | arr.any() |
【例】布尔值数组操作示例
In [476]: arr = np.random.randint(-10,10,size=(3,3))
In [477]: arr
Out[477]:
array([[-5, 5, 6],
[ 0, -2, -5],
[-2, 7, 9]])
In [478]: (arr>0).sum()
Out[478]: 4
In [479]: bools = arr>0
In [480]: bools
Out[480]:
array([[False, True, True],
[False, False, False],
[False, True, True]])
In [481]: bools.any()
Out[481]: True
In [482]: bools.all()
Out[482]: False
5. 唯一值及集合运算(对一维数组的操作)
numpy包含一些针对一维ndarray的基础集合操作。
方法 | 描述 |
---|---|
unique(x) | 计算x的唯一值,并排序 |
intersect1d(x,y) | 计算x和y的交集,并排序 |
union1d(x,y) | 计算x和y的并集,并排序 |
in1d(x,y) | 计算x中的元素是否包含在y中,返回一个布尔值数组 |
setdiff1d(x,y) | 差集,在x中但不在y中的x的元素 |
setxor1d(x,y) | 异或集,在x或y中,但不属于x、y交集的元素 |
注:以上方法中的x和y均为一维数组。
【例】一维数组处理示例
In [484]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will'])
In [485]: np.unique(names)
Out[485]: array(['Bob', 'Joe', 'Will'], dtype='<U4')
In [486]: values = np.array([6,0,0,3,2,5,6])
In [487]: np.in1d(values, [2,3,6])
Out[487]: array([ True, False, False, True, True, False, True])
6. 数组版三元表达式where
where函数是对Python三元表达式 x if condition else y 的向量化实现。通过where函数我们可以通过条件选择对数组中的数值进行替换,或者进行其他操作。以下例1说明具体语法,例2简单应用。
【例1】where语法示例
In [489]: xarr = np.array([1.1,1.2,1.3,1.4,1.5])
In [490]: yarr = np.array([2.1,2.2,2.3,2.4,2.5])
In [491]: cond = np.array([True,False,True,True,False])
In [492]: result = np.where(cond, xarr, yarr)
In [493]: result
Out[493]: array([1.1, 2.2, 1.3, 1.4, 2.5])
【例2】where应用示例
In [495]: arr = np.random.randn(4,4)
In [496]: arr
Out[496]:
array([[ 0.49042258, -0.15916412, 0.50222044, -0.28018826],
[ 1.698109 , 0.72016023, -0.19839624, -0.36279838],
[ 0.0680141 , 0.74892178, -0.97592457, 0.58013547],
[ 0.23533146, 0.01091129, 0.14336871, 1.35089938]])
In [497]: arr > 0
Out[497]:
array([[ True, False, True, False],
[ True, True, False, False],
[ True, True, False, True],
[ True, True, True, True]])
In [498]: np.where(arr > 0, 2, -2)
Out[498]:
array([[ 2, -2, 2, -2],
[ 2, 2, -2, -2],
[ 2, 2, -2, 2],
[ 2, 2, 2, 2]])
In [499]: np.where(arr > 0, 2, arr)
Out[499]:
array([[ 2. , -0.15916412, 2. , -0.28018826],
[ 2. , 2. , -0.19839624, -0.36279838],
[ 2. , 2. , -0.97592457, 2. ],
[ 2. , 2. , 2. , 2. ]])