文章目錄
- 結構化數據:NumPy 的結構化數組
- 探索結構化數組的創建
- 更高級的復合類型
- 記錄數組:結構化數組的變體
- 走向 Pandas
結構化數據:NumPy 的結構化數組
雖然我們的數據通常可以用同質數組很好地表示,但有時情況并非如此。本文將演示 NumPy 的結構化數組和記錄數組的用法,它們為復合的、異構的數據提供了高效的存儲方式。雖然這里展示的模式適用于簡單操作,但此類場景通常更適合使用 Pandas 的 DataFrame
。
import numpy as np
假設我們有幾類關于多個人的數據(比如姓名、年齡和體重),并且我們希望將這些值存儲起來以便在 Python 程序中使用。
我們可以將它們分別存儲在三個獨立的數組中:
name = ['Alice', 'Bob', 'Cathy', 'Doug']
age = [25, 45, 37, 19]
weight = [55.0, 85.5, 68.0, 61.5]
但是這樣做有些笨拙,實際使用起來并不方便,因為這里沒有任何內容能表明這三個數組是相關聯的;NumPy 的結構化數組允許我們通過使用單一結構來更自然地存儲所有這些數據。
回想一下,之前我們使用如下表達式創建了一個簡單的數組:
x = np.zeros(4, dtype=int)
我們同樣可以使用復合數據類型規范來創建結構化數組:
# 使用復合數據類型創建結構化數組
data = np.zeros(4, dtype={'names':('name', 'age', 'weight'),'formats':('U10', 'i4', 'f8')})
print(data.dtype)
[('name', '<U10'), ('age', '<i4'), ('weight', '<f8')]
這里的 'U10'
表示“最大長度為 10 的 Unicode 字符串”,'i4'
表示“4 字節(即 32 位)整數”,而 'f8'
表示“8 字節(即 64 位)浮點數”。
我們將在下一節討論這些類型代碼的其他選項。
現在我們已經創建了一個空的容器數組,可以用我們的值列表來填充這個數組了:
data['name'] = name
data['age'] = age
data['weight'] = weight
print(data)
[('Alice', 25, 55. ) ('Bob', 45, 85.5) ('Cathy', 37, 68. )('Doug', 19, 61.5)]
如我們所愿,數據現在已經方便地排列在一個結構化數組中。
結構化數組的一個便利之處在于,我們現在既可以通過索引,也可以通過字段名來引用數值:
# 獲取所有姓名
data['name']
array(['Alice', 'Bob', 'Cathy', 'Doug'], dtype='<U10')
# 獲取第一行數據
data[0]
np.void(('Alice', 25, 55.0), dtype=[('name', '<U10'), ('age', '<i4'), ('weight', '<f8')])
# 獲取最后一行的姓名
data[-1]['name']
np.str_('Doug')
通過布爾掩碼,我們甚至可以進行更復雜的操作,比如根據年齡進行篩選:
# 獲取小于30歲人員的姓名
data[data['age'] < 30]['name']
array(['Alice', 'Doug'], dtype='<U10')
如果你想進行比這些更復雜的操作,建議你考慮使用 Pandas 包。
正如你將看到的,Pandas 提供了一個 DataFrame
對象,這是一種基于 NumPy 數組構建的數據結構,提供了多種有用的數據操作功能,類似于你在這里看到的內容,而且功能遠不止這些。
探索結構化數組的創建
結構化數組的數據類型可以通過多種方式指定。
前面我們已經看到了字典的方法:
np.dtype({'names':('name', 'age', 'weight'),'formats':('U10', 'i4', 'f8')})
dtype([('name', '<U10'), ('age', '<i4'), ('weight', '<f8')])
為清晰起見,數值類型也可以使用 Python 類型或 NumPy 的 dtype
來指定:
np.dtype({'names':('name', 'age', 'weight'),'formats':((np.str_, 10), int, np.float32)})
dtype([('name', '<U10'), ('age', '<i8'), ('weight', '<f4')])
復合類型也可以用元組列表的形式指定:
np.dtype([('name', 'S10'), ('age', 'i4'), ('weight', 'f8')])
dtype([('name', 'S10'), ('age', '<i4'), ('weight', '<f8')])
如果你不關心類型的名稱,也可以僅用逗號分隔的字符串來指定類型:
np.dtype('S10,i4,f8')
dtype([('f0', 'S10'), ('f1', '<i4'), ('f2', '<f8')])
縮寫的字符串格式代碼可能一開始不太直觀,但它們基于簡單的原則。
第一個(可選)字符 <
或 >
,分別表示“小端序”或“大端序”,用于指定有效位的排序方式。
下一個字符指定數據的類型:字符、字節、整數、浮點數等(見下表)。
最后一個或多個字符表示對象的字節大小。
字符 | 描述 | 示例 |
---|---|---|
'b' | 字節 | np.dtype('b') |
'i' | 有符號整數 | np.dtype('i4') == np.int32 |
'u' | 無符號整數 | np.dtype('u1') == np.uint8 |
'f' | 浮點數 | np.dtype('f8') == np.int64 |
'c' | 復數浮點數 | np.dtype('c16') == np.complex128 |
'S' , 'a' | 字符串 | np.dtype('S5') |
'U' | Unicode 字符串 | np.dtype('U') == np.str_ |
'V' | 原始數據(void) | np.dtype('V') == np.void |
更高級的復合類型
可以定義更高級的復合類型。例如,你可以創建一種類型,其中每個元素包含一個數組或矩陣的值。
下面,我們將創建一個數據類型,其中包含一個 mat
字段,該字段是一個 3 × 3 3\times 3 3×3 的浮點型矩陣:
tp = np.dtype([('id', 'i8'), ('mat', 'f8', (3, 3))])
X = np.zeros(1, dtype=tp)
print(X[0])
print(X['mat'][0])
(0, [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]])
[[0. 0. 0.][0. 0. 0.][0. 0. 0.]]
現在,X
數組中的每個元素都包含一個 id
和一個 3 × 3 3\times 3 3×3 的矩陣。
為什么要使用這種方式,而不是簡單的多維數組,或者 Python 字典呢?
其中一個原因是,這種 NumPy 的 dtype
可以直接映射到 C 語言的結構體定義,因此包含數組內容的緩沖區可以被適當編寫的 C 程序直接訪問。
如果你需要為操作結構化數據的遺留 C 或 Fortran 庫編寫 Python 接口,結構化數組可以提供強大的接口能力。
記錄數組:結構化數組的變體
NumPy 還提供了記錄數組(np.recarray
類的實例),它們與前面介紹的結構化數組幾乎完全相同,但有一個額外的特性:字段可以作為屬性訪問,而不僅僅是作為字典鍵訪問。
回想一下,我們之前通過如下方式訪問示例數據集中的年齡字段:
data['age']
array([25, 45, 37, 19], dtype=int32)
如果我們將數據視為記錄數組(record array),就可以用更簡潔的方式訪問字段:
data_rec = data.view(np.recarray)
data_rec.age
array([25, 45, 37, 19], dtype=int32)
缺點是,對于記錄數組,即使使用相同的語法,訪問字段時也會有一些額外的開銷:
%timeit data['age']
%timeit data_rec['age']
%timeit data_rec.age
34 ns ± 0.458 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
481 ns ± 12.6 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
975 ns ± 12.8 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
這種更方便的記法是否值得(略微的)額外開銷,將取決于你的具體應用場景。
走向 Pandas
本文關于結構化數組和記錄數組的內容之所以安排在本部分的結尾,是因為它很好地引出了我們接下來要介紹的包:Pandas。
結構化數組在某些場景下非常有用,比如你需要用 NumPy 數組映射到 C、Fortran 或其他語言中的二進制數據格式時。
但在日常的結構化數據處理中,Pandas 包無疑是更好的選擇;在接下來的章節中,我們將深入探討它。