数据分析 -- NumPy②

网友投稿 233 2022-09-17

数据分析 -- NumPy②

目录

​​多维数组及其创建​​​​多维数组的属性​​​​二维数组的加减乘除​​

​​广播规则​​

​​可以正常进行的广播​​​​不能正常进行的广播​​

​​二维数组的通用方法​​​​二维数组的索引和分片​​​​布尔索引​​​​NumPy中的使用方法​​

​​arange() 方法​​​​随机方法​​​​genfromtxt() 方法​​

​​用NumPy计算均方误差​​​​易错点反思①​​

多维数组及其创建

一维数组只有行,二维数组相比一维数组多了列这个维度,而三维数组则类似多个二维数组堆叠在一起,形如一个立方体。理论上可以往更多的维度延伸,但最常用的还是二维数组。

还是和列表进行类别,二维数组相当于单层的嵌套列表。并且我们可以将单层嵌套列表传入 np.array() 方法创建一个二维数组。

# 单层嵌套列表nested_list = [[1, 2], [3, 4]]print(nested_list)# 输出:[[1, 2], [3, 4]]# 二维数组data = np.array(nested_list)print(data)# 输出:# [[1 2]# [3 4]]

嵌套列表就是列表中的元素也是列表的列表。可以看到,通过嵌套列表创建的二维数组也是用空格分隔的,并且分成了两行。列表中的第一个元素 [1, 2] 在第一行,第二个元素 [3, 4] 在第二行(也就是说一个列表元素占一行)。

同样,上一关学过的 ones() 和 zeros() 方法同样也能快速创建元素全为 1 和 0 的二维数组。与之前的区别在于,创建二维数组要传入一个包含行和列信息的元组。比如:np.ones((m, n)) 表示创建一个 m 行 n 列且元素全为 1 的二维数组。

ones = np.ones((3, 2))print(ones)# 输出:# [[1. 1.]# [1. 1.]# [1. 1.]]zeros = np.zeros((3, 2))print(zeros)# 输出:# [[0. 0.]# [0. 0.]# [0. 0.]]

更多维的数组的创建,只要传入嵌套层数更多的列表即可。创建三维数组的方式如下:

同理,ones() 方法和 zeros() 方法也是如此。创建三维数组只需传入一个长度为 3 的元组,分别指定了每个维度上的元素个数。

多维数组的属性

​​ndim​​:多维数组维度的个数。例如:二维数组的 ndim 为 2;​​shape​​:多维数组的形状。它是一个元组,每个元素分别表示每个维度中数组的长度。对于 m 行和 n 列的的数组,它的 shape 将是 (m, n)。因此,shape 元组的长度(元素个数)就是 ndim 的值;​​size​​:多维数组中所有元素的个数。shape 元组中每个元素的乘积就是 size 的值;​​dtype​​:多维数组中元素的类型。

例如:

data = np.array([[1, 2, 3], [4, 5, 6]])print('ndim:', data.ndim)print('shape:', data.shape)print('size:', data.size)print('dtype:', data.dtype)# 输出:# ndim: 2# shape: (2, 3)# size: 6# dtype: int64

小贴士:int64 是 numpy 提供的类型,表示 64 位的整数。

注意: 对于 [1 2 3] 这样的一维数组,它的 shape 是 (3,),表示有三个元素。在元组中只有一个元素时,元素后面的逗号是不能省略的,否则会被认为是加了括号的 3,而不是元组。

二维数组的加减乘除

二维数组的加减乘除与一维数组差不多

二维数组和数学中的 ​​矩阵​​ 很相似,常被用于进行矩阵间的运算。但二维数组间直接用 * 进行计算的方式和矩阵乘法计算的方式并不相同,应该用 @ 符号进行矩阵间的乘法计算。

广播规则

维度相同的数组间可以计算(条件是形状要相同)。那么维度不相同的呢?其实也是可以的。 这得益于 numpy 中的 ​​​广播规则​​​,我们知道它是指较小维度的数组在较大维度的数组上进行”广播“,以便它们具有兼容的形状。 当运算中的 2 个数组的形状不同时,numpy 将自动触发广播机制,它具体的规则是:在较小维度数组的 shape 元组前补 1,直到两个数组的 shape 元组长度相同。接着将元素在值为 1 的维度上进行复制,直到两个数组的形状相同。 如果无法使两个数组的形状相同,则会抛出 ValueError: operands could not be broadcast together 的异常。

可以正常进行的广播

例如:

data = np.array([[1, 2], [3, 4], [5, 6]])ones = np.ones(2)print(data.shape)print(ones.shape)# 输出:# (3, 2)# (2,)

data 的形状是 (3, 2),ones 的形状是 (2,)。根据规则在较小维度的 shape 元组前补 1,直到和较大维度的 shape 元组长度相同,补 1 后 ones 的形状是 (1, 2)。接着将元素在值为 1 的维度上进行复制,最后 ones 的形状变成了 (3, 2),和 data 的形状相同,因此可以进行计算。

浅色方块为复制出来的元素

不能正常进行的广播

例如:

data = np.array([[1, 2], [3, 4], [5, 6]])ones = np.ones(3)print(data.shape)print(ones.shape)# 输出:# (3, 2)# (3,)

当 ones 的形状是 (3,) 时,补 1 后变成 (1, 3),最后变成 (3, 3)。形状与 data 不相同,无法进行计算。如果尝试执行 data + ones,会产出如下的报错:

ValueError: operands could not be broadcast together with shapes (3,2) (3,)

二维数组的通用方法

二维数组的通用方法和一维数组的通用方法的基本用法类似,只是多了一个维度的数据而已。

因为二维数组多了个维度,所以它的通用方法可以更加的灵活。不仅可以对所有数据进行计算,还可以针对某个维度上的数据进行计算。

这里就要引入一个概念——轴(axis)。轴和维度的概念是类似的,一维数组有 1 个轴,二维数组有 2 个轴,三维数组有 3 个轴等等。

在 numpy 中,我们可以用 axis 参数来指定轴,从 0 开始依次增加递增的数分别对应着不同的轴。

在一维数组中,axis=0 就代表着它唯一的轴;二维数组中 axis=0 和 axis=1 代表其中的行轴和列轴;在三维数组中,axis=0、axis=1 和 axis=2 分别代表对应的三条轴。下图清晰的展示了 axis 和对应轴的关系:

在一维数组中因为只有一个轴,一般用不到 axis。需要注意的是,在二维数组中 axis=0 的轴是向下的,和一维数组中有所不同,千万不要混淆。 其实按照列表中的一个元素相当于数组中的一行,我们可以把一维数组就想想成为一个竖着的,这样就统一了axis = 0,都代表行轴,向下指。

在通用方法中,通过 axis 参数可以指定计算方向。以二维数组中的 max() 方法为例,指定 axis=0 将会在行轴方向求最大值,指定 axis=1 将会在列轴方向求最大值。

data = np.array([[1, 2], [5, 3], [4, 6]])# 不指定 axisprint(data.max())# 输出:6# axis=0print(data.max(axis=0))# 输出:[5 6]# axis=1print(data.max(axis=1))# 输出:[2 5 6]

二维数组的索引和分片

二维数组的索引和分片同样和一维数组类似,只是在行索引的基础上再加上列索引。形如 data[m, n],其中 data 是二维数组,m 是行索引或分片,n 是列索引或分片。

那么,data[0, 1] 就表示获取 data 中第一行第二列的元素。如果省略第二个参数 n 的话表示获取所有列,data[0] 就表示获取整个第一行,相当于 data[0, :]。

如果想要获取第一列则可以写成 data[:,0];如果想获取 2、3 两行可以写成 data[1:3],相当于 data[1:3, :]。你可以将索引和分片结合起来,以获取二维数组中你想要的任意数据。(此处可以用指针去理解,m告诉我们指针指向哪两行,而n告诉我们具体的列数)

例如:

data = np.array([[1, 2], [3, 4], [5, 6]])print(data[0, 1])# 输出:2print(data[:, 0])# 输出:[1 3 5]print(data[1:3])# 输出:# [[3 4]# [5 6]]

可以看到,在二维数组中,当行和列都是索引时,结果是具体的元素;当行和列中一个是索引,一个是分片时,结果是一维列表;当行和列都是分片时,结果为二维数组。

例如,data[0:2, 0] 和 data[0:2, 0:1] 获取的都是 1 和 3 这两个元素,但其结果一个是 [1 3],一个是 [[1] [3]],实际上并不相同。

以上的都是numpy的基本索引,numpy 中的高级索引分为:

布尔索引花式索引

布尔索引

布尔索引,顾名思义就是用布尔值作为索引去获取需要的元素。 首先我们要明确一个事,numpy在进行数值运算时(也就是加减乘除)会将多维数组中的每个元素进行计算,在进行逻辑运算时也会这样(也就是大于,等于,小于,不等于·····) 例如:

data = np.array([[1, 2], [3, 4], [5, 6]])print(data > 3)#结果是:#[[False False]# [False True]# [ True True]]

大于 3 的元素位置值为 True,小于等于 3 的元素位置值为 False。而这个布尔类型的数组就是布尔索引,通过它可以筛选出值为 True 位置的元素(既然是索引那就要用xx[ ]的形式来进行元素的筛选)。因此,获取数组中所有大于 3 的元素的代码可以这样写:

ata = np.array([[1, 2], [3, 4], [5, 6]])print(data[data > 3])# 输出:[4 5 6]

注意:如果有多个布尔表达式,and 改用 &,or 改用 |,not 改用 ~,并且每个条件要用括号括起来, == 和 != 不变也可用。

例如:

data = np.array([[1, 2], [3, 4], [5, 6]])# 大于 3 或者小于 2print(data[(data > 3) | (data < 2)])# 输出:[1 4 5 6]# 大于 3 或者不小于 2(即大于等于 2)print(data[(data > 3) | ~(data < 2)])# 输出:[2 3 4 5 6]data = np.array([[1, 2], [3, 4], [5, 6]])# 等于 3print(data[data == 3])# 输出:[3]# 不等于 3print(data[data != 3])# 输出:[1 2 4 5 6]

NumPy中的使用方法

arange() 方法

numpy 中的 arange() 方法和 Python 中的 range() 用法类似,不同之处在于 arange() 方法生成的是数组,而 range() 方法生成的是 range 类型的序列。 例如:

# 生成 1-9 的数组print(np.arange(1, 10))# 输出:[1 2 3 4 5 6 7 8 9]# 生成 0-9 的数组print(np.arange(10))# 输出:[0 1 2 3 4 5 6 7 8 9]# 生成 1-9 的数组,步长为 2print(np.arange(1, 10, 2))# 输出:[1 3 5 7 9]

随机方法

np.random.rand(x) 生成一个随机数组(生成 [0, 1) 之间的随机小数)并且可以指定形状 x为生成的随机数组的形状(shape)

例如:

# 不传参数时print(np.random.rand())# 输出:0.1392571183916036# 传入一个参数时print(np.random.rand(3))# 输出:[0.7987698 0.52115291 0.70452156]# 传入多个参数时print(np.random.rand(2, 3))# 输出:# [[0.08539006 0.97878203 0.23976172]# [0.34301963 0.48388704 0.63304024]]

np.random.randint(m,n,shape) numpy 中的 np.random.randint() 方法和 Python 中的 random.randint() 类似,不同之处在于,random.randint(m, n) 生成的是 [m, n] 之间的整数,而 np.random.randint(m, n) 生成的是 [m, n) 之间的整数,这点一定注意要区分。

例如:

# 不传入形状时print(np.random.randint(0, 5))# 输出:3# 形状为一维数组时print(np.random.randint(0, 5, 3))# 输出:[4 0 1]# 形状为二维数组时print(np.random.randint(0, 5, (2, 3)))# 输出:# [[0 2 1]# [4 2 0]]

genfromtxt() 方法

genfromtxt() 方法用于文件的读取。我们学习 numpy 是要到实际生活中应用的,而生活中我们的数据来源通常是一个文件,例如 CSV 文件。

genfromtxt() 方法常用的参数有两个,分别是数据源和分隔符。假设我们要用 numpy 读取一个以逗号分隔的 CSV 文件,可以这样写:

data = np.genfromtxt('data.csv', delimiter=',')

第一个参数是数据源,可以是本地文件的路径,也可以是网络文件的地址。delimiter 参数用于指定分隔符,CSV 文件一般是用逗号作为分隔符,当遇到其他符号分隔的文件时,用 delimiter 参数进行指定即可。

genfromtxt() 方法的返回值是一个多维数组,这样你就可以根据前面所学的知识对其进行处理了。

用NumPy计算均方误差

numpy 在科学计算、神经网络、机器学习等领域深受欢迎。这需要我们拥有较好数学功底,配合 numpy 中数学计算的方法,可以快捷方便地完成这些领域的计算工作。

在机器学习中,​​均方误差​​常被作为模型的损失函数,用来预测和回归,它的公式如下:

​​n​​​ 是数据集的个数,​​Y_prediction​​​ 是模型预测的结果集,​​Y​​ 是实际的数据集。将预测的结果和实际的值作差后进行平方求和,最后除以数据集的个数,得到的就是均方误差。均方误差越小,说明模型预测的越准确,反之则越不准确。

np.sum() 是对数组求和的方法 np.square() 是对数组计算平方的方法

predictions 是预测结果的多维数组,labels 是实际值的多维数组:

predictions = np.array([1, 1, 1])labels = np.array([1, 2, 3])error = (1 / labels.size) * np.sum(np.square(predictions - labels))print(error)# 输出:1.6666666666666665

易错点反思①

题目:现在要求将二维数组[[1 2] [3 4]]的第一列平方,得到结果二维数组[[1 2] [9 4]]

刚开始我做的时候,我想的是将第一列切片切出来,再直接使用square()函数就可以了(因为多维数组的切片是视图形式,直接对本体进行操作) 但是出错了:

b = np.array([[1,2],[3,4]])np.square(b[:,0])print(b)#[[1 2] [3 4]]

还是没有改变,其实问题就出现在square()方法上,你只是将切片的引用传递给了形参,而这个形参在方法体内无论怎么变,都不会影响到传递给方法的实参,也就对切片没有影响,所以结果没有发生改变。

要想达到目的,可以考虑使用for循环:

import numpy as npa = np.array([[1,2],[3,4]])for i in range(a.shape[0]): a[i,0] = a[i,0] * a[i,0]print(a)#结果[[1 2] [9 4]]

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:营销最前线:恐怖的比亚迪!
下一篇:web前端笔记 -- HTML①
相关文章

 发表评论

暂时没有评论,来抢沙发吧~