numpy
在讨论numpy的具体函数和方法之前,我要先说明一下两个问题:1,numpy中的数组和向量。2,numpy中的“多轴数组”。
维度vs轴数
numpy中里有多维数组,为了避免和线性代数中的多维数组区别开,这里暂时称之为多轴数组。
我们首先生成一个三维数组,里面存放数字0-36:
arr = np.arange(36).reshape(3, 4, 3)
我们可以将这个三轴数组看作一个立方体:每一个小立方体代表一个数字,这些数字按照一定的方式堆砌(排列组合)成一个3*4*3的立方体。三个轴分别为axis0, axis1, axis2。
那么这个reshape方法中的3个参数3,4,3到底指的是什么呢?其实,它们规定了:这个轴的方向上有几个维度!
注意,我们说的numpy中的二维数组(比如是n阶的),实际上是线性代数中的n维数组,或者称为n阶方阵。我们在python里把它叫做二维数组,并不表明这个数组在线性空间中是2阶的,而是这个数组可以在二维平面中表示出来。比如我们举的这个例子,三轴数组就不能在二维平面中表现出来,但我们可以在三维空间里将它形象化。
有了上面的解释,我们便可以知道:axis0方向上有三个维度,axis1方向上有4个维度,axis2方向上有3个维度。
从axis0的方向上看过去,一共有3个维度,每一维都是4*3的”二轴数组“。
从axis1的方向上看过去,一共有4个维度,每一维都是3*3的”二轴数组“。
从axis2的方向上看过去,一共有3个维度,每一维都是3*4的”二轴数组“。
好了,其实我们只要知道python中的多维数组,和线性代数中的多维数组完全不同就可以了。我们通常还是只使用二轴数组,只要不造成歧义,表述成“二维n阶数组”也是可以的。
numpy中按轴进行的操作有很多,我们将轴的概念理解好,会对矩阵操作带来很大的方便,接下来举几个例子:
import numpy as np arr = np.arange(12).reshape(4, 3) mean0 = np.mean(arr,axis=0) #在axis0方向(向下,列方向)上操作,即对每一列求均值 mean1 = np.mean(arr,axis=1) #每一行的均值 sum0 = np.sum(arr,axis=0) #每一列的和 sum1 = np.sum(arr,axis=1) #每一行的和 min0 = arr.min(0) #每一列的最小值 min1 = arr.min(1) #每一行的最小值 max0 = arr.max(0) #每一列的最大值 min1 = arr.max(1) #每一行的最大值 mean0 Out[8]: array([4.5, 5.5, 6.5]) mean1 Out[9]: array([ 1., 4., 7., 10.]) sum0 Out[12]: array([18, 22, 26]) sum1 Out[11]: array([ 3, 12, 21, 30])
我们定义了一个4*3的矩阵。axis0方向的长度是4,axis1方向的长度是3.从axis0的方向看过去矩阵是3个4维列向量,从axis1的方向看过去是4个3维的行向量。
所以,按axis=0操作时,都是按照(axis1个)列向量操作的;按axis=1操作时都是按照(axis0个)列向量操作的;
我们按照axis0来求每个列向量平均数,得到了一个3阶数组;按照axis1来求每个行向量平均数,得到了一个4阶数组。
我们已经将arr reshape成了一个4*3的矩阵,axis0对应的轴上应该有四个维度(4行),按axis0轴求和就是将这四个维度(中的4个数据)求和,之后再按axis1的方向移动求另外三个数据的和,一共移动3次。
当然也可以简记为:axis=0是按列,axis=1是按行操作。
向量vs数组
我们用shape方法来获取一个矩阵的形状,它会会返回一个元组,还会用到T方法来进行矩阵转置。举一个简单的例子:
arr1 = np.arange(36) arr1.shape Out[14]: (36,) arr1.T.shape Out[15]: (36,)
对于arr1这个数组,转置没有效果。这说明我们有一个错误的观念:多维数组就是矩阵,一维数组就是列向量。这句话的前半句没有任何问题,但一维数组只是“一组有顺序的数”,并不是向量。你瞧它的形状是(36,),而非(36,1)。欲将其变为列向量或者行向量也简单,需要用到numpy中的expand_dims方法,需要给他两个参数,第二个是给定的轴(在这个轴的方向上增加维度)。也可以直接用matrix生成矩阵。
arr2 = np.expand_dims(arr1, axis=0) #朝着axis0的方向,即行 arr3 = np.expand_dims(arr1, axis=1) #朝着axis1的方向,即列 arr4 = np.matrix([1, 2, 3]) arr5 = np.matrix([1, 2, 3]).T arr2.shape Out[17]: (1, 36) #行向量 arr3.shape Out[18]: (36, 1) #列向量 arr4.shape Out[19]: (1, 3) #行向量 arr5.shape Out[20]: (3, 1) #列向量
除了expand_dims方法外,还有其他的构造向量的方法,我们用下面的例子做一个测试,探索一下array函数构造矩阵的原理:
test1 = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8]) test2 = np.array([[0, 1, 2, 3, 4, 5, 6, 7, 8]]) test1.shape Out[25]: (9,) test2.shape Out[26]: (1, 9)
可以看出test1的方式是构造了一个数组,而test2的方式是构造了一个行向量。分析一下array构造矩阵的原理:
np.array([[#, #, #], [#, #, #], [#, #, #]])
array方法中,传入的参数是一个列表。这个列表中存在n个子列表,便构造n行的矩阵,每个子列表中有m列元素就构造m列的矩阵。所以array的维度是由传入的列表中,子列表的个数n,和子列表中的元素个数m共同确定的。
那么特例就是:子列表的个数为1,相当于构造了一个1行的矩阵,即行向量。子列表有多个,但每个子列表中元素个数为1,那么这就是一个列向量咯。
数乘vs点乘
arr1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) arr2 = np.arange(1, 10).reshape((3, 3)) arr3 = np.eye(3) arr4 = np.matrix([1,2,3]).T arr1 + 10 Out[65]: [[11 12 13] [14 15 16] [17 18 19]] arr1 + arr4 Out[77]: matrix([[ 2, 3, 4], [ 6, 7, 8], [10, 11, 12]]) arr1*arr3 Out[66]: [[1. 0. 0.] [0. 5. 0.] [0. 0. 9.]] arr1.dot(arr3) #等同于np.dot(arr1,arr3) Out[67]: [[1. 2. 3.] [4. 5. 6.] [7. 8. 9.]] 2**arr1 Out[68]: [[ 2 4 8] [ 16 32 64] [128 256 512]] arr1**2 Out[69]: [[ 1 4 9] [16 25 36] [49 64 81]]
数与矩阵相加,即与矩阵每一个元素相加。列向量与矩阵相加,即列向量与每一列相加。
np.dot(arr1,arr3)进行的是常规的矩阵乘法,两个矩阵的形状必须遵循“前列等后行“的规则。而arr1*arr3就是对应位置的元素作乘积,需要两个矩阵的形状相同。
切片vs拷贝
我们之间已经知道列表的切片就等于浅拷贝。那么对于数组也一样吗。
myarr16 = myarr[1] myarr17 = myarr[1, 2] myarr18 = myarr[1][2] myarr19 = myarr[:, ::-1] myarr20 = myarr[::-1, ::-1] myarr21 = myarr[::-1].copy() np.may_share_memory(myarr19, myarr) Out[46]: True np.may_share_memory(myarr20, myarr) Out[47]: True np.may_share_memory(myarr21, myarr) Out[48]: False
数组的切片有两种方法,如myarr17和myarr18。为了和列表切片区分开来,习惯于用myarr18的构造方式。
用numpy中的方法may_share_memory来查看,两个数组是否共享内存,也即是否为新数组创了新内存。可以发现切片始终与原数组共享内存,而copy方法进行了内存的创建,赋值。
高级切片
array的切片的元素可以是一个列表,表示抽取除这些行,或列。
myarr22 = np.random.randint(0, 20 ,10).reshape(2, 5) myarr23 = myarr22[:, [4, 2]] myarr23 Out[54]: array([[17, 4], [ 1, 15]])
myarr22是一个2*5的矩阵,我们得到的myarr23是抽取出每一行的第5列,和第3列组成的新数组。
重新排序
myarr19 = myarr[:, ::-1] myarr20 = myarr[::-1, ::-1] myarr21 = myarr[::-1].copy()
其实我们利用切片就可以对数组进行逆序排列。
myarr19进行列逆序排列, 第一个:不能少。因为np是默认按行进行操作的,如果只给一个参数,如myarr21会将该切片操作应用到每一行上,即将myarr的行之间逆序。
数组没有列表的原地排序的sort方法。
array0 = np.array([[9, 8, 7, 6], [6, 5, 4, 3], [3, 2, 1, 0]]) array1 = np.sort(array0, axis=0) #朝着axis0的维度,对每一个列向量排序 array2 = np.argsort(array0, axis=0) array3 = np.sort(array0, axis=1) #朝着axis1的维度,对每一个行向量排序 array4 = np.argsort(array0, axis=1) array1 Out[50]: array([[3, 2, 1, 0], [6, 5, 4, 3], [9, 8, 7, 6]]) array2 Out[51]: array([[2, 2, 2, 2], [1, 1, 1, 1], [0, 0, 0, 0]], dtype=int64) array3 Out[52]: array([[6, 7, 8, 9], [3, 4, 5, 6], [0, 1, 2, 3]]) array4 Out[53]: array([[3, 2, 1, 0], [3, 2, 1, 0], [3, 2, 1, 0]], dtype=int64)
array2是按列排序返回顺序列表,返回矩阵中的列向量表示:原矩阵中对应列向量从小到大排列之后,对应元素在原列表中的位置。也即是array1中列向量的元素在array0中对应列向量的索引。
array4是按行排序返回顺序列表,返回矩阵中的行向量表示:原矩阵中对应行向量从小到大排列之后,对应元素在原列表中的位置。也即是array3中列向量的元素在array0中对应行向量的索引。
等差数列
等差数列有两种生成方法:
myarr3 = np.arange(1, 10, 3) myarr4 = np.linspace(9, 10, 5) myarr5 = np.linspace(9, 10, 5, endpoint = False) myarr3 Out[41]: array([1, 4, 7]) myarr4 Out[42]: array([9. , 9.25, 9.5, 9.75, 10.]) myarr5 Out[43]: array([9. , 9.2, 9.4, 9.6, 9.8])
arange(start, end(exclusive), step) 三个参数,前两个参数表示生成等差数列的区间,左闭右开。第三个参数为步长缺省值为1。
linspace(start, end, number_point) 将区间切分为number_point-1份。其实linspace还有一个默认参数 endpoint = True,缺省时置True,即左闭右闭的闭区间。np.linspace(start, end, number_point, endpoint = False) 将区间切分为number_point份,因为endpoint = False,所以与arange一样是左闭右开。
特殊矩阵
全零矩阵,全一矩阵,单位阵,对角阵,随机矩阵的创建方法:
myarr6 = np.ones((3, 4)) myarr7 = np.zeros((3, 4)) myarr8 = np.eye(4) #eye的参数不能是远足,只能是一个数字 myarr9 = np.diag([1, 2, 3, 4]) myarr10 = np.random.rand(4, 2)
其中,ones和zeros的参数都只能是来描述矩阵的形状的元组。eye的参数只能是一个int类型数据。diag的参数一个存放着特征值的列表。
另外提一个空矩阵,只分配地址,但不存入数据
矩阵扩展
我们可以用numpy中的tile函数对数组朝指定方向进行扩展指定次数:
mylist = [7, 999, 4] array1 = np.tile(mylist, (1, 3)) array2 = np.tile(mylist, (3, 2)) array3 = np.tile(mylist, 2) array1 Out[55]: array([[ 7, 999, 4, 7, 999, 4, 7, 999, 4]]) array2 Out[56]: array([[ 7, 999, 4, 7, 999, 4], [ 7, 999, 4, 7, 999, 4], [ 7, 999, 4, 7, 999, 4]]) array3 Out[57]: array([ 7, 999, 4, 7, 999, 4])
tile(mylist, (m, n))相当于构造了一个m*n的矩阵,并将里面的每个元素填充维mylist。
hstack函数,和r_函数都是在axis0方向上合并,即合并在右边。vstack函数,和c_函数都是在axis1方向上合并,即合并在下边。
myarr1 = np.array([[ 8., 5.], [ 1., 6.]]) myarr2 = np.array([[ 1., 9.], [ 8., 5.]]) np.hstack((myarr1, myarr2)) Out[69]: array([[8., 5., 1., 9.], [1., 6., 8., 5.]]) np.c_[myarr1, myarr2] Out[73]: array([[8., 5., 1., 9.], [1., 6., 8., 5.]]) np.vstack((myarr1, myarr2)) array([[8., 5.], [1., 6.], [1., 9.], [8., 5.]]) np.r_[myarr1, myarr2] Out[72]: array([[8., 5.], [1., 6.], [1., 9.], [8., 5.]])
数据类型
myarr12 = np.array([1, 2, 3]) myarr13 = np.array([1, 2, 3, 4], dtype = 'float') myarr14 = np.array([1., 2., 3.]) myarr15 = np.array([1+1j, 2+2j, 3+3*1j]) #numpy中的复数符号是1j,而不是j
myarr12是int32型的数组,myarr13,myarr14都是float64型的数组。我们当然也可以构造复数complex128类型的数组,比如myarr15。
矩阵大小
arr0 = np.arange(0, 12).reshape(4, 3) arr0.size Out[65]: 12 arr0.shape Out[66]: (4, 3) arr0.shape[0] Out[67]: 4 arr0.shape[1] Out[68]: 3 arr0.dtype.itemsize Out[69]: 4
生成一个4*3的矩阵,size方法获得其中元素的个数,返回一个int数据。shape方法返回一个元组(num,dim),第一个元素为行数,第二个元素为列数。
dtype.itemsize是numpy中的方法,获取数组所占用的字节数。