前言
数组在Python编程中非常广泛,其中涉及到子维数据提取、提取后维度保留、各维度向量调序、多维数据操作、广播、…,让初学者眼花缭乱,可以说是学习python的一个坎。本人想把数组涉及到的相关常用点,简单通俗地一 一展开,希望能对大家有所帮助。
一、先从数组下标去理解数组,因为通过数组下标能实现数组操作的所有功能
数组相关的概念很多,让人应接不暇。但其实我们只需要掌握1点,怎么灵活用数组下标取需要的数组子集,然后数组的所有概念和功能函数,我们自己都能通过数组下标来实现。
在这里,我对数组下标的理解是对数组的每1个元素进行唯一的位置编号,然后通过位置编号就能取出对应的组元素。
具体来说就是1个数组,它的shape ( s 0 , s 1 , . . . , s n − 1 ) (s_{0},s_{1},...,s_{n-1}) (s0,s1,...,sn−1)里有n个数字,我们就称这数组为n维数组, 然后数组下标 [ i 0 , i 1 , . . . , i n − 1 ] [i_{0},i_{1},...,i_{n-1}] [i0,i1,...,in−1]里也有n个数字编号,这n个数字编号组合代表了某一数组元素位置。
如:
a = np.arange(2*3*4).reshape(2,3,4)
print(a)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
'''因为np.where(condition)只带1个参数时,能获得数组中满足条件的元素的索引,即数组下标,
所以我们用a==a,让数组每个元素都满足条件,就能得到所有数组元素的下标'''
indexes = np.where(a==a)
'''打印所有数组元素索引,索引indexes有3个子索引列表,第0个子索引列表代表数组下标[i0,,]第0位置的索引,
第1个子索引列表代表数组下标[,i1,]第1位置的索引,
第2个子索引列表代表数组下标[,,i2]第2位置的索引.
'''
print(indexes)
(array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], dtype=int64),
array([0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2], dtype=int64),
array([0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3], dtype=int64))
'''通过获取到indexes,打印indexes代表的数组元素'''
print(a[indexes])
'''因为indexes里的3个子索引是以1维形式表现,3个子索引在内部会过广播匹配,最终生成的数组下标
仍是1维, 所以通过生成的数组下标提取的元素会按数组下标的1维形式存放'''
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
'''如果想把上面3个子索引列表变换成我们好理解的数组下标,我们可以通过zip让各子索引列表
竖向展开,再按顺序把各子索引列表的对应元素按位置匹配组合,生成单独的数组下标'''
normal_indexes = list(zip(*indexes))
print(normal_indexes)
[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 1, 3),
(0, 2, 0), (0, 2, 1), (0, 2, 2), (0, 2, 3), (1, 0, 0), (1, 0, 1), (1, 0, 2), (1, 0, 3),
(1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 0), (1, 2, 1), (1, 2, 2), (1, 2, 3)]
'''在python中不能把各个数组元素独立数组下标放到1个列表里去取数组元素,这是因为下面normal_indexes
是list时,这个list占据的数组下标[]里的第0位置,所以list里的所有数字都是第0位置的索引,比如
normal_indexes里的(0,0,2)这3个数字都是第0位置的索引,而第0位置索引最大为1,所以报错'''
print(a[normal_indexes])
print(a[normal_indexes])
~^^^^^^^^^^^^^^^^
IndexError: index 2 is out of bounds for axis 0 with size 2
上面print(indexes)打印出的索引,可能会让熟悉c++等常规编程的同学感觉不适应,比如c++的数组下标各对应元素不会揉在一起。下面我们用例子,来讲解python的这种用法:
a = np.arange(2*3*4).reshape(2,3,4)
'''在c++中我们只能独立数组下标分别去取出数组元素的9、19这2个值.'''
print(a[0,2,1])
9
print(a[1,1,3])
19
'''在python中,我们可以把要取的所有数组元素下标,按下标各元素位置一次性打包到数组下标里,
从而一次获取多个要取的数组元素'''
print(a[[0,1],[2,1],[1,3]])
[ 9 19]
'''python是将数组下标[,,]里各索引编号集合竖向展开,再横向配队获取数组元素下标。有了这个特性,
python内部会用索引广播,用更灵活的方式去生成数组下标'''
数组a下标第0位置 数组a下标第1位置 数组a下标第2位置 组合后的数组a元素下标
0 2 1 0,2,1
1 1 3 1,1,3
二、数组下标最终都是通过索引广播生成
在python中,在数组下标[]的索引位置可以用slice ':',来取数组元素,比如:
a = np.arange(2*3*4).reshape(2,3,4)
print(a)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
print(a[0:2,0:3,1])
[[ 1 5 9]
[13 17 21]]
因为上面的a[0:2, 0:3, 1]中的0:2代表的是0、1, 0:3代表的是0、1、2。那我们可不可以像一中那样直接把0、1作为列表放到第0子索引位置上,0、1、2作为列表放到第1子索引位置上,从而像print(a[0:2,0:3,1])那样取出数组元素。
a = np.arange(2*3*4).reshape(2,3,4)
print(a[[0,1], [0,1,2], 1])
print(a[[0,1], [0,1,2], 1])
~^^^^^^^^^^^^^^^^^^^
IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (2,) (3,)
结果上面报索引列表广播shapes (2,) (3,) 不匹配。我们可以稍微改下,使索引列表广播能匹配
a = np.arange(2*3*4).reshape(2,3,4)
print(a[ np.array([0,1]).reshape(2,1), [0,1,2], 1])
[[ 1 5 9]
[13 17 21]]
为什么上面改成print(a[ np.array([0,1]).reshape(2,1), [0,1,2], 1])就可以,秘密就在python数组内部会对各索引子列表进行广播。如
'''在np.broadcast_arrays中,传进来的列表、元组、标量都会在内部转成数组,好方便执行广播功能'''
broadcast_indexes = np.broadcast_arrays(np.array([0,1]).reshape(2,1), [0,1,2], 1 )
print(broadcast_indexes)
[array([[0, 0, 0],
[1, 1, 1]]),
array([[0, 1, 2],
[0, 1, 2]]),
array([[1, 1, 1],
[1, 1, 1]])]
'''将广播后得到的索引列表通过*解包,这样传进[]里就不是一个list参数,而是3个参数,分别对应list
里的3个子索引列表'''
print(a[*broadcast_indexes])
[[ 1 5 9]
[13 17 21]]
最后提下,数组下标里的 : 、 i 0 : i 1 、 i 0 : i 1 : i 2 、 . . . :、i_{0}:i_{1}、i_{0}:i_{1}:i_{2}、... :、i0:i1、i0:i1:i2、...都是通过索引广播实现,由于一次不宜涉及太多,将放在另一篇讲述。
三、调换数组下标各子索引列表顺序的应用
上面讲了那么多,都是数组的常规应用,现在我们通过下面把图片格式的(h,w,c)数组转成(c,h,w)数组的应用来加深数组下标的理解。
'''pic为一张图片的原始格式数组,图片大小为2行4列像素,每个像素是rgb值'''
pic = np.arange(2*4*3).reshape(2,4,3)
'''为使pic数组数据更清晰,我们设每个像素的r值为1,g值为2,b值为3'''
pic[:,:,0] = 1
pic[:,:,1] = 2
pic[:,:,2] = 3
print(pic.shape)
pic.shape:(2, 4, 3)
print(pic)
[[[1 2 3]
[1 2 3]
[1 2 3]
[1 2 3]]
[[1 2 3]
[1 2 3]
[1 2 3]
[1 2 3]]]
'''获取图片数组pic的行数、列数、单个像素由几个值组成'''
h,w,c = pic.shape
'''生成图片通道格式数组'''
channels = np.zeros(c*h*w).reshape(c,h,w)
'''pic数组每个元素的下标[i0,i1,i2]与channels数组每个元素下标[i2,i0,i1]相对应'''
for i in range(c):
for y in range(h):
for x in range(w):
channels[i,y,x] = pic[y,x,i]
print(channels.shape)
(3, 2, 4)
print(channels)
[[[1 1 1 1]
[1 1 1 1]]
[[2 2 2 2]
[2 2 2 2]]
[[3 3 3 3]
[3 3 3 3]]]
上面把图片格式的(h,w,c)转成通道格式的(c,h,w),可以直接用np.transpose()就能实现。如:
pic = np.arange(2*4*3).reshape(2,4,3)
'''为使pic数组数据更清晰,我们设每个像素的r值为1,g值为2,b值为3'''
pic[:,:,0] = 1
pic[:,:,1] = 2
pic[:,:,2] = 3
'''下面np.transpose()的axes= (2,0,1)里的2代表原数组第2索引列表,现在2在()中位于第0位置,
所以原数组第2索引列表被调到了新数组第0索引位置上'''
channels = np.transpose(pic, axes= (2,0,1))
print(channels)
[[[1 1 1 1]
[1 1 1 1]]
[[2 2 2 2]
[2 2 2 2]]
[[3 3 3 3]
[3 3 3 3]]]
后续
今天以数组下标的角度讲解python数组,下一篇以数组向量的方式讲解python怎么提取数组某维向量。
1318

被折叠的 条评论
为什么被折叠?



