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

Jinku Hu 2023年12月11日
  1. numpy.reshape()
  2. ndarray.reshape()
  3. reshape() 函式/方法記憶體
  4. numpy.resize()
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]])
作者: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

DelftStack.com 創辦人。Jinku 在機器人和汽車行業工作了8多年。他在自動測試、遠端測試及從耐久性測試中創建報告時磨練了自己的程式設計技能。他擁有電氣/ 電子工程背景,但他也擴展了自己的興趣到嵌入式電子、嵌入式程式設計以及前端和後端程式設計。

LinkedIn Facebook