NumPy——快速处理数据
标准安装的Python中用列表(list)保存一组值,可以用来当作数组使用,不过由于列表的元素可以是任何对象,因此列表中所保存的是对象的指针。这样为了保存一个简单的[1,2,3],需要有3个指针和三个整数对象。对于数值运算来说这种结构显然比较浪费内存和CPU计算时间。
此外Python还提供了一个array模块,array对象和列表不同,它直接保存数值,和C语言的一维数组比较类似。但是由于它不支持多维,也没有各种运算函数,因此也不适合做数值运算。
NumPy的诞生弥补了这些不足,NumPy提供了两种基本的对象:ndarray(N-dimensional array object)和 ufunc(universal function object)。ndarray(下文统一称之为数组)是存储单一数据类型的多维数组,而ufunc则是能够对数组进行处理的函数。
一、ndarray对象
函数库的导入
推荐以下方式导入NumPy函数库:
import numpy as np
1. 一维数组
1.1 通过Python序列创建一维数组
我们可以通过给array函数传递Python的序列对象创建一维数组。
array函数创建一维数组实例:
a = np.array([1, 2, 3, 4])
- shape属性的使用:
数组的大小可以通过其shape属性获得,数组c的shape有两个元素,因此它是二维数组,其中第0轴的长度为3,第1轴的长度为4。还可以通过修改数组的shape属性,在保持数组元素个数不变的情况下,改变数组每个轴的长度。下面的例子将数组c的shape改为(4,3),注意从(3,4)改为(4,3)并不是对数组进行转置,而只是改变每个轴的大小,数组元素在内存中的位置并没有改变。
shape属性使用 实例1:
c.shape = 4,3
当某个轴的元素为-1时,将根据数组元素的个数自动计算此轴的长度,因此下面的程序将数组c的shape改为了(2,6)。
shape属性使用 实例2:
c.shape = 2,-1
- reshape方法的使用:
使用数组的reshape方法,可以创建一个改变了尺寸的新数组,原数组的shape保持不变。
reshape方法使用实例:
我对数组“a”进行尺寸改变得到一个新的数组d,再对比a和d。
d = a.reshape((2,2))
1.2 通过NumPy函数创建一维数组
上面的例子都是先创建一个Python序列,然后通过array函数将其转换为数组,这样做显然效率不高。因此NumPy提供了很多专门用来创建数组的函数。
- arange函数:
arange函数类似于python的range函数,通过指定开始值、终值和步长来创建一维数组,注意数组不包括终值。
arange函数使用实例:
np.arange(0,1,0.1) #arrange(始值,终值,步长)
- linspace函数:
linspace函数通过指定开始值、终值和元素个数来创建一维数组,可以通过endpoint关键字指定是否包括终值,缺省设置是包括终值。
linspace函数使用实例:
np.linspace(0, 1, 12) # linspace(始值,终值,元素个数)
- logspace函数:
logspace函数和linspace类似,不过它创建等比数列,下面的例子产生1(10^0)到100(10^2)、
有20个元素的等比数列。
2. 多维数组
多维数组的存取和一维数组类似,因为多维数组有多个轴,因此它的下标需要用多个值来表示,NumPy采用组元(tuple)作为数组的下标。组元不需要圆括号,虽然我们经常在Python中用圆括号将组元括起来,但是实际上组元的语法定义只需要用逗号隔开即可。
2.1 通过给array函数传递Python多层嵌套的序列创建多维数组
array函数创建二维数组实例:
c = np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]])
2.2 通过给NumPy函数中的arange函数创建多维数组
arange函数创建多维数组实例:
np.arange(0, 60, 10).reshape(-1, 1) + np.arange(0, 6)
3. 结构数组
在C语言中我们可以通过struct关键字定义结构类型,结构中的字段占据连续的内存空间,每个结构体占用的内存大小都相同,因此可以很容易地定义结构组。和C语言一样,在NumPy中也很容易对这种结构数组进行操作。只要NumPy中的结构定义和C语言中的定义相同,NumPy就可以很方便地读取C语言的结构数组的二进制数据,转换为NumPy的结构数组。
结构数组创建实例:
假设我们需要定义一个结构数组,它的每个元素都有name, age和weight字段。
persontype = np.dtype({
'names':['name', 'age', 'weight'],
'formats':['S32','i', 'f']})
a = np.array([("Zhang",32,75.5),("Wang",24,65.2)],dtype=persontype)
我们先创建一个dtype对象persontype,通过其字典参数描述结构类型的各个字段。字典有两个关键字:names,formats。每个关键字对应的值都是一个列表。names定义结构中的每个字段名,而formats则定义每个字段的类型:
- S32 : 32个字节的字符串类型,由于结构中的每个元素的大小必须固定,因此需要指定字符串的长度
- i : 32bit的整数类型,相当于np.int32
- f : 32bit的单精度浮点数类型,相当于np.float32
然后我们调用array函数创建数组,通过关键字参数 dtype=persontype, 指定所创建的数组的元素类型为结构persontype。
4. 存取元素
4.1 数组元素的存取方法和Python的标准方法相同
数组元素存取实例:
a[5] # 用整数作为下标可以获取数组中的某个元素
a[3:5] # 用范围作为下标获取数组的一个切片,包括a[3]不包括a[5]
a[:5] # 省略开始下标,表示从a[0]开始
a[:-1] # 下标可以使用负数,表示从数组后往前数
a[2:4] = 100,101 # 下标还可以用来修改元素的值,a[2]和a[3]的值被修改为100,101
a[1:-1:2] # 范围中的第三个参数表示步长,2表示隔一个元素取一个元素
a[::-1] # 省略范围的开始下标和结束下标,步长为-1,整个数组头尾颠倒
a[5:1:-2] # 步长为负数时,开始下标必须大于结束下标
4.2 除了使用下标范围存取元素之外,NumPy还提供了两种存取元素的高级方法:使用整数序列、使用布尔数组
- 使用整数序列:
当使用整数序列对数组元素进行存取时,将使用整数序列中的每个元素作为下标,整数序列可以是列表或者数组。使用整数序列作为下标获得的数组不和原始数组共享数据空间。
使用整数序列进行数组元素存取实例:
x = np.arange(10,1,-1)
x[[3, 3, 1, 8]] # 获取x中的下标为3, 3, 1, 8的4个元素,组成一个新的数组b
b = x[np.array([3,3,1,8])]
x[[3,5,1]] = -1, -2, -3 # 整数序列下标也可以用来修改元素的值
- 使用布尔数组:
当使用布尔数组b作为下标存取数组x中的元素时,将收集数组x中所有在数组b中对应下标为True的元素。使用布尔数组作为下标获得的数组不和原始数组共享数据空间。
注:这种方式只对应于布尔数组,不能使用布尔列表。
使用布尔数组进行数组元素存取实例:
x = np.arange(5,0,-1)
x[np.array([True, False, True, False, False])]
# 布尔数组中下标为0,2的元素为True,因此获取x中下标为0,2的元素
x[[True, False, True, False, False]]
# 如果是布尔列表,则把True当作1, False当作0,按照整数序列方式获取x中的元素
x[np.array([True, False, True, True])]
# 布尔数组的长度不够时,不够的部分都当作False
x[np.array([True, False, True, True])] = -1, -2, -3
# 布尔数组下标也可以用来修改元素
5. 内存结构
如下图所示,是ndarray数组对象在内存中的储存方式,数组的描述信息保存在一个数据结构中,这个结构引用两个对象:一块用于保存数据的存储区域和一个用于描述元素类型的dtype对象。
数据存储区域保存着数组中所有元素的二进制数据,dtype对象则知道如何将元素的二进制数据转换为可用的值。数组的维数、大小等信息都保存在ndarray数组对象的数据结构中。
二、ufunc运算
ufunc是universal function的缩写,它是一种能对数组的每个元素进行操作的函数。NumPy内置的许多ufunc函数都是在C语言级别实现的,因此它们的计算速度非常快。
由于Python的操作符重载功能,计算两个数组相加可以简单地写为a+b,而np.add(a,b,a)则可以用a+=b来表示。下面是数组的运算符和其对应的ufunc函数的一个列表,注意除号”/”的意义根据是否激活future.division有所不同。
数组对象所支持的操作符 | |
---|---|
y = x1 + x2 | add(x1, x2 [, y]) |
y = x1 - x2 | subtract(x1, x2 [, y]) |
y = x1 * x2 | multiply (x1, x2 [, y]) |
y = x1 / x2 | divide (x1, x2 [, y]), 如果两个数组的元素为整数,那么用整数除法 |
y = x1 / x2 | true divide (x1, x2 [, y]), 总是返回精确的商 |
y = x1 // x2 | floor divide (x1, x2 [, y]), 总是对返回值取整 |
y = -x | negative(x [,y]) |
y = x1**x2 | power(x1, x2 [, y]) |
y = x1 % x2 | remainder(x1, x2 [, y]), mod(x1, x2, [, y]) |
1. 广播
当我们使用ufunc函数对两个数组进行计算时,ufunc函数会对这两个数组的对应元素进行计算,因此它要求这两个数组有相同的大小(shape相同)。如果两个数组的shape不同的话,会进行如下的广播(broadcasting)处理:
a. 让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分都通过在前面加1补齐
b. 输出数组的shape是输入数组shape的各个轴上的最大值
c. 如果输入数组的某个轴和输出数组的对应轴的长度相同或者其长度为1时,这个数组能够用来计算,否则出错
d. 当输入数组的某个轴的长度为1时,沿着此轴运算时都用此轴上的第一组值
2. ufunc的方法
ufunc函数本身还有些方法,这些方法只对两个输入一个输出的ufunc函数有效,其它的ufunc对象调用这些方法时会抛出ValueError异常。
- reduce方法:
reduce方法和Python的reduce函数类似,它沿着axis轴对array进行操作,相当于将运算符插入到沿axis轴的所有子数组或者元素当中。
<op>.reduce (array=, axis=0, dtype=None)
reduce方法使用实例:
np.add.reduce([1,2,3]) # 1 + 2 + 3
np.add.reduce([[1,2,3],[4,5,6]], axis=1) # 1,4 + 2,5 + 3,6
- accumulate方法:
accumulate方法和reduce方法类似,只是它返回的数组和输入的数组的shape相同,保存所有的中间。
accumulate方法使用实例:
np.add.accumulate([1,2,3])
np.add.accumulate([[1,2,3],[4,5,6]], axis=1)
3. 矩阵运算
NumPy和Matlab不一样,对于多维数组的运算,缺省情况下并不使用矩阵运算,如果你希望对数组进行矩阵运算的话,可以调用相应的函数。
- matrix对象:
numpy库提供了matrix类,使用matrix类创建的是矩阵对象,它们的加减乘除运算缺省采用矩阵方式计算,因此用法和matlab十分类似。但是由于NumPy中同时存在ndarray和matrix对象,因此用户很容易将两者弄混。这有违Python的“显式优于隐式”的原则,因此并不推荐在较复杂的程序中使用matrix。
matrix对象使用实例:
a = np.matrix([[1,2,3],[5,5,6],[7,9,9]])
a*a**-1
因为a是用matrix创建的矩阵对象,因此乘法和幂运算符都变成了矩阵运算,于是上面计算的是矩阵a和其逆矩阵的乘积,结果是一个单位矩阵。
4. 文件存取
NumPy提供了多种文件操作函数方便我们存取数组内容。文件存取的格式分为两类:二进制和文本。而二进制格式的文件又分为NumPy专用的格式化二进制类型和无格式类型。
- tofile函数:
使用数组的方法函数tofile可以方便地将数组中数据以二进制的格式写进文件。tofile输出的数据没有格式,因此用numpy.fromfile读回来的时候需要自己格式化数据。
tofile函数使用实例:
a = np.arange(0,12)
a.shape = 3,4
a.tofile("a.bin")
b = np.fromfile("a.bin", dtype=np.float) # 按照float类型读入的数据是错误的
a.dtype # 查看a的dtype
b = np.fromfile("a.bin", dtype=np.int32) # 按照int32类型读入的数据就是正确的
b.shape = 3, 4 # 按照a的shape修改b的shape
从上面的例子可以看出,需要在读入的时候设置正确的dtype和shape才能保证数据一致。并且tofile函数不管数组的排列顺序是C语言格式的还是Fortran语言格式的,统一使用C语言格式输出。此外如果fromfile和tofile函数调用时指定了sep关键字参数的话,数组将以文本格式输入输出。
- numpy.load和numpy.save函数:
numpy.load和numpy.save函数以NumPy专用的二进制类型保存数据,这两个函数会自动处理元素类型和shape等信息,使用它们读写数组就方便多了,但是numpy.save输出的文件很难和其它语言编写的程序读入。
numpy.load和numpy.save函数使用实例:
a = np.arange(0,12)
a.shape = 3,4
np.save("a.npy", a) #save(文件名,数组)
c = np.load( "a.npy" ) #load(文件名)