NumPy 是 Python 数值计算非常重要的一个包。很多科学计算包都是以 NumPy 的数组对象为基础开发的。

本文用代码快速过了一遍 NumPy 的基本操作,对 NumPy 整体有一个把握。希望能对读者有帮助。

NumPy ndarray:多维数组对象

ndarray 是 NumPy 很重要的特性,它是快速而灵活的数据集容器。ndarray 可以在整个数据集上进行类似于标量的运算,既快速又方便。

在 Jupyter Notebook 上,首先引入 NumPy 包:

 import numpy as np

创建 ndarray,其中包含随机值(np.random.randn):

 np.random.seed(666)  # 设置随机种子
data = np.random.randn(2, 3)
data
array([[ 0.82418808,  0.479966  ,  1.17346801],
[ 0.90904807, -0.57172145, -0.10949727]])

进行一些运算(乘法和加法):

 data * 10
data + data
array([[ 8.24188083,  4.79966003, 11.73468012],
[ 9.09048069, -5.71721452, -1.09497268]])
array([[ 1.64837617, 0.95993201, 2.34693602],
[ 1.81809614, -1.1434429 , -0.21899454]])

ndarray 所有的数据必须是相同类型,其属性 shape 表示形状大小,dtype 表示该数组的数据类型。

 data.shape
data.dtype
(2, 3)
dtype('float64')

ndarray 的创建

最简单的创建方法是使用 array 函数。该函数根据接收的序列对象,生成新的 NumPy 数组。

 data1 = [6, 66, 666, 6666, 66666]
arr1 = np.array(data1)
arr1
 arr2.shape
arr2.ndim
array([    6,    66,   666,  6666, 66666])

嵌套的序列(比如说嵌套列表),可以生成多维的数组。

 data2 = [[6, 66, 666], [6666, 66666, 666666]]
arr2 = np.array(data2)
arr2
array([[     6,     66,    666],
[ 6666, 66666, 666666]])

前面介绍了 ndarray 的属性 shape 表示形状大小,它还有一个属性 ndim 表示维度大小,我们可以对比两者的区别。

 arr2.shape
arr2.ndim
(2, 3)
2

除了 np.array,还有一些函数可以创建新数组:zerosonesempty 等等。(注意到 empty 不一定返回零值,某些情况它可能返回未初始化的不确定值

 np.zeros(6)
np.ones([2, 3])
np.empty((2, 3, 2))
array([0., 0., 0., 0., 0., 0.])
Out[26]:
array([[1., 1., 1.],
[1., 1., 1.]])
Out[26]:
array([[[0.00000000e+000, 0.00000000e+000],
[2.96439388e-323, 0.00000000e+000],
[2.14321575e-312, 1.58817677e-052]], [[6.50119216e-091, 1.74483574e-076],
[1.28392645e+160, 1.35027265e+161],
[3.99910963e+252, 9.02421558e-309]]])

np.arange 类似于 Python 的 range 函数,只不过它生成的是 ndarray。

 np.arange(6)
np.array(range(6)) # arange 等价于该效果
array([0, 1, 2, 3, 4, 5])
array([0, 1, 2, 3, 4, 5])

ndarray 的数据类型

前面介绍的 dtype 可以在创建时指定:

 arr1 = np.array([6, 66, 666], dtype=np.float64)
arr2 = np.array([6, 66, 666], dtype=np.int32)
arr1.dtype
arr2.dtype
dtype('float64')
dtype('int32')

一些典型的数据类型有:uint8、int32、float64、float128、complex64、bool、object、string_、unicode_ 等。

使用 astype 可以对数据类型进行显式转换。

 arr = np.array([6, 66, 666])
arr.dtype
float_arr = arr.astype(np.float64)
float_arr.dtype
dtype('int64')
dtype('float64')

浮点数数组转为整型数组:

 arr = np.array([1.7, 2.7, 3.7])
arr
arr.astype(np.int32)
array([1.7, 2.7, 3.7])
array([1, 2, 3], dtype=int32)

字符串数组转为浮点数数组(float 是 Python 类型,NumPy 自动解析识别):

 numeric_strings = np.array(['1.7', '-2.7', '3.7'], dtype=np.string_)
float_arr = numeric_strings.astype(float)
float_arr
float_arr.dtype
array([ 1.7, -2.7,  3.7])
dtype('float64')

使用其他数组的 dtype 属性来进行转换:

 int_arr = np.arange(6)
int_arr
float_arr = np.array([6., 66., 666.], dtype=np.float64)
int_arr.astype(float_arr.dtype)
array([0, 1, 2, 3, 4, 5])
array([0., 1., 2., 3., 4., 5.])

ndarray 的算术运算

ndarray 的算术运算可以避免写 for 循环,这既方便,性能也好。

数组与相同大小的数组之间的运算:

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

数组与标量的运算,会按数组的元素一一进行计算。

 1 / arr
arr ** 0.5
array([[1.        , 0.5       , 0.33333333],
[0.25 , 0.2 , 0.16666667]])
array([[1. , 1.41421356, 1.73205081],
[2. , 2.23606798, 2.44948974]])

相同大小的数组之间的比较运算,会返回布尔值的数组:

 arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
arr2
arr
arr2 > arr
array([[ 0.,  4.,  1.],
[ 7., 2., 12.]])
array([[1., 2., 3.],
[4., 5., 6.]])
array([[False, True, False],
[ True, False, True]])

索引和切片

一维数组很简单,看起来类似于 Python 列表:

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

第 5 行用到了 ndarray 的 broadcast 机制,这是和原生 Python 列表的第一个区别,我们可以看到列表会报错:

 li = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
li
li[5]
li[5:8]
li[5:8] = 12
li
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
5
[5, 6, 7]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-43-b59603238145> in <module>
3 li[5]
4 li[5:8]
----> 5 li[5:8] = 12
6 li TypeError: can only assign an iterable

ndarray 的切片(或索引)是一个指针(NumPy 称之为视图 view),不会复制底层数据,修改切片会直接影响到数组本身。

 arr_slice = arr[5:8]
arr_slice
arr_slice[1] = 666
arr
array([12, 12, 12])
array([ 0, 1, 2, 3, 4, 12, 666, 12, 8, 9])

切片 [:] 的赋值会影响到所有元素:

 arr_slice[:] = 777
arr
array([  0,   1,   2,   3,   4, 777, 777, 777,   8,   9])

由于 NumPy 经常和大型数据集打交道,因此切片是指针并不奇怪。如果希望不影响原数组,应该使用 copy 方法

 arr_slice = arr[5:8].copy()
arr_slice[:] = 666
arr
array([  0,   1,   2,   3,   4, 777, 777, 777,   8,   9])

在二维数组中,每个索引代表的元素不再是标量。要找到每个元素有两种方法。

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

在这样的二维数组中,axis 0 可以理解为,axis 1 可以理解为

三维数组的使用示例:

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

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

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

用切片进行索引

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

注意,如果使用切片作为索引,得到的总是相同维度的视图,而如果使用索引,则必定得到的是低维的数组

 arr2d
# 使用切片
arr2d[1:2, :2]
# 使用了索引
arr2d[1, :2]
arr2d[:2, 2]
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
array([[4, 5]])
array([4, 5])
array([3, 6])

布尔索引

首先生成一些数据:

 names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
np.random.seed(666)
data = np.random.randn(7, 4)
names
data
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')
array([[ 0.82418808, 0.479966 , 1.17346801, 0.90904807],
[-0.57172145, -0.10949727, 0.01902826, -0.94376106],
[ 0.64057315, -0.78644317, 0.60886999, -0.93101185],
[ 0.97822225, -0.73691806, -0.29873262, -0.46058737],
[-1.08879299, -0.57577075, -1.68290077, 0.22918525],
[-1.75662522, 0.84463262, 0.27721986, 0.85290153],
[ 0.1945996 , 1.31063772, 1.5438436 , -0.52904802]])

假设每个 name 对应着 data 中的每一行,我们希望选择 Bob 有关的数据。

 names == 'Bob'
data[names == 'Bob']
array([ True, False, False,  True, False, False, False])
array([[ 0.82418808, 0.479966 , 1.17346801, 0.90904807],
[ 0.97822225, -0.73691806, -0.29873262, -0.46058737]])

可以看到,选择了第 0 行和第 3 行的数据。需要注意的是布尔数组的长度必须和该维度的长度相同,否则会报错。

 np.array([ True, False, False,  True, False, False, False])[:-1]
data[np.array([ True, False, False, True, False, False, False])[:-1]]
array([ True, False, False,  True, False, False])
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-72-07628751d568> in <module>
1 np.array([ True, False, False, True, False, False, False])[:-1]
----> 2 data[np.array([ True, False, False, True, False, False, False])[:-1]] IndexError: boolean index did not match indexed array along dimension 0; dimension is 7 but corresponding boolean dimension is 6

布尔索引结合切片或索引的情况:

 data[names == 'Bob', 2:]
data[names == 'Bob', 3]
array([[ 1.17346801,  0.90904807],
[-0.29873262, -0.46058737]])
array([ 0.90904807, -0.46058737])

要取反,可以使用 != 或者 ~:

 data [names != 'Bob']
data[~(names == 'Bob')]
array([[-0.57172145, -0.10949727,  0.01902826, -0.94376106],
[ 0.64057315, -0.78644317, 0.60886999, -0.93101185],
[-1.08879299, -0.57577075, -1.68290077, 0.22918525],
[-1.75662522, 0.84463262, 0.27721986, 0.85290153],
[ 0.1945996 , 1.31063772, 1.5438436 , -0.52904802]])
array([[-0.57172145, -0.10949727, 0.01902826, -0.94376106],
[ 0.64057315, -0.78644317, 0.60886999, -0.93101185],
[-1.08879299, -0.57577075, -1.68290077, 0.22918525],
[-1.75662522, 0.84463262, 0.27721986, 0.85290153],
[ 0.1945996 , 1.31063772, 1.5438436 , -0.52904802]])

如果要结合多个布尔条件,使用布尔运算符 &(且) 和 |(或):

 names
mask = (names == 'Bob') | (names == 'Will')
mask
data[mask]
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')
array([ True, False, True, True, True, False, False])
array([[ 0.82418808, 0.479966 , 1.17346801, 0.90904807],
[ 0.64057315, -0.78644317, 0.60886999, -0.93101185],
[ 0.97822225, -0.73691806, -0.29873262, -0.46058737],
[-1.08879299, -0.57577075, -1.68290077, 0.22918525]])

注意:使用布尔进行索引后返回的数组总是是原数组的拷贝,而非指针,也没有 broadcast 机制。这是与索引或者切片所不同的地方。

 data
data_copy = data[names == 'Bob']
data_copy
data_copy[:, :] = 666
data_copy
data
array([[ 0.82418808,  0.479966  ,  1.17346801,  0.90904807],
[-0.57172145, -0.10949727, 0.01902826, -0.94376106],
[ 0.64057315, -0.78644317, 0.60886999, -0.93101185],
[ 0.97822225, -0.73691806, -0.29873262, -0.46058737],
[-1.08879299, -0.57577075, -1.68290077, 0.22918525],
[-1.75662522, 0.84463262, 0.27721986, 0.85290153],
[ 0.1945996 , 1.31063772, 1.5438436 , -0.52904802]])
array([[ 0.82418808, 0.479966 , 1.17346801, 0.90904807],
[ 0.97822225, -0.73691806, -0.29873262, -0.46058737]])
array([[666., 666., 666., 666.],
[666., 666., 666., 666.]])
array([[ 0.82418808, 0.479966 , 1.17346801, 0.90904807],
[-0.57172145, -0.10949727, 0.01902826, -0.94376106],
[ 0.64057315, -0.78644317, 0.60886999, -0.93101185],
[ 0.97822225, -0.73691806, -0.29873262, -0.46058737],
[-1.08879299, -0.57577075, -1.68290077, 0.22918525],
[-1.75662522, 0.84463262, 0.27721986, 0.85290153],
[ 0.1945996 , 1.31063772, 1.5438436 , -0.52904802]])

但是注意是返回后的数组才是拷贝,可以用布尔索引来直接进行赋值,也可以使用 broadcast 机制。

 data[names == 'Bob'] = 666
data
array([[ 6.66000000e+02,  6.66000000e+02,  6.66000000e+02,
6.66000000e+02],
[-5.71721452e-01, -1.09497268e-01, 1.90282649e-02,
-9.43761065e-01],
[ 6.40573153e-01, -7.86443172e-01, 6.08869993e-01,
-9.31011849e-01],
[ 6.66000000e+02, 6.66000000e+02, 6.66000000e+02,
6.66000000e+02],
[-1.08879299e+00, -5.75770746e-01, -1.68290077e+00,
2.29185248e-01],
[-1.75662522e+00, 8.44632619e-01, 2.77219863e-01,
8.52901533e-01],
[ 1.94599603e-01, 1.31063772e+00, 1.54384360e+00,
-5.29048024e-01]])

fancy 索引

fancy 索引在 NumPy 中指的是使用整型数组来进行索引。

假设有以下一个 ndarray:

 arr = np.empty((8, 4))
for i in range(8):
arr[i] = i
arr
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.]])

使用 fancy 索引可以选择行的顺序:

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

如果传入多个索引数组的话,会针对每个既定顺序的行选择既定顺序的列,最终得到几个元素的数组。

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

下面示例的效果是变换数组所选行和列的顺序,而不会降维。

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

注意:fancy 索引同样返回的是数组的拷贝,而非指针。

数组的转置和轴的交换

转置(.T)返回的是底层数组的视图,而非数组的拷贝。

 arr = np.arange(15). reshape((3, 5))
arr
arr.transpose() # 转置
arr.T # 更简便的写法
array([[ 0,  1,  2,  3,  4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])

矩阵的内积计算使用 np.dot

 arr1 = np.array([[3, -1], [1, 2]])
arr1
arr2 = np.array([[2, 1]])
arr2
np.dot(arr1, arr2.T)
array([[ 3, -1],
[ 1, 2]])
array([[2, 1]])
array([[5],
[4]])

transpose 可以接收元组参数来交换轴:

 arr = np.arange(6).reshape((2, 3))
arr
arr.transpose((0, 1))
arr.transpose((1, 0))
arr = np.arange(16).reshape((2, 2, 4))
arr
# 置换 axis=1 和 axis=0, axis=2 不变
# 由于 axis=2 不变,那么
# 设 a = [0, 1, 2, 3] b = [4, 5, 6, 7] c = [8, 9, 10, 11] d = [12, 13, 14, 15]
# arr = [[a, b], [c, d]]
# arr.transpose(1, 0) = arr.T = [[a, c], [b, d]] = [[[0, 1, 2, 3], [8, 9, 10, 11]], [[4, 5, 6, 7], [12, 13, 14, 15]]]
arr.transpose((1, 0, 2))
array([[0, 1, 2],
[3, 4, 5]])
array([[0, 1, 2],
[3, 4, 5]])
array([[0, 3],
[1, 4],
[2, 5]])
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]], [[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]], [[ 4, 5, 6, 7],
[12, 13, 14, 15]]])

swapaxes 同样可以交换轴:

 arr
arr.swapaxes(1, 2)
arr.transpose(0, 2, 1) # 类似于 arr.swapaxes(1, 2)
array([[[ 0,  1,  2,  3],
[ 4, 5, 6, 7]], [[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]], [[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]], [[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])

注意 swapaxes 同样返回的是数组的视图,而不是数组的拷贝。

通用函数

通用函数执行的是按元素的运算。

sqrtexp

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

maximumminimum

 np.random.seed(666)
x = np.random.randn(8)
x
y = np.random.randn(8)
y
np.maximum(x, y)
np.minimum(x, y)
array([ 0.82418808,  0.479966  ,  1.17346801,  0.90904807, -0.57172145,
-0.10949727, 0.01902826, -0.94376106])
array([ 0.64057315, -0.78644317, 0.60886999, -0.93101185, 0.97822225,
-0.73691806, -0.29873262, -0.46058737])
array([ 0.82418808, 0.479966 , 1.17346801, 0.90904807, 0.97822225,
-0.10949727, 0.01902826, -0.46058737])
array([ 0.64057315, -0.78644317, 0.60886999, -0.93101185, -0.57172145,
-0.73691806, -0.29873262, -0.94376106])

典型的一元通用函数有:

  • abs, fabs
  • sqrt
  • square
  • exp
  • log, log10, log2, log1p
  • sign
  • ceil, floor, rint
  • modf
  • isnan
  • isfinite, isinf
  • cos, cosh, sin, sinh, tan, tanh
  • arccos, arccosh, arcsin, arcsinh, arctan, arctanh
  • logical_not

典型的二元通用函数有:

  • add
  • subtract
  • multiply
  • divide, floor_divide
  • power
  • maximum, fmax
  • minimum, fmin
  • mod
  • copysign
  • greater, greater_equal, less, less_equal, equal, not_equal
  • logical_and, logical_or, logical_xor

面向数组编程

直接面向数组编程可以避免使用 for 循环,这又称为向量化(vectorization),一般来说性能会好上一两个量级以上。

 points = np.arange(-5, 5, 0.01)
xs, ys = np.meshgrid(points, points)
xs
ys
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]])
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]])
 z = np.sqrt(xs ** 2 + ys ** 2)
z
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 建立这个二维数组的数据可视化。

 import matplotlib.pyplot as plt

 plt.imshow(z, cmap=plt.cm.gray)
plt.colorbar() plt.title('Image plot of $\sqrt{x^2 + y^2}$ for a grid of values')

带条件逻辑的数组运算

numpy.where 函数是 x if condition else y 的向量化版本。

 x = np.array([6, 66, 666, 6666, 66666])
y = np.array([7, 77, 777, 7777, 77777])
cond = np.array([True, False, True, True, False])
result = np.where(cond, x, y)
result
array([    6,    77,   666,  6666, 77777])

典型的使用场景:

 np.random.seed(666)
arr = np.random.randn(4, 4)
arr
arr > 0
np.where(arr > 0, 1, -1) # 对两种情况都赋值
np.where(arr > 0, arr, 0) # 只处理小于零的情况
array([[ 0.82418808,  0.479966  ,  1.17346801,  0.90904807],
[-0.57172145, -0.10949727, 0.01902826, -0.94376106],
[ 0.64057315, -0.78644317, 0.60886999, -0.93101185],
[ 0.97822225, -0.73691806, -0.29873262, -0.46058737]])
array([[ True, True, True, True],
[False, False, True, False],
[ True, False, True, False],
[ True, False, False, False]])
array([[ 1, 1, 1, 1],
[-1, -1, 1, -1],
[ 1, -1, 1, -1],
[ 1, -1, -1, -1]])
array([[0.82418808, 0.479966 , 1.17346801, 0.90904807],
[0. , 0. , 0.01902826, 0. ],
[0.64057315, 0. , 0.60886999, 0. ],
[0.97822225, 0. , 0. , 0. ]])

数学和统计运算

典型的聚合函数有:sum,mean,std。

 np.random.seed(666)
arr = np.random.randn(6, 6)
arr
arr.mean()
np.mean(arr)
arr.sum()
arr.std()
array([[ 0.82418808,  0.479966  ,  1.17346801,  0.90904807, -0.57172145,
-0.10949727],
[ 0.01902826, -0.94376106, 0.64057315, -0.78644317, 0.60886999,
-0.93101185],
[ 0.97822225, -0.73691806, -0.29873262, -0.46058737, -1.08879299,
-0.57577075],
[-1.68290077, 0.22918525, -1.75662522, 0.84463262, 0.27721986,
0.85290153],
[ 0.1945996 , 1.31063772, 1.5438436 , -0.52904802, -0.6564723 ,
-0.2015057 ],
[-0.70061583, 0.68713795, -0.02607576, -0.82975832, 0.29655378,
-0.3126795 ]])
-0.03691228536732661
-0.03691228536732661
-1.328842273223758
0.81811319613762

mean 和 sum 有一个 axis 参数用于指定针对哪一行:

 arr.mean(axis=1)
arr.sum(axis=0)
array([ 0.45090857, -0.23212411, -0.36376326, -0.20593112,  0.27700915,
-0.14757295])
array([-0.3674784 , 1.0262478 , 1.27645117, -0.8521562 , -1.13434309,
-1.27756353])

一些典型的统计性方法:

  • sum
  • mean
  • std, var
  • min, max
  • argmin, argmax
  • cumsum
  • cumprod

布尔数组的方法

any 检查某数组是否有一个以上的 True,all 检查某数组是否所有值都为 True。

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

排序

 np.random.seed(666)
arr = np.random.randn(6)
arr
arr.sort()
arr
array([ 0.82418808,  0.479966  ,  1.17346801,  0.90904807, -0.57172145,
-0.10949727])
array([-0.57172145, -0.10949727, 0.479966 , 0.82418808, 0.90904807,
1.17346801])

针对某一维度进行排序:

 np.random.seed(666)
arr = np.random.randn(5, 3)
arr
arr.sort(1)
arr
array([[ 0.82418808,  0.479966  ,  1.17346801],
[ 0.90904807, -0.57172145, -0.10949727],
[ 0.01902826, -0.94376106, 0.64057315],
[-0.78644317, 0.60886999, -0.93101185],
[ 0.97822225, -0.73691806, -0.29873262]])
array([[ 0.479966 , 0.82418808, 1.17346801],
[-0.57172145, -0.10949727, 0.90904807],
[-0.94376106, 0.01902826, 0.64057315],
[-0.93101185, -0.78644317, 0.60886999],
[-0.73691806, -0.29873262, 0.97822225]])

查看四分位数:

 np.random.seed(666)
arr = np.random.randn(1000)
arr.sort()
arr[int(0.25 * len(arr))]
arr[int(0.75 * len(arr))]
-0.6786323778244538
0.6879940536373379

唯一值和集合逻辑

NumPy 有一些一维数组的基本的集合运算。其中 np.unique 是非常常用的。

 names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
np.unique(names)
ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
np.unique(ints)
# 纯 Python 实现
sorted(set(names))
array(['Bob', 'Joe', 'Will'], dtype='<U4')
array([1, 2, 3, 4])
['Bob', 'Joe', 'Will']

np.in1d 检查两个数组值的关系。

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

数组常用的集合运算有:

  • unique(x)
  • intersect1d(x, y)
  • union1d(x, y)
  • in1d(x, y)
  • setdiff1d(x, y)
  • setxor1d(x, y)

数组的文件输入和输出

NumPy 可以用文本或者二进制的方式把数据存储到硬盘,我们一般都选用二进制的方式。

np.savenp.load 分别是以二进制存储和加载的函数。

 arr = np.arange(10)
# 持久化
np.save('arr_data.npy', arr)
# 加载
np.load('arr_data.npy')
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

还可以以非压缩或者压缩的格式保存多个 NumPy 数据:

 # 非压缩
np.savez('arr_archive.npz', a=arr, b=arr)
# 压缩
np.savez_compressed('arr_compressed.npz', a=arr, b=arr)
# 加载
arch = np.load('arr_archive.npz')
arch['b']
cmp = np.load('arr_compressed.npz')
cmp['a']
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

线性代数

矩阵运算使用的是 dot 函数。

 x = np.array([[1., 2., 3.], [4., 5., 6.]])
y = np.array([[1., 2.], [-1, 0], [0, 1]])
x
y
x.dot(y)
np.dot(x, y) # 等价于 x.dot(y)
x @ y # Python3.5,等价于 x.dot(y)
array([[1., 2., 3.],
[4., 5., 6.]])
array([[ 1., 2.],
[-1., 0.],
[ 0., 1.]])
array([[-1., 5.],
[-1., 14.]])
array([[-1., 5.],
[-1., 14.]])
array([[-1., 5.],
[-1., 14.]])

numpy.linalg 有很多关于矩阵分解(如求逆、行列式等)的函数。

 from numpy.linalg import inv, qr

 np.random.seed(666)
X = np.random.randn(3, 3)
mat = X.T.dot(X)
inv(mat)
mat.dot(inv(mat)) # 等于单位矩阵
q, r = qr(mat)
r
array([[ 0.94360937,  0.09940334, -0.46256198],
[ 0.09940334, 0.70123855, -0.05689685],
[-0.46256198, -0.05689685, 0.78260277]])
array([[ 1.00000000e+00, 8.64663156e-18, -4.04784612e-17],
[ 1.27360445e-17, 1.00000000e+00, 6.98841494e-18],
[-7.26002573e-17, -9.92956304e-18, 1.00000000e+00]])
array([[-1.7499547 , 0.22916446, -1.66008207],
[ 0. , -1.43686954, -0.22584526],
[ 0. , 0. , 1.09786224]])

常用的 numpy.linalg 函数:

  • diag
  • dot
  • trace
  • det
  • eig
  • inv
  • pinv
  • qr
  • svd
  • solve
  • lstsq

生成伪随机数

numpy.random 有各种生成随机数样本的函数,比原生 Python 随机数生成模块速度快很多。

生成的随机数称为伪随机数,因为这些随机数实际上依赖于 np.random.seed 的值。

 np.random.seed(666)
samples = np.random.normal(size=(4, 4))
samples
array([[ 0.82418808,  0.479966  ,  1.17346801,  0.90904807],
[-0.57172145, -0.10949727, 0.01902826, -0.94376106],
[ 0.64057315, -0.78644317, 0.60886999, -0.93101185],
[ 0.97822225, -0.73691806, -0.29873262, -0.46058737]])

可以使用全局随机种子:

 rng = np.random.RandomState(666)
rng.randn(10)
array([ 0.82418808,  0.479966  ,  1.17346801,  0.90904807, -0.57172145,
-0.10949727, 0.01902826, -0.94376106, 0.64057315, -0.78644317])

常用的随机函数如下:

  • seed
  • permutation
  • shuffle
  • rand
  • randint
  • randn
  • binomial
  • normal
  • beta
  • chisquare
  • gamma
  • uniform

使用 NumPy 实现一个随机游走的例子

 np.random.seed(666)
nsteps = 1000 # 随机游走的步数
draws = np.random.randint(0, 2, size=nsteps) # 生成 0 或 1,一共 nsteps 个点
steps = np.where(draws == 0, 1, -1) # 等于 0 则设为 1,不等于 0 则设为 -1
walk = steps.cumsum() # 累积求和
walk.min()
walk.max()
(np.abs(walk) >= 10).argmax() # 第一次绝对值超过 10 的索引
plt.plot(walk[:100])

同时模拟多个随机游走

 np.random.seed(666)
nwalks = 5000
nsteps = 1000
draws = np.random.randint(0, 2, size=(nwalks, nsteps))
steps = np.where(draws > 0, 1, -1)
walks = steps.cumsum(1) # 1 表示针对 axis=1
walks
walks.shape # 行表示模拟的不同随机游走趋势,列表示不同的步数
array([[ -1,  -2,  -1, ..., -22, -21, -22],
[ 1, 2, 1, ..., 20, 19, 20],
[ 1, 0, -1, ..., -14, -13, -12],
...,
[ -1, 0, 1, ..., 24, 25, 26],
[ -1, -2, -1, ..., 16, 15, 16],
[ -1, 0, -1, ..., -24, -23, -22]])
(5000, 1000)

总结

本文介绍了 NumPy 基本操作。

NumPy 最重要的数据结构是 ndarray。我们熟悉了:

  • 怎么创建 ndarray
  • ndarray 的数据类型
  • ndarray 的算术运算
  • 如何使用索引切片布尔索引fancy 索引
  • 数组的转置轴的变换

围绕着 ndarray,NumPy 还提供了很多函数和方法:

  • 通用函数:实现快速的按元素的运算
  • 面向数组的编程:条件逻辑、数学和统计方法、布尔数组的方法、排序方法、唯一值及集合逻辑
  • 数组的持久化和加载方法
  • 线性代数方面dotnumpy.linalg
  • 有关随机数生成的 np.random

最后是一个有关随机游走的简单示例。

通过本文我们主要知道了 NumPy 的核心数据结构 ndarray,以及有关它的各种函数出现的源头都是:

向量化的思想——数值计算尽量别用 for 循环,这样代码看起来简单,性能也好得多。

最后欢迎留言,有不足之处请指正。

参考

  • 《Python for Data Analysis, 2nd Edition》by Wes McKinney

NumPy基本操作快速熟悉的更多相关文章

  1. guava 学习笔记(二) 瓜娃(guava)的API快速熟悉使用

    guava 学习笔记(二) 瓜娃(guava)的API快速熟悉使用 1,大纲 让我们来熟悉瓜娃,并体验下它的一些API,分成如下几个部分: Introduction Guava Collection ...

  2. 入职一个月快速熟悉大型Vue项目经验感想

    来到和睦的公司家庭已经一个月出头了,从技术层面来说,公司项目PC端是我目前来说接触的最大最复杂的项目了,德老师也说这个不断开发更新迭代的项目的代码量相对于全国的web来说是蛮多的,对于快速熟悉这样的大 ...

  3. 快速熟悉Velocity

    果然公司用的东西跟平时学的东西不太一样,我们公司前台页面并不是我们熟悉的.html或者.jsp文件,而是很多人不知道的 .vm文件,其实只要我们理解了jsp文件,vm文件也就是一些基本语法不同而已. ...

  4. 快速熟悉Oracle索引

    一.索引 1.1 什么是索引? 一种用于提升查询效率的数据库对象: 通过快速定位数据的方法,减少磁盘的输入输出操作: 索引信息与表独立存放: Oracle数据库自动使用和维护索引. 1.2 索引分类 ...

  5. Numpy基本操作

    NumPy:数组计算 NumPy是高性能科学计算和数据分析的基础包.它是Pandas等其他各种工具的基础 NumPy的主要功能: ndarray,一个多维数据结构,高校且节省空间 无需循环即可对整组数 ...

  6. 矩阵库Numpy基本操作

    NumPy是一个关于矩阵运算的库,熟悉Matlab的都应该清楚,这个库就是让python能够进行矩阵话的操作,而不用去写循环操作. 下面对numpy中的操作进行总结. numpy包含两种基本的数据类型 ...

  7. guava 学习笔记 瓜娃(guava)的API快速熟悉使用

    1,大纲 让我们来熟悉瓜娃,并体验下它的一些API,分成如下几个部分: Introduction Guava Collection API Guava Basic Utilities IO API C ...

  8. 学习笔记(二) 瓜娃(guava)的API快速熟悉使用

    1,大纲 让我们来熟悉瓜娃,并体验下它的一些API,分成如下几个部分: Introduction Guava Collection API Guava Basic Utilities IO API C ...

  9. 【转】瓜娃(guava)的API快速熟悉使用

    http://www.cnblogs.com/snidget/archive/2013/02/05/2893344.html 1,大纲 让我们来熟悉瓜娃,并体验下它的一些API,分成如下几个部分: I ...

随机推荐

  1. python实现两个两个的翻转字符串

    #!/usr/bin/python str = "ABCDEFGH" print(str) lenstr=len(str) print(lenstr) ss='' for i in ...

  2. python 之类的继承

    #类的继承 class As1(): def As2(self): print("he11...") class As2(As1): def As2(self): print(&q ...

  3. svn在cleanup 时,提示privious operation has not finished,解决方案

    在updated代码时,svn 提示 上一次操作失败,需要cleanup. 执行cleanup时,提示:cleanup失败,因为上一次操作失败,请先执行cleanup.很幽默的提示. svn的“.sv ...

  4. oracle拼接sql语句

    示例: select  'select a.xh,a.dj,a.xzb from xsjbxxb a where a.xzb=' || chr(39)  ||   a.xzb ||  chr(39)  ...

  5. javascript 数组之间增加某个符合arr.join('、');

    var arr=["a","b","c"]; arr.join(',');//返回值是字符串:a,b,c

  6. JDK安装及配置——Linux系统

    一.首先下载JDK版本 链接如下:https://www.oracle.com/technetwork/java/javase/downloads/index.html 截止写博客时,最新版已经到12 ...

  7. Win10应用商城删除后部分应用出错的解决方案

    出错图示 解决方案 查找完整包名:Get-AppxPackage -allusers | Select Name, PackageFullName Add-appxpackage -register ...

  8. AQS原理解析 AbstractQueuedSynchronizer

    AQS实现原理  https://blog.csdn.net/ym123456677/article/details/80381354   https://www.cnblogs.com/keleli ...

  9. framework7 Autocomplete (自动完成) 具体使用

    官网地址:https://framework7.io/docs/autocomplete.html#autocomplete-parameters 效果图: <meta charset=&quo ...

  10. FindWindow和FindWindowEx函数使用

    FindWindow(   lpClassName,        {窗口的类名}   lpWindowName: PChar {窗口的标题} ): HWND;              {返回窗口的 ...