[Python与数学建模-数据处理与可视化]-1数值计算工具NumPy

  虽然列表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所列。


图片.png

图片.png

  程序文件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所列。


图片.png

  例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所示的六种。


图片.png

  运用比较运算符返回的是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所列。


图片.png

图片.png

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=, 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文件,并且返回一个类似于字典的对象,可以通过数组名作为键获取数组的内容。

你可能感兴趣的:([Python与数学建模-数据处理与可视化]-1数值计算工具NumPy)