基础
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
,我们可以将一个数组沿水平方向进行切分。 切分时可以指定:
- 目标数组个数 —— 将自动计算切分后的数组的形状
- 切分列下标 —— 将在指定的列下标处进行切分
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)仍可以以一种有意义的方式进行处理。
广播有两条规则:
- 维度不够就补,直到补成和维度最大的一致为止;
- 数据不够就沿着维度拷贝
在应用广播规则以后,所有的数组的形状必然是一致的。可以在关于广播的官方细节文档中了解更多信息。
时髦索引(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]]])
我们也可以把i
和j
打包到一个序列中,然后使用这个序列来进行索引:
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]])
但是得千万注意,不要把i
和j
放在一个数组(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_stack
, dstack
, hstack
, vstack
。实际上只要记住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的不一样。
二者的主要区别在于,matplotlib
的hist
会自动绘制出直方图,而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()
进一步学习
如果完全消化了本课的内容,进一步学习建议:
-
阅读《利用Python进行数据分析(Python for Data Analysis)》
-
阅读NumPy手册