第四章
本文代码仓库地址
NumPy(Numerical Python的简称)是Python数值计算最重要的基础包,可以高效处理大数组的数据。
ndarray:一种多维数组对象 NumPy最重要的一个特点就是其N维数组对象(即ndarray),该对象是一个快速而灵活的大数据集容器。
可以利用这种数组对整块数据执行一些数学运算,其语法跟标量元素之间的运算一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [12 ]: import numpy as np In [13 ]: data = np.random.randn(2 , 3 ) In [14 ]: data Out[14 ]: array([[-0.2047 , 0.4789 , -0.5194 ], [-0.5557 , 1.9658 , 1.3934 ]]) In [15 ]: data * 10 Out[15 ]: array([[ -2.0471 , 4.7894 , -5.1944 ], [ -5.5573 , 19.6578 , 13.9341 ]]) In [16 ]: data + data Out[16 ]: array([[-0.4094 , 0.9579 , -1.0389 ], [-1.1115 , 3.9316 , 2.7868 ]])
ndarray中的所有元素必须是相同类型的。
每个数组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象):
1 2 3 4 5 In [17 ]: data.shape Out[17 ]: (2 , 3 ) In [18 ]: data.dtype Out[18 ]: dtype('float64' )
多数情况下我们不需要深入理解NumPy,但是精通面向数组的编程和思维方式能很大程度的提高我们解决问题的能力。
笔记:当你在本系列文章中看到“数组”、“NumPy数组”、”ndarray”时,基本上都指的是同一样东西,即ndarray对象。
创建ndarray 创建数组最简单的办法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的ndarry。
以一个列表的转换为例:
1 2 3 4 In [19 ]: data1 = [6 , 7.5 , 8 , 0 , 1 ] In [20 ]: arr1 = np.array(data1) In [21 ]: arr1 Out[21 ]: array([ 6. , 7.5 , 8. , 0. , 1. ])
嵌套序列(比如由一组等长列表组成的列表)将会被转换为一个多维数组:
1 2 3 4 5 6 In [22 ]: data2 = [[1 , 2 , 3 , 4 ], [5 , 6 , 7 , 8 ]] In [23 ]: arr2 = np.array(data2) In [24 ]: arr2 Out[24 ]: array([[1 , 2 , 3 , 4 ], [5 , 6 , 7 , 8 ]])
因为data2是列表的列表,NumPy数组arr2的两个维度的shape是从data2引入的。可以用属性ndim(维度)和shape验证:
1 2 3 4 In [25 ]: arr2.ndim Out[25 ]: 2 In [26 ]: arr2.shape Out[26 ]: (2 , 4 )
除np.array之外,还有一些函数也可以新建数组。
zeros和ones分别可以创建指定长度或形状的全0或全1数组。empty可以创建一个没有任何具体值的数组。
要用这些方法创建多维数组,只需传入一个表示形状的元组即可。
arange是Python内置函数range的数组版。
ndarray的数据类型 记不住这些NumPy的dtype也没关系,新手更是如此。
通常只需要知道你所处理的数据的大致类型是浮点数、复数、整数、布尔值、字符串,还是普通的Python对象即可。
可以通过ndarray的astype方法明确地将一个数组从一个dtype转换成另一个dtype:
1 2 3 4 5 6 7 8 9 In [37 ]: arr = np.array([1 , 2 , 3 , 4 , 5 ]) In [38 ]: arr.dtype Out[38 ]: dtype('int64' ) In [39 ]: float_arr = arr.astype(np.float64) In [40 ]: float_arr.dtype Out[40 ]: dtype('float64' )
如果某字符串数组表示的全是数字,也可以用astype将其转换为数值形式:
1 2 3 4 In [44 ]: numeric_strings = np.array(['1.25' , '-9.6' , '42' ], dtype=np.string_) In [45 ]: numeric_strings.astype(float ) Out[45 ]: array([ 1.25 , -9.6 , 42. ])
NumPy数组的运算 不用编写循环即可对数据执行批量运算,称其为矢量化(vectorization)。
大小相等的数组之间的任何算术运算都会将运算应用到元素级:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [51 ]: arr = np.array([[1. , 2. , 3. ], [4. , 5. , 6. ]]) In [52 ]: arr Out[52 ]: array([[ 1. , 2. , 3. ], [ 4. , 5. , 6. ]]) In [53 ]: arr * arr Out[53 ]: array([[ 1. , 4. , 9. ], [ 16. , 25. , 36. ]]) In [54 ]: arr - arr Out[54 ]: array([[ 0. , 0. , 0. ], [ 0. , 0. , 0. ]])
数组与标量的算术运算会将标量值传播到各个元素:
1 2 3 4 5 6 7 8 9 In [55 ]: 1 / arr Out[55 ]: array([[ 1. , 0.5 , 0.3333 ], [ 0.25 , 0.2 , 0.1667 ]]) In [56 ]: arr ** 0.5 Out[56 ]: array([[ 1. , 1.4142 , 1.7321 ], [ 2. , 2.2361 , 2.4495 ]])
大小相同的数组之间的比较会生成布尔值数组:
1 2 3 4 5 6 7 8 9 10 11 In [57 ]: arr2 = np.array([[0. , 4. , 1. ], [7. , 2. , 12. ]]) In [58 ]: arr2 Out[58 ]: array([[ 0. , 4. , 1. ], [ 7. , 2. , 12. ]]) In [59 ]: arr2 > arr Out[59 ]: array([[False , True , False ], [ True , False , True ]], dtype=bool )
基本的索引和切片 ndarray的索引是一个内容丰富的主题,因为选取数据子集或单个元素的方式有很多。
一维数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 In [60 ]: arr = np.arange(10 ) In [61 ]: arr Out[61 ]: array([0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]) In [62 ]: arr[5 ] Out[62 ]: 5 In [63 ]: arr[5 :8 ] Out[63 ]: array([5 , 6 , 7 ]) In [64 ]: arr[5 :8 ] = 12 In [65 ]: arr Out[65 ]: array([ 0 , 1 , 2 , 3 , 4 , 12 , 12 , 12 , 8 , 9 ])
将一个标量值赋值给一个切片时(如arr[5:8]=12),该值会自动传播到整个选区。
数组切片是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上。 (因为NumPy的设计目的是处理大数据,这样可以节省大量的内存和性能。)
注意:如果想要得到的是ndarray切片的一份副本而非视图,就需要明确地进行复制操作,例如arr[5:8].copy()。
二维数组 1 2 3 4 In [72 ]: arr2d = np.array([[1 , 2 , 3 ], [4 , 5 , 6 ], [7 , 8 , 9 ]]) In [73 ]: arr2d[2 ] Out[73 ]: array([7 , 8 , 9 ])
可以传入一个以逗号隔开的索引列表来选取单个元素(下面两种方式是等价的):
1 2 3 4 5 In [74 ]: arr2d[0 ][2 ] Out[74 ]: 3 In [75 ]: arr2d[0 , 2 ] Out[75 ]: 3
多维数组 1 2 3 4 5 6 7 8 In [76 ]: arr3d = np.array([[[1 , 2 , 3 ], [4 , 5 , 6 ]], [[7 , 8 , 9 ], [10 , 11 , 12 ]]]) In [77 ]: arr3d Out[77 ]: array([[[ 1 , 2 , 3 ], [ 4 , 5 , 6 ]], [[ 7 , 8 , 9 ], [10 , 11 , 12 ]]])
arr3d[0]是一个2×3数组:
1 2 3 4 In [78 ]: arr3d[0 ] Out[78 ]: array([[1 , 2 , 3 ], [4 , 5 , 6 ]])
标量值和数组都可以被赋值给arr3d[0]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 In [79 ]: old_values = arr3d[0 ].copy() In [80 ]: arr3d[0 ] = 42 In [81 ]: arr3d Out[81 ]: array([[[42 , 42 , 42 ], [42 , 42 , 42 ]], [[ 7 , 8 , 9 ], [10 , 11 , 12 ]]]) In [82 ]: arr3d[0 ] = old_values In [83 ]: arr3d Out[83 ]: array([[[ 1 , 2 , 3 ], [ 4 , 5 , 6 ]], [[ 7 , 8 , 9 ], [10 , 11 , 12 ]]])
相似的,arr3d[1,0]可以访问索引以(1,0)开头的那些值(以一维数组的形式返回):
1 2 In [84 ]: arr3d[1 , 0 ] Out[84 ]: array([7 , 8 , 9 ])
虽然是用两步进行索引的,表达式是相同的:
1 2 3 4 5 6 7 8 9 In [85 ]: x = arr3d[1 ] In [86 ]: x Out[86 ]: array([[ 7 , 8 , 9 ], [10 , 11 , 12 ]]) In [87 ]: x[0 ] Out[87 ]: array([7 , 8 , 9 ])
在上面所有这些选取数组子集的例子中,返回的数组都是视图 。
切片索引 ndarray的切片语法跟Python列表这样的一维对象差不多:
1 2 3 4 5 In [88 ]: arr Out[88 ]: array([ 0 , 1 , 2 , 3 , 4 , 64 , 64 , 64 , 8 , 9 ]) In [89 ]: arr[1 :6 ] Out[89 ]: array([ 1 , 2 , 3 , 4 , 64 ])
对于之前的二维数组arr2d,其切片方式稍显不同:
1 2 3 4 5 6 7 8 9 10 In [90 ]: arr2d Out[90 ]: array([[1 , 2 , 3 ], [4 , 5 , 6 ], [7 , 8 , 9 ]]) In [91 ]: arr2d[:2 ] Out[91 ]: array([[1 , 2 , 3 ], [4 , 5 , 6 ]])
切片是沿着一个轴向选取元素的。表达式arr2d[:2]可以被认为是“选取arr2d的前两行”。
可以一次传入多个切片,就像传入多个索引那样:
1 2 3 4 In [92 ]: arr2d[:2 , 1 :] Out[92 ]: array([[2 , 3 ], [5 , 6 ]])
像这样进行切片时,只能得到相同维数的数组视图。通过将整数索引 和切片 混合,可以得到低维度的切片。
例如,可以选取第二行的前两列:
1 2 In [93 ]: arr2d[1 , :2 ] Out[93 ]: array([4 , 5 ])
相似的,还可以选择第三列的前两行:
1 2 In [94 ]: arr2d[:2 , 2 ] Out[94 ]: array([3 , 6 ])
注意,“只有冒号”表示选取整个轴,可以像下面这样只对高维轴进行切片:
1 2 3 4 5 In [95 ]: arr2d[:, :1 ] Out[95 ]: array([[1 ], [4 ], [7 ]])
自然,对切片表达式的赋值操作也会被扩散到整个选区:
1 2 3 4 5 6 7 In [96 ]: arr2d[:2 , 1 :] = 0 In [97 ]: arr2d Out[97 ]: array([[1 , 0 , 0 ], [4 , 0 , 0 ], [7 , 8 , 9 ]])
布尔型索引 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 In [98 ]: names = np.array(['Bob' , 'Joe' , 'Will' , 'Bob' , 'Will' , 'Joe' , 'Joe' ]) In [99 ]: data = np.random.randn(7 , 4 ) In [100 ]: names Out[100 ]: array(['Bob' , 'Joe' , 'Will' , 'Bob' , 'Will' , 'Joe' , 'Joe' ], dtype='<U4' ) In [101 ]: data Out[101 ]: array([[ 0.0929 , 0.2817 , 0.769 , 1.2464 ], [ 1.0072 , -1.2962 , 0.275 , 0.2289 ], [ 1.3529 , 0.8864 , -2.0016 , -0.3718 ], [ 1.669 , -0.4386 , -0.5397 , 0.477 ], [ 3.2489 , -1.0212 , -0.5771 , 0.1241 ], [ 0.3026 , 0.5238 , 0.0009 , 1.3438 ], [-0.7135 , -0.8312 , -2.3702 , -1.8608 ]])
假设每个名字都对应data数组中的一行,而我们想要选出对应于名字”Bob”的所有行。跟算术运算一样,数组的比较运算(如==)也是矢量化的。因此,对names和字符串”Bob”的比较运算将会产生一个布尔型数组:
1 2 In [102 ]: names == 'Bob' Out[102 ]: array([ True , False , False , True , False , False , False ], dtype=bool )
这个布尔型数组可用于数组索引:
1 2 3 4 In [103 ]: data[names == 'Bob' ] Out[103 ]: array([[ 0.0929 , 0.2817 , 0.769 , 1.2464 ], [ 1.669 , -0.4386 , -0.5397 , 0.477 ]])
布尔型数组的长度必须跟被索引的轴长度一致。此外,还可以将布尔型数组跟切片、整数(或整数序列,稍后将对此进行详细讲解)混合使用:
1 2 3 4 In [103 ]: data[names == 'Bob' ] Out[103 ]: array([[ 0.0929 , 0.2817 , 0.769 , 1.2464 ], [ 1.669 , -0.4386 , -0.5397 , 0.477 ]])
注意:如果布尔型数组的长度不对,布尔型选择就会出错,因此一定要小心。
下面的例子选取了names == 'Bob'的行,并索引了列:
1 2 3 4 5 6 7 In [104 ]: data[names == 'Bob' , 2 :] Out[104 ]: array([[ 0.769 , 1.2464 ], [-0.5397 , 0.477 ]]) In [105 ]: data[names == 'Bob' , 3 ] Out[105 ]: array([ 1.2464 , 0.477 ])
布尔型索引选取数组中的数据,将总是创建数据的副本。
注意:Python关键字and和or在布尔型数组中无效。要使用&与|。
通过布尔型数组设置值是一种经常用到的手段。为了将data中的所有负值都设置为0,我们只需:
1 2 3 4 5 6 7 8 9 10 11 In [113 ]: data[data < 0 ] = 0 In [114 ]: data Out[114 ]: array([[ 0.0929 , 0.2817 , 0.769 , 1.2464 ], [ 1.0072 , 0. , 0.275 , 0.2289 ], [ 1.3529 , 0.8864 , 0. , 0. ], [ 1.669 , 0. , 0. , 0.477 ], [ 3.2489 , 0. , 0. , 0.1241 ], [ 0.3026 , 0.5238 , 0.0009 , 1.3438 ], [ 0. , 0. , 0. , 0. ]])
通过一维布尔数组设置整行或列的值也很简单:
1 2 3 4 5 6 7 8 9 10 11 In [115 ]: data[names != 'Joe' ] = 7 In [116 ]: data Out[116 ]: array([[ 7. , 7. , 7. , 7. ], [ 1.0072 , 0. , 0.275 , 0.2289 ], [ 7. , 7. , 7. , 7. ], [ 7. , 7. , 7. , 7. ], [ 7. , 7. , 7. , 7. ], [ 0.3026 , 0.5238 , 0.0009 , 1.3438 ], [ 0. , 0. , 0. , 0. ]])
花式索引 花式索引(Fancy indexing)是一个NumPy术语,它指的是利用整数数组进行索引。
假设我们有一个8×4数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 In [117 ]: arr = np.empty((8 , 4 )) In [118 ]: for i in range (8 ): .....: arr[i] = i In [119 ]: arr Out[119 ]: array([[ 0. , 0. , 0. , 0. ], [ 1. , 1. , 1. , 1. ], [ 2. , 2. , 2. , 2. ], [ 3. , 3. , 3. , 3. ], [ 4. , 4. , 4. , 4. ], [ 5. , 5. , 5. , 5. ], [ 6. , 6. , 6. , 6. ], [ 7. , 7. , 7. , 7. ]])
为了以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray即可:
1 2 3 4 5 6 In [120 ]: arr[[4 , 3 , 0 , 6 ]] Out[120 ]: array([[ 4. , 4. , 4. , 4. ], [ 3. , 3. , 3. , 3. ], [ 0. , 0. , 0. , 0. ], [ 6. , 6. , 6. , 6. ]])
一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 In [122 ]: arr = np.arange(32 ).reshape((8 , 4 )) In [123 ]: arr Out[123 ]: 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 , 30 , 31 ]]) In [124 ]: arr[[1 , 5 , 7 , 2 ], [0 , 3 , 1 , 2 ]] Out[124 ]: array([ 4 , 23 , 29 , 10 ])
最终选出的是元素(1,0)、(5,3)、(7,1)和(2,2)。无论数组是多少维的,花式索引总是一维的。
花式索引总是将数据复制到新数组中。
数组转置和轴对换 转置返回的是源数据的视图(不会进行任何复制操作)。
数组不仅有transpose方法,还有一个特殊的T属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 In [126 ]: arr = np.arange(15 ).reshape((3 , 5 )) In [127 ]: arr Out[127 ]: array([[ 0 , 1 , 2 , 3 , 4 ], [ 5 , 6 , 7 , 8 , 9 ], [10 , 11 , 12 , 13 , 14 ]]) In [128 ]: arr.T Out[128 ]: array([[ 0 , 5 , 10 ], [ 1 , 6 , 11 ], [ 2 , 7 , 12 ], [ 3 , 8 , 13 ], [ 4 , 9 , 14 ]])
在进行矩阵计算时,经常需要用到该操作,比如利用np.dot计算矩阵内积:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [129 ]: arr = np.random.randn(6 , 3 ) In [130 ]: arr Out[130 ]: array([[-0.8608 , 0.5601 , -1.2659 ], [ 0.1198 , -1.0635 , 0.3329 ], [-2.3594 , -0.1995 , -1.542 ], [-0.9707 , -1.307 , 0.2863 ], [ 0.378 , -0.7539 , 0.3313 ], [ 1.3497 , 0.0699 , 0.2467 ]]) In [131 ]: np.dot(arr.T, arr) Out[131 ]: array([[ 9.2291 , 0.9394 , 4.948 ], [ 0.9394 , 3.7662 , -1.3622 ], [ 4.948 , -1.3622 , 4.3437 ]])
对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑子):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 In [132 ]: arr = np.arange(16 ).reshape((2 , 2 , 4 )) In [133 ]: arr Out[133 ]: array([[[ 0 , 1 , 2 , 3 ], [ 4 , 5 , 6 , 7 ]], [[ 8 , 9 , 10 , 11 ], [12 , 13 , 14 , 15 ]]]) In [134 ]: arr.transpose((1 , 0 , 2 )) Out[134 ]: array([[[ 0 , 1 , 2 , 3 ], [ 8 , 9 , 10 , 11 ]], [[ 4 , 5 , 6 , 7 ], [12 , 13 , 14 , 15 ]]])
这里,第一个轴被换成了第二个,第二个轴被换成了第一个,最后一个轴不变。
简单的转置可以使用.T,它其实就是进行轴对换而已。ndarray还有一个swapaxes方法,它需要接受一对轴编号:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 In [135 ]: arr Out[135 ]: array([[[ 0 , 1 , 2 , 3 ], [ 4 , 5 , 6 , 7 ]], [[ 8 , 9 , 10 , 11 ], [12 , 13 , 14 , 15 ]]]) In [136 ]: arr.swapaxes(1 , 2 ) Out[136 ]: array([[[ 0 , 4 ], [ 1 , 5 ], [ 2 , 6 ], [ 3 , 7 ]], [[ 8 , 12 ], [ 9 , 13 ], [10 , 14 ], [11 , 15 ]]])
swapaxes也是返回源数据的视图(不会进行任何复制操作)。
数据处理 NumPy数组使你可以将许多种数据处理任务表述为简洁的数组表达式。
用数组表达式代替循环的做法,通常被称为矢量化。一般来说,矢量化数组运算要比等价的纯Python方式快上一两个数量级(甚至更多),尤其是各种数值计算。
将条件逻辑表述为数组运算 numpy.where函数是三元表达式x if condition else y的矢量化版本。
假设我们有一个布尔数组和两个值数组:
1 2 3 4 5 6 7 8 9 10 In [165 ]: xarr = np.array([1.1 , 1.2 , 1.3 , 1.4 , 1.5 ]) In [166 ]: yarr = np.array([2.1 , 2.2 , 2.3 , 2.4 , 2.5 ]) In [167 ]: cond = np.array([True , False , True , True , False ]) In [170 ]: result = np.where(cond, xarr, yarr) In [171 ]: result Out[171 ]: array([ 1.1 , 2.2 , 1.3 , 1.4 , 2.5 ])
np.where的第二个和第三个参数不必是数组,它们都可以是标量值。
在数据分析工作中,where通常用于根据另一个数组而产生一个新的数组。
使用np.where,可以将标量和数组结合起来。
传递给where的数组大小可以不相等,甚至可以是标量值。
数学和统计方法 sum、mean以及标准差std等聚合计算(aggregation,通常叫做约简(reduction))既可以当做数组的实例方法调用,也可以当做顶级NumPy函数使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 In [177 ]: arr = np.random.randn(5 , 4 ) In [178 ]: arr Out[178 ]: array([[ 2.1695 , -0.1149 , 2.0037 , 0.0296 ], [ 0.7953 , 0.1181 , -0.7485 , 0.585 ], [ 0.1527 , -1.5657 , -0.5625 , -0.0327 ], [-0.929 , -0.4826 , -0.0363 , 1.0954 ], [ 0.9809 , -0.5895 , 1.5817 , -0.5287 ]]) In [179 ]: arr.mean() Out[179 ]: 0.19607051119998253 In [180 ]: np.mean(arr) Out[180 ]: 0.19607051119998253 In [181 ]: arr.sum () Out[181 ]: 3.9214102239996507
mean和sum这类的函数可以接受一个axis选项参数,用于计算该轴向上的统计值,最终结果是一个少一维的数组:
1 2 3 4 5 In [182 ]: arr.mean(axis=1 ) Out[182 ]: array([ 1.022 , 0.1875 , -0.502 , -0.0881 , 0.3611 ]) In [183 ]: arr.sum (axis=0 ) Out[183 ]: array([ 3.1693 , -2.6345 , 2.2381 , 1.1486 ])
这里,arr.mean(1)是“计算行的平均值”,arr.sum(0)是“计算每列的和”。
其他如cumsum和cumprod之类的方法则不聚合,而是产生一个由中间结果组成的数组:
1 2 3 4 In [184 ]: arr = np.array([0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ]) In [185 ]: arr.cumsum() Out[185 ]: array([ 0 , 1 , 3 , 6 , 10 , 15 , 21 , 28 ])
在多维数组中,累加函数(如cumsum)返回的是同样大小的数组,但是会根据每个低维的切片沿着标记轴计算部分聚类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 In [186 ]: arr = np.array([[0 , 1 , 2 ], [3 , 4 , 5 ], [6 , 7 , 8 ]]) In [187 ]: arr Out[187 ]: array([[0 , 1 , 2 ], [3 , 4 , 5 ], [6 , 7 , 8 ]]) In [188 ]: arr.cumsum(axis=0 ) Out[188 ]: array([[ 0 , 1 , 2 ], [ 3 , 5 , 7 ], [ 9 , 12 , 15 ]]) In [189 ]: arr.cumprod(axis=1 ) Out[189 ]: array([[ 0 , 0 , 0 ], [ 3 , 12 , 60 ], [ 6 , 42 , 336 ]])
用于布尔型数组的方法 在上面这些方法中,布尔值会被强制转换为1(True)和0(False)。
因此,sum经常被用来对布尔型数组中的True值计数:
1 2 3 4 In [190 ]: arr = np.random.randn(100 ) In [191 ]: (arr > 0 ).sum () Out[191 ]: 42
方法any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True:
1 2 3 4 5 6 7 In [192 ]: bools = np.array([False , False , True , False ]) In [193 ]: bools.any () Out[193 ]: True In [194 ]: bools.all () Out[194 ]: False
这两个方法也能用于非布尔型数组,所有非0元素将会被当做True。
排序 跟Python内置的列表类型一样,NumPy数组也可以通过sort方法就地排序:
1 2 3 4 5 6 7 8 9 In [195 ]: arr = np.random.randn(6 ) In [196 ]: arr Out[196 ]: array([ 0.6095 , -0.4938 , 1.24 , -0.1357 , 1.43 , -0.8469 ]) In [197 ]: arr.sort() In [198 ]: arr Out[198 ]: array([-0.8469 , -0.4938 , -0.1357 , 0.6095 , 1.24 , 1.43 ])
多维数组可以在任何一个轴向上进行排序,只需将轴编号传给sort即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 In [199 ]: arr = np.random.randn(5 , 3 ) In [200 ]: arr Out[200 ]: array([[ 0.6033 , 1.2636 , -0.2555 ], [-0.4457 , 0.4684 , -0.9616 ], [-1.8245 , 0.6254 , 1.0229 ], [ 1.1074 , 0.0909 , -0.3501 ], [ 0.218 , -0.8948 , -1.7415 ]]) In [201 ]: arr.sort(1 ) In [202 ]: arr Out[202 ]: array([[-0.2555 , 0.6033 , 1.2636 ], [-0.9616 , -0.4457 , 0.4684 ], [-1.8245 , 0.6254 , 1.0229 ], [-0.3501 , 0.0909 , 1.1074 ], [-1.7415 , -0.8948 , 0.218 ]])
顶级方法np.sort返回的是数组的已排序副本,而就地排序则会修改数组本身。
唯一化以及其它的集合逻辑 np.unique用于找出数组中的唯一值并返回已排序的结果:
1 2 3 4 5 6 7 8 9 10 11 In [206 ]: names = np.array(['Bob' , 'Joe' , 'Will' , 'Bob' , 'Will' , 'Joe' , 'Joe' ]) In [207 ]: np.unique(names) Out[207 ]: array(['Bob' , 'Joe' , 'Will' ], dtype='<U4' ) In [208 ]: ints = np.array([3 , 3 , 3 , 2 , 2 , 1 , 1 , 4 , 4 ]) In [209 ]: np.unique(ints) Out[209 ]: array([1 , 2 , 3 , 4 ])
np.in1d用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组:
1 2 3 4 In [211 ]: values = np.array([6 , 0 , 0 , 3 , 2 , 5 , 6 ]) In [212 ]: np.in1d(values, [2 , 3 , 6 ]) Out[212 ]: array([ True , False , False , True , True , False , True ], dtype=bool )
线性代数 NumPy提供了一个用于矩阵乘法的dot函数(既是一个数组方法也是numpy命名空间中的一个函数)。
伪随机数生成 numpy.random模块对Python内置的random进行了补充,增加了一些用于高效生成多种概率分布的样本值的函数。
可以用NumPy的np.random.seed更改随机数生成种子:
1 In [244 ]: np.random.seed(1234 )
numpy.random的数据生成函数使用了全局的随机种子。要避免全局状态,可以使用numpy.random.RandomState,创建一个与其它隔离的随机数生成器:
1 2 3 4 5 6 In [245 ]: rng = np.random.RandomState(1234 ) In [246 ]: rng.randn(10 ) Out[246 ]: array([ 0.4714 , -1.191 , 1.4327 , -0.3127 , -0.7206 , 0.8872 , 0.8596 , -0.6365 , 0.0157 , -2.2427 ])