📊 第6章 图解数组计算模块 NumPy
📄 一句话概括

数组切片返回的是视图,列表切片返回的是复制。改切片会改原数组。 二维用方括号里一个逗号:逗号前是行,逗号后是列。 切片是视图、花式索引是复制。这两种索引语义不同,混起来就出错。

📝 本章和前面几章的关系

上一篇讲运算。这一篇讲怎么把要的那一块取出来,以及改数据前必须先知道的一件事。 这套索引和 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 秒实验

  1. 把 B = A[2:5] 改成 B = A[2:5].copy(),再改 B[0],看 A 变不变。
  2. 用 M[::-1, ::-1] 取一次,看是旋转 180 度还是只翻了行。
  3. 想一想:为什么布尔索引能直接拿数组当索引?它内部到底做了什么?

常见误区

  1. 以为数组切片是复制:它是视图,改切片会改原数组。
  2. 用 and 连两个条件:要写 &,还要各自加括号。
  3. 二维写成 M[0][1] 就以为等价:能跑,但多绕一步,切片组合时还会失效。
  4. 把视图传来传去:函数里改了一下,调用方的数据就变了。
自测三问(点开对答案) 1. 数组切片和列表切片最大的区别是什么? —— 数组切片返回视图,和原数组共享内存;列表切片返回复制,互不影响。 2. M[:, 1] 取的是什么? —— 所有行的第 2 列。冒号是「这一维全要」,逗号后面是列。 3. 切片和花式索引,哪个返回复制? —— 花式索引返回复制,切片返回视图。

取出来了,接下来要改形状、加减内容。下一篇:reshape 和 append 的那些坑。

相关笔记