数组切片返回的是视图,列表切片返回的是复制。改切片会改原数组。 二维用方括号里一个逗号:逗号前是行,逗号后是列。 切片是视图、花式索引是复制。这两种索引语义不同,混起来就出错。
上一篇讲运算。这一篇讲怎么把要的那一块取出来,以及改数据前必须先知道的一件事。 这套索引和 Pandas 的 loc、iloc 是一个道理,只是这里没有列名可用。
一、一维和列表一样,但结果是视图
A = np.arange(10) 是 0 到 9。A[0] 给 0,A[-1] 给 9,A[2:5] 给 [2 3 4]。
切片含头不含尾,负下标从末尾数起,这一套和列表完全一致。
但数组切片返回的是视图。B = A[2:5] 之后,B 和 A 是同一块内存。
B[0] = 99 之后,A 变成 [0 1 99 3 4 5 6 7 8 9]——第 3 个位置被改了。
列表切片是复制,改副本不影响原列表。数组反过来。
这一条差别极大。取一段数据顺手改了一下,原数据悄悄就变了。
二、二维:一个逗号分开行列
3 行 4 列的矩阵,M[0] 给第一行 [1 2 3 4]。
M[0, 1] 给第 1 行第 2 列那个 2,M[:, 1] 给第 2 列全部 [2 6 10]。
逗号前是行,逗号后是列。一个逗号把两件事分开,就这么简单。
冒号的意思是「这一维全要」,所以 M[:, 1] 就是所有行的第 2 列。
M[0][1] 也能跑出 2,但它是先取行再取列,多绕一步;M[0, 1] 才是标准写法。
三、切片组合与步长
M[0:2, 1:3] 取前两行、第 2 到 3 列,得到 2 行 2 列。
M[:, ::2] 取所有行的偶数列,步长 2 就是隔一个取一个。
M[::-1] 把行整个倒过来,负步长就是倒着走。
行和列可以各自切、各自带步长,一次写完,不用分两步。
这一套在 Pandas 的 iloc 里一模一样,只是那边写成 .iloc[行, 列]。
四、布尔索引:用条件挑
A > 80 得到的不是一个数,是一个同样长的布尔数组。
拿它当索引,A[A > 80],就只留下 True 那些位置的值。
两个条件要各自加括号、用 & 连:A[(A > 80) & (A < 92)]。
& 不能写成 and。and 只能比两个布尔值,比不了两个布尔数组。
括号也不能省。运算符优先级会让 A > 80 & A < 92 变成另一个意思。
五、花式索引:给一串位置
A[ [1, 3, 5]] 取第 2、4、6 个元素,位置是自己列的。
这里要写两层方括号:外层是索引,内层是位置列表。
花式索引返回的是复制,不是视图。改了不影响原数组。
这和切片正好相反。切片省内存但危险,花式安全但多占一份。 分清「视图」和「复制」,是改数据之前必须确认的事。
六、动手 Demo:索引与切片
先看一维切片改一下会连带改原数组,再看二维的行列取法和切片组合。 最后是布尔索引和花式索引,重点对比哪个是视图、哪个是复制。
import numpy as np
A = np.arange(10)
print("A = %s" % A)
print("A[0] = %d,A[-1] = %d,A[2:5] = %s" % (A[0], A[-1], A[2:5]))
B = A[2:5]
B[0] = 99
print("改切片之后 A = %s" % A)
M = np.arange(1, 13).reshape(3, 4)
print("3 行 4 列:")
print(M)
print("M[0] = %s" % M[0])
print("M[0, 1] = %d" % M[0, 1])
print("M[:, 1] = %s" % M[:, 1])
print("M[0:2, 1:3]:")
print(M[0:2, 1:3])
print("M[:, ::2]:")
print(M[:, ::2])
print("M[::-1]:")
print(M[::-1])
S = np.array([88, 92, 75, 85, 90, 68])
print("S > 80:%s" % (S > 80))
print("S[S > 80]:%s" % S[S > 80])
print("两个条件:%s" % S[(S > 80) & (S < 92)])
C = np.arange(10) * 10
print("C = %s" % C)
print("C[ [1, 3, 5]] = %s" % C[ [1, 3, 5]])
D = C[ [1, 3, 5]]
D[0] = 0
print("改 D 之后 C = %s" % C)
实跑输出:
--------------------------------------------------------------------
索引与切片
--------------------------------------------------------------------
① 一维:和列表一样
--------------------------------------------------------------------
A = [0 1 2 3 4 5 6 7 8 9]
A[0] = 0,A[-1] = 9,A[2:5] = [2 3 4]。
切片含头不含尾,和列表一样。
但数组切片返回的是视图,不是复制:改切片会改原数组。
改了切片的第一个之后,A 变成 [ 0 1 99 3 4 5 6 7 8 9]。
! 数组切片是视图,列表切片是复制。这一条差别极大,改数据前必须知道。
② 二维:先行后列
--------------------------------------------------------------------
3 行 4 列的矩阵:
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
M[0] 是第一行:[1 2 3 4]
M[0, 1] 是第 1 行第 2 列:2
M[:, 1] 是第 2 列全部:[ 2 6 10]
逗号前是行,逗号后是列。一个逗号分开两件事。
! M[0][1] 也能跑,但 M[0, 1] 才是标准写法,也更快。
③ 切片组合
--------------------------------------------------------------------
M[0:2, 1:3] 取前两行、第 2 到 3 列:
[2, 3]
[6, 7]
M[:, ::2] 取所有行、偶数列:
[1, 3]
[5, 7]
[9, 11]
M[::-1] 把行倒过来:
[9, 10, 11, 12]
[5, 6, 7, 8]
[1, 2, 3, 4]
! 切片可以带步长,负步长是倒序。这个和列表完全一致。
④ 布尔索引:用条件挑
--------------------------------------------------------------------
A > 80 得到 [ True True False True True False]
A[A > 80] 得到 [88 92 85 90]
条件本身是个布尔数组,拿它当索引就只留下 True 那些。
两个条件要各自加括号、用 & 连:[88 85 90]
! & 不能写成 and。and 只能比两个布尔值,比不了两个布尔数组。
⑤ 花式索引:给一串位置
--------------------------------------------------------------------
A = [ 0 10 20 30 40 50 60 70 80 90]
A[ [1, 3, 5]] 取第 2、4、6 个:[10 30 50]
和切片不一样:花式索引返回的是复制,不是视图。
改了 B 之后 A 还是 [ 0 10 20 30 40 50 60 70 80 90]——原数组没动。
! 切片是视图、花式索引是复制。这两种索引的语义不同,混起来会出错。
第一条输出里 A 的第 3 个位置变成 99,这就是视图的后果——B 不是新数组。
看是不是视图有个快办法:改一下看原数组变没变。或者看 B.base 是不是指向 A。
要安全地拿一段副本,写 A[2:5].copy(),多写三个字母省一夜排查。
30 秒实验
- 把 B = A[2:5] 改成 B = A[2:5].copy(),再改 B[0],看 A 变不变。
- 用 M[::-1, ::-1] 取一次,看是旋转 180 度还是只翻了行。
- 想一想:为什么布尔索引能直接拿数组当索引?它内部到底做了什么?
常见误区
- 以为数组切片是复制:它是视图,改切片会改原数组。
- 用 and 连两个条件:要写 &,还要各自加括号。
- 二维写成 M[0][1] 就以为等价:能跑,但多绕一步,切片组合时还会失效。
- 把视图传来传去:函数里改了一下,调用方的数据就变了。
自测三问(点开对答案)
1. 数组切片和列表切片最大的区别是什么? —— 数组切片返回视图,和原数组共享内存;列表切片返回复制,互不影响。 2. M[:, 1] 取的是什么? —— 所有行的第 2 列。冒号是「这一维全要」,逗号后面是列。 3. 切片和花式索引,哪个返回复制? —— 花式索引返回复制,切片返回视图。取出来了,接下来要改形状、加减内容。下一篇:reshape 和 append 的那些坑。
- ☐ (本篇完,下一篇 6.8 重塑与增、删、改、查)