import numpy as np #导入 NumPy 库 np.__version__ #打印numpy版本
Out[1]:
'1.22.2'
2.3 NumPy 数组操作
NumPy数组的常用操作包括形状改变、运算和统计等,这些操作大多可以通过两种方式实现:
- 通过NumPy数组ndarray类定义的方法实现
- 通过NumPy函数对数组的操作实现
两种方式功能相同,主要区别在于通过NumPy函数操作时,函数的第一个参数是要操作的数组。而通过ndarray类的相应方法则没有该参数,因为数组本身对象来调用方法。
2.3.1 修改数组形状
改变形状-reshape方法/函数
在不改变元素数目条件下修改形状,即各维度的形状乘积要等于数组元素总数,调用方式:
-
通过NumPy数组方法调用格式如下:
- numpy.ndarray.reshape(newshape, order='C'),其中ndarray就是创建的数组对象
-
通过NumPy函数调用格式如下:
-
numpy.reshape(arr, newshape, order='C'),其中arr就是创建的数组对象
-
newshape参数:表示各维度shape参数,可以是一个整数或者整数数组,转成一维数组用一个整数传入;转成多维数组时,函数reshape的该参数只能用整数数组或元组、列表的组合方式表示,不能分开传入,而数组的方法reshape则可以分开传入,这点需要注意区分。
-
order参数:'C' -- 按行,'F' -- 按列,'A' -- 原顺序,'k' -- 元素在内存中的出现顺序。
-
In [2]:
arr3_1_1 = np.arange(12) #创建一维数组 arr3_1_1
Out[2]:
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
In [3]:
arr3_1_1.ndim #维度为1
Out[3]:
1
对该数组对象调用reshape方法,将一维数组改为shape形状(3,4),3 * 4=12个元素,保持元素数目不变
In [4]:
arr3_1_2 = arr3_1_1.reshape(3,4) #调用数组方法reshape,各维度参数可以分开传入,也可以组合传入:arr1.reshape((3,4)) arr3_1_2
Out[4]:
array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])
In [5]:
arr3_1_2.reshape(12) #传入一个整数转成一维数组
Out[5]:
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
或者是通过reshape函数调用:
In [6]:
arr3_1_3 = np.reshape(arr3_1_1,(3,4)) #第一个参数是需要改变形状的数组,第二个参数是shape形状,只能通过整数数组或元组列表的组合方式传入,不能分开传入 arr3_1_3
Out[6]:
array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])
In [7]:
arr3_1_3.ndim #维度为2,行和列两个维度
Out[7]:
2
In [8]:
arr3_1_4 = arr3_1_1.reshape(4,-1) arr3_1_4
Out[8]:
array([[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8], [ 9, 10, 11]])
-1表示除了确定的轴长4外剩余的轴长,即arr1元素个数为12/4=3,所以-1表示3
In [9]:
arr3_1_4.shape #形状为4行3列
Out[9]:
(4, 3)
将一维数组改为shape形状(2,4,3),2 * 4 * 3=24个元素,保持元素数目不变
In [10]:
arr3_1_5 = np.arange(24).reshape(2,4,-1) arr3_1_5
Out[10]:
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]]])
In [11]:
arr3_1_5.ndim
Out[11]:
3
In [12]:
arr3_1_5.shape
Out[12]:
(2, 4, 3)
维度为3,对应[[[ ]]]],最里面第三层的[]每个有3个元素,第二层的[]每个有4个元素,对应于四个第三层,而最外面层有2个元素,对应两个第二层
注意:后面介绍的NumPy数组操作就不再区分是调用NumPy数组对象的方法还是调用NumPy函数,只以其中一种方式进行讲解。两者总体区别在于:
- 调用方式不同,数组方法调用是:NumPy数组对象.方法名(),函数调用是:np.函数名(),
- 调用NumPy函数的第一个参数一般是NumPy数组。
展平数组-ravel()函数
展平数组元素功能,即从多维转变成一维数组。返回的是数组视图,修改展平后的数组会影响原始数组。
In [13]:
arr3_1_6 = np.arange(6).reshape(2,3) arr3_1_6
Out[13]:
array([[0, 1, 2], [3, 4, 5]])
In [14]:
arr3_1_7 = arr3_1_6.ravel() #通过ndarray数组对象的方法调用,展平数组元素,默认按行展平 arr3_1_7
Out[14]:
array([0, 1, 2, 3, 4, 5])
或者:
In [15]:
arr3_1_8 = np.ravel(arr3_1_6) #通过NumPy函数调用,展平数组元素,默认按行展平 arr3_1_8
Out[15]:
array([0, 1, 2, 3, 4, 5])
In [16]:
arr3_1_8[1] = 100 arr3_1_8
Out[16]:
array([ 0, 100, 2, 3, 4, 5])
In [17]:
arr3_1_6
Out[17]:
array([[ 0, 100, 2], [ 3, 4, 5]])
指定展开方式,如按列展平数组元素,也就是先第0列从上往下,再第1列从上往下输出:
In [18]:
arr3_1_9 = arr3_1_6.ravel('F') #按列展平数组元素 arr3_1_9
Out[18]:
array([ 0, 3, 100, 4, 2, 5])
In [19]:
arr3_1_9[1] = 100 arr3_1_9
Out[19]:
array([ 0, 100, 100, 4, 2, 5])
In [20]:
arr3_1_6
Out[20]:
array([[ 0, 100, 2], [ 3, 4, 5]])
可以看到,若按行展平(默认),展平后数组重新赋值后,原数组对应位置值也改变。若按列展平,展平后数组重新赋值后,原数组对应位置值没有改变。
多维数组的展平:
In [21]:
arr3_1_10 = np.arange(24).reshape(2,3,4) arr3_1_10
Out[21]:
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]]])
In [22]:
arr3_1_11 = arr3_1_10.ravel() arr3_1_11
Out[22]:
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])
从最里面轴向向外展平数组元素,也就是从轴0到轴n-1依次迭代元素:
In [23]:
for i in [0,1]: for j in [0,1,2]: for k in [0,1,2,3]: print(arr3_1_10[i,j,k])
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
写的更通用一点就是这样子:
In [24]:
for i in np.arange(arr3_1_10.shape[0]): #轴0迭代 for j in np.arange(arr3_1_10.shape[1]): #轴1迭代 for k in np.arange(arr3_1_10.shape[2]): #轴2迭代 print(arr3_1_10[i,j,k])
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
In [25]:
arr3_1_10
Out[25]:
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]]])
In [26]:
arr3_1_12 = arr3_1_10.ravel('F') arr3_1_12
Out[26]:
array([ 0, 12, 4, 16, 8, 20, 1, 13, 5, 17, 9, 21, 2, 14, 6, 18, 10, 22, 3, 15, 7, 19, 11, 23])
大家可以体会一下多维数组设置order='F'是如何展平的,按照轴向从最外层0到最里层n-1(n为维度数)的顺序展平:
In [27]:
for i in [0,1,2,3]: for j in [0,1,2]: for k in [0,1]: print(arr3_1_10[k,j,i])
0 12 4 16 8 20 1 13 5 17 9 21 2 14 6 18 10 22 3 15 7 19 11 23
展平数组-flatten()函数
功能和参数与ravel函数类似,也用于展平数组,但返回的是展平后的一维数组的副本,变换后不会影响原数组
In [28]:
arr3_1_13 = np.random.randint(2,12,6).reshape(2,3) arr3_1_13
Out[28]:
array([[11, 5, 5], [ 3, 11, 4]])
In [29]:
arr3_1_14 = arr3_1_13.flatten('F')#通过数组对象的方法flatten调用,按列展平 arr3_1_14
Out[29]:
array([11, 3, 5, 11, 5, 4])
在生成的新数组上做赋值操作:
In [30]:
arr3_1_14[1] = 100 arr3_1_14
Out[30]:
array([ 11, 100, 5, 11, 5, 4])
In [31]:
arr3_1_13
Out[31]:
array([[11, 5, 5], [ 3, 11, 4]])
可见在展平后的一维数组的赋值不影响原数组。
2.3.9 常用的统计函数
使用NumPy数组可以使你利用简单的数组表达式完成多种数据操作任务,而无须写些大量循环。这种利用数组表达式来替代显式循环的方法,称为向量化。通常,向量化的数组操作会比纯Python的等价实现在速度上快一到两个数量级(甚至更多)。
np.ndarray
提供了许多一元通用统计函数。比如数组求和、求最大最小值等。
注意:默认的,没有指定轴向时,这些一元操作是对整个数组进行计算,没有考虑到数组的形状。
In [144]:
arr3_9_1 = np.array([[65,10,60,25,50],[40,70,90,20,80]]) arr3_9_1
Out[144]:
array([[65, 10, 60, 25, 50], [40, 70, 90, 20, 80]])
numpy.max()和numpy.min()
这两个函数分别沿给定的轴返回最大和最小元素。未指定轴向则统计数组的所有元素的最大最小值
In [145]:
np.max(arr3_9_1)
Out[145]:
90
In [146]:
np.min(arr3_9_1)
Out[146]:
10
numpy.argmax()和numpy.argmin()
这两个函数分别沿给定的轴返回最大和最小元素的索引。
不指定轴向则得到数组中元素根据展平后的一维数组获得最大或最小值的下标
In [147]:
np.argmin(arr3_9_1) #也可以写成调用ndarray数组方法:arr3_9_1.argmin()
Out[147]:
1
In [148]:
np.argmax(arr3_9_1) #也可以写成调用ndarray数组方法:arr3_9_1.argmax()
Out[148]:
7
展平后的一维数组:
In [149]:
arr3_9_1.flatten()
Out[149]:
array([65, 10, 60, 25, 50, 40, 70, 90, 20, 80])
In [150]:
arr3_9_1.flatten()[np.argmax(arr3_9_1)] #通过下标索引获得的最大值
Out[150]:
90
In [151]:
arr3_9_1.flatten()[np.argmin(arr3_9_1)] #通过下标索引获得的最小值
Out[151]:
10
沿某个维度进行统计
默认情况下,每一个numpy的统计函数都是对数组中所有元素进行操作。我们可以用关键字参数axis指定沿着哪个轴进行统计。
- 当axis=0时,表示沿着轴0统计,二维数组即沿着纵轴统计。
- 当axis=1时,表示沿着轴1统计,二维数组即横轴统计。
指定轴向则沿轴向取最值下标:
In [152]:
arr3_9_1
Out[152]:
array([[65, 10, 60, 25, 50], [40, 70, 90, 20, 80]])
In [153]:
maxindex = np.argmax(arr3_9_1, axis = 0)#沿轴0获取最大值下标 maxindex
Out[153]:
array([0, 1, 1, 0, 1], dtype=int64)
In [154]:
max = np.max(arr3_9_1, axis = 0)#沿轴0获取最大值 max
Out[154]:
array([65, 70, 90, 25, 80])
argmax和max输出关系:argmax输出的下标作为该轴索引与其他轴下标组成花式索引得到相应轴向的最大值max:
In [155]:
arr3_9_1[maxindex,(0,1,2,3,4)]
Out[155]:
array([65, 70, 90, 25, 80])
又如求轴1方向的最小值下标及索引值:
In [156]:
minindex = np.argmin(arr3_9_1, axis = 1) minindex
Out[156]:
array([1, 3], dtype=int64)
In [157]:
arr3_9_1[(0,1),minindex] #注意minindex为轴1下标
Out[157]:
array([10, 20])
得到的结果和在轴1上执行min函数相同:
In [158]:
min = np.min(arr3_9_1, axis = 1) min
Out[158]:
array([10, 20])
求各行和:
In [159]:
np.sum(arr3_9_1,axis = 1) #统计二维数组轴1即横向各行之和
Out[159]:
array([210, 300])
求各行平均值:
In [160]:
np.mean(arr3_9_1,axis = 1)#统计二维数组轴1即横向各行平均值
Out[160]:
array([42., 60.])
求各行中位数: 中位数是指数组排序后处于中间位置的数,与平均值的区别是中位数是数组的某个元素。
In [161]:
np.median(arr3_9_1,axis = 1)#统计二维数组轴1即横向各行中位数
Out[161]:
array([50., 70.])
求各行均方差:
In [162]:
np.std(arr3_9_1,axis = 1)#统计二维数组轴1即横向各行的均方差值
Out[162]:
array([21.11871208, 26.07680962])
求各行累计和:
In [163]:
arr3_9_1
Out[163]:
array([[65, 10, 60, 25, 50], [40, 70, 90, 20, 80]])
In [164]:
np.cumsum(arr3_9_1,axis = 1) #累计和 可以通过结果的值 如:3,5,7的得来是 0+3 1+4 2+5
Out[164]:
array([[ 65, 75, 135, 160, 210], [ 40, 110, 200, 220, 300]])
即沿着轴1将元素依次累加并替换原先元素。如60所在位置,替换成65+10+60=135。
求各行累计积:
In [165]:
np.cumprod(arr3_9_1,axis =1) #跟累加处理方式类似,一个个剩下来
Out[165]:
array([[ 65, 650, 39000, 975000, 48750000], [ 40, 2800, 252000, 5040000, 403200000]])
即沿着轴1将元素依次相乘并替换原先元素。如60所在位置,替换成65 * 10 * 60=39000。
2.3.10 通用函数 ( ufunc)
通用函数(或简称ufunc)是以ndarrays逐个元素的方式运行的函数,支持数组广播、类型转换和其他几个标准功能。也就是说,ufunc 是一个函数的“向量化”包装器,它接受固定数量的特定输入并产生固定数量的特定输出。
在 NumPy 中,通用函数是numpy.ufunc类的实例 。许多内置函数是在编译后的 C 代码中实现的。基本的 ufunc 对标量进行操作,但也有一种广义类型,其基本元素是子数组(向量、矩阵等),并且广播是在其他维度上完成的。最简单的例子是加法运算符:
这些函数在对ndarray对象进行运算的速度比使用循环或者列表推导式要快很多。
目前numpy在一种或多种类型上定义了 60 多个通用函数 ,涵盖了广泛的操作。
一元ufunc:
- abs丶fabs 计算整数丶浮点数或复数的绝对值。对于非复数值,可以使用更快的fabs。
- sqrt:计算各元素的平方根。
- square:计算各元素的平方。
- exp:计算各元素的指数(e^x)
- log丶log10丶log2丶log1p 分别为自然对数(底数为e)丶底数为10的log丶底数为2的log丶log(1+x)
- sign:计算各元素的正负号:1(正数)丶0(零)丶-1(负数)
- ceil:计算各元素的ceiling值,即大于等于该值的最小整数
- floor:计算各元素的floor值,即小于等于该值的最大整数
- rint:将各元素四舍五入到最接近的整数,保留dtype
- modf:将数组的小数和整数部分以两个独立数组的形式返回
- isnan:返回一个表示“哪些值是NaN(这不是一个数字)”的布尔型数组
- cos丶cosh丶sin丶sinh:普通型和双曲型三角函数
- tan丶tanh丶arccos丶arccosh丶arcsin丶arcsinh丶arctan丶arctanh:反三角函数
如下对square()求平方函数示意图:
就举几个例子说明如何使用:
In [166]:
arr3_10_1 = np.array([[-30.2,10.5,60.8,-20.3,50],[4,-70.2,90.8,20.1,-80.5]]) arr3_10_1
Out[166]:
array([[-30.2, 10.5, 60.8, -20.3, 50. ], [ 4. , -70.2, 90.8, 20.1, -80.5]])
In [167]:
arr3_10_2 = np.abs(arr3_10_1) arr3_10_2
Out[167]:
array([[30.2, 10.5, 60.8, 20.3, 50. ], [ 4. , 70.2, 90.8, 20.1, 80.5]])
In [168]:
np.exp(arr3_10_1)
Out[168]:
array([[7.66137370e-14, 3.63155027e+04, 2.54158419e+26, 1.52694016e-09, 5.18470553e+21], [5.45981500e+01, 3.25482296e-31, 2.71605748e+39, 5.36190464e+08, 1.09469770e-35]])
In [169]:
np.square(arr3_10_1)
Out[169]:
array([[ 912.04, 110.25, 3696.64, 412.09, 2500. ], [ 16. , 4928.04, 8244.64, 404.01, 6480.25]])
In [170]:
np.sqrt(arr3_10_2)
Out[170]:
array([[5.49545267, 3.24037035, 7.79743548, 4.50555213, 7.07106781], [2. , 8.37854403, 9.5289034 , 4.48330235, 8.97217922]])
In [171]:
np.ceil(arr3_10_1)
Out[171]:
array([[-30., 11., 61., -20., 50.], [ 4., -70., 91., 21., -80.]])
In [172]:
np.floor(arr3_10_1)
Out[172]:
array([[-31., 10., 60., -21., 50.], [ 4., -71., 90., 20., -81.]])
二元ufunc:
- add:将数组中对应的元素相加
- subtract:从第一个数组中减去第二个数组中的元素
- multiply:数组元素相乘
- divide丶floor_divide:除法或向下圆整除法(丢弃余数)
- power:对第一个数组中的元素A,根据第二个数组中的相应元素B,计算A^B
- maximum丶fmax:元素级的最大值计算。fmax将忽略NAN
- minimum丶fmin:元素级的最小值计算。fmax将忽略NAN
- mod:元素级的求模计算(除法的余数)
- copysign:将第二个数组中的值的符号复制给第一个数组中的值。
- greater丶greater_equal丶less丶less_equal丶equal丶not_equal:执行元素级的比较运算,最终产生布尔型数组。相当于运算符>丶>=丶<丶<=丶==丶!=
- logical_and丶logical_or丶logical_xor:执行元素级的真值逻辑运算。相当于运算符&丶|丶^(与或异)
我们以最常用的四则运算和比较运算为例:
- 四则运算:加(+)、减(-)、乘(* )、除(/)、幂(**)、求余(%)。数组间的四则运算表示对每个数组中的元素分别进行四则运算,所以形状必须相同。四则运算与二元ufunc函数的对应关系如下:
In [173]:
import numpy as np arr3_10_3 = np.random.randint(1,10,size = [3,2]) arr3_10_3
Out[173]:
array([[1, 3], [7, 4], [9, 3]])
In [174]:
arr3_10_4 = np.random.randint(1,10,size = [3,2]) arr3_10_4
Out[174]:
array([[5, 3], [7, 5], [9, 7]])
In [175]:
arr3_10_3 + arr3_10_4 #数组相加,对应位置元素相加
Out[175]:
array([[ 6, 6], [14, 9], [18, 10]])
In [176]:
arr3_10_3 - arr3_10_4 #数组相减,对应位置元素相减
Out[176]:
array([[-4, 0], [ 0, -1], [ 0, -4]])
In [177]:
arr3_10_3 * arr3_10_4 #数组相乘,对应位置元素相乘
Out[177]:
array([[ 5, 9], [49, 20], [81, 21]])
In [178]:
arr3_10_3 / arr3_10_4 #数组相除,对应位置元素相除
Out[178]:
array([[0.2 , 1. ], [1. , 0.8 ], [1. , 0.42857143]])
- 比较运算:>、<、==、>=、<=、!=。比较运算返回的结果是一个布尔数组,每个元素为每个数组对应元素的比较结果,即True或False。比较运算与二元ufunc函数的对应关系如下:
In [179]:
arr3_10_3 < arr3_10_4
Out[179]:
array([[ True, False], [False, True], [False, True]])
In [180]:
arr3_10_3 <= arr3_10_4
Out[180]:
array([[ True, True], [ True, True], [ True, True]])
In [181]:
arr3_10_3 == arr3_10_4
Out[181]:
array([[False, True], [ True, False], [ True, False]])
在NumPy中*
号仍然表示乘法,矩阵乘积用np.dot
来计算。
In [182]:
np.dot(arr3_10_3,arr3_10_4.T)
Out[182]:
array([[ 14, 22, 30], [ 47, 69, 91], [ 54, 78, 102]])
二元通用函数由于涉及两个数组运算,要求两个数组的形状相同,当两个数组的形状不一样时,同样需要这两个数组满足广播机制的条件,否则报错。
2.3.11 NumPy 广播(Broadcast)
如果两个数组 a 和 b 形状相同,即满足 a.shape == b.shape,那么二元ufunc函数对数组a,b的运算就是 a 与 b 数组对应位置元素运算。这要求维数相同,且各维度的长度相同。
In [183]:
arr3_11_1 = np.array([1,2,3,4]) arr3_11_2 = np.array([10,20,30,40]) arr3_11_3 = arr3_11_1 + arr3_11_2 print (arr3_11_3)
[11 22 33 44]
当运算中的 2 个数组的形状不同时,numpy 将自动触发广播机制, 广播(broadcasting)是指不同形状的数组之间执行算术运算的方式。
广播的原则:
两个数组的形状从后往前对齐,对齐的组成一对并从后往前逐对比较,多出的维度则不需要比对,若比对的各组要么两个值相等或要么其中一个值为1,则满足广播机制,这两个数组可以做二元通用函数运算。运算结果的数组形状为各数组的维度的最大值,包括多出的维度。
In [184]:
arr1 = np.random.randint(0,10,size=(3,4,5,6)) arr2 = np.random.randint(0,10,size=(3,1,4,1,1)) (arr1 + arr2).shape
Out[184]:
(3, 3, 4, 5, 6)
In [185]:
arr3_11_4 = np.array([[0, 0, 0],[1, 1, 1],[2, 2, 2], [3, 3, 3]]) #arr1.shape = (4,3) arr3_11_5 = np.array([1, 2, 3]) #arr2.shape = (3,) arr_sum = arr3_11_4 + arr3_11_5 print(arr_sum)
[[1 2 3] [2 3 4] [3 4 5] [4 5 6]]
arr3_11_4的shape为(4,3),arr3_11_5的shape为(3,)。前者是二维,后者是一维。arr3_11_4的第二维长度为3,和arr3_11_5的维度相同。arr3_11_4和arr3_11_5的shape并不一样,但是它们可以执行相加操作,因为满足广播机制,维度对齐后的轴长相同,在这个例子当中是将arr3_11_5沿着0轴进行扩展:
In [186]:
arr3_11_6 = np.arange(24).reshape(3,4,2) arr3_11_7 = np.arange(8).reshape(4,2) arr3_11_6 + arr3_11_7
Out[186]:
array([[[ 0, 2], [ 4, 6], [ 8, 10], [12, 14]], [[ 8, 10], [12, 14], [16, 18], [20, 22]], [[16, 18], [20, 22], [24, 26], [28, 30]]])
(3,4,2)和(4,2)的维度是不相同的,前者为3维,后者为2维。但是它们维度对齐后的轴长相同,都为(4,2),所以可以沿着0轴进行广播。
同样,(4,2,3)还和(3)是兼容的,后者需要在两个轴上进行扩展。
In [187]:
arr3_11_8 = np.arange(2)#[0,1] arr3_11_6 + arr3_11_8
Out[187]:
array([[[ 0, 2], [ 2, 4], [ 4, 6], [ 6, 8]], [[ 8, 10], [10, 12], [12, 14], [14, 16]], [[16, 18], [18, 20], [20, 22], [22, 24]]])
arr3_11_8的值[0,1]可以先沿着轴0扩展成形状(4,2),再扩展成(3,4,2)。
下面的形状(3,4)就不能和(3,4,2)运算。
In [188]:
arr3_11_9 = np.arange(12).reshape(3,4) arr3_11_6 + arr3_11_9
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Input In [188], in <module> 1 arr3_11_9 = np.arange(12).reshape(3,4) ----> 2 arr3_11_6 + arr3_11_9 ValueError: operands could not be broadcast together with shapes (3,4,2) (3,4)
对齐后的轴长不相等,所以不能广播。
In [189]:
arr3_11_10 = np.array([[0, 0, 0],[1, 1, 1],[2, 2, 2], [3, 3, 3]]) #arr3_11_10.shape = (4,3) print('arr3_11_10:\n',arr3_11_10) arr3_11_11 = np.array([[1],[2],[3],[4]]) #arr3_11_11.shape = (4, 1) print('arr3_11_11:\n',arr3_11_11) arr_sum = arr3_11_10 + arr3_11_11 print('arr_sum:\n',arr_sum)
arr3_11_10: [[0 0 0] [1 1 1] [2 2 2] [3 3 3]] arr3_11_11: [[1] [2] [3] [4]] arr_sum: [[1 1 1] [3 3 3] [5 5 5] [7 7 7]]
arr3_11_10的shape为(4,3),arr3_11_11的shape为(4,1),它们都是二维的,但是第二个数组在1轴上的长度为1,所以,可以在1轴上面进行广播,满足广播机制。
在这种情况下,两个数组的维度要保证相等,其中有一个轴的长度为1,这样就会沿着长度为1的轴进行扩展。这样的例子还有:(4,6)和(1,6)。(3,5,6)和(1,5,6)、(3,1,6)、(3,5,1),后面三个分别会沿着0轴,1轴,2轴进行广播。
In [190]:
arr3_11_12 = np.arange(4).reshape(1,4,1) arr3_11_10 + arr3_11_12
Out[190]:
array([[[0, 0, 0], [2, 2, 2], [4, 4, 4], [6, 6, 6]]])
两个数组满足广播机制:
In [191]:
arr3_11_13 = np.arange(3).reshape(3,1,1) arr3_11_10 + arr3_11_13
Out[191]:
array([[[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]], [[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]], [[2, 2, 2], [3, 3, 3], [4, 4, 4], [5, 5, 5]]])
In [192]:
arr3_11_14 = np.array([[1],[2]]) #arr3_11_14形状为(2,1) arr_sum = arr3_11_10 + arr3_11_14 arr_sum
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Input In [192], in <module> 1 arr3_11_14 = np.array([[1],[2]]) #arr3_11_14形状为(2,1) ----> 2 arr_sum = arr3_11_10 + arr3_11_14 3 arr_sum ValueError: operands could not be broadcast together with shapes (4,3) (2,1)
上述报错是因为两个数组不满足广播机制:
练习:下列两个数组是否可以做数组加法运算,为什么,能的话执行加法运算
- 创建一个均匀分布的范围在[10,50]的形状为(1,3,5)的数组
- 创建一个形状为(5,3,1)的从2-16的数组
In [ ]:
练习:
以你的学号后三位作为种子,创建一个均匀分布的范围在[学号后三位,学号后三位+100]的形状为(3,2,4)的数组,分别沿着三个轴向统计出求和,最大和最小值,所有元素的求和,最大和最小值
In [ ]:
numpy性能比较
通过numpy数组运算来分析为什么使用numpy,分别通过numpy和原生python进行一组数据的平方和立方之和
In [193]:
def npsum(n):#通过numpy数组运算实现 a = np.arange(n) ** 2 b = np.arange(n) ** 3 c = a + b return c
In [194]:
def pysum(n):#通过原生python的list实现 a = [i ** 2 for i in range(n)] b = [i ** 3 for i in range(n)] c = [a[i] + b[i] for i in range(n)] return c
In [195]:
d = npsum(5) d
Out[195]:
array([ 0, 2, 12, 36, 80])
In [196]:
type(d)
Out[196]:
numpy.ndarray
In [197]:
e = pysum(5) e
Out[197]:
[0, 2, 12, 36, 80]
In [198]:
type(e)
Out[198]:
list
In [199]:
%timeit npsum(100)
5.69 µs ± 348 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [200]:
%timeit pysum(100)
69.9 µs ± 1.63 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
numpy优点:
- 代码简洁:numpy以数组和矩阵为粒度进行运算,不像python需要借助于循环从底层实现
- 性能高:numpy的数组存储效率高,大多以c来实现,创建存储容器‘array'的时候在c上是寻找到一连串区域来存放,但是python存放的时候则不是连续的区域,这使得python在索引这个容器里的数据时不是那么的有效率
- 大量的科学计算函数
numpy能把简单好用的python和高性能的c语言合并在一起,当调用numpy功能时,其实调用了很多的c语言而不是纯的python。因为numpy的快速矩阵相乘的运算,能将乘法分配到计算机中的多个核,让运算并行,从而实现多线程/多进程,这种并行的计算大大加快了运算的速度。
学好了numpy,有助于学好深度学习框架如tensorflow和pytorch,其中pytorch甚至被认为是GPU版本的numpy