乐趣区

关于人工智能:Numpy基础知识回顾

文章和代码等曾经归档至【Github 仓库:https://github.com/timerring/dive-into-AI】或者公众号【AIShareLab】回复 python 数据分析 也可获取。

NumPy 的局部性能如下:

  • ndarray,一个具备 矢量算术运算 简单播送能力 的疾速且节俭空间的多维数组。
  • 用于对整组数据进行疾速运算的规范数学函数(无需编写循环)。
  • 用于读写磁盘数据的工具以及用于操作内存映射文件的工具。
  • 线性代数、随机数生成以及傅里叶变换性能。
  • 用于集成由 C、C++、Fortran 等语言编写的代码的 A C API。

因为 NumPy 提供了一个简略易用的 C API,因而很容易将数据传递给由低级语言编写的内部库,内部库也能以 NumPy 数组的模式将数据返回给 Python。这个性能使 Python 成为一种包装 C /C++/Fortran 历史代码库的抉择,并使被包装库领有一个动静的、易用的接口。

NumPy 自身并没有提供如许高级的数据分析性能,了解 NumPy 数组以及面向数组的计算将有助于你更加高效地应用诸如 pandas 之类的工具。

对于大部分数据分析利用而言,我最关注的性能次要集中在:

  • 用于数据整顿和清理、子集结构和过滤、转换等疾速的矢量化数组运算。
  • 罕用的数组算法,如排序、惟一化、汇合运算等。
  • 高效的描述统计和数据聚合 / 摘要运算。
  • 用于异构数据集的合并 / 连贯运算的数据对齐和关系型数据运算。
  • 将条件逻辑表述为数组表达式(而不是带有 if-elif-else 分支的循环)。
  • 数据的分组运算(聚合、转换、函数利用等)。

pandas 提供了一些 NumPy 所没有的畛域特定的性能,如工夫序列解决等。

NumPy 之于数值计算特地重要的起因之一,是因为它能够高效解决大数组的数据。这是因为:

  • NumPy 是在一个间断的内存块中存储数据,独立于其余 Python 内置对象。NumPy 的 C 语言编写的算法库能够操作内存,而不用进行类型查看或其它前期工作。比起 Python 的内置序列,NumPy 数组应用的内存更少
  • NumPy 能够在整个数组上执行简单的计算,而不须要 Python 的 for 循环

要搞明确具体的性能差距,考查一个蕴含一百万整数的数组,和一个等价的 Python 列表:

In [7]: import numpy as np

In [8]: my_arr = np.arange(1000000)

In [9]: my_list = list(range(1000000))

各个序列别离乘以 2:

In [10]: %time for _ in range(10): my_arr2 = my_arr * 2
CPU times: user 20 ms, sys: 50 ms, total: 70 ms
Wall time: 72.4 ms

In [11]: %time for _ in range(10): my_list2 = [x * 2 for x in my_list]
CPU times: user 760 ms, sys: 290 ms, total: 1.05 s
Wall time: 1.05 s

基于 NumPy 的算法要比纯 Python 快 10 到 100 倍(甚至更快),并且应用的内存更少。

4.1 NumPy 的 ndarray:一种多维数组对象

NumPy 最重要的一个特点就是其 N 维数组对象(即 ndarray),该对象是一个疾速而灵便的大数据集容器。你能够利用这种数组对整块数据执行一些数学运算,其语法跟标量元素之间的运算一样。

要明确 Python 是如何利用与标量值相似的语法进行批次计算,我先引入 NumPy,而后生成一个蕴含随机数据的小数组:

In [12]: import numpy as np

# Generate some random data
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]])

第一个例子中,所有的元素都乘以 10。第二个例子中,每个元素都与本身相加。

ndarray 是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是雷同类型的。每个数组都有一个 shape(一个示意各维度大小的元组)和一个 dtype(一个用于阐明数组数据类型的对象):

In [17]: data.shape
Out[17]: (2, 3)

In [18]: data.dtype
Out[18]: dtype('float64')

创立 ndarray

array 函数

创立数组最简略的方法就是应用 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

因为 data2 是列表的列表,NumPy 数组 arr2 的两个维度的 shape 是从 data2 引入的。能够用属性 ndimshape 验证:

In [25]: arr2.ndim
Out[25]: 2

In [26]: arr2.shape
Out[26]: (2, 4)
dtype

除非特地阐明,np.array 会尝试 为新建的这个数组推断出一个较为适合的数据类型。数据类型保留在一个非凡的 dtype 对象中。比如说,在下面的两个例子中,咱们有:

In [27]: arr1.dtype
Out[27]: dtype('float64')

In [28]: arr2.dtype
Out[28]: dtype('int64')
zeros 和 ones 和 empty

np.array 之外,还有一些函数也能够新建数组。比方,zerosones 别离能够创立指定长度或形态的全 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

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])

表 4 - 1 列出了一些数组创立函数。因为 NumPy 关注的是数值计算,因而,如果没有特地指定,数据类型根本都是 float64(浮点数)。

ndarray 的数据类型

dtype(数据类型)是一个非凡的对象,它含有 ndarray 将一块内存解释为特定数据类型所需的信息:

In [33]: arr1 = np.array([1, 2, 3], dtype=np.float64)

In [34]: arr2 = np.array([1, 2, 3], dtype=np.int32)

In [35]: arr1.dtype
Out[35]: dtype('float64')

In [36]: arr2.dtype
Out[36]: dtype('int32')

dtype 是 NumPy 灵便交互其它零碎的源泉之一。少数状况下,它们间接映射到相应的机器示意,这使得“读写磁盘上的二进制数据流”以及“集成低级语言代码(如 C、Fortran)”等工作变得更加简略。数值型 dtype 的命名形式雷同:一个类型名(如 float 或 int),前面跟一个用于示意各元素位长的数字。规范的双精度浮点值(即 Python 中的 float 对象)须要占用 8 字节(即 64 位)。因而,该类型在 NumPy 中就记作 float64。表 4 - 2 列出了 NumPy 所反对的全副数据类型。

笔记:记不住这些 NumPy 的 dtype 也没关系,老手更是如此。通常只须要晓得你所解决的数据的大抵类型是浮点数、复数、整数、布尔值、字符串,还是一般的 Python 对象即可。当你须要控制数据在内存和磁盘中的存储形式时(尤其是对大数据集),那就得理解如何管制存储类型。

astype

你能够通过 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')

如果某字符串数组示意的全是数字,也能够用 astype 将其转换为数值模式:

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 提供了更多非数值数据的便当的解决办法。

如果转换过程因为某种原因而失败了(比方某个不能被转换为 float64 的字符串),就会引发一个 ValueError。这里,我比拟懒,写的是 float 而不是 np.float64;NumPy 很聪慧,它会将 Python 类型映射到等价的 dtype 上。

数组的 dtype 还有另一个属性:

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.])

你还能够用简洁的类型代码来示意 dtype:

In [49]: empty_uint32 = np.empty(8, dtype='u4')

In [50]: empty_uint32
Out[50]: 
array([         0, 1075314688,          0, 1075707904,          0,
       1075838976,          0, 1072693248], dtype=uint32)

笔记:调用 astype 总会创立一个新的数组(一个数据的备份),即便新的 dtype 与旧的 dtype 雷同。

NumPy 数组的运算

数组很重要,因为它使你不必编写循环即可对数据执行批量运算。NumPy 用户称其为 矢量化(vectorization)。大小相等的数组之间的任何算术运算都会将运算利用到元素级:

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)

不同大小的数组之间的运算叫做播送(broadcasting)。

根本的索引和切片

NumPy 数组的索引是一个内容丰盛的主题,因为选取数据子集或单个元素的形式有很多。一维数组很简略。从外表上看,它们跟 Python 列表的性能差不多:

In [60]: arr = np.arange(10)

In [61]: arr
Out[61]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

如上所示,当你将一个标量值赋值给一个切片时(如 arr[5:8]=12),该值会主动流传(也就说前面将会讲到的“播送”)到整个选区。

跟列表最重要的区别在于,数组切片是原始数组的视图 。这意味着数据不会被复制, 视图上的任何批改都会间接反映到源数组上

作为例子,先创立一个 arr 的切片:

In [66]: arr_slice = arr[5:8]

In [67]: arr_slice
Out[67]: array([12, 12, 12])

当初,当我批改 arr_slice 中的值,变动也会体现在原始数组 arr 中:

In [68]: arr_slice[1] = 12345

In [69]: arr
Out[69]: array([    0,     1,     2,     3,     4,    12, 12345,    12,     8,   
  9])

因为 NumPy 的设计目标是解决大数据,所以你能够设想一下,如果 NumPy 保持要将数据复制来复制去的话会产生何等的性能和内存问题。

留神:如果你想要失去的是 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

图 4 - 1 阐明了二维数组的索引形式。轴 0 作为行,轴 1 作为列。

在多维数组中,如果省略了前面的索引,则返回对象会是一个维度低一点的 ndarray(它含有高一级维度上的所有数据)。因而,在 2×2×3 数组 arr3d 中:

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]]])

类似的,arr3d[1,0]能够拜访索引以 (1,0) 结尾的那些值(以一维数组的模式返回):

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])

对于之前的二维数组 arr2d,其切片形式稍显不同:

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:]
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])

图 4 - 2 对此进行了阐明。留神,“只有冒号”示意选取整个轴,因而你能够像上面这样只对高维轴进行切片:

In [95]: arr2d[:, :1]
Out[95]: 
array([[1],
       [4],
       [7]])

天然,对切片表达式的赋值操作也会被扩散到整个选区:

In [96]: arr2d[:2, 1:] = 0

In [97]: arr2d
Out[97]: 
array([[1, 0, 0],
       [4, 0, 0],
       [7, 8, 9]])

布尔型索引

来看这样一个例子,假如咱们有一个用于存储数据的数组以及一个存储姓名的数组(含有反复项)。在这里,我将应用 numpy.random 中的 randn 函数生成一些正态分布的随机数据:

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 [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' 的行,并索引了列:

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.]])

前面会看到,这类二维数据的操作也能够用 pandas 不便的来做。

花式索引

花式索引(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]]])

这里,第一个轴被换成了第二个,第二个轴被换成了第一个,最初一个轴不变。

简略的转置能够应用.T,它其实就是进行轴对换而已。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 也是返回源数据的视图(不会进行任何复制操作)。

4.2 通用函数:疾速的元素级数组函数

sqrt 和 exp

通用函数(即 ufunc)是一种对 ndarray 中的数据执行元素级运算的函数。你能够将其看做简略函数(承受一个或多个标量值,并产生一个或多个标量值)的 矢量化包装器

许多 ufunc 都是简略的元素级变体,如 sqrtexp

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])
maximum

这里,numpy.maximum计算了 x 和 y 中元素级别最大的元素。

modf

尽管并不常见,但有些 ufunc 确实能够返回多个数组。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])

罕用的通用函数

表 4 - 3 和表 4 - 4 别离列出了一些一元和二元 ufunc。

4.3 利用数组进行数据处理

用数组表达式代替循环的做法,通常被称为矢量化。一般来说,矢量化数组运算要比等价的纯 Python 形式快上一两个数量级(甚至更多)。在前面内容中将介绍播送,这是一种针对矢量化计算的弱小伎俩。

作为简略的例子,假如咱们想要在一组值(网格型)上计算函数 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]])

作为第 9 章的先导,我用 matplotlib 创立了这个二维数组的可视化:

In [160]: import matplotlib.pyplot as plt

In [161]: plt.imshow(z, cmap=plt.cm.gray); plt.colorbar()
Out[161]: <matplotlib.colorbar.Colorbar at 0x7f715e3fa630>

In [162]: plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
Out[162]: <matplotlib.text.Text at 0x7f715d2de748>

见图 4 -3。这张图是用 matplotlib 的 imshow 函数创立的。

将条件逻辑表述为数组运算

numpy.where

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 的 第二个和第三个参数不用是数组,它们都能够是标量值 。在数据分析工作中,where 通常用于 依据另一个数组而产生一个新的数组。假如有一个由随机数据组成的矩阵,你心愿将所有正值替换为 2,将所有负值替换为-2。若利用 np.where,则会非常简单:

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 的数组大小能够不相等,甚至能够是标量值。

数学和统计办法

sum、mean 以及 std

能够通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean 以及标准差 std 等聚合计算(aggregation,通常叫做约简(reduction))既能够当做 数组的实例办法调用,也能够当做顶级 NumPy 函数 应用。

这里,我生成了一些正态分布随机数据,而后做了聚类统计:

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]])

罕用的统计函数

表 4 - 5 列出了全副的根本数组统计办法。后续章节中有很多例子都会用到这些办法。

用于布尔型数组的办法

通过布尔数组计数

在下面这些办法中,布尔值会被强制转换为 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 和 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

在 pandas 中还能够找到一些其余跟排序无关的数据操作(比方依据一列或多列对表格型数据进行排序)。

惟一化以及其它的汇合逻辑

np.unique

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

另一个函数 np.in1d 用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组:

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)

罕用汇合函数

4.4 用于数组的文件输入输出

NumPy 可能读写磁盘上的文本数据或二进制数据。只探讨 NumPy 的内置二进制格局,因为更多的用户会应用 pandas 或其它工具加载文本或表格数据。

np.save 和 np.load

np.savenp.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

通过 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

如果要将数据压缩,能够应用 numpy.savez_compressed:

In [219]: np.savez_compressed('arrays_compressed.npz', a=arr, b=arr)

4.5 线性代数

dot

线性代数(如矩阵乘法、矩阵合成、行列式以及其余方阵数学等)是任何数组库的重要组成部分。不像某些语言(如 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(Math Kernel Library,可能有,取决于你的 NumPy 版本)等:

In [231]: from numpy.linalg import inv, qr

In [232]: X = np.random.randn(5, 5)

In [233]: mat = X.T.dot(X)

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))
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)

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]])

表达式 X.T.dot(X)计算 X 和它的转置 X.T 的点积。

线性代数函数

表 4 - 7 中列出了一些最罕用的线性代数函数。

4.6 伪随机数生成

np.random.normal

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]])

而 Python 内置的 random 模块则只能一次生成一个样本值。从上面的测试后果中能够看出,如果须要产生大量样本值,numpy.random 快了不止一个数量级:

In [240]: from random import normalvariate

In [241]: N = 1000000

In [242]: %timeit samples = [normalvariate(0, 1) for _ in range(N)]
1.77 s +- 126 ms per loop (mean +- std. dev. of 7 runs, 1 loop each)

In [243]: %timeit np.random.normal(size=N)
61.7 ms +- 1.32 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)

随机种子(全局与非全局)!

咱们说这些都是 伪随机数 ,是因为它们都是通过 算法基于随机数生成器种子,在确定性的条件下生成的 。你能够用 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])

numpy.random 中的局部函数

4.7 示例:随机散步

咱们通过模仿随机散步来阐明如何使用数组运算。先来看一个简略的随机散步的例子:从 0 开始,步长 1 和-1 呈现的概率相等。

上面是一个通过内置的 random 模块以纯 Python 的形式实现 1000 步的随机散步:

In [247]: import random
   .....: position = 0
   .....: walk = [position]
   .....: steps = 1000
   .....: for i in range(steps):
   .....:     step = 1 if random.randint(0, 1) else -1
   .....:     position += step
   .....:     walk.append(position)
   .....:

图 4 - 4 是依据前 100 个随机散步值生成的折线图:

In [249]: plt.plot(walk[:100])

不难看出,这其实就是随机散步中各步的累计和,能够用一个数组运算来实现。因而,我用 np.random 模块一次性随机产生 1000 个“掷硬币”后果(即两个数中任选一个),将其别离设置为 1 或-1,而后计算累计和:

In [251]: nsteps = 1000

In [252]: draws = np.random.randint(0, 2, size=nsteps)

In [253]: steps = np.where(draws > 0, 1, -1)

In [254]: walk = steps.cumsum()

有了这些数据之后,咱们就能够沿着散步门路做一些统计工作了,比方求取最大值和最小值:

In [255]: walk.min()
Out[255]: -3

In [256]: walk.max()
Out[256]: 31

当初来看一个简单点的统计工作——首次穿梭工夫,即随机散步过程中第一次达到某个特定值的工夫。假如咱们想要晓得本次随机散步须要多久能力间隔初始 0 点至多 10 步远(任一方向均可)。np.abs(walk)>=10 能够失去一个布尔型数组,它示意的是间隔是否达到或超过 10,而咱们想要晓得的是第一个 10 或-10 的索引。能够用 argmax 来解决这个问题,它返回的是该布尔型数组第一个最大值的索引(True 就是最大值):

In [257]: (np.abs(walk) >= 10).argmax()
Out[257]: 37

留神,这里应用 argmax 并不是很高效,因为它无论如何都会对数组进行齐全扫描。在本例中,只有发现了一个 True,那咱们就晓得它是个最大值了。

一次模仿多个随机散步

如果你心愿模仿多个随机散步过程(比方 5000 个),只需对下面的代码做一点点批改即可生成所有的随机散步过程。只有给 numpy.random 的函数传入一个二元元组就能够产生一个二维数组,而后咱们就能够一次性计算 5000 个随机散步过程(一行一个)的累计和了:

In [258]: nwalks = 5000

In [259]: nsteps = 1000

In [260]: draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 or 1

In [261]: steps = np.where(draws > 0, 1, -1)

In [262]: walks = steps.cumsum(1)

In [263]: walks
Out[263]: 
array([[1,   0,   1, ...,   8,   7,   8],
       [1,   0,  -1, ...,  34,  33,  32],
       [1,   0,  -1, ...,   4,   5,   4],
       ..., 
       [1,   2,   1, ...,  24,  25,  26],
       [1,   2,   3, ...,  14,  13,  14],
       [-1,  -2,  -3, ..., -24, -23, -22]])

当初,咱们来计算所有随机散步过程的最大值和最小值:

In [264]: walks.max()
Out[264]: 138

In [265]: walks.min()
Out[265]: -133

失去这些数据之后,咱们来计算 30 或-30 的最小穿梭工夫。这里略微简单些,因为不是 5000 个过程都达到了 30。咱们能够用 any 办法来对此进行查看:

In [266]: hits30 = (np.abs(walks) >= 30).any(1)

In [267]: hits30
Out[267]: array([False,  True, False, ..., False,  True, False], dtype=bool)

In [268]: hits30.sum() # Number that hit 30 or -30
Out[268]: 3410

而后咱们利用这个布尔型数组选出那些穿梭了 30(绝对值)的随机散步(行),并调用 argmax 在轴 1 上获取穿梭工夫:

In [269]: crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)

In [270]: crossing_times.mean()
Out[270]: 498.88973607038122

请尝试用其余散布形式失去散步数据。只需应用不同的随机数生成函数即可,如 normal 用于生成指定均值和标准差的正态分布数据:

In [271]: steps = np.random.normal(loc=0, scale=0.25,
   .....:                          size=(nwalks, nsteps))
退出移动版