ndarray 上的索引ndarrays
#
參見
ndarrays
可以使用標準 Python x[obj]
語法進行索引,其中 x 是陣列,而 obj 是選取。根據 obj 的不同,有不同種類的索引可用:基本索引、進階索引和欄位存取。
以下大多數範例展示了在參考陣列中的資料時索引的使用方式。這些範例在指派給陣列時也同樣適用。請參閱將值指派給索引陣列以取得關於指派如何運作的具體範例和說明。
請注意,在 Python 中,x[(exp1, exp2, ..., expN)]
等同於 x[exp1, exp2, ..., expN]
;後者只是前者的語法糖。
基本索引#
單一元素索引#
單一元素索引的工作方式與其他標準 Python 序列完全相同。它是從 0 開始的,並接受負索引以從陣列末尾開始索引。
>>> x = np.arange(10)
>>> x[2]
2
>>> x[-2]
8
沒有必要將每個維度的索引分成各自的方括號集合。
>>> x.shape = (2, 5) # now x is 2-dimensional
>>> x[1, 3]
8
>>> x[1, -1]
9
請注意,如果使用比維度少的索引來索引多維陣列,則會得到一個次維陣列。例如
>>> x[0]
array([0, 1, 2, 3, 4])
也就是說,每個指定的索引都會選取對應於其餘選取維度的陣列。在上面的範例中,選擇 0 表示長度為 5 的剩餘維度未指定,並且傳回的是該維度和大小的陣列。必須注意的是,傳回的陣列是一個視圖,也就是說,它不是原始陣列的副本,而是指向與原始陣列相同的記憶體位置中的值。在這種情況下,會傳回第一個位置 (0) 的一維陣列。因此,在傳回的陣列上使用單一索引,會導致傳回單一元素。也就是說
>>> x[0][2]
2
因此請注意 x[0, 2] == x[0][2]
,儘管第二種情況效率較低,因為在第一個索引之後建立了一個新的暫時陣列,然後再以 2 進行索引。
注意
NumPy 使用 C 順序索引。這表示最後一個索引通常代表記憶體位置變更最快速的位置,這與 Fortran 或 IDL 不同,在 Fortran 或 IDL 中,第一個索引代表記憶體中位置變更最快速的位置。這種差異代表著極大的混淆可能性。
切片與步幅#
基本切片將 Python 的基本切片概念擴展到 N 維。當 obj 是一個 slice
物件(由方括號內的 start:stop:step
表示法建構)、整數或切片物件和整數的元組時,會發生基本切片。Ellipsis
和 newaxis
物件也可以與這些物件交錯使用。
使用 N 個整數進行索引的最簡單情況會傳回一個 陣列純量,代表對應的項目。與 Python 中一樣,所有索引都是從零開始的:對於第 i 個索引 \(n_i\),有效範圍是 \(0 \le n_i < d_i\),其中 \(d_i\) 是陣列形狀的第 i 個元素。負索引會被解釋為從陣列末尾開始計數(即,如果 \(n_i < 0\),則表示 \(n_i + d_i\))。
由基本切片產生的所有陣列始終是原始陣列的視圖。
注意
NumPy 切片會建立一個視圖,而不是像內建 Python 序列(如字串、元組和列表)那樣建立副本。從大型陣列中提取一小部分在提取後變得無用時,必須小心,因為提取的小部分包含對大型原始陣列的參考,該陣列的記憶體將不會被釋放,直到所有從它衍生的陣列都被垃圾回收為止。在這種情況下,建議使用明確的 copy()
。
序列切片的標準規則適用於每個維度上的基本切片(包括使用步進索引)。需要記住的一些有用的概念包括
基本切片語法是
i:j:k
,其中 i 是起始索引,j 是停止索引,而 k 是步幅 (\(k\neq0\))。這會選取索引值為 i、i + k、…、i + (m - 1) k 的 m 個元素(在對應的維度中),其中 \(m = q + (r\neq0)\),而 q 和 r 是將 j - i 除以 k 得到的商和餘數:j - i = q k + r,因此 i + (m - 1) k < j。例如>>> x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> x[1:7:2] array([1, 3, 5])
負數 i 和 j 會被解釋為 n + i 和 n + j,其中 n 是對應維度中的元素數。負數 k 會使步進朝向較小的索引。從上面的範例來看
>>> x[-2:10] array([8, 9]) >>> x[-3:3:-1] array([7, 6, 5, 4])
假設 n 是正在切片的維度中的元素數。那麼,如果沒有給定 i,則當 k > 0 時,預設為 0;當 k < 0 時,預設為 n - 1。如果沒有給定 j,則當 k > 0 時,預設為 n;當 k < 0 時,預設為 -n-1。如果沒有給定 k,則預設為 1。請注意,
::
與:
相同,表示選取此軸上的所有索引。從上面的範例來看>>> x[5:] array([5, 6, 7, 8, 9])
如果選取元組中的物件數量少於 N,則對於任何後續維度,都會假定為
:
。例如>>> x = np.array([[[1],[2],[3]], [[4],[5],[6]]]) >>> x.shape (2, 3, 1) >>> x[1:2] array([[[4], [5], [6]]])
整數 i 傳回的值與
i:i+1
相同,除了傳回物件的維度會減少 1。特別是,如果選取元組的第 p 個元素是整數(且所有其他項目都是:
),則會傳回維度為 N - 1 的對應子陣列。如果 N = 1,則傳回的物件是陣列純量。這些物件在純量中說明。如果選取元組的所有項目都是
:
,但第 p 個項目是切片物件i:j:k
,則傳回的陣列的維度為 N,它是透過沿著第 p 個軸堆疊元素 i、i+k、…、i + (m - 1) k < j 的整數索引傳回的子陣列而形成的。在切片元組中有多個非
:
項目的基本切片,其行為類似於重複應用使用單一非:
項目的切片,其中非:
項目會依序取得(所有其他非:
項目都替換為:
)。因此,在基本切片下,x[ind1, ..., ind2,:]
的作用類似於x[ind1][..., ind2, :]
。警告
以上內容對於進階索引不適用。
您可以使用切片來設定陣列中的值,但(與列表不同)您永遠無法增加陣列的大小。
x[obj] = value
中要設定的值的大小必須與x[obj]
的形狀相同(或可廣播到相同的形狀)。切片元組始終可以建構為 obj 並在
x[obj]
表示法中使用。切片物件可以用於建構中,以取代[start:stop:step]
表示法。例如,x[1:10:5, ::-1]
也可以實作為obj = (slice(1, 10, 5), slice(None, None, -1)); x[obj]
。這對於建構適用於任意維度陣列的通用程式碼可能很有用。如需更多資訊,請參閱處理程式碼中可變數量的索引。
維度索引工具#
有些工具可以簡化陣列形狀與運算式和指派的輕鬆匹配。
Ellipsis
展開為選取元組索引所有維度所需的 :
物件的數量。在大多數情況下,這表示展開的選取元組的長度為 x.ndim
。可能只存在單一省略號。從上面的範例來看
>>> x[..., 0]
array([[1, 2, 3],
[4, 5, 6]])
這等同於
>>> x[:, :, 0]
array([[1, 2, 3],
[4, 5, 6]])
選取元組中的每個 newaxis
物件都用於將結果選取的維度擴展一個單位長度的維度。新增的維度是選取元組中 newaxis
物件的位置。newaxis
是 None
的別名,並且 None
可以取代它使用,結果相同。從上面的範例來看
>>> x[:, np.newaxis, :, :].shape
(2, 1, 3, 1)
>>> x[:, None, :, :].shape
(2, 1, 3, 1)
這對於以其他方式需要明確調整形狀運算的方式組合兩個陣列可能很方便。例如
>>> x = np.arange(5)
>>> x[:, np.newaxis] + x[np.newaxis, :]
array([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8]])
進階索引#
當選取物件 obj 是非元組序列物件、ndarray
(資料型別為整數或布林值),或至少有一個序列物件或 ndarray(資料型別為整數或布林值)的元組時,會觸發進階索引。進階索引有兩種型別:整數和布林值。
進階索引始終傳回資料的副本(與傳回視圖的基本切片形成對比)。
警告
進階索引的定義表示 x[(1, 2, 3),]
與 x[(1, 2, 3)]
根本不同。後者等同於 x[1, 2, 3]
,這會觸發基本選取,而前者會觸發進階索引。請務必理解為什麼會發生這種情況。
整數陣列索引#
整數陣列索引允許根據陣列的 N 維索引選取陣列中的任意項目。每個整數陣列都代表該維度的多個索引。
索引陣列中允許使用負值,其運作方式與單一索引或切片相同
>>> x = np.arange(10, 1, -1)
>>> x
array([10, 9, 8, 7, 6, 5, 4, 3, 2])
>>> x[np.array([3, 3, 1, 8])]
array([7, 7, 9, 2])
>>> x[np.array([3, 3, -3, 8])]
array([7, 7, 4, 2])
如果索引值超出範圍,則會擲回 IndexError
>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[np.array([1, -1])]
array([[3, 4],
[5, 6]])
>>> x[np.array([3, 4])]
Traceback (most recent call last):
...
IndexError: index 3 is out of bounds for axis 0 with size 3
當索引由與正在索引的陣列維度一樣多的整數陣列組成時,索引很簡單,但與切片不同。
進階索引始終會廣播並以一的方式迭代
result[i_1, ..., i_M] == x[ind_1[i_1, ..., i_M], ind_2[i_1, ..., i_M],
..., ind_N[i_1, ..., i_M]]
請注意,結果形狀與(廣播)索引陣列形狀 ind_1, ..., ind_N
相同。如果索引無法廣播到相同的形狀,則會引發例外 IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes...
。
使用多維索引陣列進行索引往往是不太常見的用法,但它們是被允許的,並且對於某些問題很有用。我們將從最簡單的多維情況開始
>>> y = np.arange(35).reshape(5, 7)
>>> y
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]])
>>> y[np.array([0, 2, 4]), np.array([0, 1, 2])]
array([ 0, 15, 30])
在這種情況下,如果索引陣列具有相符的形狀,並且每個正在索引的陣列維度都有一個索引陣列,則結果陣列的形狀與索引陣列的形狀相同,並且這些值對應於索引陣列中每個位置的索引集。在此範例中,兩個索引陣列的第一個索引值都是 0,因此結果陣列的第一個值是 y[0, 0]
。下一個值是 y[2, 1]
,最後一個值是 y[4, 2]
。
如果索引陣列的形狀不同,則會嘗試將它們廣播到相同的形狀。如果它們無法廣播到相同的形狀,則會引發例外
>>> y[np.array([0, 2, 4]), np.array([0, 1])]
Traceback (most recent call last):
...
IndexError: shape mismatch: indexing arrays could not be broadcast
together with shapes (3,) (2,)
廣播機制允許索引陣列與其他索引的純量組合。效果是純量值用於索引陣列的所有對應值
>>> y[np.array([0, 2, 4]), 1]
array([ 1, 15, 29])
跳到下一個複雜程度,可以使用索引陣列僅部分索引陣列。需要稍微思考一下才能理解在這種情況下會發生什麼。例如,如果我們只對 y 使用一個索引陣列
>>> y[np.array([0, 2, 4])]
array([[ 0, 1, 2, 3, 4, 5, 6],
[14, 15, 16, 17, 18, 19, 20],
[28, 29, 30, 31, 32, 33, 34]])
它會產生一個新陣列的建構,其中索引陣列的每個值都會從正在索引的陣列中選取一個列,而結果陣列具有結果形狀(索引元素數、列的大小)。
一般來說,結果陣列的形狀將是索引陣列的形狀(或所有索引陣列廣播到的形狀)與正在索引的陣列中任何未使用維度(未索引的維度)的形狀的串連。
範例
從每一列中,應選取特定元素。列索引只是 [0, 1, 2]
,而行索引指定要為對應列選擇的元素,此處為 [0, 1, 0]
。將兩者一起使用,可以使用進階索引來解決此任務
>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[[0, 1, 2], [0, 1, 0]]
array([1, 4, 5])
為了實現與上述基本切片類似的行為,可以使用廣播。ix_
函式可以協助進行此廣播。最好透過範例來理解這一點。
範例
從 4x3 陣列中,應使用進階索引選取角落元素。因此,需要選取所有行是 [0, 2]
之一且列是 [0, 3]
之一的所有元素。若要使用進階索引,需要明確選取所有元素。使用先前說明的方法,可以寫成
>>> x = np.array([[ 0, 1, 2],
... [ 3, 4, 5],
... [ 6, 7, 8],
... [ 9, 10, 11]])
>>> rows = np.array([[0, 0],
... [3, 3]], dtype=np.intp)
>>> columns = np.array([[0, 2],
... [0, 2]], dtype=np.intp)
>>> x[rows, columns]
array([[ 0, 2],
[ 9, 11]])
但是,由於上面的索引陣列只是重複自身,因此可以使用廣播(比較諸如 rows[:, np.newaxis] + columns
之類的運算)來簡化此操作
>>> rows = np.array([0, 3], dtype=np.intp)
>>> columns = np.array([0, 2], dtype=np.intp)
>>> rows[:, np.newaxis]
array([[0],
[3]])
>>> x[rows[:, np.newaxis], columns]
array([[ 0, 2],
[ 9, 11]])
此廣播也可以使用 ix_
函式來實現
>>> x[np.ix_(rows, columns)]
array([[ 0, 2],
[ 9, 11]])
請注意,如果沒有 np.ix_
呼叫,則只會選取對角線元素
>>> x[rows, columns]
array([ 0, 11])
此差異是關於使用多個進階索引進行索引時要記住的最重要的事情。
範例
進階索引可能有用的真實範例是用於色彩查找表,我們想要將影像的值對應到 RGB 三元組以進行顯示。查找表的形狀可能為 (nlookup, 3)。使用形狀為 (ny, nx) 且 dtype=np.uint8(或任何整數型別,只要值在查找表的界限內)的影像索引此類陣列,將會產生形狀為 (ny, nx, 3) 的陣列,其中每個像素位置都關聯一個 RGB 值的三元組。
布林陣列索引#
當 obj 是布林型別的陣列物件時,例如可能從比較運算子傳回的物件時,會發生此進階索引。單一布林索引陣列實際上與 x[obj.nonzero()]
相同,如上所述,obj.nonzero()
傳回整數索引陣列的元組(長度為 obj.ndim
),顯示 obj 的 True
元素。但是,當 obj.shape == x.shape
時,它會更快。
如果 obj.ndim == x.ndim
,則 x[obj]
會傳回一個一維陣列,其中填滿了 x 的元素,這些元素對應於 obj 的 True
值。搜尋順序將為列優先、C 樣式。如果 obj 的形狀與 x 的對應維度不符,則會引發索引錯誤,無論這些值是 True
還是 False
。
這種情況的一個常見用途是篩選所需的元素值。例如,可能希望從陣列中選取所有不是 numpy.nan
的項目
>>> x = np.array([[1., 2.], [np.nan, 3.], [np.nan, np.nan]])
>>> x[~np.isnan(x)]
array([1., 2., 3.])
或希望將常數新增至所有負數元素
>>> x = np.array([1., -1., -2., 3])
>>> x[x < 0] += 20
>>> x
array([ 1., 19., 18., 3.])
一般來說,如果索引包含布林陣列,則結果將與將 obj.nonzero()
插入到相同位置並使用上述整數陣列索引機制相同。x[ind_1, boolean_array, ind_2]
等同於 x[(ind_1,) + boolean_array.nonzero() + (ind_2,)]
。
如果只有一個布林陣列且沒有整數索引陣列存在,則這很簡單。只需注意確保布林索引的維度正好與它應該處理的維度一樣多。
一般來說,當布林陣列的維度少於正在索引的陣列時,這等同於 x[b, ...]
,這表示 x 由 b 索引,後接盡可能多的 :
以填滿 x 的秩。因此,結果的形狀是一個維度,其中包含布林陣列的 True 元素數,後接正在索引的陣列的其餘維度
>>> x = np.arange(35).reshape(5, 7)
>>> b = x > 20
>>> b[:, 5]
array([False, False, False, True, True])
>>> x[b[:, 5]]
array([[21, 22, 23, 24, 25, 26, 27],
[28, 29, 30, 31, 32, 33, 34]])
此處從索引陣列中選取第 4 列和第 5 列,並組合以建立二維陣列。
範例
從陣列中,選取總和小於或等於 2 的所有列
>>> x = np.array([[0, 1], [1, 1], [2, 2]])
>>> rowsum = x.sum(-1)
>>> x[rowsum <= 2, :]
array([[0, 1],
[1, 1]])
結合多個布林索引陣列或布林值與整數索引陣列,最好透過 obj.nonzero()
類比來理解。ix_
函式也支援布林陣列,並且可以正常運作,不會有任何意外。
範例
使用布林索引來選取總和為偶數的所有列。同時,應使用進階整數索引選取第 0 行和第 2 行。使用 ix_
函式,可以使用以下方式完成
>>> x = np.array([[ 0, 1, 2],
... [ 3, 4, 5],
... [ 6, 7, 8],
... [ 9, 10, 11]])
>>> rows = (x.sum(-1) % 2) == 0
>>> rows
array([False, True, False, True])
>>> columns = [0, 2]
>>> x[np.ix_(rows, columns)]
array([[ 3, 5],
[ 9, 11]])
如果沒有 np.ix_
呼叫,則只會選取對角線元素。
或者不使用 np.ix_
(比較整數陣列範例)
>>> rows = rows.nonzero()[0]
>>> x[rows[:, np.newaxis], columns]
array([[ 3, 5],
[ 9, 11]])
範例
使用形狀為 (2, 3) 且具有四個 True 元素的二維布林陣列,從形狀為 (2, 3, 5) 的三維陣列中選取列,會產生形狀為 (4, 5) 的二維結果
>>> x = np.arange(30).reshape(2, 3, 5)
>>> x
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]]])
>>> b = np.array([[True, True, False], [False, True, True]])
>>> x[b]
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]])
結合進階索引和基本索引#
當索引中至少有一個切片 (:
)、省略號 (...
) 或 newaxis
時(或陣列的維度多於進階索引的數量),行為可能會更複雜。這就像串連每個進階索引元素的索引結果。
在最簡單的情況下,只有單一進階索引與切片組合。例如
>>> y = np.arange(35).reshape(5,7)
>>> y[np.array([0, 2, 4]), 1:3]
array([[ 1, 2],
[15, 16],
[29, 30]])
實際上,切片和索引陣列運算是獨立的。切片運算會提取索引為 1 和 2 的行(即第 2 行和第 3 行),然後索引陣列運算會提取索引為 0、2 和 4 的列(即第 1 列、第 3 列和第 5 列)。這等同於
>>> y[:, 1:3][np.array([0, 2, 4]), :]
array([[ 1, 2],
[15, 16],
[29, 30]])
單一進階索引可以取代切片,例如,結果陣列將會相同。但是,它是副本,並且可能具有不同的記憶體配置。在可能的情況下,切片是較佳的選擇。例如
>>> x = np.array([[ 0, 1, 2],
... [ 3, 4, 5],
... [ 6, 7, 8],
... [ 9, 10, 11]])
>>> x[1:2, 1:3]
array([[4, 5]])
>>> x[1:2, [1, 2]]
array([[4, 5]])
理解多個進階索引組合的最簡單方法可能是從結果形狀的角度來思考。索引運算有兩個部分,基本索引(不包括整數)定義的子空間和進階索引部分定義的子空間。需要區分兩種索引組合情況
進階索引都彼此相鄰。例如
x[..., arr1, arr2, :]
,但不是x[arr1, :, 1]
,因為1
在這方面是進階索引。
在第一種情況下,從進階索引運算產生的維度在結果陣列中排在最前面,而子空間維度排在後面。在第二種情況下,來自進階索引運算的維度會插入到結果陣列中,位置與它們在初始陣列中的位置相同(後者邏輯使簡單的進階索引的行為就像切片一樣)。
範例
假設 x.shape
為 (10, 20, 30),而 ind
是形狀為 (2, 5, 2) 的索引 intp
陣列,則 result = x[..., ind, :]
的形狀為 (10, 2, 5, 2, 30),因為 (20,) 形狀的子空間已替換為 (2, 5, 2) 形狀的廣播索引子空間。如果我們讓 i、j、k 迴圈遍歷 (2, 5, 2) 形狀的子空間,則 result[..., i, j, k, :] = x[..., ind[i, j, k], :]
。此範例產生的結果與 x.take(ind, axis=-2)
相同。
範例
假設 x.shape
為 (10, 20, 30, 40, 50),並假設 ind_1
和 ind_2
可以廣播 (broadcast) 成形狀 (2, 3, 4)。那麼 x[:, ind_1, ind_2]
的形狀會是 (10, 2, 3, 4, 40, 50),因為來自 X 的 (20, 30) 形狀的子空間已被來自索引的 (2, 3, 4) 子空間取代。然而,x[:, ind_1, :, ind_2]
的形狀會是 (2, 3, 4, 10, 30, 50),因為沒有明確的位置可以放入索引子空間,因此它被附加到開頭。總是可以使用 .transpose()
來將子空間移動到任何想要的位置。請注意,這個範例無法使用 take
複製。
範例
切片 (Slicing) 可以與廣播的布林索引結合使用
>>> x = np.arange(35).reshape(5, 7)
>>> b = x > 20
>>> b
array([[False, False, False, False, False, False, False],
[False, False, False, False, False, False, False],
[False, False, False, False, False, False, False],
[ True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True]])
>>> x[b[:, 5], 1:3]
array([[22, 23],
[29, 30]])
欄位存取#
參見
如果 ndarray
物件是結構化陣列,則可以使用字串(類似字典)索引陣列來存取陣列的欄位。
索引 x['field-name']
會傳回陣列的新 檢視 (view),其形狀與 *x* 相同(當欄位是子陣列時除外),但資料類型為 x.dtype['field-name']
,並且僅包含指定欄位中的資料部分。此外,記錄陣列 純量也可以用這種方式「索引」。
也可以使用欄位名稱列表索引結構化陣列,例如 x[['field-name1', 'field-name2']]
。從 NumPy 1.16 開始,這會傳回僅包含這些欄位的檢視。在舊版本的 NumPy 中,它會傳回副本。有關多欄位索引的更多資訊,請參閱使用者指南中有關結構化陣列的章節。
如果存取的欄位是子陣列,則子陣列的維度會附加到結果的形狀。例如
>>> x = np.zeros((2, 2), dtype=[('a', np.int32), ('b', np.float64, (3, 3))])
>>> x['a'].shape
(2, 2)
>>> x['a'].dtype
dtype('int32')
>>> x['b'].shape
(2, 2, 3, 3)
>>> x['b'].dtype
dtype('float64')
扁平迭代器索引#
x.flat
會傳回一個迭代器,它將迭代整個陣列(以 C-contiguous 風格,最後一個索引變化最快)。只要選擇物件不是元組 (tuple),這個迭代器物件也可以使用基本切片或進階索引進行索引。從 x.flat
是一個一維檢視這個事實來看,這應該很清楚。它可以用於具有一維 C 風格扁平索引的整數索引。因此,任何傳回陣列的形狀都是整數索引物件的形狀。
將值賦值給索引陣列#
如前所述,可以使用單個索引、切片以及索引和遮罩陣列來選擇陣列的子集進行賦值。賦值給索引陣列的值必須在形狀上一致(形狀相同或可廣播到索引產生的形狀)。例如,允許將常數賦值給切片
>>> x = np.arange(10)
>>> x[2:7] = 1
或正確大小的陣列
>>> x[2:7] = np.arange(5)
請注意,如果將較高類型賦值給較低類型(例如浮點數賦值給整數),賦值可能會導致變更,甚至可能導致例外(將複數賦值給浮點數或整數)
>>> x[1] = 1.2
>>> x[1]
1
>>> x[1] = 1.2j
Traceback (most recent call last):
...
TypeError: can't convert complex to int
與某些參考(例如陣列和遮罩索引)不同,賦值始終是對陣列中的原始資料進行的(實際上,沒有其他方式是合理的!)。但請注意,某些操作可能無法如人們天真地預期的那樣工作。這個特殊的例子經常讓人感到驚訝
>>> x = np.arange(0, 50, 10)
>>> x
array([ 0, 10, 20, 30, 40])
>>> x[np.array([1, 1, 3, 1])] += 1
>>> x
array([ 0, 11, 20, 31, 40])
人們期望第一個位置會遞增 3。實際上,它只會遞增 1。原因是從原始陣列中提取一個新陣列(作為臨時陣列),其中包含索引 1、1、3、1 的值,然後將值 1 加到臨時陣列,然後將臨時陣列賦值回原始陣列。因此,陣列在 x[1] + 1
的值被賦值給 x[1]
三次,而不是遞增 3 次。
在程式中處理可變數量的索引#
當處理可變數量的索引時,索引語法非常強大但有限制。例如,如果您想編寫一個可以處理具有不同維度數量的引數的函數,而無需為每個可能的維度數量編寫特殊情況程式碼,該怎麼做?如果將元組提供給索引,則該元組將被解釋為索引列表。例如
>>> z = np.arange(81).reshape(3, 3, 3, 3)
>>> indices = (1, 1, 1, 1)
>>> z[indices]
40
因此,可以使用程式碼來建構任意數量的索引元組,然後在索引中使用它們。
可以使用 Python 中的 slice() 函數在程式中指定切片。例如
>>> indices = (1, 1, 1, slice(0, 2)) # same as [1, 1, 1, 0:2]
>>> z[indices]
array([39, 40])
同樣地,可以使用 Ellipsis 物件透過程式碼指定省略符號。
>>> indices = (1, Ellipsis, 1) # same as [1, ..., 1]
>>> z[indices]
array([[28, 31, 34],
[37, 40, 43],
[46, 49, 52]])
因此,可以使用 np.nonzero()
函數的輸出直接作為索引,因為它始終傳回索引陣列的元組。
由於元組的特殊處理,它們不會像列表那樣自動轉換為陣列。例如
>>> z[[1, 1, 1, 1]] # produces a large array
array([[[[27, 28, 29],
[30, 31, 32], ...
>>> z[(1, 1, 1, 1)] # returns a single value
40
詳細說明#
以下是一些詳細說明,對於日常索引而言並不重要(沒有特定的順序)
NumPy 原生索引類型是
intp
,可能與預設整數陣列類型不同。intp
是足以安全索引任何陣列的最小資料類型;對於進階索引,它可能比其他類型更快。對於進階賦值,通常不保證迭代順序。這表示如果一個元素被設定多次,則無法預測最終結果。
空(元組)索引是零維陣列的完整純量索引。
x[()]
如果x
是零維的,則傳回純量,否則傳回檢視。另一方面,x[...]
始終傳回檢視。如果索引中存在零維陣列並且它是完整的整數索引,則結果將是純量而不是零維陣列。(不會觸發進階索引。)
當省略符號 (
...
) 存在但沒有大小(即取代零:
)時,結果仍然始終是陣列。如果不存在進階索引,則為檢視,否則為副本。布林陣列的
nonzero
等效性不適用於零維布林陣列。當進階索引操作的結果沒有元素,但個別索引超出範圍時,是否引發
IndexError
是未定義的(例如,x[[], [123]]
,其中123
超出範圍)。當在賦值期間發生轉換錯誤時(例如,使用字串序列更新數值陣列),要賦值的陣列最終可能會處於無法預測的部分更新狀態。但是,如果發生任何其他錯誤(例如索引超出範圍),則陣列將保持不變。
進階索引結果的記憶體佈局針對每個索引操作進行了最佳化,並且不能假定任何特定的記憶體順序。
當使用子類別(尤其是操縱其形狀的子類別)時,預設的
ndarray.__setitem__
行為將為基本索引呼叫__getitem__
,但不會為進階索引呼叫。對於這樣的子類別,最好使用基底類別 ndarray 檢視資料來呼叫ndarray.__setitem__
。如果子類別的__getitem__
不傳回檢視,則必須執行此操作。