1. NumPy
1.1. 目录
1.2. 简介
NumPy是一个基于Python数组和矩阵的运算库。
将普通的数组转换成矩阵,进行线性代数运算。
本文的示例程序均以如下方式导入NumPy函数库:
import numpy as np
1.3. 使用
1.3.1. array对象
创建
首先需要创建数组才能对其进行其它操作。
- 我们可以通过给array函数传递Python的序列对象创建数组,如果传递的是多层嵌套的序列,将创建多维数组:
> a = np.array([1, 2, 3, 4])
> b = np.array((5, 6, 7, 8))
> c = np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]])
> b
array([5, 6, 7, 8])
> c
array([[1, 2, 3, 4],
[4, 5, 6, 7],
[7, 8, 9, 10]])
> c.dtype
dtype('int32')
- arange函数类似于python的range函数,通过指定开始值、终值和步长来创建一维数组,注意数组不包括终值:
> np.arange(0,1,0.1)
array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
linspace函数通过指定开始值、终值和元素个数来创建一维数组,可以通过endpoint关键字指定是否包括终值,缺省设置是包括终值:
> np.linspace(0, 1, 12)
array([ 0. , 0.09090909, 0.18181818, 0.27272727, 0.36363636,
0.45454545, 0.54545455, 0.63636364, 0.72727273, 0.81818182,
0.90909091, 1. ])
logspace函数和linspace类似,不过它创建等比数列,下面的例子产生1(10^0)到100(10^2)、有20个元素的等比数列:
> np.logspace(0, 2, 20)
array([ 1. , 1.27427499, 1.62377674, 2.06913808,
2.6366509 , 3.35981829, 4.2813324 , 5.45559478,
6.95192796, 8.8586679 , 11.28837892, 14.38449888,
18.32980711, 23.35721469, 29.76351442, 37.92690191,
48.32930239, 61.58482111, 78.47599704, 100. ])
- 此外,使用frombuffer,fromstring, fromfile等函数可以从字节序列创建数组,下面以fromstring为例:
> s = "abcdefgh"
Python的字符串实际上是字节序列,每个字符占一个字节,因此如果从字符串s创建一个8bit的整数数组的话,所得到的数组正好就是字符串中每个字符的ASCII编码:
> np.fromstring(s, dtype=np.int8)
array([ 97, 98, 99, 100, 101, 102, 103, 104], dtype=int8)
如果从字符串s创建16bit的整数数组,那么两个相邻的字节就表示一个整数,把字节98和字节97当作一个16位的整数,它的值就是98*256+97 = 25185。可以看出内存中是以little endian(低位字节在前)方式保存数据的。
> np.fromstring(s, dtype=np.int16)
array([25185, 25699, 26213, 26727], dtype=int16)
> 98*256+97
25185
如果把整个字符串转换为一个64位的双精度浮点数数组,那么它的值是:
> np.fromstring(s, dtype=np.float)
array([ 8.54088322e+194])
显然这个例子没有什么意义,但是可以想象如果我们用C语言的二进制方式写了一组double类型的数值到某个文件中,那们可以从此文件读取相应的数据,并通过fromstring函数将其转换为float64类型的数组。
我们可以写一个Python的函数,它将数组下标转换为数组中对应的值,然后使用此函数创建数组:
> def func(i):
... return i%4+1
...
> np.fromfunction(func, (10,))
array([ 1., 2., 3., 4., 1., 2., 3., 4., 1., 2.])
- fromfunction函数的第一个参数为计算每个数组元素的函数,第二个参数为数组的大小(shape),因为它支持多维数组,所以第二个参数必须是一个序列,本例中用(10,)创建一个10元素的一维数组。
下面的例子创建一个二维数组表示九九乘法表,输出的数组a中的每个元素a[i, j]都等于func2(i, j):
>>> def func2(i, j):
... return (i+1) * (j+1)
...
>>> a = np.fromfunction(func2, (9,9))
>>> a
array([[ 1., 2., 3., 4., 5., 6., 7., 8., 9.],
[ 2., 4., 6., 8., 10., 12., 14., 16., 18.],
[ 3., 6., 9., 12., 15., 18., 21., 24., 27.],
[ 4., 8., 12., 16., 20., 24., 28., 32., 36.],
[ 5., 10., 15., 20., 25., 30., 35., 40., 45.],
[ 6., 12., 18., 24., 30., 36., 42., 48., 54.],
[ 7., 14., 21., 28., 35., 42., 49., 56., 63.],
[ 8., 16., 24., 32., 40., 48., 56., 64., 72.],
[ 9., 18., 27., 36., 45., 54., 63., 72., 81.]])
1.3.2. shape和reshape
数组的大小可以通过其shape属性获得:
> a.shape
(4,)
> c.shape
(3, 4)
数组a的shape只有一个元素,因此它是一维数组。
而数组c的shape有两个元素,因此它是二维数组,其中第0轴的长度为3,第1轴的长度为4。
还可以通过修改数组的shape属性,在保持数组元素个数不变的情况下,改变数组每个轴的长度。
下面的例子将数组c的shape改为(4,3),注意从(3,4)改为(4,3)并不是对数组进行转置,而只是改变每个轴的大小,数组元素在内存中的位置并没有改变:
> c.shape = 4,3
> c
array([[ 1, 2, 3],
[ 4, 4, 5],
[ 6, 7, 7],
[ 8, 9, 10]])
当某个轴的元素为-1时,将根据数组元素的个数自动计算此轴的长度,因此下面的程序将数组c的shape改为了(2,6):
> c.shape = 2,-1
> c
array([[ 1, 2, 3, 4, 4, 5],
[ 6, 7, 7, 8, 9, 10]])
使用数组的reshape方法,可以创建一个改变了尺寸的新数组,原数组的shape保持不变:
> d = a.reshape((2,2))
> d
array([[1, 2],
[3, 4]])
> a
array([1, 2, 3, 4])
数组a和d其实共享数据存储内存区域,因此修改其中任意一个数组的元素都会同时修改另外一个数组的内容:
> a[1] = 100 # 将数组a的第一个元素改为100
> d # 注意数组d中的2也被改变了
array([[ 1, 100],
[ 3, 4]])
1.3.3. dtype
数组的元素类型可以通过dtype属性获得。上面例子中的参数序列的元素都是整数,因此所创建的数组的元素类型也是整数,并且是32bit的长整型。可以通过dtype参数在创建时指定元素类型:
> np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]], dtype=np.float)
array([[ 1., 2., 3., 4.],
[ 4., 5., 6., 7.],
[ 7., 8., 9., 10.]])
> np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]], dtype=np.complex)
array([[ 1.+0.j, 2.+0.j, 3.+0.j, 4.+0.j],
[ 4.+0.j, 5.+0.j, 6.+0.j, 7.+0.j],
[ 7.+0.j, 8.+0.j, 9.+0.j, 10.+0.j]])
1.3.4. 存取元素
数组元素的下标存取方法和Python的标准方法相同:
> a = np.arange(10)
> a[5] # 用整数作为下标可以获取数组中的某个元素
5
> a[3:5] # 用范围作为下标获取数组的一个切片,包括a[3]不包括a[5]
array([3, 4])
> a[:5] # 省略开始下标,表示从a[0]开始
array([0, 1, 2, 3, 4])
> a[:-1] # 下标可以使用负数,表示从数组后往前数
array([0, 1, 2, 3, 4, 5, 6, 7, 8])
> a[2:4] = 100,101 # 下标还可以用来修改元素的值
> a
array([ 0, 1, 100, 101, 4, 5, 6, 7, 8, 9])
> a[1:-1:2] # 范围中的第三个参数表示步长,2表示隔一个元素取一个元素
array([ 1, 101, 5, 7])
> a[::-1] # 省略范围的开始下标和结束下标,步长为-1,整个数组头尾颠倒
array([ 9, 8, 7, 6, 5, 4, 101, 100, 1, 0])
> a[5:1:-2] # 步长为负数时,开始下标必须大于结束下标
array([ 5, 101])
和Python的列表序列不同,通过下标范围获取的新的数组是原始数组的一个视图。
它与原始数组共享同一块数据空间:
> b = a[3:7] # 通过下标范围产生一个新的数组b,b和a共享同一块数据空间
> b
array([101, 4, 5, 6])
> b[2] = -10 # 将b的第2个元素修改为-10
> b
array([101, 4, -10, 6])
> a # a的第5个元素也被修改为10
array([ 0, 1, 100, 101, 4, -10, 6, 7, 8, 9])
除了使用下标范围存取元素之外,NumPy还提供了两种存取元素的高级方法。
使用整数序列
当使用整数序列对数组元素进行存取时,将使用整数序列中的每个元素作为下标,整数序列可以是列表或者数组。使用整数序列作为下标获得的数组不和原始数组共享数据空间。
> x = np.arange(10,1,-1)
> x
array([10, 9, 8, 7, 6, 5, 4, 3, 2])
> x[[3, 3, 1, 8]] # 获取x中的下标为3, 3, 1, 8的4个元素,组成一个新的数组
array([7, 7, 9, 2])
> b = x[np.array([3,3,-3,8])] #下标可以是负数
> b[2] = 100
> b
array([7, 7, 100, 2])
> x # 由于b和x不共享数据空间,因此x中的值并没有改变
array([10, 9, 8, 7, 6, 5, 4, 3, 2])
> x[[3,5,1]] = -1, -2, -3 # 整数序列下标也可以用来修改元素的值
> x
array([10, -3, 8, -1, 6, -2, 4, 3, 2])
使用布尔数组
当使用布尔数组b作为下标存取数组x中的元素时,将收集数组x中所有在数组b中对应下标为True的元素。使用布尔数组作为下标获得的数组不和原始数组共享数据空间,注意这种方式只对应于布尔数组,不能使用布尔列表。
> x = np.arange(5,0,-1)
> x
array([5, 4, 3, 2, 1])
> x[np.array([True, False, True, False, False])]
> # 布尔数组中下标为0,2的元素为True,因此获取x中下标为0,2的元素
array([5, 3])
> x[[True, False, True, False, False]]
> # 如果是布尔列表,则把True当作1, False当作0,按照整数序列方式获取x中的元素
array([4, 5, 4, 5, 5])
> x[np.array([True, False, True, True])]
> # 布尔数组的长度不够时,不够的部分都当作False
array([5, 3, 2])
> x[np.array([True, False, True, True])] = -1, -2, -3
> # 布尔数组下标也可以用来修改元素
> x
array([-1, 4, -2, -3, 1])
布尔数组一般不是手工产生,而是使用布尔运算的ufunc函数产生,关于ufunc函数请参照 《ufunc运算 》一节。
> x = np.random.rand(10) # 产生一个长度为10,元素值为0-1的随机数的数组
> x
array([ 0.72223939, 0.921226 , 0.7770805 , 0.2055047 , 0.17567449,
0.95799412, 0.12015178, 0.7627083 , 0.43260184, 0.91379859])
> x>0.5
> # 数组x中的每个元素和0.5进行大小比较,得到一个布尔数组,True表示x中对应的值大于0.5
array([ True, True, True, False, False, True, False, True, False, True], dtype=bool)
> x[x>0.5]
> # 使用x>0.5返回的布尔数组收集x中的元素,因此得到的结果是x中所有大于0.5的元素的数组
array([ 0.72223939, 0.921226 , 0.7770805 , 0.95799412, 0.7627083 ,
0.91379859])
1.3.5. 多维数组
1.3.6. 结构数组
1.3.7. 内存结构
1.4. matrix对象
1.5. ufunc运算
1.5.1. 广播
1.5.2. ufunc的方法
1.6. 文件存取
NumPy提供了多种文件操作函数方便我们存取数组内容。
文件存取的格式分为两类:二进制和文本。
而二进制格式的文件又分为NumPy专用的格式化二进制类型和无格式类型。
1.6.1. tofile和numpy.fromfile
使用数组的tofile方法可以方便地将数组中数据以二进制的格式写进文件。
需要的时候可以使用numpy.fromfile来读取。
tofile输出的数据没有指定格式,因此用numpy.fromfile读回来的时候需要指定数据的格式。
请看下面的例子:
> a = np.arange(0,12)
> a.shape = 3,4
> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
> a.tofile("a.bin")
> b = np.fromfile("a.bin", dtype=np.float) # 按照float类型读入数据
> b # 读入的数据是错误的
array([ 2.12199579e-314, 6.36598737e-314, 1.06099790e-313,
1.48539705e-313, 1.90979621e-313, 2.33419537e-313])
> a.dtype # 查看a的dtype
dtype('int32')
> b = np.fromfile("a.bin", dtype=np.int32) # 按照int32类型读入数据
> b # 数据是一维的
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
> b.shape = 3, 4 # 按照a的shape修改b的shape
> b # 这次终于正确了
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
1.6.2. numpy.load、numpy.save和numpy.savez
numpy.load和numpy.save函数以NumPy专用的二进制类型保存数据。
这两个函数会自动处理元素类型和shape等信息,使用它们读写数组就方便多了。
但是numpy.save输出的文件很难和其它语言编写的程序读入。
> a = np.arange(0, 12)
> a.shape = 3,4
> np.save("a.npy", a)
> c = np.load( "a.npy" )
> c
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
numpy.savez
如果你想将多个数组保存到一个文件中的话,可以使用numpy.savez函数。
savez函数的第一个参数是文件名,其后的参数都是需要保存的数组,也可以使用关键字参数为数组起一个名字,非关键字参数传递的数组会自动起名为arr_0, arr_1, ...。
savez函数输出的是一个压缩文件(扩展名为npz),其中每个文件都是一个save函数保存的npy文件,文件名对应于数组名。
load函数自动识别npz文件,并且返回一个类似于字典的对象,可以通过数组名作为关键字获取数组的内容:
> a = np.array([[1,2,3],[4,5,6]])
> b = np.arange(0, 1.0, 0.1)
> c = np.sin(b)
> np.savez("result.npz", a, b, sin_array = c)
> r = np.load("result.npz")
> r["arr_0"] # 数组a
array([[1, 2, 3],
[4, 5, 6]])
> r["arr_1"] # 数组b
array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
> r["sin_array"] # 数组c
array([ 0. , 0.09983342, 0.19866933, 0.29552021, 0.38941834,
0.47942554, 0.56464247, 0.64421769, 0.71735609, 0.78332691])
1.6.3. numpy.savetxt和numpy.loadtxt
使用numpy.savetxt和numpy.loadtxt可以读写1维和2维的数组。
请看下面的例子:
> a = np.arange(0,12,0.5).reshape(4,-1)
> np.savetxt("a.txt", a) # 缺省按照'%.18e'格式保存数据,以空格分隔
> np.loadtxt("a.txt")
array([[ 0. , 0.5, 1. , 1.5, 2. , 2.5],
[ 3. , 3.5, 4. , 4.5, 5. , 5.5],
[ 6. , 6.5, 7. , 7.5, 8. , 8.5],
[ 9. , 9.5, 10. , 10.5, 11. , 11.5]])
> np.savetxt("a.txt", a, fmt="%d", delimiter=",") #改为保存为整数,以逗号分隔
> np.loadtxt("a.txt",delimiter=",") # 读入的时候也需要指定逗号分隔
array([[ 0., 0., 1., 1., 2., 2.],
[ 3., 3., 4., 4., 5., 5.],
[ 6., 6., 7., 7., 8., 8.],
[ 9., 9., 10., 10., 11., 11.]])
1.6.4. 使用文件对象
我们也使用文件对象读写数组数据。
例如对于load和save函数来说,如果使用文件对象的话,可以将多个数组储存到一个npy文件中。
请看下面的例子:
> a = np.arange(8)
> b = np.add.accumulate(a)
> c = a + b
> f = file("result.npy", "wb")
> np.save(f, a) # 顺序将a,b,c保存进文件对象f
> np.save(f, b)
> np.save(f, c)
> f.close()
> f = file("result.npy", "rb")
> np.load(f) # 顺序从文件对象f中读取内容
array([0, 1, 2, 3, 4, 5, 6, 7])
> np.load(f)
array([ 0, 1, 3, 6, 10, 15, 21, 28])
> np.load(f)
array([ 0, 2, 5, 9, 14, 20, 27, 35])