Numpy
在Python中已经提供了基本的计算能力,能够对整数、浮点数以及复数进行计算,但是并未提供数组计算的能力,而数组计算的能力是很多科学计算底层所必须的,因此Numpy包出现了,Numpy包提供了丰富的数值计算方法,因此许多机器学习甚至深度学习的包底层都是以来于Numpy
本小节对Numpy所提供了计算能力和一些简单常用的方法做相应的介绍,针对Numpy中更复杂深入的内容,可以在后续需要使用到时再进行了解。
在使用之前,首先需要确保当前使用的环境中正确安装了Numpy,同其他的包一样,可以使用conda或pip来安装。
bash
conda install numpy
# or
pip install numpy
安装完成后,在使用Numpy时,需要首先将其导入
python
import numpy as np # 导入Numpy并将其重命名为np,数据分析常见操作
1.1 数组
数组(array)是Numpy中的一个基础数据结构,数组和前面所讲到的列表有相似之处,但是数组可以定义维度,可以是一维、二维...甚至n维,因此在Numpy中将其称为ndarray,也就是N维数组(N-dimensional array)的简称。
1.1.1 创建数组
在Numpy中有两种方式来创建一个一维或多维的数组
- 通过已有的数据来创建一个数组
- 通过
Numpy提供的方法来创建一个数组
python
list1 = [1,2,3,4] # Python列表
a1 = np.array(list1) # 通过Numpy的array方法将其转换为数组
print(a1)
print(type(a1)) # 其类型为ndarray
print(a1.shape) # 查看形状
print(a1.ndim) # 查看维度信息
[1 2 3 4]
<class 'numpy.ndarray'>
(4,)
1
python
# 从已有数据创建一个二维数组
list2 = [[1,2,3],[4,5,6],[7,8,9]]
a = np.array(list2) # 通过Numpy的array方法将其转换为数组
print(a)
print(type(a))
print(a.shape)
print(a.ndim) # 查看维度信息
[[1 2 3]
[4 5 6]
[7 8 9]]
<class 'numpy.ndarray'>
(3, 3)
2
创建数组时,可以指定其数组元素的类型。使用 dtype 函数可以查看数组元素的类型
python
a = [1,2,3,4,5]
b = np.array(a).astype('float') # 创建数组时指定元素类型为 float
print(b)
print(b.dtype) # 查看数组元素的类型
c = np.array(a).astype('object') # 创建数组时指定元素类型为字符串 object
print(c)
print(c.dtype) # 查看数组元素的类型
[1. 2. 3. 4. 5.]
float64
[1 2 3 4 5]
object
数组和列表是有区别的,首先数组输出中各个元素之间是以空格间隔,而列表的输出中各个元素是以,间隔
python
list3 = [1,2,3,4]
a1 = np.array(list3)
print(a1)
print(list3)
[1 2 3 4]
[1, 2, 3, 4]
同时数组和列表的运算方式也不同,同样形式的运算作用在数组和列表上会得到完全不同的结果
python
list4 = [1,2,3,4]
a1 = np.array(list4)
print(list4 * 2)
print(a1 * 2)
[1, 2, 3, 4, 1, 2, 3, 4]
[2 4 6 8]
可以看到列表乘2的结果是将列表中的元素全部复制了一遍,而数组乘2的结果是数组中每一个元素的值都乘2,也即对运算做了广播(broadcast),在做运算时,我们更希望的是数组的运算方式
同样,可以使用Numpy提供的方法快速创建一维或者多维数组
python
ones = np.ones(4) # 通过Numpy提供的方法,声明一个全是1的数组
print(ones)
print(type(ones))
print(ones.shape)
print(ones.ndim)
[1. 1. 1. 1.]
<class 'numpy.ndarray'>
(4,)
1
python
zeros = np.zeros(4) # 声明一个全是0的数组
print(zeros,end="\n\n")
arange = np.arange(0,4,1) # 声明一个递增的数组,规则参考range
print(arange,end="\n\n")
line = np.linspace(0,20,5) # 声明一个由(0,20)五等分的结果组成的数组
print(line)
[0. 0. 0. 0.]
[0 1 2 3]
[ 0. 5. 10. 15. 20.]
python
ones = np.ones((3,3)) # 声明一个3x3全是1的数组
print(ones,end="\n\n")
zeros = np.zeros((2,3,2)) # 声明一个2x3x2全是0的数组
print(zeros,end="\n\n")
rand = np.random.randn(2,3) # 声明一个2x3的数组,元素全部随机填充
print(rand,end="\n\n")
eye = np.eye(4) # 声明一个4x4的单位数组
print(eye)
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
[[[0. 0.]
[0. 0.]
[0. 0.]]
[[0. 0.]
[0. 0.]
[0. 0.]]]
[[ 0.87594299 -0.15858892 1.385174 ]
[-0.86297466 0.55303073 0.28822829]]
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]
python
empty = np.empty((2,3)) # 声明一个大小为 6 的空数组
empty.fill(1) # 向数组中填充元素
print(empty,end="\n\n")
zeros = np.zeros_like(empty) # 声明一个和empty形状相同,元素为 0 的数组
print(zeros.shape) # 查看形状
print(zeros,end="\n\n")
ones = np.ones_like(empty) # 声明一个和empty形状相同,元素为 1 的数组
print(ones.shape) # 查看形状
print(ones)
[[1. 1. 1.]
[1. 1. 1.]]
(2, 3)
[[0. 0. 0.]
[0. 0. 0.]]
(2, 3)
[[1. 1. 1.]
[1. 1. 1.]]
1.1.2 随机数生成
Numpy提供了大量的随机数生成的模块,能够快速的按照不同的分布来生成随机数
python
a = np.random.rand(2,3) # 产生(0,1)之间均匀分布的随机数
print(a,end="\n\n")
b = np.random.randint(0,100,size=(3,3)) # 随机生成(0,100)之间的随机数,并填充到形状为3x3的数组中
print(b,end="\n\n")
norm = np.random.normal(0,1,size=(4,4)) # 生成符合N(0,1)正态分布的随机数,并填充到形状为4x4的数组中
print(norm)
[[0.29778685 0.00331446 0.67195933]
[0.63893124 0.74188645 0.23314167]]
[[98 9 7]
[84 31 34]
[83 77 68]]
[[-3.65897271e-03 3.90870164e-01 -2.48183283e-03 -1.64943910e-01]
[ 1.22491285e+00 4.07798109e-01 -3.68227359e-01 -2.53180612e-01]
[-3.14278809e+00 1.58484411e+00 6.29293717e-01 -1.97658728e-01]
[-1.57571913e-01 -1.16098910e+00 3.61036004e-02 -5.95937772e-01]]
除了生成随机数,还可以对已有的数组进行元素顺序的打乱
python
a = np.arange(10) # 声明一个从 0 到 9 的有序数组
print(a)
np.random.shuffle(a) # 打乱数组,每次运行都得到不同结果
print(a)
[0 1 2 3 4 5 6 7 8 9]
[9 7 8 1 3 5 4 6 2 0]
有时候希望每次进行随机操作时得到的随机数是相同的,则需要指定随机种子
python
np.random.seed(100) # 指定随机种子
np.random.normal(0, 1, 10) # 不论执行多少次,得到的随机数都是相同的
array([-1.74976547, 0.3426804 , 1.1530358 , -0.25243604, 0.98132079,
0.51421884, 0.22117967, -1.07004333, -0.18949583, 0.25500144])
1.1.3 文件读写
在Numpy中提供了方便的文件读写能力,能够方便的将数据写入到txt文件中并从中读取,或者是将数据保存成npy格式的数据来获得更快的数据读写速度。
python
a = np.random.randint(0,100,size=(2,5)) # 创建2x5的数组,用0到100的随机数填充
a
array([[16, 9, 93, 86, 2],
[27, 4, 31, 1, 13]])
python
np.savetxt("data/text_data.txt",a) # 将a的内容保存到data文件夹下的text_data.txt中,并且各个数据之间以空格分隔
python
# 查看data/text_data.txt的内容
%pycat data/text_data.txt
同样可以使用np.loadtxt从txt文本中读取数据生成数组
python
b = np.loadtxt("data/text_data.txt") # 从data/text_data.txt中读取数据
b
array([[16., 9., 93., 86., 2.],
[27., 4., 31., 1., 13.]])
除了将数据保存成txt格式外,Numpy还可以以npy格式保存和读取数据,这种格式具有更好的读写性能
python
np.save("data/npy_data.npy",a) # 将数组a存储成npy格式
python
b = np.load("data/npy_data.npy") # 读取npy格式的数据
b
array([[16, 9, 93, 86, 2],
[27, 4, 31, 1, 13]])
如果数据中带有其他分隔符,读取文件的时候也需要指定其他分隔符
python
%%writefile data/comma.txt
1, 2, 3, 4, 5, 6
7, 8, 9, 10, 11, 12
Overwriting data/comma.txt
python
data = np.loadtxt('data/comma.txt', delimiter=',') # 指定读取文件的分隔符为逗号 ,
print(data)
[[ 1. 2. 3. 4. 5. 6.]
[ 7. 8. 9. 10. 11. 12.]]
1.2 数组的索引与切片
数组的索引和切片在Numpy中同Python中列表的切片是相同的,在切片时都是左闭右开的原则,但相比于Python中列表的切片,Numpy中还有条件切片
python
a = np.arange(0,10)
print(a)
print(a[3]) # 取下标为3的元素
print(a[5:8]) # 取下标5到7的元素
print(a[-3]) # 取倒数第三个元素
print(a[::2]) # 设置步长为2
print(a[::-1],end="\n\n") # 倒序
# 条件切片
print(a[a > 3]) # 取大于3的元素
print(a[a % 2 == 1]) # 取奇数
print(np.where(a > 3)) # 查找大于 3 的元素的位置
print(np.where(a % 2 == 1)) # 查找奇数元素的位置
[0 1 2 3 4 5 6 7 8 9]
3
[5 6 7]
7
[0 2 4 6 8]
[9 8 7 6 5 4 3 2 1 0]
[4 5 6 7 8 9]
[1 3 5 7 9]
(array([4, 5, 6, 7, 8, 9], dtype=int64),)
(array([1, 3, 5, 7, 9], dtype=int64),)
python
data = np.arange(12).reshape((2,3,2))
print(data)
[[[ 0 1]
[ 2 3]
[ 4 5]]
[[ 6 7]
[ 8 9]
[10 11]]]
python
print(data[0,1],end="\n\n") # 取第0层第1行的数据
print(data[:,0],end="\n\n") # 取所有层第0行的数据
print(data[1,0,0],end="\n\n") # 取第1层第0行第0列的数据
print(data[:,:,1],end="\n\n") # 取所有层所有行的第1列的数据
print(data[-1,0,0],end="\n\n") # 取倒数第1层第0行第0列的数据
print(data[:,:,-1],end="\n\n") # 取所有层所有行的倒数第1列的数据
[2 3]
[[0 1]
[6 7]]
6
[[ 1 3 5]
[ 7 9 11]]
6
[[ 1 3 5]
[ 7 9 11]]
python
print(data[data > 4],end="\n\n") # 取所有大于4的元素
print(data[data % 2 == 0],end="\n\n") # 取所有的偶数
print(np.where(data > 4),end="\n\n") # 查找大于 4 的元素的位置
print(np.where(data % 2 == 0)) # 查找偶数元素的位置
[ 5 6 7 8 9 10 11]
[ 0 2 4 6 8 10]
(array([0, 1, 1, 1, 1, 1, 1], dtype=int64), array([2, 0, 0, 1, 1, 2, 2], dtype=int64), array([1, 0, 1, 0, 1, 0, 1], dtype=int64))
(array([0, 0, 0, 1, 1, 1], dtype=int64), array([0, 1, 2, 0, 1, 2], dtype=int64), array([0, 0, 0, 0, 0, 0], dtype=int64))
Numpy 中还可以用 bool 类型的数组来进行选取,与元素 true 对应位置的元素将被选取,而与元素 false 对应位置的元素不被选取
python
indexs = [True, True, False, False, True] # 用 bool 数组作为索引
data = np.arange(5)
print(data)
print(data[indexs]) # true 位置的元素将被选取
[0 1 2 3 4]
[0 1 4]
1.3 数组的操作
1.3.1 数组的排序
Numpy提供了对数组进行排序的能力,可以使用np.sort函数对数组进行排序,同时在排序时,np.sort函数有一个axis的参数,通过这个参数可以指定排序的方向。
而对axis参数的理解,可以认为当axis=i时,Numpy就会沿着第i个下标变化的方向进行操作
当数组只有一维时,可以直接使用np.sort函数进行排序
python
a = np.random.randint(100,size=10) # 10个元素的数组,用小于100的随机数进行填充
print(a)
print(np.sort(a)) # 默认是递增排序
print(np.argsort(a)) # 也可以获取排序之后的索引位置
[83 4 91 59 67 7 49 47 65 61]
[ 4 7 47 49 59 61 65 67 83 91]
[1 5 7 6 3 9 8 4 0 2]
在数组的维度不止一维时,就可以通过axis来指定排序的方向了
python
a = np.random.randint(100,size=(3,3)) # 声明一个二维3x3的数组,用小于100的随机数进行填充
a
array([[14, 55, 71],
[80, 2, 94],
[19, 98, 63]])
以上面3x3维度为2的数组为例,数组维度为2也就表明axis的取值有0和1两个值,当我们指定axis=0时,也就是让Numpy按照第0个下标变化的方向进行操作,也就是按照a[0][0], a[1][0], a[2][0]的方向进行排序,同时还有a[0][1], a[1][1], a[2][1]和a[0][2], a[1][2], a[2][2],放在二维中,就是按照列进行排序,下面来看看结果是否为分析的结果对应
python
np.sort(a,axis=0)
array([[14, 2, 63],
[19, 55, 71],
[80, 98, 94]])
可以看到上述结果中是对各个列进行排序,当我们指定axis=1时,也就是Numpy按照第1个下标变换的方向进行操作,也就是按照a[0][0], a[0][1], a[0][2],同时还有a[1][0], a[1][1], a[1][2]和a[2][0], a[2][1], a[2][2],放在二维时,也就是按照行进行排序
python
np.sort(a,axis=1)
array([[14, 55, 71],
[ 2, 80, 94],
[19, 63, 98]])
排序的标准也可以自己进行指定,此时使用的是 np.lexsort 函数
python
a = np.random.randint(100, size=(3,3)) # 声明一个二维3x3的数组,用小于100的随机数进行填充
print(a, end="\n\n")
index = np.lexsort([-1 * a[:,0]]) # 按照第0列的降序,对所有的行进行排序
print(a[index],"\n\n")
[[53 27 56]
[30 48 47]
[39 38 44]]
[[53 27 56]
[39 38 44]
[30 48 47]]
1.3.2 数组形状操作
Numpy提供了对数组形状进行改变的能力,可以方便的转换数组的形状
python
d = np.arange(12) # 声明一个有12个元素的数组
print(d)
print(d.shape)
print(d.ndim,end="\n\n")
d1 = d.reshape((4,3)) # 将其reshape为4x3的数组
print(d1)
print(d1.shape)
print(d1.ndim,end="\n\n")
d2 = d.reshape((2,3,-1)) # 将其转换为2x3x2形状的数组,(2,3,-1)中最后的-1指的是让numpy自己推断剩下的维度是多少
print(d2)
print(d2.shape)
print(d2.ndim)
[ 0 1 2 3 4 5 6 7 8 9 10 11]
(12,)
1
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
(4, 3)
2
[[[ 0 1]
[ 2 3]
[ 4 5]]
[[ 6 7]
[ 8 9]
[10 11]]]
(2, 3, 2)
3
展平数组
python
a = np.arange(9).reshape((3,3))
print(a)
print(a.shape,end="\n\n")
f = a.flatten() # 使用flatten将其展平为一维数组
print(f)
print(f.shape,end="\n\n")
f1 = a.reshape(9) # 或者使用reshape
print(f1)
print(f1.shape)
[[0 1 2]
[3 4 5]
[6 7 8]]
(3, 3)
[0 1 2 3 4 5 6 7 8]
(9,)
[0 1 2 3 4 5 6 7 8]
(9,)
增加维度
python
a = np.arange(10)
print(a)
print(a.shape)
a = a[np.newaxis, :] # 为数组增加额外的维度
print(a)
print(a.shape)
[0 1 2 3 4 5 6 7 8 9]
(10,)
[[0 1 2 3 4 5 6 7 8 9]]
(1, 10)
压缩数组
python
a = a.squeeze() # 压缩数组,去掉多余的维度
print(a)
print(a.shape)
[0 1 2 3 4 5 6 7 8 9]
(10,)
1.3.3 数组的拼接
python
a = np.ones((3))
b = np.zeros((4))
c = np.concatenate((a,b)) # 将两个数组连接
print(c)
print(c.shape)
print("垂直(vertical)堆叠两个数组")
a1 = np.ones((2,2))
b1 = np.zeros((2,2))
print(np.vstack((a1,b1)))
print("水平堆叠两个数组")
print(np.hstack((a1,b1)))
[1. 1. 1. 0. 0. 0. 0.]
(7,)
垂直(vertical)堆叠两个数组
[[1. 1.]
[1. 1.]
[0. 0.]
[0. 0.]]
水平堆叠两个数组
[[1. 1. 0. 0.]
[1. 1. 0. 0.]]
1.4 数组的相关运算
1.4.1 数组内的运算
同时Numpy提供了大量的通用函数,可以用来计算最大值、最小值以及开方等常用运算等
python
a = np.arange(1,10).reshape((3,3))
print(a,end="\n")
print(np.min(a)) # 输出a中的最小值
print(np.max(a)) # 输出a中的最大值
print(np.sum(a)) # 计算a中所有元素的和
print(np.mean(a)) # 计算a中元素的均值
print(np.var(a)) # 计算a中元素的方差
print(np.std(a)) # 计算a中元素的标准差
[[1 2 3]
[4 5 6]
[7 8 9]]
1
9
45
5.0
6.666666666666667
2.581988897471611
python
print(np.abs(a)) # 计算a中的所有元素的绝对值
print(np.sqrt(a)) # 计算a中所有元素的正平方根
print(np.log2(a)) # 计算a中所有元素以2为底的对数
[[1 2 3]
[4 5 6]
[7 8 9]]
[[1. 1.41421356 1.73205081]
[2. 2.23606798 2.44948974]
[2.64575131 2.82842712 3. ]]
[[0. 1. 1.5849625 ]
[2. 2.32192809 2.5849625 ]
[2.80735492 3. 3.169925 ]]
对数组赋值有两种方式,第一种方式是直接用等号。这种情况下,修改其中一个数组另一个数组也会随之改变
python
a = np.arange(10)
print(a)
b = a # 使用等号为数组赋值
b[0] = 99 # 修改数组 b,数组 a 也会发生变化
print(b)
print(a)
[0 1 2 3 4 5 6 7 8 9]
[99 1 2 3 4 5 6 7 8 9]
[99 1 2 3 4 5 6 7 8 9]
如果想让赋值前后的两个数组不相关联,能够各自独立进行修改,可以使用 copy 函数
python
a = np.arange(10)
print(a)
b = a.copy() # 使用 copy 函数为数组赋值
b[0] = 99 # 修改数组 b,数组 a 不会发生变化
print(b)
print(a)
[0 1 2 3 4 5 6 7 8 9]
[99 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
python
a = np.arange(1, 10)
print(a)
print(a.prod()) # 计算 a 中所有元素的累乘
[1 2 3 4 5 6 7 8 9]
362880
python
a = np.array([1.2, 3.4, 5.6, 7.8])
print(a)
print(a.round()) # 计算 a 中所有元素四舍五入的结果
[1.2 3.4 5.6 7.8]
[1. 3. 6. 8.]
python
a = np.arange(10)
print(a)
print(a.clip(3,7)) # 数组中比 3 小的元素全部变为 3,比 7 大的元素全部变为 7
[0 1 2 3 4 5 6 7 8 9]
[3 3 3 3 4 5 6 7 7 7]
python
a = np.array([2, 3, 1, 4, 5, 9, 6, 7])
print(a.argmin()) # 获取 a 中最小元素的索引
print(a.argmax()) # 获取 a 中最大元素的索引
2
5
1.4.2 数组间的运算
python
a = np.ones((3,3))
b = np.zeros((3,3))
c = np.eye(3)
print("数乘数组")
print(3 * a)
print("数组加法")
print(a + a)
print("数组减法")
print(b-a)
print("数组乘法")
print(a * c)
print("数组除法")
print(c / a)
print("数组幂")
m1 = np.array([0, 1, 2, 3])
m2 = np.array([1, 2, 3, 4])
print(np.power(m1, m2))
数乘数组
[[3. 3. 3.]
[3. 3. 3.]
[3. 3. 3.]]
数组加法
[[2. 2. 2.]
[2. 2. 2.]
[2. 2. 2.]]
数组减法
[[-1. -1. -1.]
[-1. -1. -1.]
[-1. -1. -1.]]
数组乘法
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
数组除法
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
数组幂
[ 0 1 8 81]
1.4.3 矩阵的操作
矩阵的性质
python
a = np.ones((3,3))
b = np.zeros((3,3))
c = np.eye(3)
d = np.arange(0,9).reshape((3,3))
e = np.array([[1, 2], [3, 4]])
python
print(d)
print("矩阵的转置")
print(np.transpose(d))
print("矩阵的行列式")
print(np.linalg.det(c))
print("矩阵的逆")
print(np.linalg.inv(e))
print("特征值分解")
e_vals,e_vecs = np.linalg.eig(e)
print("特征值", e_vals)
print("特征向量\n", e_vecs)
print("奇异值分解")
u,s,vh= np.linalg.svd(e)
print("奇异值", s)
[[0 1 2]
[3 4 5]
[6 7 8]]
矩阵的转置
[[0 3 6]
[1 4 7]
[2 5 8]]
矩阵的行列式
1.0
矩阵的逆
[[-2. 1. ]
[ 1.5 -0.5]]
特征值分解
特征值 [-0.37228132 5.37228132]
特征向量
[[-0.82456484 -0.41597356]
[ 0.56576746 -0.90937671]]
奇异值分解
奇异值 [5.4649857 0.36596619]
矩阵内积
python
a = np.ones((3,3))
b = np.random.rand(3,2)
print("矩阵内积")
print(np.dot(a,b)) # 可以使用np.dot来计算两个矩阵的内积
print(a @ b) # 也可以使用@符号来计算两个矩阵的内积
矩阵内积
[[2.61394649 1.26702742]
[2.61394649 1.26702742]
[2.61394649 1.26702742]]
[[2.61394649 1.26702742]
[2.61394649 1.26702742]
[2.61394649 1.26702742]]