数据分析 -- NumPy②

举报
十八岁讨厌编程 发表于 2022/08/06 00:04:26 2022/08/06
【摘要】 目录 多维数组及其创建多维数组的属性二维数组的加减乘除广播规则可以正常进行的广播不能正常进行的广播 二维数组的通用方法二维数组的索引和分片布尔索引NumPy中的使用方法arange() ...

多维数组及其创建

一维数组只有行,二维数组相比一维数组多了列这个维度,而三维数组则类似多个二维数组堆叠在一起,形如一个立方体。理论上可以往更多的维度延伸,但最常用的还是二维数组。
在这里插入图片描述
还是和列表进行类别,二维数组相当于单层的嵌套列表。并且我们可以将单层嵌套列表传入 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
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

嵌套列表就是列表中的元素也是列表的列表。可以看到,通过嵌套列表创建的二维数组也是用空格分隔的,并且分成了两行。列表中的第一个元素 [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.]]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述
更多维的数组的创建,只要传入嵌套层数更多的列表即可。创建三维数组的方式如下:
在这里插入图片描述
同理,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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

小贴士: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,)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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,)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

当 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]])

# 不指定 axis
print(data.max())
# 输出:6

# axis=0
print(data.max(axis=0))
# 输出:[5 6]

# axis=1
print(data.max(axis=1))
# 输出:[2 5 6]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

二维数组的索引和分片

二维数组的索引和分片同样和一维数组类似,只是在行索引的基础上再加上列索引。形如 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])
# 输出:2

print(data[:, 0])
# 输出:[1 3 5]

print(data[1:3])
# 输出:
# [[3 4]
#  [5 6]]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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

例如,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]]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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

  
 
  • 1
  • 2
  • 3

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

例如:

data = np.array([[1, 2], [3, 4], [5, 6]])
# 大于 3 或者小于 2
print(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]])
# 等于 3
print(data[data == 3])
# 输出:[3]

# 不等于 3
print(data[data != 3])
# 输出:[1 2 4 5 6]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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 的数组,步长为 2
print(np.arange(1, 10, 2))
# 输出:[1 3 5 7 9]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

随机方法

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]]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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]]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

genfromtxt() 方法

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

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

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

  
 
  • 1

第一个参数是数据源,可以是本地文件的路径,也可以是网络文件的地址。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
  • 5

易错点反思①

题目:现在要求将二维数组[[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]]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

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

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

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

文章来源: blog.csdn.net,作者:十八岁讨厌编程,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/zyb18507175502/article/details/122692710

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200