虽然列表list可以完成数组操作,但不是真正意义上的数组,当数据量很大时,其速度很慢,故提供了NumPy扩展库完成数组操作。很多高级扩展库也依赖于它,比如Scipy、Pandas和Matplotlib等。
NumPy提供了两种基本的对象:ndarray(N-dimensional Array Object)和ufunc(Universal Function Object)。ndarray(称为array数组,下文统一称为数组)是存储单一数据类型的多维数组,而ufunc则是能够对数组进行处理的通用函数。
1数组的创建、属性和操作
1.1数组的创建
通过NumPy库的array函数实现数组的创建,如果向array函数中传入了一个列表或元组,将构造简单的一维数组;如果传入多个嵌套的列表或元组,则可以构造一个二维数组。构成数组的元素都具有相同的数据类型。下面分别构造一维数组和二维数组。
例2.1 利用array函数创建数组示例。
程序文件Pex2_1.py
import numpy as np #导入模块并命名为np
a = np.array([2,4,8,20,16,30]) #单个列表创建一维数组
#嵌套元组创建二维数组
b = np.array(((1,2,3,4,5),(6,7,8,9,10),
(10,9,1,2,3),(4,5,6,8,9.0)))
print("一维数组:",a)
print("二维数组:\n",b)
执行结果:
一维数组:
[ 2 4 8 20 16 30]
二维数组:
[[ 1. 2. 3. 4. 5.]
[ 6. 7. 8. 9. 10.]
[10. 9. 1. 2. 3.]
[ 4. 5. 6. 8. 9.]]
如上结果所示,可以将列表或元组转换为一个数组。在第二个数组b中,输入的元素含有整型和浮点型两种数据类型,但输出的数组元素都转化为相同的浮点型。
例2.2 利用arange、linspace、empty等函数生成数组示例。
程序文件Pex2_2.py
import numpy as np
a=np.arange(4,dtype=float) #创建浮点型数组:[0., 1.,2., 3.]
b=np.arange(0,10,2,dtype=int) #创建整型数组:[0, 2, 4, 6, 8]
c=np.empty((2,3),int) #创建2×3的整型空矩阵
d=np.linspace(-1,2,5) #创建数组:[-1., -0.25, 0.5, 1.25, 2.]
e=np.random.randint(0,3,(2,3)) #生成[0,3)上的2行3列的随机整数数组
注2.1
(1)上面程序运行后,没有输出,如果想看输出结果,读者可以自己加上print语句;我们以后的程序设计中,对于一些不重要的中间结果,也不输出了。或者使用Anaconda运行,在Spyder的控制台下可以直接看到输出结果。
(2)empty函数只分配数组所使用的内存,不对数组元素值进行初始化操作,因此它的运行速度是最快的,上述程序中c=np.empty((2,3),int)的返回值是随机的,每次运行都是不一样的。
例2.3 使用虚数单位“j”生成数组。
程序文件Pex2_3.py
import numpy as np
a=np.linspace(0,2,5) #生成数组:[0., 0.5, 1., 1.5, 2.]
b=np.mgrid[0:2:5j] #等价于np.linspace(0,2,5)
x,y=np.mgrid[0:2:4j,10:20:5j] #生成[0,2]×[10,20]上的4×5的二维数组
print("x={}\ny={}".format(x,y))
1.2数组的属性
为了更好地理解和使用数组,了解数组的基本属性是十分必要的。数组的属性及其说明如表2.1所列。
程序文件Pex2_4.py
import numpy as np
a=np.random.randint(1,11,(3,5)) #生成[1,10]区间上3行5列的随机整数数组
print("维数:",a.ndim); #维数:2
print("维度:",a.shape) #维度:(3,5)
print("元素总数:",a.size); #元素总数:15
print("类型:",a.dtype) #类型:int32
print("每个元素字节数:",a.itemsize) #字节数:4
例2.5 生成数学上一维向量的三种模式。
程序文件Pex2_5.py
import numpy as np
a=np.array([1,2,3])
print("维度为:",a.shape) #维度为:(3,)
b=np.array([[1,2,3]])
print("维度为:",b.shape) #维度为:(1,3)
c=np.array([[1],[2],[3]])
print("维度为:",c.shape) #维度为:(3,1)
1.3数组元素的索引
NumPy中的array数组与Python基础数据结构列表(list)的区别是:列表中的元素可以是不同的数据类型,而array数组只允许存储相同的数据类型。
①对于一维数组来说,Python原生的列表和NumPy的数组的切片操作都是相同的,无非是记住一个规则:列表名(或数组名)[start: end: step],但不包括索引end对应的值。
②二维数据列表元素的引用方式为a[i][j];array数组元素的引用方式为a[i,j]。
NumPy比一般的Python 序列提供更多的索引方式。除了用整数和切片的一般索引外,数组还可以布尔索引及花式索引。
(1)一般索引
例2.6 数组索
引示例。
程序文件Pex2_6.py
import numpy as np
a = np.array([2,4,8,20,16,30])
b = np.array(((1,2,3,4,5),(6,7,8,9,10),
(10,9,1,2,3),(4,5,6,8,9.0)))
print(a[[2,3,5]]) #一维数组索引,输出:[ 8 20 30]
print(a[[-1,-2,-3]]) #一维数组索引,输出:[30 16 20]
print(b[1,2]) #输出第2行第3列元素:8.0
print(b[2]) #输出第3行元素:[10. 9. 1. 2. 3.]
print(b[2,:]) #输出第3行元素:[10. 9. 1. 2. 3.]
print(b[:,1]) #输出第2列所有元素:[2. 7. 9. 5.]
print(b[[2,3],1:4]) #输出第3、4行,第2、3、4列的元素
print(b[1:3,1:3]) #输出第2、3行,第2、3列的元素
如上结果所示,在一维数组的索引中,可以将任意位置的索引组装为列表,用作对应元素的获取;在二维数组中,位置索引必须写成[rows,cols]的形式,方括号的前半部分用于控制二维数组的行索引,后半部分用于控制数组的列索引。如果需要获取所有的行或列元素,那么,对应的行索引或列索引需要用英文状态的冒号表示。
(2)布尔索引
例2.7 布尔索引示例。
程序文件Pex2_7.py
from numpy import array, nan, isnan
a=array([[1, nan, 2], [4, nan, 3]])
b=a[~isnan(a)] #提取a中非nan的数
print("b=",b)
print("b中大于2的元素有:", b[b>2])
运行结果:
b= [1. 2. 4. 3.]
b中大于2的元素有: [4. 3.]
(3)花式索引
花式索引的索引值是一个数组。对于使用一维整型数组作为索引,如果被索引数据是一维数组,那么索引的结果就是对应位置的元素;如果被索引数据是二维数组,那么就是对应下标的行。
对于二维被索引数据来说,索引值可以是二维数据,当索引值为两个维度相同的一维数组组成的二维数组时,以两个维度作为横纵坐标索引出单值后组合成新的一维数组。
例2.8 花式索引示例。
程序文件Pex2_8.py
from numpy import array
x = array([[1,2],[3,4],[5,6]])
print("前两行元素为:\n", x[[0,1]]) #输出:[[1,2],[3,4]]
print("x[0][0]和x[1][1]为:", x[[0,1],[0,1]]) #输出:[1 4]
print("以下两种格式是一样的:")
print(x[[0,1]][:,[0,1]]) # 输出:[[1,2],[3,4]],
print(x[0:2,0:2]) #同上,输出第1、2行,第1、2列的元素
1.4数组的修改
这里数组的修改是指数组元素的修改,和数组维数的扩大或缩小。
例2.9 数组修改示例。
程序文件Pex2_9.py
import numpy as np
x = np.array([[1,2],[3,4],[5,6]])
x[2,0] = -1 #修改第3行、第1列元素为-1
y=np.delete(x,2,axis=0) #删除数组的第3行
z=np.delete(y,0, axis=1) #删除数组的第1列
t1=np.append(x,[[7,8]],axis=0) #增加一行
t2=np.append(x,[[9],[10],[11]],axis=1) #增加一列
1.5数组的变形
在对数组进行操作时,经常要改变数组的维度。在NumPy中,常用reshape函数改变数据的形状,也就是改变数组的维度。其参数为一个正整数元组,分别指定数组在每个维度上的大小。reshape函数在改变原始数据的形状的同时不改变原始数据的值。如果指定的维度和数组的元素数目不吻合,则函数将抛出异常。
数组变形和转换的一些函数(方法也统称函数)如表2.2所列。
例2.10 reshape和resize变形示例。
程序文件Pex2_10.py
import numpy as np
a=np.arange(4).reshape(2,2) #生成数组[[0,1],[2,3]]
b=np.arange(4).reshape(2,2) #生成数组[[0,1],[2,3]]
print(a.reshape(4,),'\n',a) #输出:[0 1 2 3]和[[0,1],[2,3]]
print(b.resize(4,),'\n',b) #输出:None和[0 1 2 3]
如上结果所示,虽然reshape和resize都是用来改变数组形状的,但是reshape只是返回改变形状后的视图,数组本身是不变的;而resize没有返回,直接改变数组本身的形状。
如果需要将多维数组降为一维数组,利用ravel、flatten和reshape三种方法均可以实现。
例2.11 数组降维示例。
程序文件Pex2_11.py
import numpy as np
a=np.arange(4).reshape(2,2) #生成数组[[0,1],[2,3]]
b=np.arange(4).reshape(2,2) #生成数组[[0,1],[2,3]]
c=np.arange(4).reshape(2,2) #生成数组[[0,1],[2,3]]
print(a.reshape(-1),'\n',a) #输出:[0 1 2 3]和[[0,1],[2,3]]
print(b.ravel(),'\n',b) #输出:[0 1 2 3]和[[0,1],[2,3]]
print(c.flatten(),'\n',c) #输出:[0 1 2 3]和[[0,1],[2,3]]
从显示效果看,三种方法是一样的,原数组都没有修改。但我们在平时使用时,flatten()比较合适,在使用过程中flatten()分配了新的内存;ravel()返回的是一个数组的视图,e=b.ravel()是允许的。
例2.12 数组组合效果示例。
程序文件Pex2_12.py
import numpy as np
a=np.arange(4).reshape(2,2) #生成数组[[0,1],[2,3]]
b=np.arange(4,8).reshape(2,2) #生成数组[[4,5],[6,7]]
c1=np.vstack([a,b]) #垂直方向组合
c2=np.r_[a,b] #垂直方向组合
d1=np.hstack([a,b]) #水平方向组合
d2=np.c_[a,b] #水平方向组合
例2.13 数组分割示例。
程序文件Pex2_13.py
import numpy as np
a=np.arange(4).reshape(2,2) #构造2行2列的数组
b=np.hsplit(a,2) #把a平均分成2个列数组
c=np.vsplit(a,2) #把a平均分成2个行数组
print(b[0],'\n',b[1],'\n',c[0],'\n',c[1])
2数组的运算、通用函数和广播运算
2.1四则运算
在NumPy库中,实现四则运算既可以使用运算符号+、-、、/,也可以使用函数add、substract、multiply、divide。需要注意的是,函数只能接受两个对象的运算,如果需要多个对象的运算,就得使用嵌套方法。
另外还有三个数学运算符,分别是余数、整除和幂次,可以使用符号%、//、*,也可以使用函数fmod、modf和power。但是整除的函数应用会稍微复杂一点,需要写成np.modf(a/b)[1]的格式,因为modf可以返回数值的小数部分和整数部分,而整数部分就是要取的整数值。
例2.14 数组简单运算示例。
程序文件Pex2_14.py
import numpy as np
a=np.arange(10,15); b=np.arange(5,10)
c=a+b; d=a*b #对应元素相加和相乘
e1=np.modf(a/b)[0] #对应元素相除的小数部分
e2=np.modf(a/b)[1] #对应元素相除的整数部分
2.2比较运算
数组间的比较运算有表2.3所示的六种。
运用比较运算符返回的是bool类型的值,即True和False。
例2.15 比较运算示例。
程序文件Pex2_15.py
import numpy as np
a=np.array([[3,4,9],[12,15,1]])
b=np.array([[2,6,3],[7,8,12]])
print(a[a>b]) #取出a大于b的所有元素,输出:[ 3 9 12 15]
print(a[a>10]) #取出a大于10的所有元素,输出:[12 15]
print(np.where(a>10,-1,a)) #a中大于10的元素改为-1
print(np.where(a>10,-1,0)) #a中大于10的元素改为-1,否则为0
最后一个print语句输出为:
[[ 0 0 0]
[-1 -1 0]]
通过上述运行结果可以看出,多维数组通过bool索引返回的都是一维数组;np.where返回的数组保持原来的形状。
2.3ufunc函数
3.ufunc函数
ufunc函数全称为通用函数,是一种能够对数组中的逐个元素进行操作的函数。ufunc函数是针对数组进行操作的,并且都以NumPy数组作为输出。使用ufunc函数比使用math库中的函数效率要高很多。目前NumPy支持超过60多种的通用函数。这些函数包括广泛的操作,如四则运算、求模、取绝对值、幂函数、指数函数、三角函数、位运算、比较运算和逻辑运算等。
例2.16 ufunc函数效率示例。
程序文件Pex2_16.py
import numpy as np, time, math
x=[i*0.01 for i in range(1000000)]
start=time.time() # 1970纪元后经过的浮点秒数
for (i,t) in enumerate(x): x[i]=math.sin(t)
print("math.sin:", time.time()-start)
y=np.array([i*0.01 for i in range(1000000)])
start=time.time()
y=np.sin(y)
print("numpy.sin:", time.time()-start)
运行结果:
math.sin: 0.3449997901916504
numpy.sin: 0.010999917984008789
可以发现对数组的操作,numpy函数整体花费时间比math模块函数要少得多。
2.4ufunc函数的广播机制
广播(Broadcasting)是指不同形状的数组之间执行算术运算的方式。当使用ufunc函数进行数组计算时,ufunc函数会对两个数组的对应元素进行计算。进行这种计算的前提是两个数组的维度相容。若两个数组的维度不相容时,则NumPy会实行广播机制。但是数组的广播功能是有规则的,如果不满足这些规则,运算时就会出错。数组的主要广播规则为:
(1)各输入数组的维度可以不相等,但必须确保从右到左的对应维度值相等。
(2)如果对应维度值不相等,就必须保证其中一个为1。
例2.17 广播机制示例。
程序文件Pex2_17.py
import numpy as np
a=np.arange(0, 20, 10).reshape(-1, 1) #变形为1列的数组,行数自动计算
b=np.arange(0, 3)
print(a+b)
运行结果:
[[ 0 1 2]
[10 11 12]]
3NumPy.random模块的随机数生成
虽然在Python内置的random模块中可以生成随机数,但是每次只能随机生成一个随机数,而且随机数的种类也不够丰富。建议使用NumPy.random模块的随机数生成函数,一方面可以生成随机向量,另一方面函数丰富。关于各种常见的随机数生成函数,如表2.4所列。
4文本文件和二进制文件存取
NumPy提供了多种文件操作函数以方便用户存取数组内容。文件存取的格式分为两类:二进制和文本。而二进制格式的文件又分为NumPy专用的格式化二进制类型和无格式类型。
4.1文本文件的存取
4.1.1savetxt()和loadtxt()存取文本文件
savetxt()可以把1维和2维数组保存到文本文件。loadtxt()可以把文本文件中的数据加载到1维和2维数组中。
例2.18 文本文件存取示例。
程序文件Pex2_18.py
import numpy as np
a=np.arange(0,3,0.5).reshape(2,3) #生成2×3的数组
np.savetxt("Pdata2_18_1.txt", a) #缺省按照'%.18e'格式保存数值,以空格分隔
b=np.loadtxt("Pdata2_18_1.txt") #返回浮点型数组
print("b=",b)
np.savetxt("Pdata2_18_2.txt", a, fmt="%d", delimiter=",") #保存为整型数据,以逗号分隔
c=np.loadtxt("Pdata2_18_2.txt",delimiter=",") #读入的时候也需要指定逗号分隔
print("c=",c)
运行结果:
b= [[0. 0.5 1. ]
[1.5 2. 2.5]]
c= [[0. 0. 1.]
[1. 2. 2.]]
例2.19 文本文件Pdata2_19.txt中存放如下格式的数据:
6 2 6 7 4 2 5 9
4 9 5 3 8 5 8 2
5 2 1 9 7 4 3 3
7 6 7 3 9 2 7 1
2 3 9 5 7 2 6 5
5 5 2 2 8 1 4 3
把其中的数据读入到数组a,并提取数组a的前2行、第2列到第4列的元素,构造一个2行3列的数组b。
程序文件Pex2_19.py
import numpy as np
a=np.loadtxt("Pdata2_19.txt") #返回值a为浮点型数据
b=a[0:2,1:4] #获取a的第1,2行,第2,3,4列
print("b=",b)
程序运行结果如下:
b= [[2. 6. 7.]
[9. 5. 3.]]
例2.20 文本文件Pdata2_20.txt中存放如下格式的数据:
姓名,年龄,体重,身高
张三,30,75,165
李四,45,60,179
王五,15,39,120
提取其中的数值数据。
程序文件Pex2_20.py
import numpy as np
a=np.loadtxt("Pdata2_20.txt",dtype=str,delimiter=",")
b=a[1:,1:].astype(float) #提取a矩阵的数值行和数值列,并转换类型
print("b=",b)
运行结果:
b= [[ 30. 75. 165.]
[ 45. 60. 179.]
[ 15. 39. 120.]]
如果需要处理复杂的数据结构,比如处理缺失数据等情况,可以使用genfromtxt。
4.1.2genfromtxt读入文本文件数据
它的调用格式为:
genfromtxt(fname, dtype=<class 'float'>, comments='#', delimiter=None, skip_header=0, skip_footer=0, converters=None, missing_values=None, filling_values=None, usecols=None, names=None, excludelist=None, deletechars=None, replace_space='_', autostrip=False, case_sensitive=True, defaultfmt='f%i', unpack=None, usemask=False, loose=True, invalid_raise=True, max_rows=None, encoding='bytes')
我们介绍其中的一些常用参数:
(1)fname:指定需要读入数据的文件名。
(2)dtype:指定读入数据的数据类型,默认为浮点型,如果原数据集中含有字符型数据,必须指定数据类型为“str”。
(3)comments:指定注释符,默认为“#”,如果原数据的行首有“#”,将忽略这些行的读入。
(4)delimiter:指定数据集的列分隔符。
(5)skip_header:是否跳过数据集的首行,默认不跳过。
(6)skip_footer:是否跳过数据集的脚注,默认不跳过。
(7)converters:将指定列的数据转换成其他数值。
(8)miss_values:指定缺失值的标记,如果原数据集含指定的标记,读入后这样的数据就为缺失值。
(9)filling_values:指定缺失值的填充值。
(10)usecols:指定需要读入的列。
(11)names:为读入数据的列设置列名称
(12)encoding:如果文件中含有中文,有时需要指定字符编码。
例2.21 纯文本文件Pdata2_21.txt中存放如下数据。分别读取其中的前6行前8列数据、第9列的数值数据、最后一行数据。
6 2 6 7 4 2 5 9 60kg
4 9 5 3 8 5 8 2 55kg
5 2 1 9 7 4 3 3 51kg
7 6 7 3 9 2 7 1 43kg
2 3 9 5 7 2 6 5 41kg
5 5 2 2 8 1 4 -999 52kg
35 37 22 32 41 32 43 38
程序文件Pex2_21.py
import numpy as np
#读前6行前8列数据
a=np.genfromtxt("Pdata2_21.txt",max_rows=6, usecols=range(8))
b=np.genfromtxt("Pdata2_21.txt",dtype=str,max_rows=6,usecols=[8]) #读第9列数据
b=[float(v.rstrip('kg')) for (i,v) in enumerate(b)] #删除kg,并转换为浮点型数据
c=np.genfromtxt("Pdata2_21.txt",skip_header=6) #读最后一行数据
print(a,'\n',b,'\n',c)
4.2二进制格式文件存取
4.2.1tofile()和fromfile()存取二进制格式文件
使用数组对象的tofile()方法可以方便地将数组中的数据以二进制格式写进文件,tofile()输出的数据不保存数组形状和元素类型等信息。因此用fromfile()函数读回数据时需要用户指定元素类型,并对数组的形状进行适当的修改。
例2.22 tofile和fromfile存取二进制格式文件示例。
程序文件Pex2_22.py
import numpy as np
a=np.arange(6).reshape(2,3)
a.tofile('Pdata2_22.bin')
b=np.fromfile('Pdata2_22.bin',dtype=int).reshape(2,3)
print(b)
4.2.2load()、save()和savez()存取NumPy专用的二进制格式文件
load()和save()用NumPy专用的二进制格式存取数据,它们会自动处理元素类型和形状等信息。
如果想将多个数组保存到一个文件中,可以使用savez()。savez()的第一个参数是文件名,其后的参数都是需要保存的数组,输出的是一个扩展名为npz的压缩文件。
例2.23 存取NumPy专用的二进制格式文件示例。
程序文件Pex2_23.py
import numpy as np
a=np.arange(6).reshape(2,3)
np.save("Pdata2_23_1.npy",a)
b=np.load("Pdata2_23_1.npy")
c=np.arange(6,12).reshape(2,3)
d=np.sin(c)
np.savez("Pdata2_23_2.npz",c,d)
e=np.load("Pdata2_23_2.npz")
f1=e["arr_0"] #提取第一个数组的数据
f2=e["arr_1"] #提取第二个数组的数据
用解压软件打开“Pdata2_23_2.npz”文件,会发现其中有两个文件:“arr_0.npy”、“arr_1.npy”,其中分别保存着数组c、d的内容。load()自动识别npz文件,并且返回一个类似于字典的对象,可以通过数组名作为键获取数组的内容。