NumPy陣列重塑形狀和調整大小

NumPy中有兩個跟形狀轉換相關的函式(及方法)reshape以及resize,它們都能方便的改變矩陣的形狀,但是它們之間又有一個顯著的差別,我們會著重的來講。

numpy.reshape()

我們先來看看會在各種資料計算中經常用到的改變陣列形狀的函式reshape()

import numpy as np

arrayA = np.arange(8)
# arrayA = array([0, 1, 2, 3, 4, 5, 6, 7])

np.reshape(arrayA, (2, 4))
#array([[0, 1, 2, 3],
#       [4, 5, 6, 7]])

這裡它把一個有8個元素的向量,轉換成了形狀為(4, 2)的一個矩陣。因為轉換前後的元素數目一樣,所以能夠成功的進行轉換,假如前後數目不一樣的話,就會有錯誤ValueError報出。

In [1]: np.reshape(arrayA, (3, 4))
    ---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
ValueError: cannot reshape array of size 8 into shape (3,4)

我們仔細看轉換後的資料,第一行是arrayA的前四個資料,第二行是arrayA的後四個資料,也就是它是按行來填充資料的,有些時候我們需要把資料填充的順序改成按列來填充,那我們需要改變函式中的另外一個輸入引數order=

In [1]: np.reshape(arrayA, (2, 4), order='F')
Out[1]: array([[0, 2, 4, 6],
        	  [1, 3, 5, 7]])

order預設的引數是C,也就是按行填充,當引數變為F時,就變成按列填充。

說明
這裡用按行或者按列來填充,是為了便於理解,其實具體的不同是由於函式是按照類似C的索引順序還是按照類似Fortan的索引順序來讀取輸入矩陣的內容,具體可以參照Numpy reshape的官方說明文件

ndarray.reshape()

除了用NumPy的reshape函式外,你還可以用資料ndarray物件裡面的reshape方法來進行矩陣形狀變化。它的語法跟numpy.reshape()類似,區別在於不用輸入矩陣作為引數了。

In [1]: arrayB = arrayA.reshape((2, 4))
    
In [2]: arrayB
Out[2]:	array([[0, 1, 2, 3],
       		[4, 5, 6, 7]])
In [1]: arrayA
Out[2]: array([0, 1, 2, 3, 4, 5, 6, 7])    

可以看得出,用法跟reshape函式類似。 這裡想強調的一點是ndarray.reshape()方法不會改變原矩陣的資料、形狀等,而只是返回一個新的矩陣。

reshape()函式/方法記憶體

reshape函式或者方法生成的新陣列和原始陣列是共用一個記憶體的,有點類似於Python裡面的shallow copy,當你改變一個陣列的元素,另外一個陣列的元素也相應的改變了。

In [1]: arrayA = np.arange(8)
    	arrayB = arrayA.reshape((2, 4))
        arrayB
Out[2]:	array([[0, 1, 2, 3],
       		[4, 5, 6, 7]])
In [2]: arrayA[0] = 10
    	arrayA
Out[2]: array([10, 1, 2, 3, 4, 5, 6, 7]) 
In [3]: arrayB    
Out[3]:	array([[10, 1, 2, 3],
       		[4, 5, 6, 7]])    

numpy.resize()

numpy.resize()reshape類似,可以改變矩陣的形狀,但它有幾點不同,

  1. 沒有order引數了,它只有跟reshape裡面order='C'的方式。
  2. 假如要轉換成的矩陣形狀中的元素數量跟原矩陣不同,它會強制進行轉換,而不報錯。

我們具體來看一下第二點

In [1]: arrayA = np.arange(8)
        arrayB = np.resize(arrayA, (2, 4))
Out[1]: array([[0, 1, 2, 3],
       [4, 5, 6, 7]])

這是尺寸大小正常情況, 跟reshape的結果是一樣的。

In [1]: arrayC = np.resize(arrayA, (3, 4))
    	arrayC
Out[1]: array([[0, 1, 2, 3],
       [4, 5, 6, 7],
       [0, 1, 2, 3]])
In [2]: arrayD = np.resize(arrayA, (4, 4))
    	arrayD
Out[2]: array([[0, 1, 2, 3],
       [4, 5, 6, 7],
       [0, 1, 2, 3],
       [4, 5, 6, 7]])

當新形狀行數超出的話,它會開始重複填充原始矩陣的內容,實現形狀大小的自動調整而不報錯。

In [1]: arrayE = np.resize(arrayA, (2, 2))
		arrayE
Out[1]: array([[0, 1],
       [2, 3]])    
In [2]: np.resize(arrayA, (1,4))
Out[2]: array([[0, 1, 2, 3]])

當新形狀比原形狀所需要的資料小的時候,它會從原矩陣讀讀取出所需要個數的資料,然後按照先按行填充的方式來對新矩陣元素賦值。

注意
當新矩陣的形狀在原矩陣形狀內時,比如(2, 2(2, 4) 的情形,新矩陣的元素不是按照子集來獲取的。比如上例中,np.resize(arrayA, (2, 2))不是array([[0, 1], [4, 5])。假如你需要這樣的擷取方法,我們會在後續的索引和切片操作中介紹到的。
In [1]: np.resize(arrayA, (3, 5))
Out[1]: array([[0, 1, 2, 3, 4],
       [5, 6, 7, 0, 1],
       [2, 3, 4, 5, 6]])    

當新形狀比原形狀要大時,它會先按行去填充舊矩陣的元素,並且在元素被用光後,再重複的填充這些元素,直到新矩陣的最後一元素。

resize函式/方法記憶體

reshape不一樣的是,resize函式/方法生成的新陣列跟原陣列並不共用一個記憶體,所以彼此元素的改變不會影響到對方。

In [1]: arrayA = np.arange(8)
    	arrayB = arrayA.reshape((2, 4))
        arrayB
Out[2]:	array([[0, 1, 2, 3],
       		[4, 5, 6, 7]])
In [2]: arrayA[0] = 10
    	arrayA
Out[2]: array([10, 1, 2, 3, 4, 5, 6, 7]) 
In [3]: arrayB    
Out[3]:	array([[0, 1, 2, 3],
       		[4, 5, 6, 7]])