1.2 NumPy数组的操作
1.2.1 数组的数据类型操作
作为一个强大的科学计算库,NumPy支持的数据类型远不止Python原生的几种数据类型。表1-1所示为NumPy支持的数据类型。
表1-1 NumPy支持的数据类型
续表
1.创建数组时指定数组的数据类型
表1-1中的数据类型可以通过np.bool_、np.float16等形式调用,创建数组时可以指定数据类型。
>>> a = np.array([0, 1, 0, 10], dtype=np.bool_) >>> a array([False, True, False, True]) #将0值转换为False,非0值转换为True
这里要分清使用的是Python的数据类型,还是NumPy的数据类型。例如,int是Python的数据类型,可以使用dtype=int;而int_是NumPy的数据类型,必须使用np.int_。
NumPy中后缀带下画线“_”的数据类型指向的是Python原生的数据类型,也就是说,np.bool_与Python中的原生bool数据类型等效,np.float_与Python中的原生float类型等效。
2.查看NumPy数组的数据类型
我们可以通过NumPy数组自带的dtype属性来查看数组的数据类型。
>>> a.dtype #查看数组a的数据类型。注意,dtype后面没有括号 dtype('bool') >>>type(a) #查看变量的数据类型 NumPy.ndarray
为什么输出的类型是bool而不是bool_呢?这是因为显示了原生的数据类型。查看数组的数据类型是NumPy对象的一种方法;查看变量的数据类型是Python的一个函数。
3.修改已有数组的数据类型
一个数组已经被创建,但是想要改变其数据类型,那就可以使用.astype()方法。
>>> a.astype(np.int) #数组a接上例 array([0, 1, 0, 1]) >>>b = np.random.random((2,2)) >>>b array([[0.02914317, 0.645534 ], [0.61839509, 0.64155607]]) >>>b.dtype dtype('float64') >>>b.astype(np.float16) #返回16位浮点数,但不改变原数组的数据类型 array([[0.02914, 0.6455 ], [0.618 , 0.6416 ]], dtype=float16) >>>b.dtype dtype('float64') #仍是64位浮点数
将64位浮点数改为16位浮点数,精度会变低,显示的位数会变少。但此时查看原数组仍是64位浮点数。要完全改变原数组的数据类型,需将返回值赋给原数组变量。
1.2.2 数组的形状及其相关操作
在NumPy中,数组用于存储多维数据,所以数组的形状指的是数据的维度大小,以及每一维度元素的个数。与数组形状相关的概念有维度(轴)和形状(秩)。
与数组形状相关的方法如下。
(1).ndim:查看维度(轴)。
(2).shape:查看形状(秩)。
(3).size:查看元素个数。
(4).itemsize:查看元素所占的字节。
注意,这些方法均没有括号。
1.查看数组形状
查看数组形状的代码如下。
>>> a = np.array([[2, 3, 4], [5, 6, 7]]) #创建2*3的二维数组 >>> a array([[2, 3, 4], [5, 6, 7]]) >>> a.shape #查看形状属性
输出结果如下。
(2, 3)
可以看到,查看形状属性时返回的是一个元组,元素的长度代表数组的维度。元组的每一个属性代表对应的维度的元素个数,(2,3)就表示第1个维度的元素个数是2(2行),第2个维度的长度是3(3列)。
2.修改数组形状
创建数组后,数组的形状也是可以改变的。使用数组的.reshape()方法可以改变数组的形状。
>>> a = np.ones((2, 12)) >>> a array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]) >>> a.shape (2, 12) >>> a.size #查看数组的长度,即总元素个数 24 >>> b = a.reshape(2, 3, 4) #a.reshape()方法用于返回改变形状的数组,但不改变a的数组形状 >>> b array([[[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]]]) >>> b.shape (2, 3, 4) >>> b = a.reshape((2,3,4)) #元组作为参数,结果相同 >>> b array([[[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]]]) >>> b.shape (2, 3, 4) >>> b.ndim #查看数组的维度(轴)数 3
可以看到,.reshape()方法可以同时传入多个描述形状的数字,也可以传入一个数组。不过,将形状改变为一维数组时,传入的必须是元组。另外需要注意,传入.reshape()方法的多个参数的乘积必须与改变前数组的总长度相等,即2×3×4=24,否则系统会报错。
显然,计算时需要特别小心。因此,NumPy还提供了一个“−1”的表示方式。数组新的shape属性要与原来的匹配,如果等于−1,那么NumPy会根据剩下的维度计算出数组的另外一个shape属性值。
例如下面的代码。
b = a.reshape(2, 3, −1) #指定前两个维度的长度,第三个维度为“−1”则会自动匹配 b.shape (2, 3, 4)
3.将多维数组转换为一维数组的专用方法.flatten()
NumPy数组专门提供了.flatten()方法将一个多维数组转换为一维数组。这个方法在执行数组运算时非常有用。
>>> a = np.ones((2, 3)) >>> b = a.flatten() >>> b array([1., 1., 1., 1., 1., 1.]) >>> b.shape (6,)
1.2.3 数组元素访问:索引与切片
NumPy的数组访问一般通过索引与切片实现,NumPy在这一方面可谓功能非常强大。NumPy数组中所有的位置索引都是从0开始的,我们可以根据位置索引来精确读取数据。
索引与切片的所有实例都以数组a展开。
>>> a = np.arange(36).reshape((4, 9))#a = np.arange(36).reshape((4, -1)) >>> a array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8], [ 9, 10, 11, 12, 13, 14, 15, 16, 17], [18, 19, 20, 21, 22, 23, 24, 25, 26], [27, 28, 29, 30, 31, 32, 33, 34, 35]])
(1)读取一行数据。
>>> a[1] #读取第2行数据 array([ 9, 10, 11, 12, 13, 14, 15, 16, 17])
(2)读取连续多行数据。
>>> a[:2] #读取前2行数据 array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ], [ 9, 10, 11, 12, 13, 14, 15, 16, 17]]) >>> a[1:] #读取第2行后面的所有行数据 array([[ 9, 10, 11, 12, 13, 14, 15, 16, 17], [18, 19, 20, 21, 22, 23, 24, 25, 26], [27, 28, 29, 30, 31, 32, 33, 34, 35]])
也可以加上步长。
>>> a[::2] #每隔一行读取一次数据 array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8], [18, 19, 20, 21, 22, 23, 24, 25, 26]])
(3)读取不连续多行数据。
>>> a[[0,-1]] #读取第一行和最后一行数据 array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8], [27, 28, 29, 30, 31, 32, 33, 34, 35]])
可以看到,根据索引对NumPy取值的方法与Python中使用列表索引取值的方法类似,都是在方括号中传入位置进行索引取值。对不连续多行进行索引时,每一位数据之间用逗号隔开行位置形成列表取行数据;对连续行进行索引时,使用冒号隔开开始和结束行位置。实际取行时,结束行标号要减一。连续行的开始行和结束行标号可省略,如a[::]。省略时表示从数组的开始位置到结尾位置获取全部内容。下面是取列数据的操作,与取行数据相似。
(4)读取一列数据。
>>> a[:,1] #读取第2列数据 array([ 1, 10, 19, 28])
(5)读取连续多列数据。
>>> a[:,1:3] #读取第2列到第3列数据 array([[ 1, 2], [10, 11], [19, 20], [28, 29]])
(6)读取不连续多列数据。
>>> a[:,[0,3]] #读取第1列和第4列数据 array([[ 0, 3], [ 9, 12], [18, 21], [27, 30]]))
(7)读取连续多行多列数据。
>>> a[1:3:,1:3] #读取第2、3行中的第2、3列数据 array([[10, 11], [19, 20]])
(8)读取多个不连续位置的数据。
通过上面的讲解,你应该明白读取行、读取列的规律了,那么如果读取不连续的多行多列呢?例如读取第1、3行与第2、4列,你可能认为是a[[0, 2], [1, 3]],我们来看看以下代码。
>>> a[[0, 2], [1, 3]] array([ 1, 21])
由结果可知,返回的并不是预期的数据,而是第1行第2列、第3行第4列的数据,也就是(0,1)和(2,3)位置的数据。读取第1、3行与第2、4列数据方法如下。
>>> a[[0,0,2,2],[1,3,1,3]] array([ 1, 3, 19, 21])
(9)读取单个数据。
>>> b = a[3,3] >>> b 30 >>> type(b) #取单个类型时,返回的就是一个确切的NumPy类型数值 <class 'NumPy.int64'>
1.2.4 数组运算
1.算术运算
NumPy可以进行加减乘除、求n次方和取余数等运算。
参与数组运算的可以是两个ndarray数组,也可以是数组与单个数值。如果两个均为数组,需要注意的是数组必须具有相同的形状或符合数组广播规则。
NumPy的广播规则如下。
● 如果两个数组的维度不相同,那么小维度数组的形状会在最左边补1。
● 如果两个数组的形状在任何一个维度上都不匹配,那么数组的形状会沿着维度为1扩展,以匹配另外一个数组的形状。
● 如果两个数组的形状在任何一个维度上都不匹配并且没有任何一个维度为1,那么会引起异常。
NumPy数组主要的算术运算如下。
print(np.add(x1,x2)) #与x1+x2等价 print(np.subtract(x1,x2)) #与x1-x2等价 print(np.multiply(x1,x2)) #与x1*x2等价 print(np.divide(x1,x2)) #与x1/x2等价 print(np.power(x1,x2)) #x1的x2次方 print(np.remainder(x1,x2)) #取余数也可以用np.mod(x1,x2)
【动动手练习1-4】 数组算术运算
(1)相同形状数组的运算。
>>> x1=np.array([[1,2,3],[5,6,7],[9,8,7]]) >>>x1.shape (3, 3) >>> x2=np.ones([3,3]) >>> x2.shape (3, 3) >>> x1+x2 #与np.add(x1,x2)等效 array([[ 2., 3., 4.], [ 6., 7., 8.], [10., 9., 8.]]) #其他运算(减、乘、除)请读者自行实验 >>> np.mod(((x1+x2)*x1/3).astype(np.int8),x1) #取余操作 array([[0, 0, 1], [0, 2, 4], [3, 0, 4]], dtype=int32) >>>x2*=3 >>>x2 array([[3., 3., 3.], [3., 3., 3.], [3., 3., 3.]]) >>> np.power(x1,x2) #乘方操作 array([[ 1., 8., 27.], [125., 216., 343.], [729., 512., 343.]])
取余操作时,需要经过多重算术运算再进行取余操作。
(2)不同形状数组的运算,必须符合广播规则。
>>> x3=np.full((2,3),4) >>> x3 array([[4, 4, 4], [4, 4, 4]]) >>> x3.shape (2, 3) >>> x1+x3 ValueError: operands could not be broadcast together with shapes (3,3) (2,3)
系统提示值错误:操作数不能与形状(3,3)(2,3)一起广播。
下面看一下一维数组能否和已有的多维数组进行运算。
>>> x4=np.arange(8) >>> x4.shape (8,) >>>x1+x4 ValueError: operands could not be broadcast together with shapes (3,3) (8,)
一维数组形状值大于二维数组的形状值时,会返回错误。数组广播规则已经明确,只有一维数组的形状值不大于二维数组的形状值时才可以广播匹配。
下面验证一维数组形状值不大于二维数组的形状值时,数组运算的情况。
>>> x4=np.arange(3) #一维数组形状值等于二维数组的形状值 >>> x4.shape (3,) >>> x1+x4 array([[1, 3, 5], [5, 7, 9], [9, 9, 9]]) >>> x3+x4 array([[4, 5, 6], [4, 5, 6]]) >>> x4=np.arange(1) #一维数组形状值小于二维数组的形状值 >>> x1+x4 array([[1, 2, 3], [5, 6, 7], [9, 8, 7]])
2.数组逻辑判断表达式的连接操作
在数据分析过程中,我们常常会遇到需要将序列中的数值元素进行对比或加以条件判断的情况,在Python中可以运用NumPy数组的布尔逻辑来解决这些问题。NumPy中的逻辑运算包括逻辑判断表达式与连接操作符两个方面。
(1)逻辑判断表达式:由等于(==)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)、不等于(!=)等逻辑运算符实现。这些逻辑运算符都是对两个相同形状的数组进行相应位置元素的比较判断,或是一个数组对一个值进行比较判断,结果是一个逻辑值的列表。而对整个数组进行比较得到一个逻辑值的判断则需要借助后面介绍的all和any函数完成。
(2)连接操作符:与(&)、或(|)。连接操作符可以连接逻辑判断表达式。这里与后面介绍的逻辑运算函数logical_and()和logical_or()的作用一致,用于比较试验。
【动动手练习1-5】 数组逻辑运算
>>> import NumPy as np >>> x_obj = np.random.rand(10) #包含10个随机数的数组 >>> x_obj array([0.27891809, 0.8573368 , 0.78180964, 0.89926442, 0.44110754, 0.69994068, 0.84545436, 0.31694934, 0.7900553 , 0.58884895]) >>> x_obj > 0.5 #比较数组中每个元素值是否大于0.5 array([False, True, True, True, False, True, True, False, True, True]) >>>x_obj[x_obj> 0.5] #获取数组中大于0.5的元素 array([0.8573368 , 0.78180964, 0.89926442, 0.69994068, 0.84545436, 0.7900553 , 0.58884895]) >>> sum((x_obj>0.4) & (x_obj<0.6)) #值大于0.4且小于0.6的元素数量,True表示1,False表示0 2 >>> a = np.array([1, 2, 3]) >>> b = np.array([3, 2, 1]) >>> a > b #两个数组中对应位置上元素的比较 array([False, False, True]) >>> a[a>b] #数组a中大于数组b对应位置上元素的值 array([3]) >>> a == b array([False, True, False]) >>> a[a==b] array([2]) >>> x_obj = np.arange(1, 10) >>> x_obj array([1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> x_obj[(x%2==0)&(x>5)] #大于5的偶数,两个数组进行布尔“与”运算;判断表达式加括号 array([6, 8]) >>> x_obj[(x%2==0)|(x>5)] #大于5的元素或者偶数元素,布尔“或”运算;判断表达式加括号 array([2, 4, 6, 7, 8, 9])
以上任何通过逻辑运算能够访问的数组,自然也可以被赋值。
>>> x_obj[(x_obj %2==0)|( x_obj >5)] =0 >>> x_obj array([1, 0, 3, 0, 5, 0, 0, 0, 0])