Numpy Tutorial

摘要

NumPy 是 Numeric Python,是 Python 中很重要的一个科学计算类库,在机器学习、数据分析中应用广泛,也是很多相关领域类库的基础。

1. 基本概念

NumPy的主要对象是一个均衡的多维数组。也就是一张元素的表格,这些元素通常是数字,所有的元素都是相同类型的,通过下标构成的元组进行索引。在NumPy中的多维数组的维度被成为轴(axes)。轴的数量被称为秩(rank)

例如,在3D空间中的一个点的坐标[1, 2, 1]可以表示为一个秩为1的数组。因为它只有一个轴。这个轴的长度是3.在下面的例子中,这个数组的秩为2(这是一个二维数组)。第一维(轴)的长度为2,第二维的长度为3。

1
2
3
4
[
[1, 0, 0],
[0, 1, 2]
]

NumPy 的数组类被称为 ndarray,其别名为 array。注意,numpy.array 与标准 Python 类库中的 array.array 是不同的,标准类库中的数组只能处理一维数组,并且只提供了极少数的功能。下面列举了一些ndarray 对象的重要属性。

1.1. 维度(ndarray.ndim)

表示数组中轴(维度)的数量。在Python中,维度的数量也被称为秩(rank)

一维数组

1
2
3
4
import numpy as np

myArray = np.array([1, 0])
print(myArray.ndim) # print 1

二维数组

1
2
3
4
import numpy as np

myArray = np.array([[1, 0, 3], [2, 3, 5]])
print(myArray.ndim) # print 2

三维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

myArray = np.array(
[
[
[1, 2],
[3, 1]
], [
[1, 2],
[3, 1]
]
]
)
print(myArray.ndim) # print 3

1.2. 维度的大小(ndarray.shape)

是一个表示数组维度大小的元组。元组中的整型数字表示数组中相应维度的大小。例如一个有 n 行 m 列的举证,shape 就是(n, m)。这个 shape 元组的长度也就是秩(rank)或者说是维度的数量。

一维数组

1
2
3
4
import numpy as np

myArray = np.array([1, 0])
print(myArray.shape)

这个打印的结果是 (2) 。这是个一维数组,一共有两个元素,因此shape是(2)。

二维数组

1
2
3
4
import numpy as np

myArray = np.array([[1, 0, 3], [2, 3, 5]])
print(myArray.shape)

这个打印的结果是(2, 3)。这是一个二维数组,因此这个shape元组中有两个元素。

第一个元素表示第一个维度中元素的数量,这个数组中第一个维度中有两个元素,分别是[1, 0, 3], [2, 3, 5]。

第二个元素表示第二个维度中元素的数量,也就是[1, 0, 3]和[2, 3, 5]中都有三个元素,因此是3。

三维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

myArray = np.array(
[
[
[1, 2, 1],
[3, 1, 1]
], [
[1, 2, 1],
[3, 1, 1]
]
]
)
print(myArray.shape)

这个打印出来的结果是(2, 2, 3),这个数组是一个三维数组,因此这个shape元组中有三个元素。

第一个维度中有两个元素,第二个维度中也是有两个元素,第三个维度则是最内部的有三个元素的数组。

1.3. 元素数量(ndarray.size)

数组中所有元素的数量。这个数字等同于 shape 的元组中所有元素的乘积。

一维数组

1
2
3
4
import numpy as np

myArray = np.array([1, 0])
print(myArray.size)

这个打印出来的结果是2。表示这个数组中一个有两个元素。

二维数组

1
2
3
4
import numpy as np

myArray = np.array([[1, 0, 3], [2, 3, 0]])
print(myArray.size)

这个数组中有六个元素,因此打印出来的结果是6。

1.4. 元素类型(ndarray.dtype)

一个用于描述数组中元素类型的对象。使用标准Python类型可以创建或指定dtype,除此之外,NumPy还提供了其自定义的类型:numpy.int32,numpy.int16和numpy.float64等等

浮点型

1
2
3
4
import numpy as np

myArray = np.array([3.2, 5.1])
print(myArray.dtype)

这个打印出来的内容是 float64。表示这里的元素是浮点数。

整型

1
2
3
4
import numpy as np

myArray = np.array([3, 5])
print(myArray.dtype)

这里打印出来的内容是 int32。表示是32位整型。

布尔型

1
2
3
4
import numpy as np

myArray = np.array([False, True])
print(myArray.dtype)

这里打印出来的内容是 bool。表示是布尔值。

字符型

1
2
3
4
import numpy as np

myArray = np.array(['a', 'cc'])
print(myArray.dtype)

这里打印出来的内容是U2。U表示的是数据类型,表示Unicode,后面的2表示数据的长度是2个字符。

2. 创建数组

2.1. 将Python数据结构转为NumPy数组

2.1.1. 将元组创建为数组

array()函数可以接受Python的元组为参数,并将这个元组创建为数组。

1
2
3
4
import numpy as np

myArray = np.array((3, 2, 4, 5))
print(myArray)

打印内容为:[3 2 4 5]。

2.1.2. 将列表创建为数组

array()函数可以接受Python中的列表作为参数,将其转为数组。

1
2
3
4
import numpy as np

myArray = np.array([[1, 0, 3], [2, 3, 0]])
print(myArray)

2.2. 使用占位符创建指定shape的数组

所谓使用占位符创建数组的意思就是创建一个数组,然后使用占位符来填充其中的每个元素。比如创建一个 shape 为 (2,) 的数组,则表示创建一个如下形式的数组:

1
[占位符, 占位符]

如果要创建一个shape为(2, 3)的数组,则结果为如下形式:

1
2
3
4
[
[占位符, 占位符, 占位符],
[占位符, 占位符, 占位符]
]

2.2.1. 使用0作为占位符创建数组

使用 zeros() 函数接受一个 shape 元组可以用来创建占位符为 0 的指定大小的数组。

1
2
3
4
import numpy as np

myArray = np.zeros((3, 4))
print(myArray)

打印结果为:

1
2
3
[[ 0.  0.  0.  0.]
[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]]

2.2.2. 使用1作为占位符创建数组

使用 ones() 函数接受一个 shape 元组可以用来创建占位符为1的指定大小的数组。

1
2
3
4
import numpy as np

myArray = np.ones((3, 4))
print(myArray)

打印结果:

1
2
3
[[ 1.  1.  1.  1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]

2.2.3. 使用随机浮点数作为占位符创建数组

使用 empty() 函数接受一个shape元组可以用来创建占位符为随机数的指定大小的数组。

1
2
3
4
import numpy as np

myArray = np.empty((3, 4))
print(myArray)

打印结果:

1
2
3
[[  8.82769181e+025   7.36662981e+228   7.54894003e+252   2.95479883e+137]
[ 1.42800637e+248 2.64686750e+180 1.09936856e+248 6.99481925e+228]
[ 7.54894003e+252 7.67109635e+170 2.64686750e+180 3.50784751e-191]]

2.2.4. 创建 0 1 对称数组

创建中轴线为1,其余为0填充的数组。

1
2
3
4
import numpy as np

myArray = np.eye(5)
print(myArray)

打印结果:

1
2
3
4
5
[[ 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.]]

2.3. 创建数字序列数组

2.3.1. arange()

使用 arange() 创建数字序列数组,这个函数接受三个参数,前两个参数分别表示序列的首尾,第三个参数表示步长。

1
2
3
4
import numpy as np

myArray = np.arange(1, 20, 2)
print(myArray)

该段代码创建的数组为:

1
[ 1  3  5  7  9 11 13 15 17 19]

使用 reshape() 调整数组维度,刺入将上面的数组调整为 (2, 5):

1
print(myArray.reshape(2, 5))

打印结果:

1
2
[[ 1  3  5  7  9]
[11 13 15 17 19]]

2.3.2. linespace()

以线性等分形式创建数组,给定起始值和切分的分数,自动创建等分元素数组。

1
2
3
4
import numpy as np

myArray = np.linspace(0, 5, 11)
print(myArray)

打印结果:

1
[ 0.   0.5  1.   1.5  2.   2.5  3.   3.5  4.   4.5  5. ]

2.3.3. 创建重复序列数组

1
2
3
4
5
import numpy as np

myArray = np.array([1, 2, 3] * 3)
# or myArray = np.repeat([1, 2, 3], 3)
print(myArray)

打印结果:

1
[1 2 3 1 2 3 1 2 3]

2.3.4. 创建重复元素数组

注意与重复序列数组的区别。

1
2
3
4
import numpy as np

myArray = np.repeat([1, 2, 3], 3)
print(myArray)

打印结果:

1
[1 1 1 2 2 2 3 3 3]

3. 基本运算

3.1. 数据类型

1
2
3
4
5
6
7
import numpy as np

myArray = np.array([[4, 5, 6], [14, 25, 36]])
print(myArray.dtype)

newArray = myArray.astype('f')
print(newArray.dtype)

结果为:

1
2
int32
float32

数组上的算术运算是作用于数组内的元素上的。

3.2. 减法运算

1
2
3
4
5
6
import numpy as np

array1 = np.array([10, 20, 30, 40])
array2 = np.array([1, 2, 3, 4])
array3 = array1 - array2
print(array3)

打印结果:[9, 18, 27, 36]。观察这个运算后的结果可以发现上面两个数组相减实际上是对应位置上的元素进行了减法运算。

3.3. 平方运算

同样的道理,对数组进行平方运算也同样是作用在数组的元素上。

1
2
3
4
5
import numpy as np

array1 = array([1, 2, 3, 4])
array2 = array1 ** 2
print(array2)

结果为:[1, 4, 9, 16]

3.4. 矩阵乘法运算

首先复习一下矩阵的乘法运算,有如下两个A、B矩阵相乘:

1
2
3
4
5
a b    e f
c d g h

ae+bg af+bh
ce+dg cf+dh

仔细观察其运算规则实际上新的矩阵的第一行的第一个元素是原矩阵A的第一行分别与矩阵B的第一列元素相乘之后的和。而第一行第二个元素是矩阵A的第一行与矩阵B的第二列乘积的和。

在 NumPy 中矩阵相乘需要使用 dot() 方法,而不是直接使用 “*”

1
2
3
4
5
6
import numpy as np

array1 = ones((2, 2))
array2 = ones((2, 2))
array3 = array1.dot(array2)
print(array3)

结果为:

1
2
[[ 2.  2.]
[ 2. 2.]]

3.5. 数学函数

常用的数学计算,比如求和、求平均、最值等。

1
2
3
4
5
6
7
8
9
10
11
import numpy as np

myArray = np.array([1, 5, 0, -7, 23, -2])
print("sum: " + str(myArray.sum()))
print("max: " + str(myArray.max()))
print("min: " + str(myArray.min()))
print("mean: " + str(myArray.mean()))
print("std: " + str(myArray.std()))

print("index of max: " + str(myArray.argmax()))
print("index of min: " + str(myArray.argmin()))

结果为:

1
2
3
4
5
6
7
sum: 20
max: 23
min: -7
mean: 3.33333333333
std: 9.49853789918
index of max: 4
index of min: 3

4. 数组操作

4.1. 合并数组

4.1.1. 竖直合并 vstack()

1
2
3
4
5
import numpy as np

myArray = np.ones((2, 3))
myArray2 = np.vstack([myArray, myArray*2])
print(myArray2)

结果为:

1
2
3
4
[[ 1.  1.  1.]
[ 1. 1. 1.]
[ 2. 2. 2.]
[ 2. 2. 2.]]

4.1.2. 水平合并 vstack()

1
2
3
4
5
import numpy as np

myArray = np.ones((2, 3))
myArray2 = np.hstack([myArray, myArray*2])
print(myArray2)

结果为:

1
2
[[ 1.  1.  1.  2.  2.  2.]
[ 1. 1. 1. 2. 2. 2.]]

4.2. 转置

1
2
3
4
5
import numpy as np

myArray = np.array([[4, 5, 6], [14, 25, 36]])
print(myArray)
print(myArray.T)

结果为:

1
2
3
4
5
6
7
8
# myArray
[[ 4 5 6]
[14 25 36]]

# myArray.T
[[ 4 14]
[ 5 25]
[ 6 36]]

4.3. 索引和切片

4.3.1. 一维数组切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np

myArray = np.arange(13)**2
print(myArray) # [ 0 1 4 9 16 25 36 49 64 81 100 121 144]

# 数组索引是从 0 开始的。
print(myArray[2]) # 4

# 提取索引从 1 到 4 的元素
print(myArray[1:5]) # [ 1 4 9 16]

# 从后往前获取 4 个元素
print(myArray[-4:]) # [ 81, 100, 121, 144]

# 第二个冒号可以用来表示步长 array [start:stop:step]
print(myArray[-5::-2]) # [64, 36, 16, 4, 0]

4.3.2. 二维数组切片

二维数组切片的语法:

1
array[row, column]

首先创建一个测试用的二维数组:

1
2
3
4
5
import numpy as np

myArray = np.arange(36)
myArray.resize((6, 6))
print(myArray)

结果为:

1
2
3
4
5
6
[[ 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 32 33 34 35]]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 索引
print(myArray[2, 2]) # 4

# 索引为 3 的行中的 索引为 3 - 5 的元素
print(myArray[3, 3:6]) # [21, 22, 23]

# 选择前两行的前五个元素
print(myArray[:2, :-1]) # [[ 0, 1, 2, 3, 4], [ 6, 7, 8, 9, 10]]

# 条件筛选
print(myArray[myArray > 30]) # [31, 32, 33, 34, 35]

# 条件筛选重新赋值
myArray[myArray > 30] = 30

重新赋值后的数组:

1
2
3
4
5
6
[[ 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, 30, 30, 30, 30, 30]]

4.4. 复制

在 Numpy 中处理复制操作时,需要比较小心,这个涉及到值拷贝和引用拷贝的问题。

仍然创建一个二维数组用于测试。

1
2
3
4
5
import numpy as np

myArray = np.arange(36)
myArray.resize((6, 6))
print(myArray)

结果为:

1
2
3
4
5
6
[[ 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 32 33 34 35]]

然后对这个二维数组进行切片:

1
2
myArray2 = myArray[:3, :3]
print(myArray2)

打印结果:

1
2
3
[[ 0,  1,  2],
[ 6, 7, 8],
[12, 13, 14]]

接下来我们对这个 myArray2 进行修改:

1
2
myArray2[:] = 0
print(myArray2)

修改后,myArray2 所有的元素都变成了 0:

1
2
3
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]

此时,我们再观察一下 myArray,看看它有什么变化:

1
print(myArray)

打印结果:

1
2
3
4
5
6
[[ 0,  0,  0,  3,  4,  5],
[ 0, 0, 0, 9, 10, 11],
[ 0, 0, 0, 15, 16, 17],
[18, 19, 20, 21, 22, 23],
[24, 25, 26, 27, 28, 29],
[30, 30, 30, 30, 30, 30]]

我们发现这个数组的值也被修改了,这个就是所谓的引用,myArray2 并没有重新创建一个数组。

要避免这个问题,需要使用 copy() 方法来拷贝数组。

1
2
3
4
5
6
7
8
myArray_copy = myArray.copy()

print(myArray_copy)

myArray_copy[:] = 10

print(myArray_copy)
print(myArray)

对比几次打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 一开始拷贝的 myArray_copy
[[ 0, 0, 0, 3, 4, 5],
[ 0, 0, 0, 9, 10, 11],
[ 0, 0, 0, 15, 16, 17],
[18, 19, 20, 21, 22, 23],
[24, 25, 26, 27, 28, 29],
[30, 30, 30, 30, 30, 30]]

# 修改后的 myArray_copy
[[10 10 10 10 10 10]
[10 10 10 10 10 10]
[10 10 10 10 10 10]
[10 10 10 10 10 10]
[10 10 10 10 10 10]
[10 10 10 10 10 10]]

# 原始的 myArray
[[ 0, 0, 0, 3, 4, 5],
[ 0, 0, 0, 9, 10, 11],
[ 0, 0, 0, 15, 16, 17],
[18, 19, 20, 21, 22, 23],
[24, 25, 26, 27, 28, 29],
[30, 30, 30, 30, 30, 30]]

4.5. 遍历数组

1
2
3
4
import numpy as np

myArray = np.random.randint(0, 10, (4,3))
print(myArray)

结果为:

1
2
3
4
[[8 3 8]
[4 5 0]
[7 4 7]
[1 3 5]]

遍历数组:

1
2
for row in myArray:
print(row)

打印结果:

1
2
3
4
[8 3 8]
[4 5 0]
[7 4 7]
[1 3 5]

利用索引遍历:

1
2
for i in range(len(myArray)):
print(myArray[i])

打印结果:

1
2
3
4
[8 3 8]
[4 5 0]
[7 4 7]
[1 3 5]

通过索引和行遍历:

1
2
for i, row in enumerate(myArray):
print('row', i, 'is', row)

打印结果:

1
2
3
4
row 0 is [8 3 8]
row 1 is [4 5 0]
row 2 is [7 4 7]
row 3 is [1 3 5]

遍历多个数组:

1
2
3
4
myArray2 =  myArray * 2 # 创建第二个数组

for i, j in zip(myArray, myArray2):
print(i,'+',j,'=',i+j)

打印结果为:

1
2
3
4
[8 3 8] + [16  6 16] = [24  9 24]
[4 5 0] + [ 8 10 0] = [12 15 0]
[7 4 7] + [14 8 14] = [21 12 21]
[1 3 5] + [ 2 6 10] = [ 3 9 15]