其他相关笔记:
- Python数据分析笔记0:前言
- Python数据分析笔记1:基本数据结构和文件
- Python数据分析笔记2:Numpy数组和矢量运算
- Python数据分析笔记3:pandas入门
- Python数据分析笔记4:数据的加载与存储
- Python数据分析笔记5:数据清洗
- Python数据分析笔记6:数据聚合、合并、重塑
- Python数据分析笔记7:绘图与可视化
- Python数据分析笔记8:数据聚合与分组运算
创建ndarray
创建数组最简单的办法就是使用array
函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组。以一个列表的转换为例:
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. ])
嵌套序列(比如由一组等长列表组成的列表)将会被转换为一个多维数组:
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]])
用ndim
和shape
验证数组属性:
In [25]: arr2.ndim Out[25]: 2 In [26]: arr2.shape Out[26]: (2, 4)
除非特别说明,np.array会尝试为新建的数组推断出一个较为合适的数据类型。数据类型保存在一个特殊的dtype
对象中。比如说,在上面的两个例子中有:
In [27]: arr1.dtype Out[27]: dtype('float64') In [28]: arr2.dtype Out[28]: dtype('int64')
用zeros
和ones
分别可以创建指定长度或形状的全0或全1数组。empty可以创建一个没有任何具体值的数组。要用这些方法创建多维数组,只需传入一个表示形状的元组即可:
In [29]: np.zeros(10) Out[29]: array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) In [30]: np.zeros((3, 6)) Out[30]: array([[ 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0.]]) In [31]: np.empty((2, 3, 2)) Out[31]: array([[[ 0., 0.], [ 0., 0.], [ 0., 0.]], [[ 0., 0.], [ 0., 0.], [ 0., 0.]]])
注意:认为np.empty会返回全0数组的想法是不安全的。很多情况下(如前所示),它返回的都是一些未初始化的垃圾值。
arange
是Python内置函数range的数组版:
In [32]: np.arange(15) Out[32]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
函数 | 说明 |
array | 将输入数据(列表、元组、数组或其它序列类型)转换成ndarray。要么推断出dtype,要么指定dtype。默认直接复制输入数据。 |
asarray | 将输入转换成ndarray,如果输入本身就是一个ndarray就不进行复制。 |
arange | 类似于内置的range ,但返回的是一个ndarray而不是列表。 |
ones ,ones_like | 根据指定的形状和dtype创建一个全1数组。ones_like 以另一个数组为参数,并根据其形状和dtype创建一个全1数组。 |
zeros ,zeros_like | 类似于ones ,ones_like |
empty ,empty_like | 创建新数组,只分配内存空间但不填充任何值。 |
full ,full_like | 用fill_value中的所有值,根据指定的形状和dtype创建一个数组。` |
eye ,identity | 创建一个正方的N*N单位矩阵。 |
通过ndarray的astype
方法明确地将一个数组从一个dtype转换成另一个dtype:
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') In [41]: arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1]) In [42]: arr Out[42]: array([ 3.7, -1.2, -2.6, 0.5, 12.9, 10.1]) In [43]: arr.astype(np.int32) Out[43]: array([ 3, -1, -2, 0, 12, 10], dtype=int32) 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.string_类型时,一定要小心,因为NumPy的字符串数据是大小固定的,发生截取时,不会发出警告。pandas提供了更多非数值数据的便利的处理方法。
astype
属性创建一个新的指定类型数组:
In [46]: int_array = np.arange(10) In [47]: calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64) In [48]: int_array.astype(calibers.dtype) Out[48]: array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
NumPy数组的运算
Numpy数组的运算会传递到每一个元素。
数组与数组:
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.]])
数组与标量:
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]])
大小相同的数组之间的比较:
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)
索引和切片
基本的索引和切片
对于一维数组:
索引操作:
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])
对切片进行操作会改变原数组的值:
In [66]: arr_slice = arr[5:8] In [67]: arr_slice Out[67]: array([12, 12, 12])
In [68]: arr_slice[1] = 12345 In [69]: arr Out[69]: array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])
arr_slice[:]
会给数组中的所有值赋值:
In [70]: arr_slice[:] = 64 In [71]: arr Out[71]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
注意:如果想要得到的是ndarray切片的一份副本而非视图,就需要明确地进行复制操作,例如
arr[5:8].copy()
。
对于高维度数组:
在一个二维数组中,各索引位置上的元素不再是标量而是一维数组:
In [72]: arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) In [73]: arr2d[2] Out[73]: array([7, 8, 9])
因此,可以对各个元素进行递归访问。可以传入一个以逗号隔开的索引列表来选取单个元素。也就是说,下面两种方式是等价的:
In [74]: arr2d[0][2] Out[74]: 3 In [75]: arr2d[0, 2] Out[75]: 3
在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray(它含有高一级维度上的所有数据)。
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数组:
In [78]: arr3d[0] Out[78]: array([[1, 2, 3], [4, 5, 6]])
标量值和数组都可以被赋值给arr3d[0]:
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]]])
进一步索引:
In [84]: arr3d[1, 0] Out[84]: array([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列表这样的一维对象差不多:
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])
对于二维数组,其切片方式稍显不同:
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]])
可以看出,它是沿着第0轴(即第一个轴)切片的。也就是说,切片是沿着一个轴向选取元素的。表达式arr2d[:2]可以被认为是“选取arr2d的前两行”。
一次传入多个切片,与数组索引类似:
In [92]: arr2d[:2, 1:] #前两行的1,2,3···列 Out[92]: array([[2, 3], [5, 6]])
通过将整数索引和切片混合,可以得到低维度的切片。
选取第二行的前两列:
选取第二行的前两列: In [93]: arr2d[1, :2] Out[93]: array([4, 5]) 选择第三列的前两行: In [94]: arr2d[:2, 2] Out[94]: array([3, 6])
对切片表达式的赋值操作也会被扩散到整个选区:
In [96]: arr2d[:2, 1:] = 0 In [97]: arr2d Out[97]: array([[1, 0, 0], [4, 0, 0], [7, 8, 9]])
布尔型索引
假设我们有一个用于存储数据的数组以及一个存储姓名的数组(含有重复项)。
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”的比较运算将会产生一个布尔型数组:
In [102]: names == 'Bob' Out[102]: array([ True, False, False, True, False, False, False], dtype=bool)
这个布尔型数组可用于数组索引:
In [103]: data[names == 'Bob'] Out[103]: array([[ 0.0929, 0.2817, 0.769 , 1.2464], [ 1.669 , -0.4386, -0.5397, 0.477 ]])
此外,还可以将布尔型数组跟切片、整数(或整数序列,稍后将对此进行详细讲解)混合使用:
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 ])
要选择除”Bob”以外的其他值,既可以使用不等于符号(!=),也可以通过~对条件进行否定:
In [106]: names != 'Bob' Out[106]: array([False, True, True, False, True, True, True], dtype=bool) In [107]: data[~(names == 'Bob')] Out[107]: array([[ 1.0072, -1.2962, 0.275 , 0.2289], [ 1.3529, 0.8864, -2.0016, -0.3718], [ 3.2489, -1.0212, -0.5771, 0.1241], [ 0.3026, 0.5238, 0.0009, 1.3438], [-0.7135, -0.8312, -2.3702, -1.8608]]) #或使用~ In [108]: cond = names == 'Bob' In [109]: data[~cond] Out[109]: array([[ 1.0072, -1.2962, 0.275 , 0.2289], [ 1.3529, 0.8864, -2.0016, -0.3718], [ 3.2489, -1.0212, -0.5771, 0.1241], [ 0.3026, 0.5238, 0.0009, 1.3438], [-0.7135, -0.8312, -2.3702, -1.8608]])
选取这三个名字中的两个需要组合应用多个布尔条件,使用&(和)、|(或)之类的布尔算术运算符即可:
In [110]: mask = (names == 'Bob') | (names == 'Will') In [111]: mask Out[111]: array([ True, False, True, True, True, False, False], dtype=bool) In [112]: data[mask] Out[112]: array([[ 0.0929, 0.2817, 0.769 , 1.2464], [ 1.3529, 0.8864, -2.0016, -0.3718], [ 1.669 , -0.4386, -0.5397, 0.477 ], [ 3.2489, -1.0212, -0.5771, 0.1241]])
通过布尔型索引选取数组中的数据,将总是创建数据的副本,即使返回一模一样的数组也是如此。
注意:Python关键字and和or在布尔型数组中无效。要使用&与|。
通过布尔型数组设置值是一种经常用到的手段。为了将data中的所有负值都设置为0,只需:
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. ]])
通过一维布尔数组设置整行或列的值:
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数组:
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即可:
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.]])
这段代码确实达到我们的要求了!使用负数索引将会从末尾开始选取行:
In [121]: arr[[-3, -5, -7]] Out[121]: array([[ 5., 5., 5., 5.], [ 3., 3., 3., 3.], [ 1., 1., 1., 1.]])
一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组:
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)。无论数组是多少维的,花式索引总是一维的。
这个花式索引的行为可能会跟某些用户的预期不一样,选取矩阵的行列子集应该是矩形区域的形式才对。下面是得到该结果的一个办法:
In [125]: arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]] Out[125]: array([[ 4, 7, 5, 6], [20, 23, 21, 22], [28, 31, 29, 30], [ 8, 11, 9, 10]])
记住,花式索引跟切片不一样,它总是将数据复制到新数组中。
数组转置和轴对换
转置是重塑的一种特殊形式,它返回的是源数据的视图(不会进行任何复制操作)。数组不仅有transpose
方法,还有一个特殊的T
属性:
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
计算矩阵内积:
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
需要得到一个由轴编号组成的元组才能对这些轴进行转置:
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]]])
这里,第一个轴被换成了第二个,第二个轴被换成了第一个,最后一个轴不变。
ndarray还有一个swapaxes
方法,它需要接受一对轴编号:
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
也是返回源数据的视图(不会进行任何复制操作)。
通用函数:快速的元素级数组函数
通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数,可以将其看做简单函数的矢量化包装器。
许多ufunc都是简单的元素级变体,如sqrt
和exp
:
In [137]: arr = np.arange(10) In [138]: arr Out[138]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [139]: np.sqrt(arr) Out[139]: array([ 0. , 1. , 1.4142, 1.7321, 2. , 2.2361, 2.4495, 2.6458, 2.8284, 3. ]) In [140]: np.exp(arr) Out[140]: array([ 1. , 2.7183, 7.3891, 20.0855, 54.5982, 148.4132, 403.4288, 1096.6332, 2980.958 , 8103.0839])
这些都是一元(unary)ufunc。另外一些(如add
或maximum
)接受2个数组(因此也叫二元(binary)ufunc),并返回一个结果数组:
In [141]: x = np.random.randn(8) In [142]: y = np.random.randn(8) In [143]: x Out[143]: array([-0.0119, 1.0048, 1.3272, -0.9193, -1.5491, 0.0222, 0.7584, -0.6605]) In [144]: y Out[144]: array([ 0.8626, -0.01 , 0.05 , 0.6702, 0.853 , -0.9559, -0.0235, -2.3042]) In [145]: np.maximum(x, y) Out[145]: array([ 0.8626, 1.0048, 1.3272, 0.6702, 0.853 , 0.0222, 0.7584, -0.6605])
modf
返回多个数组,它是Python内置函数divmod的矢量化版本,它会返回浮点数数组的小数和整数部分:
In [146]: arr = np.random.randn(7) * 5 In [147]: arr Out[147]: array([-3.2623, -6.0915, -6.663 , 5.3731, 3.6182, 3.45 , 5.0077]) In [148]: remainder, whole_part = np.modf(arr) In [149]: remainder Out[149]: array([-0.2623, -0.0915, -0.663 , 0.3731, 0.6182, 0.45 , 0.0077]) In [150]: whole_part Out[150]: array([-3., -6., -6., 5., 3., 3., 5.])
Ufuncs可以接受一个out可选参数,这样就能在数组原地进行操作:
In [151]: arr Out[151]: array([-3.2623, -6.0915, -6.663 , 5.3731, 3.6182, 3.45 , 5.0077]) In [152]: np.sqrt(arr) Out[152]: array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378]) In [153]: np.sqrt(arr, arr) Out[153]: array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378]) In [154]: arr Out[154]: array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])
函数 | 说明 |
abs ,fabs | 计算整数、浮点数或复数的绝对值。对于非负数值,可以使用更快的fabs 。 |
sqrt | 计算各元素的平方根。相当于arr**0.5 |
square | 计算各元素的平方。相当于arr**2 。 |
exp | 计算各元素的自然指数。 |
log ,log10 ,log2 ,log1p | 分别为自然对数、底数为10的对数、底数为2的对数、log(1+x)。 |
sign | 计算各元素的正负号:1、0、-1。 |
ceil | 计算各元素的ceiling值,即大于等于该值的最小整数。 |
floor | 计算各元素的floor值,即小于等于该值的最大整数。 |
rint | 将各元素四舍五入到最接近的整数。保留dtype。 |
modf | 将数组的小数和整数部分以两个独立的数组返回。 |
isnan | 返回一个表示“哪些值是NaN”的布尔型数组。 |
isfinite ,isinf | 分别返回一个表示“哪些元素是有穷的(非inf、非NaN)”或“哪些元素是无穷的”的布尔型数组。 |
cos ,cosh ,sin ,sinh ,tan ,tanh | 普通型和双曲型三角函数。 |
arccos ,arccosh ,arcsin ,arcsinh ,arctan ,arctanh | 反三角函数。 |
logical_not | 计算各元素not x的真值。相当于-arr 。 |
函数 | 说明 |
add | 将数组中对应的元素相加。 |
subtract | c从第一个数组中减去第二个数组中的元素。 |
multiply | 数组元素相乘。 |
divide ,floor_divide | 除法或向下圆整除法(丢弃余数)。 |
power | 对第一个数组中的元素A,根据第二个数组中的元素B,计算A^B。 |
maximum ,fmax | 元素级的最大值计算。fmax将忽略NaN。 |
minimun ,fmin | 元素级的最小值计算。fmin将忽略NaN。 |
mod | 元素级的求模运算。 |
copysign | 将第二个数组中的值的符号复制给第一个数组。 |
greater ,greater_equal ,less ,less_equal ,equal ,not_equal | 元素级的比较运算。 |
logical_and ,logical_or ,logical_xor | 元素级的真值逻辑运算。 |
利用数组进行数据处理
作为简单的例子,假设我们想要在一组值(网格型)上计算函数sqrt(x^2+y^2)
。
np.meshgrid
函数接受两个一维数组,并产生两个二维矩阵(对应于两个数组中所有的(x,y)对):
In [155]: points = np.arange(-5, 5, 0.01) # 1000 equally spaced points In [156]: xs, ys = np.meshgrid(points, points) In [157]: ys Out[157]: array([[-5. , -5. , -5. , ..., -5. , -5. , -5. ], [-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99], [-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98], ..., [ 4.97, 4.97, 4.97, ..., 4.97, 4.97, 4.97], [ 4.98, 4.98, 4.98, ..., 4.98, 4.98, 4.98], [ 4.99, 4.99, 4.99, ..., 4.99, 4.99, 4.99]])
现在,把这两个数组当做两个浮点数那样编写表达式即可:
In [158]: z = np.sqrt(xs ** 2 + ys ** 2) In [159]: z Out[159]: array([[ 7.0711, 7.064 , 7.0569, ..., 7.0499, 7.0569, 7.064 ], [ 7.064 , 7.0569, 7.0499, ..., 7.0428, 7.0499, 7.0569], [ 7.0569, 7.0499, 7.0428, ..., 7.0357, 7.0428, 7.0499], ..., [ 7.0499, 7.0428, 7.0357, ..., 7.0286, 7.0357, 7.0428], [ 7.0569, 7.0499, 7.0428, ..., 7.0357, 7.0428, 7.0499], [ 7.064 , 7.0569, 7.0499, ..., 7.0428, 7.0499, 7.0569]])
将条件逻辑表述为数组运算
numpy.where
函数是三元表达式x if condition else y
的矢量化版本。假设我们有一个布尔数组和两个值数组:
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])
假设我们想要根据cond中的值选取xarr和yarr的值:当cond中的值为True时,选取xarr的值,否则从yarr中选取。列表推导式的写法应该如下所示:
In [168]: result = [(x if c else y) .....: for x, y, c in zip(xarr, yarr, cond)] In [169]: result Out[169]: [1.1000000000000001, 2.2000000000000002, 1.3, 1.3999999999999999, 2.5]
若使用np.where
,则可以将该功能写得非常简洁:
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
的第二个和第三个参数不必是数组,它们都可以是标量值。假设有一个由随机数据组成的矩阵,希望将所有正值替换为2,将所有负值替换为-2:
In [172]: arr = np.random.randn(4, 4) In [173]: arr Out[173]: array([[-0.5031, -0.6223, -0.9212, -0.7262], [ 0.2229, 0.0513, -1.1577, 0.8167], [ 0.4336, 1.0107, 1.8249, -0.9975], [ 0.8506, -0.1316, 0.9124, 0.1882]]) In [174]: arr > 0 Out[174]: array([[False, False, False, False], [ True, True, False, True], [ True, True, True, False], [ True, False, True, True]], dtype=bool) In [175]: np.where(arr > 0, 2, -2) Out[175]: array([[-2, -2, -2, -2], [ 2, 2, -2, 2], [ 2, 2, 2, -2], [ 2, -2, 2, 2]])
使用np.where
,可以将标量和数组结合起来。例如,用常数2替换arr中所有正的值:
In [176]: np.where(arr > 0, 2, arr) # set only positive values to 2 Out[176]: array([[-0.5031, -0.6223, -0.9212, -0.7262], [ 2. , 2. , -1.1577, 2. ], [ 2. , 2. , 2. , -0.9975], [ 2. , -0.1316, 2. , 2. ]])
传递给where的数组大小可以不相等,甚至可以是标量值。
数学和统计方法
生成一些正态分布随机数据,然后做聚类统计。
mean
和sum
函数:
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选项参数,用于计算该轴向上的统计值,最终结果是一个少一维的数组:
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
之类的方法则不聚合,而是产生一个由中间结果组成的数组:
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)返回的是同样大小的数组,但是会根据每个低维的切片沿着标记轴计算部分聚类:
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]])
方法 | 说明 |
sum | 对数组中全部或某轴向的元素求和。 |
mean | 算术平均数。 |
std ,var | 求标准差和方差,自由可调整,默认为n。 |
min ,max | 最小值和最大值。 |
argmin ,argmax | 分别为最大和最小元素的索引。 |
cumsum ,cumprod | 所有元素的累计和和累计积。 |
用于布尔型数组的方法
在上面这些方法中,布尔值会被强制转换为1(True)和0(False)。因此sum
经常被用来对布尔型数组中的True值计数:
In [190]: arr = np.random.randn(100) In [191]: (arr > 0).sum() # Number of positive values Out[191]: 42
另外还有两个方法any
和all
,它们对布尔型数组非常有用。any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True:
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
方法就地排序:
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即可:
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
返回的是数组的已排序副本,而就地排序则会修改数组本身。计算数组分位数最简单的办法是对其进行排序,然后选取特定位置的值:
In [203]: large_arr = np.random.randn(1000) In [204]: large_arr.sort() In [205]: large_arr[int(0.05 * len(large_arr))] # 5% quantile Out[205]: -1.5311513550102103
唯一化以及其它的集合逻辑
NumPy提供了一些针对一维ndarray的基本集合运算。
最常用的可能要数np.unique
了,它用于找出数组中的唯一值并返回已排序的结果:
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.unique等价的纯Python代码来对比一下:
In [210]: sorted(set(names)) Out[210]: ['Bob', 'Joe', 'Will']
另一个函数np.in1d
用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组:
In [211]: values = np.array([6, 0, 0, 3, 2, 5, 6]) In [212]: np.in1d(values, [2, 3, 6]) #检查数组的元素是否等于2或3或6 Out[212]: array([ True, False, False, True, True, False, True], dtype=bool)
方法 | 说明 |
unique(x) | 计算x中的唯一元素,并返回有序结果。 |
intersect1d(x,y) | 计算x和y中的公共元素,并返回有序结果。 |
union1d(x,y) | 计算x和y的并集,并返回有序结果。 |
in1d(x,y) | 得到一个表示“x的元素是否包含于y”的布尔型数组。 |
setdiff1d(x,y) | 集合的差,即元素在x中且不在y中。 |
setxor1d(x,y) | 集合的对称差,即存在于一个数组中但不同时存在于两个数组中的元素。 |
用于数组的文件输入输出
NumPy能够读写磁盘上的文本数据或二进制数据。
np.save
和np.load
是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为.npy的文件中的:
In [213]: arr = np.arange(10) In [214]: np.save('some_array', arr)
如果文件路径末尾没有扩展名.npy,则该扩展名会被自动加上。然后就可以通过np.load读取磁盘上的数组:
In [215]: np.load('some_array.npy') Out[215]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
通过np.savez
可以将多个数组保存到一个未压缩文件中,将数组以关键字参数的形式传入即可:
In [216]: np.savez('array_archive.npz', a=arr, b=arr)
加载.npz文件时,你会得到一个类似字典的对象,该对象会对各个数组进行延迟加载:
In [217]: arch = np.load('array_archive.npz') In [218]: arch['b'] Out[218]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
如果要将数据压缩,可以使用numpy.savez_compressed
:
In [219]: np.savez_compressed('arrays_compressed.npz', a=arr, b=arr)
线性代数
线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分。不像某些语言(如MATLAB),通过*对两个二维数组相乘得到的是一个元素级的积,而不是一个矩阵点积。因此,NumPy提供了一个用于矩阵乘法的dot
函数(既是一个数组方法也是numpy命名空间中的一个函数):
In [223]: x = np.array([[1., 2., 3.], [4., 5., 6.]]) In [224]: y = np.array([[6., 23.], [-1, 7], [8, 9]]) In [225]: x Out[225]: array([[ 1., 2., 3.], [ 4., 5., 6.]]) In [226]: y Out[226]: array([[ 6., 23.], [ -1., 7.], [ 8., 9.]]) In [227]: x.dot(y) Out[227]: array([[ 28., 64.], [ 67., 181.]]) #x.dot(y)等价于np.dot(x, y): In [228]: np.dot(x, y) Out[228]: array([[ 28., 64.], [ 67., 181.]])
一个二维数组跟一个大小合适的一维数组的矩阵点积运算之后将会得到一个一维数组:
In [229]: np.dot(x, np.ones(3)) Out[229]: array([ 6., 15.])
@
符(类似Python 3.5)也可以用作中缀运算符,进行矩阵乘法:
In [230]: x @ np.ones(3) Out[230]: array([ 6., 15.])
numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西。它们跟MATLAB和R等语言所使用的是相同的行业标准线性代数库,如BLAS、LAPACK、Intel MKL等:
In [231]: from numpy.linalg import inv, qr In [232]: X = np.random.randn(5, 5) In [233]: mat = X.T.dot(X) #X.T.dot(X)计算X和它的转置X.T的点积。 In [234]: inv(mat) Out[234]: array([[ 933.1189, 871.8258, -1417.6902, -1460.4005, 1782.1391], [ 871.8258, 815.3929, -1325.9965, -1365.9242, 1666.9347], [-1417.6902, -1325.9965, 2158.4424, 2222.0191, -2711.6822], [-1460.4005, -1365.9242, 2222.0191, 2289.0575, -2793.422 ], [ 1782.1391, 1666.9347, -2711.6822, -2793.422 , 3409.5128]]) In [235]: mat.dot(inv(mat)) #计算X与它的逆矩阵的点积 Out[235]: array([[ 1., 0., -0., -0., -0.], [-0., 1., 0., 0., 0.], [ 0., 0., 1., 0., 0.], [-0., 0., 0., 1., -0.], [-0., 0., 0., 0., 1.]]) In [236]: q, r = qr(mat) #计算qr分解 In [237]: r Out[237]: array([[-1.6914, 4.38 , 0.1757, 0.4075, -0.7838], [ 0. , -2.6436, 0.1939, -3.072 , -1.0702], [ 0. , 0. , -0.8138, 1.5414, 0.6155], [ 0. , 0. , 0. , -2.6445, -2.1669], [ 0. , 0. , 0. , 0. , 0.0002]])
函数 | 说明 |
diag | 以一维数组的形式返回方阵的对角线(或非对角线元素),或将一维数组转为方阵(非对角线元素为0)。 |
dot | 矩阵乘法。 |
trace | 计算对角线元素的和。 |
det | 计算矩阵行列式。 |
eig | 计算方阵的本征值和本征向量。 |
inv | 计算方阵的逆。 |
pinv | 计算矩阵的Moore-Penrose伪逆。 |
qr | 计算QR分解。 |
svd | 计算奇异值分解SVD。 |
solve | 解线性方程组Ax=b,其中A为一个方阵。 |
lstsq | 计算AX=b的最小二乘解。 |
伪随机数生成
numpy.random模块对Python内置的random
进行了补充,增加了一些用于高效生成多种概率分布的样本值的函数。例如,你可以用normal
来得到一个标准正态分布的4×4样本数组:
In [238]: samples = np.random.normal(size=(4, 4)) In [239]: samples Out[239]: array([[ 0.5732, 0.1933, 0.4429, 1.2796], [ 0.575 , 0.4339, -0.7658, -1.237 ], [-0.5367, 1.8545, -0.92 , -0.1082], [ 0.1525, 0.9435, -1.0953, -0.144 ]])
可以用NumPy的np.random.seed
更改随机数生成种子:
In [244]: np.random.seed(1234)
numpy.random
的数据生成函数使用了全局的随机种子。要避免全局状态,可以使用numpy.random.RandomState
,创建一个与其它隔离的随机数生成器:
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])
函数 | 说明 |
seed | 确定随机数生成器的种子。 |
permutation | 返回一个序列的随机排列或返回一个随机排列的范围。 |
shuffle | 对一个序列就地随机排序。 |
rand | 产生均匀分布的样本值。 |
randint | 从给定的上下限范围内随机选取整数。 |
randn | 产生标准正态分布的样本值。 |
binomal | 产生二项分布的样本值。 |
normal | 产生正态分布的样本值。 |
beta | 产生beta分布的样本值。 |
chiquare | 产生卡方分布的样本值。 |
gamma | 产生gamma分布的样本值。 |
uniform | 产生在[0,1)中均匀分布的样本值。 |