NumPy是Python的一个扩展库,负责数组和矩阵运行。相较于传统Python,NumPy运行效率高,速度快,是利用Python处理数据必不可少的工具。
这个NumPy快速入门系列分为四篇,包含了NumPy大部分基础知识,每篇阅读时间不长,但内容含量高。大家最好亲自码一遍代码,这样可以更有收获。
前面的内容传送门:
Python进阶之NumPy快速入门(一)
基础运算
NumPy数组的基本运算,即加减乘除。我们分成两种情况:
我们先看两个形状一样的数组基础运算:
代码:
import numpy as np
a = np.array([1, 2, 3])
b = np.arange(10,13)
print (a+b, a-b)
print (a*b, a/b)
讲解:
我们建立了a,b两个一维数组,分别采用直接创建和用arange函数创建的方法。对于同样大小的数组之间的加减乘除运算,运算规则是对位元素一一对应。也就是说a的第一个元素和b的第一个元素进行运算,a的第二个元素和b的第二个元素进行运算,以此类推,所有对位的元素进行运算。
运行结果:
[11 13 15] [-9 -9 -9]
[10 22 36] [0.1 0.18181818 0.25]
广播机制
如果a,b两个数组的形状(shape)并不一样,那么运算规则又是什么样子的呢?Numpy对于两个不同形状的数组的运算采用一种叫做广播(broadcast)的机制负责运算:
代码:
a = np.array([[1, 2, 3],[4, 5, 6]])
b = np.arange(0,3)
print (a+b)
讲解:
a是一个2*3的数组,而b的形状是1*3,广播机制会让他们之间的加法得到一个相对合理的结果:
运行结果:
[[1 3 5]
[4 6 8]]
不难发现广播让a中第一个维度[1,2,3]加上b之后成为结果的第一个维度,让a中的第二个维度[4,5,6]加上之后成为结果的第二个维度。广播的规律总结起来有以下几点:
对于NumPy的广播,我给大家的建议是会多少用多少,尽量不要超出自己知识范围内使用。
高级运算:
代码:
a = np.array([1, 2, 3])
b = np.arange(10,13)
print (a**2, np.power(a,2))
print (np.sin(a))
print (np.mod(b,a))
讲解:
我们建立了a,b两个数组,第一个运算是求a每个元素的平方,有两种方法实现,二者结果相同。第二个运算,我们尝试了一下三角函数中的正弦函数。最后,我们用数组b对于数组a取余运算,除了11对于2取余等于1之外,其余都是0。
运行结果:
[1 4 9] [1 4 9]
[0.84147098 0.90929743 0.14112001]
[0 1 0]
索引就是像是GPS导航,可以直接到数组中的特定位置的元素。我们把数组的索引按方式不同分成两种,然后分别介绍:
数字索引
数字索引,顾名思义,就是根据数字来定位数组中元素,这个十分好理解。我们将数字索引分成两种方式:
对于一维数组,单个数字索引和列表方法一样。比如我们有一个数组A,那么A[x]就是索引A数组中的第x个元素,这里切记x从0开始计数,所以准确来讲是索引第x+1个元素。
对于二维的NumPy数组,我们也可以用一维索引的方法,这时我们会索引出某一行。
代码:
import numpy as np
A = np.arange(0,12)
print (A[3])
A = A.reshape((2,6))
print (A[1])
讲解:
我们首先建立了一个0到11的数组A,我们试图索引它的第四个元素。接着我们利用了一个变形技术reshape把A转换成一个二维数组,然后用一维索引得到变形后的第二行所有元素。
运行结果:
3
[ 6 7 8 9 10 11]
单个数字也可以扩展到二维甚至更高维度,例如对于二维数组索引方式一般可以写成A[1,1]或者A[1][1]。
现在我们着重介绍一下用冒号进行范围索引,因为我们有时候想要一段的数组,这时候范围索引就显得很方便实用。具体而言,有两种方式:
代码:
A = np.eye(5)
print (A[2,2], A[2][2])
print (A[1][0:4:2])
讲解:
我们首先用numpy.eye()函数建立了一个5乘以5的单位矩阵。先测试一下二维索引中单体索引,A[2,2]和A[2][2]两种方式都是可以的。接着我们测试一下范围索引,第一个[1]表示A矩阵的第二行:[0 1 0 0 0];后面的[0:4:2]其实只能索引出来两个数字,就是0和3两个位置上的数字。
运行结果:
1.0 1.0
[0. 0.]
布尔索引
这是一种通过布尔(逻辑)运算来获得符合条件元素的索引方式。简单来说,你可以通过给定一定的条件,筛选出满足条件的元素。这种索引方式是我们日常使用Numpy数组较为常用和使用的方法。
代码:
A = np.arange(0,9).reshape(3,3)
B = np.array([1, 2+6j, 5, 3.5+5j])
print (A>5)
print (A[A>5])
print (B[np.iscomplex(B)])
讲解:
我们先用两行代码给大家展示一下布尔索引的运算过程,第18行代码其实才是完整的操作,打印出A数组中大于5的元素,以一个一维数组的形式数出来。第17代码其实给出布尔运算的一步,输出结果为:大于5的位置是True而小于5的位置是False,接着通过真假关系带入A数组,最终把真的元素挑出来。这就是布尔索引的运算过程。B是一个打印出复数元素的例子,原理是一样的。
这一节课我们尝试用循环的方式,遍历数组中所有元素。考虑到常见的数组往往不止一个维度,因此单纯用while和for循环写起来很费事,所以我们有必要学习NumPy自带的遍历方法。
迭代数组 nditer
Numpy自带一个数组迭代器,叫nditer,可以让我们灵活访问数组中元素:
代码:
import numpy as np
A = np.arange(0,12).reshape(3,4)
for n in np.nditer(A):
print (n, end=' ')
讲解:
我们照例创建了一个形状为(3,4)的二维数组A,利用nditer配合for循环的格式,依次迭代访问数组A中的元素。注意到在print函数中,我们给参数end赋值了一个空格字符串,目的是让打印出来的元素可以被空格间隔。
运行结果:
0 1 2 3 4 5 6 7 8 9 10 11
大家可以尝试一下给end赋值别的字符串,例如逗号,换行等等。
控制顺序
事实上,nditer有一个参数来控制遍历顺序。这个参数叫order,有两个值可以选择:
我们来看个例子:
代码:
B = np.arange(0,9).reshape(3,3)
for n in np.nditer(B,order='C'):
print (n, end=' ')
print ('')
for n in np.nditer(B, order='F'):
print (n, end=' ')
讲解:
正如我们上面所说,'C'和'F'分别代表行和列优先。值得一提的这里的C,F并不是我们常见的row和column的缩写,而是代码C语言标准格式和Fortran格式,二者都是一种程序语言。
运行结果:
0 1 2 3 4 5 6 7 8
0 3 6 1 4 7 2 5 8
修改元素
nditer在遍历数组的时候,给我们提供了一个读写的选项,也就是说,我们根据这个读写开关可以改变数组的数值。这个参数叫做op_flags,默认值是只读模式'read-only',此时不可以修改元素。但是我们可以让op_flags赋值'readwrite' 或者 'writeonly':
代码:
C = np.arange(0,8).reshape(2,4)
for n in np.nditer( C,op_flags=['writeonly'] ):
n[...] = 2*n
print (C)
讲解:
我们利用'writeonly'将遍历的读写模式变成只写模式,大家也可以尝试'readwrite'一下看看效果如何。对于每个元素,我们都让它扩大两倍。有一点,我们用了n[...]格式,让乘以两倍后的元素重新赋值回去,[...]不可或缺。
运行结果:
[[ 0 2 4 6]
[ 8 10 12 14]]