NumPy的入门知识和基本操作

基础

NumPy提供的最主要的对象就是同数据类型的多维数组(homogeneous multidimensional array)。 他实际上可以被理解成一张表(元素通常是数字),所有元素具有相同的类型,这些元素可以使用由正整数构建的元组(tuple)来索引。 在NumPy中,各个维度被称为轴(axes)。轴的总数被称为秩(rank)(后面对于轴、秩这种术语,我们会使用它们的英文名字,毕竟我们在写代码的时候也用的是英文)。

举例来说,三维空间中的一个点的坐标是[1, 2, 1],它就是一个rank是1的一个数组。因为它只有一axis,而这个axis的长度是3。
再来看一个例子:

[[1., 0., 0.],
 [0., 1., 2.]]

该数组的rank是2(两个维度),第一个维度的长度是2(2行),第二个维度的长度是3(3列)。

NumPy的数组类叫做ndarray。同时也有个别名array。 这里需要注意一下,numpy.array和Python标准库的array.array完全不是一个东西,后者仅仅支持一维数组且提供的功能较少。 ndarray对象比较重要的属性有:

ndarray.ndim
数组的axes的数目。

ndarray.shape
数组的形状。如一个n * m的矩阵,它的shape就是(n, m)。而且len(ndarray.shape)==ndarray.ndim

ndarray.size
数组中所有元素的个数。它实际上是ndarray.shape中所有值的乘积。

ndarray.dtype
用于描述数组中元素类型的对象。我们可以使用Python标准类型来创建或指定dtype。不过NumPy也提供了一些它自己的类型,比如:numpy.int32, numpy.int16, numpy.float64等。

ndarray.itemsize
数组中单个元素以字节计的大小。比如,若类型是float64,则itemsize就是8(=64/8)。它跟ndarray.dtype.itemsize是等价的。

ndarray.data
存储数组中实际的数据。通常我们不会直接用到这个属性,因为当我们需要访问数据时几乎总是使用数组提供的索引功能。

一个例子

In [3]:

import numpy as np
a = np.arange(15).reshape(3, 5)

In [4]:

a

Out[4]:

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

In [5]:

a.shape

Out[5]:

(3, 5)

In [6]:

a.ndim

Out[6]:

2

In [7]:

a.dtype.name, a.itemsize

Out[7]:

('int64', 8)

In [8]:

a.size

Out[8]:

15

In [9]:

type(a)

Out[9]:

numpy.ndarray

In [10]:

b = np.array([6,7,8])
b

Out[10]:

array([6, 7, 8])

In [11]:

type(b)

Out[11]:

numpy.ndarray

创建数组

有好几种方法可以创建数组。
比如:通过array,我们可以从普通的Python列表或者元组直接创建。元素的类型根据原类型推断。

In [12]:

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

Out[12]:

array([2, 3, 4])

In [13]:

a.dtype

Out[13]:

dtype('int64')

In [14]:

b = np.array([1.2, 3.5, 5.1])
b.dtype

Out[14]:

dtype('float64')

一个常见的错误是,把数组内容当做参数直接传给array,而不是作为一个列表或元组:

In [15]:

a = np.array(1,2,3,4) # 错误
a = np.array([1,2,3,4]) # 正确
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-15-27afcd997b15> in <module>()
----> 1 a = np.array(1,2,3,4) # 错误
      2 a = np.array([1,2,3,4]) # 正确

ValueError: only 2 non-keyword arguments accepted

array将两层嵌套序列转成二维数组,三层嵌套序列转成三维数组,以此类推:

In [16]:

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

Out[16]:

array([[1.5, 2. , 3. ],
       [4. , 5. , 6. ]])

数据类型在创建时也可以指定:

In [17]:

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

Out[17]:

array([[1.+0.j, 2.+0.j],
       [3.+0.j, 4.+0.j]])

在实际工作中,我们常常需要固定形状大小的数组,但在定义数组时还没有加载数据。
NumPy为我们提供了一些包含占位符的数组创建函数。这样我们就可以避免使用动态数组,因为动态数组在增长的时候是很耗时的。

  • zeros函数可以创建所有元素均为0的数组;
  • ones函数可以创建所有元素均为1的数组;
  • empty函数创建的数组,其元素都是随机的!!!

默认情况下,创建时的dtype都是float64:

In [18]:

np.zeros( (3, 4) ) # 它的参数并不是数据,而是数组的形状(shape)

Out[18]:

array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

In [19]:

np.ones( (2,3,4), dtype=np.int16 ) # 指定数据类型

Out[19]:

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)

In [24]:

np.empty( (2,4) ) # 一定要小心,元素都是随机的!

Out[24]:

array([[0., 0., 0., 0.],
       [0., 0., 0., 0.]])

NumPy提供了一个类似于Python标准库中的range类似的函数np.arange来生成数字序列:

In [22]:

np.arange( 10, 30, 5)

Out[22]:

array([10, 15, 20, 25])

In [23]:

np.arange(0, 2, 0.3) # 接受浮点数参数

Out[23]:

array([0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])

不过因为浮点数的有限精度问题,arange返回的数组长度可能无法预知。因此,最好使用linspace,它可以指定所需要的数组长度,而不需要设定步长:

In [25]:

np.linspace( 0, 2, 9 )

Out[25]:

array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  ])

In [26]:

from numpy import pi
x = np.linspace(0, 2*pi, 100) # 对于要采样很多点的时候特别有用
f = np.sin(x)

In [27]:

f

Out[27]:

array([ 0.00000000e+00,  6.34239197e-02,  1.26592454e-01,  1.89251244e-01,
        2.51147987e-01,  3.12033446e-01,  3.71662456e-01,  4.29794912e-01,
        4.86196736e-01,  5.40640817e-01,  5.92907929e-01,  6.42787610e-01,
        6.90079011e-01,  7.34591709e-01,  7.76146464e-01,  8.14575952e-01,
        8.49725430e-01,  8.81453363e-01,  9.09631995e-01,  9.34147860e-01,
        9.54902241e-01,  9.71811568e-01,  9.84807753e-01,  9.93838464e-01,
        9.98867339e-01,  9.99874128e-01,  9.96854776e-01,  9.89821442e-01,
        9.78802446e-01,  9.63842159e-01,  9.45000819e-01,  9.22354294e-01,
        8.95993774e-01,  8.66025404e-01,  8.32569855e-01,  7.95761841e-01,
        7.55749574e-01,  7.12694171e-01,  6.66769001e-01,  6.18158986e-01,
        5.67059864e-01,  5.13677392e-01,  4.58226522e-01,  4.00930535e-01,
        3.42020143e-01,  2.81732557e-01,  2.20310533e-01,  1.58001396e-01,
        9.50560433e-02,  3.17279335e-02, -3.17279335e-02, -9.50560433e-02,
       -1.58001396e-01, -2.20310533e-01, -2.81732557e-01, -3.42020143e-01,
       -4.00930535e-01, -4.58226522e-01, -5.13677392e-01, -5.67059864e-01,
       -6.18158986e-01, -6.66769001e-01, -7.12694171e-01, -7.55749574e-01,
       -7.95761841e-01, -8.32569855e-01, -8.66025404e-01, -8.95993774e-01,
       -9.22354294e-01, -9.45000819e-01, -9.63842159e-01, -9.78802446e-01,
       -9.89821442e-01, -9.96854776e-01, -9.99874128e-01, -9.98867339e-01,
       -9.93838464e-01, -9.84807753e-01, -9.71811568e-01, -9.54902241e-01,
       -9.34147860e-01, -9.09631995e-01, -8.81453363e-01, -8.49725430e-01,
       -8.14575952e-01, -7.76146464e-01, -7.34591709e-01, -6.90079011e-01,
       -6.42787610e-01, -5.92907929e-01, -5.40640817e-01, -4.86196736e-01,
       -4.29794912e-01, -3.71662456e-01, -3.12033446e-01, -2.51147987e-01,
       -1.89251244e-01, -1.26592454e-01, -6.34239197e-02, -2.44929360e-16])

作为补充,可以看看它们的文档: array, zeros, zeros_like, ones, ones_like, empty, empty_like, arange, linspace, numpy.random.rand, numpy.random.randn, fromfunction, fromfile

可以在notebook里面直接np.array?阅读,也可以去NumPy的官方文档

打印数组

打印出来非常类似于嵌套列表

In [28]:

a = np.arange(6) # 一维
print a
[0 1 2 3 4 5]

In [29]:

b = np.arange(12).reshape(3,4) # 二维,reshape后面会讲
print b
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

In [30]:

c = np.arange(24).reshape(2,3,4) # 三维
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会自动忽略中间部分的数据,只打印首尾:

In [31]:

print np.arange(10000)
[   0    1    2 ... 9997 9998 9999]

In [32]:

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选项来关闭该功能:
np.set_printoptions(threshold='nan')

基本操作

在数组上进行算术操作都是元素级别的(elementwise)。

In [33]:

a = np.array([20, 30, 40, 50])
b = np.arange(4) # 0, 1, 2, 3
c = a - b
c

Out[33]:

array([20, 29, 38, 47])

In [34]:

b ** 2

Out[34]:

array([0, 1, 4, 9])

In [35]:

10 * np.sin(a)

Out[35]:

array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])

In [36]:

a < 35

Out[36]:

array([ True,  True, False, False])

在线性代数中,乘号*一般表达的是矩阵乘法,但在NumPy中它表示元素级别的乘法。
矩阵乘法在NumPy中使用dot

In [37]:

A = np.array([ [1, 1],
             [0, 1]] )
B = np.array( [[2, 0],
              [3, 4]])
A * B # 元素级别乘法

Out[37]:

array([[2, 0],
       [0, 4]])

In [38]:

A.dot(B) # 矩阵乘法

Out[38]:

array([[5, 4],
       [3, 4]])

In [39]:

np.dot(A, B) # 矩阵乘法的另一种表达方式

Out[39]:

array([[5, 4],
       [3, 4]])

上面的操作符都会新建一个数组,而有一些操作符则会直接修改现有数组,比如+=*=:

In [40]:

a = np.ones((2,3), dtype=int)
b = np.random.random((2,3))
a *= 3
a

Out[40]:

array([[3, 3, 3],
       [3, 3, 3]])

In [41]:

b += a
b

Out[41]:

array([[3.04938179, 3.82179681, 3.84289888],
       [3.97974097, 3.33673677, 3.13912622]])

In [42]:

a += b # 类型转换失效,因为b是float,更加精确,而a是int
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-42-289421031488> in <module>()
----> 1 a += b # 类型转换失效,因为b是float,更加精确,而a是int

TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'

ndarray类里内置了很多方法来完成一些一元操作符,比如计算所有元素之和:

In [43]:

a = np.random.random((2,3))
a

Out[43]:

array([[0.29685988, 0.08922229, 0.72497749],
       [0.39473332, 0.03308139, 0.4120154 ]])

In [44]:

print a.sum(), a.min(), a.max()
1.9508897798325509 0.033081391083282674 0.724977493910644

默认情况下,这些方法不关心数组的形状,会将所有元素一起计算。
不过可以通过指定axis(轴)来计算沿着该轴的数据:

In [45]:

b = np.arange(12).reshape(3,4)
b

Out[45]:

array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [46]:

b.sum(axis=0) # 每一列的和

Out[46]:

array([12, 15, 18, 21])

In [47]:

b.min(axis=1) # 每一行的最小值

Out[47]:

array([0, 4, 8])

In [48]:

b.cumsum(axis=1) # 每一行的累加和

Out[48]:

array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])

通用函数(Universal Functions)

NumPy提供了一些常用的数学函数,如:sin, cos, exp等。在NumPy中,它们被称为"通用函数"(ufunc)。 在NumPy中,这些函数作用在数组上时,都是作用在每一个单独的元素上,并产生一个新的数组。

In [49]:

B = np.arange(3)
B

Out[49]:

array([0, 1, 2])

In [50]:

np.exp(B)

Out[50]:

array([1.        , 2.71828183, 7.3890561 ])

In [51]:

np.sqrt(B)

Out[51]:

array([0.        , 1.        , 1.41421356])

In [52]:

C = np.array([2., -1., 4.])
np.add(B, C)

Out[52]:

array([2., 0., 6.])

作为补充,可以了解下以下函数:
all, any, apply_along_axis, argmax, argmin, argsort, average, bincount, ceil, clip, conj, corrcoef, cov, cross, cumprod, cumsum, diff, dot, floor, inner, inv, lexsort, max, maximum, mean, median, min, minimum, nonzero, outer, prod, re, round, sort, std, sum, trace, transpose, var, vdot, vectorize, where

索引,切片以及遍历

一维数组非常类似于Python的序列(list, tuple),它们可以被索引(index),被切片(slice),被遍历(iterate)。

In [53]:

a = np.arange(10)**3
a

Out[53]:

array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])

In [54]:

a[2]

Out[54]:

8

In [55]:

a[2:5]

Out[55]:

array([ 8, 27, 64])

In [56]:

a[:6:2] = -1000 # 等价于 a[0:6:2] = -1000:从开始到第6个(不含)元素,以2为步长将元素设置为-1000
a

Out[56]:

array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,
         729])

In [57]:

a[::-1]

Out[57]:

array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1,
       -1000])

In [58]:

for i in a:
    print i**(1/3.0)
nan
1.0
nan
3.0
nan
4.999999999999999
5.999999999999999
6.999999999999999
7.999999999999999
8.999999999999998
/Users/sunkepeng/anaconda2/lib/python2.7/site-packages/ipykernel_launcher.py:2: RuntimeWarning: invalid value encountered in power
  

多维数组每一个轴(axis)均有一个索引(index)。这些索引使用","(半角逗号)分隔,以元组(tuple)的形式表现:

In [59]:

def f(x,y):
    return 10*x+y

b = np.fromfunction(f,(5,4),dtype=int)
b

Out[59]:

array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])

In [60]:

b[2,3]

Out[60]:

23

In [61]:

b[0:5, 1] # 第二列,0,1,2,3,4行

Out[61]:

array([ 1, 11, 21, 31, 41])

In [62]:

b[:,1] # 第二列,所有行

Out[62]:

array([ 1, 11, 21, 31, 41])

In [63]:

b[1:3, : ] # 第1,2行,所有列

Out[63]:

array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

若提供的索引不够,那么NumPy会自动补全其他轴的索引:

In [64]:

b[-1] # 等价于b[-1, :]

Out[64]:

array([40, 41, 42, 43])

另外,NumPy还有...这种写法,它会自动判断所需要填补的索引:

比如,我们现在有一个拥有五个轴(axis)的数组:

  • x[1,2,...] 等价于 x[1,2,:,:,:]
  • x[...,3] 等价于 x[:,:,:,:,3]
  • x[4,...,5,:] 等价于 x[4,:,:,5,:]

In [65]:

c = np.array( [[[  0,  1,  2],               # 一个三维的数组
                [ 10, 12, 13]],
                [[100,101,102],
                [110,112,113]]])
c.shape

Out[65]:

(2, 2, 3)

In [66]:

c[1,...]

Out[66]:

array([[100, 101, 102],
       [110, 112, 113]])

In [67]:

 c[...,2] 

Out[67]:

array([[  2,  13],
       [102, 113]])

遍历是从第一个轴开始的:

In [68]:

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属性:

In [69]:

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

作为补充,可以参考:
Indexing,Indexing(reference) , newaxis, ndenumerate, indices

形状操作(shape manipulation)

修改一个数组的形状

In [70]:

a = np.floor(10*np.random.random((3,4)))
a

Out[70]:

array([[3., 9., 7., 5.],
       [9., 2., 7., 3.],
       [1., 6., 4., 6.]])

In [71]:

a.shape

Out[71]:

(3, 4)

在NumPy中有多种方法可以改变一个数组的形状。
请注意,下面三种方式均返回一个修改后的数组,但又不改变原来的数组:

In [72]:

a.ravel() # 返回打平了的数组

Out[72]:

array([3., 9., 7., 5., 9., 2., 7., 3., 1., 6., 4., 6.])

In [73]:

a.reshape(6 ,2) # 返回目标形状的数组

Out[73]:

array([[3., 9.],
       [7., 5.],
       [9., 2.],
       [7., 3.],
       [1., 6.],
       [4., 6.]])

In [74]:

a.T # 返回它的转置

Out[74]:

array([[3., 9., 1.],
       [9., 2., 6.],
       [7., 7., 4.],
       [5., 3., 6.]])

In [75]:

a.T.shape, a.shape

Out[75]:

((4, 3), (3, 4))

ravel()返回的数组,其元素的顺序默认是C语言风格(C-style)的,即最右侧的索引"变化的最快",因此a[0,0]的下一个元素是a[0,1]。一般而言,NumPy使用这种顺序来新建数组,所以ravel()一般不需要拷贝数组,但是如果传给ravel()的数组是从其他数组切片得到的,或者使用不寻常的选项构建的,那就不得不拷贝一次数据了。
ravel()reshape()函数本身也接受一个可选的参数,可以使用Fortran风格(Fortran-style)的数组,它最左侧的索引变化的最快,即a[0,0]的下一个元素是a[1,0]。

reshape函数不会改变原数组,而ndarray.resize方法则会改变原数组:

In [76]:

a

Out[76]:

array([[3., 9., 7., 5.],
       [9., 2., 7., 3.],
       [1., 6., 4., 6.]])

In [77]:

a.resize((2,6))
a

Out[77]:

array([[3., 9., 7., 5., 9., 2.],
       [7., 3., 1., 6., 4., 6.]])

如果在reshape操作时将某一维度设置成-1,那么该维度就会被自动计算,比如:

In [78]:

a.reshape(3, -1)

Out[78]:

array([[3., 9., 7., 5.],
       [9., 2., 7., 3.],
       [1., 6., 4., 6.]])

更多请参考ndarray.shape, reshape, resize, ravel

将不同的数组堆叠(stacking)起来

多个数组可以沿着不同的轴堆叠起来:

In [79]:

a = np.floor(10*np.random.random((2,2)))
a

Out[79]:

array([[3., 4.],
       [0., 5.]])

In [80]:

b = np.floor(10*np.random.random((2,2)))
b

Out[80]:

array([[9., 5.],
       [6., 4.]])

In [81]:

np.vstack((a,b))

Out[81]:

array([[3., 4.],
       [0., 5.],
       [9., 5.],
       [6., 4.]])

In [82]:

np.hstack((a,b))

Out[82]:

array([[3., 4., 9., 5.],
       [0., 5., 6., 4.]])

column_stack可以将一维数组作为一列插入一个二维数组:

In [83]:

c = np.array([3,3])
np.column_stack((a,c))

Out[83]:

array([[3., 4., 3.],
       [0., 5., 3.]])

对于超过两维的情况,这里我们就不讨论了。对我们这个系列的课程而言,不会用到超过2维的情况。

将一个数组切分成多个

使用hsplit,我们可以将一个数组沿水平方向进行切分。 切分时可以指定:

  1. 目标数组个数 —— 将自动计算切分后的数组的形状
  2. 切分列下标 —— 将在指定的列下标处进行切分

In [84]:

a = np.floor(10*np.random.random((2,12)))
a

Out[84]:

array([[3., 2., 7., 7., 2., 3., 1., 9., 3., 5., 8., 7.],
       [9., 5., 8., 2., 1., 9., 2., 2., 0., 3., 6., 2.]])

In [85]:

np.hsplit(a,3)   # 切分成三个数组

Out[85]:

[array([[3., 2., 7., 7.],
        [9., 5., 8., 2.]]), array([[2., 3., 1., 9.],
        [1., 9., 2., 2.]]), array([[3., 5., 8., 7.],
        [0., 3., 6., 2.]])]

In [86]:

np.hsplit(a,(3,4))   # 在第三列和第四列进行切分,从1开始计数

Out[86]:

[array([[3., 2., 7.],
        [9., 5., 8.]]), array([[7.],
        [2.]]), array([[2., 3., 1., 9., 3., 5., 8., 7.],
        [1., 9., 2., 2., 0., 3., 6., 2.]])]

类似的,vsplit可以沿着垂直方向进行切分,而array_split则允许指定切分的轴。

拷贝(copy)与视图(view)

当我们操作数组的时候,其数据有时候会被拷贝到一个新的数组,而有时又不会。
这对于初学者而言常常是一个觉得很不清晰的地方。
实际上,一共有三种情况:

根本不拷贝

简单的赋值不会发生任何拷贝行为:

In [87]:

a = np.arange(12)
b = a # 不会新建对象
b is a  # a和b是对同一个ndarray对象的两个名字

Out[87]:

True

In [88]:

b.shape = 3,4 # 同时会修改a的形状
a.shape

Out[88]:

(3, 4)

Python以引用的方式传递可变对象,所以函数调用不会产生拷贝:

In [89]:

def f(x):
    print id(x)

print id(a) # id是一个对象(实例)独一无二的标识符
f(a)
4745060560
4745060560

视图和浅拷贝

不同的数据对象可以共享相同的数据。view方法新建一个数组对象,但仍然使用相同的数据。

In [90]:

c = a.view()
c is a

Out[90]:

False

In [91]:

c.base is a # c仅仅是a中数据的一个视图

Out[91]:

True

In [92]:

c.flags.owndata

Out[92]:

False

In [93]:

c.shape = 2,6 # a的形状不会改变
a.shape

Out[93]:

(3, 4)

In [94]:

c[0, 4] = 1234 # a的数据会改变!!!
a

Out[94]:

array([[   0,    1,    2,    3],
       [1234,    5,    6,    7],
       [   8,    9,   10,   11]])

In [95]:

c

Out[95]:

array([[   0,    1,    2,    3, 1234,    5],
       [   6,    7,    8,    9,   10,   11]])

对一个数组进行切片会返回它的视图:

In [96]:

s = a[:, 1:3]
s[:] = 10
a

Out[96]:

array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

深拷贝

copy方法会对数组及其数据做一次完整的拷贝。

In [97]:

d = a.copy() # 新建了一个数组对象,而且把数据也拷贝了一份
d is a

Out[97]:

False

In [98]:

d.base is a # d不从a共享任何东西

Out[98]:

False

In [99]:

d[0,0] = 9999
a

Out[99]:

array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

基础函数及方法总览

我们这里按照类目列出了一些最常用的NumPy的函数及方法。请参考routines以了解更全的列表。

Array Creation
arange, array, copy, empty, empty_like, eye, fromfile, fromfunction, identity, linspace, logspace, mgrid, ogrid, ones, ones_like, r, zeros, zeros_like

Conversions
ndarray.astype, atleast_1d, atleast_2d, atleast_3d, mat

Manipulations
array_split, column_stack, concatenate, diagonal, dsplit, dstack, hsplit, hstack, ndarray.item, newaxis, ravel, repeat, reshape, resize, squeeze, swapaxes, take, transpose, vsplit, vstack

Questions
all, any, nonzero, where

Ordering
argmax, argmin, argsort, max, min, ptp, searchsorted, sort

Operations
choose, compress, cumprod, cumsum, inner, ndarray.fill, imag, prod, put, putmask, real, sum

Basic Statistics
cov, mean, std, var

Basic Linear Algebra
cross, dot, outer, linalg.svd, vdot

广播规则(broadcasting rules)

广播使得在多个输入没有完全一致的形状(shape)的时候,通用函数(universal functions)仍可以以一种有意义的方式进行处理。

广播有两条规则:

  1. 维度不够就补,直到补成和维度最大的一致为止;
  2. 数据不够就沿着维度拷贝

 

在应用广播规则以后,所有的数组的形状必然是一致的。可以在关于广播的官方细节文档中了解更多信息。

时髦索引(fancy indexing)及索引技巧

NumPy比一般的Python序列支持更多的索引功能。
在支持使用整型数字及切片来进行索引的基础上,数组还可以使用整型数据和布尔型数组来进行索引。

使用索引数组(array if indices)来进行索引

In [100]:

a = np.arange(12)**2   # 12个平方数
i = np.array( [ 1,1,3,8,5 ] ) # 一些索引
a[i]

Out[100]:

array([ 1,  1,  9, 64, 25])

In [101]:

j = np.array( [ [ 3, 4], [ 9, 7 ] ] ) # 一个二维的索引数组
a[j]

Out[101]:

array([[ 9, 16],
       [81, 49]])

当数组a是多维的时候,一个单独的索引数组表达的是数组a的第一个维度。
下面这个例子定义了一个调色盘,然后图片以调色盘的索引来表示,它可以诠释上面这句话的含义:

In [102]:

palette = np.array( [ [0,0,0],        # 黑色
                     [255,0,0],       # 红色
                     [0,255,0],       # 绿色
                     [0,0,255],       # 蓝色
                     [255,255,255] ] ) # 白色

image = np.array( [ [ 0, 1, 2, 0 ],    # 每一个值对应调色盘中的一种颜色
                   [ 0, 3, 4, 0 ]  ] )

palette[image]

Out[102]:

array([[[  0,   0,   0],
        [255,   0,   0],
        [  0, 255,   0],
        [  0,   0,   0]],

       [[  0,   0,   0],
        [  0,   0, 255],
        [255, 255, 255],
        [  0,   0,   0]]])

我们也可以让索引有多个维度。不过,所有的索引数组在每一个维度上就必须有同样的形状:

In [103]:

a = np.arange(12).reshape(3,4)
a

Out[103]:

array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [104]:

i = np.array( [ [0,1],       # 数组a的第一个维度
               [1,2] ] )
j = np.array( [ [2,1],       # 数组a的第二个维度
               [3,3] ] )
a[i, j]                      # i和j必须要有同样的形状

Out[104]:

array([[ 2,  5],
       [ 7, 11]])

In [105]:

a[i,2]

Out[105]:

array([[ 2,  6],
       [ 6, 10]])

In [106]:

a[:,j]            # 所有行都要,而j又是2*2的,所以最终的数组的形状是 3 * 2 *2

Out[106]:

array([[[ 2,  1],
        [ 3,  3]],

       [[ 6,  5],
        [ 7,  7]],

       [[10,  9],
        [11, 11]]])

我们也可以把ij打包到一个序列中,然后使用这个序列来进行索引:

In [107]:

l = [i, j]
a[l]   # 等价于a[i, j]
/Users/sunkepeng/anaconda2/lib/python2.7/site-packages/ipykernel_launcher.py:2: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
  

Out[107]:

array([[ 2,  5],
       [ 7, 11]])

但是得千万注意,不要把ij放在一个数组(ndarray)里,因为这个数组表达的是以第一个维度来对数组a进行索引。

In [108]:

s = np.array( [i,j] )
a[s] 
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-108-a1bcf3e0a417> in <module>()
      1 s = np.array( [i,j] )
----> 2 a[s]

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

In [109]:

a[tuple(s)]     # 等价于a[i,j]

Out[109]:

array([[ 2,  5],
       [ 7, 11]])

另一个常用的使用数组进行索引的方式是:在一个时间依赖(time-dependent)的序列中找到最大值:

In [110]:

time = np.linspace(20, 145, 5)            # 时间轴
data = np.sin(np.arange(20)).reshape(5,4) # 4个数据序列
time

Out[110]:

array([ 20.  ,  51.25,  82.5 , 113.75, 145.  ])

In [115]:

data

Out[115]:

array([[ 0.        ,  0.84147098,  0.90929743,  0.14112001],
       [-0.7568025 , -0.95892427, -0.2794155 ,  0.6569866 ],
       [ 0.98935825,  0.41211849, -0.54402111, -0.99999021],
       [-0.53657292,  0.42016704,  0.99060736,  0.65028784],
       [-0.28790332, -0.96139749, -0.75098725,  0.14987721]])

In [112]:

ind = data.argmax(axis=0)   # axis=0 - 列方向
ind

Out[112]:

array([2, 0, 3, 1])

In [113]:

time_max = time[ind]    # 找出每个序列最大值的时间点
time_max

Out[113]:

array([ 82.5 ,  20.  , 113.75,  51.25])

In [114]:

data_max = data[ind, xrange(data.shape[1])]     # data[ind[0],0], data[ind[1],1]...
data_max

Out[114]:

array([0.98935825, 0.84147098, 0.99060736, 0.6569866 ])

In [116]:

np.all(data_max == data.max(axis=0))      # 验证一下

Out[116]:

True

使用索引数组不仅可以访问数组元素,还可以更改数组元素:

In [117]:

a = np.arange(5)
a

Out[117]:

array([0, 1, 2, 3, 4])

In [118]:

a[[1,3,4]] = 0
a

Out[118]:

array([0, 0, 2, 0, 0])

不过如果同一个索引在索引数组中出现多次的话,那么赋值操作实际上就会被多次执行:

In [119]:

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

Out[119]:

array([2, 1, 3, 3, 4])

不过要注意的是,Python的+=语法结构的行为会出乎你的意料:

In [120]:

a = np.arange(5)
a[[0,0,2]]+=1
a

Out[120]:

array([1, 1, 3, 3, 4])

使用布尔数组(boolean array)来索引

前面我们使用整型的索引数组来访问数据的时候,我们实际上是提供了想要访问的数据的坐标。
而布尔数组则所有不同,它明确指示了那些数组元素是想要的,那些数组元素是不想要的。

一种最自然的布尔数组的想法是,用于索引的布尔数组的形状和数据数组的形状完全一致:

In [121]:

a = np.arange(12).reshape(3,4)
b = a > 4
b                 # b和a的形状一致

Out[121]:

array([[False, False, False, False],
       [False,  True,  True,  True],
       [ True,  True,  True,  True]])

In [122]:

a[b]

Out[122]:

array([ 5,  6,  7,  8,  9, 10, 11])

这种性质非常适合赋值:

In [123]:

a[b] = 0
a

Out[123]:

array([[0, 1, 2, 3],
       [4, 0, 0, 0],
       [0, 0, 0, 0]])

让我们来看个例子,这个例子使用布尔索引来生成一副曼德布洛特集合(Mandelbrot set)中的一个图形:

In [124]:

import matplotlib.pyplot as plt
def mandelbrot( h,w, maxit=20 ):
    """Returns an image of the Mandelbrot fractal of size (h,w)."""
    y,x = np.ogrid[ -1.4:1.4:h*1j, -2:0.8:w*1j ]
    c = x+y*1j
    z = c
    divtime = maxit + np.zeros(z.shape, dtype=int)
    
    for i in range(maxit):
        z = z**2 + c
        diverge = z*np.conj(z) > 2**2          # boolean
        div_now = diverge & (divtime==maxit)   # boolean
        divtime[div_now] = i                   # boolean index
        z[diverge] = 2                         # boolean index
        
    return divtime

In [125]:

plt.imshow(mandelbrot(400,400))
plt.show()

第二种使用布尔索引的方式跟整型索引更加相似。
对于数组的每一个维度,使用一个一维数组来指定所需要的数据切片:

In [126]:

a = np.arange(12).reshape(3,4)
a

Out[126]:

array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [127]:

b1 = np.array([False,True,True])   # 第一维
b2 = np.array([True,False,True,False]) # 第二维

a[b1, :]    # 选择行

Out[127]:

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

In [128]:

a[:, b2]  # 选择列

Out[128]:

array([[ 0,  2],
       [ 4,  6],
       [ 8, 10]])

In [129]:

a[b1, b2] # 选定坐标 (1,0)和(2,2),这个实际上很奇怪,一般不会用

Out[129]:

array([ 4, 10])

ix_()函数

ix_函数可以用于使用不同的向量生成它们之间的笛卡尔积。
比如我们现在有a, b, c三个长度不一致的向量,我们想计算所有可能的a + b * c的值:

In [130]:

a = np.array([2,3,4,5])
b = np.array([8,5,4])
c = np.array([5,4,6,8,3])

ax,bx,cx = np.ix_(a,b,c)
result = ax+bx*cx
result

Out[130]:

array([[[42, 34, 50, 66, 26],
        [27, 22, 32, 42, 17],
        [22, 18, 26, 34, 14]],

       [[43, 35, 51, 67, 27],
        [28, 23, 33, 43, 18],
        [23, 19, 27, 35, 15]],

       [[44, 36, 52, 68, 28],
        [29, 24, 34, 44, 19],
        [24, 20, 28, 36, 16]],

       [[45, 37, 53, 69, 29],
        [30, 25, 35, 45, 20],
        [25, 21, 29, 37, 17]]])

In [131]:

result[3,2,4]

Out[131]:

17

In [132]:

a[3]+b[2]*c[4]

Out[132]:

17

上面这种用法实际上是非常巧妙地利用了广播机制。不过这个技巧确实有点高了:-)

基本线性代数

让我们继续,虽然已经很累了。。。
这一节进行基本的线性代数在NumPy中的介绍。

简单的数组操作

如有必要,请阅读linalg的文档以了解更多。

In [133]:

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

In [134]:

a.transpose() # 转置

Out[134]:

array([[1., 3.],
       [2., 4.]])

In [135]:

np.linalg.inv(a) # 求逆

Out[135]:

array([[-2. ,  1. ],
       [ 1.5, -0.5]])

In [136]:

u = np.eye(2) # 单位阵
u

Out[136]:

array([[1., 0.],
       [0., 1.]])

In [137]:

j = np.array([[0.0, -1.0], [1.0, 0.0]])

In [138]:

np.dot (j, j) # 矩阵乘法

Out[138]:

array([[-1.,  0.],
       [ 0., -1.]])

In [139]:

np.trace(u) # 矩阵的迹

Out[139]:

2.0

解线性方程:
x1+2x2=5x1+2x2=5
3x1+4x2=73x1+4x2=7

In [140]:

y = np.array([[5.], [7.]])
np.linalg.solve(a, y)

Out[140]:

array([[-3.],
       [ 4.]])

In [141]:

np.linalg.eig(j) # 求特征值及特征向量

Out[141]:

(array([0.+1.j, 0.-1.j]),
 array([[0.70710678+0.j        , 0.70710678-0.j        ],
        [0.        -0.70710678j, 0.        +0.70710678j]]))

技巧与小贴士(Tricks and Tips)

这里给一些短小但有用的贴士。

"自动"变形("automatic" reshaping)

在改变一个数组的形状的时候,我们可以省略一个维度的值,它会自动被计算出来:

In [142]:

a = np.arange(30)
a.shape = 2,-1,3 # 这里的-1指的是在满足其他维度的情况下,这个维度自动帮我算出来
a.shape

Out[142]:

(2, 5, 3)

In [143]:

a

Out[143]:

array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8],
        [ 9, 10, 11],
        [12, 13, 14]],

       [[15, 16, 17],
        [18, 19, 20],
        [21, 22, 23],
        [24, 25, 26],
        [27, 28, 29]]])

向量堆叠(vector stacking)

我们如何将等长的行向量组建成一个二维的数组呢?
我们可以使用column_stackdstackhstackvstack。实际上只要记住stack这个词就行了。

In [144]:

x = np.arange(0,10,2)                     # x=([0,2,4,6,8])
y = np.arange(5)                          # y=([0,1,2,3,4])

m = np.vstack([x,y])
m                                    

Out[144]:

array([[0, 2, 4, 6, 8],
       [0, 1, 2, 3, 4]])

In [145]:

xy = np.hstack([x,y])
xy

Out[145]:

array([0, 2, 4, 6, 8, 0, 1, 2, 3, 4])

超过二维时,这些函数的逻辑看上去就有些奇怪了,我们几乎也不需要关心,因为几乎不会用到。

直方图(histogram)

NumPy的histogram函数作用在一个数组上的时候,会返回一对向量(a pair of vectors):

  • 每个桶的计数
  • 桶的划分

请注意,matplotlib也有一个函数(hist)可以创建直方图,它和NumPy的不一样。
二者的主要区别在于,matplotlibhist会自动绘制出直方图,而numpy.histogram仅仅生成数据。

In [147]:

mu, sigma = 2, 0.5
v = np.random.normal(mu,sigma,10000) # 使用正态分布随机生成一万个点

# matplotlib版本
plt.hist(v, bins=50, normed=1) # 数据都规范化了
plt.show()

In [148]:

# NumPy版本
# 先生成histogram数据,然后再绘制
(n, bins) = np.histogram(v, bins=50, normed=True) # bins是区间的上下界
plt.plot(.5*(bins[1:]+bins[:-1]), n)
plt.show()

进一步学习

如果完全消化了本课的内容,进一步学习建议:

  1. 阅读《利用Python进行数据分析(Python for Data Analysis)》

  2. 阅读NumPy手册

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值