NumPy 超詳細教程(3):ndarray 的內部機理及高級迭代

系列文章地址

ndarray 對象的內部機理

在前面的內容中,咱們已經詳細講述了 ndarray 的使用,在本章的開始部分,咱們來聊一聊 ndarray 的內部機理,以便更好的理解後續的內容。python

一、ndarray 的組成

ndarray 與數組不一樣,它不只僅包含數據信息,還包括其餘描述信息。ndarray 內部由如下內容組成:數組

  • 數據指針:一個指向實際數據的指針。
  • 數據類型(dtype):描述了每一個元素所佔字節數。
  • 維度(shape):一個表示數組形狀的元組。
  • 跨度(strides):一個表示從當前維度前進道下一維度的當前位置所須要「跨過」的字節數。

NumPy 中,數據存儲在一個均勻連續的內存塊中,能夠這麼理解,NumPy 將多維數組在內部以一維數組的方式存儲,咱們只要知道了每一個元素所佔的字節數(dtype)以及每一個維度中元素的個數(shape),就能夠快速定位到任意維度的任意一個元素。bash

dtypeshape 前文中已經有詳細描述,這裏咱們來說下 strideside

示例函數

ls = [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]], 
      [[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]
a = np.array(ls, dtype=int)
print(a)
print(a.strides)
複製代碼

輸出:oop

[[[ 1  2  3  4]
  [ 5  6  7  8]
  [ 9 10 11 12]]

 [[13 14 15 16]
  [17 18 19 20]
  [21 22 23 24]]]
(48, 16, 4)
複製代碼

上例中,咱們定義了一個三維數組,dtypeintint 佔 4個字節。 第一維度,從元素 1 到元素 13,間隔 12 個元素,總字節數爲 48; 第二維度,從元素 1 到元素 5,間隔 4 個元素,總字節數爲 16; 第三維度,從元素 1 到元素 2,間隔 1 個元素,總字節數爲 4。 因此跨度爲(48, 16, 4)。post

普通迭代

ndarray 的普通迭代跟 Python 及其餘語言中的迭代方式無異,N 維數組,就要用 N 層的 for 循環。性能

示例:spa

import numpy as np

ls = [[1, 2], [3, 4], [5, 6]]
a = np.array(ls, dtype=int)
for row in a:
    for cell in row:
        print(cell)
複製代碼

輸出:指針

1
2
3
4
5
6
複製代碼

上例中,row 的數據類型依然是 numpy.ndarray,而 cell 的數據類型是 numpy.int32

nditer 多維迭代器

NumPy 提供了一個高效的多維迭代器對象:nditer 用於迭代數組。在普通方式的迭代中,N 維數組,就要用 N 層的 for 循環。可是使用 nditer 迭代器,一個 for 循環就能遍歷整個數組。(由於 ndarray 在內存中是連續的,連續內存不就至關因而一維數組嗎?遍歷一維數組固然只須要一個 for 循環就好了。)

一、基本示例

例一:

ls = [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]],
      [[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]
a = np.array(ls, dtype=int)
for x in np.nditer(a):
    print(x, end=", ")
複製代碼

輸出:

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 
複製代碼

二、order 參數:指定訪問元素的順序

建立 ndarray 數組時,能夠經過 order 參數指定元素的順序,按行仍是按列,這是什麼意思呢?來看下面的示例:

例二:

ls = [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]],
      [[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]
a = np.array(ls, dtype=int, order='F')
for x in np.nditer(a):
    print(x, end=", ")
複製代碼

輸出:

1, 13, 5, 17, 9, 21, 2, 14, 6, 18, 10, 22, 3, 15, 7, 19, 11, 23, 4, 16, 8, 20, 12, 24, 
複製代碼

nditer 默認之內存中元素的順序(order='K')訪問元素,對比例一可見,建立 ndarray 時,指定不一樣的順序將影響元素在內存中的位置。

例三: nditer 也能夠指定使用某種順序遍歷。

ls = [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]],
      [[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]
a = np.array(ls, dtype=int, order='F')
for x in np.nditer(a, order='C'):
    print(x, end=", ")
複製代碼

輸出:

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 
複製代碼

行主順序(order='C')和列主順序(order='F'),參看 en.wikipedia.org/wiki/Row-_a…。例一是行主順序,例二是列主順序,若是將 ndarray 數組想象成一棵樹,那麼會發現,行主順序就是深度優先,而列主順序就是廣度優先。NumPy 中之因此要分行主順序和列主順序,主要是爲了在矩陣運算中提升性能,順序訪問比非順序訪問快幾個數量級。(矩陣運算將會在後面的章節中講到)

三、op_flags 參數:迭代時修改元素的值

默認狀況下,nditer 將視待迭代遍歷的數組爲只讀對象(readonly),爲了在遍歷數組的同時,實現對數組元素值得修改,必須指定 op_flags 參數爲 readwrite 或者 writeonly 的模式。

例四:

import numpy as np

a = np.arange(5)
for x in np.nditer(a, op_flags=['readwrite']):
    x[...] = 2 * x
print(a)
複製代碼

輸出:

[0 1 2 3 4]
複製代碼

四、flags 參數

flags 參數須要傳入一個數組或元組,既然參數類型是數組,我本來覺得能夠傳入多個值的,可是,就下面介紹的 4 種經常使用選項,我試了,不能傳多個,例如 flags=['f_index', 'external_loop'],運行報錯

(1)使用外部循環:external_loop

將一維的最內層的循環轉移到外部循環迭代器,使得 NumPy 的矢量化操做在處理更大規模數據時變得更有效率。

簡單來講,當指定 flags=['external_loop'] 時,將返回一維數組而並不是單個元素。具體來講,當 ndarray 的順序和遍歷的順序一致時,將全部元素組成一個一維數組返回;當 ndarray 的順序和遍歷的順序不一致時,返回每次遍歷的一維數組(這句話特別很差描述,看例子就清楚了)。

例五:

import numpy as np

ls = [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]],
      [[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]
a = np.array(ls, dtype=int, order='C')
for x in np.nditer(a, flags=['external_loop'], order='C'):
    print(x,)
複製代碼

輸出:

[ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]
複製代碼

例六:

b = np.array(ls, dtype=int, order='F')
for x in np.nditer(b, flags=['external_loop'], order='C'):
    print(x,)
複製代碼

輸出:

[1 2 3 4]
[5 6 7 8]
[ 9 10 11 12]
[13 14 15 16]
[17 18 19 20]
[21 22 23 24]
複製代碼

(2)追蹤索引:c_index、f_index、multi_index

例七:

import numpy as np

a = np.arange(6).reshape(2, 3)
it = np.nditer(a, flags=['f_index'])

while not it.finished:
    print("%d <%d>" % (it[0], it.index))
    it.iternext()
複製代碼

輸出:

0 <0>
1 <2>
2 <4>
3 <1>
4 <3>
5 <5>
複製代碼

這裏索引之因此是這樣的順序,由於咱們選擇的是列索引(f_index)。直觀的感覺看下圖:

image

遍歷元素的順序是由 order 參數決定的,而行索引(c_index)和列索引(f_index)不論如何指定,並不會影響元素返回的順序。它們僅表示在當前內存順序下,若是按行/列順序返回,各個元素的下標應該是多少。

例八:

import numpy as np

a = np.arange(6).reshape(2, 3)
it = np.nditer(a, flags=['multi_index'])

while not it.finished:
    print("%d <%s>" % (it[0], it.multi_index))
    it.iternext()
複製代碼

輸出:

0 <(0, 0)>
1 <(0, 1)>
2 <(0, 2)>
3 <(1, 0)>
4 <(1, 1)>
5 <(1, 2)>
複製代碼

五、同時迭代多個數組

說到同時遍歷多個數組,第一反應會想到 zip 函數,而在 nditer 中不須要。

例九:

a = np.array([1, 2, 3], dtype=int, order='C')
b = np.array([11, 12, 13], dtype=int, order='C')
for x, y in np.nditer([a, b]):
    print(x, y)
複製代碼

輸出:

1 11
2 12
3 13
複製代碼

其餘函數

一、flatten函數

flatten 函數將多維 ndarray 展開成一維 ndarray 返回。 語法:

flatten(order='C')
複製代碼

示例:

import numpy as np

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=int, order='C')
b = a.flatten()
print(b)
print(type(b))
複製代碼

輸出:

[1 2 3 4 5 6]
<class 'numpy.ndarray'>
複製代碼

二、flat

flat 返回一個迭代器,能夠遍歷數組中的每個元素。

import numpy as np

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=int, order='C')
for b in a.flat:
    print(b)
print(type(a.flat))
複製代碼

輸出:

1
2
3
4
5
6
<class 'numpy.flatiter'>
複製代碼

歡迎關注個人公衆號

大齡碼農的Python之路

掃碼關注個人我的公衆號
相關文章
相關標籤/搜索