NumPy(Numerical Python)是Python科学计算的基础库,提供了高性能的多维数组对象以及用于操作这些数组的工具。是几乎所有Python科学计算和数据分析库的底层依赖

它最核心的作用是用高效的多维数组来储存和计算数据

Python自带的列表也能存放数据,但是列表更偏向通用容器,而NumPy提供的数组更适合做数学计算

1. ndarray数组基础

ndarray是NumPy中最核心的数据结构,全称是"N-dimensional array",也就是N维数组,NumPy里的大部分操作,都是围绕ndarray展开的

1.1. 一维数组

一维数组可以理解为一排数据,相当于数学中的向量

用NumPy创建一维数组:

1
2
3
4
5
import numpy as np

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

print(arr)

输出:

1
[1 2 3 4 5]

1.2. 二维数组

二维数组可以理解为一个表格,相当于数学中的矩阵

用NumPy创建二维数组:

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

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

print(arr)

输出:

1
2
[[1 2 3]
[4 5 6]]

这是一个2行3列的二维数组

1.3. 三维数组

三维数组则可以理解为多个二维数组,这也是理解更多维度数组的一个思路

用NumPy创建三维数组:

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

print(arr)

输出:

1
2
3
4
5
[[[ 1  2  3]
[ 4 5 6]]

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

2个二维数组;每个二维数组有2行;每行有3列

1.4. ndarray的维度:ndim

ndim表示数组的维度数量

例子:

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

print(arr1.ndim)
print(arr2.ndim)

输出:

1
2
1
2

1.5. ndarray的形状:shape

shape表示数组每个维度的大小

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

print(arr.shape)

输出:

1
(2, 3)

意思是2行3列

严格来说是:

axis 0 方向有 2 个元素

axis 1 方向有 3 个元素

1.6. ndarray的数据类型:dtype

dtype表示数组元素的数据类型

例子:

1
2
3
arr = np.array([1, 2, 3])

print(arr.dtype)

输出:

1
int64

如果是小数:

1
2
arr = np.array([1.1, 2.2, 3.3])
print(arr.dtype)

输出可能是:

1
float64

NumPy数组通常要求元素类型统一

创建数组时也可以手动指定类型:

1
2
3
4
arr = np.array([1, 2, 3], dtype=np.float64)

print(arr)
print(arr.dtype)

输出:

1
2
[1., 2., 3.]
float64

注意这里不是四舍五入,而是直接截断小数部分

1.7. ndarray的元素个数:size

size表示数组中总共有多少个元素

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

print(arr.size)

输出:

1
6

这个数组有2行3列,一共6个元素

1.8. ndarray每个元素占多少字节:itemsize

1
2
3
arr = np.array([1, 2, 3], dtype=np.int32)

print(arr.itemsize)

输出:

1
4

因为int32占32bit,也就是4字节

2. 数组创建

在NumPy中,创建数组最常用的对象是ndarray。数组可以从Python列表创建,也可以按照指定形状、指定范围或指定初始值创建

2.1. np.array()

np.array()是最常用的创建数组的方法

np.array()可以把Python中已有的数据转换成NumPy数组

完整函数签名:

1
2
3
4
5
6
7
8
9
10
11
numpy.array(
object, # 要被转换成数组的数据
dtype=None, # 指定数组元素的数据类型
*,
copy=True, # 是否复制数据,默认复制
order='K', # 控制数组在内存中的存储顺序,默认保持输入对象原本的内存布局
subok=False, # 控制是否保留子类类型,默认保留
ndmin=0, # 表示生成数组的最小维度
ndmax=0, # 表示生成数组的最大维度
like=None # 参考另一个数组对象创建兼容的数组
)

最常用的三个参数:

1
np.array(object, dtype=None, ndmin=0)

例子:

1
2
3
4
import numpy as np

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

输出:

1
[1 2 3]

指定数据类型:

1
2
a = np.array([1, 2, 3], dtype=float)
print(a)

输出:

1
[1. 2. 3.]

2.2. np.zeros()

np.zeros()用来创建一个全是0的数组

完整函数签名:

1
2
3
4
5
6
7
8
np.zeros(
shape, # 数组形状
dtype=None, # 数组元素数据类型,默认是np.float64
order='C', # 控制数组在内存中的存储顺序,默认为表示C风格的行优先储存
*,
device=None, # 设备参数,日常一般用不上,主要是为了兼容Array API标准
like=None # 参考另一个数组对象创建兼容的数组
)

最常用的两个参数:

1
np.zeros(shape, dtype=None)

例子:

1
2
3
4
import numpy as np

a = np.zeros(5)
print(a)

输出:

1
[0. 0. 0. 0. 0.]

可以用元组来创建多维数组:

1
2
a = np.zeros((2, 3))
print(a)

输出:

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

与**np.zeros()**用法相似,**np.ones()**用来创建全1数组

2.3. np.empty()

np.empty()用来创建一个不初始化值的数组

完整函数签名:

1
2
3
4
5
6
7
8
np.empty(
shape, # 数组形状
dtype=None, # 数组元素数据类型,默认是np.float64
order='C', # 控制数组在内存中的存储顺序,默认为表示C风格的行优先储存
*,
device=None, # 设备参数,日常一般用不上,主要是为了兼容Array API标准
like=None # 参考另一个数组对象创建兼容的数组
)

与**np.zeros()**用法相似,最主要区别只是不初始化元素

例子:

1
2
3
4
import numpy as np

a = np.empty(5)
print(a)

输出类似于:

1
2
[8.62134873e-316 0.00000000e+000 1.51751028e-147 1.26489931e-300
2.37151510e-322]

np.empty()里面的值不是随机数,而是内存里原来残留的数据。

2.4. np.arange()

np.arange()是Numpy里用来创建等差数组的函数

完整函数签名:

1
2
3
4
5
6
7
8
9
numpy.arange(
[start, ] # 起始值,可省略
stop, # 结束值
[step, ] # 步长
dtype=None, # 数组元素数据类型,默认情况下,如果传的是整数,通常会生成整数数组;如果传的是浮点数,就会生成浮点数组
*,
device=None, # 设备参数,日常一般用不上,主要是为了兼容Array API标准
like=None # 参考另一个数组对象创建兼容的数组
)

例子:

1
2
arr = np.arange(5)
print(arr)

输出:

1
[0 1 2 3 4]

2.5. np.linspace()

np.linspace()是NumPy里用来创建等间隔数组的函数

完整函数签名:

1
2
3
4
5
6
7
8
9
10
11
numpy.linspace(
start, # 起始值
stop, # 结束值
num=50, # 生成多少个数
endpoint=True, # 表示是否包含终点stop,默认是True
retstep=False, # 表示是否返回步长,默认是False
dtype=None, # 数组元素数据类型
axis=0, # 沿着哪个轴来操作,默认0就是行方向
*,
device=None # 设备参数,日常一般用不上,主要是为了兼容Array API标准
)

例子:

1
2
3
4
5
6
start = np.array([0, 10])
stop = np.array([1, 20])

arr = np.linspace(start, stop, 5, axis=0)
print(arr)
print(arr.shape)

输出:

1
2
3
4
5
6
7
[[ 0.   10.  ]
[ 0.25 12.5 ]
[ 0.5 15. ]
[ 0.75 17.5 ]
[ 1. 20. ]]

(5, 2)

这里的含义是:

第1列:从0到1生成5个数

第2列:从10到20生成5个数

2.6. np.full()

np.full()是NumPy里用来创建用同一个值填满的数组

完整函数签名:

1
2
3
4
5
6
7
8
9
numpy.full(
shape, # 数组形状
fill_value, # 用于填充的值
dtype=None, # 数组元素数据类型
order='C', # 控制数组在内存中的存储顺序,默认为表示C风格的行优先储存
*,
device=None, # 设备参数,日常一般用不上,主要是为了兼容Array API标准
like=None # 参考另一个数组对象创建兼容的数组
)

例子:

1
2
3
4
import numpy as np

arr = np.full(5, 7)
print(arr)

输出:

1
[7 7 7 7 7]

2.7. np.eye()

np.eye()是NumPy里用来创建二维对角线数组的函数

完整函数签名:

1
2
3
4
5
6
7
8
9
10
numpy.eye(
N, # 表示输出数组的行数
M=None, # 表示输出数组的列数
k=0, # 表示对角线的位置,k=0表示主对角线,正数表示上方的对角线,负数表示下方的对角线
dtype=float, # 数组元素数据类型
order='C', # 控制数组在内存中的存储顺序,默认为表示C风格的行优先储存
*,
device=None, # 设备参数,日常一般用不上,主要是为了兼容Array API标准
like=None # 参考另一个数组对象创建兼容的数组
)

例子:

1
np.eye(3)

输出:

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

3.数组引索和切片

3.1. 一维数组引索

一维数组可以理解为一排数据,或者数学中的一个向量

与列表相似,NumPy的数组中的每个元素有自己的编号,也就是引索
数组:

1
arr = np.array([10, 20, 30, 40, 50])
1
2
3
元素:  10   20   30   40   50
正索引: 0 1 2 3 4
负索引:-5 -4 -3 -2 -1

例子:

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

arr = np.array([10, 20, 30, 40 ,50])

print(arr[0])
print(arr[2])
print(arr[4])

print(arr[-1])
print(arr[-2])
print(arr[-5])

输出:

1
2
3
4
5
6
10
30
50
50
40
10

3.2. 一维数组切片

基本格式:

1
2
3
4
arr[start:stop:step]
# 起始位置
# 结束位置,但不包含该位置
# 步长

切片遵循左开右闭

例子:

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

arr = np.array([10, 20, 30, 40, 50])

print(arr[1:4]) # 省略步长则默认为1
print(arr[:3]) # 省略起始位置,则默认从头开始
print(arr[2:]) # 省略结束位置,则默认到数组末尾
print(arr[:])
print(arr[::2])

输出:

1
2
3
4
5
[20 30 40]
[10 20 30]
[30 40 50]
[10 20 30 40 50]
[10 30 50]

NumPy切片是视图,这意味着切片并不是一份完全独立的新数据,因此修改切片的同时也会修改原数组。这也是NumPy数组切片与Python列表切片最重要的区别

例子:

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

arr = np.array([10, 20, 30, 40, 50])

part = arr[1:4]
part[0] = 200

print(part)
print(arr)

输出:

1
2
[200  30  40]
[ 10 200 30 40 50]

因此,当我们想拷贝一份独立的新数据时可以使用copy():

1
part = arr[1:4].copy()

例子:

1
2
3
4
5
6
7
arr = np.array([10, 20, 30, 40, 50])

part = arr[1:4].copy()
part[0] = 200

print(part)
print(arr)

输出:

1
2
[200  30  40]
[10 20 30 40 50]

3.3. 二维数组引索

二维数组可以理解为由行和列组成的表格

引索基本语法:

1
arr[行索引, 列索引]

例子:

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

arr = np.array([
[10, 20, 30],
[40, 50, 60],
[70, 80, 90]
])

print(arr[0, 0])
print(arr[1, 2])
print(arr[2, 1])

输出:

1
2
3
10
60
80

获取一整行/列:

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

arr = np.array([
[10, 20, 30],
[40, 50, 60],
[70, 80, 90]
])

print(arr[1])
print(arr[1, :])

print(arr[:, 1])

输出:

1
2
3
[40 50 60]
[40 50 60]
[20 50 80]

3.4. 二维数组切片

基本格式:

1
arr[行切片, 列切片]

例子:

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

arr = np.array([
[10, 20, 30, 40],
[50, 60, 70, 80],
[90, 100, 110, 120]
])

print(arr[0:2, 1:3])
print(arr[1:3, :])
print(arr[:, 1:3])
print(arr[:, ::2])

输出:

1
2
3
4
5
6
7
8
9
10
[[20 30]
[60 70]]
[[ 50 60 70 80]
[ 90 100 110 120]]
[[ 20 30]
[ 60 70]
[100 110]]
[[ 10 30]
[ 50 70]
[ 90 110]]

这里需要注意,如果二维数组引索其中一行,则得到的是一个一维数组;但如果二维数组通过切片取其中一行,则得到的仍是一个二维数组

例子:

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

arr = np.array([
[10, 20, 30],
[40, 50, 60]
])

a = arr[0]

print(a)
print(a.shape)

b = arr[0:1]

print(b)
print(b.shape)

输出:

1
2
3
4
[10 20 30]
(3,)
[[10 20 30]]
(1, 3)

3.5. 花式引索

花式索引也叫高级索引,它允许使用整数列表或整数数组,同时选择多个不连续的位置

例子:

1
2
3
4
5
6
7
import numpy as np

arr = np.array([10, 20, 30, 40, 50])

result = arr[[0, 2, 4]]

print(result)

输出:

1
[10 30 50]

其中[0, 2, 4]表示需要选择的引索位置

二维数组:

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

arr = np.array([
[10, 20, 30],
[40, 50, 60],
[70, 80, 90],
[100, 110, 120]
])

print(arr[[0, 2]])

输出:

1
2
[[10 20 30]
[70 80 90]]

这里arr[[0, 2]]表示选择第0行和第2行

同时指定多个行列坐标:

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

arr = np.array([
[10, 20, 30],
[40, 50, 60],
[70, 80, 90],
[100, 110, 120]
])

print(arr[[0, 1, 2], [1, 2, 0]])

输出:

1
[20 60 70]

这里是把行索引和列索引一一对应

1
2
3
4
5
行索引:0   1   2
列索引:1 2 0
↓ ↓ ↓
坐标: (0,1) (1,2) (2,0)
元素: 20 60 70

上面等价于:

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

选择指定行和指定列形成矩形区域用np.ix_():

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

arr = np.array([
[10, 20, 30],
[40, 50, 60],
[70, 80, 90],
[100, 110, 120]
])

result = arr[np.ix_([0, 2], [0, 2])]

print(result)

输出:

1
2
[[10 30]
[70 90]]

与普通的切片不同,花式索引通常返回一个新的数组副本:

1
2
3
4
5
6
7
arr = np.array([10, 20, 30, 40, 50])

result = arr[[0, 2, 4]]
result[0] = 1000

print(result)
print(arr)

输出:

1
2
[1000   30   50]
[10 20 30 40 50]

但是直接通过花式索引赋值仍然可以修改原数组:

1
2
3
4
5
arr = np.array([10, 20, 30, 40, 50])

arr[[0, 2, 4]] = 0

print(arr)

输出:

1
[ 0 20  0 40  0]

3.6. 布尔索引

布尔索引使用由True和False组成的布尔数组筛选数据

True → 保留对应元素

False → 不保留对应元素

例子:

1
2
3
4
5
6
7
import numpy as np

arr = np.array([10, 20, 30, 40, 50])

mask = np.array([True, False, True, False, True])

print(arr[mask])

输出:

1
[10 30 50]

布尔数组的形状通常需要与被筛选的数组相匹配

与花式索引相同,布尔索引返回的也是副本,并且直接通过布尔引索赋值之后仍可以修改原数组

3.6.1. 通过条件生成布尔数组

布尔索引最常见的用法不是手动编写True和False,而是通过条件生成

例子:

1
2
3
4
5
6
7
import numpy as np

arr = np.array([10, 20, 30, 40, 50])

mask = arr > 30

print(mask)

输出:

1
[False False False True True]

3.6.2. 多个条件组合

NumPy中不能直接使用Python中的and和or来进行逻辑运算

可以使用:

1
2
3
& 与  同时满足两个条件
| 或 满足其中一个条件
~ 取反 不满足某个条件

例子:

1
2
3
arr[(arr > 20) & (arr < 50)]
arr[(arr < 20) | (arr > 40)]
arr[~(arr > 30)]

输出:

1
2
3
[30 40]
[10 50]
[10 20 30]

3.6.2. 二维数组布尔索引

例子:

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

arr = np.array([
[10, 20, 30],
[40, 50, 60],
[70, 80, 90]
])

print(arr[arr > 50])
print(arr > 50)

输出:

1
2
3
4
[60 70 80 90]
[[False False False]
[False False True]
[ True True True]]

需要注意,结果变成了一维数组,因为NumPy会把所有满足条件的元素依次取出,然后组成一维数组

布尔条件也可以用于筛选整行:

1
2
3
4
5
6
7
8
9
arr = np.array([
[10, 20],
[30, 40],
[50, 60]
])

mask = np.array([True, False, True])

print(arr[mask])

输出:

1
2
[[10 20]
[50 60]]

4. 数组运算

NumPy的核心优势之一,就是能够直接对整个数组进行运算

4.1. 标量运算

当数组和标量进行运算时,NumPy会让这个标量分别和数组中的每个元素运算

例子:

1
2
3
4
5
import numpy as np

arr = np.array([1, 2, 3, 4])

print(arr + 10)

输出:

1
[11 12 13 14]

二维数组在与标量进行运算时也是逐元素进行

4.2. 数组运算

数组和数组运算则是作用于两个数组对应位置的元素上

例子:

1
2
3
4
5
6
import numpy as np

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

print(arr1 + arr2)

输出:

1
[5 7 9]

这里需要注意乘法:

1
2
3
4
5
6
import numpy as np

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

print(arr1 * arr2)

输出:

1
[4, 10, 18]

这里是逐元素乘法,而不是线性代数中的矩阵乘法

在NumPy中矩阵乘法的运算法是**@**

4.3. 比较运算

比较发生在每一个元素上,返回的不是一个布尔值,而是一个布尔数组

例子:

1
2
3
4
5
import numpy as np

arr = np.array([10, 20, 30, 40])

print(arr > 20)

输出:

1
[False False  True  True]

数组之间比较也是对应位置逐元素的比较

4.4. 逻辑运算

4.4.1. 与&

与表示两个条件必须同时成立

例子:

1
2
3
4
5
import numpy as np

arr = np.array([5, 10, 15, 20, 25, 30])

print((arr > 10) & (arr < 25))

输出:

1
[False False  True  True  True  True]

4.4.2. 或|

或表示两个条件只要有一个成立即可

例子:

1
2
3
4
5
import numpy as np

arr = np.array([5, 10, 15, 20, 25, 30])

print((arr < 10) | (arr > 25))

输出:

1
[ True False False False False  True]

4.4.3. 非~

非会把0和1反转为1和0

例子:

1
2
3
4
5
6
import numpy as np

arr = array([5, 10, 15, 20, 25, 30])

condition = arr > 15
print(~condition)

输出:

1
[ True  True  True False False False]

4.4.4. 异或^

异或表示两个条件中恰好一个成立

例子:

1
2
3
4
5
6
import numpy as np

a = np.array([True, True, False, False])
b = np.array([True, False, True, False])

print(a ^ b)

输出:

1
[False  True  True False]

4.4.5. all()

判断是否所有元素都是True

例子:

1
2
3
arr = np.array([10, 20, 30])

print(np.all(arr > 0))

输出:

1
True

4.4.6. any()

判断是否至少有一个元素为True

例子:

1
2
3
4
5
import numpy as np

arr = np.array([10, 20, 30])

print(np.all(arr > 25))

输出:

1
True

4.5. 向量化计算

不自己写Python循环逐个处理元素,而是直接对整个NumPy数组执行运算

普通Python循环:

1
2
3
4
5
6
7
8
nums = [1, 2, 3, 4]

result = []

for x in nums:
result.append(x * 2)

print(result)

输出:

1
[2, 4, 6, 8]

NumPy向量化写法:

1
2
3
4
5
6
7
import numpy as np

arr = np.array([1, 2, 3, 4])

result = arr * 2

print(result)

输出:

1
[2 4 6 8]

虽然向量化的本质依旧是逐元素运算,不过这些操作都是由NumPy底层完成的,通常比Python循环更快

前面讲过的几种运算基本都是通过向量化运算进行的

向量化计算的核心思维是“不要想如何遍历每一个元素,而是想能否把整个规则写进一个数组表达式”

5. 数学函数

NumPy提供了大量的数学函数方便我们进行运算,这些数学函数大多数都属于通用函数,即会对数组中的元素进行批量,逐元素计算,并返回新的数组

5.1. np.abs()

用于计算数组中每个元素的绝对值

例子:

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

a = np.array([
[-1, 2, -3],
[ 4, -5, 6]
])

print(np.abs(a))

输出:

1
2
[[1 2 3]
[4 5 6]]

对于NumPy数组来说,下面两种写法效果基本相同:

1
2
3
np.abs(a)

abs(a)

在NumPy代码中通常写成np.abs(),这样能够明确表示调用的是NumPy数学函数

5.2. np.sqrt()

用于计算每个元素的非负平方根

数学运算:
$$
y = \sqrt{x}
$$

例子:

1
2
3
4
5
6
7
import numpy as np

a = np.array([0, 1, 4, 9, 16, 25])

result = np.sqrt(a)

print(result)

输出:

1
[0. 1. 2. 3. 4. 5.]

结果是浮点数,这是因为平方根结果不一定是整数

5.3. np.exp()

用于计算以自然常数e为底数的指数

数学运算:
$$
y = e^x
$$

例子:

1
2
3
4
5
import numpy as np

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

print(np.exp(a))

输出:

1
[ 1.          2.71828183  7.3890561  20.08553692]

NumPy也提供了常量:

1
np.e

所以:

1
np.e ** a

等同于上面的写法

5.4. np.log()

用于计算以自然常数e为底数的对数

数学运算:
$$
y = \log_e x
$$

例子:

1
2
3
4
5
import numpy as np

a = np.array([1, np.e, np.e ** w])

print(np.log(a))

输出:

1
[0. 1. 2.]

如果想计算任意底数的对数方程可以使用数学上的换底公式:
$$
\log_b x = \frac{\ln x}{\ln b}
$$

以3为例:

1
2
3
4
5
6
7
import numpy as np

a = np.array([1, 3, 9, 27])

result = np.log(a) / np.log(3)

print(result)

输出:

1
[0. 1. 2. 3.]

5.5. 三角函数

1
2
3
np.sin()
np.cos()
np.tan()

NumPy也提供了圆周率:

1
np.pi

5.5.1. np.sin()

正弦函数

例子:

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

angles = np.array([
0,
np.pi / 6,
np.pi / 2,
np.pi
])

print(np.sin(angles))

输出:

1
[0.0000000e+00 5.0000000e-01 1.0000000e+00 1.2246468e-16]

5.5.2. np.cos()

余弦函数

例子:

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

angles = np.array([
0,
np.pi / 3,
np.pi / 2,
np.pi
])

print(np.cos(angles))

输出:

1
[ 1.000000e+00  5.000000e-01  6.123234e-17 -1.000000e+00]

5.5.3. np.tan()

正切函数

例子:

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

angles = np.array([
0,
np.pi / 4
])

print(np.tan(angles))

输出:

1
[0. 1.]

5.5.4. np.deg2rad()

用于把角度制转换成弧度制

例子:

1
2
3
4
5
6
7
import numpy as np

degrees = np.array([0, 30, 90, 180])

radians = np.deg2rad(degrees)

print(radians)

输出:

1
[0.         0.52359878 1.57079633 3.14159265]

5.5.5. np.rad2deg()

用于把弧度制转换成角度制

例子:

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

radians = np.array([
0,
np.pi / 6,
np.pi / 2,
np.pi
])

degrees = np.rad2deg(radians)

print(degrees)

输出:

1
[  0.  30.  90. 180.]

5.5.6. np.arcsin()

反正弦函数

输入三角函数值,返回弧度

例子:

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

values = np.array([0, 0.5, 1])

angles = np.arcsin(values)

print(angles)
print(np.rad2dge(angles))

输出:

1
2
[0.         0.52359878 1.57079633]
[ 0. 30. 90.]

5.5.7. np.arccos()

反余弦函数

输入三角函数值,返回弧度

例子:

1
2
3
4
5
6
7
import numpy as np

values = np.array([1, 0.5, 0])

angles = np.arccos(values)

print(np.rad2deg(angles))

输出:

1
[ 0. 60. 90.]

5.5.8. np.arctan()

反正切函数

输入三角函数值,返回弧度

例子:

1
2
3
4
5
6
7
import numpy as np

values = np.array([0, 1])

angles = np.arctan(values)

print(np.rad2deg(angles))

输出:

1
[ 0. 45.]

5.6. 取整函数

这里先提前说一下,以下取整函数的取整不等于转换为整数类型,数据类型依旧是浮点型

5.6.1. np.floor()

向下取整

floor(x)即取得小于或等于x的最大整数

例子:

1
2
3
4
5
import numpy as np

a = np.array([-2.7, -1.5, -0.2, 0.2, 1.5, 2.7])

print(np.floor(a))

输出:

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

5.6.2. np.ceil()

向上取整

ceil(x)即取得大于或等于x的最小整数

例子:

1
2
3
4
5
import numpy as np

a = np.array([-2.7, -1.5, -0.2, 0.2, 1.5, 2.7])

print(np.ceil(a))

输出:

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

5.6.3. np.trunc()

向零取整,即直接删除小数部分

例子:

1
2
3
4
5
import numpy as np

a = np.array([-2.7, -1.5, -0.2, 0.2, 1.5, 2.7])

print(np.trunc(a))

输出:

1
[-2. -1. -0.  0.  1.  2.]

5.6.4. np.round()

四舍五入到指定小数位数

简单函数签名:

1
2
3
4
np.round(
a, # 输入的数据
decimals=0 # 保留的小数位数,默认为0
)

例子:

1
2
3
4
5
import numpy as np

a = np.array([3.14159, 2.71828, 1.23456])

print(np.round(a, 2))

输出:

1
[3.14 2.72 1.23]

但是对于正好位于两个整数中间的数,例如:

1
2
3
4
0.5
1.5
2.5
3.5

NumPy 不是简单地全部“向上取整”,而是取距离最近的偶数,也叫做银行家舍入或round half to even

1
2
3
4
5
0.5 → 0   选择偶数0
1.5 → 2 选择偶数2
2.5 → 2 选择偶数2
3.5 → 4 选择偶数4
4.5 → 4 选择偶数4

还有一点:decimals可以是负数,当decimals是负数的时候则可以对十位、百位等进行取整数

5.6.5. np.rint()

将元素取到最接近的整数值,并同样使用中间值取最近偶数的规则

例子:

1
2
3
4
5
import numpy as np

a = np.array([1.2, 1.5, 2.5, 2.8])

print(np.rint(a))

输出:

1
[1. 2. 2. 3.]

6. 统计函数和axis

这一节的函数大多属于聚合函数归约函数

聚合函数:把一组数据汇总成统计结果

归约函数:沿某个轴合并元素,使数组维度或元素数量减少

6.1. axis

axis表示沿着哪个维度进行运算

对于二维数组:

1
a.shape == (行数, 列数)

因此:

1
2
3
axis = 0:沿着第0个维度,也就是行的方向计算,最终每一列得到一个结果
axis = 1:沿着第1个维度,也就是列的方向计算,最终每一行得到一个结果
axis = None:不指定维度,则对于数组所有的元素都进行运算

6.2. np.sum()

对于指定维度进行求和

完整函数签名:

1
2
3
4
5
6
7
8
9
np.sum(
a, # 需要进行运算的元素
axis=None, # 指定沿哪个维度运算
dtype=None, # 指定计算过程和结果使用的数据类型
out=None, # 结果输入提前写好的数组
keepdims=<no value>, # 是否保留被消除的维度
initial=<no value>, # 设置初始值
where=<no value> # 通过布尔条件来选择参与计算的元素
)

例子:

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
import numpy as np

# a
np.sum([1, 2, 3]) # 6

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

# axis
np.sum(a, axis=0) # array([5, 7, 9])
np.sum(a, axis=1) # array([6, 15])

# dtype
np.sum(a, dtype=np.float64) # 21.0

# out
result = np.empty(3, dtype=int)
np.sum(a, axis=0, out=result) # [5 7 9]

# keepdims
np.sum(a, axis=0) # array([5, 7, 9]); shape: (3,)
np.sum(a, axis=0, keepdims=True) # array([[5, 7, 9]]); shape: (1, 3)
np.sum(a, axis=1) # array([6, 15]); shape: (2,)
np.sum(a, axis=1, keepdims=True) # array([[6],[15]]); shape: (2, 1)

#initial
np.sum([1, 2, 3], initial=10) # 16

# where
data = np.array([1, 2, 3, 4])
np.sum(data, where=data > 2) # 7

6.3. np.mean()

对于指定维度计算算术平均值

数学公式:
$$
\bar{x} = \frac{x_1+x_2+...+x_n}{n}
$$

完整函数签名:

1
2
3
4
5
6
7
8
9
np.mean(
a, # 需要进行运算的元素
axis=None, # 指定沿哪个维度运算
dtype=None, # 指定计算过程和结果使用的数据类型
out=None, # 结果输入提前写好的数组
keepdims=<no value>, # 是否保留被消除的维度
*,
where=<no value> # 通过布尔条件来选择参与计算的元素
)

例子:

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

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

# a
np.mean(a) # 3.5

# axis
np.mean(a, axis=0) # array([2.5, 3.5, 4.5])
np.mean(a, axis=1) # array([2., 5.])

# dtype
data = np.array([1, 2, 3])
np.mean(data, dtype=np.float64) # 2.0

6.4. np.max()

用于得到指定维度的最大值

完整函数签名:

1
2
3
4
5
6
7
8
np.max(
a, # 需要进行运算的元素
axis=None, # 指定沿哪个维度运算
out=None, # 结果输入提前写好的数组
keepdims=<no value>, # 是否保留被消除的维度
initial=<no value>, # 设置初始值
where=<no value> # 通过布尔条件来选择参与计算的元素
)

例子:

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

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

# a
np.max(a) # 6

# axis
np.max(a, axis=0) # array([4, 5, 6])
np.max(a, axis=1) # array([3, 6])

# initial
np.max([1, 2, 3], initial=10) # 10

6.5. np.min()

用于得到指定维度的最小值

完整函数签名:

1
2
3
4
5
6
7
8
np.min(
a, # 需要进行运算的元素
axis=None, # 指定沿哪个维度运算
out=None, # 结果输入提前写好的数组
keepdims=<no value>, # 是否保留被消除的维度
initial=<no value>, # 设置初始值
where=<no value> # 通过布尔条件来选择参与计算的元素
)

例子:

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

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

# a
np.min(a) # 1

# axis
np.min(a, axis=0) # array([1, 2, 3])
np.min(a, axis=1) # array([1, 4])

# initial
np.min([1, 2, 3], initial=0) # 0

6.6. np.var()

用于得到指定维度的方差

方差用于描述一组数据与平均值之间的离散程度

数学公式:
$$
\sigma^2 = \frac{1}{N}\sum_{i=1}^{N} (x_i - \bar{x})^2
$$

完整函数签名:

1
2
3
4
5
6
7
8
9
10
11
12
np.var(
a, # 需要进行运算的元素
axis=None, # 指定沿哪个维度运算
dtype=None, # 指定计算过程和结果使用的数据类型
out=None, # 结果输入提前写好的数组
ddof=0, # 自由度修正量
keepdims=<no value>, # 是否保留被消除的维度
*,
where=<no value>, # 通过布尔条件来选择参与计算的元素
mean=<no value>, # 可传入已计算好的平均值
correction=<no value> # correction是与数组API兼容的ddof别名,所以两者不能同时传入
)

这里详细说一下自由度修正量(Delta Degrees of Freedom)

NumPy的除数是 N - ddof

默认情况下ddof=0 也就是只除以N

如果修改为ddof=1 也就是除以N-1

例子:

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

data = np.array([1, 2, 3])

np.var(data, ddof=0)
# 0.6666666666666666

np.var(data, ddof=1)
# 1.0

6.7. np.std()

用于得到指定维度的标准差

标准差其实就是方差的平方根:
$$
\sigma = \sqrt{\sigma^2}
$$

完整函数签名:

1
2
3
4
5
6
7
8
9
10
11
12
np.std(
a, # 需要进行运算的元素
axis=None, # 指定沿哪个维度运算
dtype=None, # 指定计算过程和结果使用的数据类型
out=None, # 结果输入提前写好的数组
ddof=0, # 自由度修正量
keepdims=<no value>, # 是否保留被消除的维度
*,
where=<no value>, # 通过布尔条件来选择参与计算的元素
mean=<no value>, # 可传入已计算好的平均值
correction=<no value> # # correction是与数组API兼容的ddof别名,所以两者不能同时传入
)

例子:

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

data = np.array([1, 2, 3])

np.var(data)
# 0.6666666666666666

np.std(data)
# 0.816496580927726

6.8. np.argmax()

用于搜索最大值的位置

函数签名:

1
2
3
4
5
6
7
np.argmax(
a, # 需要进行运算的元素
axis=None, # 指定沿哪个维度运算
out=None, # 结果输入提前写好的数组
*,
keepdims=<no value> # 是否保留被消除的维度
)

前面的np.max()是返回最大值本身,np.argmax()则是返回最大值所在的索引

例子:

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

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

# a
np.argmax(a) # 5
# 这里返回的是5,因为没有指定axis时,会把数组展平:array([1, 2, 3, 4, 5, 6])

# axis
np.argmax(a, axis=0) # array([1, 1, 1])
np.argmax(a, axis=1) # array([2, 2])

6.9. np.argmin()

用于搜索最小值的位置

函数签名:

1
2
3
4
5
6
7
np.argmin(
a, # 需要进行运算的元素
axis=None, # 指定沿哪个维度运算
out=None, # 结果输入提前写好的数组
*,
keepdims=<no value> # 是否保留被消除的维度
)

例子:

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

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

# a
np.argmin(a) # 0
# 这里返回的是0,因为没有指定axis时,会把数组展平:array([1, 2, 3, 4, 5, 6])

# axis
np.argmin(a, axis=0) # array([0, 0, 0])
np.argmin(a, axis=1) # array([0, 0])

7. 数组形状变换

7.1. reshape()

用来在不改变元素总数的情况下,重新组织数组的形状

函数签名:

1
2
3
4
5
6
np.reshape(
a, # 要改变形状的原数组
shape, # 数组改变后的新形状
order='C', # 引索遍历顺序,默认是按照C语言风格
copy=None # 是否拷贝底层数组数据
)

copy这个参数默认是None,即能返回视图时返回视图,不能返回视图时返回拷贝

数组方法形式:

1
array.reshape(shape, *, order='C', copy=None)

例子:

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

a = np.arange(12)

print(a)
print(a.shape)

b = a.reshape(3, 4)

print(b)
print(b.shape)

c = a.reshape(2, 2, 3)

print(c)
print(c.shape)

输出:

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

(3, 4)

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

[[ 6 7 8]
[ 9 10 11]]]

(2, 2, 3)

这里转换时元素总数必须相等比如:

1
2
3
4
5
a.reshape(3, 4)
a.reshape(4, 3)
a.reshape(2, 6)
a.reshape(2, 2, 3)
a.reshape(1, 12)

这些转换都是12个元素,所以都是合法的

可以使用-1自动推导维度

reshape()允许其中一个维度写成-1,由NumPy根据元素总数自动计算,一个形状最多只有一个-1

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

a = np.arange(12)

b = a.reshape(3, -1)

print(b)
print(b.shape)

输出:

1
2
3
4
5
[[ 0  1  2  3]
[ 4 5 6 7]
[ 8 9 10 11]]

(3, 4)

这里进行了一个简单的数学运算自动推导出缺失维度的元素数量

7.2. flatten()

将多维数组展平成一维数组

函数签名:

1
ndarray.flatten(order='C') # 引索遍历顺序,默认是按照C语言风格

注意它是ndarray的方法,而不是np.flatten()

flatten()返回是副本,不是视图

例子:

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

b = a.flatten()

print(b)
print(b.shape)

输出:

1
2
[1 2 3 4 5 6]
(6,)

7.3. ravel()

ravel()也会把数组展平成一维

函数签名:

1
2
3
4
np.ravel(
a, # 需要被展平的元素
order='C' # 引索遍历顺序,默认是按照C语言风格
)

也有方法形式:

1
a.ravel(order='C')

它与flatten()最大的区别是ravel()尽可能返回视图,只有必要时才能创建副本

例子:

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

b = np.ravel(a)

print(b)
print(b.shape)

输出:

1
2
[1 2 3 4 5 6]
(6,)

7.4. transpose()

用来交换数组的轴

函数签名:

1
2
3
4
np.transpose(
a, # 需要交换轴的数组
axes=None # 指定新数组的轴来自于原数组的哪个轴
)

方法形式:

1
a.transpose(*axes)

例子,二维数组转置:

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

print(a.shape)

b = np.transpose(a)

print(b)
print(b.shape)

输出:

1
2
3
4
5
6
(2, 3)
[[1 4]
[2 5]
[3 6]]

(3, 2)

包括axes参数:

1
2
3
4
5
6
7
import numpy as np

a = np.zeros((2, 3, 4))
print(a.shape)

b = np.transpose(a, (1, 0, 2))
print(b.shape)

输出:

1
2
(2, 3, 4)
(3, 2, 4)

执行流程:

1
2
3
4
5
6
7
8
9
新 axis=0 ← 原 axis=1
新 axis=1 ← 原 axis=0
新 axis=2 ← 原 axis=2

原形状:(2, 3, 4)
原轴号: 0 1 2

新顺序:(1, 0, 2)
新形状:(3, 2, 4)

不传axes则默认把所有的轴的顺序反转:

1
2
3
4
5
6
7
import numpy as np

a = np.zeros((2, 3, 4))

b = np.transpose(a)

print(b.shape)

输出:

1
(4, 3, 2)

7.5. .T

.T是数组的转置属性:

1
array.T

相当于:

1
array.transpose()

.T会返回转置数组的视图

.T大多用于快速获取二维矩阵的转置

例子:

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

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

print(a.T)

输出:

1
2
3
[[1 4]
[2 5]
[3 6]]

7.6. concatenate()

用来沿着一个已经存在的轴连接多个数组

函数签名:

1
2
3
4
5
6
7
8
np.concatenate(
arrays, # 表示需要连接的一组数组
axis=0, # 指定沿哪一个轴连接数组
out=None, # 用于指定一个已创建好的数组,用来保存连接结果
*,
dtype=None, # 指定结果数组的数据类型
casting='same_kind' # 用于控制在指定dtype或out时,允许采用什么级别的数据类型转换
)

例子:

1
2
3
4
5
6
7
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

c = np.concatenate((a, b))

print(c)
print(c.shape)

输出:

1
2
[1 2 3 4 5 6]
(6,)

二维数组沿axis=0连接

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

a = np.array([
[1, 2],
[3, 4]
])

b = np.array([
[5, 6]
])

c = np.concatenate((a, b), axis=0)

print(c)
print(c.shape)

输出:

1
2
3
4
5
[[1 2]
[3 4]
[5 6]]

(3, 2)

沿哪个轴连接,哪个轴的长度可以不同;其他轴必须相同

当axis=None时,所有数组会先被展平,再连接:

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

b = np.array([
[5, 6]
])

c = np.concatenate((a, b), axis=None)

print(c)
print(c.shape)

输出:

1
2
[1 2 3 4 5 6]
(6,)

7.7. stack()

用来沿着一个新创建的轴堆叠多个数组

函数签名:

1
2
3
4
5
6
7
8
np.stack(
arrays, # 表示需要堆叠的一组数组
axis=0, # 新轴插入到结果数组的哪个位置
out=None, # 用于指定一个已经存在的数组,用来保存最终结果
*,
dtype=None, # 用于指定结果数组的数据类型
casting='same_kind' # 用于控制在指定dtype或out时,允许采用什么级别的数据类型转换
)

例子:

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

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

c = np.stack((a, b), axis=0)

print(c)
print(c.shape)

输出:

1
2
3
4
[[1 2 3]
[4 5 6]]

(2, 3)

8. 广播机制

广播机制决定了不同形状的数组能不能直接进行运算,以及运算后结果的形状是什么

8.1. 标量广播

标量就是单独的一个数

1
2
3
10
3.14
-5

NumPy标量和Python标量都可以参与广播

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

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

result = a + 10
print(result)

输出:

1
2
[[11 12 13]
[14 15 16]]

标量可以与任意形状的数组广播

8.2. 一维数组和二维数组广播

首先要区分以下几种数组:

1
2
3
4
5
6
7
8
9
a = np.array([1, 2, 3])
b = np.array([[1, 2, 3]])
c = np.array([[1],
[2],
[3]])

print(a.shape) # (3,)
print(b.shape) # (1, 3)
print(c.shape) # (3, 1)

一维数组和二维数组可以按列匹配:

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

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

b = np.array([10, 20, 30])

print(a + b)

输出:

1
2
[[11 22 33]
[14 25 36]]

判断广播时,从形状的最后一个维度开始向前比较:

1
2
a.shape = (2, 3)
b.shape = (3,)

最后一维是3和3,因此可以广播

每一行加不同的值:

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

a = np.array([
[1, 2, 3],
[4, 5, 6]
])

b = np.array([
[10],
[20]
])

print(a + b)

输出:

1
2
[[11 12 13]
[24 25 26]]

8.3. 广播规则

1.从最后一个维度开始比较

2.对应维度满足两个维度相等或其中一个维度为1

例子:

1
2
3
4
3 和 3:兼容
1 和 3:兼容
5 和 1:兼容
2 和 3:不兼容

3.缺少的前置维度按照1处理

例子:

1
2
3
4
5
A.shape = (2, 3, 4)
B.shape = (4,)
相当于
A.shape = (2, 3, 4)
B.shape = (1, 1, 4)

4.结果每个维度取较大的值

例子:

1
2
3
4
5
A.shape = (3, 1)
B.shape = (1, 4)

第一维:3 和 1 -> 3
第二维:1 和 4 -> 4

8.4. 广播相关函数

8.4.1. np.broadcast_shapes()

可以计算多个形状广播后的结果

1
2
3
4
5
import numpy as np

result_shape = np.broadcast_shapes((2, 3), (3,))

print(result_shape)

输出:

1
(2, 3)

如果两数组不可广播则会报错

8.4.2. np.broadcast_to()

可以显式查看数组广播后的样子

1
2
3
4
5
6
7
import numpy as np

a = np.array([1, 2, 3])

result = np.broadcast_to(a, (2, 3))

print(result)

输出:

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

9. 随机数

NumPy的随机数功能位于numpy.random模块中。它不仅能生成随机小数,随机整数,还能生成符合正态分布等概率分布的数据,以及完成数组打乱和随机抽样

我们首先需要通过**np.random.default_rng()**函数创建一个随机数生成器对象:

1
2
3
4
5
import numpy as np

rng = np.random.default_rng()

print(rng)

输出:

1
Generator(PCG64)

也可以直接指定随机种子:

1
2
3
import numpy as np

rng = np.random.default_rng(42)

9.1. random()

random()用来生成位于以下区间的随机浮点数:[0.0,1.0)

它服从连续均匀分布,即区间中的不同位置具有相同的被抽取机会

函数签名:

1
2
3
4
5
rng.random(
size=None, # 输出数组的形状
dtype=np.float64, # 指定生成结果的参数类型
out=None # 用于指定一个已创建好的数组,用来保存结果
)

例子:

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

rng = np.random.default_rng(42)

result = rng.random(5)
print(result)

result = rng.random((2, 3))
print(result)

输出:

1
2
3
[0.77395605 0.43887844 0.85859792 0.69736803 0.09417735]
[[0.97562235 0.7611397 0.78606431]
[0.12811363 0.45038594 0.37079802]]

虽然random()本身只能随机[0.0,1.0)之间的小数,但是可以通过数学的方法来生成指定区间的小数

比如我们想生成:
$$
[a,b)
$$

可以使用公式:
$$
a+(b-a)x
$$
x为random()生成的随机数

9.2. integers()

用来生成随机整数

函数签名:

1
2
3
4
5
6
7
rng.integers(
low, # 生成范围的下界
high=None, # 生成范围的上界
size=None, # 输出数组的形状
dtype=np.int64, # 指定生成结果的参数类型
endpoint=False # 生成范围是否包括上界,默认不包括,也就是左闭右开
)

例子:

1
2
3
4
5
6
7
import numpy as np

rng = np.random.default_rng(42)

result = rng.integers(0, 10)

print(result)

输出:

1
0

9.3. normal()

用于生成符合正态分布,也称高斯分布的随机数

正态分布的概率密度函数是:
$$
f(x)=\frac{1}{\sigma\sqrt{2\pi}}
e^{-\frac{(x-\mu)^2}{2\sigma^2}}
$$

$\mu$是均值

$\sigma$是标准差

$\sigma^2$是方差

函数签名:

1
2
3
4
5
rng.normal(
loc=0.0 # 均值
scale=1.0 # 标准差
size=None # 输出数组的形状
)

例子:

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

rng = np.random.default_rng(42)

result = rng.normal(
loc=170,
scale=6,
size=(2, 3)
)

print(result)

输出:

1
2
[[171.82830248 163.76009536 174.50270717]
[175.6433883 158.29378887 162.18692296]]

9.4. 随机种子

计算机生成的随机数是伪随机数

这个随机数是根据算法从一个初始状态不断计算出来,这个初始信息通常由随机种子提供

9.4.1. 不设置种子

1
2
3
4
5
import numpy as np

rng = np.random.default_rng()

print(rng.random(3))

再次运行整个程序时,结果通常不同,因为 seed=None 时,生成器会从操作系统获取新的、不可预测的熵

9.4.2. 设置种子

1
2
3
4
5
import numpy as np

rng = np.random.default_rng(42)

print(rng.random(3))

输出:

1
[0.77395605 0.43887844 0.85859792]

用相同种子重新创建随机数生成器则仍然会得到相同的起始序列

固定种子的主要作用是让实验结果可以复现

当同一个生成器连续调用时:

1
2
3
4
5
6
import numpy as np

rng = np.random.default_rng(42)

print(rng.random(3))
print(rng.random(3))

输出:

1
2
[0.77395605 0.43887844 0.85859792]
[0.69736803 0.09417735 0.97562235]

两次结果并不相同,原因是随机数生成器具有内部状态。第一次生成随机数之后,内部状态向前推进;第二次会从新的状态继续生成

9.5. 打乱数组

9.5.1. shuffle()

函数签名:

1
2
3
4
rng.shuffle(
x, # 需要打乱的数组
axis=0 # 需要打乱的维度
)

打乱一维数组:

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

rng = np.random.default_rng(42)

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

result = rng.shuffle(arr)

print(arr)
print(result)

输出:

1
2
[5 3 4 2 1]
None

这里会直接原地打乱数组,因此不需要用变量来接收返回值,即使接收了也会是None

打乱二维数组:

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

rng = np.random.default_rng(42)

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

rng.shuffle(arr)

print(arr)

输出:

1
2
3
[[5 6]
[3 4]
[1 2]]

默认axis=0,因此打乱的是行

axis=1:

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

rng = np.random.default_rng(42)

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

rng.shuffle(arr, axis=1)

print(arr)

输出:

1
2
[[3 2 1]
[6 5 4]]

9.5.2. permutation()

同样是打乱数组,与shuffle()不同的是,shuffle()是原地修改,而permutation()则是返回打乱后的副本

函数签名:

1
2
3
4
rng.permutation(
x, # 需要打乱的数组
axis=0 # 需要打乱的维度
)

例子:

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

rng = np.random.default_rng(42)

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

new_arr = rng.permutation(arr)

print(arr)
print(new_arr)

输出:

1
2
[1 2 3 4 5]
[5 3 4 2 1]

可以看到原来的arr没有变化

9.6. 随机抽样

随机抽样主要使用:

1
rng.choice()

函数签名:

1
2
3
4
5
6
7
8
rng.choice(
a, # 需要抽样的数组
size=None, # 抽取的数量和结果的形
replace=True, # 是否放回
p=None, # 表示每个元素被抽中的概率
axis=0, # 沿哪个轴进行抽样
shuffle=True # 表示无放回抽样时,抽取出来的结果是否再进行随机打乱
)

抽取一个元素:

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

rng = np.random.default_rng(42)

arr = np.array([10, 20, 30, 40, 50])

result = rng.choice(arr)

print(result)

输出:

1
10

抽取多个元素:

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

rng = np.random.default_rng(42)

arr = np.array([10, 20, 30, 40, 50])

result = rng.choice(
arr,
size=3
)

print(result)

输出:

1
[10 40 40]

因为默认是放回的,所以同一个元素可以被重复抽取

指定抽取概率p:

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

rng = np.random.default_rng(42)

arr = np.array(["A", "B", "C"])
rng.choice(arr, size=10)

result = rng.choice(
arr,
size=10,
p=[0.7, 0.2, 0.1]
)

print(result)

这里表示:

1
2
3
A 的概率为 0.7
B 的概率为 0.2
C 的概率为 0.1

p中的各个概率需要满足相加和为1

二维数组抽样:

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

rng = np.random.default_rng(42)

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

result = rng.choice(
arr,
size=2,
replace=False
)

print(result)

输出:

1
2
[[1 2]
[5 6]]

10. 缺失值和特殊值

10.1. np.nan

nan是Not a Number的缩写,表示“不是一个有效数字”

1
2
3
import numpy as np

print(np.nan)

输出:

1
nan

它本质上是一种特殊的浮点数表示。NumPy官方将其定义为IEEE754标准中的Not a Number

np.nan可以主动表示缺失值:

1
2
3
4
5
import numpy as np

scores = np.array([90, 85, np.nan, 88])

print(scores)

输出:

1
[90. 85. nan 88.]

np.nan 从数学意义上表示“不是一个数字”,只是在数据分析中经常被用来表示缺失值

非法数学运算也可能会产生nan:

1
2
3
4
5
import numpy as np

result = np.log(-1)

print(result)

输出:

1
nan

当一个计算中包含nan时,结果也会变成nan

nan不等于自身:

1
2
3
import numpy as np

print(np.nan == np.nan)

输出:

1
False

nan表示一个不确定或无效的数值,两个不确定值不能认为一定相等

如果想判断缺失值可以使用np.isnan函数:

1
2
3
4
5
import numpy as np

a = np.nan

print(np.isnan(a))

输出:

1
True

np.nan是浮点特殊值,因此普通整数数组不能直接保存它

1
2
3
4
5
6
import numpy as np

arr = np.array([1, 2, np.nan, 4])

print(arr)
print(arr.dtype)

输出:

1
2
[ 1.  2. nan  4.]
float64

虽然其他元素写的是整数,但为了保存np.nan,NumPy会把整个数组转换成浮点数组

如果先创建整数数组,再赋值np.nan则会报错

10.2. np.inf

inf是infinity的缩写,表示正无穷大

1
2
3
4
import numpy as np

print(np.inf)
print(-np.inf)

输出:

1
2
inf
-inf

NumPy浮点数组除以零时,通常会产生无穷值,并伴随警告

1
2
3
4
5
import numpy as np

arr = np.array([1.0, -1.0])

print(arr / 0)

输出:

1
[ inf -inf]

10.3. np.isnan()

用于判断输入的元素是否为nan

函数签名:

1
2
3
4
5
6
7
8
9
10
11
np.isnan(
x, # 输入的数据
/,
out=None, # 指定将计算结果保存到哪个已有数组中
*,
where=True, # 控制哪些位置执行判断
casting='same_kind', # 控制输入数据和输出数据进行类型转换时,允许使用多宽松的转换规则
order='K', # 控制新创建的输出数组使用哪种内存排列方式
dtype=None, # 用于指定ufunc计算时采用的数据类型
subok=True # 控制输出是否保留ndarray子类
)

它是一个通用函数,也就是ufunc,它会逐元素判断输入是否为nan

例子:

1
2
3
4
5
6
7
import numpy as np

arr = np.array([1, np.nan, 3, np.nan])

result = np.isnan(arr)

print(result)

输出:

1
[False  True False  True]

10.4. np.isinf()

用于判断输入的元素是否为正无穷或负无穷

函数签名:

1
2
3
4
5
6
7
8
9
10
11
np.isinf(
x, # 输入的数据
/,
out=None, # 指定将计算结果保存到哪个已有数组中
*,
where=True, # 控制哪些位置执行判断
casting='same_kind', # 控制输入数据和输出数据进行类型转换时,允许使用多宽松的转换规则
order='K', # 控制新创建的输出数组使用哪种内存排列方式
dtype=None, # 用于指定ufunc计算时采用的数据类型
subok=True # 控制输出是否保留ndarray子类
)

例子:

1
2
3
4
5
6
7
import numpy as np

arr = np.array([1, np.inf, 3, -np.inf, 5])

mask = np.isinf(arr)

print(arr[mask])

输出:

1
[ inf -inf]

如果想单独判断正无穷和负无穷可以使用:

1
2
np.isposinf()
np.isneginf()

10.5. np.nanmean()

np.nanmean()用于计算算术平均值,但会忽略数组中的nan

$$
平均值=\frac{所有非nan元素之和}{非nan元素的数量}
$$

np.mean()的计算中包含nan,所以整个结果会变成nan。而np.nanmean()则会忽略nan

函数签名:

1
2
3
4
5
6
7
8
9
np.nanmean(
a, # 输入的数据
axis=None, # 执行运算的维度
dtype=None, # 用于指定计算时采用的数据类型
out=None, # 指定将计算结果保存到哪个已有数组中
keepdims=<no value>, # 决定被归约的轴是否保留
*,
where=<no value> # 指定哪些元素参与平均值计算
)

例子:

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

arr = np.array([
[1, np.nan, 3],
[4, 5, np.nan]
])

print(np.nanmean(arr))

输出:

1
3.25

11. 文件读写

NumPy中常见的文件读写可以分为两组

保存格式 保存函数 读取函数 特点
NumPy 二进制格式 np.save() np.load() 保留数组的 shapedtype 等信息
普通文本格式 np.savetxt() np.loadtxt() 文件可直接阅读,也方便其他软件使用

11.1. np.save()

函数签名:

1
2
3
4
5
np.save(
file, # 数组保存的位置
arr, # 要保存的数组
allow_pickle=True # 是否允许使用Python的pickle机制保存对象数组
)

输出:

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

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

np.save("data", arr)

执行后,当前目录会生成:

1
data.npy

当file是字符串或Path对象,并且文件名没有以.npy结尾时,NumPy会自动添加.npy

.npy文件不仅保存数组元素,还保存了数组的shape、dtype、内存排列信息和实际数据

因此可以:

1
2
3
4
5
loaded = np.load("data.npy")

print(loaded)
print(loaded.shape)
print(loaded.dtype)

输出:

1
2
3
4
5
6
[[1 2 3]
[4 5 6]]

(2, 3)

int32

11.2. np.load()

可以读取.npy文件、.npz文件、部分pickle文件

函数签名:

1
2
3
4
5
6
7
8
9
np.load(
file, # 表示要读取的文件
mmap_mode=None, # 表示是否需要使用内存映射读取文件
allow_pickle=False, # 表示是否允许读取使用Python pickle保存的对象
fix_imports=True, # 用于兼容Python2生成的pickle文件
encoding="ASCII", # 用于读取Python2生成的字符串pickle数据
*,
max_header_size=10000 # 允许读取的.npy文件头最大大小,单位是字节
)

默认情况下,NumPy 会把数组数据读取到内存中,但是如果文件非常大,例如有几十GB,而你只想读取其中一小部分,把整个文件加载到内存中会非常浪费

内存映射的核心特点是:数组数据主要保存在磁盘上,程序访问某一部分数据时,再按需读取对应部分,而不是一次性把整个数组加载进内存

可用模式:

1
2
3
4
5
mmap_mode = None # 不使用内存映射
mmap_mode = "r" # 只读模式
mmap_mode = "r+" # 读写模式
mmap_mode = "w+" # 写时复制模式
mmap_mode = "c" # 读写并创建或覆盖映射

一个.npy文件不只有数组的数据,还会包含头部信息,例如:数组的dtype、数组的shape、数组是否按Fortran顺序存储、文件格式版本。通常文件头非常小,默认的 10000 已经足够

读取.npy文件时,返回一个数组;读取.npz文件时,返回一个类似字典的NpzFile对象

11.2.1. 加载.npy

.npy文件通常只保存一个数组

例子:

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

arr = np.array([
[10, 20, 30],
[40, 50, 60]
])

np.save("scores.npy", arr)

loaded_arr = np.load("scores.npy")

print(loaded_arr)
print(loaded_arr.shape)
print(loaded_arr.dtype)

输出:

1
2
3
4
[[10 20 30]
[40 50 60]]
(2, 3)
int64

11.2.2. 加载.npz

.npz可以保存多个数组

例子:

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

names = np.array(["Tom", "Jack", "Alice"])
scores = np.array([90, 85, 96])

np.save(
"students.npz",
names=names,
scores=scores
)

with np.load("students.npz") as data:
print(data.files)
print(data["names"])
print(data["scores"])

输出:

1
2
3
['names', 'scores']
['Tom' 'Jack' 'Alice']
[90 85 96]

11.3. np.savetxt()

用于将NumPy数组保存为文本文件(.txt、.csv等)

函数签名:

1
2
3
4
5
6
7
8
9
10
11
np.savetxt(
fname, # 保存文件的路径或者文件对象
X, # 需要保存的数组
fmt="%.18e", # 格式化字符串,默认使用科学计数法,保留18位小数
delimiter=" ", # 控制列之间用什么隔开,默认空格
newline="\n", # 行结束符,表示每一行结束都加上\n
header="", # 文件头信息,默认空
footer="", # 文件尾信息,默认空
comments="# ", # 注释符号
encoding=None # 文件编码,None就是使用系统默认
)

例子:

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

data=np.array([
[1.23456, 2.34567],
[3.45678, 4.56789]
])

np.savetxt(
"result.csv",
data,
fmt="%.2f",
delimiter=",",
header="x,y",
comments=""
)

输出:

1
2
3
x,y
1.23,2.35
3.46,4.57

11.4. np.loadtxt()

用来从格式比较规则的文本文件中读取数据。默认返回浮点数组

函数签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
np.loadtxt(
fname, # 要读取的文件名或文件对象
dtype=float, # 指定读取后的数组数据类型
comments="#", # 注释符号
delimiter=None, # 指定列之间的分隔符
converters=None, # 表示任意空白字符分隔
skiprows=0, # 跳过开头多少行
usecols=None, # 选择读取哪些行
unpack=False, # 是否转置返回结果
ndmin=0, # 指定返回数组最低维度
encoding=None, # 指定文件编码
max_rows=None, # 最多读取多少行
*,
quotechar=None, # 处理带引号的数据
like=None # 指定返回类似哪个数组对象
)