《用Python进行科学计算》——NumPy

Python中的列表可以当做数组使用,不过由于列表可以是任何对象,因此列表中所保存的是对象的指针。这样保存一个简单的[1,2,3]需要3个指针和三个整数对象。对于数值运算来说这种结构显然比较浪费内存和CPU计算时间。
Python还提供了一种array模块,array对象和列表不同,它直接保存数值,和C语言中的一维数组比较类似。但是它不支持多维,也没有各种运算函数,因此不适合做数值运算。
NumPy的诞生弥补了这些不足,NumPy提供了两种基本的对象:ndarray和ufunc。ndarray是存储单一数据类型的多维数组,而ufunc则是能够对数组进行处理的函数。

ndarray对象

导入

In [1]: import numpy as np

创建数组

In [1]: import numpy as np

In [2]: a=np.array([1,2,3,4])

In [3]: b=np.array([5,6,7,8])

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

In [6]: b
Out[6]: array([5, 6, 7, 8])

In [7]: c
Out[7]: 
array([[ 1,  2,  3,  4],
       [ 4,  5,  6,  7],
       [ 7,  8,  9, 10]])

In [8]: c.dtype
Out[8]: dtype('int64')

获取数组的大小

In [9]: a.shape
Out[9]: (4,)

In [10]: c.shape
Out[10]: (3, 4)

数组shape只有一个元素,因此它是一维数组,而数组c的shape有两个元素,因此它是二维数组。其中第0轴的长度为3,第一轴长度为4,还可以在保持数组元素个数不变的情况下,改变数组每个轴的长度。注意不是转置,只是改变每个轴的大小,数组元素在内存中的位置并没有改变:

In [12]: c.shape=4,3

In [13]: c
Out[13]: 
array([[ 1,  2,  3],
       [ 4,  4,  5],
       [ 6,  7,  7],
       [ 8,  9, 10]])

当某个轴的元素为-1时,将根据数组元素的个数自动计算此轴的长度。

In [14]: c.shape=2,-1

In [15]: c
Out[15]: 
array([[ 1,  2,  3,  4,  4,  5],
       [ 6,  7,  7,  8,  9, 10]])

使用数组的reshape方法,可以创建一个改变了尺寸的新数组,原数组的shape保持不变:

In [29]: d=a.reshape((2,2))

In [30]: d
Out[30]: 
array([[1, 2],
       [3, 4]])

In [31]: a
Out[31]: array([1, 2, 3, 4])

数组a和d器是共享数据存储内存区域,因此修改其中任意一个数组的元素都会同时修改另一个数组的内容。

In [32]: a[1]=100

In [33]: d
Out[33]: 
array([[  1, 100],
       [  3,   4]])

数组元素类型可以通过dtype属性获得。也可以通过dtype参数在创建时指定元素类型。

In [2]: np.array([[1,2,3,4],[4,5,6,7],[7,8,9,10]],dtype=np.float)
Out[2]: 
array([[ 1.,  2.,  3.,  4.],
       [ 4.,  5.,  6.,  7.],
       [ 7.,  8.,  9., 10.]])

In [3]: np.array([[1,2,3,4],[4,5,6,7],[7,8,9,10]],dtype=np.complex)
Out[3]: 
array([[ 1.+0.j,  2.+0.j,  3.+0.j,  4.+0.j],
       [ 4.+0.j,  5.+0.j,  6.+0.j,  7.+0.j],
       [ 7.+0.j,  8.+0.j,  9.+0.j, 10.+0.j]])

上面的例子都是先创建一个Python序列,然后通过array函数将其转换成数组这样做显然效率不高。因此NumPy提供了很多专门用来创建数组的函数。

  • arange函数类似于python的range函数,通过指定开始值、终值和步长来创建一维数组,注意数组不包括终值。
In [4]: np.arange(0,1,0.1)
Out[4]: array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])

  • linspace函数通过指定开始值、终值和元素个数来创建一维数组,可以通过endpoint关键字指定是否包含终值、缺省设置是包括终值。
In [5]: np.linspace(0,1,12)
Out[5]: 
array([0.        , 0.09090909, 0.18181818, 0.27272727, 0.36363636,
       0.45454545, 0.54545455, 0.63636364, 0.72727273, 0.81818182,
       0.90909091, 1.        ])
  • logspace和linspace类似,不过它创建等比数列。
In [6]: np.logspace(0,2,20)
Out[6]: 
array([  1.        ,   1.27427499,   1.62377674,   2.06913808,
         2.6366509 ,   3.35981829,   4.2813324 ,   5.45559478,
         6.95192796,   8.8586679 ,  11.28837892,  14.38449888,
        18.32980711,  23.35721469,  29.76351442,  37.92690191,
        48.32930239,  61.58482111,  78.47599704, 100.        ])

此外,使用frombuffer、fromsring、fromfile等函数可以从字节序列创建数组。
Python的字符串实际上是字节序列,每个字占一个字节。因此如果从字符串s创建一个8bit的整数数组的话,所得到的数组正好就是字符串中每个字符的ASSII码

扫描二维码关注公众号,回复: 3449238 查看本文章
In [4]: import numpy as np

In [5]: s="abcdefgh"

In [6]: np.fromstring(s,dtype=np.int8)
/home/kiosk/anaconda2/envs/DataAnalysis/bin/ipython:1: DeprecationWarning: The binary mode of fromstring is deprecated, as it behaves surprisingly on unicode inputs. Use frombuffer instead
  #!/home/kiosk/anaconda2/envs/DataAnalysis/bin/python
Out[6]: array([ 97,  98,  99, 100, 101, 102, 103, 104], dtype=int8)

如果从字符串s创建16bit的整数组,那么两个相邻的字节就表示一个整数,把字节98和字节97当作一个16位的整数,它的值就是98*256+97=25185.

In [7]: np.fromstring(s,dtype=np.int16)
/home/kiosk/anaconda2/envs/DataAnalysis/bin/ipython:1: DeprecationWarning: The binary mode of fromstring is deprecated, as it behaves surprisingly on unicode inputs. Use frombuffer instead
  #!/home/kiosk/anaconda2/envs/DataAnalysis/bin/python
Out[7]: array([25185, 25699, 26213, 26727], dtype=int16)

把整个字符转换成为一个64位的双精度浮点数数组。

In [8]: np.fromstring(s,dtype=np.float)
/home/kiosk/anaconda2/envs/DataAnalysis/bin/ipython:1: DeprecationWarning: The binary mode of fromstring is deprecated, as it behaves surprisingly on unicode inputs. Use frombuffer instead
  #!/home/kiosk/anaconda2/envs/DataAnalysis/bin/python
Out[8]: array([8.54088322e+194])

存取元素
和python中的列表存取方法类似,支持索引、切片。不同的是,通过下标范围获取的数组是原始数组的一个视图。它与原始数组共享一块数据空间。

In [9]: a=np.arange(10)

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

In [11]: b=a[3:7]

In [12]: b
Out[12]: array([3, 4, 5, 6])

In [13]: b[2]=100

In [14]: a
Out[14]: array([  0,   1,   2,   3,   4, 100,   6,   7,   8,   9])

使用整数序列
使用整数序列对数组进行存取时,将使用整数序列中的每个元素作为下标。

In [23]: x=np.arange(10,1,-1)

In [24]: x
Out[24]: array([10,  9,  8,  7,  6,  5,  4,  3,  2])

In [25]: x[[3,4,3,5]]
Out[25]: array([7, 6, 7, 5])

In [26]: b=x[np.array([3,3,-3,8])]

In [27]: b
Out[27]: array([7, 7, 4, 2])

In [28]: b[2]=100

In [29]: b
Out[29]: array([  7,   7, 100,   2])

In [30]: x
Out[30]: array([10,  9,  8,  7,  6,  5,  4,  3,  2])

In [31]: #由于b和x不共享数据空间,因此x中的值并不改变;

In [32]: x[[3,5,1]]=-1,-2,-3

In [33]: x
Out[33]: array([10, -3,  8, -1,  6, -2,  4,  3,  2])

In [34]: #整数序列下标也可以用来修改元素的值

使用布尔数组
当使用布尔数组b作为下标存取数组x中的元素时,将收集数组x中所有在数组b中对应下标为True的元素。

In [35]: x=np.arange(5,0,-1)

In [36]: x
Out[36]: array([5, 4, 3, 2, 1])

In [37]: x[np.array([True,False,True,False,False])]
Out[37]: array([5, 3])

布尔值一般不是手工产生的,而是使用布尔运算的ufunc函数产生。

In [45]: x=np.random.rand(10)  #产生一个长度为10,元素值为0-1的随机数数组

In [46]: x
Out[46]: 
array([0.64943044, 0.11683809, 0.2144533 , 0.44342287, 0.55800646,
       0.32401408, 0.51980074, 0.88956549, 0.31523517, 0.04540783])

#大于0.5的返回True
In [47]: x>0.5
Out[47]: 
array([ True, False, False, False,  True, False,  True,  True, False,
       False])

#返回x中大于0.5的元素
In [48]: x[[x>0.5]]
Out[48]: array([0.64943044, 0.55800646, 0.51980074, 0.88956549])

多维数组

NumPy采用元组作为数组得下标。

创建数组

In [49]: np.arange(0,60,10).reshape(-1,1) + np.arange(0,6)
Out[49]: 
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])
      

多维数组同样也可以用整数序列和布尔数组进行存取。

In [57]: a=np.arange(0,60,10).reshape(-1,1) + np.arange(0,6)

In [58]: a
Out[58]: 
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])

In [59]: a[(0,1,2,3,4),(1,2,3,4,5)]
Out[59]: array([ 1, 12, 23, 34, 45])

In [60]: #取a[0,1],a[1,2]...a[4,5]

In [62]: a[3:,[0,1,2]]
Out[62]: 
array([[30, 31, 32],
       [40, 41, 42],
       [50, 51, 52]])

In [63]: #取第三行以后,每行的第0,1,2列

In [65]: mask=np.array([1,0,1,0,0,1],dtype=np.bool)

In [66]: a[mask,2]
Out[66]: array([ 2, 22, 52])

In [67]: #取第0,2,5行的第二列

结构数组

在C语言中我们可以通过struct关键字定义结构类型,结构中的字段占据连续的内存空间,每个结构体占用的内存大小都相同,因此可以很容易的定义结构数组。和C语言一样,在NumPy中也很容易对这种结构数组进行操作。只要NumPy中的结构定义和C语言种的定义相同,NumPy
就可以很方便的读取C语言的结构数组的二进制数据,转换为Numpy
的结构数据。
假设我们需要定义一个结构数组。

import numpy as np


persontype=np.dtype({
    'names':['name','age','weight'],
    'formats':['S32','i','f']
})

a=np.array([("Zhang",32,75.5),("Wang",24,65.2)],dtype=persontype)

我们先创建一个dtype对象persontype,通过其字典参数描述结构类型的各字段。字典有两个关键字:names,formats。每个关键字对应的值都是一个列表。names定义结构中的每个字段名,而formats则定义每个字段的类型:

  • S32:32个字节的字符串类型,由于结构中的每个元素的大小必须固定,因此需要指定字符串的长度。
  • i:32bit的整数类型,相当于np.int32;
  • f:32bit的单精度浮点数类型,相当于np.float32。

然后我们调用array函数创建数组,通过关键字参数dtype=persontype,指定所创建的数组的元素类型为结构persontype。

In [5]: a.dtype
Out[5]: dtype([('name', 'S32'), ('age', '<i4'), ('weight', '<f4')])

这里看到了另一个描述结构类型的方法:一个包含多个元组的列表,类型描述前面为我们添加了‘|’,‘<’等字符,这些字符用来描述字段值的字节顺序。

  • |:忽视字节顺序;
  • <:低位字节在前;
  • :高位字节在前。
    结构数组地存取方式和一般数组相同,通过下标能够取的其中的元素,看上去像是元组,实际上它是一个结构:

In [5]: a[0]
Out[5]: (b'Zhang', 32, 75.5)

In [6]: a[0].dtype
Out[6]: dtype([('name', 'S32'), ('age', '<i4'), ('weight', '<f4')])

a[0]是一个结构元素,它和数组a共享内存数据,因此可以通过它改变它的字段,改变原始数组中的对应字段:

In [7]: c=a[1]

In [8]: c
Out[8]: (b'Wang', 24, 65.2)

In [9]: c["name"]="Li"

In [10]: a[1]["name"]
Out[10]: b'Li'

我们不但可以获得结构元素的某个字段,还可以直接获得结构数组的字段,它返回的是原始数组的视图,因此可以通过修改b[0]改变a[0][“age”]:

In [14]: b=a[:]["age"]

In [15]: b
Out[15]: array([32, 24], dtype=int32)

In [16]: b[0]=40

In [17]: a[0]["age"]
Out[17]: 40

结构类型中可以包括其它的结构类型,下面的语句创建一个有一个字段f1的结构,f1的值是另一个结构,它有字段f2,其类型为16bit整数。

In [20]: np.dtype([('f1',[('f2',np.int16)])])
Out[20]: dtype([('f1', [('f2', '<i2')])])

当某个字段类型为数组时,用元组的第三个参数表示,下面描述的f1字段是一个shape为(2,3)的双精度浮点数组。

In [21]: np.dtype([('f0','i4'),('f1','f8',(2,3))])
Out[21]: dtype([('f0', '<i4'), ('f1', '<f8', (2, 3))])

用下面的字典参数也可以定义结构类型,字典的关键字为结构中字段名,值为字段的类型描述,但是由于字典的关键字是没有顺序的,因此字段的顺序需要在类型描述中给出,类型描述是一个组元,它的第二个值给出字段的字节为单位的偏移量。

In [22]: np.dtype({'surname':('S25',0),'age':(np.uint8,25)})
Out[22]: dtype([('surname', 'S25'), ('age', 'u1')])

内存结构

关于数组的描述信息保存在一个数据结构中,这个结构引用两个对象:一块用于保存数据的存储区域和一个用于描述元素的类型的dtype对象。
数据存储区域保存着数组中所有元素的二进制数据,dtype对象则知道如何将元素的二进制数据转换成可用的值。

>>> a = np.array([[0,1,2],[3,4,5],[6,7,8]], dtype=np.float32)

在这里插入图片描述

stides中保存的是当每个轴的下标增加1时,数据存储区中的指针所增加的字节数。比如图中的stidies为12,4,即第0轴得下标增加1,数据的地址增加12个字节,正好是3个单精度浮点数的总字节数;第1轴下标增加1时,数据地址增加4个字节,正好是单精度浮点数的字节数。
如果strides中的数值正好和对应轴所占据的字节数相同的话,那么数据在内存中是连续存储的。然而并不一定都是连续存储的,比如原始数组的视图和它共享数据存储区域:

In [4]: b=a[::2,::2]

In [5]: b
Out[5]: 
array([[0., 2.],
       [6., 8.]], dtype=float32)

In [6]: b.strides
Out[6]: (24, 8)

由于数组b和数组a共享数据存储区,而b中的第0轴和第一轴都是数组a中隔一个元素取一个,因此数组b的strides变成了24,8。正好是数组a的两倍。
元素在数据存储区中的排列格式有两种:C语言格式和Fortan语言格式。在C语言中,多维数组的第0轴是最上位的,即第0轴的下标增加1时,元素的地址增加的字节数最多。而Fortan语言的多维数组的第0轴是最下位的,第0轴得下标增加1时,地址只增加一个元素的字节数。在Numpy中,元素在内存中的排列缺省是以C语言格式存储的,如果想要改成Fortan格式,只需要给数组传递order=“F”:

>>> c = np.array([[0,1,2],[3,4,5],[6,7,8]], dtype=np.float32, order="F")
>>> c.strides
(4, 12)

ufunc运算


ufunc是universal function的缩写,它是一种能对数组的每个元素进行操作的函数。NumPy内置得许多ufuc函数都是在C语言级别实现的,因此它们的计算速度非常快。

In [7]: x=np.linspace(0,2*np.pi,10) #对数组x的每个元素进行正弦计算

In [8]: y=np.sin(x)

In [9]: y
Out[9]: 
array([ 0.00000000e+00,  6.42787610e-01,  9.84807753e-01,  8.66025404e-01,
        3.42020143e-01, -3.42020143e-01, -8.66025404e-01, -9.84807753e-01,
       -6.42787610e-01, -2.44929360e-16])

计算之后x中的值并没有改变,而是新创建了一个数组保存结果。
如果希望将sin函数所计算的结果直接覆盖到数组x上去的话,可以将要被覆盖的数组作为第二个参数传递给ufuc函数。

In [10]: t=np.sin(x,x)

In [11]: x
Out[11]: 
array([ 0.00000000e+00,  6.42787610e-01,  9.84807753e-01,  8.66025404e-01,
        3.42020143e-01, -3.42020143e-01, -8.66025404e-01, -9.84807753e-01,
       -6.42787610e-01, -2.44929360e-16])

In [12]: id(t)==id(x)
Out[12]: True

它所做的事就是对x中的每个值求正弦值,并且把结果保存到x中的对应位置中。
NumPy中有众多的ufunc函数为我们提供各式各样的计算。除了sin这种单输入函数之外,还有许多多个输入的函数,add函数就是一个最常用的例子。

In [13]: a=np.arange(0,4)

In [14]: a
Out[14]: array([0, 1, 2, 3])

In [15]: b=np.arange(1,5)

In [16]: b
Out[16]: array([1, 2, 3, 4])

In [17]: np.add(a,b)
Out[17]: array([1, 3, 5, 7])

In [18]: np.add(a,b,a)
Out[18]: array([1, 3, 5, 7])

In [19]: a
Out[19]: array([1, 3, 5, 7])

add函数返回一个新的数组。此数组的每个元素都为两个参数数组的对应元素之和。它接收第3个参数指定计算结果所要写入的数组,如果指定的话。add函数就不再产生新的数组。
下面是数组的运算符和其对应的ufunc函数的一个列表:

python ufunc
y=x1+x2 add(x1,x2[,y])
y=x1-x2 subtract(x1,x2[,y])
y=x1*x2 multiply(x1,x2[,y])
y=x1/x2 divide(x1,x2[,y])
y=x1/x2 true divide(x1,x2[,y])返回精确的商
y=x1//x2 floor divide(x1,x2[,y]),总是对返回值取整
y=-x negative(x,[,y])
y=x1**x2 power(x1,x2[,y])
y=x1%x2 remainder(x1,x2[,y]),mod(x1,x2,[,y])

广播

当我们使用ufunc函数对两个数组进行计算时,ufunc函数会对这两个数组的对应元素进行计算,因此它要求这两个数组有相同的大小。如果两数组的shape不同的话,会进行如下的广播处理:

  • 让所有输入数组都向其中shape最长的数组看齐。shape中不足的部分都通过在前面加1补齐;
  • 输出数组的shape是输入数组shape的各个轴的最大值;
  • 如果输入数字的某个轴和输出数组的对应轴的长度相同或者其长度为1时。这个数组能够用来计算,否则出错。
  • 当输入数组的某个轴的长度为1时,沿着此轴运算时都用此轴上的第一组值。
In [20]: #先创建一个二维数组a,其shape为(6,1)
In [27]: a=np.arange(0,60,10).reshape(-1,1)

In [28]: a
Out[28]: 
array([[ 0],
       [10],
       [20],
       [30],
       [40],
       [50]])

In [29]: a.shape
Out[29]: (6, 1)

In [30]: #在创建一维数组b,其shape为(5,)

In [31]: b=np.arange(0,5)

In [32]: b
Out[32]: array([0, 1, 2, 3, 4])

In [33]: b.shape
Out[33]: (5,)
In [34]: #计算a和b的和

In [35]: c=a+b

In [36]: c
Out[36]: 
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24],
       [30, 31, 32, 33, 34],
       [40, 41, 42, 43, 44],
       [50, 51, 52, 53, 54]])

由于a和b的shape长度不同,根据规则1,需要让b的shape向a对齐,与是将b的shape前面加1,补齐为(1,5)。相当于:

In [38]: b.shape=1,5

In [39]: b
Out[39]: array([[0, 1, 2, 3, 4]])

这样加法运算的两个输入数组的shape分别为(6,1)和(1,5),根据规则2,输出数组的各个轴长度为输入数组各个轴的长度的最大值,可知输出数组的shape为(6,5)。

由于b的第0轴上的长度为1,而a的第0轴的长度为6,因此为了让他们在第0轴上能够向加,需要将b在第0轴上的长度扩展为6,相当于:

In [40]: b=b.repeat(6,axis=0)

In [41]: b
Out[41]: 
array([[0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4]])

由于a的第一轴的长度为1,而b的第一轴的长度为5,因此为了让他们在第一轴上能够相加,需要将a在第一轴上的长度扩展为5,相当于:

In [42]: a=a.repeat(5,axis=1)

In [43]: a
Out[43]: 
array([[ 0,  0,  0,  0,  0],
       [10, 10, 10, 10, 10],
       [20, 20, 20, 20, 20],
       [30, 30, 30, 30, 30],
       [40, 40, 40, 40, 40],
       [50, 50, 50, 50, 50]])


但是numpy在执行a+b运算的时候,其内部不会真正将长度为1的轴用repeat函数进行扩展,这样太浪费空间了。
快速产生a,b数组

In [44]: x,y=np.ogrid[0:5,0:5]

In [45]: x
Out[45]: 
array([[0],
       [1],
       [2],
       [3],
       [4]])

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

ogrid像一个多维数组一样,用切片组元作为下表进行存取,返回的是一组可以用来广播计算的数组。切片方式:

  • 开始值:结束值:步长,和np.arange类似;
  • 开始值:结束值:长度j,当第三个存参数为虚数时,它表示返回的数组长度。
In [47]: x,y=np.ogrid[0:1:4j,0:1:3j]

In [48]: x
Out[48]: 
array([[0.        ],
       [0.33333333],
       [0.66666667],
       [1.        ]])

In [49]: y
Out[49]: array([[0. , 0.5, 1. ]])

ufunc的方法
ufunc函数本身还有些方法,这些方法只对两个输入一个输出的ufunc函数有效,其他的ufuc对象调用这些方法时抛出ValueError异常。

reduce:方法和Python的reduce函数类似,它沿着axis轴对array进行操作,相当将运算符插入到沿axis轴的所有子数组或者元素当中。

In [2]: np.add.reduce([1,2,3])
Out[2]: 6

In [3]: np.add.reduce([[1,2,3],[4,5,6]],axis=1)
Out[3]: array([ 6, 15])

accumulate:和reduce方法类似,只是它返回的数组和输入的数组的shape相同,保存所有中间计算结果:

In [4]: np.add.accumulate([1,2,3])
Out[4]: array([1, 3, 6])

In [5]: np.add.accumulate([[1,2,3],[4,5,6]],axis=1)
Out[5]: 
array([[ 1,  3,  6],
       [ 4,  9, 15]])
       
In [6]: np.add.accumulate([[1,2,3],[4,5,6]],axis=0)
Out[6]: 
array([[1, 2, 3],
       [5, 7, 9]])

reduceat:计算多组reduce的结果,通过indices参数制定一系列reduce的起始和终了位置。

In [11]: a=np.array([1,2,3,4])

In [12]: result=np.add.reduceat(a,indices=[0,1,0,2,0,3,0])

In [13]: result
Out[13]: array([ 1,  2,  3,  3,  6,  4, 10])

如果给出indices中元素前一个小于后一个则用reduce计算a中对应元素放入result。否则将a中索引对应元素放入result。

result[0]=a[0]=1
result[1]=a[1]=2
result[2]=a[0]+a[1]=3
result[3]=a[2]=3
result[4]=a[0]+a[1]+a[2]=6
result[5]=a[3]=4
result[6]=a[0]+a[1]+a[2]+a[3]=10

outer
计算等同于如下程序

>>> a.shape += (1,)*b.ndim
>>> <op>(a,b)
>>> a = a.squeeze()

其中squeeze的功能是剔除数组a中长度为1的轴。

In [2]: np.multiply.outer([1,2,3,4,5],[2,3,4])
Out[2]: 
array([[ 2,  3,  4],
       [ 4,  6,  8],
       [ 6,  9, 12],
       [ 8, 12, 16],
       [10, 15, 20]])

通过outer方法计算结果是乘法表:

  2,3,4
1
2
3
4
5

矩阵运算

NumPy和Matlab不一样,对于多维数组的运算,缺省情况下并不使用矩阵运算,如果你想进行矩阵运算的话,可以调用相应的函数。
matix对象:
numpy库提供了matrix类,使用matrix类创建的是矩阵对象,它们的加减乘除运算缺省采用矩阵方式计算。
矩阵的乘积可以使用dot函数进行计算。对于二维数组,它计算的是矩阵乘积,对于一维数组,他的计算是其点积。当需要将一维数组当作列矢量或者行矢量进行矩阵运算时,推荐使用reshape函数将一维数组转换为二维数组:

In [4]: a=np.array([1,2,3])

In [5]: a.reshape((-1,1))
Out[5]: 
array([[1],
       [2],
       [3]])

In [6]: a.reshape((1,-1))
Out[6]: array([[1, 2, 3]])

除了dot计算乘积之外,NumPy还提供了inner和outer等多种计算乘积的函数,这些函数计算乘积的方式不同,尤其是当对于多维数组的时候,更容易搞混。

  • dot:对于来嗯个一维数组,计算的是这两个数组对应下标元素的乘积和;对于二维数组,计算的是两个数组的矩阵乘积;对于多维数组,它的通用公式计算公式如下,结果数组中的每个元素都是:数组a的最后一维上的所有元素与数组b的倒数第二位上的所有元素乘积和。
dot(a,b)[i,j,k,m]=sum(a[i,j,:]*b[k,:,m])

创建两个3维数组,这两个数组的最后两维满足矩阵乘积的条件:

In [9]: a=np.arange(12).reshape(2,3,2)

In [10]: b=np.arange(12,24).reshape(2,2,3)

In [11]: c=np.dot(a,b)

In [12]: c
Out[12]: 
array([[[[ 15,  16,  17],
         [ 21,  22,  23]],

        [[ 69,  74,  79],
         [ 99, 104, 109]],

        [[123, 132, 141],
         [177, 186, 195]]],


       [[[177, 190, 203],
         [255, 268, 281]],

        [[231, 248, 265],
         [333, 350, 367]],

        [[285, 306, 327],
         [411, 432, 453]]]])

In [13]: np.alltrue(c[0,:,0,:])==np.dot(a[0],b[0])
Out[13]: 
array([[False, False, False],
       [False, False, False],
       [False, False, False]])

In [14]: np.alltrue(c[0,:,0,:]==np.dot(a[0],b[0]))
Out[14]: True

doc乘积的结果c可以看作数组a,b的多个子矩阵的乘积。

  • inner:和dot乘积一样,对于两个一维数组,计算的是这两个数组对应下标元素的乘积和;对于多维数组,它计算的结果数组中的每个元素都是:数组a和数组b的最后一维内积,因此数组a和b的最后一维的长度必须相同。
In [27]: a=np.arange(12).reshape(2,3,2)

In [28]: b=np.arange(12,24).reshape(2,3,2)

In [29]: a
Out[29]: 
array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5]],

       [[ 6,  7],
        [ 8,  9],
        [10, 11]]])

In [30]: b
Out[30]: 
array([[[12, 13],
        [14, 15],
        [16, 17]],

       [[18, 19],
        [20, 21],
        [22, 23]]])

In [31]: c=np.inner(a,b)

In [32]: c.shape
Out[32]: (2, 3, 2, 3)

In [33]: c
Out[33]: 
array([[[[ 13,  15,  17],
         [ 19,  21,  23]],

        [[ 63,  73,  83],
         [ 93, 103, 113]],

        [[113, 131, 149],
         [167, 185, 203]]],


       [[[163, 189, 215],
         [241, 267, 293]],

        [[213, 247, 281],
         [315, 349, 383]],

        [[263, 305, 347],
         [389, 431, 473]]]])
  • outer:只按照一维数组进行计算,如果传入参数是多维数组,则先将此数组展平为一维数组之后在进行计算。outer乘积计算的列向量和行向量的矩阵乘积:
In [34]: np.outer([1,2,3],[4,5,6,7])
Out[34]: 
array([[ 4,  5,  6,  7],
       [ 8, 10, 12, 14],
       [12, 15, 18, 21]])

矩阵中更高级一些的元算可以在NumPy的线性代数库linalg中找到。
solve函数可以求解多元一次方程组。

In [39]: a=np.random.rand(10,10)

In [40]: b=np.random.rand(10)

In [41]: x=np.linalg.solve(a,b)

In [42]: x
Out[42]: 
array([-0.9147439 , -0.09919866,  0.20089151, -0.39604682,  0.14503776,
        1.20763067,  1.26333672,  0.59108902,  0.00201528, -1.12964907])

In [43]: np.sum(np.abs(np.dot(a,x)-b))
Out[43]: 1.9984014443252818e-15

solve函数有两个参数a和b。a是一个N*N的二维数组,而b是一个长度维N的一维数组,solve函数找到一个长度为N的一维数组x,使得a和x的矩阵乘积正好等于b,数组x就是多元一次方程组的解。

文件存取

NumPy提供了多种文件操作函数方便我们存取数组内容。文家存取的格式分为两类:二进制和文本,二进制格式又分为NumPy专用的格式化二进制和无格式类型。
使用数组的方法函数tofile可以方便的将数组中数组以二进制的格式写进文件。tofile输出的数据没有格式,因此numpy.fromfile度回来的时候需要自己格式化数据:

In [57]: a=np.arange(0,12)

In [58]: a.shape=3,4

In [59]: a
Out[59]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [60]: a.tofile("a.bin")

In [61]: b=np.fromfile("a.bin",dtype=np.float)

In [62]: b
Out[62]: 
array([0.0e+000, 4.9e-324, 9.9e-324, 1.5e-323, 2.0e-323, 2.5e-323,
       3.0e-323, 3.5e-323, 4.0e-323, 4.4e-323, 4.9e-323, 5.4e-323])

In [63]: a.dtype
Out[63]: dtype('int64')

In [64]: b=np.fromfile("a.bin",dtype=np.int64) #按照int64类型读入数据

In [65]: b
Out[65]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

In [66]: b.shape=3,4

In [67]: b
Out[67]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

所以再读入的时候设置正确的dtype和shape才能保证数据一致。并且tofile函数不管数组的排列顺序是C语言格式的还是Fortran语言格式的,统一使用C语言格式输出。
此外,如果fromfile和tofile函数调用时指定了sep关键字参数的话,数组将以文本格式输入输出。

numpy.load和numpy.save函数以Numpy专用的二进制类型保存数据,这两个函数会自动处理元素类型和shape等信息,使用它们读写数组就方便多了。但是numpy.save输出的文件很难和其他语言编写的程序读入;

In [68]: np.save("a.npy",a)

In [69]: c=np.load("a.npy")

In [70]: c
Out[70]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

如果你想将多个数组保存到一个文件中的话,可以使用munpy.savez函数。这个函数第一个参数是文件名,其后的参数都是需要保存的数组,也可以使用关键字参数为数组起一个名字。非关键字参数传递的数组会自动起名为arr_0, arr_1, …。savez函数输出的是一个压缩文件,其中每个文件都是一个save函数保存的npy文件,文件名对应于数组名。load函数自动识别npz文件,并且返回一个类似于字典的对象,可以通过数组名作为关键字获取数组的内容。

>>> a = np.array([[1,2,3],[4,5,6]])
>>> b = np.arange(0, 1.0, 0.1)
>>> c = np.sin(b)
>>> np.savez("result.npz", a, b, sin_array = c)
>>> r = np.load("result.npz")
>>> r["arr_0"] # 数组a
array([[1, 2, 3],
       [4, 5, 6]])
>>> r["arr_1"] # 数组b
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9])
>>> r["sin_array"] # 数组c
array([ 0.        ,  0.09983342,  0.19866933,  0.29552021,  0.38941834,
        0.47942554,  0.56464247,  0.64421769,  0.71735609,  0.78332691])

如果你用解压软件打开result.npz文件的话会发现其中有三个文件:arr_0.npy, arr_1.npy, sin_array.npy,其中分别保存着数组a, b, c的内容。

使用numpy.savetxt和numpy.loadtxt可以读写1维和2维数组:

In [87]: a=np.arange(0,12,0.5).reshape(4,-1)

In [88]: np.savetxt("a.txt",a)

In [89]: np.loadtxt("a.txt")
Out[89]: 
array([[ 0. ,  0.5,  1. ,  1.5,  2. ,  2.5],
       [ 3. ,  3.5,  4. ,  4.5,  5. ,  5.5],
       [ 6. ,  6.5,  7. ,  7.5,  8. ,  8.5],
       [ 9. ,  9.5, 10. , 10.5, 11. , 11.5]])

In [90]: np.savetxt("a.txt",a,fmt="%d",delimiter=",") #改为保存整数,以
    ...: 逗号分开

In [91]: np.loadtxt("a.txt",delimiter=",")#读入的时候也需要指定逗号分隔
Out[91]: 
array([[ 0.,  0.,  1.,  1.,  2.,  2.],
       [ 3.,  3.,  4.,  4.,  5.,  5.],
       [ 6.,  6.,  7.,  7.,  8.,  8.],
       [ 9.,  9., 10., 10., 11., 11.]])

文件名和文件对象

本节介绍所举的例子都是传递的文件名,也可以传递已经打开的文件对象,例如对于load和save函数来说,如果使用文件对象的话,可以将多个数组储存到一个npy文件中

>>> a = np.arange(8)
>>> b = np.add.accumulate(a)
>>> c = a + b
>>> f = file("result.npy", "wb")
>>> np.save(f, a) # 顺序将a,b,c保存进文件对象f
>>> np.save(f, b)
>>> np.save(f, c)
>>> f.close()
>>> f = file("result.npy", "rb")
>>> np.load(f) # 顺序从文件对象f中读取内容
array([0, 1, 2, 3, 4, 5, 6, 7])
>>> np.load(f)
array([ 0,  1,  3,  6, 10, 15, 21, 28])
>>> np.load(f)
array([ 0,  2,  5,  9, 14, 20, 27, 35])

猜你喜欢

转载自blog.csdn.net/mashaokang1314/article/details/82748113