Python for Data Analysis | NumPy
NumPy (Numerical Python)是高性能科学计算和数据分析的基础包。依照标准的NumPy约定,首先导入NumPy基础包。
1 In [1]: import numpy as np
ndarray是一个通用的同构数据多维容器,其中的所有元素必须是相同类型的。每个数组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象)。
1 In [2]: data1 = [6, 7.5, 8, 0, 1] 2 3 In [3]: arr1 = np.array(data1) 4 5 In [4]: arr1 6 Out[4]: array([ 6. , 7.5, 8. , 0. , 1. ]) 7 8 In [5]: data2 = [[1, 2, 3, 4], [5, 6, 7, 8]] 9 10 In [6]: arr2 = np.array(data2) 11 12 In [7]: arr2 13 Out[7]: 14 array([[1, 2, 3, 4], 15 [5, 6, 7, 8]]) 16 17 In [8]: arr2.ndim 18 Out[8]: 2 19 20 In [9]: arr2.shape 21 Out[9]: (2L, 4L) 22 23 In [10]: arr1.dtype 24 Out[10]: dtype('float64') 25 26 In [11]: arr2.dtype 27 Out[11]: dtype('int32')
除非显式说明,np.array会尝试为新建的数组推断出一个较为合适的数据类型。数据类型保存在一个特殊的dtype对象中。
除np.array之外,还有一些函数也可以新建数组。比如,zeros和ones分别可以创建指定长度或形状的全0或全1数组。empty可以创建一个没有任何具体值的数组,很多情况下,它返回的都是一些未初始化的垃圾值。arange是Python内置函数range的数组版。
1 In [12]: np.zeros(10) 2 Out[12]: array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) 3 4 In [13]: np.ones((3, 6)) 5 Out[13]: 6 array([[ 1., 1., 1., 1., 1., 1.], 7 [ 1., 1., 1., 1., 1., 1.], 8 [ 1., 1., 1., 1., 1., 1.]]) 9 10 In [14]: np.empty((2, 3, 2)) 11 Out[14]: 12 array([[[ 0.00000000e+000, 6.36598737e-314], 13 [ 0.00000000e+000, 1.27319747e-313], 14 [ 1.27319747e-313, 1.27319747e-313]], 15 16 [[ 1.27319747e-313, 1.27319747e-313], 17 [ 0.00000000e+000, 4.44659081e-323], 18 [ 2.54639495e-313, 6.42285340e-323]]]) 19 20 In [15]: np.arange(15) 21 Out[15]: 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,只不过产生的是全0数组而已 |
empty, empty_like | 创建新数组,只分配内存空间但不填充任何值 |
eye, identity | 创建一个正方的N*N单位矩阵(对角线为1,其余为0) |
ndarray的数据类型
dtype(数据类型)是一个特殊的对象,它含有ndarray将一块内存解释为特定数据类型所需的信息。
1 In [16]: arr3 = np.array([1, 2, 3], dtype=np.float64) 2 3 In [17]: arr4 = np.array([1, 2, 3], dtype=np.int32) 4 5 In [18]: arr3.dtype 6 Out[18]: dtype('float64') 7 8 In [19]: arr4.dtype 9 Out[19]: dtype('int32')
NumPy的数据类型
类型 | 类型代码 | 说明 |
int8, uint8 | i1, u1 | 有符号和无符号的8位(1个字节)整型 |
int16, uint16 | i2, u2 | 有符号和无符号的16位(2个字节)整型 |
int32, uint32 | i4, u4 | 有符号和无符号的32位(4个字节)整型 |
int64, uint64 | i8, u8 | 有符号和无符号的64位(8个字节)整型 |
float16 | f2 | 半精度浮点数 |
float32 | f4或f | 标准的单精度浮点数。与C的float兼容 |
float64 | f8或d | 标准的双精度浮点数。与C的double和Python的float对象兼容 |
float128 | f16或g | 扩展精度浮点数 |
complex64, complex128, complex256 | c8, c16, c32 | 分别用两个32位、64位或128位浮点数表示的复数 |
bool | ? | 存储True和False值的布尔类型 |
object | O | Python对象类型 |
string_ | S | 固定长度的字符串类型(每个字符1个字节)。例如,要创建一个长度为10的字符串,应使用S10 |
unicode_ | U | 固定长度的unicode类型(字节数由平台决定)。跟字符串的定义方式一样(如U10) |
可以通过ndarray的astype方法显式地转换其dtype。
1 In [20]: arr = np.array([1, 2, 3, 4, 5]) 2 3 In [21]: arr.dtype 4 Out[21]: dtype('int32') 5 6 In [22]: float_arr = arr.astype(np.float32) 7 8 In [23]: float_arr.dtype 9 Out[23]: dtype('float32')
如果将浮点数转换成整数,小数部分将会被截断。
1 In [24]: arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1]) 2 3 In [25]: arr 4 Out[25]: array([ 3.7, -1.2, -2.6, 0.5, 12.9, 10.1]) 5 6 In [26]: arr.astype(np.int32) 7 Out[26]: array([ 3, -1, -2, 0, 12, 10])
如果某字符串数组表示的全是数字,也可以用astype将其转换为数值形式。如果转换过程因为某种原因而失败了,就会引发一个TypeError。
1 In [27]: numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_) 2 3 In [28]: numeric_strings.astype(float) 4 Out[28]: array([ 1.25, -9.6 , 42. ])
调用astype会创建一个新的数组(原始数据的一份拷贝),即使新dtype跟老dtype相同也是如此。
1 In [29]: int_array = np.arange(10) 2 3 In [30]: calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64) 4 5 In [31]: int_array.astype(calibers.dtype) 6 Out[31]: array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
还可以用简洁的类型代码来表示dtype。
1 In [32]: empty_uint32 = np.empty(8, dtype='u4') 2 3 In [33]: empty_uint32 4 Out[33]: 5 array([ 0, 1075314688, 0, 1075707904, 0, 6 1075838976, 0, 1072693248], dtype=uint32)
数组很重要,因为它使你不用编写循环即可对数据执行批量运算。这通常就叫做矢量化(vectorization)。大小相等的数组之间的任何算术运算都会将运算应用到元素级。
1 In [34]: arr = np.array([[1., 2., 3.], [4., 5., 6.]]) 2 3 In [35]: arr 4 Out[35]: 5 array([[ 1., 2., 3.], 6 [ 4., 5., 6.]]) 7 8 In [36]: arr * arr 9 Out[36]: 10 array([[ 1., 4., 9.], 11 [ 16., 25., 36.]]) 12 13 In [37]: arr - arr 14 Out[37]: 15 array([[ 0., 0., 0.], 16 [ 0., 0., 0.]])
同样,数组与标量的算术运算也会将那个标量值传播到各个元素。
1 In [38]: 1 / arr 2 Out[38]: 3 array([[ 1. , 0.5 , 0.33333333], 4 [ 0.25 , 0.2 , 0.16666667]]) 5 6 In [39]: arr ** 0.5 7 Out[39]: 8 array([[ 1. , 1.41421356, 1.73205081], 9 [ 2. , 2.23606798, 2.44948974]])
不同大小的数组之间的运算叫做广播(broadcasting)。
基本的索引和切片
一维数组很简单。从表面上看,它们跟Python列表的功能差不多。
1 In [40]: arr = np.arange(10) 2 3 In [41]: arr 4 Out[41]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 5 6 In [42]: arr[5] 7 Out[42]: 5 8 9 In [43]: arr[5:8] 10 Out[43]: array([5, 6, 7]) 11 12 In [44]: arr[5:8] = 12 13 14 In [45]: arr 15 Out[45]: array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
跟列表最重要的区别在于,数组切片是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到原数组上。
1 In [46]: arr_slice = arr[5:8] 2 3 In [47]: arr_slice[1] = 12345 4 5 In [48]: arr 6 Out[48]: array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9]) 7 8 In [49]: arr_slice[:] = 64 9 10 In [50]: arr 11 Out[50]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
由于NumPy的设计目的是处理大数据,假如NumPy坚持要将数据复制来复制去会产生性能和内存的问题。
如果想要得到的是ndarray切片的一份副本而非视图,就需要显示地进行复制操作,例如:
1 In [51]: arr_copy = arr[5:8].copy() 2 3 In [52]: arr_copy 4 Out[52]: array([64, 64, 64])
对于高维度数组,能做的事情更多。在一个二维数组中,各索引位置上的元素不再是标量而是一维数组。
1 In [53]: arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 2 3 In [54]: arr2d[2] 4 Out[54]: array([7, 8, 9])
因此,可以对各个元素进行递归访问,下面两种方式是等价的。
1 In [55]: arr2d[0][2] 2 Out[55]: 3 3 4 In [56]: arr2d[0, 2] 5 Out[56]: 3
在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray(它含有高一级维度上的所有数据)。
1 In [57]: arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]) 2 3 In [58]: arr3d 4 Out[58]: 5 array([[[ 1, 2, 3], 6 [ 4, 5, 6]], 7 8 [[ 7, 8, 9], 9 [10, 11, 12]]])
括号外面的“维度”是一维、二维、三维、四维之类的意思,而括号里面的应该理解为“轴”。也就是说,返回的低维数组含有原始高维数组某条轴上的所有数据。
1 In [59]: arr3d[0] 2 Out[59]: 3 array([[1, 2, 3], 4 [4, 5, 6]])
标量值和数组都可以被赋值给arr3d[0]。
1 In [60]: old_values = arr3d[0].copy() 2 3 In [61]: arr3d[0] = 42 4 5 In [62]: arr3d 6 Out[62]: 7 array([[[42, 42, 42], 8 [42, 42, 42]], 9 10 [[ 7, 8, 9], 11 [10, 11, 12]]]) 12 13 In [63]: arr3d[0] = old_values 14 15 In [64]: arr3d 16 Out[64]: 17 array([[[ 1, 2, 3], 18 [ 4, 5, 6]], 19 20 [[ 7, 8, 9], 21 [10, 11, 12]]])
以此类推,arr3d[1, 0]可以访问索引以(1, 0)开头的那些值(以一维数组的形式返回)。
1 In [65]: arr3d[1, 0] 2 Out[65]: array([7, 8, 9])
注意,上面所有选取的数组子集,返回的数组都是视图。
切片索引
ndarray的切片语法跟Python列表这样的一维对象差不多。
高维度对象的花样更多,可以在一个或多个轴上进行切片,也可以跟整数索引混合使用。对于上面那个二维数组arr2d,其切片方式稍显不同。
1 In [66]: arr[1:6] 2 Out[66]: array([ 1, 2, 3, 4, 64]) 3 4 In [67]: arr2d 5 Out[67]: 6 array([[1, 2, 3], 7 [4, 5, 6], 8 [7, 8, 9]]) 9 10 In [68]: arr2d[:2] 11 Out[68]: 12 array([[1, 2, 3], 13 [4, 5, 6]])
可以看出,它是沿着第0轴(即第一个轴)切片的。也就是说,切片是沿着一个轴向选取元素的。可以一次传入多个切片,就像传入多个索引那样。
1 In [69]: arr2d[:2, 1:] 2 Out[69]: 3 array([[2, 3], 4 [5, 6]])
像这样进行切片时,只能得到相同维数的数组视图。通过将整数索引和切片混合,可以得到低纬度的切片。
1 In [70]: arr2d[1, :2] 2 Out[70]: array([4, 5]) 3 4 In [71]: arr2d[2, :1] 5 Out[71]: array([7])
只有冒号表示选取整个轴。
1 In [72]: arr2d[:, :1] 2 Out[72]: 3 array([[1], 4 [4], 5 [7]])
对切片表达式的赋值操作也会被扩散到整个选区。
1 In [73]: arr2d[:2, 1:] = 0 2 3 In [74]: arr2d 4 Out[74]: 5 array([[1, 0, 0], 6 [4, 0, 0], 7 [7, 8, 9]])
布尔型索引
创建一个用于存储数据的数组以及一个存储姓名的数组(含有重复项)。在这里,使用numpy.random中的randn函数生成一些正态分布的随机数据。
1 In [75]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe']) 2 3 In [76]: data = randn(7, 4) 4 5 In [77]: names 6 Out[77]: 7 array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], 8 dtype='|S4') 9 10 In [78]: data 11 Out[78]: 12 array([[-0.24163849, -0.23459994, -0.24719784, -1.20380418], 13 [-0.99264448, -0.47646621, 1.66668485, -0.69357321], 14 [-0.24069996, -0.39208598, 1.784898 , -0.01276921], 15 [-0.52683112, 1.17691902, 0.97273763, -0.34324074], 16 [-0.25789946, 2.28378994, 1.29357984, -0.40457604], 17 [ 0.09578344, -1.08720329, -0.94361066, -0.55400444], 18 [-2.65934967, -0.85699433, -0.68510993, -0.68212353]])
假设每个名字都对应data数组中的一行,而我们想要选出对应于名字“Bob”的所有行。跟算术运算一样,数组的比较运算(如==)也是矢量化的。因此,对names和执法车“Bob”的比较运算将会产生一个布尔型数组。
这个布尔型数组可用于数组索引。布尔型数组的长度必须跟被索引的轴长度一致。此外,还可以将布尔型数组跟切片、整数(或整数序列 )混合使用。
1 In [79]: names == 'Bob' 2 Out[79]: array([ True, False, False, True, False, False, False], dtype=bool) 3 4 In [80]: data[names == 'Bob'] 5 Out[80]: 6 array([[-0.24163849, -0.23459994, -0.24719784, -1.20380418], 7 [-0.52683112, 1.17691902, 0.97273763, -0.34324074]]) 8 9 In [81]: data[names == 'Bob', 2:] 10 Out[81]: 11 array([[-0.24719784, -1.20380418], 12 [ 0.97273763, -0.34324074]]) 13 14 In [82]: data[names == 'Bob', 3] 15 Out[82]: array([-1.20380418, -0.34324074])
要选择除“Bob”以外的其他值,可以使用!=或~对条件进行否定。
1 In [83]: names != 'Bob' 2 Out[83]: array([False, True, True, False, True, True, True], dtype=bool) 3 4 In [84]: data[~(names == 'Bob')] 5 Out[84]: 6 array([[-0.99264448, -0.47646621, 1.66668485, -0.69357321], 7 [-0.24069996, -0.39208598, 1.784898 , -0.01276921], 8 [-0.25789946, 2.28378994, 1.29357984, -0.40457604], 9 [ 0.09578344, -1.08720329, -0.94361066, -0.55400444], 10 [-2.65934967, -0.85699433, -0.68510993, -0.68212353]])
选取这三个名字中的两个需要组合应用多个布尔条件,使用&(和)、|(或)之类的布尔算术运算符即可。
1 In [85]: mask = (names == 'Bob') | (names == 'Will') 2 3 In [86]: mask 4 Out[86]: array([ True, False, True, True, True, False, False], dtype=bool) 5 6 In [87]: data[mask] 7 Out[87]: 8 array([[-0.24163849, -0.23459994, -0.24719784, -1.20380418], 9 [-0.24069996, -0.39208598, 1.784898 , -0.01276921], 10 [-0.52683112, 1.17691902, 0.97273763, -0.34324074], 11 [-0.25789946, 2.28378994, 1.29357984, -0.40457604]])
通过布尔型索引选取数组中的数据,将总是创建数据的副本,即使返回一模一样的数组也是如此。
通过布尔型数组设置值是一种经常用到的手段。如下操作可以将data中的所有负值都设置为0。
1 In [88]: data[data < 0] = 0 2 3 In [89]: data 4 Out[89]: 5 array([[ 0. , 0. , 0. , 0. ], 6 [ 0. , 0. , 1.66668485, 0. ], 7 [ 0. , 0. , 1.784898 , 0. ], 8 [ 0. , 1.17691902, 0.97273763, 0. ], 9 [ 0. , 2.28378994, 1.29357984, 0. ], 10 [ 0.09578344, 0. , 0. , 0. ], 11 [ 0. , 0. , 0. , 0. ]])
通过一维布尔数组设置整行或列的值也很简单。
1 In [90]: data[names != 'Joe'] = 7 2 3 In [91]: data 4 Out[91]: 5 array([[ 7. , 7. , 7. , 7. ], 6 [ 0. , 0. , 1.66668485, 0. ], 7 [ 7. , 7. , 7. , 7. ], 8 [ 7. , 7. , 7. , 7. ], 9 [ 7. , 7. , 7. , 7. ], 10 [ 0.09578344, 0. , 0. , 0. ], 11 [ 0. , 0. , 0. , 0. ]])
花式索引(Fancy indexing)
花式索引指利用整数数组进行索引。
假设有一个8*4数组。
1 In [92]: arr = np.empty((8, 4)) 2 3 In [93]: for i in range(8): 4 ....: arr[i] = i 5 ....: 6 7 In [94]: arr 8 Out[94]: 9 array([[ 0., 0., 0., 0.], 10 [ 1., 1., 1., 1.], 11 [ 2., 2., 2., 2.], 12 [ 3., 3., 3., 3.], 13 [ 4., 4., 4., 4.], 14 [ 5., 5., 5., 5.], 15 [ 6., 6., 6., 6.], 16 [ 7., 7., 7., 7.]])
为了以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray即可。
1 In [95]: arr[[4, 3, 0, 6]] 2 Out[95]: 3 array([[ 4., 4., 4., 4.], 4 [ 3., 3., 3., 3.], 5 [ 0., 0., 0., 0.], 6 [ 6., 6., 6., 6.]])
使用负数索引将会从末尾开始选取行。
1 In [96]: arr[[-3, -5, -7]] 2 Out[96]: 3 array([[ 5., 5., 5., 5.], 4 [ 3., 3., 3., 3.], 5 [ 1., 1., 1., 1.]])
一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组。
1 In [97]: arr = np.arange(32).reshape((8, 4)) 2 3 In [98]: arr 4 Out[98]: 5 array([[ 0, 1, 2, 3], 6 [ 4, 5, 6, 7], 7 [ 8, 9, 10, 11], 8 [12, 13, 14, 15], 9 [16, 17, 18, 19], 10 [20, 21, 22, 23], 11 [24, 25, 26, 27], 12 [28, 29, 30, 31]]) 13 14 In [99]: arr[[1, 5, 7, 2], [0, 3, 1, 2]] 15 Out[99]: array([ 4, 23, 29, 10])
若想得到矩形区域的形式,可使用如下两种方法。
In [100]: arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]] Out[100]: array([[ 4, 7, 5, 6], [20, 23, 21, 22], [28, 31, 29, 30], [ 8, 11, 9, 10]]) In [101]: arr[np.ix_([1, 5, 7, 2], [0, 3, 1, 2])] Out[101]: array([[ 4, 7, 5, 6], [20, 23, 21, 22], [28, 31, 29, 30], [ 8, 11, 9, 10]])
np.ix_函数可以将两个一维整数数组转换为一个用于选取方形区域的索引器。
花式索引跟切片不一样,它总是将数据复制到新数组中。
数组转置和轴对换
1 In [102]: arr = np.arange(15).reshape((3, 5)) 2 3 In [103]: arr 4 Out[103]: 5 array([[ 0, 1, 2, 3, 4], 6 [ 5, 6, 7, 8, 9], 7 [10, 11, 12, 13, 14]]) 8 9 In [104]: arr.T 10 Out[104]: 11 array([[ 0, 5, 10], 12 [ 1, 6, 11], 13 [ 2, 7, 12], 14 [ 3, 8, 13], 15 [ 4, 9, 14]])
进行矩阵计算时,经常需要用到该操作,比如利用np.dot计算矩阵内积XTX。
1 In [105]: arr = np.random.randn(6, 3) # 正态分布的数据 2 3 In [106]: np.dot(arr.T, arr) 4 Out[106]: 5 array([[ 4.68575796, -3.75884254, 0.63948286], 6 [-3.75884254, 5.68851663, -0.82923221], 7 [ 0.63948286, -0.82923221, 6.8373498 ]])
对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置。
1 In [107]: arr = np.arange(16).reshape((2, 2, 4)) 2 3 In [108]: arr 4 Out[108]: 5 array([[[ 0, 1, 2, 3], 6 [ 4, 5, 6, 7]], 7 8 [[ 8, 9, 10, 11], 9 [12, 13, 14, 15]]]) 10 11 In [109]: arr.transpose((1, 0, 2)) 12 Out[109]: 13 array([[[ 0, 1, 2, 3], 14 [ 8, 9, 10, 11]], 15 16 [[ 4, 5, 6, 7], 17 [12, 13, 14, 15]]])
ndarray还有一个swapaxes方法,它需要接受一对轴编号。
1 In [110]: arr 2 Out[110]: 3 array([[[ 0, 1, 2, 3], 4 [ 4, 5, 6, 7]], 5 6 [[ 8, 9, 10, 11], 7 [12, 13, 14, 15]]]) 8 9 In [111]: arr.swapaxes(1, 2) 10 Out[111]: 11 array([[[ 0, 4], 12 [ 1, 5], 13 [ 2, 6], 14 [ 3, 7]], 15 16 [[ 8, 12], 17 [ 9, 13], 18 [10, 14], 19 [11, 15]]])
swapaxes也是返回源数据的视图(不会进行任何复制操作)。
通用函数:快速的元素级数组函数
通用函数(ufunc)是一种对ndarray中的数据执行元素级运算的函数。你可以将其看做简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。
许多ufunc都是简单的元素级变体,如sqrt和exp,都是一元(unary) ufunc。
1 In [112]: arr = np.arange(10) 2 3 In [113]: np.sqrt(arr) 4 Out[113]: 5 array([ 0. , 1. , 1.41421356, 1.73205081, 2. , 6 2.23606798, 2.44948974, 2.64575131, 2.82842712, 3. ]) 7 8 In [114]: np.exp(arr) 9 Out[114]: 10 array([ 1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 11 2.00855369e+01, 5.45981500e+01, 1.48413159e+02, 12 4.03428793e+02, 1.09663316e+03, 2.98095799e+03, 13 8.10308393e+03])
另外一些(如add或maximum)接受2个数组,也叫二元(binary) ufunc,并返回一个结果数组。
1 In [115]: x = randn(8) 2 3 In [116]: y = randn(8) 4 5 In [117]: x 6 Out[117]: 7 array([-0.64771364, -0.18078026, -0.24693276, -2.66106796, -0.45317613, 8 1.54711649, -0.0375964 , -1.0901548 ]) 9 10 In [118]: y 11 Out[118]: 12 array([ 1.41686574, 1.20127503, 2.71963088, 0.02950306, -0.60579883, 13 -0.8918989 , 0.12507466, 0.36762386]) 14 15 In [119]: np.maximum(x, y) 16 Out[119]: 17 array([ 1.41686574, 1.20127503, 2.71963088, 0.02950306, -0.45317613, 18 1.54711649, 0.12507466, 0.36762386])
还有些ufunc可以返回多个数组。modf就是一个例子,它是Python内置函数divmod的矢量化版本,用于浮点数数组的小数和整数部分。
1 In [120]: arr = randn(7) * 5 2 3 In [121]: np.modf(arr) 4 Out[121]: 5 (array([ 0.32274642, -0.08805371, -0.74151469, -0.25121898, 0.77358351, 6 0.00854241, 0.50774374]), 7 array([ 7., -11., -1., -9., 4., 9., 5.]))
一元ufunc
函数 | 说明 |
abs, fabs | 计算整数、浮点数或复数的绝对值。对于非负数值,可以使用更快的fabs |
sqrt | 计算各元素的平方根。相当于arr ** 0.5 |
square | 计算各元素的平方。相当于arr ** 2 |
exp | 计算各元素的指数ex |
log, log10, log2, log1p | 分别为自然对数(底数为e)、底数为10的log、底数为2的log、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 |
二元ufunc
函数 | 说明 |
add | 将数组中对应的元素相加 |
subtract | 从第一个数组中减去第二个数组中的元素 |
multiply | 数组元素相乘 |
divide, floor_divide | 除法或向下圆整除法(丢弃余数) |
power | 对第一个数组中的元素A,根据第二个数组中的相应元素B,计算AB |
maximum, fmax | 元素级的最大值计算。fmax将忽略NaN |
minimum, fmin | 元素级的最小值计算。fmin将忽略NaN |
mod | 元素级的求模计算(除法的余数) |
copysign | 将第二个数组中的值的符号复制给第一个数组中的值 |
greater, greater_equal, less, less_equal, equal, not_equal | 执行元素级的比较运算,最终产生布尔型数组。相当于中缀运算符>、>=、<、<=、==、!= |
logical_and, logical_or, logical_xor |
执行元素级的真值逻辑运算。相当于中缀运算符&、|、^ |
利用数组进行数据处理
NumPy数组使你可以将许多种数据处理任务表述为简洁的数组表达式。用数组表达式代替循环的做法,通常被称为矢量化。一般来说,矢量化数组运算要比等价的纯Python方式快上一两个数量级(甚至更多),尤其是各种数值计算。
假设我们想要在一组值(网格型)上计算函数sqrt(x^2 + y^2)。np.meshgrid函数接受两个一维数组,并产生两个二维矩阵(对应于两个数组中所有的(x, y)对)。
1 In [122]: points = np.arange(-5, 5, 0.01) 2 3 In [123]: xs, ys = np.meshgrid(points, points) 4 5 In [124]: ys 6 Out[124]: 7 array([[-5. , -5. , -5. , ..., -5. , -5. , -5. ], 8 [-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99], 9 [-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98], 10 ..., 11 [ 4.97, 4.97, 4.97, ..., 4.97, 4.97, 4.97], 12 [ 4.98, 4.98, 4.98, ..., 4.98, 4.98, 4.98], 13 [ 4.99, 4.99, 4.99, ..., 4.99, 4.99, 4.99]]) 14 15 In [125]: import matplotlib.pyplot as plt 16 17 In [126]: z = np.sqrt(xs ** 2 + ys ** 2) 18 19 In [127]: z 20 Out[127]: 21 array([[ 7.07106781, 7.06400028, 7.05693985, ..., 7.04988652, 22 7.05693985, 7.06400028], 23 [ 7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 24 7.04985815, 7.05692568], 25 [ 7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 26 7.04278354, 7.04985815], 27 ..., 28 [ 7.04988652, 7.04279774, 7.03571603, ..., 7.0286414 , 29 7.03571603, 7.04279774], 30 [ 7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 31 7.04278354, 7.04985815], 32 [ 7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 33 7.04985815, 7.05692568]])
用matplotlib的imshow函数创建函数值(一个二维数组)的图形化结果。
1 In [128]: plt.imshow(z, cmap=plt.cm.gray); plt.colorbar() 2 Out[128]: <matplotlib.colorbar.Colorbar at 0x8b77550>
将条件逻辑表述为数组运算
numpy.where函数是三元表达式x if condition else y的矢量化版本。
假设我们有一个布尔数组和两个值数组。
1 In [129]: xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5]) 2 3 In [130]: yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5]) 4 5 In [131]: cond = np.array([True, False, True, True, False])
假设想要根据cond中的值选取xarr和yarr的值:当cond中的值为True时,选取xarr的值,否则从yarr中选取。列表推导式写法如下所示。
1 In [132]: result = [(x if c else y) for x, y, c in zip(xarr, yarr, cond)] 2 3 In [133]: result 4 Out[133]: [1.1000000000000001, 2.2000000000000002, 1.3, 1.3999999999999999, 2.5]
这有几个问题。第一,它对大数组的处理速度不是很快。第二,无法用于多维数组。若使用np.where,则可以将该功能写得非常简洁。
1 In [134]: result = np.where(cond, xarr, yarr) 2 3 In [135]: result 4 Out[135]: array([ 1.1, 2.2, 1.3, 1.4, 2.5])
np.where的第二个和第三个参数不必是数组,它们都可以是标量值。在数据分析工作中,where通常用于根据一个数组而产生另一个新的数组。
假设有一个由随机数据组成的矩阵,你希望将所有正值替换为2,将所有负值替换为-2。
1 In [136]: arr = randn(4, 4) 2 3 In [137]: arr 4 Out[137]: 5 array([[ 0.13208407, 0.38511885, 0.84476485, -0.42971921], 6 [ 0.89066011, 0.03607712, 0.26014312, -0.54552452], 7 [-0.80120293, 0.17089746, -1.65256178, -0.52249465], 8 [-0.78260376, 0.68144873, 1.71542368, -1.9778403 ]]) 9 10 In [138]: np.where(arr > 0, 2, -2) 11 Out[138]: 12 array([[ 2, 2, 2, -2], 13 [ 2, 2, 2, -2], 14 [-2, 2, -2, -2], 15 [-2, 2, 2, -2]]) 16 17 In [139]: np.where(arr > 0, 2, arr) 18 Out[139]: 19 array([[ 2. , 2. , 2. , -0.42971921], 20 [ 2. , 2. , 2. , -0.54552452], 21 [-0.80120293, 2. , -1.65256178, -0.52249465], 22 [-0.78260376, 2. , 2. , -1.9778403 ]])
假如有两个布尔型数组cond1和cond2,需要根据4种不同的布尔值组合实现不同的复制操作。
result = []
for i in range(n):
if cond1[i] and cond2[i]:
result.append(0)
elif cond1[i]:
result.append(1)
elif cond2[i]:
result.append(2)
else:
result.append(3)
==>
np.where(cond1 & cond2, 0,
np.where(cond1, 1,
np.where(cond2, 2, 3)))
==>
result = 1 * (cond1 ~cond2) + 2 * (cond2 & ~cond1) + 3 * ~(cond1 | cond2)
数学和统计方法
sum\mean以及标准差std等聚合计算(aggregation,通常叫做约简(reduction))既可以当做数组的实力方法调用,也可以当做顶级NumPy函数使用。
1 In [140]: arr = np.arange(20).reshape((5, 4)) 2 3 In [141]: arr 4 Out[141]: 5 array([[ 0, 1, 2, 3], 6 [ 4, 5, 6, 7], 7 [ 8, 9, 10, 11], 8 [12, 13, 14, 15], 9 [16, 17, 18, 19]]) 10 11 In [142]: arr.mean() 12 Out[142]: 9.5 13 14 In [143]: np.mean(arr) 15 Out[143]: 9.5 16 17 In [144]: arr.sum() 18 Out[144]: 190
mean和sum这类的函数可以接受一个axis参数(用于计算该轴向上的统计值),最终结果是一个少一维的数组。
1 In [145]: arr.mean(axis=1) # 横向 2 Out[145]: array([ 1.5, 5.5, 9.5, 13.5, 17.5]) 3 4 In [146]: arr_test.mean(axis=0) # 纵向 5 Out[146]: array([ 8., 9., 10., 11.]) 6 7 In [147]: arr.sum(1) # 横向 8 Out[147]: array([ 6, 22, 38, 54, 70]) 9 10 In [148]: arr.sum(0) # 纵向 11 Out[148]: array([40, 45, 50, 55])
其他如cumsum和cumprod之类的方法则不聚合,而是产生一个由中间结果组成的数组。
1 In [149]: arr.cumsum(0) # 从上往下累加 2 Out[149]: 3 array([[ 0, 1, 2, 3], 4 [ 4, 6, 8, 10], 5 [12, 15, 18, 21], 6 [24, 28, 32, 36], 7 [40, 45, 50, 55]]) 8 9 In [150]: arr.cumsum(1) # 从左往右累加 10 Out[150]: 11 array([[ 0, 1, 3, 6], 12 [ 4, 9, 15, 22], 13 [ 8, 17, 27, 38], 14 [12, 25, 39, 54], 15 [16, 33, 51, 70]]) 16 17 In [151]: arr_test.cumprod(0) # 从上往下累乘 18 Out[151]: 19 array([[ 0, 1, 2, 3], 20 [ 0, 5, 12, 21], 21 [ 0, 45, 120, 231], 22 [ 0, 585, 1680, 3465], 23 [ 0, 9945, 30240, 65835]]) 24 25 In [152]: arr.cumprod(1) # 从左往右累乘 26 Out[152]: 27 array([[ 0, 0, 0, 0], 28 [ 4, 20, 120, 840], 29 [ 8, 72, 720, 7920], 30 [ 12, 156, 2184, 32760], 31 [ 16, 272, 4896, 93024]])
方法 | 说明 |
sum | 对数组中全部或某轴向的元素求和。零长度的数组的sum为0 |
mean | 算术平均数。零长度的数组的mean为NaN |
std, var | 分别为标准差和方差,自由度可调(默认为n) |
min, max | 最小值、最大值 |
argmin, argmax | 分别为最小和最大元素的索引 |
cumsum | 所有元素的累计和 |
cumprod | 所有元素的累计积 |
用于布尔型数组的方法
布尔值会被强制转换为1 (True)和0 (False),因此,sum经常被用来对布尔型数组中的True值计数。
1 In [153]: arr = randn(100) 2 3 In [154]: (arr > 0).sum() # 正值的数量 4 Out[154]: 44
另外还有两个方法any和all,它们对布尔型数组非常有用。any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True。这两个方法也能用于非布尔型数组,所有非0元素将会被当做True。
1 In [155]: bools = np.array([False, False, True, False]) 2 3 In [156]: bools.any() 4 Out[156]: True 5 6 In [157]: bools.all() 7 Out[157]: False
排序
跟Python内置的列表类型一样,NumPy数组也可以通过sort方法就地排序。
1 In [158]: arr = randn(8) 2 3 In [159]: arr 4 Out[159]: 5 array([-1.00725508, 0.98423161, 0.29045038, -0.84720679, 0.24818793, 6 -0.69373071, -0.61944318, -0.48809313]) 7 8 In [160]: arr.sort() 9 10 In [161]: arr 11 Out[161]: 12 array([-1.00725508, -0.84720679, -0.69373071, -0.61944318, -0.48809313, 13 0.24818793, 0.29045038, 0.98423161])
多维数组可以在任何一个轴向上进行排序,只需将轴编号传给sort即可。
1 In [162]: arr = randn(5, 3) 2 3 In [163]: arr 4 Out[163]: 5 array([[-1.29648 , 0.9073108 , 0.52712817], 6 [ 2.26450484, -0.61048906, -0.80558396], 7 [-0.41270803, -1.77715014, 0.09797753], 8 [-0.79638103, 0.28536478, 0.60873928], 9 [-1.54672981, 0.00659394, 2.64116085]]) 10 11 In [164]: arr.sort(1) 12 13 In [165]: arr 14 Out[165]: 15 array([[-1.29648 , 0.52712817, 0.9073108 ], 16 [-0.80558396, -0.61048906, 2.26450484], 17 [-1.77715014, -0.41270803, 0.09797753], 18 [-0.79638103, 0.28536478, 0.60873928], 19 [-1.54672981, 0.00659394, 2.64116085]])
顶级方法np.sort返回的是数组的已排序副本,而就地排序则会修改数组本身。
计算数组分位数最简单的办法是对其进行排序,然后选取特定位置的值。
1 In [166]: large_arr = randn(1000) 2 3 In [167]: large_arr.sort() 4 5 In [168]: large_arr[int(0.05 * len(large_arr))] # 5%分位数 6 Out[168]: -1.6073993782516713
唯一化以及其他的集合逻辑
NumPy提供了一些针对一维ndarray的基本集合运算 。
最常用的np.unique用于找出数组中的唯一值并返回已排序的结果。
1 In [169]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe']) 2 3 In [170]: np.unique(names) # np.unique 4 Out[170]: 5 array(['Bob', 'Joe', 'Will'], 6 dtype='|S4') 7 8 In [171]: sorted(set(names)) # 与np.unique等价的纯Python代码 9 Out[171]: ['Bob', 'Joe', 'Will'] 10 11 In [172]: ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4]) 12 13 In [173]: np.unique(ints) 14 Out[173]: array([1, 2, 3, 4])
另一个函数np.in1d用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组。
1 In [174]: values = np.array([6, 0, 0, 3, 2, 5, 6]) 2 3 In [175]: np.in1d(values, [2, 3, 6]) 4 Out[175]: 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) | 集合的对称差,即存在于一个数组中但不同事存在于两个数组中的元素 |
用于数组的文件输入输出
将数组以二进制格式保存到磁盘
np.save和np.load是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为.npy的文件中的。
如果文件路径末尾没有扩展名.npy,,则该扩展名会被自动加上。然后就可以通过np.load读取磁盘上的数组。
1 In [176]: arr = np.arange(10) 2 3 In [177]: np.save('some_array', arr) 4 5 In [178]: np.load('some_array.npy') 6 Out[178]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
* 默认存到C:\Users\I******\下。
通过np.savez可以将多个数组保存到一个压缩文件中,将数组以关键字参数的形式传入即可。
加载.npz文件时,会得到一个类似字典的对象,该对象会对各个数组进行延迟加载。
1 In [179]: np.savez('array_archive.npz', a=arr, b=arr) 2 3 In [180]: arch = np.load('array_archive.npz') 4 5 In [181]: arch['b'] 6 Out[181]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
存取文本文件
主要介绍pandas中的read_csv和read_table函数,有时需要用到np.loadtxt或更为专门化的np.genfromtxt将数据加载到普通的NumPy数组中。
这些函数都有许多选项可供使用:指定各种分隔符、针对特定列的转换器函数、需要跳过的行数等。
以一个简单的逗号分隔文件(CSV)为例。
在桌面上准备一个array_ex.txt文件。
1 In [182]: !type C:\Users\I******\Desktop\array_ex.txt 2 1, 2, 3, 4 3 5, 6, 7, 8 4 9, 10, 11, 12 5 13, 14, 15, 16
* Linux用!cat命令。
1 In [183]: arr = np.loadtxt('C:/Users/I******/Desktop/array_ex.txt', delimiter=',') 2 3 In [184]: arr 4 Out[184]: 5 array([[ 1., 2., 3., 4.], 6 [ 5., 6., 7., 8.], 7 [ 9., 10., 11., 12.], 8 [ 13., 14., 15., 16.]])
np.savetxt执行的是相反的操作:将数组写到以某种分隔符隔开的文本文件中。genfromtxt跟loadtxt差不多,只不过它面向的是结构化数组和缺失数据处理。
线性代数
线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分。
NumPy提供了一个用于矩阵乘法的dot函数。一个二维数组跟一个大小合适的一维数组的矩阵点积运算之后将会得到一个一维数组。
1 In [185]: x = np.array([[1., 2., 3.], [4., 5., 6.]]) 2 3 In [186]: y = np.array([[6., 23.], [-1, 7], [8, 9]]) 4 5 In [187]: x 6 Out[187]: 7 array([[ 1., 2., 3.], 8 [ 4., 5., 6.]]) 9 10 In [188]: y 11 Out[188]: 12 array([[ 6., 23.], 13 [ -1., 7.], 14 [ 8., 9.]]) 15 16 In [189]: x.dot(y) # 相当于np.dot(x, y) 17 Out[189]: 18 array([[ 28., 64.], 19 [ 67., 181.]]) 20 21 In [190]: np.dot(x, np.ones(3)) 22 Out[190]: array([ 6., 15.])
numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西。它们跟MATLAB和R等语言所使用的是相同的行业标准级Fortran库,如BLAS、LAPACK、Intel MKL(取决于NumPy版本)等。
1 In [191]: from numpy.linalg import inv, qr 2 3 In [192]: X = randn(5, 5) 4 5 In [193]: mat = X.T.dot(X) 6 7 In [194]: inv(mat) 8 Out[194]: 9 array([[ 2.08148061, -3.02697504, 3.29113328, 3.63073975, 10 3.17540995], 11 [ -3.02697504, 7.43043424, -8.4159297 , -8.82368225, 12 -7.35383878], 13 [ 3.29113328, -8.4159297 , 9.77330844, 10.11068097, 14 8.49847822], 15 [ 3.63073975, -8.82368225, 10.11068097, 10.79680831, 16 8.77868064], 17 [ 3.17540995, -7.35383878, 8.49847822, 8.77868064, 18 7.96898586]]) 19 20 In [195]: mat.dot(inv(mat)) 21 Out[195]: 22 array([[ 1.00000000e+00, 1.24344979e-14, -9.76996262e-15, 23 0.00000000e+00, 2.66453526e-15], 24 [ 1.16573418e-15, 1.00000000e+00, 9.32587341e-15, 25 -1.02140518e-14, 1.09912079e-14], 26 [ 1.77635684e-15, 3.55271368e-15, 1.00000000e+00, 27 -3.55271368e-15, 3.55271368e-15], 28 [ -1.77635684e-15, -6.21724894e-15, 9.76996262e-15, 29 1.00000000e+00, 1.77635684e-15], 30 [ -2.66453526e-15, 3.55271368e-15, -3.55271368e-15, 31 -3.55271368e-15, 1.00000000e+00]]) 32 33 In [196]: q, r = qr(mat) 34 35 In [197]: r 36 Out[197]: 37 array([[-3.09191222, -5.86978804, -9.50897763, 2.86932201, 2.83097945], 38 [ 0. , -7.26666489, -1.77228669, -3.69753445, -0.76990612], 39 [ 0. , 0. , -3.77411475, 2.81521735, 0.92530753], 40 [ 0. , 0. , 0. , -1.26643317, 1.49563918], 41 [ 0. , 0. , 0. , 0. , 0.06008892]])
常用的numpy.linalg函数
函数 | 说明 |
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样本数组。而Python内置的random模块一次只能生成一个样本值。
1 In [198]: samples = np.random.normal(size=(4, 4)) 2 3 In [199]: samples 4 Out[199]: 5 array([[-0.79207267, -0.5357362 , 1.56771009, 0.91310463], 6 [-0.3306783 , 0.87415848, 0.37660745, -0.55515277], 7 [ 0.70718283, -0.05512835, 2.08685221, 0.74321819], 8 [ 0.70621388, -0.69941929, -1.17831157, -0.10862419]])
从下面的测试结果中可以看出,如果需要产生大量样本值,numpy.random快了不止一个数量级。
1 In [200]: from random import normalvariate 2 3 In [201]: N = 1000000 4 5 In [202]: %timeit samples = [normalvariate(0, 1) for _ in xrange(N)] 6 1 loop, best of 3: 1.38 s per loop 7 8 In [203]: %timeit np.random.normal(size=N) 9 10 loops, best of 3: 48 ms per loop
部分numpy.random函数
函数 | 说明 |
seed | 确定随机数生成器的种子 |
permutation | 返回一个序列的随机排列或返回一个随机排列的范围 |
shuffle | 对一个序列就地随机排列 |
rand | 产生均匀分布的样本值 |
randint | 从给定的上下限范围内随机选取整数 |
randn | 产生正态分布(平均值为0,标准差为1)的样本值,类似于MATLAB接口 |
binomial | 产生二项分布的样本值 |
normal | 产生正态(高斯)分布的样本值 |
beta | 产生Beta分布的样本值 |
chisquare | 产生卡方分布的样本值 |
gamma | 产生Gamma分布的样本值 |
uniform | 产生在[0, 1)中均匀分布的样本值 |