本节导图:https://www.processon.com/view/link/5fcc5e81f346fb3fc8776929
Python列表的元素可以是任意对象,列表里保存的是对象的指针。比如存储 [1, 2, 3]
,需要三个指针和三个整数对象,对于数值运算来讲,这很浪费内存和CPU计算。
Python也提供了array
模块,它可以直接保存数值(而不是对象),但是它不支持多维数组、也缺乏丰富的运算函数。
ndarray即n维数组,它弥补了以上不足,它提供了如下对象:
ndarray是Numpy的核心对象,Numpy中的所有函数都是围绕ndarray进行处理的。
我们先将numpy
导入
import numpy as np
a1 = np.array([1, 2, 3]) a1
array([1, 2, 3])
a2 = np.array([[4, 5, 6], [7, 8, 9]]) a2
array([[4, 5, 6], [7, 8, 9]])
shape
属性,查看数组的行数和列数print(a1.shape) print(a2.shape)
(3,) (2, 3)
reshape()
方法,创建特定shape的新数组a3 = a2.reshape((3, 2)) a3
array([[4, 5], [6, 7], [8, 9]])
reshape后得到的数组与原数组共享存储,我们可以尝试来修改下
a3[0,0] = 0 # 均发生修改 print(a3) print(a2)
[[0 5] [6 7] [8 9]] [[0 5 6] [7 8 9]]
dtype
属性,查看数组的元素类型a1.dtype
dtype('int32')
通过整数列表创建的数组,默认的dtype,在64位系统里是int64,在32位系统里是int32
a4 = np.array([1, 2, 3], dtype=np.int32) a4
array([1, 2, 3])
astype()
方法进行类型转换a4.astype(np.float32) # 将dtype int32转为float32
array([1., 2., 3.], dtype=float32)
通过列表创建数组效率显然不高,numpy提供了很多专门用来创建数组的函数。
arange()
创建等差数组类似于内置函数range()
,arange()
函数指定开始值、终止值、步长,函数返回一个等差一维数组。
np.arange(0, 10, 1)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
注意到,这里不包含终止值
可以后接reshape()
方法,指定特定shape
np.arange(0, 10, 1).reshape((5, 2))
array([[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]])
linspace()
创建等差数组linspace()
函数指定开始值、终止值、元素个数返回等差数组。
np.linspace(1, 10, 10) # 将1到10 划分10等份
array([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
注意到,这里包含终止值。可以设置参数endpoint=False
,来排除终止值
np.linspace(1, 10, 10, endpoint=False)
array([1. , 1.9, 2.8, 3.7, 4.6, 5.5, 6.4, 7.3, 8.2, 9.1])
logspace()
创建等比数组和linspace()
函数用法类似,logspace()
函数也指定开始值、终止值、元素个数,但它需要额外传入公比参数base
,默认为10
np.logspace(0, 5, 5, base=2, endpoint=False)
array([ 1., 2., 4., 8., 16.])
注意,这里的起始值和终止值其实是公比2
的次幂
zeros()
ones()
full()
初始化特定值np.zeros((2, 3), dtype=np.int32) # 初始化shape为(2,3)的数组所有元素为int32的0
array([[0, 0, 0], [0, 0, 0]])
np.ones((3, 2), dtype=np.float32) # 初始化shape为(3,2)的数组所有元素为float32的1
array([[1., 1.], [1., 1.], [1., 1.]], dtype=float32)
np.full(4, 10, dtype=np.float64) # 初始化shape为(4,)的数组所有元素为float64的10
array([10., 10., 10., 10.])
像存取列表list的方式那样,来存取数组
a = np.arange(10) # 定义一个数组 a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(a[5]) # 获取数组第5个元素(从0开始)
5
print(a[:5]) # 获取数组前5个元素(不包括第5个) print(a[2:5]) # 获取数组第2到第5个元素(包括第2个,不包括第5个) print(a[:-1]) # 获取数组从头到尾的元素(不包括最后一个),-1表示倒数第1个元素
[0 1 2 3 4] [2 3 4] [0 1 2 3 4 5 6 7 8]
print(a[1:9:2]) # 从1到9,每2步取一个元素
[1 3 5 7]
a[1] = 11 # 将第1个元素替换 print(a)
[ 0 11 2 3 4 5 6 7 8 9]
a[0:2] = 100, 101 # 替换前2个元素 print(a)
[100 101 2 3 4 5 6 7 8 9]
b = np.arange(5) # 定义b print(b) c = b[:3] # 切片获得c print(c) c[0] = 100 # 更改c print(c) print(b) # b也发生了修改
[0 1 2 3 4] [0 1 2] [100 1 2] [100 1 2 3 4]
这里与list的切片不同,列表的切片返回的是一个新的列表
b = list(range(5)) # 定义b print(b) c = b[:3] # 切片获得c print(c) c[0] = 100 # 更改c print(c) print(b) # b并没有被修改
[0, 1, 2, 3, 4] [0, 1, 2] [100, 1, 2] [0, 1, 2, 3, 4]
# 定义二维数组a a = np.arange(10).reshape((-1, 2)) # -1代表自动计算shape[0],这里是shape为(5,2) a
array([[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]])
print(a[1,:]) # 获取第1行 print(a[:,1]) # 获取第1列 print(a[1:3, 1]) # 第1到3行,第1列
[2 3] [1 3 5 7 9] [3 5]
除了使用索引和切片,我们还可以使用list
或者数组来存取元素
a = np.arange(100, 105) # 定义a a
array([100, 101, 102, 103, 104])
a[[0,2,4]] # 使用列表,按照列表内声明的索引取元素,返回新数组
array([100, 102, 104])
a[np.array([0,2,4])] # 使用数组,按照索引取元素
array([100, 102, 104])
a[np.array([True, False, False, True, True])] # 使用bool数组,返回对应位置为True的数组
array([100, 103, 104])
数组获取到的数组,是原始数组的副本
b = a[np.array([0,2,4])] # 通过数组获取数组b print(b) b[0] = 0 # 更改b的元素 print(b) # b发生修改 print(a) # a未发生修改
[100 102 104] [ 0 102 104] [100 101 102 103 104]
ufunc是universal function的缩写,它是一种能对数组的每个元素进行运算的函数。
数组之间的加减乘除等运算,可以通过Python内置的+ - * /
等运算符实现,也可以使用add() subtract()
等函数实现,两者等价。
完整的运算符参考下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Awk1LOS-1637239987972)(images/数组运算符ufun函数.png)]
# 定义两个数组 a = np.arange(10) b = np.full(10, -1) print(a) print(b)
[0 1 2 3 4 5 6 7 8 9] [-1 -1 -1 -1 -1 -1 -1 -1 -1 -1]
a + b # 使用运算符+
array([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8])
np.multiply(a, b) # 使用函数乘
array([ 0, -1, -2, -3, -4, -5, -6, -7, -8, -9])
类似的,数组之间的比较运算也可以通过运算符> = <
等实现,等价于使用equal() less()
等函数。它返回一个布尔数组。
完整的运算符参考如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M99MLXoA-1637239987978)(images/比较运算符ufunc函数.png)]
# 定义两个随机数组 a = np.random.rand(5) b= np.random.rand(5) print(a) print(b)
[0.45512267 0.54405151 0.24847556 0.90709983 0.21287093] [0.07250248 0.85389154 0.0598896 0.58928252 0.47854526]
a > b # 使用运算符>
array([ True, False, True, True, False])
np.less_equal(a, b) # 使用函数less_equal
array([False, True, False, False, True])
布尔运算在python里面用and or not
关键字实现,无法实现运算符重载,所以没有操作符,只能使用函数logical_and() logical_or()
等实现。
什么是运算符重载?你可以通过它来定义(或重定义)两个对象的+ - * /
等运算。这些运算符的操作本质上是函数调用,你可以在你的类里面实现__add__()
内置函数,然后你的类就具有+
加法的功能了。这里不展开,感兴趣的同学参考:https://zhuanlan.zhihu.com/p/162931696
# 定义两个bool数组 a = np.array([True, False, False]) b = np.array([True, True, False]) print(a) print(b)
[ True False False] [ True True False]
print(np.logical_and(a, b)) # 逻辑与 print(np.logical_or(a, b)) # 逻辑或 print(np.logical_not(a)) # 逻辑非 print(np.logical_xor(a, b)) # 逻辑异或
[ True False False] [ True True False] [False True True] [False True False]
上面所讲的ufunc函数处理的都是shape相同的多个数组,如果要处理的数组shape不同,会进行如下广播处理:
看起来比较抽象,可以通过两个例子来理解这4条规则。
arr0 = np.array([ # 2维数组,shape为(2,3) [1, 2, 3], [4, 5, 6] ]) arr2 = np.array([10, 20, 30]) # 1维数组,shape为(3,) print(arr0.shape) print(arr2.shape)
(2, 3) (3,)
arr_sum = arr0 + arr2 # 广播发生,计算两个数组之和 print(arr_sum) print(arr_sum.shape) # 输出shape:根据规则1,arr2补齐为(1,3);根据规则2,输出shape为(max(2,1),max(3,3)) = (2,3)
[[11 22 33] [14 25 36]] (2, 3)
我们已经知道输出shape为(2,3),那具体每个元素如何计算呢?
arr0
的shape为(2,3)
,arr2
的shape为(1,3)
,
过程参考下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pPB1A1Xr-1637239987979)(images/广播1.png)]
arr2 = np.array([ # 2维数组,shape为(1,3) [10, 20, 30] ]) arr3 = np.array([ # 2维数组,shape为(3,1) [10], [20], [30] ]) print(arr2.shape) print(arr3.shape)
(1, 3) (3, 1)
arr_sum = arr2 + arr3 # 广播发生,计算数组之和 print(arr_sum) print(arr_sum.shape) # 输出shape:规则1满足;根据规则2,输出shape为(max(1,3), max(3,1)) = (3,3)
[[20 30 40] [30 40 50] [40 50 60]] (3, 3)
如何计算每个元素?
过程参考下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uzhPCc7H-1637239987984)(images/广播2.png)]
numpy还提供了大量对数组进行处理的函数,充分利用这些函数,能简化程序的逻辑、提高程序运行速度。
numpy.random
模块中提供了大量的随机数相关的函数,下面是一些重点函数列表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PeEhFx8i-1637239987986)(images/随机数1.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l9iZDbeZ-1637239987988)(images/随机数2.png)]
看几个例子
print(np.random.rand(2,3)) # 随机生成shape为(2,3)的二维数组,每个元素在0~1之间 print(np.random.randn(2,3)) # 随机生成shape为(2,3)的二维数组,每个元素符合标准正态分布(均值为0,标准差为1) print(np.random.randint(1,10,size=(2,3))) # 随机生成shape为(2,3)的二维数组,每个元素在1到10之间,不包含10
[[0.71599632 0.77172853 0.28996579] [0.72852298 0.60934017 0.84835497]] [[-0.42632997 0.85967941 -1.09201344] [-0.23171307 -0.17257021 0.90615894]] [[2 3 6] [1 2 3]]
print(np.random.uniform(1,10,(2,3))) # 随机生成shape为(2,3)的二维数组,每个元素符合1~10的均匀分布
[[3.06191826 6.84866151 4.98272061] [7.88468112 9.32084376 6.50330689]]
a = np.arange(10) np.random.shuffle(a) # 使用shuffle函数打乱数组a,数组a被改变,无返回 print(a)
[4 3 6 8 7 0 1 5 9 2]
a = np.arange(10) print(np.random.permutation(a)) # 使用permutation函数打乱数组a,数组a不变,返回打乱后的新数组 print(a)
[1 6 9 4 0 2 3 5 8 7] [0 1 2 3 4 5 6 7 8 9]
np.random.seed(0) # 设置随机种子,使得每次运行的随机数都一致(使得随机数可复现) print(np.random.rand(2,3))
[[0.5488135 0.71518937 0.60276338] [0.54488318 0.4236548 0.64589411]]
本节介绍的函数如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ab0fNMim-1637239987989)(images/求和平均值方差.png)]
看几个例子
a = np.random.randint(0,5,(2,3)) print(a)
[[4 0 0] [4 2 1]]
print(np.sum(a, axis=0)) # 沿维度0求和 print(np.sum(a, axis=1)) # 沿维度1求和 print(np.sum(a, axis=0, keepdims=True)) # 保留维度
[8 2 1] [4 7] [[8 2 1]]
其它几个函数使用方式类似,这里不再细讲
常见的大小与排序函数,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TC2Tfeik-1637239987991)(images/大小与排序.png)]
这里注意,min()
与minimum()
的区别、max()
与maximum()
的区别
# 定义两个数组 a = np.random.randint(0,5,(2,3)) b = np.random.randint(0,5,(2,3)) print(a) print(b)
[[0 1 1] [0 1 4]] [[3 0 3] [0 2 3]]
print(np.min(a, axis=0)) # 沿着维度0对a取最小值(单个数组) print(np.minimum(a, b)) # 比较两个数组,取每个位置的最小值
[0 1 1] [[0 0 1] [0 1 3]]
常见的数组操作函数如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cSS8nOOh-1637239987993)(images/多维数组操作.png)]
我们来看些例子
a = np.full((2,2), 1) # shape为(2,2),元素为1 b = np.full((2,2), 2) # shape为(2,2),元素为2 print(a) print(b)
[[1 1] [1 1]] [[2 2] [2 2]]
print(np.concatenate((a,b))) # 沿着维度0拼接 print(np.concatenate((a,b), axis=1)) # 沿着维度1拼接
[[1 1] [1 1] [2 2] [2 2]] [[1 1 2 2] [1 1 2 2]]
print(np.vstack((a,b))) # 沿着维度0拼接 print(np.hstack((a,b))) # 沿着维度1拼接
[[1 1] [1 1] [2 2] [2 2]] [[1 1 2 2] [1 1 2 2]]
常见的乘积运算函数如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BCezh9Jo-1637239987995)(images/各种乘积运算.png)]
我们来看些例子
a = np.full((3,1), 1) # shape为(3,1),元素为1 b = np.full((1,3), 2) # shape为(1,3),元素为2 print(a) print(b)
[[1] [1] [1]] [[2 2 2]]
print(np.dot(a,b)) # 矩阵积, shape为(3,3)
[[2 2 2] [2 2 2] [2 2 2]]
print(np.dot(b,a)) # 矩阵积, shape为(1,1)
[[6]]
a = np.arange(0,5) b = np.arange(0,-5,-1) print(a) print(b)
[0 1 2 3 4] [ 0 -1 -2 -3 -4]
print(np.inner(a,b)) # 内积,返回标量
-30
如上所述,操作数组的函数非常多,这里只是介绍了5种常用的大类,每个类里面也仅仅举了一小部分的使用例子,对于大家来讲,需要做:
本节主要给大家讲了两个知识点:ndarray对象和numpy庞大的函数库。
实践中,python相比java、c等语言,确实慢。但是在做多维数组的计算时,如果你能用好numpy的ndarray及其配套的函数,(而不是使用list和for循环),那你的Python程序 运行效率将会很高。