NumPy(Numerical Python)基础

NumPy,是Numerical Python的简称,它是目前Python数值计算中最为重要的基础包。

NumPy之所以重要,其中一个原因就是它的设计对于含有大量数组的数据非常有效。此外还有如下原因:

  • NumPy在内部将数据存储在连续的内存块上,这与其他的Python内建数据结构是不同的。NumPy的算法库是用C语言写的,所以在操作数据内存时,不需要任何类型检查或者其他管理操作。NumPy数组使用的内存量也小于其他Python内建序列。
  • NumPy可以针对全量数组进行复杂计算而不需要写Python循环。

下面的例子将展现NumPy的不同,假设一个NumPy数组包含100万个整数,还有一个同样数据内容的Python列表:

In [3]: import numpy as np

In [4]: myArr=np.arange(1000000)

In [5]: myList=list(range(1000000))

In [6]: %time for _ in range(10):myArr2=myArr*2
Wall time: 38.9 ms

In [7]: %time for _ in range(10):myList2=[x*2 for x in myList]
Wall time: 1.52 s

可见NumPy的方法比Python方法要快得多。

1 NumPy ndarray:多维数组对象

NumPy的核心特征之一就是N-维数组对象——ndarrayndarray是Python中一个快速、灵活的大型数据集容器。

数组允许你使用类似于标量的操作语法在整块数据上进行数学计算:

首先我们生成一个小的随机数组:

In [8]: data=np.random.randn(2,3)

In [9]: data
Out[9]: 
array([[-0.87283691, -1.694129  , -0.4310078 ],
       [ 0.822441  ,  1.95883674,  0.66952787]])

然后对data进行一些数学操作

In [10]: data*10
Out[10]: 
array([[ -8.72836909, -16.94128997,  -4.31007798],
       [  8.22440997,  19.58836741,   6.69527866]])

In [11]: data+data
Out[11]: 
array([[-1.74567382, -3.38825799, -0.8620156 ],
       [ 1.64488199,  3.91767348,  1.33905573]])

在第一个数学操作中,所有的元素都同时乘以了10。在第二个数学操作中,数组中的对应元素进行了相加。

一个ndarray是一个通用的多维同类数据容器,也就是说,它包含的每一个元素均为相同类型。每一个数组都有一个shape属性,用来表征数组每一维度的数量;每一个数组都有一个dtype属性,用来描述数组的数据类型:

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

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

在下文中,当你看到“数组”、“NumPy数组”或“ndarray”时,他们都表示同一个对象:ndarray对象。

注意:由于NumPy专注于数值计算,如果没有特别指明的话,默认的数据类型是float64(浮点型)。

1.1 生成ndarray

生成数组最简单的方式就是使用array函数。array函数接收任意的序列型对象(当然也包括其他的数组),生成一个新的包含传递数据的NumPy数组。例如,列表的转换就是一个好例子:

In [14]: data1=[6,7.5,8,3,4]

In [15]: arr1=np.array(data1)

In [16]: arr1
Out[16]: array([ 6. ,  7.5,  8. ,  3. ,  4. ])

对于嵌套序列,例如同等长度的列表,将会自动转换成多维数组:

In [17]: data2=[[1,2,3,4],[5,6,7,8]]

In [18]: arr2=np.array(data2)

In [19]: arr2
Out[19]: 
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])

因为data2是一个包含列表的列表,所以Numpy数组arr2形成了二维数组。我们可以通过检查ndimshape属性来确认这一点:

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

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

除非显式地指定,否则np.array会自动推断生成数组的数据类型。数据类型被存储在一个特殊的元数据dtype中:

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

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

除了np.array,还有很多其他函数可以创建新数组。例如,给定长度及形状后,

  • zeros可以一次性创造全0数组,
  • ones可以一次性创造全1数组。
  • empty则可以创建一个没有初始化数值的数组。

想要创建高维数组,则需要为shape传递一个元组:

In [24]: np.zeros(10)
Out[24]: array([ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.])

In [25]: np.zeros((3,6))
Out[25]: 
array([[ 0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

In [26]: np.ones(10)
Out[26]: array([ 1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.])

In [27]: np.ones((2,3,2))
Out[27]: 
array([[[ 1.,  1.],
        [ 1.,  1.],
        [ 1.,  1.]],

       [[ 1.,  1.],
        [ 1.,  1.],
        [ 1.,  1.]]])

In [28]: np.empty((2,4))
Out[28]: 
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])

注意:想要使用np.empty来生成一个全0数组,并不安全,有些时候它可能会返回未初始化的垃圾数值。

arange是Python内建函数range的数组版:

In [29]: np.arange(15)
Out[29]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])

np.asarray将输入转换为ndarray

In [30]: a=[1,2,3]

In [31]: arr3=np.asarray(a)

In [32]: arr3
Out[32]: array([1, 2, 3])

arrayasarray都可以将结构数据转化为ndarray,但是主要区别就是当数据源是ndarray时,array仍然会copy出一个副本,占用新的内存,但asarray不会。

1.2 ndarray的数据类型

数据类型,即dytpe,是一个特殊的对象,它包含了ndarray需要为某一种类型数据所申明的内存块信息(也称为元数据,即表示数据的数据):

np.array可以通过dtype指定数据类型:

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

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

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

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

数据的dtype通常都是按照一个方式命名:类型名,比如float和int,后面再接上表明每个元素位数的数字。一个标准的双精度浮点值(Python中数据类型为float),将使用8字节或64位。因此,这个类型在NumPy中称为float64。下表将展现所有的NumPy所支持的数据类型。

NumPy(Numerical Python)基础篇_python学习

你可以使用astype方法显式地转换数组的数据类型:

In [39]: arr=np.array([1,2,3,4,5])

In [40]: arr.dtype
Out[40]: dtype('int32')
In [42]: floatArr=arr.astype(np.float64)
In [43]: floatArr.dtype
Out[43]: dtype('float64')

如果你有一个数组,里面的元素都是表达数字含义的字符串,也可以通过astype将字符串转换为数字:

In [44]: numericStrings=np.array(['1.25','-9.6','42'],dtype=np.string_)

In [45]: numericStrings.astype(float)
Out[45]: array([  1.25,  -9.6 ,  42.  ])

这里我偷懒地使用float来代替np.float64,是因为NumPy可以使用相同别名来表征与Python精度相同的Python数据类型。

还可以使用另一个数组dtype属性来指定数组类型:

In [49]: intArray=np.arange(10)

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

In [51]: calibers=np.array([.22,.27,.35],dtype=np.float64)

In [52]: intArray.astype(calibers.dtype)
Out[52]: array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])

还可使用表中的类型代码来传入类型数据:

In [53]: emptyUnit32=np.empty(8,dtype='u4')

In [54]: emptyUnit32
Out[54]: array([0, 0, 0, 0, 0, 0, 0, 0], dtype=uint32)

注意:使用astype时总是生成一个新的数组,即使你传入的dtype与之前一样。

1.3 NumPy数组算术

数组之所以重要是因为它允许你进行批量操作而无须任何for循环。NumPy用户称这种特性为向量化。任何在两个等尺寸数组之间的算术操作都应用了逐元素操作的方式:

In [56]: arr=np.array([[1.,2.,3.],[4.,5.,6.]])

In [57]: arr
Out[57]: 
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.]])

In [58]: arr*arr
Out[58]: 
array([[  1.,   4.,   9.],
       [ 16.,  25.,  36.]])

In [59]: arr-arr
Out[59]: 
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

带有标量计算的算术操作,会把计算参数传递给数组的每一个元素:

In [60]: 1/arr
Out[60]: 
array([[ 1.        ,  0.5       ,  0.33333333],
       [ 0.25      ,  0.2       ,  0.16666667]])

In [61]: arr**0.5
Out[61]: 
array([[ 1.        ,  1.41421356,  1.73205081],
       [ 2.        ,  2.23606798,  2.44948974]])

同尺寸数组之间的比较,会产生一个布尔值数组:

In [62]: arr2=np.array([[0.,4.,1.],[7.,2.,12.]])

In [63]: arr2
Out[63]: 
array([[  0.,   4.,   1.],
       [  7.,   2.,  12.]])

In [64]: arr2>arr
Out[64]: 
array([[False,  True, False],
       [ True, False,  True]], dtype=bool)

不同尺寸的数组间的操作,将会用到广播特性,我们会在高阶篇讲解。

1.4 基础索引与切片

NumPy数组索引有很多种方式可以让你选中数据的子集或某个单个元素。一维数组比较简单,看起来和Python的列表很类似:

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

In [67]: arr[5]
Out[67]: 5

In [68]: arr[5:8]
Out[68]: array([5, 6, 7])

如你所见,如果你传入了一个数值给数组的切片,例如arr[5:8] = 12,数值被传递给了整个切片:

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

区别于Python的内建列表,数组的切片是原数组的视图这意味着数据并不是被复制了,任何对于视图的修改都会反映到原数组上
例如:

In [72]: arrSlice=arr[5:8]

In [73]: arrSlice
Out[73]: array([12, 12, 12])

当我改变arrSlice,变化也会体现在原数组上:

In [74]: arrSlice[1]=12345

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

[:]将会引用数组的所有值:

In [76]: arrSlice[:]=64

In [77]: arr
Out[77]: array([ 0,  1,  2,  3,  4, 64, 64, 64,  8,  9])

注意:如果你还是想要一份数组切片的拷贝而不是一份视图的话,你就必须显式地复制这个数组,例如arr[5:8].copy()

对更高维度的数组,你会有更多选择。在一个二维数组中,每个索引值对应的元素不再是一个值,而是一个一维数组:

In [80]: arr2d=np.array([[1,2,3],[4,5,6],[7,8,9]])

In [81]: arr2d[2]
Out[81]: array([7, 8, 9])

因此,单个元素可以通过递归的方式获得。但是要多写点代码,你可以通过传递一个索引的逗号分隔列表去选择单个元素,以下两种方式效果一样:

In [82]: arr2d[0][2]
Out[82]: 3

In [83]: arr2d[0,2]
Out[83]: 3

下图展示二维数组上的索引,我们可以将0轴看作“行”,将1轴看作“列”:
NumPy(Numerical Python)基础篇_python学习_02

在多维数组中,你可以省略后续索引值,返回的对象将是降低一个维度的数组。因此在一个2×2×3的数组arr3d中:

In [94]: arr3d=np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])

In [95]: arr3d
Out[95]: 
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

       [[ 7,  8,  9],
        [10, 11, 12]]])

arr3d[0]是一个2×3的数组:

In [96]: arr3d[0]
Out[96]: 
array([[1, 2, 3],
       [4, 5, 6]])

标量和数组都可以传递给arr3d[0]:

In [97]: oldValue=arr3d[0].copy()

In [98]: arr3d[0]=42

In [99]: arr3d
Out[99]: 
array([[[42, 42, 42],
        [42, 42, 42]],

类似地,arr3d[1, 0]返回的是一个一维数组:

In [104]: arr3d[1,0]
Out[104]: array([7, 8, 9])

上面的表达式可以分解为下面两步:

In [105]: x=arr3d[1]

In [106]: x
Out[106]: 
array([[ 7,  8,  9],
       [10, 11, 12]])

In [107]: x[0]
Out[107]: array([7, 8, 9])

注意:以上的数组子集选择中,返回的数组都是视图

数组的切片索引:

与Python列表的一维对象类似,数组可以通过类似的语法进行切片:

In [114]: arr
Out[114]: array([ 0,  1,  2,  3,  4, 64, 64, 64,  8,  9])

In [115]: arr[1:6]
Out[115]: array([ 1,  2,  3,  4, 64])

再回想下前面的二维数组,arr2d,对数组进行切片略有不同:

In [116]: arr2d
Out[116]: 
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

In [117]: arr2d[:2]
Out[117]: 
array([[1, 2, 3],
       [4, 5, 6]])

如你所见,数组沿着轴0进行了切片。表达式arrzd[:2]的含义为选择arr2d的前两“行”。

你可以进行多组切片,与多组索引类似:

In [118]: arr2d[:2,1:]
Out[118]: 
array([[2, 3],
       [5, 6]])

当你像在上面这个例子中那样切片时,你需要按照原数组的维度进行切片。如果将索引和切片混合,就可以得到低维度的切片

In [119]: arr2d[1,:2]
Out[119]: array([4, 5])

类似地,我也可以选择第三列,但是只选择前两行:

In [120]: arr2d[:2,2]
Out[120]: array([3, 6])

如下图所示。

NumPy(Numerical Python)基础篇_python学习_03

需要注意的是,单独一个冒号表示选择整个轴上的数组,因此你可以按照下面的方式在更高维度上进行切片:

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

当然对切片表达式赋值时,整个切片都会重新赋值:

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

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

1.5 布尔索引

让我们考虑以下例子,假设我们的数据都在数组中,并且数组中的数据是一些存在重复的人名。使用numpy.random中的randn函数来生成一些随机正态分布的数据:

In [125]: names=np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])

In [126]: data=np.random.randn(7,4)

In [127]: names
Out[127]: 
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'],
      dtype='<U4')

In [128]: data
Out[128]: 
array([[-0.89412047, -0.42046182,  0.16146748, -0.14959688],
       [-0.40675299,  0.64242108, -0.50005725, -0.82097767],
       [ 0.32188333, -0.52883977,  0.64080695, -0.05199697],
       [ 1.33759774, -0.23715279,  1.25083876,  0.67882217],
       [ 0.78827395,  0.55551406, -0.71361533,  1.05719943],
       [ 0.59454886,  0.11691395,  0.14344497,  2.21820962],
       [-0.37751872, -0.39142441,  0.43449828,  0.02141508]])

假设每个人名都和data数组中的一行相对应,并且我们想要选中所有’Bob’对应的行。

与数学操作类似,数组的比较操作(比如==)也是可以向量化的。因此,比较names数组和字符串’Bob’会产生一个布尔值数组:

In [129]: names=='Bob'
Out[129]: array([ True, False, False,  True, False, False, False], dtype=bool)

在索引数组时可以传入布尔值数组

In [130]: data[names=='Bob']
Out[130]: 
array([[-0.89412047, -0.42046182,  0.16146748, -0.14959688],
       [ 1.33759774, -0.23715279,  1.25083876,  0.67882217]])

布尔值数组的长度必须和数组轴索引长度一致

你甚至还可以用切片或整数值(或整数值的序列,后续会介绍)对布尔值数组进行混合和匹配。

注意:当布尔值数组的长度不正确时,布尔值选择数据的方法并不会报错,因此在使用该特性的时候要小心。

我们还可以选择了names == 'Bob’的行,并索引了各个列:

In [131]: data[names=='Bob',2:]
Out[131]: 
array([[ 0.16146748, -0.14959688],
       [ 1.25083876,  0.67882217]])

In [132]: data[names=='Bob',3]
Out[132]: array([-0.14959688,  0.67882217])

为了选择除了’Bob’以外的其他数据,你可以使用!=或在条件表达式前使用对条件取反:

In [139]: data[names !='Bob']
Out[139]: 
array([[-0.40675299,  0.64242108, -0.50005725, -0.82097767],
       [ 0.32188333, -0.52883977,  0.64080695, -0.05199697],
       [ 0.78827395,  0.55551406, -0.71361533,  1.05719943],
       [ 0.59454886,  0.11691395,  0.14344497,  2.21820962],
       [-0.37751872, -0.39142441,  0.43449828,  0.02141508]])

In [140]: data[~(names=='Bob')]
Out[140]: 
array([[-0.40675299,  0.64242108, -0.50005725, -0.82097767],
       [ 0.32188333, -0.52883977,  0.64080695, -0.05199697],
       [ 0.78827395,  0.55551406, -0.71361533,  1.05719943],
       [ 0.59454886,  0.11691395,  0.14344497,  2.21820962],
       [-0.37751872, -0.39142441,  0.43449828,  0.02141508]])

符号可以在你想要对一个通用条件进行取反时使用:

In [153]: cond=names=='Bob'

In [154]: data[~cond]
Out[154]: 
array([[-0.40675299,  0.64242108, -0.50005725, -0.82097767],
       [ 0.32188333, -0.52883977,  0.64080695, -0.05199697],
       [ 0.78827395,  0.55551406, -0.71361533,  1.05719943],
       [ 0.59454886,  0.11691395,  0.14344497,  2.21820962],
       [-0.37751872, -0.39142441,  0.43449828,  0.02141508]])

当要选择三个名字中的两个时,可以对多个布尔值条件进行联合,需要使用数学操作符如&(and)和|(or):

In [155]: mask=(names=='Bob')|(names=='Will')

In [156]: mask
Out[156]: array([ True, False,  True,  True,  True, False, False], dtype=bool)

In [157]: data[mask]
Out[157]: 
array([[-0.89412047, -0.42046182,  0.16146748, -0.14959688],
       [ 0.32188333, -0.52883977,  0.64080695, -0.05199697],
       [ 1.33759774, -0.23715279,  1.25083876,  0.67882217],
       [ 0.78827395,  0.55551406, -0.71361533,  1.05719943]])

使用布尔值索引选择数据时,总是生成数据的拷贝,即使返回的数组并没有任何变化。

注意:Python的关键字andor对布尔值数组并没有用,请使用&(and)和|(or)来代替。

基于常识来设置布尔值数组的值也是可行的。将data中所有的负值设置为0,我们需要做:

In [159]: data
Out[159]: 
array([[ 0.        ,  0.        ,  0.16146748,  0.        ],
       [ 0.        ,  0.64242108,  0.        ,  0.        ],
       [ 0.32188333,  0.        ,  0.64080695,  0.        ],
       [ 1.33759774,  0.        ,  1.25083876,  0.67882217],
       [ 0.78827395,  0.55551406,  0.        ,  1.05719943],
       [ 0.59454886,  0.11691395,  0.14344497,  2.21820962],
       [ 0.        ,  0.        ,  0.43449828,  0.02141508]])

利用一维布尔值数组对每一行设置数值也是非常简单的:

In [162]: data
Out[162]: 
array([[ 7.        ,  7.        ,  7.        ,  7.        ],
       [ 0.        ,  0.64242108,  0.        ,  0.        ],
       [ 7.        ,  7.        ,  7.        ,  7.        ],
       [ 7.        ,  7.        ,  7.        ,  7.        ],
       [ 7.        ,  7.        ,  7.        ,  7.        ],
       [ 0.59454886,  0.11691395,  0.14344497,  2.21820962],
       [ 0.        ,  0.        ,  0.43449828,  0.02141508]])

使用pandas方便地在二维数据上进行上述的操作,以后会介绍。

1.6 神奇索引

神奇索引是NumPy中的术语,用于描述使用整数数组进行数据索引。

In [164]: for i in range(8):
     ...:     arr[i]=i
     ...: 

In [165]: arr
Out[165]: 
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.]])

为了选出一个符合特定顺序的子集,你可以简单地通过传递一个包含指明所需顺序的列表或数组来完成:

In [166]: arr[[4,3,0,7]]
Out[166]: 
array([[ 4.,  4.,  4.,  4.],
       [ 3.,  3.,  3.,  3.],
       [ 0.,  0.,  0.,  0.],
       [ 7.,  7.,  7.,  7.]])

如果使用负的索引,将从尾部进行选择:

In [167]: arr[[-3,-5,-7]]
Out[167]: 
array([[ 5.,  5.,  5.,  5.],
       [ 3.,  3.,  3.,  3.],
       [ 1.,  1.,  1.,  1.]])

传递多个索引数组时情况有些许不同,这样会根据每个索引元组对应的元素选出一个一维数组:

In [168]: arr=np.arange(32).reshape((8,4))

In [169]: arr
Out[169]: 
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 [170]: arr[[1,5,7,2],[0,3,1,2]]
Out[170]: array([ 4, 23, 29, 10])

在上述例子中,元素(1, 0)、(5, 3)、(7, 1)和(2, 2)被选中。如果不考虑数组的维数(本例中是二维),神奇索引的结果总是一维的。

在本例中,神奇索引的行为和一些用户所设想的并不相同。通常情况下,我们所设想的结果是通过选择矩阵中行列的子集所形成的矩形区域。下面是实现我们想法的一种方式:

In [172]: arr[[1,5,7,2]][:,[0,3,1,2]]
Out[172]: 
array([[ 4,  7,  5,  6],
       [20, 23, 21, 22],
       [28, 31, 29, 30],
       [ 8, 11,  9, 10]])

请牢记神奇索引与切片不同,它总是将数据复制到一个新的数组中

1.7 数组转置和换轴

转置是一种特殊的数据重组形式,可以返回底层数据的视图而不需要复制任何内容。数组拥有transpose方法,也有特殊的.T属性:

In [173]: arr=np.arange(15).reshape((3,5))

In [174]: arr
Out[174]: 
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])

In [175]: arr.T
Out[175]: 
array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])

当进行矩阵计算时,你可能会经常进行一些特定操作,比如,当计算矩阵内积会使用np.dot

In [176]: arr=np.random.randn(6,3)

In [177]: arr
Out[177]: 
array([[ 0.43490809,  1.86441539, -1.02264956],
       [-0.62337491,  1.67335413,  1.49459031],
       [-0.37326413,  0.23498063, -0.45610503],
       [-1.13486383,  0.82876635,  1.35396902],
       [ 0.12299609,  0.01378183, -0.21528651],
       [-1.67351574,  0.2282682 ,  2.19521356]])

In [178]: np.dot(arr.T,arr)
Out[178]: 
array([[  4.8207663 ,  -1.64083975,  -6.44297531],
       [ -1.64083975,   7.07052466,   2.10741381],
       [ -6.44297531,   2.10741381,  10.18618708]])

对于更高维度的数组,transpose方法可以接收包含轴编号的元组,用于置换轴(拓展下思维):

In [190]: arr=np.arange(16).reshape((2,2,4))

In [191]: arr
Out[191]: 
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])

In [192]: arr.transpose((0,2,1))
Out[192]: 
array([[[ 0,  4],
        [ 1,  5],
        [ 2,  6],
        [ 3,  7]],

       [[ 8, 12],
        [ 9, 13],
        [10, 14],
        [11, 15]]])

在这里,轴已经被重新排序,使得原先的第二个轴变为第一个,原先的第一个轴变成了第二个变为,最后一个轴并没有改变。
对于transpose的参数:如(0,1,2)对应(2,2,4);(0,2,1)对应(2,4,2)

使用.T进行转置是换轴的一个特殊案例。ndarray有一个swapaxes方法,该方法接收一对轴编号作为参数,并对轴进行调整用于重组数据:

In [193]: arr
Out[193]: 
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])

In [194]: arr.swapaxes(1,2)
Out[194]: 
array([[[ 0,  4],
        [ 1,  5],
        [ 2,  6],
        [ 3,  7]],

       [[ 8, 12],
        [ 9, 13],
        [10, 14],
        [11, 15]]])

swapaxes返回的是数据的视图,而没有对数据进行复制。

2 通用函数:快速的逐元素数组函数

通用函数,也可以称为ufunc,是一种ndarray数据中进行逐元素操作的函数。某些简单函数接收一个或多个标量数值,并产生一个或多个标量结果,而通用函数就是对这些简单函数的向量化封装。

有很多ufunc是简单的逐元素转换,比如sqrtexp函数:

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

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

In [197]: np.sqrt(arr)
Out[197]: 
array([ 0.        ,  1.        ,  1.41421356,  1.73205081,  2.        ,
        2.23606798,  2.44948974,  2.64575131,  2.82842712,  3.        ])

In [198]: np.exp(arr)
Out[198]: 
array([  1.00000000e+00,   2.71828183e+00,   7.38905610e+00,
         2.00855369e+01,   5.45981500e+01,   1.48413159e+02,
         4.03428793e+02,   1.09663316e+03,   2.98095799e+03,
         8.10308393e+03])

这些是所谓的一元通用函数,接收y一个数组并返回一个数组作为结果。

还有一些通用函数,比如addmaximum则会接收两个数组并返回一个数组作为结果,因此称为二元通用函数:

In [206]: x=np.random.randn(8)

In [207]: y=np.random.randn(8)

In [208]: x
Out[208]: 
array([ 1.63166293,  0.18836267,  0.36451741,  1.11050876,  1.1087304 ,
        0.44256716,  0.19144269,  0.27928437])

In [209]: y
Out[209]: 
array([ 1.16806423, -0.05249454,  0.97241914, -0.33117397, -0.06592521,
        0.27072546, -0.14238005,  0.6313865 ])

In [210]: np.maximum(x,y)
Out[210]: 
array([ 1.63166293,  0.18836267,  0.97241914,  1.11050876,  1.1087304 ,
        0.44256716,  0.19144269,  0.6313865 ])

In [211]: np.add(x,y)
Out[211]: 
array([ 2.79972716,  0.13586813,  1.33693655,  0.77933478,  1.04280519,
        0.71329262,  0.04906264,  0.91067088])

这里,numpy.maximum对x和y逐个元素进行得到最大值。

也有一些通用函数返回多个数组。比如modf,是Python内建函数divmod的向量化版本。它返回了一个浮点值数组的小数部分和整数部分

In [212]: arr=np.random.randn(7)*5

In [213]: arr
Out[213]: 
array([ -1.84578642,  -0.99515375,  -2.02592448,  -2.04486449,
       -12.47831449,   3.75158473,   2.33752581])

In [214]: remainder,wholePart=np.modf(arr)

In [215]: remainder
Out[215]: 
array([-0.84578642, -0.99515375, -0.02592448, -0.04486449, -0.47831449,
        0.75158473,  0.33752581])

In [216]: wholePart
Out[216]: array([ -1.,  -0.,  -2.,  -2., -12.,   3.,   2.])

这里列举一些通用函数:

  • 一元通用函数
函数名 描述
abs,fabs 逐函数地计算整数,浮点数或复数的绝对值
sqrt 计算每个元素的平方根(与arr**0.5相等)
square 计算每个元素的平方(与arr**2相等)
  • 二元通用函数
函数名 描述
add 将数组的对应元素相加
subtract 第一个数组元素减第二个数组对应元素
multiply 将数组的对应元素相乘
divide,floor_divide 第一个数组元素除以或整除以第二个数组对应元素
power 将第二个数组的元素作为第一个 数组对应元素的幂次方
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 进行逐个元素的逻辑操作(与 逻辑操作符&. |. ^效果一致)

3 使用数组进行面向数组编程

使用NumPy数组可以使你利用简单的数组表达式完成多种数据操作任务,而无须写些大量循环。这种利用数组表达式来替代显式循环的方法,称为向量化。通常,向量化的数组操作会比纯Python的等价实现在速度上快一到两个数量级(甚至更多),这对所有种类的数值计算产生了最大的影响。

作为一个简单的示例,假设我们想要对一些网格数据来计算函数sqrt(x^2 + y^2)的值。np.meshgrid函数接收两个一维数组,并根据两个数组的所有(x, y)对生成一个二维矩阵:

In [5]: points=np.arange(-5,5,0.01)# 1000 equally spaced points

In [6]: xs,ys=np.meshgrid(points,points)

In [7]: ys
Out[7]: 
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 [8]: xs
Out[8]: 
array([[-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       ...,
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99],
       [-5.  , -4.99, -4.98, ...,  4.97,  4.98,  4.99]])

现在,你可以用和两个坐标值同样的表达式来使用函数:

In [9]: z=np.sqrt(xs**2+ys**2)

In [10]: z
Out[10]: 
array([[ 7.07106781,  7.06400028,  7.05693985, ...,  7.04988652,
         7.05693985,  7.06400028],
       [ 7.06400028,  7.05692568,  7.04985815, ...,  7.04279774,
         7.04985815,  7.05692568],
       [ 7.05693985,  7.04985815,  7.04278354, ...,  7.03571603,
         7.04278354,  7.04985815],
       ...,
       [ 7.04988652,  7.04279774,  7.03571603, ...,  7.0286414 ,
         7.03571603,  7.04279774],
       [ 7.05693985,  7.04985815,  7.04278354, ...,  7.03571603,
         7.04278354,  7.04985815],
       [ 7.06400028,  7.05692568,  7.04985815, ...,  7.04279774,
         7.04985815,  7.05692568]])

使用matplotlib来生成这个二维数组的可视化:

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

In [19]: plt.show()

NumPy(Numerical Python)基础篇_python学习_04

3.1 将条件逻辑作为数组操作

numpy.where函数是三元表达式x if condition else y的向量化版本。假设我们有一个布尔值数组和两个数值数组:

In [20]: xarr=np.array([1.1,1.2,1.3,1.4,1.5])

In [21]: yarr=np.array([2.1,2.2,2.3,2.4,2.5])

In [22]: cond=np.array([True,False,True,True,False])

假设cond中的元素为True时,我们取xarr中的对应元素值,否则取yarr中的元素。我们可以通过列表推导式来完成,像下列代码这样:

In [23]: result=[(x if c else y)for x,y,c in zip(xarr,yarr,cond)]

In [24]: result
Out[24]: [1.1000000000000001, 2.2000000000000002, 1.3, 1.3999999999999999, 2.5]

这样会产生多个问题。首先,如果数组很大的话,速度会很慢(因为所有的工作都是通过解释器解释Python代码完成)。其次,当数组是多维时,就无法凑效了。而使用np.where时,就可以非常简单地完成:

In [25]: result=np.where(cond,xarr,yarr)

In [26]: result
Out[26]: array([ 1.1,  2.2,  1.3,  1.4,  2.5])

np.where的第二个和第三个参数并不需要是数组,它们可以是标量。where在数据分析中的一个典型用法是根据一个数组来生成一个新的数组。假设你有一个随机生成的矩阵数据,并且你想将其中的正值都替换为2,将所有的负值替换为-2,使用np.where会很容易实现:

In [27]: arr=np.random.randn(4,4)

In [28]: arr
Out[28]: 
array([[ 1.37269448, -0.34302535, -0.1347065 , -0.21584618],
       [-0.01130576, -1.39947124, -0.14525684,  0.25395165],
       [ 1.66207732,  0.7099269 , -0.75340511,  2.07840543],
       [-1.76960793,  0.33001967, -2.05205631, -0.23086318]])

In [29]: arr>0
Out[29]: 
array([[ True, False, False, False],
       [False, False, False,  True],
       [ True,  True, False,  True],
       [False,  True, False, False]], dtype=bool)

In [30]: np.where(arr>0,2,-2)
Out[30]: 
array([[ 2, -2, -2, -2],
       [-2, -2, -2,  2],
       [ 2,  2, -2,  2],
       [-2,  2, -2, -2]])

你可以使用np.where将标量和数组联合,例如,我可以像下面的代码那样将arr中的所有正值替换为常数2:

In [31]: np.where(arr>0,2,arr) # 仅将正值设置为2
Out[31]: 
array([[ 2.        , -0.34302535, -0.1347065 , -0.21584618],
       [-0.01130576, -1.39947124, -0.14525684,  2.        ],
       [ 2.        ,  2.        , -0.75340511,  2.        ],
       [-1.76960793,  2.        , -2.05205631, -0.23086318]])

3.2 数学和统计方法

许多关于计算整个数组统计值或关于轴向数据的数学函数,可以作为数组类型的方法被调用。你可以使用聚合函数(通常也叫缩减函数),比如summeanstd(标准差),既可以直接调用数组实例的方法,也可以使用顶层的NumPy函数。

此处我生成了一些正态分布的随机数,并计算了部分聚合统计数据:

In [32]: arr=np.random.randn(5,4)

In [33]: arr
Out[33]: 
array([[-0.03304949,  1.93194584, -1.1230818 , -1.03654134],
       [-1.46226783, -0.45064104,  2.70765724, -2.03105991],
       [ 0.59024734,  0.79672987, -0.16060697, -0.48210045],
       [-0.13542699, -0.9491309 ,  0.45616927,  0.3331243 ],
       [-1.56575792,  1.29820459,  1.6194832 , -0.33264842]])

In [34]: arr.mean()
Out[34]: -0.0014375695653257915

In [35]: np.mean(arr)
Out[35]: -0.0014375695653257915

In [36]: arr.sum()
Out[36]: -0.028751391306515828

meansum等函数可以接收一个可选参数axis,这个参数可以用于计算给定轴向上的统计值,形成一个下降一维度的数组:

In [37]: arr.mean(axis=1)
Out[37]: array([-0.0651817 , -0.30907788,  0.18606745, -0.07381608,  0.25482036])


In [38]: arr.mean(axis=0)
Out[38]: array([-0.52125098,  0.52542167,  0.69992419, -0.70984516])

arr.mean(1)表示“计算每一列的平均值”,而arr.sum(0)表示“计算行轴向的累和”。

其他的方法,例如cumsumcumprod不会聚合,它们会产生一个中间结果

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

In [42]: arr.cumsum()
Out[42]: array([ 0,  1,  3,  6, 10, 15, 21, 28], dtype=int32)

在多维数组中,像cumsum这样的累积函数返回相同长度的数组,但是可以在指定轴向上根据较低维度的切片进行部分聚合:

In [44]: arr=np.array([[0,1,2],[3,4,5],[6,7,8]])

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

In [46]: arr.cumsum(axis=0)
Out[46]: 
array([[ 0,  1,  2],
       [ 3,  5,  7],
       [ 9, 12, 15]], dtype=int32)

In [47]: arr.cumsum(axis=1)
Out[47]: 
array([[ 0,  1,  3],
       [ 3,  7, 12],
       [ 6, 13, 21]], dtype=int32)

这里列举一些基础数组统计方法

方法 描述
sum 沿着轴向计算所有元素的累和,0长度的数组,累和为0
mean 数学平均,0长度的数组平均值为NaN
std,var 标准差和方差,可以选择自由度调整(默认分母是n)
min,max 最小值和最大值
argmin,argmax 最小值和最大值的位置
cumsum 从0开始元素累积和
cumprod 从1开始元素累积积

3.3 布尔值数组的方法

在前面介绍的方法,布尔值会被强制为1(True)和0False)。因此,sum通常可以用于计算布尔值数组中的True的个数:

In [48]: arr=np.random.randn(100)

In [49]: (arr>0).sum() # 正值个数
Out[49]: 48

对于布尔值数组,有两个非常有用的方法anyallany检查数组中是否至少有一个True,而all检查是否每个值都是True

In [50]: bools=np.array([False,False,True,False])

In [51]: bools.any()
Out[51]: True

In [52]: bools.all()
Out[52]: False

这些方法也可适用于非布尔值数组,所有的非0元素都会按True处理。

3.4 排序

和Python的内建列表类型相似,NumPy数组可以使用sort方法按位置排序:

In [53]: arr=np.random.randn(6)

In [54]: arr
Out[54]: 
array([ 1.44818722,  0.70287678, -0.48637124, -1.70994466, -0.42102148,
        1.69289396])

In [55]: arr.sort()

In [56]: arr
Out[56]: 
array([-1.70994466, -0.48637124, -0.42102148,  0.70287678,  1.44818722,
        1.69289396])

你可以在多维数组中根据传递的axis值,沿着轴向对每个一维数据段进行排序:

In [57]: arr=np.random.randn(5,3)

In [58]: arr
Out[58]: 
array([[ 0.25460328, -0.21277726,  0.59628914],
       [-0.15334087,  0.86854163,  0.98782802],
       [-0.74630985, -0.22902159, -0.08847989],
       [ 1.23971864,  0.82599879,  0.13872593],
       [ 0.38278173, -1.51187611,  0.57595778]])

In [59]: arr.sort(1)

In [60]: arr
Out[60]: 
array([[-0.21277726,  0.25460328,  0.59628914],
       [-0.15334087,  0.86854163,  0.98782802],
       [-0.74630985, -0.22902159, -0.08847989],
       [ 0.13872593,  0.82599879,  1.23971864],
       [-1.51187611,  0.38278173,  0.57595778]])

np.sort方法返回的则是已经排序好的数组拷贝,而不是对原数组按位置排序。

In [62]: arr
Out[62]: 
array([[-1.29047004,  0.3232859 , -2.10851172],
       [-1.17325772,  1.25104885,  0.50311541],
       [ 0.51118219, -0.78847183,  0.24601182],
       [-0.69279496,  0.1691795 , -0.05495197],
       [ 1.13416797, -0.55640695,  0.22453922]])

In [63]: np.sort(arr,axis=1)
Out[63]: 
array([[-2.10851172, -1.29047004,  0.3232859 ],
       [-1.17325772,  0.50311541,  1.25104885],
       [-0.78847183,  0.24601182,  0.51118219],
       [-0.69279496, -0.05495197,  0.1691795 ],
       [-0.55640695,  0.22453922,  1.13416797]])

In [64]: arr
Out[64]: 
array([[-1.29047004,  0.3232859 , -2.10851172],
       [-1.17325772,  1.25104885,  0.50311541],
       [ 0.51118219, -0.78847183,  0.24601182],
       [-0.69279496,  0.1691795 , -0.05495197],
       [ 1.13416797, -0.55640695,  0.22453922]])

3.5 唯一值与其他集合逻辑

NumPy包含一些针对一维ndarray的基础集合操作。常用的一个方法是np.unique,返回的是数组中唯一值排序后形成的数组:

In [67]: names=np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])

In [68]: np.unique(names)
Out[68]: 
array(['Bob', 'Joe', 'Will'],
      dtype='<U4')

In [69]: ints=np.array([3,3,3,2,2,1,1,4,4])

In [70]: np.unique(ints)
Out[70]: array([1, 2, 3, 4])

np.unique和纯Python实现相比较:

In [71]: sorted(set(names))
Out[71]: ['Bob', 'Joe', 'Will']

另一个函数,np.in1d,可以检查一个数组中的值是否在另外一个数组中,并返回一个布尔值数组:

In [72]: values=np.array([6,0,0,3,2,5,6])

In [73]: np.in1d(values,[2,3,6])
Out[73]: array([ True, False, False,  True,  True, False,  True], dtype=bool)

数组的集合操作

方法 描述
unique(x) 计算×的唯一值,并排序
intersect1d(x,y) 计算×和y的交集,并排序
union1d(x,y) 计算×和y的并集,并排序
ini1d() 计算×中的元素是否包含在y中,返回一个布尔值数组
setdiff1d(x,y) 差集,在×中但不在y中的×的元素
setxor1d(x,y) 异或集,在×或y中,但不属于×、y交集的元素

4 使用数组进行文件输入和输出

NumPy可以在硬盘中将数据以文本或二进制文件的形式进行存入硬盘或由硬盘载入。在本节,我将只讨论NumPy的内建二进制格式,因为大部分用户更倾向于使用pandas或其他工具来载入文本或表格型数据。

np.savenp.load是高效存取硬盘数据的两大工具函数。数组在默认情况下是以未压缩的格式进行存储的,后缀名是.npy

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

In [4]: np.save('some_array',arr)

NumPy(Numerical Python)基础篇_python学习_05

如果文件存放路径中没写.npy时,后缀名会被自动加上。硬盘上的数组可以使用np.load进行载入:

In [5]: np.load('some_array.npy')
Out[5]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

你可以使用np.savez并将数组作为参数传递给该函数,用于在未压缩文件中保存多个数组:

In [6]: np.savez('arrayArchive.npz',a=arr,b=arr)

当载入一个.npy文件的时候,你会获得一个字典型的对象,并通过该对象很方便地载入单个数组:

In [7]: arch=np.load('arrayArchive.npz')

In [8]: arch['b']
Out[8]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

5 线性代数

线性代数,比如矩阵乘法、分解、行列式等方阵数学,是所有数组类库的重要组成部分。和Matlab等其他语言相比,NumPy的线性代数中所不同的是*是矩阵的逐元素乘积,而不是矩阵的点乘积。因此NumPy的数组方法和numpy命名空间中都有一个函数dot,用于矩阵的操作:

In [11]: x=np.array([[1.,2.,3.],[4.,5.,6.]])

In [12]: y=np.array([[6.,23.],[-1,7],[8,9]])

In [13]: x
Out[13]: 
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.]])

In [14]: y
Out[14]: 
array([[  6.,  23.],
       [ -1.,   7.],
       [  8.,   9.]])

In [15]: x.dot(y)
Out[15]: 
array([[  28.,   64.],
       [  67.,  181.]])

x.dot(y)等价于np.dot(x, y)

In [16]: np.dot(x,y)
Out[16]: 
array([[  28.,   64.],
       [  67.,  181.]])

一个二维数组和一个长度合适的一维数组之间的矩阵乘积,其结果是一个一维数组:

In [17]: np.dot(x,np.ones(3))
Out[17]: array([  6.,  15.])

特殊符号@也作为中缀操作符,用于点乘矩阵操作:

In [18]: x @ np.ones(3)
Out[18]: array([  6.,  15.])

numpy.linalg拥有一个矩阵分解的标准函数集,以及其他常用函数,例如求逆和行列式求解。这些函数都是通过在MATLAB和R等其他语言使用的相同的行业标准线性代数库来实现的,例如BLAS、LAPACK或英特尔专有的MKL(数学核心库)(是否使用MKL取决于使用NumPy的版本):

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

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

In [26]: inv(mat)
Out[26]: 
array([[ 6.27361681,  0.21413277, -2.10784927,  0.88789016,  1.16687286],
       [ 0.21413277,  0.81349612,  0.23828606, -0.13675758,  0.19600023],
       [-2.10784927,  0.23828606,  1.10000762, -0.40129199, -0.45456528],
       [ 0.88789016, -0.13675758, -0.40129199,  0.2900041 ,  0.14127489],
       [ 1.16687286,  0.19600023, -0.45456528,  0.14127489,  0.47877011]])

In [27]: mat.dot(inv(mat))
Out[27]: 
array([[  1.00000000e+00,  -4.48048335e-18,  -6.88373257e-17,
          1.46296096e-17,   1.80181577e-16],
       [  6.09657689e-16,   1.00000000e+00,  -1.47375802e-16,
          1.81774693e-16,  -7.92478134e-17],
       [ -6.34304347e-16,   3.02540851e-17,   1.00000000e+00,
         -1.23856362e-16,   9.12309641e-17],
       [  7.54243991e-16,  -1.05561747e-16,   1.87814766e-16,
          1.00000000e+00,  -8.45483750e-17],
       [ -8.00317186e-17,  -4.00074463e-16,  -2.06059262e-16,
          4.38296871e-17,   1.00000000e+00]])

In [28]: q,r=qr(mat)

In [29]: r
Out[29]: 
array([[-1.64620018,  2.99450089, -3.66763049,  3.86291548, -1.62371872],
       [ 0.        , -2.93808377,  3.51353498,  1.06839142,  5.8801916 ],
       [ 0.        ,  0.        , -3.76016356, -6.83849909, -1.66644908],
       [ 0.        ,  0.        ,  0.        , -2.49132422,  1.74553809],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.73406139]])

函数 描述
diag 将一个方阵的对角(或非对角)元素作为一维数组返回,或者将―维数组转换成一个方阵,并且在非对角线上有零点
dot 矩阵点乘
trace 计算对角元素和
det 计算矩阵的行列式
eig 计算方阵的特征值和特征向量
inv 计算方阵的逆矩阵
pinv 计算矩阵的 Moore-Penrose伪逆
qr 计算QR分解
svd 计算奇异值分解(SVD)
solve 求解x的线性系统Ax = b,其中A是方阵
lstsq 计算Ax = b的最小二乘解

6 伪随机数生成

numpy.random模块填补了Python内建的random模块的不足,可以高效地生成多种概率分布下的完整样本值数组。例如,你可以使用normal来获得一个4×4的正态分布样本数组:

In [30]: samples=np.random.normal(size=[4,4])

In [31]: samples
Out[31]: 
array([[ 0.31532483, -0.1755875 ,  0.04120945,  0.30689172],
       [ 0.34147306,  0.53492713, -2.76257611, -0.64546541],
       [ 2.44510901,  0.35182538, -2.24432639, -0.12661826],
       [ 0.64133959,  1.66535095,  0.83798443, -0.52965891]])

然而Python内建的random模块一次只能生成一个值。你可以从下面的示例中看到,numpy.random在生成大型样本时比纯Python的方式快了一个数量级:

In [32]: from random import normalvariate

In [33]: N=1000000

In [34]: %timeit samples=[normalvariate(0,1) for _ in range(N)]
1.5 s ± 106 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [35]: %timeit np.random.normal(size=N)
51.3 ms ± 2.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

我们可以称这些为伪随机数,因为它们是由具有确定性行为的算法根据随机数生成器中的随机数种子生成的。你可以通过np.random.seed更改NumPy的随机数种子:

In [36]: np.random.seed(1234)

numpy.random中的数据生成函数公用了一个全局的随机数种子。为了避免全局状态,你可以使用numpy.random.RandomState生成一个随机数生成器,使数据独立于其他的随机数状态:

In [37]: rng=np.random.RandomState(1234)

In [38]: rng.randn(10)
Out[38]: 
array([ 0.47143516, -1.19097569,  1.43270697, -0.3126519 , -0.72058873,
        0.88716294,  0.85958841, -0.6365235 ,  0.01569637, -2.24268495])

以下列举numpy.random中的部分函数列表

函数 描述
seed 向随机数生成器传递随机状态种子
permutation 返回一个序列的随机排列,或者返回一个乱序的整数范围序列随机排列一个序列
shuffle 随机排列一个序列
rand 从均匀分布中抽取样本
randint 根据给定的由低到高的范围抽取随机整数
randn 从均值0方差1的正态分布中抽取样本(MATLAB型接口)
binomial 从二项分布中抽取样本
normal 从正态(高斯)分布中抽取样本
beta 从beta分布中抽取样本
chisquare 从卡方分布中抽取样本

参考:利用Python进行数据分析(原书第2版),作者:Wes McKinney, 译者:徐敬一, 出版社:机械工业出版社