26 May 2018

Python数据分析工具库-Numpy 数组支持库

1 Numpy数组

在Python中有类似数组功能的数据结构,比如list,但在数据量大时,list的运行速度便不尽如意,Numpy(Numerical Python)提供了真正的数组功能,以及对数据进行快速处理的函数,Numpy中内置函数处理数据的速度是C语言级别的。Numpy支持高级大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。Numpy中的ndarray类提供了python对多维数组对象的支持,并具备对矢量进行运算的能力,运算更为快速且节省空间。

ndarray是N维数组对象(矩阵),其中所有的元素都必须是相同类型ndarray主要包含以下几个属性:

ndarray.ndim:表示数组对象或矩阵的维度;

ndarray.shape:表示每个维度上的数组的大小;

ndarray.size:表示数组中元素的总数,等同于ndarray.shape中两个元素的乘积;

ndarray.dtype:表示数组中元素的类型;

ndarray.itemsize:表示数组中每个元素的字节大小,比如数据类型为float64的数组,其元素的字节大小为64/8=8。

比如:

>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>> a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int64'
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<type 'numpy.ndarray'>

2 创建Numpy数组

array方法

>>> import numpy as np
>>> a = np.array([2,3,4])#创建一维数组
>>> b = np.array([(1.5,2,3), (4,5,6)])#创建二维数组
>>> c = np.array( [ [1,2], [3,4] ], dtype=float64 )#创建指定数据类型的数组

如果想创建指定shape的数组,并使用占位符来初始化数组,可以用以下方法

>>> np.zeros( (3,4) )#创建3行4列矩阵,用0初始化矩阵中所有的元素
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> np.ones( (2,3,4), dtype=np.int16 ) #创建三维矩阵,维度分别为2,3,4,且用1来初始化矩阵中所有的元素
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]]], dtype=int16)
>>> np.empty( (2,3) ) #创建2行3列空矩阵,矩阵中元素初始值随机,取决于内存状态,默认情况下,创建的数组的dtype为float64。
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
       [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

对应的zeros、ones、empty还有zeros_like、ones_like、empty_like,它们以另一个数组为参数,根据其形状和dtype创建数组。

arange方法,与Python内置的range相似:

>>> numpy.arange(6)
array([0,1,2,3,4,5,])
>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])

3 数组基本数学操作

加、减、乘、点乘

>>> a = np.array( [20,30,40,50] )
>>> b = np.arange( 4 )
>>> b
array([0, 1, 2, 3])
>>> c = a-b
>>> c
array([20, 29, 38, 47])
>>> 10*np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([ True, True, False, False])
>>> A = np.array( [[1,1],
...             [0,1]] )
>>> B = np.array( [[2,0],
...             [3,4]] )
>>> A*B #矩阵相乘
array([[2, 0],
       [0, 4]])
>>> A.dot(B) #矩阵点乘
array([[5, 4],
       [3, 4]])
>>> np.dot(A, B)  #矩阵点乘
array([[5, 4],
       [3, 4]])

叠加、叠乘

>>> a = np.ones((2,3), dtype=int)
>>> b = np.random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
       [3, 3, 3]])
>>> b += a
>>> b
array([[ 3.417022  ,  3.72032449,  3.00011437],
       [ 3.30233257,  3.14675589,  3.09233859]])
>>> a += b  #浮点型不能转换成整型
TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'

取最大、最小、求和

>>> a = np.random.random((2,3))
>>> a
array([[ 0.18626021,  0.34556073,  0.39676747],
       [ 0.53881673,  0.41919451,  0.6852195 ]])
>>> a.sum()
2.5718191614547998
>>> a.min()
0.1862602113776709
>>> a.max()
0.6852195003967595

对于多维矩阵,只在其中指定轴进行操作,使用axis参数

>>> b = np.arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> b.sum(axis=0)   #对二维矩阵的列轴进行求和
array([12, 15, 18, 21])
>>> b.min(axis=1)  #求二维矩阵的每个横轴最小值
array([0, 4, 8])

通用函数(ufunc),对ndarray中的数据执行元素级运算,比如sin、cos、exp等:

>>> B = np.arange(3)
>>> B
array([0, 1, 2])
>>> np.exp(B)
array([ 1.        ,  2.71828183,  7.3890561 ])
>>> np.sqrt(B)
array([ 0.        ,  1.        ,  1.41421356])
>>> C = np.array([2., -1., 4.])
>>> np.add(B, C)
array([ 2.,  0.,  6.])

4 数组的索引、切片和遍历

和Python的list数据结构类似,Numpy的一维数组也可以进行索引、切片以及遍历。

>>> a = np.arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> a[:6:2] = -1000    #从0-6的元素中,每隔2个将元素值重新赋值为-1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> for i in a:
...     print(i + 1)
-999
2
-999
28
-999
126
217
344
513
730

二维数组的索引、切片:

>>> def f(x,y):
...     return 10*x+y
...
>>> b = np.fromfunction(f,(5,4),dtype=int) #函数式创建数组,fromfunction第一个参数为函数f,第二个参数为数组的shape,shape第一个参数为函数f第一个参数的取值范围,第二个参数为函数f第二个参数的取值范围,dtype表示数组元素类型。
>>> b
array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])
>>> b[2,3] #不是数组b中的第2行第三列的元素,而是索引为[2,3]的元素
23
>>> b[0:5, 1]  #取出从第0行到第5行的第2个元素组成数组
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]  #等同于前一句
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]  #取出第2行与第3行的所有元素组成数组
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])
>>> for row in b: #遍历得到b数组的每一行元素
...     print(row)
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]
>>> for element in b.flat: #遍历得到b数组的每一个元素
...     print(element)
...
0
1
2
3
10
11
12
13
20
21
22
23
30
31
32
33
40
41
42
43

多维数组的索引、切片:

>>> c = np.array( [[[  0,  1,  2],    
...                 [ 10, 12, 13]],
...                [[100,101,102],
...                 [110,112,113]]])
>>> c.shape
(2, 2, 3)
>>> c[1,…]   #等同于 c[1,:,:] or c[1]
array([[100, 101, 102],
       [110, 112, 113]])
>>> c[...,2]   #等同于 c[:,:,2]
array([[  2,  13],
       [102, 113]])

5 shape变化及转置

>>> a = np.floor(10*np.random.random((3,4)))
>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.shape
(3, 4)
>>> a.ravel()  # 转化为一维数组
array([ 2.,  8.,  0.,  6.,  4.,  5.,  1.,  1.,  8.,  9.,  3.,  6.])
>>> a.reshape(6,2)  # 将数组a转化为指定shape的数组
array([[ 2.,  8.],
       [ 0.,  6.],
       [ 4.,  5.],
       [ 1.,  1.],
       [ 8.,  9.],
       [ 3.,  6.]])
>>> a.T  # 数组的转置
array([[ 2.,  4.,  8.],
       [ 8.,  5.,  9.],
       [ 0.,  1.,  3.],
       [ 6.,  1.,  6.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)

注意对数组进行reshape操作不会改变原有数组a,但resize会在原有数组a上进行改变

>>> a.resize((2,6))
>>> a
array([[ 2.,  8.,  0.,  6.,  4.,  5.],
       [ 1.,  1.,  8.,  9.,  3.,  6.]])

当reshape方法中有参数为-1,则表示numpy会自己计算-1位置的维数,这在很多深度学习模型中可以见到。

>>> a.reshape(3,-1)
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])

6 数组的合并与拆分

concatenate连接

>>> x = numpy.array([[1, 2, 3], [4, 5, 6]])
>>> y = numpy.array([[7, 8, 9], [10, 11, 12]])
>>> numpy.concatenate([x, y], axis = 0)  # 竖直组合 
[[ 1  2  3][ 4  5  6][ 7  8  9][10 11 12]]
>>> numpy.concatenate([x, y], axis = 1)  # 水平组合
 [[ 1  2  3  7  8  9][ 4  5  6 10 11 12]]

横向合并,沿第一个轴进行堆叠,比如:vstackrow_stack

>>> a = np.floor(10*np.random.random((2,2)))
>>> a
array([[ 8.,  8.],
       [ 0.,  0.]])
>>> b = np.floor(10*np.random.random((2,2)))
>>> b
array([[ 1.,  8.],
       [ 0.,  4.]])
>>> np.vstack((a,b))
array([[ 8.,  8.],
       [ 0.,  0.],
       [ 1.,  8.],
       [ 0.,  4.]])

纵向合并,沿着第二个轴进行堆叠,比如hstackcolumn_stack,两者不一样,column_stack在对一维数组进行堆叠时会先将一维数组转化为二维数组,最终返回二维数组。

>>> np.hstack((a,b)) #使用hstack对二维数组进行纵向合并
array([[ 8.,  8.,  1.,  8.],
       [ 0.,  0.,  0.,  4.]])
>>> np.column_stack((a,b)) #使用column_stack对二维数组进行纵向合并
array([[ 8.,  8.,  1.,  8.],
       [ 0.,  0.,  0.,  4.]])
>>> a = np.array([4.,2.])
>>> b = np.array([3.,8.])
>>> np.column_stack((a,b)) #使用column_stack对一维数组进行纵向合并,返回二维数组
array([[ 4., 3.],
       [ 2., 8.]])
>>> np.hstack((a,b))  #使用hstack对一维数组进行纵向合并,返回一维数组
array([ 4., 2., 3., 8.])

array_split ,numpy.array_split(ary, indices_or_sections, axis=0),沿着第一个轴从左至右的顺序切分:

>>> x = np.arange(8.0)
>>> np.array_split(x, 3)
    [array([ 0.,  1.,  2.]), array([ 3.,  4.,  5.]), array([ 6.,  7.])]
>>> x = np.arange(7.0)
>>> np.array_split(x, 3)
    [array([ 0.,  1.,  2.]), array([ 3.,  4.]), array([ 5.,  6.])]

vsplit,沿着第一个轴切分,相当于split方法中参数axis=0

>>> x = np.arange(16.0).reshape(4, 4)
>>> x
array([[  0.,   1.,   2.,   3.],
       [  4.,   5.,   6.,   7.],
       [  8.,   9.,  10.,  11.],
       [ 12.,  13.,  14.,  15.]])
>>> np.vsplit(x, 2)
[array([[ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.]]),
 array([[  8.,   9.,  10.,  11.],
       [ 12.,  13.,  14.,  15.]])]

hsplit,沿着第二个轴切分,相当于split方法中参数axis=1

>>> x = np.arange(16.0).reshape(4, 4)
>>> x
array([[  0.,   1.,   2.,   3.],
       [  4.,   5.,   6.,   7.],
       [  8.,   9.,  10.,  11.],
       [ 12.,  13.,  14.,  15.]])
>>> np.hsplit(x, 2)
[array([[  0.,   1.],
       [  4.,   5.],
       [  8.,   9.],
       [ 12.,  13.]]),
 array([[  2.,   3.],
       [  6.,   7.],
       [ 10.,  11.],
       [ 14.,  15.]])]

7 数组的复制

完全不复制(No Copy at All)

>>> a = np.arange(12)
>>> b = a            # no new object is created
>>> b is a           # a and b are two names for the same ndarray object
True
>>> b.shape = 3,4    # changes the shape of a
>>> a.shape
(3, 4)

这种方式的“复制”其实没有实际复制,只是将变量b在内存的索引指向了变量a所在的内存,这样变量a和变量b均指向同一块内存,这时候改变了b就相当于改变了a

浅复制

使用view方法来创建一个新的数组对象,并把将被复制的数组a的视图(view)复制到新的数组对象c,这时的c数据完全来自于a,和 a 保持完全一致,换句话说,c的数据完全由a保管,他们两个的数据变化是一致的:

>>> c = a.view()
>>> c is a
False
>>> c.base is a       # c只是a的视图
True
>>> c.flags.owndata
False
>>> c.shape = 2,6   # a的shape不会变化
>>> a.shape
(3, 4)
>>> c[0,4] = 1234   # a的值会相应的变化
>>> a
array([[   0,    1,    2,    3],
       [1234,    5,    6,    7],
       [   8,    9,   10,   11]])

切片也是一种浅复制

>>> s = a[ : , 1:3]     # 将a的第2列与第三列浅复制给s
>>> s[:] = 10           # 将s的所有元素重新赋值为10,也会改变a相应位置的值
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

深复制

使用copy方法,不仅将被复制数组的索引复制到新的数组中,也将被复制数组的元素复制到新的数组中

>>> d = a.copy()      # 创建一个新的数组
>>> d is a
False
>>> d.base is a     
False
>>> d[0,0] = 9999
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

8 Fancy indexing与布尔索引

Fancy indexing是指传递索引数组以便一次得到多个数组元素。使用Fancy indexing时返回数组的shape是索引数组的shape而不是被索引的原数组的shape

一维数组的Fancy indexing

>>> a = np.arange(12)**2     
>>> i = np.array( [ 1,1,3,8,5 ] )    # 索引数组
>>> a[i]                                      
array([ 1,  1,  9, 64, 25])
>>> j = np.array( [ [ 3, 4], [ 9, 7 ] ] )   
>>> a[j]                       
array([[ 9, 16],
       [81, 49]])

多维数组的Fancy indexing

>>> a = np.arange(12).reshape(3,4)
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> i = np.array( [ [0,1],      # 横向索引
...                 [1,2] ] )
>>> j = np.array( [ [2,1],      # 纵向索引 
...                 [3,3] ] )
>>>
>>> a[i,j]                
array([[ 2,  5],
       [ 7, 11]])
>>> a[i,2]
array([[ 2,  6],
       [ 6, 10]])
>>>
>>> a[:,j]             
array([[[ 2,  1],
        [ 3,  3]],
       [[ 6,  5],
        [ 7,  7]],
       [[10,  9],
        [11, 11]]])

如果索引数组包含多个相同的索引,那么最后的索引会覆盖前面的索引。

>>> a = np.arange(5)
>>> a[[0,0,2]]=[1,2,3]
>>> a
array([2, 1, 3, 3, 4])

但对于类似“+=”累加的操作却不会叠加两次:

>>> a = np.arange(5)
>>> a[[0,0,2]]+=1
>>> a
array([1, 1, 3, 3, 4])

布尔索引

索引数组元素为布尔类型的值:

>>> a = np.arange(12).reshape(3,4)
>>> b = a > 4
>>> b                        
array([[False, False, False, False],
       [False,  True,  True,  True],
       [ True,  True,  True,  True]])
>>> a[b]                   
array([ 5,  6,  7,  8,  9, 10, 11])
>>> a[b] = 0                
>>> a
array([[0, 1, 2, 3],
       [4, 0, 0, 0],
       [0, 0, 0, 0]])

索引数组有多个

>>> a = np.arange(12).reshape(3,4)
>>> b1 = np.array([False,True,True])             
>>> b2 = np.array([True,False,True,False])      、

>>> a[b1,:]                                  
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

>>> a[b1]                                    
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

>>> a[:,b2]                                
array([[ 0,  2],
       [ 4,  6],
       [ 8, 10]])

9 Numpy的线性代数(Linear Algebra)

包含求逆、奇异值分解、生成对角矩阵、解线性方程组Ax=b、计算特征值与特征向量等

>>> import numpy as np
>>> a = np.array([[1.0, 2.0], [3.0, 4.0]])
>>> print(a)
[[ 1.  2.]
 [ 3.  4.]]

>>> a.transpose() # 转置
array([[ 1.,  3.],
       [ 2.,  4.]])

>>> np.linalg.inv(a) # 求逆
array([[-2. ,  1. ],
       [ 1.5, -0.5]])

>>> u = np.eye(2) # 生成对角矩阵
>>> u
array([[ 1.,  0.],
       [ 0.,  1.]])
>>> j = np.array([[0.0, -1.0], [1.0, 0.0]])

>>> np.dot (j, j) # 矩阵乘
array([[-1.,  0.],
       [ 0., -1.]])

>>> np.trace(u)  # 求对角线元素和
2.0

>>> y = np.array([[5.], [7.]])
>>> np.linalg.solve(a, y) # 解线性方程组Ax=b
array([[-3.],
       [ 4.]])

>>> np.linalg.eig(j) #计算特征值与特征向量
(array([ 0.+1.j,  0.-1.j]), array([[ 0.70710678+0.j        ,  0.70710678-0.j        ],
       [ 0.00000000-0.70710678j,  0.00000000+0.70710678j]]))

参考文献

Numpy API文档:https://docs.scipy.org/doc/numpy/user/quickstart.html


Tags:
Stats: