NumPy基础:数组和矢量计算

NumPy(Numerical Python的简称)是高性能科学计算和数据分析的基础包。它的部分功能如下:

  • ndarray,一个具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组
  • 用于对整组数据进行快速运算的标准数学函数(无需编写循环)
  • 用于读写磁盘数据的工具以及用于操作内存映射文件的工具
  • 线性代数、随机数生成以及傅里叶变换功能
  • 用于集成由C、C++、FORTRAN等语言代码的工具

NumPy本身并没有提供很高级的数据分析功能,但是理解NumPy数组以及面向对象数组的计算有助于更加高效地使用诸如pandas之类的工具。 
首先,我们需要引入NumPy模块,按照标准的NumPy约定,我们应该总是使用import numpy as np这种方式引入NumPy模块,而不是from numpy import *这种方式。

import numpy as np
  • 1

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

NumPy最重要的一个特点就是其N维数组对象(ndarray),该对象是一个快速而灵活的大数据集容器。每个ndarray都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象)。ndarray对象中的所有元素都必须是相同类型的。

创建ndarray

创建数组最简单的方式就是使用array()函数。该函数接受一切序列型的对象(包括其他数组),然后产生一个新的NumPy数组。

data1 = [6, 7.5, 8, 0, 1]
arr1 = np.array(data1)
print(arr1)
  • 1
  • 2
  • 3
[ 6.   7.5  8.   0.   1. ]
  • 1
  • 2

嵌套序列会被转化成一个多维数组:

data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
arr2 = np.array(data2)
print(arr2)
print(arr2.ndim)
print(arr2.shape)
  • 1
  • 2
  • 3
  • 4
  • 5
[[1 2 3 4]
 [5 6 7 8]]
2
(2, 4)
  • 1
  • 2
  • 3
  • 4
  • 5

除非显式地说明,np.array会尝试为新建的数组推断出一个较为合适的数据类型:

arr1.dtype
  • 1
dtype('float64')
  • 1
  • 2
arr2.dtype
  • 1
dtype('int32')
  • 1
  • 2

除了np.array以外,还有一些函数可以创建数组。比如zeros()和ones()分别创建指定形状的全0或全1数组,empty()可以创建一个没有任何具体值的数组。要使用这些方法创建数组,只需传入一个表示形状的元组即可:

np.zeros(10)
  • 1
array([ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.])
  • 1
  • 2
np.ones((2,3,4))
  • 1
array([[[ 1.,  1.,  1.,  1.],
        [ 1.,  1.,  1.,  1.],
        [ 1.,  1.,  1.,  1.]],

       [[ 1.,  1.,  1.,  1.],
        [ 1.,  1.,  1.,  1.],
        [ 1.,  1.,  1.,  1.]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
np.empty((2,3))
  • 1
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])
  • 1
  • 2
  • 3

注意:认为np.empty会返回全0数组的想法是不安全的。很多情况下它返回的都是一些未初始化的垃圾值(尽管这里创建了一个全0的多维数组)。

下表给出了一些数组常见函数。由于NumPy注重的是科学计算,因此如果没有特别说明,数据类型基本上都是float64。

数组创建函数
函数 说明
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的单位矩阵

数组和标量之间的运算

大小相等的数组之间的任何算数运算都会将运算应用到元素级:

arr = np.array([[1., 2., 3], [4., 5., 6.]])
arr
  • 1
  • 2
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.]])
  • 1
  • 2
  • 3
arr * arr
  • 1
array([[  1.,   4.,   9.],
       [ 16.,  25.,  36.]])
  • 1
  • 2
  • 3

同样,数组与标量的算数运算也会将那个标量值传播到各个元素:

arr + 2
  • 1
array([[ 3.,  4.,  5.],
       [ 6.,  7.,  8.]])
  • 1
  • 2
  • 3

基本的索引和切片

一维数组的索引和切片很简单,大体上和Python列表的功能差不多:

arr = np.arange(10)
arr
  • 1
  • 2
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  • 1
  • 2
arr[5]
  • 1
5
  • 1
  • 2
arr[5:8]
  • 1
array([5, 6, 7])
  • 1
  • 2
arr[5:8] = 11
arr
  • 1
  • 2
array([ 0,  1,  2,  3,  4, 11, 11, 11,  8,  9])
  • 1
  • 2

当将一个标量值赋值给一个切片时,该值会自动传播到整个选区。数组切片是原数组的视图,在视图上进行任何修改都会反应到原数组上:

arr_slice = arr[5:8]
arr_slice[1] = 123
arr
  • 1
  • 2
  • 3
array([  0,   1,   2,   3,   4,  11, 123,  11,   8,   9])
  • 1
  • 2
arr_slice[:] = 678
arr
  • 1
  • 2
array([  0,   1,   2,   3,   4, 678, 678, 678,   8,   9])
  • 1
  • 2

注意:如果想要得到的是ndarray切片的一份副本而非视图,就需要显式地进行复制操作,如`arr[5:8].copy()`。

对于二维数组,各索引位置上的元素不再是标量,而是一个一维数组:

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

此时,我们可以使用以逗号隔开的索引列表还选取单个元素:

arr2d[0][2]
  • 1
3
  • 1
  • 2
arr2d[0,2]
  • 1
3
  • 1
  • 2

在N维数组中,如果省略了后面的索引,则返回对象会是一个N-1维的ndarray,它含有N维数组在某条轴上的全部数据。例如,在2×2×3的数组arr3d中:

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

       [[ 7,  8,  9],
        [10, 11, 12]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

arr3d[0]
  • 1
array([[1, 2, 3],
       [4, 5, 6]])
  • 1
  • 2
  • 3

标量值和数组都可以赋值给arr3d[0]:

old_values = arr3d[0].copy()
arr3d[0] = 42
arr3d
  • 1
  • 2
  • 3
array([[[42, 42, 42],
        [42, 42, 42]],

       [[ 7,  8,  9],
        [10, 11, 12]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
arr3d[0] = old_values
arr3d
  • 1
  • 2
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

       [[ 7,  8,  9],
        [10, 11, 12]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

切片索引

通过将整数索引和切片混合,可以得到低维度的切片:

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

“只有冒号”表示选取整个轴:

arr2d[:,2]
  • 1
array([3, 6, 9])
  • 1
  • 2

此外,对切片表达式的赋值操作也会传播到整个选区:

arr2d[:2,1:] = 0
arr2d
  • 1
  • 2
array([[1, 0, 0],
       [4, 0, 0],
       [7, 8, 9]])
  • 1
  • 2
  • 3
  • 4

布尔型索引

假设我们有一个用于存储数据的数组以及一个存储姓名的数组:

names = np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
names
  • 1
  • 2
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], 
      dtype='<U4')
  • 1
  • 2
  • 3
data = np.random.randn(7,4)
data
  • 1
  • 2
array([[-0.56095103,  1.43918605, -0.30829405,  1.1868624 ],
       [-0.14234299, -0.54926634, -1.04254571, -0.23231721],
       [-0.22463693,  1.62961835, -1.16950507,  0.50883482],
       [ 0.30112583,  0.35243888, -0.5603873 ,  0.93900204],
       [ 0.81630914,  0.46270842, -0.42140297, -0.94807037],
       [ 0.95619815,  0.69410031,  0.39522927, -1.33989389],
       [ 1.15349368,  0.34423056, -0.6805079 , -0.22743466]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

假设每个名字都对应data数组的一行,而我们要选出对应名字”Bob”的所有行。我们可以对names和字符串”Bob”进行比较运算,并将运算结果用于数组索引:

data[names == 'Bob']
  • 1
array([[-0.56095103,  1.43918605, -0.30829405,  1.1868624 ],
       [ 0.30112583,  0.35243888, -0.5603873 ,  0.93900204]])
  • 1
  • 2
  • 3

布尔型数组的长度必须和被索引的轴长度一致。此外,还可以将布尔型数组跟切片、整数或整数序列混合使用:

data[names == 'Bob', 2:]
  • 1
array([[-0.30829405,  1.1868624 ],
       [-0.5603873 ,  0.93900204]])
  • 1
  • 2
  • 3

要选择除”Bob”以外的其他值,即可以使用不等号(!=),也可以使用符号~对条件进行否定:

data[~(names == 'Bob')]
  • 1
array([[-0.14234299, -0.54926634, -1.04254571, -0.23231721],
       [-0.22463693,  1.62961835, -1.16950507,  0.50883482],
       [ 0.81630914,  0.46270842, -0.42140297, -0.94807037],
       [ 0.95619815,  0.69410031,  0.39522927, -1.33989389],
       [ 1.15349368,  0.34423056, -0.6805079 , -0.22743466]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用&、|等布尔运算符可以组合多个布尔条件:

mask = (names == 'Bob') | (names == 'Will')
data[mask]
  • 1
  • 2
array([[-0.56095103,  1.43918605, -0.30829405,  1.1868624 ],
       [-0.22463693,  1.62961835, -1.16950507,  0.50883482],
       [ 0.30112583,  0.35243888, -0.5603873 ,  0.93900204],
       [ 0.81630914,  0.46270842, -0.42140297, -0.94807037]])
  • 1
  • 2
  • 3
  • 4
  • 5

通过布尔型索引选取数组中的数据,将总是创建数据的副本,即使返回的是一模一样的数组也是如此。

注意:Python关键字and和or在布尔型数组中无效。

我们可以通过布尔型索引为数组赋值,比如,我们可以按照如下方式将data中的所有负值都设置为0:

data[data < 0] = 0
data
  • 1
  • 2
array([[ 0.        ,  1.43918605,  0.        ,  1.1868624 ],
       [ 0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  1.62961835,  0.        ,  0.50883482],
       [ 0.30112583,  0.35243888,  0.        ,  0.93900204],
       [ 0.81630914,  0.46270842,  0.        ,  0.        ],
       [ 0.95619815,  0.69410031,  0.39522927,  0.        ],
       [ 1.15349368,  0.34423056,  0.        ,  0.        ]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

花式索引(Fancy Indexing)

花式索引是一个NumPy术语,它指的是利用整数数组进行索引。假设我们有一个8×4的数组:

arr = np.empty((8,4))
for i in range(8):
    arr[i] = i
arr
  • 1
  • 2
  • 3
  • 4
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.]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

为了以特定的顺序选取子集,只需传入一个用于指定顺序的整数列表或ndarray即可:

arr[[4,3,0,6]]
  • 1
array([[ 4.,  4.,  4.,  4.],
       [ 3.,  3.,  3.,  3.],
       [ 0.,  0.,  0.,  0.],
       [ 6.,  6.,  6.,  6.]])
  • 1
  • 2
  • 3
  • 4
  • 5

使用负数索引将会从末尾开始选取行:

arr[[-3,-5,-7]]
  • 1
array([[ 5.,  5.,  5.,  5.],
       [ 3.,  3.,  3.,  3.],
       [ 1.,  1.,  1.,  1.]])
  • 1
  • 2
  • 3
  • 4

如果一次传入多个索引数组,那么它会返回一个一维数组,其中的元素对应各个索引元素:

arr = np.arange(32).reshape((8,4))
arr
  • 1
  • 2
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]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
arr[[1,5,7,2],[0,3,1,2]]
  • 1
array([ 4, 23, 29, 10])
  • 1
  • 2

可以看到,最终选出的是原数组中索引为(1,0)、(5,3)、(7,1)和(2,2)的元素。这个花式索引的行为可能跟我们预期的不太一样,选取矩阵的行列子集应该是矩阵的形式才对。我们可以使用切片得到我们想要的结果:

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

另一个方法是使用np.ix_()函数,它可以将两个一维整数数组转化成一个用于选取方形区域的索引器:

arr[np.ix_([1,5,7,2],[0,3,1,2])]
  • 1
array([[ 4,  7,  5,  6],
       [20, 23, 21, 22],
       [28, 31, 29, 30],
       [ 8, 11,  9, 10]])
  • 1
  • 2
  • 3
  • 4
  • 5

注意:花式索引和切片不同,它总是将数据复制到新数组中。

数组转置和轴对换

转置是重塑的一种特殊形式,它返回的是原数组的视图。数组不仅有transpose方法,还有一个特殊的T属性:

arr = np.arange(15).reshape((3,5))
arr
  • 1
  • 2
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
  • 1
  • 2
  • 3
  • 4
arr.T
  • 1
array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
arr.transpose()
  • 1
array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置:

arr = np.arange(16).reshape((2,2,4))
arr
  • 1
  • 2
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
arr.transpose((1,0,2))
  • 1
array([[[ 0,  1,  2,  3],
        [ 8,  9, 10, 11]],

       [[ 4,  5,  6,  7],
        [12, 13, 14, 15]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以看到,当传入参数为(1,0,2)时,np.transpose()函数将编号为0的轴和编号为1的轴交换。以元素10为例,在原数组中,元素10的索引为(1,0,3),调用np.transpose((1,0,2))以后,其在转置后矩阵中的索引是(0,1,3),只交换了轴0和轴1,轴2没有变化。另外,调用arr.T与调用np.transpose((2,1.0))的结果是一样的:

arr.T
  • 1
array([[[ 0,  8],
        [ 4, 12]],

       [[ 1,  9],
        [ 5, 13]],

       [[ 2, 10],
        [ 6, 14]],

       [[ 3, 11],
        [ 7, 15]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
arr.transpose((2,1,0))
  • 1
array([[[ 0,  8],
        [ 4, 12]],

       [[ 1,  9],
        [ 5, 13]],

       [[ 2, 10],
        [ 6, 14]],

       [[ 3, 11],
        [ 7, 15]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

另外,ndarray还有一个swapaxes()函数可以进行轴对换操作,它需要接受一对轴编号,返回原数组的视图:

arr
  • 1
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
arr.swapaxes(0,1)
  • 1
array([[[ 0,  1,  2,  3],
        [ 8,  9, 10, 11]],

       [[ 4,  5,  6,  7],
        [12, 13, 14, 15]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通用函数:快速的元素级数组函数

通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。可以将他们看成简单函数(接受一个或多个标量值,返回一个或多个标量值)的矢量化包装器。

许多ufunc都是简单函数的元素级变体,如sqrt和exp:

arr = np.arange(10)
arr
  • 1
  • 2
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  • 1
  • 2
np.sqrt(arr)
  • 1
array([ 0.        ,  1.        ,  1.41421356,  1.73205081,  2.        ,
        2.23606798,  2.44948974,  2.64575131,  2.82842712,  3.        ])
  • 1
  • 2
  • 3
np.exp(arr)
  • 1
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])
  • 1
  • 2
  • 3
  • 4
  • 5

上面的是一元(unary)ufunc。另外一些(如add和maximum)接受两个数组(因此也叫二元(binary)ufunc),并返回一个结果数组:

x = np.random.randn(8)
x
  • 1
  • 2
array([ 0.8063126 ,  0.27277261,  0.13309358, -0.62439999,  1.58630178,
        1.38612243, -0.47285962, -1.81386606])
  • 1
  • 2
  • 3
y = np.random.randn(8)
y
  • 1
  • 2
array([ 0.17851129, -0.32611238, -1.63258354,  0.71656208, -1.3222537 ,
        1.92734453,  0.47939299,  0.77487499])
  • 1
  • 2
  • 3
np.maximum(x, y)
  • 1
array([ 0.8063126 ,  0.27277261,  0.13309358,  0.71656208,  1.58630178,
        1.92734453,  0.47939299,  0.77487499])
  • 1
  • 2
  • 3

此外,有些ufunc还可以返回多个数组。modf是一个例子,它是Python内置函数divmod的矢量化版本,用于分离浮点数数组的小数和整数部分:

arr = np.random.randn(7) * 5
arr
  • 1
  • 2
array([-0.90828345,  6.3259201 ,  4.2594202 ,  1.11340707, -0.13666971,
       -2.35570999, -1.67512648])
  • 1
  • 2
  • 3
np.modf(arr)
  • 1
(array([-0.90828345,  0.3259201 ,  0.2594202 ,  0.11340707, -0.13666971,
        -0.35570999, -0.67512648]), array([-0.,  6.,  4.,  1., -0., -2., -1.]))
  • 1
  • 2
  • 3

下面给出常见的一些一元ufunc和二元ufunc:

一元ufunc
函数 说明
abs、fabs 计算整数、浮点数或复数的绝对值。对于非复数值,可以使用更快的fabs
sqrt 计算各元素的平方根,相当于arr ** 0.5
square 计算个元素的平方,
exp 计算各元素的指数
log、log10、log2、log1p 分别计算自然对数(底数为e)、底数为10的对数、底数为2的对数、底数为1+x的对数
sign 计算各元素的正负号:1(整数)、0(零)、-1(负数)
ceil 计算各元素的ceiling值,即大于等于该值的最小整数
floor 计算各元素的floor值,即小于等于该值的最大整数
rint 将各元素的值四舍五入到最接近的整数,保留dtype
modf 将数组各元素的小数和整数部分以两个独立的数组的形式返回
isnan 返回一个表示“哪些值是NaN(Not a Number)”的布尔型数组
isfinite、isinf 分别返回一个表示“哪些元素是有穷的”或“哪些元素是无穷的”的布尔型数组
cos、cosh、sin、sinh、tan、tanh 普通型和双曲型三角函数
arccos、arccosh、arcsin、arcsinh、arctan、arctanh 普通型和双曲型反三角函数
logical_not 计算各元素not x的真值


二元ufunc
函数 说明
add 将数组中对应的元素相加
subtract 第一个数组减去第二个数组
multiply 数组元素相乘
divide、floor_divide 除法、向下整除法(丢弃余数)
power 对第一个数组中的元素A,根据第二个数组中的元素B,计算ABAB
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方式快上1~2个数量级,尤其是各种数值计算。

假设我们想要在一组值(网格型)上计算函数sqrt(x^2 + y^2)。np.meshgrid()函数接受两个一维数组,返回两个二维矩阵(对应两个数组中所有的(x,y)对):

points = np.arange(-5,5,.01)
xs, ys = np.meshgrid(points,points)
xs
  • 1
  • 2
  • 3
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]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

现在对该函数的求值就好办了,把这两个数组当成浮点数编写表达式即可:

z = np.sqrt(xs ** 2 + ys ** 2)
z
  • 1
  • 2
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]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
import matplotlib.pyplot as plt
%matplotlib inline
plt.imshow(z, cmap=plt.cm.gray)
plt.colorbar()
plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
  • 1
  • 2
  • 3
  • 4
  • 5
<matplotlib.text.Text at 0x1a517b049b0>
  • 1
  • 2

这里写图片描述

函数值(一个二维数组)的图形化结果如上图所示。

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

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

xarr = np.array([1.1,1.2,1.3,1.4,1.5])
yarr = np.array([2.1,2.2,2.3,2.4,2.5])
cond = np.array([True,False,True,True,False])
  • 1
  • 2
  • 3

如果我们想要根据cond中的值选取xarr和yarr的值,那么我们可以这样写:

result = np.where(cond,xarr,yarr)
result
  • 1
  • 2
array([ 1.1,  2.2,  1.3,  1.4,  2.5])
  • 1
  • 2

np.where的第二个和第三个参数不必是数组,它们都可以是标量值。假如有一个矩阵,我们希望将其中所有的正值替换为2,将所有的负值替换为-2。利用np.where会非常简单:

arr = np.random.randn(4,4)
arr
  • 1
  • 2
array([[ 0.71881883,  0.01906532, -0.89032417,  2.50463668],
       [ 0.56264023,  1.65893376, -0.87169721, -2.60599395],
       [-0.28089287,  0.92431297, -1.08051835, -0.17397565],
       [ 0.09880929, -1.58178342,  0.54471985, -0.44364905]])
  • 1
  • 2
  • 3
  • 4
  • 5
np.where(arr > 0,2,-2)
  • 1
array([[ 2,  2, -2,  2],
       [ 2,  2, -2, -2],
       [-2,  2, -2, -2],
       [ 2, -2,  2, -2]])
  • 1
  • 2
  • 3
  • 4
  • 5
np.where(arr > 0,2,arr) #只将正值设置为2
  • 1
array([[ 2.        ,  2.        , -0.89032417,  2.        ],
       [ 2.        ,  2.        , -0.87169721, -2.60599395],
       [-0.28089287,  2.        , -1.08051835, -0.17397565],
       [ 2.        , -1.58178342,  2.        , -0.44364905]])
  • 1
  • 2
  • 3
  • 4
  • 5

数学和统计方法

可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean以及标准差std等聚合计算即可以当做数组的实例方法调用,也可以当作顶级NumPy函数使用:

arr = np.random.randn(5,4)
arr
  • 1
  • 2
array([[ 0.60653118,  0.54766658,  1.12452133,  0.43230222],
       [-1.00167607,  0.18431361, -0.30092158, -1.11483239],
       [-0.03584129, -0.88705857, -0.0096091 ,  0.30542594],
       [-0.38489241,  1.17027115, -0.02813122, -0.44284961],
       [ 1.82812672,  0.85163807,  1.62514606, -2.08577268]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
np.mean(arr)
  • 1
0.11921789711624378
  • 1
  • 2
arr.mean()
  • 1
0.11921789711624378
  • 1
  • 2

mean和sum这类的函数可以接受1个axis参数(用于计算该轴向上的统计值),最终结果是一个少一维的数组:

arr.mean(axis=1)
  • 1
array([ 0.67775532, -0.55827911, -0.15677075,  0.07859948,  0.55478455])
  • 1
  • 2
arr.sum(axis=0)
  • 1
array([ 1.01224814,  1.86683084,  2.41100549, -2.90572652])
  • 1
  • 2

其他如cumsum和cumprod之类的方法则不会聚合,而是产生一个由中间结果组成的数组:

arr = np.array([[0,1,2],[3,4,5],[6,7,8]])
arr.cumsum(0)
  • 1
  • 2
array([[ 0,  1,  2],
       [ 3,  5,  7],
       [ 9, 12, 15]], dtype=int32)
  • 1
  • 2
  • 3
  • 4
arr.cumprod(1)
  • 1
array([[  0,   0,   0],
       [  3,  12,  60],
       [  6,  42, 336]], dtype=int32)
  • 1
  • 2
  • 3
  • 4

下面列出一些基本的数组统计方法:

基本的数组统计方法
方法 说明
sum 对数组中全部或某轴向的元素求和,零长度的数组的sum为0
mean 算数平均数,零长度的数组的mean为NaN
std、var 标准差和方差,自由度可调(默认为n)
min、max 最小值和最大值
argmin、argmax 分别为最小和最大元素的索引
cumsum 所有元素的累计和
cumprod 所有元素的累计积

用于布尔型数组的方法

在上面这些方法中,布尔值会被强制转化为1(True)和0(False)。因此,sum经常用来对布尔型数组中的True值计数:

arr = np.random.randn(100)
arr
  • 1
  • 2
array([  7.80171040e-02,  -1.02513475e+00,   3.22843929e-01,
         2.30963101e-01,   1.90051632e+00,  -2.19165832e-01,
        -5.30385085e-01,  -3.04017666e-01,   7.21731389e-01,
        -1.00315532e+00,  -5.98788309e-01,   4.81448136e-01,
         5.41408516e-01,  -4.93686976e-01,   1.38073843e+00,
         8.07927809e-01,   5.00414737e-01,   1.75280044e+00,
         1.57775957e+00,  -6.75917961e-01,  -6.89791246e-01,
        -6.42812879e-01,   6.01775386e-01,   3.82823061e-01,
         1.59806608e+00,   2.25209791e+00,   3.23136539e-01,
         2.29826951e-01,   4.10460270e-01,  -1.90028350e+00,
         1.11905516e+00,  -1.96160047e-01,   2.35394612e-01,
         4.72611022e-02,  -1.76848006e+00,   1.60858682e-01,
         2.02891229e+00,   1.49513212e+00,   2.12530157e-01,
         4.13358892e-01,  -1.43973561e-01,   1.11399733e-01,
         2.69051689e-01,   1.52495697e+00,  -1.27985675e+00,
        -8.10647249e-01,   1.72085864e+00,   9.51740891e-01,
        -1.17808602e+00,   1.69703052e-01,   8.76064961e-01,
        -2.67033831e+00,   1.46273181e+00,   2.86883856e-01,
        -8.19201850e-01,  -1.68478321e+00,  -4.35366053e-01,
         1.68676337e+00,  -1.19542561e+00,  -5.17113970e-02,
        -1.97193505e-02,   4.44393816e-01,  -2.80036039e-01,
        -9.55701312e-02,   1.32908058e+00,   1.11354460e+00,
        -2.15604086e-01,  -7.38703705e-02,  -1.69908679e-01,
         9.47439572e-01,  -1.03419326e+00,   1.49953108e+00,
        -9.74273046e-01,   2.43787485e+00,  -1.10787755e+00,
        -5.65635569e-01,  -9.70520581e-01,  -7.25264873e-01,
        -2.70047268e-01,   5.60049481e-01,  -8.39739734e-01,
        -3.06802236e-01,   6.63188225e-01,  -3.35904155e-01,
        -4.02090087e-01,   7.52756589e-01,  -5.72544094e-01,
         2.33250062e-03,   1.16309328e-01,  -2.01177043e+00,
        -7.03897563e-01,  -8.38662073e-01,   1.63927258e-01,
         5.17515212e-01,   1.49600412e+00,  -4.33518975e-01,
         2.45225045e+00,   1.12184091e+00,   1.24835448e+00,
         4.82839182e-01])
  • 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
  • 32
  • 33
  • 34
  • 35
(arr > 0).sum()
  • 1
55
  • 1
  • 2

另外还有两个方法any和all,它们对布尔型数组非常有用。any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True:

bools = np.array([False,False,True,False])
bools.any()
  • 1
  • 2
True
  • 1
  • 2
bools.all()
  • 1
False
  • 1
  • 2

上面这两个方法也能用于非布尔型数组,所有非0元素将会被当作True。

排序

和Python内置的列表一样,NumPy数组也可以通过sort方法就地排序:

arr = np.random.randn(8)
arr
  • 1
  • 2
array([ 0.10993707,  0.53310891, -0.11000054,  0.76957981, -0.47856471,
       -0.39485361,  0.90955727, -0.21967206])
  • 1
  • 2
  • 3
arr.sort()
arr
  • 1
  • 2
array([-0.47856471, -0.39485361, -0.21967206, -0.11000054,  0.10993707,
        0.53310891,  0.76957981,  0.90955727])
  • 1
  • 2
  • 3

多维数组可以在任一轴向上进行排序,只需将轴编号作为参数传递给sort即可:

arr = np.random.randn(5,3)
arr
  • 1
  • 2
array([[-0.90314314,  0.86505096,  1.60645742],
       [ 0.64705609,  0.14833059,  1.00294066],
       [ 1.08839789,  0.47122055, -0.29309475],
       [ 0.66432087,  1.53361673, -0.31666048],
       [-1.16355548,  0.68226654,  0.44107543]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
arr.sort(axis=1)
arr
  • 1
  • 2
array([[-0.90314314,  0.86505096,  1.60645742],
       [ 0.14833059,  0.64705609,  1.00294066],
       [-0.29309475,  0.47122055,  1.08839789],
       [-0.31666048,  0.66432087,  1.53361673],
       [-1.16355548,  0.44107543,  0.68226654]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

NumPy方法np.sort()返回的是数组已排序的副本,而就地排序则会直接修改数组本身。计算数组分位数最简单的方式就是对其进行排序,然后选取特定位置的值:

large_arr = np.random.randn(1000)
large_arr.sort()
large_arr[int(0.05 * len(large_arr))] #5%分位数
  • 1
  • 2
  • 3
-1.7394571373957528
  • 1
  • 2

唯一化以及其他的集合逻辑

NumPy提供了一些针对一维ndarray的基本集合运算。最常用的可能就是np.unique了,它用于找出数组中的唯一值并返回已排序的结果:

names
  • 1
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], 
      dtype='<U4')
  • 1
  • 2
  • 3
np.unique(names)
  • 1
array(['Bob', 'Joe', 'Will'], 
      dtype='<U4')
  • 1
  • 2
  • 3
ints = np.array([3,3,3,2,2,1,1,4,4])
np.unique(ints)
  • 1
  • 2
array([1, 2, 3, 4])
  • 1
  • 2

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

values = np.array([6,0,0,3,2,5,6])
np.in1d(values,[2,3,6])
  • 1
  • 2
array([ True, False, False,  True,  True, False,  True], dtype=bool)
  • 1
  • 2

下面给出一些常见的集合函数:

数组的集合运算
方法 说明
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的文件中。

arr = np.arange(10)
np.save('some_arr', arr)
  • 1
  • 2

如果文件路径末尾没有扩展名.npy,则该扩展会自动加上。然后我们就可以通过np.load读取磁盘上的数组:

np.load('some_arr.npy')
  • 1
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  • 1
  • 2

通过np.savez可以将多个数组保存到一个压缩文件中,将数组以关键字参数的形式传入即可:

np.savez('array_archive.npz', a=arr, b=arr)
  • 1

加载.npz文件时,我们可以得到一个类似字典的对象,该对象会对各个数组进行延迟加载:

arch = np.load('array_archive.npz')
arch['a']
  • 1
  • 2
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  • 1
  • 2

存取文本文件

我们可以使用np.loadtxt或更专门化的np.genfromtxt将数据加载到普通的NumPy数组中。这些函数都有许多选项可供使用:指定各种分隔符、针对特定列的转换器函数、需要跳过的行数等。以一个逗号分隔文件(CSV)为例,该文件可以被加载到一个二维数组中:

arr = np.loadtxt('ch04/array_ex.txt', delimiter=',')
arr
  • 1
  • 2
array([[ 0.580052,  0.18673 ,  1.040717,  1.134411],
       [ 0.194163, -0.636917, -0.938659,  0.124094],
       [-0.12641 ,  0.268607, -0.695724,  0.047428],
       [-1.484413,  0.004176, -0.744203,  0.005487],
       [ 2.302869,  0.200131,  1.670238, -1.88109 ],
       [-0.19323 ,  1.047233,  0.482803,  0.960334]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

np.savetxt执行的是相反的操作:将数组写到以某种分隔符隔开的文本文件中。np.genfromtxt和np.loadtxt差不多,只不过它面向的是结果化数组和缺失值处理。

线性代数

NumPy提供了一个用于矩阵乘法的dot函数(既是一个数组方法又是一个numpy命名空间的函数),用来计算数组的点积:

x = np.array([[1.,2.,3.],[4.,5.,6.]])
x
  • 1
  • 2
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.]])
  • 1
  • 2
  • 3
y = np.array([[6,23],[7,-1],[9,8]])
y
  • 1
  • 2
array([[ 6, 23],
       [ 7, -1],
       [ 9,  8]])
  • 1
  • 2
  • 3
  • 4
x.dot(y)
  • 1
array([[  47.,   45.],
       [ 113.,  135.]])
  • 1
  • 2
  • 3
np.dot(x, y)
  • 1
array([[  47.,   45.],
       [ 113.,  135.]])
  • 1
  • 2
  • 3

numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的函数:

from numpy.linalg import inv, qr
x = np.random.randn(5,5)
mat = x.T.dot(x)
inv(mat)
  • 1
  • 2
  • 3
  • 4
array([[ 0.97080581,  0.69952509,  0.32807994,  0.57116133,  0.48117027],
       [ 0.69952509,  1.08222129,  0.26585806,  0.43214412,  0.08022964],
       [ 0.32807994,  0.26585806,  0.41742633,  0.35787624, -0.01582423],
       [ 0.57116133,  0.43214412,  0.35787624,  1.1252964 ,  0.22345643],
       [ 0.48117027,  0.08022964, -0.01582423,  0.22345643,  0.75250724]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
mat.dot(inv(mat))
  • 1
array([[  1.00000000e+00,  -7.02026883e-17,  -3.48277978e-17,
          4.62548195e-16,  -8.72044935e-17],
       [  2.17715590e-16,   1.00000000e+00,  -9.59975760e-17,
         -1.12735566e-16,  -1.89418418e-16],
       [  5.11759305e-17,   7.20956036e-17,   1.00000000e+00,
          4.13026169e-17,   4.71745779e-17],
       [ -1.47966071e-16,  -7.41531895e-17,   8.07199323e-19,
          1.00000000e+00,  -4.28668114e-17],
       [ -6.21109493e-17,   2.26799877e-17,   2.81604452e-17,
         -1.86976926e-16,   1.00000000e+00]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
q,r = qr(mat)
r
  • 1
  • 2
array([[-7.40330483,  3.63406595,  4.53852767, -0.03289656,  5.0311309 ],
       [ 0.        , -1.2832662 ,  1.87107599,  0.03365102,  0.30984589],
       [ 0.        ,  0.        , -3.1352549 ,  1.50868538, -0.39436516],
       [ 0.        ,  0.        ,  0.        , -0.86234843,  0.73016071],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  1.08184824]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

下面给出一些常用的线性代数函数。

常用的numpy.linalg函数
函数 说明
diag 以一维数组的形式返回方阵的对角线(或非对角线)元素,或将一维数组转化为方阵(非对角线元素为0)
dot 矩阵乘法
trace 计算对角线元素和
det 计算矩阵行列式
eig 计算方阵的本征值和特征向量
inv 计算方阵的逆
pinv 计算矩阵的Moore-Penrose伪逆
qr 计算QR分解
svd 计算奇异值分解
solve 解线性方程组Ax=bAx=b,其中AA为一个方阵
lstsq 计算Ax=bAx=b的最小二乘解

随机数生成

numpy.random模块对Python内置的random进行了补充,增加了一些用于高效生成多种概率分布的样本值的函数。例如,你可以用normal来得到一个标准正太分布的4×4样本数组:

samples = np.random.normal(size=(4,4))
samples
  • 1
  • 2
array([[-0.85681072, -0.67265561,  0.87279636, -0.04188802],
       [-1.44416143, -0.38216318, -1.62525241, -2.06975896],
       [-1.30127431,  0.71888832,  1.0059813 ,  0.25227569],
       [-0.96717359, -0.36508683,  0.55353366, -0.04971921]])
  • 1
  • 2
  • 3
  • 4
  • 5

下表给出了numpy.random中的部分函数。

部分的numpy.random函数
函数 说明
seed 确定随机数生成器的种子
permutation 返回一个序列的随机排列或返回一个随机排列的范围
shuffle 对一个序列就地随机排列(即打乱一个序列)
rand 产生均匀分布的样本值
randint 从给定的上下限范围内随机选取整数
randn 产生正态分布(平均值为0,标准差为1)的样本值
binormal 产生二项分布的样本值
normal 产生正态(高斯)分布的样本值
beta 产生Beat分布的样本值
chisquare 产生卡方分布的样本值
gamma 产生Gamma分布的样本值
uniform 产生在[0,1)中均匀分布的样本值

猜你喜欢

转载自blog.csdn.net/qq_34504481/article/details/80831430