NumPy学习记录
参考资料来源:https://www.numpy.org.cn/user/quickstart.html#%E5%85%88%E5%86%B3%E6%9D%A1%E4%BB%B6
NumPy的主要对象是同构多维数组。它是一个元素表(通常是数字),所有类型都相同,由非负整数元组索引。在NumPy维度中称为 轴 。
例如,3D空间中的点的坐标[1, 2, 1]
具有一个轴。该轴有3个元素,所以我们说它的长度为3.在下图所示的例子中,数组有2个轴。第一轴的长度为2,第二轴的长度为3。
[[ 1., 0., 0.], [ 0., 1., 2.]]
NumPy的数组类被调用ndarray
。它也被别名所知 array
。请注意,numpy.array
这与标准Python库类不同array.array
,后者只处理一维数组并提供较少的功能。ndarray
对象更重要的属性是:
- ndarray.ndim - 数组的轴(维度)的个数。在Python世界中,维度的数量被称为rank。
- ndarray.shape - 数组的维度。这是一个整数的元组,表示每个维度中数组的大小。对于有 n 行和 m 列的矩阵,
shape
将是(n,m)
。因此,shape
元组的长度就是rank或维度的个数ndim
。 - ndarray.size - 数组元素的总数。这等于
shape
的元素的乘积。 - ndarray.dtype - 一个描述数组中元素类型的对象。可以使用标准的Python类型创建或指定dtype。另外NumPy提供它自己的类型。例如numpy.int32、numpy.int16和numpy.float64。
- ndarray.itemsize - 数组中每个元素的字节大小。例如,元素为
float64
类型的数组的itemsize
为8(=64/8),而complex32
类型的数组的itemsize
为4(=32/8)。它等于ndarray.dtype.itemsize
。 - ndarray.data - 该缓冲区包含数组的实际元素。通常,我们不需要使用此属性,因为我们将使用索引访问数组中的元素。
In [3]: import numpy as np In [4]: a = np.arange(15).reshape(3,5) In [5]: a Out[5]: array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) In [6]: a.shape Out[6]: (3, 5) In [7]: a.ndim Out[7]: 2 In [8]: a.dtype Out[8]: dtype('int64') In [9]: a.dtype.name Out[9]: 'int64' In [10]: a.size Out[10]: 15 In [11]: type(a) Out[11]: numpy.ndarray In [12]: a.itemsize Out[12]: 8 In [13]:
数组创建
有几种方法可以创建数组。
例如,你可以使用array函数从常规Python列表或元组中创建数组。得到的数组的类型是从Python列表中元素的类型推导出来的。
In [14]: a = np.array([2,3,4]) In [15]: a Out[15]: array([2, 3, 4]) In [16]: a.dtype Out[16]: dtype('int64') In [17]: b = np.array([1.2,3.5,5.1]) In [18]: b.dtype Out[18]: dtype('float64') In [19]:
一个常见的错误,就是调用array
的时候传入多个数字参数,而不是提供单个数字的列表类型作为参数。
In [19]: np.array(1,2,3,4) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-19-c35df19b8ce6> in <module> ----> 1 np.array(1,2,3,4) TypeError: array() takes from 1 to 2 positional arguments but 4 were given In [20]: np.array([1,2,34]) Out[20]: array([ 1, 2, 34]) In [21]:
也可以在创建时显式指定数组的类型
In [23]: c = np.array([[1,2],[3,4]],dtype=complex) In [24]: c Out[24]: array([[1.+0.j, 2.+0.j], [3.+0.j, 4.+0.j]]) In [25]:
通常,数组的元素最初是未知的,但它的大小是已知的。因此,NumPy提供了几个函数来创建具有初始占位符内容的数组。这就减少了数组增长的必要,因为数组增长的操作花费很大。
函数zeros
创建一个由0组成的数组,函数 ones
创建一个完整的数组,函数empty
创建一个数组,其初始内容是随机的,取决于内存的状态。默认情况下,创建的数组的dtype是 float64
类型的。
当然,你要是自己指定类型绝对可以.
In [25]: np.zeros((3,4),dtype=np.int) Out[25]: array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) In [26]: np.zeros((2,3)) Out[26]: array([[0., 0., 0.], [0., 0., 0.]]) In [28]: np.ones((2,3)) Out[28]: array([[1., 1., 1.], [1., 1., 1.]]) In [29]: np.ones((2,3),dtype=np.int) Out[29]: array([[1, 1, 1], [1, 1, 1]]) In [30]: np.empty((2,3)) Out[30]: array([[1., 1., 1.], [1., 1., 1.]]) In [31]:
为了创建数字组成的数组,NumPy提供了一个类似于range
的函数,该函数返回数组而不是列表。
In [32]: np.arange(10,30,5) Out[32]: array([10, 15, 20, 25]) In [33]: np.arange(0,2,0.3) Out[33]: array([0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])
当arange
与浮点参数一起使用时,由于有限的浮点精度,通常不可能预测所获得的元素的数量。出于这个原因,通常最好使用linspace
函数来接收我们想要的元素数量的函数,而不是步长(step):
In [37]: np.linspace(0,2,5) Out[37]: array([0. , 0.5, 1. , 1.5, 2. ]) In [38]: from numpy import pi In [39]: pi Out[39]: 3.141592653589793 In [40]: x = np.linspace(0,2*pi,100) In [41]: np.sin(x) Out[41]: 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]) In [42]:
另见这些API
, zeros
, zeros_like
, ones
, ones_like
, empty
, empty_like
, arange
, linspace
, numpy.random.mtrand.RandomState.rand
, numpy.random.mtrand.RandomState.randn
, fromfunction
, fromfile
打印数组
当您打印数组时,NumPy以与嵌套列表类似的方式显示它,但具有以下布局:
- 最后一个轴从左到右打印,
- 倒数第二个从上到下打印,
- 其余部分也从上到下打印,每个切片用空行分隔。
然后将一维数组打印为行,将二维数据打印为矩阵,将三维数据打印为矩数组表。
In [48]: a = np.arange(6) In [49]: a Out[49]: array([0, 1, 2, 3, 4, 5]) In [50]: print(a) [0 1 2 3 4 5] In [51]: b = np.arange(12).reshape(4,3) In [52]: b Out[52]: array([[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8], [ 9, 10, 11]]) In [53]: print(b) [[ 0 1 2] [ 3 4 5] [ 6 7 8] [ 9 10 11]] In [54]: c = np.arange(24).reshape(2,3,4) In [55]: c Out[55]: 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]]]) In [56]: 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]]]
有关 reshape
的详情,请参阅下文。
如果数组太大而无法打印,NumPy会自动跳过数组的中心部分并仅打印角点:
In [58]: print(np.arange(10000)) [ 0 1 2 ... 9997 9998 9999] In [61]: 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]]
要禁用此行为并强制NumPy打印整个数组,可以使用更改打印选项set_printoptions
。
In [65]: import sys In [66]: np.set_printoptions(threshold=sys.maxsize) In [67]: print(np.arange(10000)) [ 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
基本操作
数组上的算术运算符会应用到 元素 级别。下面是创建一个新数组并填充结果的示例:
In [75]: a = np.array([20,30,40,50]) In [76]: b = np.arange(4) In [77]: b Out[77]: array([0, 1, 2, 3]) In [78]: c = a-b In [79]: c Out[79]: array([20, 29, 38, 47]) In [80]: b**2 Out[80]: array([0, 1, 4, 9]) In [81]: 10*np.sin(a) Out[81]: array([ 9.12945251, -9.88031624, 7.4511316 , -2.62374854]) In [82]: a<35 Out[82]: array([ True, True, False, False]) In [83]:
与许多矩阵语言不同,乘积运算符*
在NumPy数组中按元素进行运算。矩阵乘积可以使用@
运算符(在python> = 3.5中)或dot
函数或方法执行:
In [85]: A = np.arange(1,5).reshape(2,2) In [86]: B = np.arange(4).reshape(2,2) In [87]: A Out[87]: array([[1, 2], [3, 4]]) In [88]: B Out[88]: array([[0, 1], [2, 3]]) In [89]: A * B Out[89]: array([[ 0, 2], [ 6, 12]]) In [90]: np.dot(A,B) Out[90]: array([[ 4, 7], [ 8, 15]]) In [92]: A @ B Out[92]: array([[ 4, 7], [ 8, 15]]) In [93]: A.dot(B) Out[93]: array([[ 4, 7], [ 8, 15]]) In [94]:
某些操作(例如+=
和 *=
)会更直接更改被操作的矩阵数组而不会创建新矩阵数组。
In [96]: a = np.ones((2,3),dtype=np.int) In [97]: b = np.random.random((2,3)) In [98]: a Out[98]: array([[1, 1, 1], [1, 1, 1]]) In [99]: b Out[99]: array([[0.8645708 , 0.10307247, 0.66873947], [0.07845996, 0.6847631 , 0.24589917]]) In [100]: a *=3 In [101]: a Out[101]: array([[3, 3, 3], [3, 3, 3]]) In [102]: b+=a In [103]: b Out[103]: array([[3.8645708 , 3.10307247, 3.66873947], [3.07845996, 3.6847631 , 3.24589917]]) In [104]: In [104]: a += b --------------------------------------------------------------------------- UFuncTypeError Traceback (most recent call last) <ipython-input-104-294cacd62d6f> in <module> ----> 1 a += b UFuncTypeError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int64') with casting rule 'same_kind' In [105]:
当使用不同类型的数组进行操作时,结果数组的类型对应于更一般或更精确的数组(称为向上转换的行为)。
In [106]: a = np.ones(3,dtype=np.int32) In [107]: a Out[107]: array([1, 1, 1], dtype=int32) In [108]: b = np.linspace(0,pi,3) In [109]: b Out[109]: array([0. , 1.57079633, 3.14159265]) In [110]: c = a + b In [111]: c.dtype.name Out[111]: 'float64' In [112]: d = np.exp(c*1j) In [113]: d Out[113]: array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j, -0.54030231-0.84147098j]) In [114]: d.dtype.name Out[114]: 'complex128'
许多一元操作,例如计算数组中所有元素的总和,都是作为ndarray
类的方法实现的。
In [116]: a Out[116]: array([[0.45049848, 0.19340773, 0.9740766 ], [0.60789612, 0.87329619, 0.10210579]]) In [117]: a.sum Out[117]: <function ndarray.sum> In [118]: a.sum() Out[118]: 3.201280916227102 In [119]: a.min() Out[119]: 0.10210578514523472 In [120]: a.max() Out[120]: 0.9740766027099907 In [121]:
默认情况下,这些操作适用于数组,就像它是一个数字列表一样,无论其形状如何。但是,通过指定axis
参数,您可以沿数组的指定轴应用操作:
In [122]: b = np.arange(12).reshape(3,4) In [123]: b Out[123]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [124]: b.sum(axis=0) # 根据行列 column操作 Out[124]: array([12, 15, 18, 21]) In [125]: b.min(axis=1) # 根据行row操作 Out[125]: array([0, 4, 8]) In [127]: b.cumsum(axis=1) Out[127]: array([[ 0, 1, 3, 6], [ 4, 9, 15, 22], [ 8, 17, 27, 38]]) In [128]:
通函数
NumPy提供熟悉的数学函数,例如sin,cos和exp。在NumPy中,这些被称为“通函数”(ufunc
)。在NumPy中,这些函数在数组上按元素进行运算,产生一个数组作为输出。
In [129]: b = np.arange(3) In [130]: b Out[130]: array([0, 1, 2]) In [131]: np.exp(b) Out[131]: array([1. , 2.71828183, 7.3890561 ]) In [132]: np.sqrt(b) Out[132]: array([0. , 1. , 1.41421356]) In [133]: c = np.array([2.,-1.,4.]) In [134]: np.add(b,c) Out[134]: array([2., 0., 6.]) In [135]: np.dtype Out[135]: numpy.dtype In [136]: e = np.add(b,c) In [137]: e.dtype Out[137]: dtype('float64') In [138]:
另见这些通函数
, 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序列类型一样。
In [139]: a = np.arange(10)**3 In [140]: a Out[140]: array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729]) In [141]: a[2] Out[141]: 8 In [142]: a[2:5] Out[142]: array([ 8, 27, 64]) In [143]: a[:6:2] = -1000 # 这个操作Python列表不行 In [144]: a Out[144]: array([-1000, 1, -1000, 27, -1000, 125, 216, 343, 512, 729]) In [145]: a[::-1] Out[145]: array([ 729, 512, 343, 216, 125, -1000, 27, -1000, 1, -1000]) In [146]: for i in a: ...: print(i**(1/3)) ...: /usr/local/bin/ipython:2: RuntimeWarning: invalid value encountered in power # -*- coding: utf-8 -*- nan 1.0 nan 3.0 nan 4.999999999999999 5.999999999999999 6.999999999999999 7.999999999999999 8.999999999999998 In [147]: for i in a: ...: print(i**(1/3.)) ...: ...: /usr/local/bin/ipython:2: RuntimeWarning: invalid value encountered in power # -*- coding: utf-8 -*- nan 1.0 nan 3.0 nan 4.999999999999999 5.999999999999999 6.999999999999999 7.999999999999999 8.999999999999998 In [148]:
多维的数组每个轴可以有一个索引。这些索引以逗号分隔的元组给出:
...: return 20*x+y ...: In [152]: b = np.fromfunction(f,(5,4),dtype=np.int) # 数组的中的参数来至于,x,y的坐标位置 In [153]: b Out[153]: array([[ 0, 1, 2, 3], [20, 21, 22, 23], [40, 41, 42, 43], [60, 61, 62, 63], [80, 81, 82, 83]]) In [154]: b[2,3] Out[154]: 43 In [155]: b[0:5,1] Out[155]: array([ 1, 21, 41, 61, 81]) In [156]: b[:,1] Out[156]: array([ 1, 21, 41, 61, 81]) In [157]: b[1:3,:] Out[157]: array([[20, 21, 22, 23], [40, 41, 42, 43]]) In [158]:
当提供的索引少于轴的数量时,缺失的索引被认为是完整的切片:(参数没有全的时候)
In [163]: b[-1] Out[163]: array([40, 41, 42, 43]) In [164]: b[-1,:] Out[164]: array([40, 41, 42, 43])
b[i]
方括号中的表达式 i
被视为后面紧跟着 :
的多个实例,用于表示剩余轴。NumPy也允许你使用三个点写为 b[i,...]
。
三个点( ...
)表示产生完整索引元组所需的冒号。例如,如果 x
是rank为5的数组(即,它具有5个轴),则:
In [165]: c = np.array( [[[ 0, 1, 2], # a 3D array (two stacked 2D arrays) ...: ... [ 10, 12, 13]], ...: ... [[100,101,102], ...: ... [110,112,113]]]) In [166]: c.shape Out[166]: (2, 2, 3) In [167]: c[1,...] Out[167]: array([[100, 101, 102], [110, 112, 113]]) In [168]: c[1] Out[168]: array([[100, 101, 102], [110, 112, 113]]) In [169]: c[...,2] Out[169]: array([[ 2, 13], [102, 113]]) In [170]: c[:,:,2] Out[170]: array([[ 2, 13], [102, 113]]) In [171]:
对多维数组进行 迭代(Iterating) 是相对于第一个轴完成的
In [172]: b Out[172]: array([[ 0, 1, 2, 3], [20, 21, 22, 23], [40, 41, 42, 43]]) In [173]: for row in b: ...: print(row) ...: [0 1 2 3] [20 21 22 23] [40 41 42 43] In [174]: for row in b: ...: print(row.__class__) ...: ...: <class 'numpy.ndarray'> <class 'numpy.ndarray'> <class 'numpy.ndarray'> In [175]:
但是,如果想要对数组中的每个元素执行操作,可以使用flat
属性,该属性是数组的所有元素的迭代器:
In [177]: for element in b.flat: ...: print(element) ...: 0 1 2 3 20 21 22 23 40 41 42 43 In [178]:
另见
(reference), newaxis
, ndenumerate
, indices
形状操纵
改变数组的形状
一个数组的形状是由每个轴的元素数量决定的:
In [179]: a = np.floor(10*np.random.random((3,4))) In [180]: a Out[180]: array([[0., 3., 6., 3.], [0., 0., 0., 3.], [6., 1., 1., 8.]]) In [181]: a.shape Out[181]: (3, 4) In [182]:
可以使用各种命令更改数组的形状。请注意,以下三个命令都返回一个修改后的数组,但不会更改原始数组:
In [179]: a = np.floor(10*np.random.random((3,4))) In [180]: a Out[180]: array([[0., 3., 6., 3.], [0., 0., 0., 3.], [6., 1., 1., 8.]]) In [181]: a.shape Out[181]: (3, 4) In [182]: a Out[182]: array([[0., 3., 6., 3.], [0., 0., 0., 3.], [6., 1., 1., 8.]]) In [183]: a.ravel() Out[183]: array([0., 3., 6., ..., 1., 1., 8.]) In [184]: a.reshape((6,2)) Out[184]: array([[0., 3.], [6., 3.], [0., 0.], [0., 3.], [6., 1.], [1., 8.]]) In [185]: a.T Out[185]: array([[0., 0., 6.], [3., 0., 1.], [6., 0., 1.], [3., 3., 8.]]) In [186]: a.T.shape Out[186]: (4, 3) In [187]: a.shape Out[187]: (3, 4) In [188]:
由 ravel() 产生的数组中元素的顺序通常是“C风格”,也就是说,最右边的索引“变化最快”,因此[0,0]之后的元素是[0,1] 。如果将数组重新整形为其他形状,则该数组将被视为“C风格”。NumPy通常创建按此顺序存储的数组,因此 ravel() 通常不需要复制其参数,但如果数组是通过获取另一个数组的切片或使用不常见的选项创建的,则可能需要复制它。还可以使用可选参数指示函数 ravel() 和 reshape(),以使用FORTRAN样式的数组,其中最左边的索引变化最快。
reval返回的是镜像,flat返回的是一个全新的对象
函数返回带有修改形状的参数,而该 ndarray.resize
方法会修改数组本身:
In [189]: a Out[189]: array([[0., 3., 6., 3.], [0., 0., 0., 3.], [6., 1., 1., 8.]]) In [190]: a.resize(2,6) # 直接修改自身 In [191]: a Out[191]: array([[0., 3., 6., 3., 0., 0.], [0., 3., 6., 1., 1., 8.]]) In [192]: b = a.reshape(3,4) # 返回一个修改shape的镜像 In [193]: b Out[193]: array([[0., 3., 6., 3.], [0., 0., 0., 3.], [6., 1., 1., 8.]]) In [194]: b[0][0]=99 In [195]: a Out[195]: array([[99., 3., 6., 3., 0., 0.], [ 0., 3., 6., 1., 1., 8.]]) In [196]: b Out[196]: array([[99., 3., 6., 3.], [ 0., 0., 0., 3.], [ 6., 1., 1., 8.]]) In [197]:
如果在 reshape 操作中将 size 指定为-1,则会自动计算其他的 size 大小:
In [200]: a Out[200]: array([[99., 3., 6., 3., 0., 0.], [ 0., 3., 6., 1., 1., 8.]]) In [201]: a.reshape(3,-1) Out[201]: array([[99., 3., 6., 3.], [ 0., 0., 0., 3.], [ 6., 1., 1., 8.]]) In [202]:
另见
将不同数组堆叠在一起
几个数组可以沿不同的轴堆叠在一起,例如:
In [203]: a = np.floor(10*np.random.random((2,2))) In [204]: a Out[204]: array([[3., 8.], [4., 6.]]) In [205]: b = np.floor(10*np.random.random((2,2))) In [206]: b Out[206]: array([[5., 0.], [2., 7.]]) In [207]: np.vstack((a,b)) Out[207]: array([[3., 8.], [4., 6.], [5., 0.], [2., 7.]]) In [208]: np.hstack((a,b)) Out[208]: array([[3., 8., 5., 0.], [4., 6., 2., 7.]]) In [209]:
该函数将column_stack
1D数组作为列堆叠到2D数组中。它仅相当于 hstack
2D数组:
In [214]: a Out[214]: array([[3., 8.], [4., 6.]]) In [215]: b Out[215]: array([[5., 0.], [2., 7.]]) In [216]: np.column_stack((a,b)) Out[216]: array([[3., 8., 5., 0.], [4., 6., 2., 7.]]) In [217]: a = np.arange(2) In [218]: b = np.arange(1,3) In [219]: a Out[219]: array([0, 1]) In [220]: b Out[220]: array([1, 2]) In [221]: np.column_stack((a,b)) Out[221]: array([[0, 1], [1, 2]]) In [222]: np.hstack((a,b)) Out[222]: array([0, 1, 1, 2]) In [223]: a[:,np.newaxis] Out[223]: array([[0], [1]]) In [224]: np.column_stack((a[:,np.newaxis],b[:,np.newaxis])) Out[224]: array([[0, 1], [1, 2]]) In [225]: np.hstack((a[:,np.newaxis],b[:,np.newaxis])) Out[225]: array([[0, 1], [1, 2]]) In [226]:
a[:,np.newaxis]会从前面选出来的轴对象,新开一个坐标轴
另一方面,该函数ma.row_stack
等效vstack
于任何输入数组。通常,对于具有两个以上维度的数组, hstack
沿其第二轴vstack
堆叠,沿其第一轴堆叠,并concatenate
允许可选参数给出连接应发生的轴的编号。
注意
在复杂的情况下,r_
和c c_
于通过沿一个轴堆叠数字来创建数组很有用。它们允许使用范围操作符(“:”)。
In [227]: np.r_[1:34,0,4] Out[227]: array([ 1, 2, 3, ..., 33, 0, 4]) In [228]: a Out[228]: array([0, 1]) In [229]: b Out[229]: array([1, 2]) In [230]: a = a[:,np.newaxis] In [231]: b = b[:,np.newaxis] In [232]: a Out[232]: array([[0], [1]]) In [233]: b Out[233]: array([[1], [2]]) In [234]: np.c_[a,b] Out[234]: array([[0, 1], [1, 2]]) In [235]: np.r_[a,b] Out[235]: array([[0], [1], [1], [2]]) In [236]:
与数组一起用作参数时, r_
和 c_
在默认行为上类似于 vstack
和 hstack
,但允许使用可选参数给出要连接的轴的编号。
另见
hstack
, vstack
, column_stack
, concatenate
, c_
, r_
将一个数组拆分成几个较小的数组
使用hsplit
,可以沿数组的水平轴拆分数组,方法是指定要返回的形状相等的数组的数量,或者指定应该在其之后进行分割的列:
In [237]: a = np.floor(10*np.random.random((2,12))) In [238]: a Out[238]: array([[7., 9., 6., ..., 2., 0., 8.], [3., 1., 2., ..., 6., 6., 4.]]) In [239]: np.hsplit(a,3) Out[239]: [array([[7., 9., 6., 2.], [3., 1., 2., 2.]]), array([[9., 5., 0., 5.], [5., 9., 6., 0.]]), array([[3., 2., 0., 8.], [0., 6., 6., 4.]])] In [240]: np.hsplit(a,(3,4)) # 对3号列,4号列进行分隔,分成三块 Out[240]: [array([[7., 9., 6.], [3., 1., 2.]]), array([[2.], [2.]]), array([[9., 5., 0., ..., 2., 0., 8.], [5., 9., 6., ..., 6., 6., 4.]])] In [241]:
vsplit
沿垂直轴分割,并array_split
允许指定要分割的轴。
拷贝和视图
当计算和操作数组时,有时会将数据复制到新数组中,有时则不会。这通常是初学者混淆的根源。有三种情况:
完全不复制
简单分配不会复制数组对象或其数据。
In [242]: a = np.arange(12) In [243]: b =a In [244]: b is a Out[244]: True In [245]: b.shape Out[245]: (12,) In [246]: a.shape Out[246]: (12,) In [247]:
Python将可变对象作为引用传递,因此函数调用不会复制。
Python函数对参数的使用都是引用传参
>>> def f(x): ... print(id(x)) ... >>> id(a) # id is a unique identifier of an object 148293216 >>> f(a) 148293216
视图或浅拷贝
不同的数组对象可以共享相同的数据。该view
方法创建一个查看相同数据的新数组对象。
In [261]: a Out[261]: array([ 0, 1, 2, ..., 9, 10, 11]) In [262]: c = a.view() In [263]: c is a Out[263]: False In [264]: c == a Out[264]: array([ True, True, True, ..., True, True, True]) In [265]: c.base is a Out[265]: True In [266]: c.flags.owndata Out[266]: False In [267]: a.flags.owndata Out[267]: True In [268]: c.shape = 2,6 In [269]: a.shape Out[269]: (12,) In [270]: a.shape = (3,4) In [271]: c Out[271]: array([[ 0, 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10, 11]]) In [273]: a Out[273]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [274]: a[0][0] = 99 In [275]: a Out[275]: array([[99, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [276]: c Out[276]: array([[99, 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10, 11]]) In [277]:
通过上面的测试可以发现,通过view拷贝处理的视图,两个用着一份数据,但shape可以不同
切片数组会返回一个视图:
In [282]: a Out[282]: array([[99, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [283]: s = a[:,1:3] In [284]: s Out[284]: array([[ 1, 2], [ 5, 6], [ 9, 10]]) In [285]: s[:] = 10 # 将其内部的元素都变成10 In [286]: s Out[286]: array([[10, 10], [10, 10], [10, 10]]) In [287]: a Out[287]: array([[99, 10, 10, 3], [ 4, 10, 10, 7], [ 8, 10, 10, 11]]) In [288]:
深拷贝
该copy
方法生成数组及其数据的完整副本。
In [289]: a Out[289]: array([[99, 10, 10, 3], [ 4, 10, 10, 7], [ 8, 10, 10, 11]]) In [290]: d = a.copy() In [291]: d is a Out[291]: False In [292]: d[:] = 888 In [293]: d Out[293]: array([[888, 888, 888, 888], [888, 888, 888, 888], [888, 888, 888, 888]]) In [294]: a Out[294]: array([[99, 10, 10, 3], [ 4, 10, 10, 7], [ 8, 10, 10, 11]]) In [295]:
有时,如果不再需要原始数组,则应在切片后调用 copy
。例如,假设a是一个巨大的中间结果,最终结果b只包含a的一小部分,那么在用切片构造b时应该做一个深拷贝:
In [308]: a=np.arange(int(1e8)) In [309]: b = a[:100].copy() In [310]: del a In [311]: b Out[311]: array([ 0, 1, 2, ..., 97, 98, 99])
如果改为使用 b = a[:100]
,则 a
由 b
引用,并且即使执行 del a
也会在内存中持久存在。
功能和方法概述
以下是按类别排序的一些有用的NumPy函数和方法名称的列表。有关完整列表,请参阅参考手册里的常用API。
- 数组的创建(Array Creation) - arange, array, copy, empty, empty_like, eye, fromfile, fromfunction, identity, linspace, logspace, mgrid, ogrid, ones, ones_like, 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 , 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
- 顺序(Ordering) - argmax
Less 基础
广播(Broadcasting)规则
广播允许通用功能以有意义的方式处理不具有完全相同形状的输入。
广播的第一个规则是,如果所有输入数组不具有相同数量的维度,则将“1”重复地预先添加到较小数组的形状,直到所有数组具有相同数量的维度。
广播的第二个规则确保沿特定维度的大小为1的数组表现为具有沿该维度具有最大形状的数组的大小。假定数组元素的值沿着“广播”数组的那个维度是相同的。
应用广播规则后,所有数组的大小必须匹配。更多细节可以在广播中找到。
花式索引和索引技巧
NumPy提供比常规Python序列更多的索引功能。除了通过整数和切片进行索引之外,正如我们之前看到的,数组可以由整数数组和布尔数组索引。
使用索引数组进行索引
In [314]: a = np.arange(12)**2 In [315]: a Out[315]: array([ 0, 1, 4, ..., 81, 100, 121]) In [316]: i= np.array([1,1,3,8,5]) In [317]: a[i] Out[317]: array([ 1, 1, 9, 64, 25]) In [318]: j = np.array([[3,4],[9,7]]) In [319]: a[j] Out[319]: array([[ 9, 16], [81, 49]]) In [320]:
当索引数组a
是多维的时,单个索引数组指的是第一个维度a
。以下示例通过使用调色板将标签图像转换为彩色图像来显示此行为。
In [321]: palette = np.array( [ [0,0,0], # black ...: ... [255,0,0], # red ...: ... [0,255,0], # green ...: ... [0,0,255], # blue ...: ... [255,255,255] ] ) # white In [322]: image = np.array( [ [ 0, 1, 2, 0 ], # each value corresponds to a color in the palette ...: ... [ 0, 3, 4, 0 ] ] ) In [323]: palette[image] # 每个索引地址就是取palette[x]的内容。这里取出是一维的数组,加上本身自己为2维,所以最后变成了三维 Out[323]: 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 [324]: p = palette[image] In [325]: p.shape Out[325]: (2, 4, 3) In [326]:
我们还可以为多个维度提供索引。每个维度的索引数组必须具有相同的形状。
In [327]: a = np.arange(12).reshape(3,4) In [328]: a Out[328]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [329]: i = np.array( [ [0,1], # indices for the first dim of a ...: ... [1,2] ] ) In [330]: j = np.array( [ [2,1], # indices for the second dim ...: ... [3,3] ] ) In [331]: a[i,j] # 两个纬度,相当于x坐标,y坐标 Out[331]: array([[ 2, 5], [ 7, 11]]) In [332]: a[i,2] # 相当于x行坐标写死 Out[332]: array([[ 2, 6], [ 6, 10]]) In [333]: a[:,j] # 这个通过:先取出a的每一个row坐标,然后每个行坐标,通过j的索引来取值,因为j是2维的,所以原来的a就多了一个维度,变成3维 Out[333]: array([[[ 2, 1], [ 3, 3]], [[ 6, 5], [ 7, 7]], [[10, 9], [11, 11]]])
当然,我们可以按顺序(比如列表)放入i
,j
然后使用列表进行索引。
In [339]: l = [i,j] In [340]: a[l] /usr/local/bin/ipython:1: 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. #!/Library/Developer/CommandLineTools/usr/bin/python3 Out[340]: array([[ 2, 5], [ 7, 11]]) In [341]: In [341]:
但是,我们不能通过放入i
和j
放入数组来实现这一点,因为这个数组将被解释为索引a的第一个维度。
>>> s = np.array( [i,j] ) In [334]: s = np.array( [i,j] ) In [335]: s Out[335]: array([[[0, 1], [1, 2]], [[2, 1], [3, 3]]]) >>> a[s] # not what we want Traceback (most recent call last): File "<stdin>", line 1, in ? IndexError: index (3) out of range (0<=index<=2) in dimension 0 # 因为a的第一维度,索引最多只有2,s里面有3,所以报索引超出范围 >>> >>> a[tuple(s)] # same as a[i,j] array([[ 2, 5], [ 7, 11]])
使用数组索引的另一个常见用法是搜索与时间相关的系列的最大值:
In [7]: time = np.linspace(20,145,5) In [8]: data = np.sin(np.arange(20)).reshape(5,4) In [10]: time Out[10]: array([ 20. , 51.25, 82.5 , 113.75, 145. ]) In [11]: data Out[11]: 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 [12]: ind = data.argmax(axis=0) # 取出列最大的索引 In [13]: ind Out[13]: array([2, 0, 3, 1]) In [14]: time_max = time[ind] In [15]: time_max Out[15]: array([ 82.5 , 20. , 113.75, 51.25]) In [16]: data_max=data[ind,range(data.shape[1])] In [17]: data_max Out[17]: array([0.98935825, 0.84147098, 0.99060736, 0.6569866 ]) In [18]: np.all(data_max == data.max(axis=0)) Out[18]: True In [19]: data[ind] # 列最大索引取出的数据 Out[19]: array([[ 0.98935825, 0.41211849, -0.54402111, -0.99999021], [ 0. , 0.84147098, 0.90929743, 0.14112001], [-0.53657292, 0.42016704, 0.99060736, 0.65028784], [-0.7568025 , -0.95892427, -0.2794155 , 0.6569866 ]]) In [20]: data[ind, 1] Out[20]: array([ 0.41211849, 0.84147098, 0.42016704, -0.95892427]) In [21]: data[ind, range(4)] # 通过range可以取出最大值 Out[21]: array([0.98935825, 0.84147098, 0.99060736, 0.6569866 ]) In [22]: data.shape[1] Out[22]: 4
您还可以使用数组索引作为分配给的目标:
In [1]: import numpy as np In [2]: a = np.arange(5) In [3]: a Out[3]: array([0, 1, 2, 3, 4]) In [4]: a[[1,3,4]] = 0 In [5]: a Out[5]: array([0, 0, 2, 0, 0]) In [6]:
但是,当索引列表包含重复时,分配会多次完成,留下最后一个值:
In [7]: a = np.arange(5) In [8]: a[[0,0,2]] = [1,2,3] In [9]: a Out[9]: array([2, 1, 3, 3, 4]) In [10]:
这是合理的,但请注意是否要使用Python的 +=
构造,因为它可能不会按预期执行:
In [10]: a = np.arange(5) In [11]: a[[0,0,2]]+=1 In [12]: a Out[12]: array([1, 1, 3, 3, 4]) In [13]:
即使0在索引列表中出现两次,第0个元素也只增加一次。这是因为Python要求“a + = 1”等同于“a = a + 1”。
使用布尔数组进行索引
当我们使用(整数)索引数组索引数组时,我们提供了要选择的索引列表。使用布尔索引,方法是不同的; 我们明确地选择我们想要的数组中的哪些项目以及我们不需要的项目。
人们可以想到的最自然的布尔索引方法是使用与原始数组具有 相同形状的 布尔数组:
In [14]: a = np.arange(12).reshape(3,4) In [15]: b = a>4 In [16]: b Out[16]: array([[False, False, False, False], [False, True, True, True], [ True, True, True, True]]) In [17]: a[b] Out[17]: array([ 5, 6, 7, 8, 9, 10, 11]) In [18]:
此属性在分配中非常有用:
In [18]: a[b] = 0 In [19]: a Out[19]: array([[0, 1, 2, 3], [4, 0, 0, 0], [0, 0, 0, 0]])
您可以查看以下示例,了解如何使用布尔索引生成Mandelbrot集的图像:现在还看不懂代码
>>> import numpy as np >>> 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 # who is diverging ... div_now = diverge & (divtime==maxit) # who is diverging now ... divtime[div_now] = i # note when ... z[diverge] = 2 # avoid diverging too much ... ... return divtime >>> plt.imshow(mandelbrot(400,400)) >>> plt.show()
使用布尔值进行索引的第二种方法更类似于整数索引; 对于数组的每个维度,我们给出一个1D布尔数组,选择我们想要的切片:
In [22]: a = np.arange(12).reshape(3,4) In [23]: a Out[23]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [24]: b1 = np.array([False,True,True]) In [25]: b2 = np.array([True,False,True,False]) In [26]: a[b1,:] Out[26]: array([[ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [27]: a[b1] Out[27]: array([[ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [28]: a[:,b2] Out[28]: array([[ 0, 2], [ 4, 6], [ 8, 10]]) In [29]: a[b1,b2] Out[29]: array([ 4, 10])
请注意,1D布尔数组的长度必须与要切片的尺寸(或轴)的长度一致。在前面的例子中,b1
具有长度为3(的数目 的行 中a
),和 b2
(长度4)适合于索引的第二轴线(列) a
。