使用字串與位元組陣列#

雖然 NumPy 主要是一個數值函式庫,但使用 NumPy 字串或位元組陣列通常很方便。以下是兩個最常見的用例:

  • 處理從資料檔案載入或記憶體對應的資料,其中資料中的一個或多個欄位是字串或位元組字串,並且欄位的最大長度是預先知道的。這通常用於名稱或標籤欄位。

  • 將 NumPy 索引和廣播與未知長度的 Python 字串陣列一起使用,這些字串陣列可能為每個值定義了資料,也可能沒有。

對於第一種用例,NumPy 提供了固定寬度的 numpy.voidnumpy.str_numpy.bytes_ 資料類型。對於第二種用例,numpy 提供了 numpy.dtypes.StringDType。下面我們將描述如何使用固定寬度和可變寬度字串陣列,如何在兩種表示形式之間轉換,並提供一些關於如何最有效率地在 NumPy 中使用字串資料的建議。

固定寬度資料類型#

在 NumPy 2.0 之前,固定寬度的 numpy.str_numpy.bytes_numpy.void 資料類型是 NumPy 中唯一可用於處理字串和位元組字串的類型。因此,它們被用作字串和位元組字串的預設 dtype。

>>> np.array(["hello", "world"])
array(['hello', 'world'], dtype='<U5')

此處偵測到的資料類型為 '<U5',或小端序 Unicode 字串資料,最大長度為 5 個 Unicode 碼位。

位元組字串也是如此

>>> np.array([b"hello", b"world"])
array([b'hello', b'world'], dtype='|S5')

由於這是一種單一位元組編碼,因此位元組順序為 ‘|’(不適用),並且偵測到的資料類型是最大 5 個字元的位元組字串。

您也可以使用 numpy.void 來表示位元組字串

>>> np.array([b"hello", b"world"]).astype(np.void)
array([b'\x68\x65\x6C\x6C\x6F', b'\x77\x6F\x72\x6C\x64'], dtype='|V5')

當處理無法很好地表示為位元組字串的位元組流,而是更適合視為 8 位元整數集合時,這最有用。

可變寬度字串#

2.0 版本新增。

注意

numpy.dtypes.StringDType 是 NumPy 的新增功能,使用 NumPy 中對彈性使用者定義資料類型的新支援來實作,並且在生產工作流程中不像舊的 NumPy 資料類型那樣經過廣泛測試。

通常,真實世界的字串資料沒有可預測的長度。

為了支援這種情況,NumPy 提供了 numpy.dtypes.StringDType,它以 UTF-8 編碼將可變寬度字串資料儲存在 NumPy 陣列中

>>> from numpy.dtypes import StringDType
>>> data = ["this is a longer string", "short string"]
>>> arr = np.array(data, dtype=StringDType())
>>> arr
array(['this is a longer string', 'short string'], dtype=StringDType())

請注意,與固定寬度字串不同,StringDType 不會由陣列元素的最大長度參數化,任意長度或短字串都可以存在於同一個陣列中,而無需為短字串中的填充位元組保留儲存空間。

另請注意,與固定寬度字串和大多數其他 NumPy 資料類型不同,StringDType 不會將字串資料儲存在「主要」ndarray 資料緩衝區中。相反地,陣列緩衝區用於儲存有關字串資料在記憶體中儲存位置的中繼資料。這種差異意味著期望陣列緩衝區包含字串資料的程式碼將無法正常運作,並且需要更新以支援 StringDType

遺失資料支援#

通常字串資料集並不完整,並且需要一個特殊標籤來指示值遺失。依預設,StringDType 沒有對遺失值的任何特殊支援,除了空字串用於填充空陣列之外

>>> np.empty(3, dtype=StringDType())
array(['', '', ''], dtype=StringDType())

您可以選擇性地建立 StringDType 的實例,並透過將 na_object 作為初始化器的關鍵字引數來支援遺失值

>>> dt = StringDType(na_object=None)
>>> arr = np.array(["this array has", None, "as an entry"], dtype=dt)
>>> arr
array(['this array has', None, 'as an entry'],
      dtype=StringDType(na_object=None))
>>> arr[1] is None
True

na_object 可以是任何任意的 Python 物件。常見的選擇有 numpy.nanfloat('nan')None、專門用於表示遺失資料的物件(如 pandas.NA),或(希望是)唯一的字串(如 "__placeholder__")。

NumPy 對類似 NaN 的 sentinel 和字串 sentinel 有特殊的處理方式。

類似 NaN 的遺失資料 Sentinel#

類似 NaN 的 sentinel 會將自身作為算術運算的結果傳回。這包括 Python nan 浮點數和 Pandas 遺失資料 sentinel pd.NA。類似 NaN 的 sentinel 在字串運算中繼承了這些行為。這表示,例如,與任何其他字串相加的結果都是 sentinel

>>> dt = StringDType(na_object=np.nan)
>>> arr = np.array(["hello", np.nan, "world"], dtype=dt)
>>> arr + arr
array(['hellohello', nan, 'worldworld'], dtype=StringDType(na_object=nan))

依照浮點數陣列中 nan 的行為,類似 NaN 的 sentinel 會排序到陣列的末尾

>>> np.sort(arr)
array(['hello', 'world', nan], dtype=StringDType(na_object=nan))

字串遺失資料 Sentinel#

字串遺失資料值是 str 的實例或 str 的子類型。如果此類陣列傳遞給字串運算或轉換,「遺失」的條目會被視為具有字串 sentinel 給定的值。比較運算也會直接對遺失的條目使用 sentinel 值。

其他 Sentinel#

其他物件(例如 None)也支援作為遺失資料 sentinel。如果在使用此類 sentinel 的陣列中存在任何遺失資料,則字串運算會引發錯誤

>>> dt = StringDType(na_object=None)
>>> arr = np.array(["this array has", None, "as an entry"])
>>> np.sort(arr)
Traceback (most recent call last):
...
TypeError: '<' not supported between instances of 'NoneType' and 'str'

強制轉換非字串#

依預設,非字串資料會強制轉換為字串

>>> np.array([1, object(), 3.4], dtype=StringDType())
array(['1', '<object object at 0x7faa2497dde0>', '3.4'], dtype=StringDType())

如果不希望此行為,則可以建立 DType 的實例,方法是在初始化器中設定 coerce=False 來停用字串強制轉換

>>> np.array([1, object(), 3.4], dtype=StringDType(coerce=False))
Traceback (most recent call last):
...
ValueError: StringDType only allows string data when string coercion is disabled.

這允許在 NumPy 用於建立陣列的資料的同一次傳遞中進行嚴格的資料驗證。設定 coerce=True 會恢復允許強制轉換為字串的預設行為。

轉換為和從固定寬度字串轉換#

StringDType 支援 numpy.str_numpy.bytes_numpy.void 之間的往返轉換。轉換為固定寬度字串在需要在 ndarray 中記憶體對應字串時,或者在需要固定寬度字串以讀取和寫入具有已知最大字串長度的欄狀資料格式時最有用。

在所有情況下,轉換為固定寬度字串都需要指定允許的最大字串長度

>>> arr = np.array(["hello", "world"], dtype=StringDType())
>>> arr.astype(np.str_)  
Traceback (most recent call last):
...
TypeError: Casting from StringDType to a fixed-width dtype with an
unspecified size is not currently supported, specify an explicit
size for the output dtype instead.

The above exception was the direct cause of the following
exception:

TypeError: cannot cast dtype StringDType() to <class 'numpy.dtypes.StrDType'>.
>>> arr.astype("U5")
array(['hello', 'world'], dtype='<U5')

numpy.bytes_ 轉換最適用於已知僅包含 ASCII 字元的字串資料,因為此範圍之外的字元無法以 UTF-8 編碼中的單一位元組表示,因此會被拒絕。

任何有效的 Unicode 字串都可以轉換為 numpy.str_,但由於 numpy.str_ 對所有字元使用 32 位元 UCS4 編碼,因此對於可以透過更節省記憶體的編碼良好表示的真實世界文字資料來說,這通常會浪費記憶體。

此外,任何有效的 Unicode 字串都可以轉換為 numpy.void,直接將 UTF-8 位元組儲存在輸出陣列中

>>> arr = np.array(["hello", "world"], dtype=StringDType())
>>> arr.astype("V5")
array([b'\x68\x65\x6C\x6C\x6F', b'\x77\x6F\x72\x6C\x64'], dtype='|V5')

必須注意確保輸出陣列有足夠的空間來容納字串中的 UTF-8 位元組,因為 UTF-8 位元組流的大小(以位元組為單位)不一定與字串中的字元數相同。