4.1 多维数组对象
每一个数组都有一个shape属性,用来表征数组每一维的数量;(还是比较重要的)
对于一个二维数组来说,就是表示行数和列数;前者是行,后者是列;
import numpy as np
data = np.random.randn(2, 3) # 生成一个两行三列的随机数组
print(“data.type=”, type(data))
print(“data=”,data)
x = data.shape
print(x)
\\\\
输出:
data.type= <class ‘numpy.ndarray’>
data= [[-0.19669816 0.52483623 0.44662295]
[-0.60083788 -0.50839557 -0.6600297 ]]
(2, 3)
#要注意的是一般输入的[[1,2,3],[4,5,6]]是list,list类型没有shape命令
注意:多维数组ndarray与list是两个完全不同的数据类型!
每一个数组都有一个dtype属性,用来描述数组的数据类型:(相对而言用的会比较少)
#紧接上面的代码
print(data.dtype)
\\
输出:
float64
4.1.1 生成ndarray
array函数接收任意的序列型对象(当然也包括其他的数组),生成一个新的包含传递数据的NumPy数组。
import numpy as np
data = [[1, 2, 3], [4, 5, 6]]
arr = np.array(data) # array函数可以把list转换成NumPy数组
print(“arr=”, arr)
print(“arr.ndim=”, arr.ndim)
\\\\
输出:
arr= [[1 2 3]
[4 5 6]]
arr.ndim= 2
注意这个时候data与arr是两个不同的数据类型;属性ndim是表示np数组的维度(可以理解成行数)
除此之外还有其它生成np数组的方法,例如zeros(生成全0)的多维数组,ones(生成全1的多维数组),有点类似于Matlab!
import numpy as np
a = np.zeros(10)
b = np.zeros((4, 3)) # 多维数组里面需要输入一个元组,行数、列数(以及更高维)
c = np.ones((3, 4))
d = np.ones((2, 2, 2))
print(“a=”, a)
print(“b=”, b)
print(“c=”, c)
print(“d=”, d)
\\\\\\\
输出:
a= [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
b= [[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
c= [[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]
d= [[[1. 1.]
[1. 1.]]
[[1. 1.]
[1. 1.]]]
注意:如果没有特别说明,默认的数据类型是float64(浮点型)
其它的数组生成函数如下图所示
4.1.2 ndarray的数据类型
对于新手来说可略过
4.1.3 NumPy数组算术
任何在两个等尺寸数组之间的算术操作都应用了逐个元素操作的方式,值得注意的是两个等尺寸的数组相乘与我们通常的矩阵乘法是不一样的。例如:
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr * arr)
\\\\\\\\\
输出:
[[ 1 4 9]
[16 25 36]]
4.1.4 基础索引和切片
区别于Python的内建列表,数组的切片是原数组的视图。这意味着数据并不是被复制了,任何对于视图的修改都会反映到原数组上。因为NumPy被设计成处理非常大的数组,如果NumPy持续复制数据会消耗大量内存。
注:如果想要一份数组切片而不是一份视图的话,需要显式地复制这个数组,例如arr[5:8].copy().
对二维数组的索引,可以将0轴(竖直方向)看作“行”,将1轴(水平方向)看成列。
1、切片操作与内建列表的切片操作类似,区别在于对更高维度切片需要在中括号中进行,对每个维度的切片类似内建列表的切片,不同维度之间用逗号分开。也可以对每个维度分开切片,用多个中括号,两种效果类似;
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
x = arr[0][2]
y = arr[0, 2]
print(“x=”, x)
print(“y=”, y)
\\\\\\\\\\\\\\\
输出:
x= 3
y= 3
2、对数组切片可以省略后续的索引值,返回的对象将是一个降低一个维度的数组;
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
x = arr[0]
print(“x=”, x)
\\\\\\\\\\\\\
输出:
x= [1 2 3]
3、切片操作包含前一个索引, 不含最后一个索引,如arr[1:3],只含第1,2个元素(从0开始算);
4、单独一个冒号表示选择整个轴上的数组,例如:arr[:,1:3]表示保留第一个维度上的数组,对第二个维度上的数组选择第1,2个元素
import numpy as np
arr = np.array([[1, 2, 3, 4], [4, 5, 6, 7]])
x = arr[:, 1:3]
print(“x=”, x)
\\\\\\\\\\\\
输出:
x= [[2 3]
[5 6]]
5、对切片进行赋值会改变原有的数组
import numpy as np
arr = np.array([[1, 2, 3, 4], [4, 5, 6, 7]])
x = arr[:, 1:3]
x = 0
print(“arr=”, arr)
arr[:, 1:3] = 0
print(“arr=”, arr)
\\\\\\
输出:
arr= [[1 2 3 4]
[4 5 6 7]]
arr= [[1 0 0 4]
[4 0 0 7]]
注:之前只是让变量x指向切片,后来又让变量x指向0,所以实际并没有改变原有数组;后面是直接对切片进行了修改所以会作用到原数组中。
4.1.5 布尔索引
在对数组进行索引时我们可以通过输入一个布尔值数组来进行索引,只会返回布尔值为真的数据列。布尔值数组的长度必须和数组轴索引长度一致。
import numpy as np
arr = np.array([[1, 2, 3, 4], [4, 5, 6, 7]])
names=np.array([“Bob”,“Joe”,“Will”,“Bob”,“Will”,“Silly”])
print(names==“Bob”)#数组的比较操作是可以直接向量化的
\\\\\\\\\\\\\\\\
输出:
[ True False False True False False]#这就是一个布尔值数组
import numpy as np
arr = np.array([[1, 2, 3, 4], [4, 5, 6, 7]])
names = np.array([“Bob”, “Joe”, “Will”, “Bob”, “Will”, “Silly”])
print(names == “Bob”)
x = arr[names == “Bob”]
print(“x=”, x)
\\\\\\\\
输出:报错!
原因:输入的布尔值数组的长度为6,而数组组索引的长度为2,不一致,所以报错!
import numpy as np
arr = np.array([[1, 2, 3, 4], [4, 5, 6, 7]])
names = np.array([“Bob”, “Joe”])
print(names == “Bob”)
x = arr[names == “Bob”]
print(“x=”, x)
\\\\\\\\\\\\\
输出:
[ True False]
x= [[1 2 3 4]]
注:只返回了布尔值为True的相关数据。
可以通过逻辑运算符(~表示取反、&、|)来得到相应的布尔值从而得到想要的数据:
1、使用布尔值索引选择数据时总是生成数据的拷贝,即使返回的数组并没有任何变化,但是对索引之后的数据进行修改,会作用到原有数组;
2、Python的关键字and和or对布尔值数组并没有用,请使用&(and)和|(or)来代替。
4.1.6 神奇索引
神奇索引时NumPy中的术语,用于描述使用整数数组进行数据索引。
我们可以通过传递一个包含指明所需顺序的列表来获取一个符合特定顺序的子集。如
import numpy as np
arr = np.empty((8, 4)) # empty可以创建一个没有初始化数值的数组
for i in range(8):
arr[i] = i
print(“arr=”, arr)
x = arr[[4, 3, 0, 6]] # 获取第4行,第3行,第0行,第6行的数据
print(“x=”, x)
\\\\\\\\\\\\
输出:
arr= [[0. 0. 0. 0.]
[1. 1. 1. 1.]
[2. 2. 2. 2.]
[3. 3. 3. 3.]
[4. 4. 4. 4.]
[5. 5. 5. 5.]
[6. 6. 6. 6.]
[7. 7. 7. 7.]]
x= [[4. 4. 4. 4.]
[3. 3. 3. 3.]
[0. 0. 0. 0.]
[6. 6. 6. 6.]]
注:如果使用负的索引,将从尾部进行选择,-1表示最尾部!传递的一定是一个列表,否则就是普通的索引!甚至会报错!
另外输入也可以是一个高维列表,也就是说arr[[4, 3, 0, 6], [1, 2, 3, 1]]也是正确的,只不过返回的数据就是位于(4,1)、(3.2)、(0,3)、(6,1)等处的数据值
4.1.7 数组转置和换轴
在此之前先补充一下关于reshape方法的知识
在很多情况下,我们需要将数组从一个形状转换为另一个形状,并且不复制任何数据(即重塑数组),基于这种情况我们可以使用reshape方法,reshape方法需要传入一个元组作为参数,用来说明需要重塑后数组各个方向上维度的大小。重塑数组并不会改变原来的数组形状而是返回一个新的数组!
import numpy as np
arr = np.arange(15)
print(“arr=”, arr)
arr1 = arr.reshape((3, 5))
print(“arr=”, arr)
print(“arr1=”, arr1)
\\\\\\\\\\\\\\\\
输出:
arr= [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
arr= [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
arr1= [[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
转置是一种特殊的数据重组形式,可以返回底层数据的视图而不需要复制任何内容。数组拥有transpose方法(任何维度都适用,更多的用于高维情形),也有特殊的T属性(适用于低维1,2维)。(有点类似于我们通常理解的矩阵转置)
import numpy as np
arr = np.arange(15).reshape((3, 5))
print(“原来的数组”, arr)
print(“转置以后的数组”, arr.T)
\\\\\\\\\\\\\\
原来的数组 [[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
转置以后的数组 [[ 0 5 10]
[ 1 6 11]
[ 2 7 12]
[ 3 8 13]
[ 4 9 14]]
进行矩阵计算时,经常会进行一些特定操作,比如计算矩阵内积会使用np.dot。
对于更高维度的数组,transpose方法可以接收包含轴编号的元组,用于置换轴。
参考
transpose方法的核心是对shape元组的索引,并不会改变原来的数组,而是会返回一个新的数组。
import numpy as np
arr = np.arange(16).reshape((2, 2, 4))
print(“arr=”, arr)
print(“数组形状”, arr.shape)
\\\\\\\\\\\\\
输出:
arr= [[[ 0 1 2 3]
[ 4 5 6 7]]
[[ 8 9 10 11]
[12 13 14 15]]]
数组形状 (2, 2, 4)
分析:上面的数组是一个三维的,即有三个轴,shape数值为(2,2,4),对应的轴编号(即索引编号)是(0,1,2),例如我们想获得2,需要进行如下索引arr[0][0][2]或者arr[0,0,2]。
import numpy as np
arr = np.arange(16).reshape((2, 2, 4))
x = arr.transpose((0, 1, 2))
y = arr.transpose((1, 0, 2))
print(“arr=”, arr)
print(“x=”, x)
print(“y=”, y)
\\\\\\\\\\\\\\
输出:
arr= [[[ 0 1 2 3]
[ 4 5 6 7]]
[[ 8 9 10 11]
[12 13 14 15]]]
x= [[[ 0 1 2 3]
[ 4 5 6 7]]
[[ 8 9 10 11]
[12 13 14 15]]]
y= [[[ 0 1 2 3]
[ 8 9 10 11]]
[[ 4 5 6 7]
[12 13 14 15]]]
分析:
从整体来看:
我们发现arr并没有发生变化,说明transpose方法不会改变原有数组,另外我们发现x与arr数组实际上一模一样的,这是因为arr.transpose((0, 1, 2))中输入的参数(实际上是一个元组)就是最原始的轴顺序,而数组y发生变化,是因为transpose方法的输入参数与数组x的输入参数不一致,最原始的轴顺序是(0,1,2),而数组y的轴顺序是(1,0,2)说明第一个轴与第二个轴交换了数据,但是第三个轴没有发生变化。
从细节上来看:
之前我们获得6需要如下索引:arr[0,1,2],针对改变后的数组y,我们想获得6则需要如下索引:y[1,0,2],我们发现索引顺序正好发生了变化,这也说明了第一个轴与第二个轴发生了变化。
ndarray有一个swapaxes方法,该方法接收一对轴编号作为参数,并对轴进行调整用于重组数据。swapaxes返回的是数据的视图,而没有对数据进行复制。
import numpy as np
arr = np.arange(16).reshape((2, 2, 4))
print(“arr=”, arr)
x = arr.swapaxes(1, 2)
y = arr.swapaxes(2, 1)
print(“x=”, x)
print(“y=”, y)
\\\\\\\\\\\\\
输出:
arr= [[[ 0 1 2 3]
[ 4 5 6 7]][[ 8 9 10 11]
[12 13 14 15]]]
x= [[[ 0 4]
[ 1 5]
[ 2 6]
[ 3 7]]
[[ 8 12]
[ 9 13]
[10 14]
[11 15]]]
y= [[[ 0 4]
[ 1 5]
[ 2 6]
[ 3 7]]
[[ 8 12]
[ 9 13]
[10 14]
[11 15]]]
注:通过上面我们可以发现,arr.swapaxes(1, 2)和arr.swapaxes(2, 1)的效果是一样的,都是表示将第1轴和第2轴进行交换(0轴不变);swapaxes方法传递一对轴编号,表示要交换的两个轴,对顺序没有要求。