046、数组索引进阶
本文最后更新于 396 天前,其中的信息可能已经过时,如有错误请发送邮件到 wuxianglongblog@163.com

Python 中的索引机制可以表示为:x[obj]。当对象 obj 是一个元组时,元组的括号可以省略,因此 x[(exp1, exp2, ..., expN)] 的索引写法与 x[exp1, exp2, ..., expN] 是等价的。在 NumPy 中,根据对象 obj 的不同,数组索引可以分成基础索引和高级索引两大类。

数组的基础索引

数组的基础索引满足以下条件:

  • 索引对象是整数
  • 索引对象是 slice 对象
  • 索引对象是一个由整数、slice 对象构成的元组
  • 索引对象还可以是 np.newaxis,或者 Python 内置的省略对象 Ellipsis
import numpy as np
a = np.arange(80).reshape(8, 10)
a
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]])

最简单的索引,使用一个 N 维整数元组,索引 N 维数组的单个元素:

a[1, 2]
12

使用切片 slice 对象,索引得到一个子数组:

a[1:9:2, 3:5]
array([[13, 14],
[33, 34],
[53, 54],
[73, 74]])

如果索引 N 维数组时,使用的元组大小小于 N,NumPy 会自动将后面缺失的维度补成:,例如,a[1:3] 相当于 a[1:3,:]

a[1:3]
array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])
a[1:3, :]
array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])

在索引时,还可以使用数字与 slice 对象进行混合使用,整数 i 的作用相当于 slice 对象 i:i+1,但是维度会被消去,即每使用一个整数,得到的数组的维度就会比 N 小 1:

a[1:2, :]
array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]])
a[1, :]
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
a[1:2, :].shape
(1, 10)
a[1, :].shape
(10,)

最后的: 还可以省略:

a[1]
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])

索引单个元素可以看成是一种特殊的混用。对于 N 维数组,如果都使用 i:i+1 的形式,最后会得到一个大小全 1 的 N 维数组;全部替换为整数 i 时,得到的数组维度要相应降低 N 维,得到 0 维数组,即普通的数字。

np.newaxis 可以在数组中插入新的维度。新维度插入后,索引对应的维度位置要相应改变:

a[1:3, np.newaxis, 1:4].shape
(2, 1, 3)

Ellipsis 对象 “...” 可以用来省略一些维度,NumPy 会根据具体的索引值,将缺少的维度自动补全:

b = a.reshape(2, 4, 2, 5)
b[..., 1].shape
(2, 4, 2)
b[Ellipsis, 1].shape
(2, 4, 2)
b[0, Ellipsis, :].shape
(4, 2, 5)

可以通过基础索引修改原来数组的值。例如,修改单个值的情况:

a
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]])
a[0, 0] = 100
a
array([[100, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[ 20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[ 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79]])

对于子数组的情况,可以使用 x[obj]=value 的形式进行赋值,只要数组 x[obj] 的形状和 value 的形状能够在数组广播机制下匹配即可。例如,将第二行修改为同一个值:

a[1] = 1
a[1]
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])

用一维数组给二维数组赋值,将第二三行变成同一个一维数组:

a[1:3] = np.arange(10)
a[1:3]
array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])

用形状为 (1,2) 的数组给第二三行赋值:

a[1:3] = np.array([[1], [2]])
a[1:3]
array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]])

基础索引返回的是原来数组的一个引用,与原来的数组共享同一块内存:

a
array([[100, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[ 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79]])

修改基础索引赋值的变量,会导致原来的数组也发生变化:

b = a[0]
b[0] = 0
a[0, 0]
0

数组的高级索引

数组支持高级索引操作,高级索引又叫花式索引(Fancy Indexing),与基础索引不同,高级索引返回的结果始终是原来数组的一个复制。

数组的高级索引需要满足:

  • 索引对象是非元组的序列。
  • 索引对象是整型或者布尔型的数组。
  • 索引对象是包含至少一个前两种类型的元素的元组。

可以使用 N 个整型数组或者列表组成的索引值,来索引 N 维数组中的任意元素。例如:

a = np.array([[1, 2], [3, 4], [5, 6]])
a
array([[1, 2],
[3, 4],
[5, 6]])

在维度 0 使用列表索引位置 [0,1,2],维度 1 使用列表索引位置 [0,1,0],最终得到数组位置在 (0,0)(1,1)(2,0) 的三个元素:

a[[0, 1, 2], [0, 1, 0]]
array([1, 4, 5])

再如,有这样一个 4×3 的数组:

b = np.arange(12).reshape(4, 3)
b
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])

使用两个数组来索引这个数组的第一、四行的第一、三列:

rows = np.array([[0, 0], [3, 3]])
cols = np.array([[0, 2], [0, 2]])
b[rows, cols]
array([[ 0, 2],
[ 9, 11]])

每个维度传入的索引数组的形状为 (2,2),因此最后得到的数组形状为 (2,2),第一行对应位置 (0,0)(0,2),第二行对应位置 (3,0)(3,2)

高级索引也支持广播机制,将刚才的索引进行简化:

rows = np.array([0, 3])
cols = np.array([0, 2])

它们的形状都是 (2,),如果对其直接索引,会得到位置 (0,0)(3,2) 的两个元素:

b[rows, cols]
array([ 0, 11])

这并不是预期想要的结果。为此,可以将 rows 的形状修改为 (2,1),再进行索引:

rows.shape = 2, 1
b[rows, cols]
array([[ 0, 2],
[ 9, 11]])

rows 的形状为 (2,1),cols 的形状为 (2,),两个维度的索引数组形状不对应,NumPy 先通过广播机制,将它们广播为匹配后的形状 (2,2),最终得到与刚才一致的结果。

高级索引与基础索引可以混用,此时,情况会变得十分复杂。例如,基础索引下有:

b[1:2, 1:3]
array([[4, 5]])

1:3 改成数组 [1, 2],得到的结果相同,但是是高级索引:

b[1:2, [1, 2]]
array([[4, 5]])

NumPy 有一套相应的规则来确定索引得到的结果。当索引中的高级索引不相邻时,高级索引对应的维度将被放在索引结果的最前面,之后是基础索引的维度。例如,数组 a 的形状为 (10,20,30,40,50),考虑形状均为 (2,3,4) 的两个索引数组 ind1ind2,索引 a[ind1, ..., ind2] 的形状为 (2,3,4,20,30,40),因为 ind1ind2 不相邻:

a = np.ones((10, 20, 30, 40, 50))
ind = np.ones((2, 3, 4), dtype=int)
a[ind, ..., ind].shape
(2, 3, 4, 20, 30, 40)

而当所有的高级索引相邻时,它会替换掉对应的维度。例如,索引 a[..., ind1, ind2, :] 会得到一个 (10,20,2,3,4,50) 的数组,高级索引的维度 (2,3,4) 替换了对应位置的 (30,40)

a[..., ind, ind, :].shape
(10, 20, 2, 3, 4, 50)

在高级索引与基础索引混用时,使用单个数字的维度会被当作高级索引,因此,索引 a [ind1,...,1] 的形状为 (2,3,4,20,30,40),因为 1 被当作高级索引,广播成了 2×3×4 的大小,导致高级索引的位置不相邻:

a[ind, ..., 1].shape
(2, 3, 4, 20, 30, 40)

还可以用一个与维度大小相等的布尔数组进行索引,并把其中为 True 的位置拿出来。利用逻辑运算时可以得到:

a = np.array([1, 2, 3, 4, 5, 6])
a % 3 == 0
array([False, False, True, False, False, True])
a[a % 3 == 0]
array([3, 6])
a[a < 4]
array([1, 2, 3])
谨此笔记,记录过往。凭君阅览,如能收益,莫大奢望。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇