前言
对于数组的操作大致分为2大类:
- 单一数组操作
- 多个数组共同操作
其中,单一数组操作可分为:
- 修改数组形状
- 翻转数组
- 修改数组维度
- 数组元素的添加与删除
多个数组共同操作可分为:
- 数组组合
- 数组拆分
1.修改数组形状
修改数组形状相关的方法有如下四种:
函数 | 描述 |
reshape | 不改变数据的条件下修改形状 |
flat | 数组元素迭代器 |
flatten | 返回一份数组拷贝,对拷贝所做的修改不会影响原始数组 |
ravel | 返回展开数组 |
1)reshape方法
numpy.reshape 函数可以在不改变数据的条件下修改形状,格式如下:
a:要修改形状的数组
newshape:整数或者整数数组,新的形状应当兼容原有形状
order:‘C’ – 按照行顺序,‘F’ – 按照列顺序,‘A’ – 按照数据在内存中存储的顺序
【例1-1】reshape方法实例
>>> a = np.arange(8)
>>> print(a,a.shape)
[0 1 2 3 4 5 6 7] (8,)
>>> b = np.reshape(a,[2,4],'C')
>>> print(b,b.shape)
[[0 1 2 3]
[4 5 6 7]] (2, 4)
>>> b = np.reshape(a,[2,4],'F')
>>> print(b,b.shape)
[[0 2 4 6]
[1 3 5 7]] (2, 4)
>>> b = np.reshape(a,[2,4],'A')
>>> print(b,b.shape)
[[0 1 2 3]
[4 5 6 7]] (2, 4)
>>> b[0][0] = 100
>>> print(a)
[100 1 2 3 4 5 6 7]
我们可以看到改变b的数据,a是跟随变动的。按行和按列排序比较好理解,但是对于‘A’要结合Fortran,C 数组内存分布来看,有兴趣的可以自行去了解,这里不展开讨论。
2)flat方法
【例1-2】flat方法实例
>>> a = np.arange(5)
>>> b = a.flat
>>> print(b,type(b))
<numpy.flatiter object at 0x7fe1033cb200> <class 'numpy.flatiter'>
>>> for x in a.flat:
>>> print(x)
0
1
2
3
4
>>> a = np.random.randint(0,10,(3,3))
>>> print(a)
>>> for x in a.flat:
>>> print(x)
[[7 1 6]
[4 9 2]
[0 7 7]]
7
1
6
4
9
2
0
7
7
flat方法比较好理解,可以用于迭代数组,对于多维数组来说也是一样的,默认按行遍历数组。
3)flatten方法
numpy.ndarray.flatten 返回一份数组展开后的拷贝,对拷贝所做的修改不会影响原始数组。
其中order与上面reshape中的order参数作用是相同的,不加赘述。
【例1-3】flatten方法实例
>>> a = np.random.randint(0,10,(3,4))
>>> print(a)
[[4 3 1 9]
[1 4 5 3]
[7 5 4 6]]
>>> b = np.flatten(a)
>>> print(b)
[4 3 1 9 1 4 5 3 7 5 4 6]
>>> b[0]=100
>>> print(a)
[[4 3 1 9]
[1 4 5 3]
[7 5 4 6]]
可以看到flatten()
确实可以按行展开数组,并且对展开后的数据进行修改不会影响原来的数组。
4)ravel方法
numpy.ravel() 展平的数组元素,返回的是数组视图(view),在第6章视图的部分我们详细讲过,修改会影响原始数组。其中order与上面reshape中的order参数作用是相同的,不加赘述。
【例1-4】ravel方法实例
>>> a = np.random.randint(0,10,(3,4))
>>> print(a)
[[4 4 6 3]
[9 7 5 7]
[2 0 7 9]]
>>> b = np.ravel(a)
>>> print(b)
[4 4 6 3 9 7 5 7 2 0 7 9]
>>> b[0]=100
>>> print(a)
[[100 4 6 3]
[ 9 7 5 7]
[ 2 0 7 9]]
我们可以看到,修改数组b确实可以影响原始数组a。
2.翻转数组
函数 | 描述 |
transpose | 对换数组的维度 |
T | 和 self.transpose() 相同 |
rollaxis | 向后滚动指定的轴 |
swapaxes | 对换数组的两个轴 |
1)transpose方法
axes是可选参数,我们可以用来翻转指定的数据,让我们来举例说明transpose()
的普通用法和加参用法:
【例2-1】transpose方法实例
>>> a = np.random.randint(0,10,(3,3))
>>> print(a)
[[4 6 1]
[7 2 4]
[7 4 7]]
>>> b = np.transpose(a)
>>> print(b)
[[4 7 7]
[6 2 4]
[1 4 7]]
>>> c = np.random.randint(0,10,(3,2,1))
>>> d = np.transpose(c,(1,0,2))
>>> print(d.shape)
(2, 3, 1)
从a到b就是transpose的普通用法,从c到d是加参用法。np.transpose(c,(1,0,2))
的含义是让第一维度和第二维度互相交换,而第三维度保持不变。
2)T方法
ndarray.T
与ndarray.transpose
类似,不同的是ndarray.T
并没有形参,举例如下:
【例2-2】T方法实例
>>> a = np.random.randint(0,10,(3,3))
>>> print(a)
[[9 1 2]
[9 2 4]
[4 4 0]]
>>> b=a.T
>>> print(b)
[[9 9 4]
[1 2 4]
[2 4 0]]
3)rollaxis方法
numpy.rollaxis 函数向后滚动特定的轴到一个特定位置,第二个参数是要滚动的轴,第三个参数是被滚动的轴索引,默认是0。
【例2-3】rollaxis方法实例
>>> a = np.random.randint(0,10,(3,4,5,6))
>>> print (a.shape)
(3, 4, 5, 6)
>>> b = np.rollaxis(a,2,0)
>>> print (b.shape)
(5, 3, 4, 6)
4)swapaxes方法
numpy.swapaxes 函数用于交换数组的两个轴,第二个参数和第三个参数分别是交换双方的索引。
【例2-4】swapaxes方法实例
>>> a = np.random.randint(0,10,(3,4,5))
>>> print (a.shape)
(3, 4, 5)
>>> b = np.swapaxes(a,0,1)
>>> print (b.shape)
(4, 3, 5)
3.修改数组维度
1)用ndarray.newaxis增加数组维度
这个我们在第一章常数的最后一节讲过,ndarray.newaxis == None
,可以用来增加数组维度:
【例3-1】用ndarray.newaxis增加数组维度
>>> a = np.random.randint(0,10,(6))
>>> print(a,a.shape)
[7 5 2 5 5 7]
>>> b = a[np.newaxis,...]
>>> print(b,b.shape)
[[7 5 2 5 5 7]] (1, 6)
>>> b = a[...,np.newaxis]
>>> print(b,b.shape)
[[7]
[5]
[2]
[5]
[5]
[7]] (6, 1)
2)用squeeze方法减少数组维度
numpy.squeeze 函数从给定数组的形状中删除一维的条目,注意所要删去的维度长度必须是1,不然会报错,原因是因为如果长度不等于1,那么squeeze会使其他维度的结构被破坏【TODO】
【例3-2】squeeze方法实例
>>> a = np.random.randint(0,10,(1,5,1,2,1))
>>> b = np.squeeze(a)
>>> print(b.shape)
(5, 2)
>>> b = np.squeeze(a,0)
>>> print(b.shape)
(5, 1, 2, 1)
>>> b = np.squeeze(a,2)
>>> print(b.shape)
(1, 5, 2, 1)
>>> b = np.squeeze(a,4)
>>> print(b.shape)
(1, 5, 1, 2)
>>> b = np.squeeze(a,1)
>>> print(b.shape)
ValueError: cannot select an axis to squeeze out which has size not equal to one
注,np.squeeze(a)
表示去除a中所有维度为1的维度,感觉跟压缩无用信息有点类似。
4.数组元素的添加与删除
1)用insert添加数组元素
【例4-1】insert方法实例
>>> a = np.array([[1, 1], [2, 2], [3, 3]])
>>> print(a)
[[1 1]
[2 2]
[3 3]]
>>> b = np.insert(a, 2, 6)
>>> print(b)
[1 1 6 2 2 3 3]
>>> b = np.insert(a, 2, 6, axis=1)
>>> print(b)
[[1 1 6]
[2 2 6]
[3 3 6]]
可以看到,如果不指定axis的值,那么原始数组会被扁平化。
2)用delete删除数组元素
【例4-2】delete方法实例
>>> a = np.random.randint(0,10,(3,3))
>>> print(a)
[[7 1 8]
[7 2 6]
[8 3 1]]
>>> b = np.delete(a, 1, axis=0)
>>> print(b)
[[7 1 8]
[8 3 1]]
>>> b = np.delete(a,[1])
>>> print(b)
[7 8 7 2 6 8 3 1]
可以看到,同insert
方法一样,如果不指定axis的值,那么原始数组会被扁平化。
5.数组组合
函数 | 描述 |
concatenate | 连接沿现有轴的数组序列 |
stack | 沿着新的轴加入一系列数组。 |
hstack | 水平堆叠序列中的数组(列方向) |
vstack | 竖直堆叠序列中的数组(行方向) |
1)concatenate方法
【例5-1】concatenate方法实例
#一维操作
>>> x = np.arange(3,6)
>>> y = np.arange(6,9)
>>> print(x.shape,y.shape)
(3,) (3,)
>>> z = np.concatenate([x,y])
>>> print(z,z.shape)
[3 4 5 6 7 8] (6,)
#二维操作
>>> x = np.random.randint(0,10,(3,3))
>>> y = np.random.randint(0,10,(3,3))
>>> print(x)
[[2 3 9]
[8 8 8]
[8 4 2]]
>>> print(y)
[[9 1 3]
[9 2 0]
[1 0 3]]
>>> z = np.concatenate([x,y])
>>> print(z,z.shape)
[[2 3 9]
[8 8 8]
[8 4 2]
[9 1 3]
[9 2 0]
[1 0 3]] (6, 3)
>>> z = np.concatenate([x,y],axis=0)
>>> print(z,z.shape)
[[2 3 9]
[8 8 8]
[8 4 2]
[9 1 3]
[9 2 0]
[1 0 3]] (6, 3)
>>> z = np.concatenate([x,y],axis=1)
>>> print(z,z.shape)
[[2 3 9 9 1 3]
[8 8 8 9 2 0]
[8 4 2 1 0 3]] (3, 6)
我们可以把concatenate
方法想做一款胶水,它在不改变拼接原材料数组的前提下,对指定的位置(axis)进行粘合。
举例:
- shape为(3,3)的两个数组沿
axis=0
粘合,结果数组shape为(6,3) - shape为(3,3)的两个数组沿
axis=1
粘合,结果数组shape为(3,6)
注:axis默认为0,若设成None,则原数组在使用前会被扁平化。
2)stack方法
【例5-2】stack方法实例
#一维操作
>>> x = np.arange(3,6)
>>> y = np.arange(6,9)
>>> print(x.shape,y.shape)
(3,) (3,)
>>> z = np.stack([x,y])
>>> print(z,z.shape)
[[3 4 5]
[6 7 8]] (2, 3)
#二维操作
>>> x = np.random.randint(0,10,(3,3))
>>> y = np.random.randint(0,10,(3,3))
>>> print(x)
[[6 6 5]
[5 3 7]
[8 3 7]]
>>> print(y)
[[4 7 1]
[7 4 7]
[9 1 3]]
>>> z = np.stack([x,y])
>>> print(z,z.shape)
[[[6 6 5]
[5 3 7]
[8 3 7]]
[[4 7 1]
[7 4 7]
[9 1 3]]] (2, 3, 3)
>>> z = np.stack([x,y],axis=0)
>>> print(z,z.shape)
[[[6 6 5]
[5 3 7]
[8 3 7]]
[[4 7 1]
[7 4 7]
[9 1 3]]] (2, 3, 3)
>>> z = np.stack([x,y],axis=1)
>>> print(z,z.shape)
[[[6 6 5]
[4 7 1]]
[[5 3 7]
[7 4 7]]
[[8 3 7]
[9 1 3]]] (3, 2, 3)
>>> z = np.stack([x,y],axis=2)
>>> print(z,z.shape)
[[[3 6]
[1 8]
[6 9]]
[[7 4]
[1 3]
[9 2]]
[[8 0]
[9 9]
[7 0]]] (3, 3, 2)
我们注意到,与concatenate
方法不同的是,stack
方法生成的数组比原始数组维度多1,且多的维度的长度等于原始数组的长度:
- 两个shape为(3,3)的原始数组,axis=0,结果数组shape为(2,3,3)
- 两个shape为(3,3)的原始数组,axis=1,结果数组shape为(3,2,3)
- 两个shape为(3,3)的原始数组,axis=2,结果数组shape为(3,3,2)
且位置正是axis。
注:axis默认为0,且只能为不超过原始数组长度的整数:
>>> z = np.stack([x,y],None)
>>> print(z,z.shape)
TypeError: an integer is required (got type NoneType)
3)hstack方法
numpy.hstack
是 numpy.stack
函数的变体,它通过水平堆叠来生成数组。
【例5-3】hstack方法实例
#一维操作
>>> x = np.arange(3,6)
>>> y = np.arange(6,9)
>>> print(x.shape,y.shape)
(3,) (3,)
>>> z = np.hstack([x,y])
>>> print(z,z.shape)
[3 4 5 6 7 8] (6,)
#二维操作
>>> x = np.random.randint(0,10,(3,3))
>>> y = np.random.randint(0,10,(3,3))
>>> print(x)
[[8 7 7]
[5 7 5]
[8 0 0]]
>>> print(y)
[[3 2 8]
[0 0 7]
[6 3 4]]
>>> z = np.hstack([x,y])
>>> print(z,z.shape)
[[8 7 7 3 2 8]
[5 7 5 0 0 7]
[8 0 0 6 3 4]] (3, 6)
以2个2维(3,3)数组为例,尝试带大家理解一下hstack
的合并原理:
第一、二维就是原始数组的第一、二维,其中第橙色、蓝色分别代表x和y,由于采用水平堆叠,所以x,y水平排列在一起。那么显而易见,结果数组shape为(3,6)。
在这里,我们无需计较后面可能存在的更高维数据,因为它们都可以被包裹进前面的数据中,可以对用户保持透明。
注:hstack并不可以指定axis,并且生成的结果数组的维度与之前保持一致。
4)vstack方法
numpy.vstack
是 numpy.stack
函数的变体,它通过垂直堆叠来生成数组。
【例5-4】vstack方法实例
#一维操作
>>> x = np.arange(3,6)
>>> y = np.arange(6,9)
>>> print(x.shape,y.shape)
(3,) (3,)
>>> z = np.vstack([x,y])
>>> print(z,z.shape)
[[3 4 5]
[6 7 8]] (2, 3)
#二维操作
>>> x = np.random.randint(0,10,(3,3))
>>> y = np.random.randint(0,10,(3,3))
>>> print(x)
[[6 5 2]
[4 1 4]
[2 1 4]]
>>> print(y)
[[7 4 8]
[8 2 0]
[2 5 0]]
>>> z = np.vstack([x,y])
>>> print(z,z.shape)
[[6 5 2]
[4 1 4]
[2 1 4]
[7 4 8]
[8 2 0]
[2 5 0]] (6, 3)
与hstack
的合并原理类似,vstack
合并原理的示意图如下:
与hstack
不同的是数据之间的堆叠方式,其他一切保持一致。
6.数组拆分
函数 | 描述 |
split | 将一个数组分割为多个子数组 |
hsplit | 将一个数组水平分割为多个子数组(按列) |
vsplit | 将一个数组垂直分割为多个子数组(按行) |
1)split方法
【例6-1】split方法实例
>>> x = np.random.randint(0,10,(3,4))
>>> print(x)
[[7 7 9 4]
[1 3 7 1]
[2 8 3 1]]
>>> y = np.split(x,[1,3])
>>> print(y,type(y),len(y))
[array([[7, 7, 9, 4]]), array([[1, 3, 7, 1],
[2, 8, 3, 1]]), array([], shape=(0, 4), dtype=int64)] <class 'list'> 3
>>> y = np.split(x,[1,2])
>>> print(y,type(y),len(y))
[array([[7, 7, 9, 4]]), array([[1, 3, 7, 1]]), array([[2, 8, 3, 1]])] <class 'list'> 3
又到了我们熟悉的切蛋糕环节,np.split(x,[1,3])
里面的[1,3]
代表着下刀的位置,注意此处的整数指索引,而落刀位置在索引前,如下图:
其中索引3是我们虚拟出来的,另外结果y是一个List型的数据类型,它的长度是3,等于[1,3]长度+1,这更加贴近实际,也就是蛋糕不交叉地切2刀,会出现三块蛋糕,如果最后一刀在原始数组尾,那么会补一个同样shape的空ndarray类型。
注:axis默认为0,代表沿着第一维度移动“刀”。
2)vsplit方法
vsplit
方法顾名思义,是垂直切的意思,但是这个水平是指“刀”移动的方向,不要误以为是刀切的方向!我们做一下对比:
【例6-2】vsplit方法实例
>>> x = np.random.randint(0,10,(3,4))
>>> print(x)
[[5 9 5 6]
[2 3 0 0]
[5 0 1 3]]
>>> y = np.split(x,[1,3])
>>> print(y,type(y),len(y))
[array([[5, 9, 5, 6]]), array([[2, 3, 0, 0],
[5, 0, 1, 3]]), array([], shape=(0, 4), dtype=int64)] <class 'list'> 3
>>> y = np.split(x,[1,2])
>>> print(y,type(y),len(y))
[array([[5, 9, 5, 6]]), array([[2, 3, 0, 0]]), array([[5, 0, 1, 3]])] <class 'list'> 3
>>> y = np.vsplit(x,[1,2])
>>> print(y,type(y),len(y))
[array([[5, 9, 5, 6]]), array([[2, 3, 0, 0]]), array([[5, 0, 1, 3]])] <class 'list'> 3
我们可以看到vsplit
的刀确实是水平切的,而且其与np.split(x,[1,3])
结果相同。
3)hsplit方法
【例6-3】hsplit方法实例
x = np.random.randint(0,10,(3,4))
print(x)
[[2 7 2 4]
[8 8 5 0]
[6 0 8 4]]
y = np.split(x,[1,3],axis=1)
print(y,type(y),len(y))
[array([[2],
[8],
[6]]), array([[7, 2],
[8, 5],
[0, 8]]), array([[4],
[0],
[4]])] <class 'list'> 3
y = np.hsplit(x,[1,3])
print(y,type(y),len(y))
[array([[2],
[8],
[6]]), array([[7, 2],
[8, 5],
[0, 8]]), array([[4],
[0],
[4]])] <class 'list'> 3
切割示意图如下: