NumPy数组和矢量运算

| 分类 python之数据分析  | 标签 python  numpy  数据分析  数组  矢量运算  线性代数  矩阵 

NumPy简介

NumPy(Numerical Python的简称)是高性能科学计算和数据分析的基础包,其部分功能如下:

  • ndarray,一个具有矢量算术运算和复杂广播功能的快速且节省空间的多维数组
  • 用于对整组数据进行快速运算的标准数学函数(无需编写循环)
  • 用于读写磁盘数据的工具以及用于操作内存映射文件的工具
  • 线性代数、随机数生成以及傅立叶变换功能
  • 用于集成由C、C++、Fortran等语言编写的代码的工具

最后一点也是从生态系统角度来看最重要的一点。由于NumPy提供了一个简单易用的C API,因此很容易将数据传递给由低级语言编写的外部库,外部库也能以NumPy数组的形式将数据返回给Python。这个功能使Python成为一种包装C/C++/Fortran历史代码库的选择,并使被包装库拥有一个动态的、易用的接口

NumPy本身并没有提供多么高级的数据分析功能,理解NumPy数组以及面向数组的计算将有助于你更加高效地使用诸如pandas之类的工具

对于大部分数据分析应用而言,主要关注的功能点在:

  • 用于数据整理和清理、子集构造和过滤、转换等快速的矢量化数组运算
  • 常用的数组算法,如排序、唯一化、集合运算等
  • 高效的描述统计和数据聚合/摘要运算
  • 用于异构数据集的合并/连接运算的数据对齐河关系型数据运算
  • 将条件逻辑表述为数组表达式(而不是带有if-elif-else分支的循环)
  • 数据的分组运算(聚合、转换、函数应用等)

虽然NumPy提供了这些功能的计算基础,但你可能还想将pandas作为数据分析工作的基础(尤其是对于结构化或表格化数据),因为它提供了能使大部分常见数据任务变得非常简洁的丰富高级接口。pandas还提供了一些NumPy所没有的更加领域特定的功能,如时间序列处理

多维数组基础

NumPy最重要的一个特点就是其N维数组对象(即ndarray),该对象是一个快速而灵活的大数据集容器,你可以利用这种数组对整块数据执行一些数学运算。下面这个简单的例子展示了ndarry的一些简单用法示例

In [1]: from numpy.random import randn    #导入numpy.random.randn

In [2]: import numpy as np                #导入numpy

In [3]: data = randn(2, 3)                #随机生成2*3的数组

In [4]: data                              #输出数据信息
Out[4]: 
array([[-0.88452944, -0.12395013, -0.33280688],
       [ 0.03746888, -0.48163027,  0.71136892]])

In [5]: data * 10                         #数组乘以10,其实就是对数组中的每个元素做乘以10操作
Out[5]: 
array([[-8.84529436, -1.2395013 , -3.32806883],
       [ 0.3746888 , -4.81630272,  7.11368919]])

In [6]: data + data                       #数组相加,其实就是数组中对应的元素做相加
Out[6]: 
array([[-1.76905887, -0.24790026, -0.66561377],
       [ 0.07493776, -0.96326054,  1.42273784]])

In [7]: data.shape                        #查看这个数组的维度大小
Out[7]: (2, 3)

In [8]: data.dtype                        #查看数组元素的数据类型
Out[8]: dtype('float64')

创建多维数组

NumPy中创建数组的最简单方法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组

In [12]: data2 = [[1,2,3], [4,5,6]]

In [13]: arr2 = np.array(data2)           #转换得到一个二维数组

In [14]: arr2
Out[14]: 
array([[1, 2, 3],
       [4, 5, 6]])

In [15]: np.zeros((2,2))                  #创建指定形状的全0数组
Out[15]: 
array([[ 0.,  0.],
       [ 0.,  0.]])

In [16]: np.ones((2,3))                   #创建指定形状的全1数组
Out[16]: 
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])

In [17]: np.empty((2,3,2))                #创建指定形状的随机数组
Out[17]: 
array([[[  2.00000000e+000,   2.00000000e+000],
        [  2.23752437e-314,   2.23182464e-314],
        [  2.23121188e-314,   2.23751673e-314]],

       [[  1.64200515e-062,   0.00000000e+000],
        [  2.23738893e-314,  -5.71320723e-157],
        [  2.00000000e+000,   2.00389639e+000]]])

In [18]: np.arange(5)                     #arange是Python内置函数range的数组版
Out[18]: array([0, 1, 2, 3, 4])

多维数组的dtype

dtype是NumPy强大和灵活的原因之一,多数情况下,它们直接映射到相应的机器表示,这使得“读写磁盘上的二进制数据流”以及“集成低级语言代码(如C、C++)”等工作变得简单

In [19]: typearr = np.array([1,2,3], dtype=np.float64)

In [20]: typearr
Out[20]: array([ 1.,  2.,  3.])

In [21]: typearr2 = typearr.astype(np.int64)

In [22]: typearr2
Out[22]: array([1, 2, 3])

In [23]: 

数组和标量之间的运算

数组很重要,因为它使你不用编写循环即可对数据执行批量运算。这通常被称为矢量化。大小相等的数组之间的任何算术运算都会将运算应用到元素级

In [3]: data = randn(2, 3)                #随机生成2*3的数组

In [4]: data                              #输出数据信息
Out[4]: 
array([[-0.88452944, -0.12395013, -0.33280688],
       [ 0.03746888, -0.48163027,  0.71136892]])

In [5]: data * 10                         #数组乘以10,其实就是对数组中的每个元素做乘以10操作
Out[5]: 
array([[-8.84529436, -1.2395013 , -3.32806883],
       [ 0.3746888 , -4.81630272,  7.11368919]])

In [6]: data + data                       #数组相加,其实就是数组中对应的元素做相加
Out[6]: 
array([[-1.76905887, -0.24790026, -0.66561377],
       [ 0.07493776, -0.96326054,  1.42273784]])

基本的索引和切片

NumPy数组的索引是一个内容丰富的主题,因为选取数据子集或单个元素的方式很多

In [24]: arr
Out[24]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [25]: arr[5]                  #索引到第5+1个元素
Out[25]: 5

In [26]: arr[5:8]                #切片:第5+1到第8个元素
Out[26]: array([5, 6, 7])

In [27]: arr[5:8] = 12           #因为数组切片是原始数组的视图,而不是复制,所以将改变原数组值

In [28]: arr
Out[28]: array([ 0,  1,  2,  3,  4, 12, 12, 12,  8,  9])

In [29]: arr_slice = arr[5:8]

In [30]: arr_slice[:] = 100

In [31]: arr
Out[31]: array([  0,   1,   2,   3,   4, 100, 100, 100,   8,   9])

In [32]: arr_copy = arr[5:8]

In [33]: arr_copy = arr[5:8].copy()      #需要显式调用copy方法才能进行复制

In [34]: arr_copy[:] = 0

In [35]: arr
Out[35]: array([  0,   1,   2,   3,   4, 100, 100, 100,   8,   9])

In [36]: 

如上面展示的,NumPy数组和Python列表最重要的一点在于,数组切片是原始数组的视图,数据不会被复制,在视图上进行的任何修改都会直接反映到源数组上!

多维数组的索引和切片

上面展示的是简单的一维数组的索引和切片操作,NumPy对于多维数组一样可以有强大的索引和切片处理能力

In [41]: arr2d = np.array([[1,2,3], [4,5,6], [7,8,9]])

In [42]: arr2d[2]             #索引二维数组的第2+1个元素(二维数组的元素是一维数组)
Out[42]: array([7, 8, 9])

In [43]: arr2d[0:1][0:1]
Out[43]: array([[1, 2, 3]])

In [44]: arr2d[0:1]
Out[44]: array([[1, 2, 3]])

In [45]: arr2d[0:1][1]         #二维数组的切片还是二维数组,[0:1]返回第一个一维数组组成的二维数组,所以索引第1+1个元素是会越界!
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-45-94b2938bdf41> in <module>()
----> 1 arr2d[0:1][1]

IndexError: index 1 is out of bounds for axis 0 with size 1

In [46]: arr2d[0:1][0]
Out[46]: array([1, 2, 3])

In [47]: arr2d[0, 0]
Out[47]: 1

In [48]: arr2d(0, 0)           #多维数组可以用[][]方式或[,]方式索引,但不能用(,)方式索引
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-48-15328b8da55e> in <module>()
----> 1 arr2d(0, 0)

TypeError: 'numpy.ndarray' object is not callable

In [49]: arr2d[0][0]
Out[49]: 1

In [50]: 

布尔型索引

In [51]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])

In [52]: data = randn(7, 4)

In [53]: data
Out[53]: 
array([[ 0.30341255, -1.43786822,  0.51386794,  2.01463296],
       [ 0.45776171,  0.63107429, -0.20841062,  0.1145517 ],
       [-0.32747699,  0.65925079, -1.06944072,  1.27271423],
       [-0.23601401,  0.8338013 ,  0.38926236, -0.57237709],
       [ 0.53513341,  0.32432814,  1.14976993,  1.43381031],
       [-1.30097774,  0.39242246,  0.71094322, -1.44163325],
       [ 2.13316269, -0.51699895, -0.9965801 , -2.11189562]])

In [54]: names == 'Bob'     #逐个元素和'Bob'比较,然后返回对应布尔型数组
Out[54]: array([ True, False, False,  True, False, False, False], dtype=bool)

In [55]: data(names == 'Bob')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-55-b9de96c81a44> in <module>()
----> 1 data(names == 'Bob')

TypeError: 'numpy.ndarray' object is not callable

In [56]: data[names == 'Bob']   #这个布尔型数组可以用于数组索引
Out[56]: 
array([[ 0.30341255, -1.43786822,  0.51386794,  2.01463296],
       [-0.23601401,  0.8338013 ,  0.38926236, -0.57237709]])

In [57]: 

通过布尔型索引选取数组中的数据,将总是创建数据的副本,即使返回一模一样的数组也是如此!

通过布尔型数组设置值也是一种常用的手段,比如为了将data中所有负值都设置为0

In [57]: data[data<0] = 0

In [58]: data
Out[58]: 
array([[ 0.30341255,  0.        ,  0.51386794,  2.01463296],
       [ 0.45776171,  0.63107429,  0.        ,  0.1145517 ],
       [ 0.        ,  0.65925079,  0.        ,  1.27271423],
       [ 0.        ,  0.8338013 ,  0.38926236,  0.        ],
       [ 0.53513341,  0.32432814,  1.14976993,  1.43381031],
       [ 0.        ,  0.39242246,  0.71094322,  0.        ],
       [ 2.13316269,  0.        ,  0.        ,  0.        ]])

In [59]: 

数组转置和轴对称

转置是重塑的一种特殊形式,它返回的是源数组的视图(不会进行任何复制操作)。数组不仅有transpose方法,还有一个特殊的T属性

In [63]: arr = np.arange(15).reshape(3, 5)

In [64]: arr
Out[64]: 
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])

In [65]: arr.T     #将n*m数组,转换成m*n的数组
Out[65]: 
array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])

In [66]: #对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置

In [67]: arr = np.arange(16).reshape((2, 2, 4))

In [68]: arr
Out[68]: 
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])

In [69]: arr.transpose((1, 0, 2))
Out[69]: 
array([[[ 0,  1,  2,  3],
        [ 8,  9, 10, 11]],

       [[ 4,  5,  6,  7],
        [12, 13, 14, 15]]])

In [70]: 

数学统计方法

可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算


In [72]: arr = np.random.randn(5,4)    #正态分布的数据

In [73]: arr
Out[73]: 
array([[-0.59250139,  0.12466126, -0.1702676 , -0.80115236],
       [-0.26961734, -1.633394  ,  3.09014231, -0.2687443 ],
       [-0.36700777,  0.2781225 ,  0.96019219, -0.05058344],
       [-0.3948132 ,  0.9610283 ,  0.33650401, -0.33880641],
       [-0.51621862, -0.18786832, -1.12316188,  1.29032237]])

In [74]: arr.mean()
Out[74]: 0.016341814740152595

In [75]: arr.sum()
Out[75]: 0.32683629480305187

In [76]: #mean、sum这类函数可以接受一个axis参数(用于计算该轴上的统计值),最终结果是一个少一维的数组

In [77]: arr.sum(axis=0)
Out[77]: array([-2.14015832, -0.45745027,  3.09340904, -0.16896415])

In [78]: arr.sum(axis=1)
Out[78]: array([-1.43926009,  0.91838667,  0.82072348,  0.56391269, -0.53692645])

In [79]: 

线性代数

线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分。不像某些语言(如MATLAB),NumPy通过*对两个二维数据相乘的到的是一个元素级的积,而不是一个矩阵点积。因此NumPy提供了一个用于矩阵乘法的dot函数

In [59]: x = np.array([[1.,2.,3.], [4.,5.,6.]])

In [60]: y = np.array([[6.,23.], [-1,7], [8,9]])

In [61]: x * y
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-61-ae1a0a4af750> in <module>()
----> 1 x * y

ValueError: operands could not be broadcast together with shapes (2,3) (3,2) 

In [62]: x.dot(y)    # 相当于np.dot(x, y)
Out[62]: 
array([[  28.,   64.],
       [  67.,  181.]])

In [63]: 

numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西。它们跟MATLAB、R等使用的是相同的行业标准级Fortran库,如BLAS、LAPACK、Intel MKL等

In [78]: from numpy.linalg import inv, qr

In [79]: X = randn(5 ,5)

In [80]: X
Out[80]: 
array([[-0.14144535,  0.45849126, -0.34373254,  0.63356198, -0.73268259],
       [ 0.81191645,  0.52261554,  0.51015228, -1.70384258,  0.70096684],
       [-0.90576872,  0.11721096,  0.07271892, -0.8040043 , -0.28687098],
       [-0.25979703, -0.16373675, -1.16902877, -0.83699982, -0.26541393],
       [-0.95002043,  0.9567228 ,  0.39812217,  0.42536089,  0.38465774]])

In [81]: mat = X.T.dot(X)

In [82]: mat
Out[82]: 
array([[  2.46966541e+00,  -6.13065209e-01,   3.22439888e-01,  -9.31401749e-01,   6.36120860e-01],
       [ -6.13065209e-01,   1.43920789e+00,   6.89844126e-01,  -1.50209995e-01,   4.08252020e-01],
       [  3.22439888e-01,   6.89844126e-01,   1.90882497e+00,   2.36110591e-03,   1.05200302e+00],
       [ -9.31401749e-01,  -1.50209995e-01,   2.36110591e-03,   4.83240381e+00,  -1.04212171e+00],
       [  6.36120860e-01,   4.08252020e-01,   1.05200302e+00,  -1.04212171e+00,   1.32887937e+00]])

In [83]: inv(mat)
Out[83]: 
array([[ 0.61487693,  0.39248176, -0.08126038,  0.06637213, -0.29853237],
       [ 0.39248176,  1.09430831, -0.36089059,  0.07032809, -0.18321527],
       [-0.08126038, -0.36089059,  1.22824104, -0.24656713, -1.01592405],
       [ 0.06637213,  0.07032809, -0.24656713,  0.3040365 ,  0.38024529],
       [-0.29853237, -0.18321527, -1.01592405,  0.38024529,  2.0541501 ]])

In [84]: mat.dot(inv(mat))
Out[84]: 
array([[  1.00000000e+00,  -7.29712860e-18,  -1.55056563e-16,  -5.06033840e-17,   0.00000000e+00],
       [ -6.67949404e-17,   1.00000000e+00,   7.45968624e-17,   2.96910970e-17,   1.11022302e-16],
       [  5.78734683e-17,   1.40591876e-16,   1.00000000e+00,   1.87616286e-17,   4.44089210e-16],
       [  1.16775222e-16,   4.41256102e-17,  -3.88375596e-16,   1.00000000e+00,   0.00000000e+00],
       [ -1.11022302e-16,   2.77555756e-17,   0.00000000e+00,   0.00000000e+00,   1.00000000e+00]])

In [85]: q, r = qr(mat)

In [86]: r
Out[86]: 
array([[-2.80200333,  0.63324603, -0.59096377,  2.63069836, -1.2405018 ],
       [ 0.        , -1.64659814, -1.77049713,  1.49444329, -1.46234326],
       [ 0.        ,  0.        , -1.35877991, -1.99900954, -0.41816119],
       [ 0.        ,  0.        ,  0.        , -3.48973471,  0.70471367],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.42567029]])

In [87]: 

些许思考

NumPy的这些用法上的东西多练练就熟悉了,最重要的不是求这些术,而是要去求道:NumPy背后隐含的统计、概率、线性代数、数学的知识和原理才是重中之重!




如果本篇文章对您有所帮助,您可以通过微信(左)或支付宝(右)对作者进行打赏!


上一篇     下一篇