抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Numpy(Numerical Python)是python的一个语言拓展程序库,它提供了一个强大的多维数组对象(ndarray),以及用于操作数组的函数和工具。NumPy是许多其他科学计算库和数据分析库的基础,如SciPy(Scientfic Python)、Pandas和Matplotlib(绘图库)。

SciPy:开源的python算法库和数学工具包,包含的模块有最优化、线性代数、积分、插值、快速傅里叶变换、信号处理和图像处理等

Pandas:另一个数据处理和分析工具,核心数据结构是两种类型的对象:Series 和 DataFrame

Matplotlib:Numpy的可视化操作界面,利用通用的图形用户界面工具包(如Tkinter)向应用程序嵌入式绘图提供API

Numpy官方手册:

NumPy user guide — NumPy v1.26 Manual

1. 数据类型

Numpy支持的数据类型可以和C语言的数据类型对应上,和python内置的六大数据类型相比,Numpy提供的数据类型相应的要细分很多,以下是常用的数据类型:

名称 概述
bool_ 布尔型数据类型(True或False)
int_ 默认的整数类型(C语言中的long,int32或int64)
intc c的int类型,int32或int64
intp 索引的整数类型
int8 整数 -128 to 127
int16 整数 -32768 to 32767
int32 整数 -2147483648 to 2147483647
int64 整数 -9223372036854775808 to 9223372026854775807
uint8 无符号整数 0 to 255
uint16 无符号整数 0 to 65535
uint32 无符号整数 0 to 4294967295
uint64 无符号整数 0 to 18446744073709551615
float_ float64类型
complex_ complex128类型,128位复数
bytes_ 字节序列数据类型,可以包含任意字节值,通常用于处理原始的二进制数据
str_ 字符串数据类型,存储 Unicode 字符串数据

在numpy中,数据类型是通过 dtype 对象来表示的。dtype 对象描述了数据在内存中的存储方式,包括数据的类型(整数、浮点数等)和字节大小等信息。每个数据类型都对应一个唯一的字符码(Character Code),用于标识该数据类型。

dtype 对象本身是一个类,它有多个属性和方法来描述和操作数据类型。例如,int32 数据类型的 dtype 对象可以通过 np.int32np.dtype('int32') 来创建。它的字符码是 'i',用于表示整数类型。

2. 创建数组

Numpy最大的特征(或者说是核心)是其提供的N维数组对象ndarray(N-dimensional array,多维数组),其有以下特征:

  1. 多维数组:ndarray是一个多维数组对象
  2. 数据类型:ndarray中的元素具有相同的数据类型,通常是数值类型,如整数(int)、浮点数(float)或复数(complex)
  3. 形状:ndarray对象的形状用于描述数组的维度。例如,一维数组的形状是一个整数,表示数组的长度;二维数组的形状是一个元组(rows, columns),表示数组的行数和列数
  4. 大小:ndarray对象的大小等于数组形状中各个维度的乘积,可以通过ndarray对象的size属性获取
  5. 内存布局:ndarray对象在内存中以连续的方式存储数据。这种连续存储的方式使得对数组的访问和操作更加高效
  6. 索引和切片:可以使用索引和切片操作访问ndarray对象中的元素。一维数组的索引类似于Python的列表索引(0下标开始),而多维数组可以用整数数组索引等来访问特定的元素或切片
  7. 广播(Broadcasting):ndarray 支持广播操作,可以在不同形状的数组之间进行运算,NumPy 会自动进行形状的调整,使得运算能够进行

ndarray就类似于python中的list,只不过ndarray只能存储同一个类型的数据

我们可以直接用ndarray构造器来创建数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
a = np.array([1, 2, 3])
print(a)
print(a.dtype)

'''
[1 2 3]
int32
'''

# 数组的类型可以在创建时显式指定(不指定默认是float64)
b = np.array([[1, 2, 3], [4, 5, 6]], dtype=complex)
print(b)

'''
[[1.+0.j 2.+0.j 3.+0.j]
[4.+0.j 5.+0.j 6.+0.j]]
'''

也可以用其他方式创建数组:

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
36
37
38
39
40
# .zero创建指定大小数组,元素以0填充
np.zeros((2, 2, 3))
'''
array([[[0., 0., 0.],
[0., 0., 0.]],

[[0., 0., 0.],
[0., 0., 0.]]])
'''

# .ones创建指定大小数组,元素以1填充
np.ones((2, 3, 4), dtype=np.int16)
'''
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]]], dtype=int16)
'''

# .empty创建一个指定形状(shape)、数据类型(dtype)且未初始化的数组
np.empty((2, 3))
'''
array([[6.23042070e-307, 4.67296746e-307, 1.69121096e-306],
[1.89145708e-307, 6.23045466e-307, 2.22526399e-307]])
'''

# .arrange类似python的range函数,需要起始值,终止值和步长(步长可以是浮点数)
np.arange(10, 30, 5)
'''
array([10, 15, 20, 25])
'''

# .linspace创建等差数列构成的数组
np.linspace(0, 2, 9)
'''
array([0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ])
'''

3. 数组属性

Numpy的数组有以下几个常用的属性:

属性 说明
ndarray.ndim 数组的维度数量(也称为秩, rank),比如一维就是1,二维就是2
ndarray.shape 数组的维度。这是一个整数元组,指示数组每个维度的大小,比如二维数组n行m列
ndarray.size 数组元素的总数,比如上面二维数组就是n*m
ndarray.dtype 数组对象的元素类型
ndarray.itemsize 数组中每个元素大小(字节为单位),如float64类型的元素数组的 itemsize 为 8 (=64/8)
ndarray.data 包含数组元素缓冲区
1
2
3
4
5
6
7
8
9
10
11
12
a = np.array([[1, 2], [2, 3], [3, 4]])
print(a.shape)
print(a.ndim)
print(a.size)
print(a.dtype)

'''
(3, 2)
2
6
int32
'''

4. 索引、切片和迭代

对于一维数据,python用的索引和切片方法,Numpy中均可以使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = np.arange(10)
print(a)
print(a[1:5:2])

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

# 布尔索引,可以在索引中放入判断式,只返回ture对应的元素
print(a[a>3])
'''
[4 5 6 7 8 9]
'''

对于多维数组,Numpy中还有一种索引方式方式称为**”整数数组索引”(integer array indexing)或“花式索引”**(fancy indexing),使用整数数组作为索引来选择数组中的元素:

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

'''
[[ 1 2 3]
[ 5 8 9]
[10 12 15]]
[ 1 8 10]
'''

两个整数数组 [0, 1, 2][0, 1, 0] 作为索引,分别表示要选择的行和列的索引。这种索引方式会返回一个由对应位置上的元素组成的新数组。

具体来说,在这个例子中,我们选择了数组 a 的以下元素:

  • 行索引为 0,列索引为 0 的元素:a[0, 0],值为 1
  • 行索引为 1,列索引为 1 的元素:a[1, 1],值为 8
  • 行索引为 2,列索引为 0 的元素:a[2, 0],值为 10

这些选中的元素被组合成一个新的一维数组 b,即 [1, 8, 10]

还可以使用逗号分隔的索引元组来访问特定元素,每个索引元组对应一个维度的切片范围:

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

'''
[[ 1 2 3]
[ 5 8 9]
[10 12 15]]
[ 2 8 12]
'''

a[:, 1] 表示对二维数组 a 的所有行进行切片,并选择索引值为1的列的元素。

在Numpy中进行数组的迭代只会发生在数组的第一个维度上,我们用以下方式生成一个数组:

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
# np.fromfunction(function, shape, **kwargs),根据指定的形状,在每个位置 (i, j) 上调用函数 f(i, j),并将返回的值作为数组的元素填充
# function 是一个函数或可调用对象,用于计算数组中每个元素的值
# shape 是一个表示数组维度的元组或整数,指定了要创建的数组的形状
# **kwargs 是可选的关键字参数,用于传递给函数的额外参数。

def f(x, y):
return 10 * x + y
b = np.fromfunction(f, (5, 4), dtype=int)
print(b)
'''
[[ 0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]]
'''

# 用for循环遍历数组b
for row in b:
print(row)
'''
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]
'''
# 对每一个数组元素进行操作,可以使用flast属性(结果略)
for element in b.flat:
print(element)

5. 广播机制

广播(broadcasting)机制描述的是Numpy在算术操作过程中,如何处理不同形状(shape)的数组。简单来说,当两个数组的维度和长度相同时(形状相同),两个数组的运算将会发生在两个数组对应的位置;当两个数组大小不一致,较小的数组会在较大的数组中被“广播”:

1
2
3
4
5
6
7
8
9
10
11
12
# 较小数组被扩展成相同的形状
a = np.array([[0,0,0],[10,10,10],[20,20,20],[30,30,30]])
b = np.array([1,2,3])

print(a + b)

'''
[[ 1 2 3]
[11 12 13]
[21 22 23]
[31 32 33]]
'''

广播机制遵循以下规则:

  1. 如果两个数组的维度数不同,那么维度较低的数组会在前面补1,直到维度数匹配。
  2. 如果两个数组的维度在任何一个维度上都不匹配,且在该维度上一个数组的形状为1,那么可以将其扩展为与另一个数组相同的形状。
  3. 如果两个数组的维度在任何一个维度上都不匹配,且在该维度上两个数组的形状都不为1,那么会引发一个错误,表示无法进行广播。

数组 a 的形状是 (4, 3),数组 b 的形状是 (3),它们的维度不匹配。但是根据广播机制的规则,可以将数组 b 扩展为 (1, 3) 的形状,使得它与数组 a 的形状匹配。在进行元素级的运算时,广播机制会自动将数组 b 在第一个维度上进行复制,使得它的形状与数组 a 相同,然后进行对应位置的运算。

如果 b 的形状是 (2, 3) 或者是 (4) 都无法进行扩张。

6. 数组基础操作

修改数组形状

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# resize、reshape修改数组形状,两者区别如下
# resize修改原数组
# reshape返回修改后的数组(不影响原数组)
a = np.arange(12)
a.reshape(3,4)
print(a)
a.resize(3,4)
print(a)
'''
[ 0 1 2 3 4 5 6 7 8 9 10 11]
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
'''

# 如果有一个维度为-1,其他维度会自动计算(不能整除会报错)
a = np.arange(12)
print(a.reshape(3,-1))
'''
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
'''

# flatten、ravel将多维数组转化为一维数组(打平)两者区别如下:
# flatten返回原始数组的拷贝,修改不会影响原数组
# ravel返回的是原数组的视图,修改会影响原数组
a = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11]])
b = a.flatten()
b[0] = 10
print(a)
c = a.ravel()
c[0] = 10
print(a)
'''
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[10 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
'''

# 展开方式有两种,“F”为按列方式展开,“C”为按行方式展开
a = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11]])
b = a.flatten(order='F')
c = a.flatten(order='C')
print(b)
print(c)
'''
[ 0 4 8 1 5 9 2 6 10 3 7 11]
[ 0 1 2 3 4 5 6 7 8 9 10 11]
'''

注意下这里的副本和视图的概念:

  • flatten返回数组的副本,也就是一个数组的完整拷贝(深拷贝),它和原始数据存在不同的物理内存,副本的修改不会影响原数据。

  • ravel返回数组的视图,也就是数据的别称或者说是引用(浅拷贝),它和原始数据物理内存在同一个位置,修改视图会影响原数据。切片修改数据会影响原始数组。

翻转数组

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
# transpose、T将数组维度进行翻转
# np.transpose(arr,axes) 多维情况下可以指定维度,比如np.transpose(a, (2, 0, 1))
a = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11]])
np.transpose(a)
a.T # 两种情况下输出是完全相同的
'''
array([[ 0, 4, 8],
[ 1, 5, 9],
[ 2, 6, 10],
[ 3, 7, 11]])
'''

# swapaxes起到类似功能,指定维度交换(和python的transpose函数一样的)
# np.swapaxes(arr,axis1,axis2)
a = np.arange(27).reshape(3,3,3)
np.transpose(a, (0, 2, 1))
np.swapaxes(a, 1, 2) # 两种情况下输出是完全相同的
'''
array([[[ 0, 3, 6],
[ 1, 4, 7],
[ 2, 5, 8]],

[[ 9, 12, 15],
[10, 13, 16],
[11, 14, 17]],

[[18, 21, 24],
[19, 22, 25],
[20, 23, 26]]])
'''

修改维度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# np.expand_dims(arr,axis)在指定位置插入新的维度
a = np.arange(4).reshape(2, 2)
b = np.expand_dims(a, axis=0) # 第0维度插入新的维度
print(b.shape)
'''
(1, 2, 2)
'''

# np.newaxis也可以新增加一个维度,只是和上面的表现方式不同
a = np.arange(4).reshape(2, 2)
b = a[np.newaxis,:,:]
print(b.shape)
'''
(1, 2, 2)
'''

# np.squeeze在数组中删除一维的维度
a = np.arange(4).reshape(1, 2, 2, 1)
b = np.squeeze(a)
print(b.shape)
'''
(2, 2)
'''

数组拼接

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
36
37
38
39
40
41
42
# np.concatenate((a1,a2),axis)多个数组在指定的维度上进行拼接,默认是0维
# 在哪个维度拼接,哪个维度可以不同,但是其他维度必需相同。输出结果的维度和原始数组的维度相同
a = np.array([[1,2,3],[4,5,6]])
b = np.array([[7,8,9],[10,11,12]])
np.concatenate((a,b),axis=0) # 两个(2,3)数组在0维上合并为(4,3)数组
'''
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
'''
np.concatenate((a,b),axis=1) # 两个(2,3)数组在1维上合并为(2,6)数组
'''
array([[ 1, 2, 3, 7, 8, 9],
[ 4, 5, 6, 10, 11, 12]])
'''

# np.stack((a1,a2),axis)也是多个数组在指定维度上拼接,准确说是堆叠,默认也是0维
# 要求堆叠的两个数组有完全一样的形状,。输出结果的维度比原始数组高一维(指定的轴上创建新的维度)
np.stack((a,b),axis=0) # 两个(2,3)数组在0维上堆叠为(2,2,3)数组
'''
array([[[ 1, 2, 3],
[ 4, 5, 6]],

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

# stack的变体
# np.hstack水平堆叠(第二个轴),np.vstack垂直堆叠(第一个轴)
np.hstack((a,b)) # 等同于np.concatenate((a,b),axis=1)
'''
array([[ 1, 2, 3, 7, 8, 9],
[ 4, 5, 6, 10, 11, 12]])
'''
np.vstack((a,b)) # 等同于np.concatenate((a,b),axis=0)
'''
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
'''

数组拆分

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
# 为了方便演示再创建一个随机数生成器对象,种子值(1)进行初始化
rg = np.random.default_rng(1)
arr = rg.integers(0, 10, size=(3, 5)) # 0-10之间取随机整数,构造(3,5)形状的数组
print(arr)
'''
[[4 5 7 9 0]
[1 8 9 2 3]
[8 4 2 8 2]]
'''
# np.split(ary,indices_or_sections,axis),axis确定沿哪个轴,默认0,横向拆分
np.split(arr, 3) # 将原数组平均拆分为3个数组
'''
[array([[4, 5, 7, 9, 0]], dtype=int64),
array([[1, 8, 9, 2, 3]], dtype=int64),
array([[8, 4, 2, 8, 2]], dtype=int64)]
'''
np.split(arr, [1,3], axis=1) # 纵向拆分,中间数组左闭右开,指示的数组位置
'''
[array([[4],
[1],
[8]], dtype=int64),
array([[5, 7],
[8, 9],
[4, 2]], dtype=int64),
array([[9, 0],
[2, 3],
[8, 2]], dtype=int64)]
'''

数组增删

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
36
37
38
39
40
41
42
43
44
# np.append(arr,values,axis=None)增加数组元素
# 需要注意默认轴为None,结果会将数组转换为一维数组并增加元素
arr = rg.integers(0, 10, size=(2, 5))
print(arr)
'''
[[4 5 7 9 0]
[1 8 9 2 3]]
'''
np.append(arr,[0,1,2,3,4]) # 不加axis参数的情况
'''
array([4, 5, 7, 9, 0, 1, 8, 9, 2, 3, 0, 1, 2, 3, 4], dtype=int64)
'''
np.append(arr,[[0,1,2,3,4]],axis=0) # 添加元素时注意维度要相同
'''
array([[4, 5, 7, 9, 0],
[1, 8, 9, 2, 3],
[0, 1, 2, 3, 4]], dtype=int64)
'''
np.append(arr,[[0,1],[2,3]],axis=1)
'''
array([[4, 5, 7, 9, 0, 0, 1],
[1, 8, 9, 2, 3, 2, 3]], dtype=int64)
'''

# np.insert(arr,obj,values,axis=None)插入数组元素
# obj是要插入的位置索引,axis是插入的轴,和上面一样,axis不加参数会使数组展开
np.insert(arr, 2, 100, axis=1) # 轴1(也就是列)的索引2之前插入数值100
'''
array([[ 4, 5, 100, 7, 9, 0],
[ 1, 8, 100, 9, 2, 3]], dtype=int64)
'''

# np.delete(arr,obj,axis=None)删除数组元素
# obj可以是整数或者切片对象,表示要删除的子数组,axis使处理的轴,也同样要指定
np.delete(arr,2,axis=1) # 轴1索引2的值被删除,也就是原数组中的元素7和9
'''
array([[4, 5, 9, 0],
[1, 8, 2, 3]], dtype=int64)
'''
np.delete(arr,np.s_[::2],axis=1) # np.s_用于创建一个切片对象,[::2]从起始到终止步长为2的形式切片,这里切了0,2,4列
'''
array([[5, 9],
[8, 2]], dtype=int64)
'''

7. 常用函数

函数 描述
数学函数
np.around() 函数返回指定数字的四舍五入值
np.floor() 返回小于或者等于指定表达式的最大整数,也就是向下取整
np.ceil() 返回大于或者等于指定表达式的最小整数,也就是向上取整
np.abs() 计算数组元素的绝对值
np.sqrt() 计算数组元素的平方根
np.sum() 计算数组元素的和
np.mean() 计算数组元素的平均值
np.max() / np.min() 找出数组的最大/最小值
字符串函数
np.char.add() 两个数组逐个字符串拼接
np.char.center() 居中字符串
np.char.capitalize() 将字符串第一个字母转为大写
np.char.title() 字符串每个单词的第一个字母转大写
np.char.lower() / np.char.upper() 数组元素转小写/大写
np.char.strip() 移除开头和结尾的特殊字符
np.char.join() 指定分隔符连接数组元素
np.char.replace() 替换字符串
np.char.split() 指定分隔符对字符串分割,返回数组列表

8. Matplotlib

在Numpy的官方文档的Numpy实际应用案例中,几乎所有的可视化数据案例都是用Matplotlib库做的。Matplotlib是python的绘图库。

Matplotlib就和R类似,能做的图非常多,要用的时候还是直接查官方文档比较方便:

Using Matplotlib — Matplotlib 3.8.0 documentation

对于一副Matplotlib绘制的图,我们需要知道以下的一些图表属性:

用Matplotlib库简单绘制一个图(推荐用Jupyter Notebook):

1
2
3
4
5
6
7
8
9
10
import numpy as np
from matplotlib import pyplot as plt

x = np.arange(0,10)
y = x**2
plt.title("Demo")
plt.xlabel("X axis")
plt.ylabel("Y axis")
plt.plot(x,y)
plt.show()

NumPy Applications — NumPy Tutorials

从上面的Numpy应用案例中看一个绘制分形图像的例子:

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
36
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable

# 计算给定网格上的julia set
def general_julia(mesh, c=-1, f=np.square, num_iter=100, radius=2):
z = mesh.copy()
diverge_len = np.zeros(z.shape) # 用于记录迭代次数
for i in range(num_iter):
conv_mask = np.abs(z) < radius # 只有网格上的元素绝对值小于radius才进行计算
z[conv_mask] = f(z[conv_mask]) + c
diverge_len[conv_mask] += 1
return diverge_len

# 在julia set中进行的操作
def accident(z):
return z - (2 * np.power(np.tan(z), 2) / (np.sin(z) * np.cos(z)))

# 绘制分形图像
def plot_fractal(fractal, title='Fractal', figsize=(10, 10), cmap='rainbow', extent=[-2, 2, -2, 2]):
plt.figure(figsize=figsize)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示符号
ax = plt.axes()
ax.set_title(f'${title}$')
ax.set_xlabel('实轴')
ax.set_ylabel('虚轴')
im = ax.imshow(fractal, extent=extent, cmap=cmap) # 在坐标轴上绘制图像
divider = make_axes_locatable(ax) # 创建一个可分离的坐标轴对象
cax = divider.append_axes("right", size="5%", pad=0.1) # 右侧添加新坐标轴对象
plt.colorbar(im, cax=cax, label='迭代次数') # 添加颜色条并设置标签

output = general_julia(mesh, f=accident, num_iter=15, c=0, radius=np.pi)
kwargs = {'title': 'Accidental \ fractal', 'cmap': 'Blues'}

plot_fractal(output, **kwargs)

欢迎小伙伴们留言评论~