NumPy之一:基本操作

  NumPy的主要对象是同类多维数组,这是一个相同类型的元素(通常是数字)组成的表。在NumPy中,维度称为axis,axis的数量叫做rank

  例如,三维空间中的一个坐标为[1, 2, 1]的点,即为rank=1的数组,因为这个数组只有一个axis。有些人可能会有疑惑,不是说三维空间么,为什么说它只有一个axis呢?axis可以理解成轴,抛开这个点,单看这个数组[1, 2, 1],它确实只需要一个axis即可表示。这个axis长度为3。

  又如,[[ 1., 0., 0.],[ 0., 1., 2.]],这个数组的rank=2(2维的)。第一维(axis)长度为2,第二维长度为3。
  
  也可以这么理解,数组是由2个行向量组成,所以第一维长度为2,每个行向量由3个向量(点)组成,所以第二维长度为3。

  Numpy的数组类称为ndarray。numpy.array和Python标准库中的array.array是两回事,标准库中的array.array只能处理一维数组且只提供了少数的功能。ndarray对象中更加重要的特性如下:

  • ndarray.ndim
    数组的axis(维)的数量。在Python中,维的数量被称作rank。

  • ndarray.shape
    数组的各个维(注意和维和维数要区分开)。它是一个数组各个维的长度构成的整数元组。对n行m列矩阵而言,shape将是(n,m)。因此,shape元组的长度也就是rank,也是维数ndim。

  • ndarray.size
    数组所有元素总数量。等于shpe元组各元素的乘积。

  • ndarray.dtype
    一个描述数组中元素类型的对象。用户可以使用Python标准类型创建或指定dtype。此外,NumPy还提供了其自有的类型,比如numpy.int32, numpy.int16, numpy.float64。

  • ndarray.itemsize
    数组各元素的占多少字节。比如,一个元素类型是float64的数组,其itemsize为8(=64/8,64位除以8)。同理,元素类型是complex32的数组的itemsize为4(=32/8)。ndarray.itemsize等于ndarray.dtype.itemsize。

  • ndarray.data
    装载数组真实元素的缓冲区。通常,我们用不到这个属性,因为我们一般使用索引访问数组元素。

1. 一个例子

>>> 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'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<type 'numpy.ndarray'>

2. 创建数组

  有多种方法来创建数组。
  你可以利用常规的Python列表或元组同伙array函数来创建一个数组。创建出的数组类型和序列元素类型是一致的。

>>> import numpy as np
>>> a = np.array([2,3,4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')
>>> c = np.array(('a','b','c'))
>>> c
array(['a', 'b', 'c'],
      dtype='|S1')
>>> c.shape
(3,)
>>> c.size
3
>>> c.ndim
1
>>> c.dtype
dtype('S1')

  注意,array函数只接收一个参数,且这个参数是一个python序列。常见的错误是给array函数传递多个数字作为参数,如a = np.array(1,2,3,4)

  数组将序列组成的序列转化为二维数组,将序列组成的序列组成的序列转化为三维数组,如此等等。

>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> b
array([[ 1.5,  2. ,  3. ],
       [ 4. ,  5. ,  6. ]])

  数组类型也可以在创建的时候明确指定。

>>> c = np.array([[1, 2], [3, 4]], dtype=complex)
>>> c
array([[ 1.+0.j,  2.+0.j],
       [ 3.+0.j,  4.+0.j]])

  一般情况是,数组元素最初是未知的,但其size是已知的。因此,NumPy提供了一些函数,利用初始的占位符来创建数组。这些函数减少了费时费力的手动填充数组的成本。

  函数zeros可以创建一个全部填充0的数组;函数ones创建的数组全部填充为1;函数empty创建的函数的初始内容是随机的,依赖于内存状态。默认情况下,这些函数创建的数组的dtype是float64。

>>> a = np.zeros((3,4))
>>> a
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> a.dtype
dtype('float64')
>>> np.ones((2,3,4), dtype = np.int16)
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))
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

  Numpy提供了一个类似于range的函数,可以返回一列数字,用以代替Python序列来创建数组。

>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 )                 # it accepts float arguments
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

  当给arange函数传递的参数是浮点数时,由于浮点数精度的有限性,通常难以预测最终将获得什么样的数字。基于此,一般更好的方法是使用函数linspace,其接收一个我们想要的元素数量作为参数,而不是step:

>>> from numpy import pi
>>> np.linspace( 0, 2, 9 )   # 9 numbers from 0 to 2
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
>>> x = np.linspace( 0, 2*np.pi, 100 )  # useful to evaluate function at lots of points
>>> f = np.sin(x)

3. 打印数组

  打印数组时,NumPy通过类似嵌套列表的方式将数组展现出来,但会遵循以下层次布局:

  • 最后的那个axis从左往右依次打印
    比如数组有4维,那么从左到右打印的是第4维(第4个axis)

  • 倒数第二个axis从上到下打印

  • 其余的axis也从上到下打印,每个切片由一个空行分开
      
    基于此,一维数组将打印成一行;二维数组将打印成一个矩阵;三维数组将打印成一个矩阵列表。

>>> a = np.arange(6)                         # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4,3)           # 2d array
>>> print(b)
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2,3,4)         # 3d array
>>> print(c)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]
 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]

  如果数组太大难以打印,NumPy将自动跳过数组的中间部分,仅打印出周边少量数据。

>>> print(np.arange(10000))
[   0    1    2 ..., 9997 9998 9999]
>>>
>>> print(np.arange(10000).reshape(100,100))
[[   0    1    2 ...,   97   98   99]
 [ 100  101  102 ...,  197  198  199]
 [ 200  201  202 ...,  297  298  299]
 ...,
 [9700 9701 9702 ..., 9797 9798 9799]
 [9800 9801 9802 ..., 9897 9898 9899]
 [9900 9901 9902 ..., 9997 9998 9999]]

  如果不想省略中间部分,可以通过set_printoptions来强制NumPy打印所有数据。

>>> np.set_printoptions(threshold='nan')

4. 基本运算

  在数组上的算术运算作用于每个元素。运算结果将填充到一个新创建的数组中。

>>> 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])
>>> b**2
array([0, 1, 4, 9])
>>> 10*np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([ True, True, False, False], dtype=bool)

  不同于许多矩阵语言,在NumPy中,乘号”*”是两个数组中位置相同的元素相乘。矩阵乘法通过dot函数或方法来实现:

>>> A = np.array( [[1,1],
...             [0,1]] )
>>> B = np.array( [[2,0],
...             [3,4]] )
>>> A*B                         # elementwise product
array([[2, 0],
       [0, 4]])
>>> A.dot(B)                    # matrix product
array([[5, 4],
       [3, 4]])
>>> np.dot(A, B)                # another matrix product
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                  # b is not automatically converted to integer type
Traceback (most recent call last):
  ...
TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'

  当不同类型的数组之间进行运算时,结果数组的类型与更通用或更精确的数组的类型一致(称作向上转型)。

>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0,np.pi,3)
>>> b.dtype.name
'float64'
>>> c = a+b
>>> c
array([ 1.        ,  2.57079633,  4.14159265])
>>> c.dtype.name
'float64'
>>> d = np.exp(c*1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
       -0.54030231-0.84147098j])
>>> d.dtype.name
'complex128'

  许多一元运算,比如计算数组所有元素的总和,是通过ndarray类的方法来实现的。

>>> 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参数,可以将运算应用于指定的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)                            # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)                            # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # cumulative sum along each row
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])
>>> b.cumsum(axis = 0)
array([[ 0,  1,  2,  3],
       [ 4,  6,  8, 10],
       [12, 15, 18, 21]])

5. 通用函数

  NumPy提供了常见的数学函数,如sin,cos,exp等。在NumPy中,这些函数称为“通用函数”(ufunc)。这些函数作用于数组中每个元素,并产生一个新的结果数组。

>>> 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.])

6. 索引、切片和迭代

  一维数组可以像Python中的列表等一样被索引、切片和迭代。

>>> 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    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # reversed a
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...     print(i**(1/3.))
...
nan
1.0
nan
3.0
nan
5.0
6.0
7.0
8.0
9.0

  多维数组的每个axis都有一个索引。这些索引由一组逗号分隔的数字给出。

>>> def f(x,y):
...     return 10*x+y
...
>>> b = np.fromfunction(f,(5,4),dtype=int)
>>> 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]
23
>>> b[0:5, 1]                       # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # each column in the second and third row of b
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

  如果给出的索引少于axis的数量,那么缺失的索引被认为是整体切片:

>>> b[-1]                                  # the last row. Equivalent to b[-1,:]
array([40, 41, 42, 43])

  b[i]的括号中的表达式被解析成一个i后面跟着足够多的冒号“:”,其足以代表其余的axis。NumPy中还可以使用点“.”写成b[i,…]的形式。

  (…)表示足够的冒号来产生一个完整的索引元组。比如,如果x是一个5维数组(就是说它有5个axis),那么

  • x[1,2,…] 等价于 x[1,2,:,:,:]
  • x[…,3] 等价于 x[:,:,:,:,3]
  • x[4,…,5,:] 等价于 x[4,:,:,5,:]
>>> c = np.array( [[[  0,  1,  2],               # a 3D array (two stacked 2D arrays)
...                 [ 10, 12, 13]],
...                [[100,101,102],
...                 [110,112,113]]])
>>> c.shape
(2, 2, 3)
>>> c[1,...]                                   # same as c[1,:,:] or c[1]
array([[100, 101, 102],
       [110, 112, 113]])
>>> c[...,2]                                   # same as c[:,:,2]
array([[  2,  13],
       [102, 113]])

  对多维数组迭代是就第一个axis而言的:

>>> for row in b:
...     print(row)
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]

  不过,如果想要对数组中每个元素执行一项操作,可以使用flat属性,它是一个针对数组所有元素的迭代器:

>>> for element in b.flat:
...     print(element)
...
0
1
2
3
10
11
12
13
20
21
22
23
30
31
32
33
40
41
42
43
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值