數據類型
另見:
Data type objects
# 數組類型和類型之間的轉換
Numpy支持比Python更多的數字類型。本部分顯示哪些是可用的,以及如何修改數組的數據類型。
數據類型 | 描述 |
---|---|
bool_ |
布爾(True或False),存儲為一個字節 |
int_ |
默認整數類型(與Clong 相同;通常是int64 或int32 ) |
INTC | 與Cint (通常為int32 或int64 )相同 |
INTP | 用於索引的整數(與Cssize_t 相同;通常是int32 或int64 ) |
INT8 | 字節(-128至127) |
INT16 | 整數(-32768至32767) |
INT32 | 整數(-2147483648至2147483647) |
Int64的 | 整數(-9223372036854775808至9223372036854775807) |
UINT8 | 無符號整數(0到255) |
UINT16 | 無符號整數(0到65535) |
UINT32 | 無符號整數(0到4294967295) |
UINT64 | 無符號整數(0到18446744073709551615) |
float_ | float64 的簡寫。 |
float16 | 半精度浮點:符號位,5位指數,10位尾數 |
FLOAT32 | 單精度浮點數:符號位,8位指數,23位尾數 |
float64 | 雙精度浮點:符號位,11位指數,52位尾數 |
complex_ | complex128 的簡寫。 |
complex64 | 復數,由兩個32位浮點數(實部和虛部) |
complex128 | 復數,由兩個64位浮點數(實部和虛部) |
除了intc
之外,還定義了平台相關的C整數類型short
,long
,longlong
。
Numpy數值類型是dtype
(data-type)對象的實例,每個類型具有唯一的特征。在你使用下面的語句導入NumPy后
>>> import numpy as np
這些類型可以用np.bool_
、np.float32
等方式訪問。
未在上表中列出的高級類型,請參見結構化數組部分。
有5個基本數字類型表示布爾(bool)、整數(int)、無符號整數(uint)、浮點數(float)和復數。那些在其名稱中具有數字的類型表示類型的位的大小(即,需要多少位來表示存儲器中的單個值)。某些類型,例如int
和intp
,根據平台(例如32位與64位機器)具有不同的位大小。當與存儲器直接尋址的低級代碼(例如C或Fortran)接口時,應該考慮這一點。
數據類型可以用作函數將python數字轉換為數組標量(有關說明,請參閱數組標量部分)、將python數字序列轉換為該類型的數組、或作為許多numpy函數或方法接受的dtype關鍵字參數。一些例子:
>>> import numpy as np >>> x = np.float32(1.0) >>> x 1.0 >>> y = np.int_([1,2,4]) >>> y array([1, 2, 4]) >>> z = np.arange(3, dtype=np.uint8) >>> z array([0, 1, 2], dtype=uint8)
數組類型也可以由字符代碼引用,主要是為了保持與舊數據包(如Numeric)的向后兼容性。有些文檔可能仍然提及這些文檔,例如:
>>> np.array([1, 2, 3], dtype='f') array([ 1., 2., 3.], dtype=float32)
我們建議使用dtype對象。
要轉換數組的類型,請使用.astype()方法(首選)或類型本身作為函數。例如:
>>> z.astype(float) array([ 0., 1., 2.]) >>> np.int8(z) array([0, 1, 2], dtype=int8)
請注意,上面我們使用Python float對象作為dtype。NumPy 知道int指代np.int_、bool表示np.bool_、 float為np.float_以及complex為np.complex_。其他數據類型沒有Python等效的類型。
要確定數組的類型,請查看dtype屬性:
>>> z.dtype dtype('uint8')
dtype對象還包含有關該類型的信息,例如其位寬和字節順序。數據類型也可以間接用於查詢類型的屬性,例如是否為整數:
>>> d = np.dtype(int) >>> d dtype('int32') >>> np.issubdtype(d, int) True >>> np.issubdtype(d, float) False
# 數組標量
Numpy通常返回數組的元素作為數組標量(與相關dtype的標量)。數組標量與Python標量不同,但大多數情況下它們可以互換使用(主要例外是Python版本比v2.x更早的版本,其中整數數組標量不能充當列表和元組的索引)。有一些例外情況,比如代碼需要非常特定的標量屬性,或者當它特別檢查某個值是否為Python標量時。通常,使用相應的Python類型函數(例如int
、float
、complex
、str
,unicode
)將數組標量顯式轉換為Python標量就很容易解決問題。
使用數組標量的主要優點是它們保留數組類型(Python可能沒有可用的匹配標量類型,例如int16
)。因此,使用數組標量可以確保數組和標量之間的相同行為,而不管該值是否在數組中。NumPy標量也有很多和數組相同的方法。
# 擴展精度
Python的浮點數通常是64位浮點數,幾乎相當於np.float64
。在某些不常見的情況下,使用Python的浮點數更精確。這在numpy中是否可行,取決於硬件和開發的環境:具體來說,x86機器提供80位精度的硬件浮點數,大多數C編譯器提供它為long double
類型,MSVC(Windows版本的標准)讓long double
和double
(64位)完全一樣。Numpy使編譯器的long double
為np.longdouble
(復數為np.clongdouble
)。你可以用np.finfo(np.longdouble)
找出你的numpy提供的是什么。
Numpy 不提供比 C 語言里 long double
更高精度的數據類型,特別是 128 位的IEEE 四倍精度的數據類型(FORTRAN的 REAL*16
) 不可用。
為了有效地進行內存的校准,np.longdouble
通常以零位進行填充,即96或者128位, 哪個更有效率取決於硬件和開發環境;通常在32位系統上它們被填充到96位,而在64位系統上它們通常被填充到128位。np.longdouble
被填充到系統默認值;為需要特定填充的用戶提供了np.float96
和np.float128
。盡管它們的名稱是這樣叫的, 但是np.float96
和np.float128
只提供與np.longdouble
一樣的精度, 即大多數x86機器上的80位和標准Windows版本中的64位。
請注意,即使np.longdouble
提供比python float
更多的精度,也很容易失去額外的精度,因為python通常強制值通過float
傳遞值。例如,%
格式操作符要求將其參數轉換為標准python類型,因此即使請求了許多小數位,也不可能保留擴展精度。使用值1 + np.finfo(np.longdouble).eps
測試你的代碼非常有用。
數組創建
另見:
Array creation routines
# 簡介
一般有5個機制創建數組:
- 從其他Python結構(例如,列表,元組)轉換
- numpy原生數組的創建(例如,arange、ones、zeros等)
- 從磁盤讀取數組,無論是標准格式還是自定義格式
- 通過使用字符串或緩沖區從原始字節創建數組
- 使用特殊庫函數(例如,random)
- 本節不包括復制、join或以其他方式擴展或改變現有數組的方法。它也不會涉及創建對象數組或結構化數組。這兩個都在它們自己的部分講述。
本節不包括復制、join或以其他方式擴展或改變現有數組的方法。它也不會涉及創建對象數組或結構化數組。這兩個都在它們自己的部分講述。
# 將Python array_like對象轉換為Numpy數組
通常,在Python中排列成array-like結構的數值數據可以通過使用array()函數轉換為數組。最明顯的例子是列表和元組。有關其使用的詳細信息,請參閱array()的文檔。一些對象可能支持數組協議並允許以這種方式轉換為數組。找出對象是否可以使用array()轉換為一個數組numpy 數組的簡單方法很簡單,只要交互式試一下,看看它是否工作!(Python方式)。
例子:
>>> x = np.array([2,3,1,0]) >>> x = np.array([2, 3, 1, 0]) >>> x = np.array([[1,2.0],[0,0],(1+1j,3.)]) # note mix of tuple and lists, and types >>> x = np.array([[ 1.+0.j, 2.+0.j], [ 0.+0.j, 0.+0.j], [ 1.+1.j, 3.+0.j]])
# Numpy原生數組的創建
Numpy內置了從頭開始創建數組的函數:
zeros(shape)將創建一個用指定形狀用0填充的數組。默認的dtype是float64。
>>> np.zeros((2, 3)) array([[ 0., 0., 0.], [ 0., 0., 0.]])
ones(shape)將創建一個用1個值填充的數組。它在所有其他方面與zeros相同。
arange()將創建具有有規律遞增值的數組。檢查文檔字符串以獲取有關可以使用的各種方式的完整信息。這里給出幾個例子:
>>> np.arange(10) array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> np.arange(2, 10, dtype=np.float) array([ 2., 3., 4., 5., 6., 7., 8., 9.]) >>> np.arange(2, 3, 0.1) array([ 2. , 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9])
請注意,關於用戶應該注意的最后用法在arange文檔字符串中有一些細微的描述。
linspace()將創建具有指定數量元素的數組,並在指定的開始值和結束值之間平均間隔。例如:
>>> np.linspace(1., 4., 6) array([ 1. , 1.6, 2.2, 2.8, 3.4, 4. ])
這個創建函數的優點是可以保證元素的數量以及開始和結束點,對於任意的開始,停止和步驟值,arange()通常不會這樣做。
indices()將創建一組數組(堆積為一個更高維的數組),每個維度一個,每個維度表示該維度中的變化。一個例子說明比口頭描述要好得多:
>>> np.indices((3,3)) array([[[0, 0, 0], [1, 1, 1], [2, 2, 2]], [[0, 1, 2], [0, 1, 2], [0, 1, 2]]])
這對於評估常規網格上多個維度的功能特別有用。
# 從磁盤讀取數組
這大概是大陣列創建的最常見情況。當然,細節很大程度上取決於磁盤上的數據格式,所以本節只能給出如何處理各種格式的一般指示。
# 標准二進制格式
各種字段都有陣列數據的標准格式。下面列出了那些已知的Python庫來讀取它們並返回numpy數組(可能有其他可能讀取並轉換為numpy數組的其他數據,因此請檢查最后一節)
HDF5: PyTables FITS: PyFITS
無法直接讀取但不易轉換的格式示例是像PIL這樣的庫支持的格式(能夠讀取和寫入許多圖像格式,如jpg,png等)。
# 常見ASCII格式
逗號分隔值文件(CSV)被廣泛使用(以及Excel等程序的導出和導入選項)。有很多方法可以在Python中閱讀這些文件。python中有CSV函數和pylab函數(matplotlib的一部分)。
更多通用的ascii文件可以在scipy中使用io軟件包讀取。
# 自定義二進制格式
有各種各樣的方法可以使用。如果文件具有相對簡單的格式,那么可以編寫一個簡單的I / O庫,並使用numpy fromfile()函數和.tofile()方法直接讀取和寫入numpy數組(盡管介意您的字節序)!如果存在一個讀取數據的良好C或C ++庫,可以使用各種技術來封裝該庫,但這肯定要做得更多,並且需要更多的高級知識才能與C或C ++接口。
# 使用特殊庫
有些庫可用於生成特殊用途的數組,並且不可能列舉所有這些數組。最常見的用途是隨機使用許多數組生成函數,這些函數可以生成隨機值數組,以及一些用於生成特殊矩陣(例如對角線)的效用函數。
NumPy I/O操作
# 使用genfromtxt導入數據
NumPy提供了幾個函數來根據表格數據創建數組。我們將重點放在genfromtxt
函數上。
In a nutshell, genfromtxt
runs two main loops. 第一個循環以字符串序列轉換文件的每一行。第二個循環將每個字符串轉換為適當的數據類型。這種機制比單一循環慢,但提供了更多的靈活性。特別的, genfromtxt
考慮到缺失值的情況, 其他更簡單的方法如loadtxt
無法做到這點.
注意
舉例時,我們將使用以下約定:
>>> import numpy as np >>> from io import BytesIO
# 定義輸入
genfromtxt
的唯一強制參數是數據的來源。它可以是一個字符串,一串字符串或一個生成器。如果提供了單個字符串,則假定它是本地或遠程文件的名稱,或者帶有read
方法的開放文件類對象,例如文件或StringIO.StringIO
對象。如果提供了字符串列表或生成器返回字符串,則每個字符串在文件中被視為一行。當傳遞遠程文件的URL時,該文件將自動下載到當前目錄並打開。
識別的文件類型是文本文件和檔案。目前,該功能可識別gzip
和bz2
(bzip2)檔案。歸檔文件的類型由文件的擴展名決定:如果文件名以'.gz'
結尾,則需要一個gzip
歸檔文件;如果它以'bz2'
結尾,則假定bzip2
存檔。
# 將行拆分為列
# delimiter
參數
一旦文件被定義並打開進行讀取,genfromtxt
會將每個非空行分割為一串字符串。 空的或注釋的行只是略過。 delimiter
關鍵字用於定義拆分應該如何進行。
通常,單個字符標記列之間的分隔。例如,逗號分隔文件(CSV)使用逗號(,
)或分號(;
)作為分隔符:
>>> data = "1, 2, 3\n4, 5, 6" >>> np.genfromtxt(BytesIO(data), delimiter=",") array([[ 1., 2., 3.], [ 4., 5., 6.]])
另一個常用的分隔符是"\t",即制表符。但是,我們不限於單個字符,任何字符串都可以。默認情況下,genfromtxt
假定delimiter=None
,這意味着該行沿着空白區域(包括制表符)分割,並且連續的空白區域被視為單個空白區域。
或者,我們可能正在處理一個固定寬度的文件,其中列被定義為給定數量的字符。在這種情況下,我們需要將delimiter
設置為單個整數(如果所有列的大小相同)或整數序列(如果列的大小可能不同):
>>> data = " 1 2 3\n 4 5 67\n890123 4" >>> np.genfromtxt(BytesIO(data), delimiter=3) array([[ 1., 2., 3.], [ 4., 5., 67.], [ 890., 123., 4.]]) >>> data = "123456789\n 4 7 9\n 4567 9" >>> np.genfromtxt(BytesIO(data), delimiter=(4, 3, 2)) array([[ 1234., 567., 89.], [ 4., 7., 9.], [ 4., 567., 9.]])
# autostrip
參數
默認情況下,當一行被分解為一系列字符串時,單個條目不會被剝離前導空白或尾隨空白。通過將可選參數autostrip設置為值True,可以覆蓋此行為:
>>> data = "1, abc , 2\n 3, xxx, 4" >>> # Without autostrip >>> np.genfromtxt(BytesIO(data), delimiter=",", dtype="|S5") array([['1', ' abc ', ' 2'], ['3', ' xxx', ' 4']], dtype='|S5') >>> # With autostrip >>> np.genfromtxt(BytesIO(data), delimiter=",", dtype="|S5", autostrip=True) array([['1', 'abc', '2'], ['3', 'xxx', '4']], dtype='|S5')
# comments
參數
可選參數comments
用於定義標記注釋開始的字符串。默認情況下,genfromtxt
假定comments='#'
。評論標記可能發生在線上的任何地方。評論標記之后的任何字符都會被忽略:
>>> data = """# ... # Skip me ! ... # Skip me too ! ... 1, 2 ... 3, 4 ... 5, 6 #This is the third line of the data ... 7, 8 ... # And here comes the last line ... 9, 0 ... """ >>> np.genfromtxt(BytesIO(data), comments="#", delimiter=",") [[ 1. 2.] [ 3. 4.] [ 5. 6.] [ 7. 8.] [ 9. 0.]]
注意!
這種行為有一個明顯的例外:如果可選參數names=True
,則會檢查第一條注釋行的名稱。
# 跳過直線並選擇列
# skip_header
和skip_footer
參數
文件中存在標題可能會妨礙數據處理。在這種情況下,我們需要使用skip_header
可選參數。此參數的值必須是一個整數,與執行任何其他操作之前在文件開頭跳過的行數相對應。同樣,我們可以使用skip_footer
屬性跳過文件的最后一行n
,並給它一個n
的值:
>>> data = "\n".join(str(i) for i in range(10)) >>> np.genfromtxt(BytesIO(data),) array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]) >>> np.genfromtxt(BytesIO(data), ... skip_header=3, skip_footer=5) array([ 3., 4.])
默認情況下,skip_header=0
和skip_footer=0
,這意味着不會跳過任何行。
# usecols
參數
在某些情況下,我們對數據的所有列不感興趣,但只有其中的一小部分。我們可以用usecols
參數選擇要導入的列。該參數接受與要導入的列的索引相對應的單個整數或整數序列。請記住,按照慣例,第一列的索引為0。負整數的行為與常規Python負向索引相同。
例如,如果我們只想導入第一列和最后一列,我們可以使用usecols =(0, -1)
:
>>> data = "1 2 3\n4 5 6" >>> np.genfromtxt(BytesIO(data), usecols=(0, -1)) array([[ 1., 3.], [ 4., 6.]])
如果列有名稱,我們也可以通過將它們的名稱提供給usecols
參數來選擇要導入哪些列,可以將其作為字符串序列或逗號分隔字符串:
>>> data = "1 2 3\n4 5 6" >>> np.genfromtxt(BytesIO(data), ... names="a, b, c", usecols=("a", "c")) array([(1.0, 3.0), (4.0, 6.0)], dtype=[('a', '<f8'), ('c', '<f8')]) >>> np.genfromtxt(BytesIO(data), ... names="a, b, c", usecols=("a, c")) array([(1.0, 3.0), (4.0, 6.0)], dtype=[('a', '<f8'), ('c', '<f8')])
# 選擇數據的類型
控制我們從文件中讀取的字符串序列如何轉換為其他類型的主要方法是設置dtype
參數。這個參數的可接受值是:
- 單一類型,如
dtype=float
。除非使用names
參數將名稱與每個列關聯(見下文),否則輸出將是給定dtype的2D格式。請注意,dtype=float
是genfromtxt
的默認值。 - 一系列類型,如
dtype =(int, float, float)
。 - 逗號分隔的字符串,例如
dtype="i4,f8,|S3"
。 - 一個包含兩個鍵
'names'
和'formats'
的字典。 - a sequence of tuples
(name, type)
, such asdtype=[('A', int), ('B', float)]
. - 現有的
numpy.dtype
對象。 - 特殊值
None
。在這種情況下,列的類型將根據數據本身確定(見下文)。
在所有情況下,除了第一種情況,輸出將是一個帶有結構化dtype的一維數組。這個dtype與序列中的項目一樣多。字段名稱由names
關鍵字定義。
當dtype=None
時,每列的類型由其數據迭代確定。我們首先檢查一個字符串是否可以轉換為布爾值(也就是說,如果字符串在小寫字母中匹配true
或false
);然后是否可以將其轉換為整數,然后轉換為浮點數,然后轉換為復數並最終轉換為字符串。通過修改StringConverter
類的默認映射器可以更改此行為。
為方便起見,提供了dtype=None選項。但是,它明顯比顯式設置dtype要慢。
# 設置名稱
# names
參數
處理表格數據時的一種自然方法是為每列分配一個名稱。如前所述,第一種可能性是使用明確的結構化dtype。
>>> data = BytesIO("1 2 3\n 4 5 6") >>> np.genfromtxt(data, dtype=[(_, int) for _ in "abc"]) array([(1, 2, 3), (4, 5, 6)], dtype=[('a', '<i8'), ('b', '<i8'), ('c', '<i8')])
另一種更簡單的可能性是將names
關鍵字與一系列字符串或逗號分隔的字符串一起使用:
>>> data = BytesIO("1 2 3\n 4 5 6") >>> np.genfromtxt(data, names="A, B, C") array([(1.0, 2.0, 3.0), (4.0, 5.0, 6.0)], dtype=[('A', '<f8'), ('B', '<f8'), ('C', '<f8')])
在上面的例子中,我們使用了默認情況下dtype=float
的事實。通過給出一個名稱序列,我們強制輸出到一個結構化的dtype。
我們有時可能需要從數據本身定義列名。在這種情況下,我們必須使用names
關鍵字的值為True
。這些名字將從第一行(在skip_header
之后)被讀取,即使該行被注釋掉:
>>> data = BytesIO("So it goes\n#a b c\n1 2 3\n 4 5 6") >>> np.genfromtxt(data, skip_header=1, names=True) array([(1.0, 2.0, 3.0), (4.0, 5.0, 6.0)], dtype=[('a', '<f8'), ('b', '<f8'), ('c', '<f8')])
names
的默認值為None
。如果我們給關鍵字賦予任何其他值,新名稱將覆蓋我們可能用dtype定義的字段名稱:
>>> data = BytesIO("1 2 3\n 4 5 6") >>> ndtype=[('a',int), ('b', float), ('c', int)] >>> names = ["A", "B", "C"] >>> np.genfromtxt(data, names=names, dtype=ndtype) array([(1, 2.0, 3), (4, 5.0, 6)], dtype=[('A', '<i8'), ('B', '<f8'), ('C', '<i8')])
# defaultfmt
參數
如果 names=None
的時候,只是預計會有一個結構化的dtype,它的名稱將使用標准的NumPy默認值 "f%i"
來定義,會產生例如f0
,f1
等名稱:
>>> data = BytesIO("1 2 3\n 4 5 6") >>> np.genfromtxt(data, dtype=(int, float, int)) array([(1, 2.0, 3), (4, 5.0, 6)], dtype=[('f0', '<i8'), ('f1', '<f8'), ('f2', '<i8')])
同樣,如果我們沒有提供足夠的名稱來匹配dtype的長度,缺少的名稱將使用此默認模板進行定義:
>>> data = BytesIO("1 2 3\n 4 5 6") >>> np.genfromtxt(data, dtype=(int, float, int), names="a") array([(1, 2.0, 3), (4, 5.0, 6)], dtype=[('a', '<i8'), ('f0', '<f8'), ('f1', '<i8')])
我們可以使用defaultfmt
參數覆蓋此默認值,該參數采用任何格式字符串:
>>> data = BytesIO("1 2 3\n 4 5 6") >>> np.genfromtxt(data, dtype=(int, float, int), defaultfmt="var_%02i") array([(1, 2.0, 3), (4, 5.0, 6)], dtype=[('var_00', '<i8'), ('var_01', '<f8'), ('var_02', '<i8')])
注意!
我們需要記住,僅當預期一些名稱但未定義時才使用defaultfmt
。
# 驗證名稱
具有結構化dtype的NumPy數組也可以被視為recarray
,其中可以像訪問屬性一樣訪問字段。因此,我們可能需要確保字段名稱不包含任何空格或無效字符,或者它不對應於標准屬性的名稱(如size
或shape
),這會混淆解釋者。genfromtxt
接受三個可選參數,這些參數可以更好地控制名稱:
deletechars
給出一個字符串,將所有必須從名稱中刪除的字符組合在一起。默認情況下,無效字符是~!@#$%^&*()-=+~\|]}[{';: /?.>,<
excludelist
給出要排除的名稱列表,如return
,file
,'_'
)。case_sensitive
是否區分大小寫(case_sensitive=True
),轉換為大寫(case_sensitive=False
或case_sensitive='upper'
)或小寫(case_sensitive='lower'
)。
# 調整轉換
# converters
參數
通常,定義一個dtype足以定義字符串序列必須如何轉換。但是,有時可能需要一些額外的控制。例如,我們可能希望確保格式為YYYY/MM/DD
的日期轉換為datetime
對象,或者像xx%
正確轉換為0到1之間的浮點數。在這種情況下,我們應該使用converters
參數定義轉換函數。
該參數的值通常是以列索引或列名稱作為關鍵字的字典,並且轉換函數作為值。這些轉換函數可以是實際函數或lambda函數。無論如何,它們只應接受一個字符串作為輸入,並只輸出所需類型的單個元素。
在以下示例中,第二列從代表百分比的字符串轉換為0和1之間的浮點數:
>>> convertfunc = lambda x: float(x.strip("%"))/100. >>> data = "1, 2.3%, 45.\n6, 78.9%, 0" >>> names = ("i", "p", "n") >>> # General case ..... >>> np.genfromtxt(BytesIO(data), delimiter=",", names=names) array([(1.0, nan, 45.0), (6.0, nan, 0.0)], dtype=[('i', '<f8'), ('p', '<f8'), ('n', '<f8')])
我們需要記住,默認情況下,dtype=float
。因此,對於第二列期望浮點數。但是,字符串'2.3%'
和'78.9%
無法轉換為浮點數,我們最終改為使用np.nan
。現在讓我們使用一個轉換器:
>>> # Converted case ... >>> np.genfromtxt(BytesIO(data), delimiter=",", names=names, ... converters={1: convertfunc}) array([(1.0, 0.023, 45.0), (6.0, 0.78900000000000003, 0.0)], dtype=[('i', '<f8'), ('p', '<f8'), ('n', '<f8')])
通過使用第二列("p"
)作為關鍵字而不是其索引(1)的名稱,可以獲得相同的結果:
>>> # Using a name for the converter ... >>> np.genfromtxt(BytesIO(data), delimiter=",", names=names, ... converters={"p": convertfunc}) array([(1.0, 0.023, 45.0), (6.0, 0.78900000000000003, 0.0)], dtype=[('i', '<f8'), ('p', '<f8'), ('n', '<f8')])
轉換器也可以用來為缺少的條目提供默認值。在以下示例中,如果字符串為空,則轉換器convert
會將已剝離的字符串轉換為相應的浮點型或轉換為-999。我們需要明確地從空白處去除字符串,因為它並未默認完成:
>>> data = "1, , 3\n 4, 5, 6" >>> convert = lambda x: float(x.strip() or -999) >>> np.genfromtxt(BytesIO(data), delimiter=",", ... converters={1: convert}) array([[ 1., -999., 3.], [ 4., 5., 6.]])
# 使用缺失值和填充值
我們嘗試導入的數據集中可能缺少一些條目。在前面的例子中,我們使用轉換器將空字符串轉換為浮點。但是,用戶定義的轉換器可能會很快變得繁瑣,難以管理。
genfromtxt
函數提供了另外兩種補充機制:missing_values
參數用於識別丟失的數據,第二個參數filling_values
用於處理這些缺失的數據。
# missing_values
默認情況下,任何空字符串都被標記為缺失。我們也可以考慮更復雜的字符串,比如"N/A"
或"???"
代表丟失或無效的數據。missing_values
參數接受三種值:
- 一個字符串或逗號分隔的字符串
該字符串將用作所有列缺失數據的標記- 一串字符串
在這種情況下,每個項目都按順序與列關聯。- 一本字典
字典的值是字符串或字符串序列。相應的鍵可以是列索引(整數)或列名稱(字符串)。另外,可以使用特殊鍵None來定義適用於所有列的默認值。
# filling_values
我們知道如何識別丟失的數據,但我們仍然需要為這些丟失的條目提供一個值。默認情況下,根據此表根據預期的dtype確定此值:
我們知道如何識別丟失的數據,但我們仍然需要為這些丟失的條目提供一個值。默認情況下,根據此表根據預期的dtype確定此值:
預期類型 | 默認 |
---|---|
bool |
False |
int |
-1 |
float |
np.nan |
complex |
np.nan+0j |
string |
'???' |
通過filling_values
可選參數,我們可以更好地控制缺失值的轉換。像missing_values
一樣,此參數接受不同類型的值:
- 一個單一的價值
這將是所有列的默認值- 一系列值
每個條目都是相應列的默認值- 一本字典
每個鍵可以是列索引或列名稱,並且相應的值應該是單個對象。我們可以使用特殊鍵None為所有列定義默認值。
在下面的例子中,我們假設缺少的值在第一列中用"N/A"
標記,並由"???"
在第三欄。如果它們出現在第一列和第二列中,我們希望將這些缺失值轉換為0,如果它們出現在最后一列中,則將它們轉換為-999:
>>> data = "N/A, 2, 3\n4, ,???" >>> kwargs = dict(delimiter=",", ... dtype=int, ... names="a,b,c", ... missing_values={0:"N/A", 'b':" ", 2:"???"}, ... filling_values={0:0, 'b':0, 2:-999}) >>> np.genfromtxt(BytesIO(data), **kwargs) array([(0, 2, 3), (4, 0, -999)], dtype=[('a', '<i8'), ('b', '<i8'), ('c', '<i8')])
# usemask
我們也可能想通過構造一個布爾掩碼來跟蹤丟失數據的發生,其中True
條目缺少數據,否則False
。為此,我們只需將可選參數usemask
設置為True
(默認值為False
)。輸出數組將成為MaskedArray
。
# 快捷方式功能
In addition to genfromtxt
, the numpy.lib.io
module provides several convenience functions derived from genfromtxt
. 這些函數與原始函數的工作方式相同,但它們具有不同的默認值。
ndfromtxt
:始終設置usemask=False
。輸出總是一個標准的numpy.ndarray
。mafromtxt
:始終設置usemask=True
。輸出總是一個MaskedArray
recfromtxt
:返回一個標准的numpy.recarray
(如果usemask=False
)或一個MaskedRecords
數組(如果usemaske=True
) 。默認的dtype是dtype=None
,這意味着每列的類型將被自動確定。recfromcsv
:類似於recfromtxt
,但使用默認的delimiter=","
。
索引
另見:
Indexing routines
數組索引指的是使用方括號([])來索引數組值。有很多選項來索引,這使numpy索引很強大,但功能上的強大也帶來一些復雜性和潛在的混亂。本部分只是與索引相關的各種選項和問題的概述。除了單個元素索引之外,大多數這些選項的細節可以在相關章節中找到。
# 賦值與引用
以下大多數示例顯示了在引用數組中的數據時使用索引。這些示例在給數組賦值時同樣適用。有關如何賦值的具體示例和說明,請參閱末尾的部分。
# 單個元素索引
一維數組的單元素索引是人們所期望的。它的工作方式與其他標准Python序列完全相同。它是從0開始的,並且接受負索引來從數組的結尾進行索引。
>>> x = np.arange(10) >>> x[2] 2 >>> x[-2] 8
與列表和元組不同,numpy數組支持多維數組的多維索引。這意味着沒有必要將每個維度的索引分成它自己的一組方括號。
>>> x.shape = (2,5) # now x is 2-dimensional >>> x[1,3] 8 >>> x[1,-1] 9
請注意,如果索引索引數量少於維度的多維數組,則會得到一個子維數組。例如:
>>> x[0] array([0, 1, 2, 3, 4])
也就是說,指定的每個索引都會選擇與所選維度的其余部分相對應的數組。在上面的例子中,選擇0表示長度為5的剩余維度未指定,並且返回的是具有該維度和大小的數組。必須注意的是,返回的數組不是原始數據的副本,而是指向與原始數組相同的內存值。在這種情況下,返回第一個位置(0)處的一維數組。因此,在返回的數組上使用單個索引會導致返回一個元素。那是:
>>> x[0][2] 2
因此,請注意,x [0,2] = x [0] [2]
, 但是第二種情況效率更低,因為一個新的臨時數組在第一個索引后創建了,這個臨時數組隨后才被2這個數字索引。
請注意那些用於IDL或Fortran內存順序的索引。Numpy使用C順序索引。這意味着最后一個索引通常代表了最快速變化的內存位置,與Fortran或IDL不同,第一個索引代表內存中變化最快的位置。這種差異代表着很大的混亂的可能性。
# 其他索引選項
可以對數組進行切片和步進,以提取具有相同數量維數的數組,但其大小與原始數據不同。切片和跨步的工作方式與對列表和元組完全相同,除此之外它們還可以應用於多個維度。幾個例子說明了最好的:
>>> x = np.arange(10) >>> x[2:5] array([2, 3, 4]) >>> x[:-7] array([0, 1, 2]) >>> x[1:7:2] array([1, 3, 5]) >>> y = np.arange(35).reshape(5,7) >>> y[1:5:2,::3] array([[ 7, 10, 13], [21, 24, 27]])
請注意,數組切片不會復制內部數組數據,但也會產生原始數據的新視圖。
可以用其他數組來索引數組,以便從數組中選擇相應列表的值來產生新的數組。有兩種不同的方式來完成這一點。一個使用一個或多個索引值數組。另一個涉及給出一個適當形狀的布爾數組來指示要選擇的值。索引數組是一個非常強大的工具,可以避免在數組中循環各個元素,從而大大提高性能。
可以使用特殊功能通過索引有效增加數組中的維數,以便生成的數組獲得在表達式或特定函數中使用所需的形狀。
# 索引數組
Numpy數組可以被其他數組(或任何其他可轉換為數組的類似序列的對象,例如除了元組之外的列表)索引;有關為什么會出現這種情況,請參閱本文檔的末尾。索引數組的使用范圍從簡單,直接的情況到復雜的難以理解的情況。對於索引數組的所有情況,返回的是原始數據的副本,而不是片段獲取的視圖。
索引數組必須是整數類型。數組中的每個值指示數組中要使用哪個值來代替索引。為了顯示:
>>> x = np.arange(10,1,-1) >>> x array([10, 9, 8, 7, 6, 5, 4, 3, 2]) >>> x[np.array([3, 3, 1, 8])] array([7, 7, 9, 2])
由值3,3,1和8組成的索引數組相應地創建了一個長度為4的數組(與索引數組相同),其中每個索引被索引數組在索引中具有的值替換。
負值是允許的,並且與單個索引或片一樣工作。
>>> x[np.array([3,3,-3,8])] array([7, 7, 4, 2])
索引值超出范圍是錯誤的:
>>> x[np.array([3, 3, 20, 8])] <type 'exceptions.IndexError'>: index 20 out of bounds 0<=index<9
一般來說,使用索引數組時返回的是與索引數組具有相同形狀的數組,但是索引數組的類型和值。作為一個例子,我們可以使用多維索引數組來代替:
>>> x[np.array([[1,1],[2,3]])] array([[9, 9], [8, 7]])
# 索引多維數組
對多維數組進行索引時,情況會變得更加復雜,特別是對於多維索引數組。這些往往是更常用的用途,但它們是允許的,並且它們對於一些問題是有用的。我們將從最簡單的多維情況開始(使用前面例子中的數組y):
>>> y[np.array([0,2,4]), np.array([0,1,2])] array([ 0, 15, 30])
在這種情況下,如果索引數組具有匹配的形狀,並且索引數組的每個維都有一個索引數組,則結果數組具有與索引數組相同的形狀,並且這些值對應於每個索引集的索引在索引數組中的位置。在此示例中,兩個索引數組的第一個索引值為0,因此結果數組的第一個值為y [0,0]。下一個值是y [2,1],最后一個是y [4,2]。
如果索引數組不具有相同的形狀,則會嘗試將它們廣播到相同的形狀。如果它們不能以相同的形狀廣播,則會引發異常:
>>> y[np.array([0,2,4]), np.array([0,1])] <type 'exceptions.ValueError'>: shape mismatch: objects cannot be
broadcast to a single shape
廣播機制允許索引數組與其他索引的標量組合。結果是標量值用於索引數組的所有對應值:
>>> y[np.array([0,2,4]), 1] array([ 1, 15, 29])
跳到復雜性的下一個級別,可以只用索引數組部分索引數組。理解在這種情況下會發生什么需要一些思考。例如,如果我們只使用一個索引數組與y:
>>> y[np.array([0,2,4])] array([[ 0, 1, 2, 3, 4, 5, 6], [14, 15, 16, 17, 18, 19, 20], [28, 29, 30, 31, 32, 33, 34]])
什么結果是一個新的數組的結構,其中索引數組的每個值從被索引的數組中選擇一行,並且結果數組具有結果形狀(行的大小,數字索引元素)。
這可能有用的一個示例是用於顏色查找表,我們想要將圖像的值映射到RGB三元組中進行顯示。查找表可能有一個形狀(nlookup,3)。使用帶有dtype = np.uint8(或任何整數類型,只要值與查找表的邊界)形狀(ny,nx)的圖像索引這樣一個數組將導致一個形狀數組(ny,nx, 3)RGB值的三倍與每個像素位置相關聯。
通常,resulant數組的形狀將是索引數組形狀(或所有索引數組廣播的形狀)與索引數組中任何未使用的維(未索引的維)的形狀的串聯。
# 布爾值或掩碼索引數組
用作索引的布爾數組的處理方式完全不同於索引數組。布爾數組的形狀必須與被索引數組的初始維相同。在最直接的情況下,布爾數組具有相同的形狀:
>>> b = y>20 >>> y[b] array([21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34])
與整數索引數組不同,在布爾情況下,結果是一維數組,其中包含索引數組中所有對應於布爾數組中所有真實元素的元素。索引數組中的元素始終以行優先(C樣式)順序進行迭代和返回。結果也與y[np.nonzero(b)]
相同。與索引數組一樣,返回的是數據的副本,而不是像切片一樣獲得的視圖。
如果y比b的維數更高,則結果將是多維的。例如:
>>> b[:,5] # use a 1-D boolean whose first dim agrees with the first dim of y array([False, False, False, True, True], dtype=bool) >>> y[b[:,5]] array([[21, 22, 23, 24, 25, 26, 27], [28, 29, 30, 31, 32, 33, 34]])
這里第4行和第5行是從索引數組中選擇出來的,並組合起來構成一個二維數組。
一般來說,當布爾數組的維數小於被索引的數組時,這相當於y [b,...],這意味着y由b索引,后面跟着很多:如填充年。因此,結果的形狀是一維,其中包含布爾數組的True元素的數量,然后是索引的數組的其余維度。
例如,使用具有四個真實元素的形狀(2,3)的二維布爾陣列來從三維形狀(2,3,5)陣列中選擇行會得到形狀的二維結果(4 ,5):
>>> x = np.arange(30).reshape(2,3,5) >>> x array([[[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]], [[15, 16, 17, 18, 19], [20, 21, 22, 23, 24], [25, 26, 27, 28, 29]]]) >>> b = np.array([[True, True, False], [False, True, True]]) >>> x[b] array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [20, 21, 22, 23, 24], [25, 26, 27, 28, 29]])
有關更多詳細信息,請參閱有關數組索引的numpy參考文檔。
# 組合索引和切片
索引數組可以與切片組合。例如:
>>> y[np.array([0,2,4]),1:3] array([[ 1, 2], [15, 16], [29, 30]])
實際上,切片被轉換為索引數組np.array([[1,2]])(形狀(1,2)),該數組與索引數組一起廣播以產生形狀的結果數組(3,2) 。
同樣,切片可以與廣播布爾指數結合使用:
>>> y[b[:,5],1:3] array([[22, 23], [29, 30]])
# 結構化索引工具
為了便於將數組形狀與表達式和賦值相匹配,可以在數組索引中使用np.newaxis對象來添加大小為1的新維度。例如:
>>> y.shape (5, 7) >>> y[:,np.newaxis,:].shape (5, 1, 7)
請注意,數組中沒有新元素,只是增加了維度。這可以很方便地以一種其他方式需要明確重塑操作的方式組合兩個數組。例如:
>>> x = np.arange(5) >>> x[:,np.newaxis] + x[np.newaxis,:] array([[0, 1, 2, 3, 4], [1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8]])
省略號語法可以用於指示完整地選擇任何剩余的未指定的維度。例如:
>>> z = np.arange(81).reshape(3,3,3,3) >>> z[1,...,2] array([[29, 32, 35], [38, 41, 44], [47, 50, 53]])
這相當於:
>>> z[1,:,:,2] array([[29, 32, 35], [38, 41, 44], [47, 50, 53]])
# 給被索引的數組賦值
如前所述,可以使用單個索引,切片以及索引和掩碼數組來選擇數組的子集。分配給索引數組的值必須是形狀一致的(形狀與索引產生的形狀相同或相同)。例如,允許為片分配一個常量:
>>> x = np.arange(10) >>> x[2:7] = 1
或者正確大小的數組:
>>> x[2:7] = np.arange(5)
請注意,如果將較高類型分配給較低類型(在int類型中添加浮點數(floats))或甚至導致異常(將復數分配給int/float類型),分配可能會導致更改:
>>> x[1] = 1.2 >>> x[1] 1 >>> x[1] = 1.2j <type 'exceptions.TypeError'>: can't convert complex to long; use long(abs(z))
與一些引用(如數組和掩碼索引)不同,賦值通常是對數組中的原始數據進行賦值的(事實上,沒有其他意義了!)。但請注意,有些行為可能不會如人們所期望的那樣行事。這個特殊的例子通常令人驚訝:
>>> x = np.arange(0, 50, 10) >>> x array([ 0, 10, 20, 30, 40]) >>> x[np.array([1, 1, 3, 1])] += 1 >>> x array([ 0, 11, 20, 31, 40])
人們預計第一個地點會增加3。實際上,它只會增加1。原因是因為從原始數據(作為臨時數據)中提取了一個新的數組,其中包含1,1,1,1,1,1的值,則將值1添加到臨時數據中,然后將臨時數據分配回原始數組。因此,x [1] +1處的數組值被分配給x [1]三次,而不是遞增3次。
# 處理程序中可變數量的索引
索引語法非常強大,但在處理可變數量的索引時受到限制。例如,如果你想編寫一個可以處理各種維數參數的函數,而不必為每個可能維數編寫特殊的代碼,那又怎么辦?如果向元組提供元組,則該元組將被解釋為索引列表。例如(使用數組z的前一個定義):
>>> indices = (1,1,1,1) >>> z[indices] 40
所以可以使用代碼來構造任意數量的索引的元組,然后在索引中使用這些元組。
切片可以通過在Python中使用slice()函數在程序中指定。例如:
>>> indices = (1,1,1,slice(0,2)) # same as [1,1,1,0:2] >>> z[indices] array([39, 40])
同樣,省略號可以通過使用省略號對象的代碼指定:
>>> indices = (1, Ellipsis, 1) # same as [1,...,1] >>> z[indices] array([[28, 31, 34], [37, 40, 43], [46, 49, 52]])
由於這個原因,可以直接使用np.where()函數的輸出作為索引,因為它總是返回索引數組的元組。
由於元組的特殊處理,它們不會自動轉換為列表。舉個例子:
>>> z[[1,1,1,1]] # produces a large array array([[[[27, 28, 29], [30, 31, 32], ... >>> z[(1,1,1,1)] # returns a single value 40
廣播(Broadcasting)
另見:
numpy.broadcast
術語broadcasting描述numpy在算術運算期間如何處理具有不同形狀的數組。受限於某些約束,較小的數組依據較大數組“broadcasting”,使得它們具有兼容的形狀。Broadcasting提供了一種矢量化數組操作的方法,使得循環發生在C而不是Python。它做到這一點且不用不必要的數據拷貝,通常導致高效的算法實現。然而,有些情況下,broadcasting是一個壞主意,因為它導致低效的內存使用並減慢計算。
NumPy操作通常是在逐個元素的基礎上在數組對上完成的。在最簡單的情況下,兩個數組必須具有完全相同的形狀,如下例所示:
>>> a = np.array([1.0, 2.0, 3.0]) >>> b = np.array([2.0, 2.0, 2.0]) >>> a * b array([ 2., 4., 6.])
當數組的形狀滿足一定的條件時,NumPy的broadcasting規則可以放寬這個限制。最簡單的broadcasting示例發生在一個操作包含數組和標量值的時候:
>>> a = np.array([1.0, 2.0, 3.0]) >>> b = 2.0 >>> a * b array([ 2., 4., 6.])
結果等同於前面的例子,其中b
是一個數組。在算術運算期間,我們可以認為標量b
被拉伸了,形成與a
相同形狀的數組。b
中的新元素是原始標量簡單的拷貝。拉伸這個比喻只是概念性的。NumPy足夠聰明,它使用原始的標量值而不會真正拷貝,使broadcasting操作盡可能的內存和計算高效。
第二個例子中的代碼比第一個例子中的代碼更有效,因為broadcasting在乘法期間移動較少的內存(b
是標量而不是數組)。
# Broadcasting的一般規則
當在兩個數組上操作時,NumPy在元素級別比較它們的形狀。它從尾隨的維度開始,並朝着前進的方向前進。兩個維度兼容,當
- 他們是平等的,或者
- 其中之一是1
如果不滿足這些條件,則拋出ValueError: frames are not aligned
異常,指示數組具有不兼容的形狀。結果數組的大小是沿着輸入數組的每個維度的最大大小。
數組不需要具有相同維度的數目。例如,如果你有一個256x256x3
數值的RGB值,並且你想要通過一個不同的值縮放圖像中的每個顏色,你可以將圖像乘以一個具有3個值的一維數組。根據broadcast規則排列這些數組的最后一個軸的大小,表明它們是兼容的:
Image (3d array): 256 x 256 x 3 Scale (1d array): 3 Result (3d array): 256 x 256 x 3
當比較的任何一個維度為1時,則使用另一個。換句話說,大小為1的維被拉伸或“復制”以匹配另一維。
在以下示例中,A和B數組都具有長度為1的軸,在broadcast操作期間將其擴展為更大的大小:
A (4d array): 8 x 1 x 6 x 1 B (3d array): 7 x 1 x 5 Result (4d array): 8 x 7 x 6 x 5
這里有一些例子:
A (2d array): 5 x 4 B (1d array): 1 Result (2d array): 5 x 4 A (2d array): 5 x 4 B (1d array): 4 Result (2d array): 5 x 4 A (3d array): 15 x 3 x 5 B (3d array): 15 x 1 x 5 Result (3d array): 15 x 3 x 5 A (3d array): 15 x 3 x 5 B (2d array): 3 x 5 Result (3d array): 15 x 3 x 5 A (3d array): 15 x 3 x 5 B (2d array): 3 x 1 Result (3d array): 15 x 3 x 5
以下是不broadcast的形狀示例:
A (1d array): 3 B (1d array): 4 # trailing dimensions do not match A (2d array): 2 x 1 B (3d array): 8 x 4 x 3 # second from last dimensions mismatched
broadcasting在實踐中的一個例子:
>>> x = np.arange(4) >>> xx = x.reshape(4,1) >>> y = np.ones(5) >>> z = np.ones((3,4)) >>> x.shape (4,) >>> y.shape (5,) >>> x + y <type 'exceptions.ValueError'>: shape mismatch: objects cannot be broadcast to a single shape >>> xx.shape (4, 1) >>> y.shape (5,) >>> (xx + y).shape (4, 5) >>> xx + y array([[ 1., 1., 1., 1., 1.], [ 2., 2., 2., 2., 2.], [ 3., 3., 3., 3., 3.], [ 4., 4., 4., 4., 4.]]) >>> x.shape (4,) >>> z.shape (3, 4) >>> (x + z).shape (3, 4) >>> x + z array([[ 1., 2., 3., 4.], [ 1., 2., 3., 4.], [ 1., 2., 3., 4.]])
Broadcasting提供了獲取兩個數組的外積(或任何其他outer操作)的方便方式。以下示例顯示了兩個1-d數組的外積操作:
>>> a = np.array([0.0, 10.0, 20.0, 30.0]) >>> b = np.array([1.0, 2.0, 3.0]) >>> a[:, np.newaxis] + b array([[ 1., 2., 3.], [ 11., 12., 13.], [ 21., 22., 23.], [ 31., 32., 33.]])
這里newaxis
索引操作符將一個新軸插入到a
中,使其成為一個二維4x1
數組。將4x1
數組與形狀為(3,)
的b
組合,產生一個4x3
數組。
有關broadcasting概念的圖解,請參閱本文。
字節交換
# 字節排序和ndarrays簡介
ndarray
是為內存中的數據提供python數組接口的對象。
經常發生的情況是,您想要使用數組查看的內存與您運行Python的計算機的字節順序不同。
例如,我可能正在使用小端CPU的計算機(例如Intel Pentium),但是我已經從大端的計算機寫入的文件中加載了一些數據。假設我已經從Sun(big-endian)計算機寫入的文件中加載了4個字節。我知道這4個字節代表兩個16位整數。在big-endian機器上,最高有效字節(MSB)首先存儲一個雙字節整數,然后存儲最低有效字節(LSB)。因此,這些字節按內存順序排列:
- MSB整數1
- LSB整數1
- MSB整數2
- LSB整數2
假設這兩個整數實際上是1和770。由於770 = 256 * 3 + 2,內存中的4個字節將分別包含:0,1,3,2。我從文件中加載的字節將包含以下內容:
>>> big_end_str = chr(0) + chr(1) + chr(3) + chr(2) >>> big_end_str '\x00\x01\x03\x02'
我們可能想要使用ndarray
來訪問這些整數。在這種情況下,我們可以圍繞這個內存創建一個數組,並告訴numpy有兩個整數,它們是16位和大端:
>>> import numpy as np >>> big_end_arr = np.ndarray(shape=(2,),dtype='>i2', buffer=big_end_str) >>> big_end_arr[0] 1 >>> big_end_arr[1] 770
請注意dtype
上的數據類型>i2
,>
表示'big-endian'(<
是小端),i2
表示'帶符號的2字節整數'。例如,如果我們的數據表示一個無符號的4字節little-endian整數,則dtype字符串應該是<u4
。
事實上,我們為什么不嘗試呢?
>>> little_end_u4 = np.ndarray(shape=(1,),dtype='<u4', buffer=big_end_str) >>> little_end_u4[0] == 1 * 256**1 + 3 * 256**2 + 2 * 256**3 True
回到我們的big_end_arr
- 在這種情況下,我們的基礎數據是big-endian(數據字節順序),我們設置了dtype匹配(dtype也是big-endian)。但是,有時你需要翻轉這些。
標量當前不包含字節順序信息,因此從數組中提取標量將以本機字節順序返回一個整數。因此:
>>> big_end_arr[0].dtype.byteorder == little_end_u4[0].dtype.byteorder True
# 更改字節順序
正如你從介紹可以想象的,有兩種方式可以影響數組的字節順序和它正在查看的底層內存之間的關系:
- 更改數組dtype中的字節順序信息,以便它將未確定的數據解釋為處於不同的字節順序。這是
arr.newbyteorder()
的作用 - 改變底層數據的字節順序,保持原來的dtype解釋。這是
arr.byteswap()
所做的。
您需要更改字節順序的常見情況是:
- 您的數據和dtype字尾不匹配,並且您想要更改dtype以使其與數據匹配。
- 您的數據和dtype字尾不匹配,您想要交換數據以便它們與dtype匹配
- 你的數據和dtype的字節匹配,但你想要交換數據和dtype來反映這一點
# 數據和dtype字節順序不匹配,將dtype更改為匹配數據
我們做一些他們不匹配的東西:
>>> wrong_end_dtype_arr = np.ndarray(shape=(2,),dtype='<i2', buffer=big_end_str) >>> wrong_end_dtype_arr[0] 256
這種情況的明顯解決方法是更改dtype,以便提供正確的排列順序:
>>> fixed_end_dtype_arr = wrong_end_dtype_arr.newbyteorder() >>> fixed_end_dtype_arr[0] 1
請注意數組在內存中沒有改變:
>>> fixed_end_dtype_arr.tobytes() == big_end_str True
# 數據和類型字節順序不匹配,更改數據以匹配dtype
如果您需要將內存中的數據設置為特定順序,則可能需要執行此操作。例如,您可能將內存寫入需要某個字節排序的文件。
>>> fixed_end_mem_arr = wrong_end_dtype_arr.byteswap() >>> fixed_end_mem_arr[0] 1
現在數組在內存中有更改:
>>> fixed_end_mem_arr.tobytes() == big_end_str False
# 數據和dtype字節順序匹配,交換數據和dtype
你可能為一個數組指定了正確的dtype,但你需要數組在內存中有相反的字節順序,你想讓dtype匹配,所以數組值是有意義的。在這種情況下,您只需執行以前的兩個操作:
>>> swapped_end_arr = big_end_arr.byteswap().newbyteorder() >>> swapped_end_arr[0] 1 >>> swapped_end_arr.tobytes() == big_end_str False
使用ndarray astype方法可以實現將數據轉換為特定dtype和字節順序的更簡單的方法:
>>> swapped_end_arr = big_end_arr.astype('<i2') >>> swapped_end_arr[0] 1 >>> swapped_end_arr.tobytes() == big_end_str False
結構化數組
# 介紹
結構化數組其實就是ndarrays,其數據類型是由組成一系列命名字段的簡單數據類型組成的。 例如:
>>> x = np.array([('Rex', 9, 81.0), ('Fido', 3, 27.0)], ... dtype=[('name', 'U10'), ('age', 'i4'), ('weight', 'f4')]) >>> x array([('Rex', 9, 81.0), ('Fido', 3, 27.0)], dtype=[('name', 'S10'), ('age', '<i4'), ('weight', '<f4')])
這里x
是長度為2的一維數組,其數據類型是具有三個字段的結構:1、名為'name'的長度為10或更小的字符串。2、名為'age'的32位整數。3、名為'weight'的32位浮點數。
如果你的索引x
是1,你會看到這樣的結構:
>>> x[1] ('Fido', 3, 27.0)
你可以通過使用字段名稱進行索引來訪問和修改結構化數組的各個字段的值:
>>> x['age'] array([9, 3], dtype=int32) >>> x['age'] = 5 >>> x array([('Rex', 5, 81.0), ('Fido', 5, 27.0)], dtype=[('name', 'S10'), ('age', '<i4'), ('weight', '<f4')])
結構化數組設計用於結構化數據的底層操作,例如解釋編譯二進制數據塊。結構化數據類型旨在模仿C語言中的 “structs”,使它們對於與C代碼接口也很有用。 為了達到這些目的,numpy支持諸如子陣列和嵌套數據類型之類的特殊功能,並允許手動控制結構的內存布局。
如果你想進行表格數據的簡單操作,那么其他 pydata 項目(例如pandas,xarray或DataArray)將為你提供更適合的更高級別的接口。 這些包也可以為表格數據分析提供更好的性能,因為NumPy中的結構化數組的類C結構內存布局會導致緩存行為不佳。
# 結構化數據類型
要使用結構化數組,首先需要定義結構化數據類型。
結構化數據類型可以被認為是一定長度的字節序列(結構的itemsize),它被解釋為一個字段集合。 每個字段在結構中都有一個名稱,一個數據類型和一個字節偏移量。 字段的數據類型可以是任何numpy數據類型,包括其他結構化數據類型,它也可以是一個子數組,其行為類似於指定形狀的ndarray。 字段的偏移是任意的,並且字段甚至可以重疊。 這些偏移通常由numpy自動確定,但也可以指定。
# 結構化數據類型創建
結構化數據類型可以使用函數numpy.dtype
創建。 有4種可選形式的規范,其靈活性和簡潔性各不相同。 這些在數據類型對象參考頁面中都有進一步的記錄,總之它們是:
-
元組列表,每個字段一個元組
每個元組都有這些屬性
(fieldname,datatype,shape)
,其中shape是可選的。fieldname
是一個字符串(或元組,如果使用標題,請參閱下面的字段標題),datatype
可以是任何可轉換為數據類型的對象,shape
是指定子陣列形狀的整數元組。>>> np.dtype([('x', 'f4'), ('y', np.float32), ('z', 'f4', (2,2))]) dtype=[('x', '<f4'), ('y', '<f4'), ('z', '<f4', (2, 2))])
如果
fieldname
是空字符串''
,那么該字段將被賦予一個默認名稱形式f#
,其中#
是該字段的整數索引,從左邊以0開始計數:>>> np.dtype([('x', 'f4'),('', 'i4'),('z', 'i8')]) dtype([('x', '<f4'), ('f1', '<i4'), ('z', '<i8')])
結構中字段的字節偏移量和總體結構中元素的大小是自動確定的。
-
一串用逗號分隔的dtype規范
在這種簡寫表示法中,任何 string dtype specifications 都可以在字符串中使用逗號分隔,字段的元素大小(itemsize)和字節偏移量是自動確定的,並且字段名稱被賦予默認名稱如":“f0”、“f1” 等。
>>> np.dtype('i8,f4,S3') dtype([('f0', '<i8'), ('f1', '<f4'), ('f2', 'S3')]) >>> np.dtype('3int8, float32, (2,3)float64') dtype([('f0', 'i1', 3), ('f1', '<f4'), ('f2', '<f8', (2, 3))])
-
字段參數數組的字典
這是最靈活的規范形式,因為它允許控制字段的字節偏移量和結構中的元素大小(itemsize)。
字典有兩個必需的鍵,'names' 和 'formats',以及四個可選鍵,'offsets','itemsize','aligned' 和 'titles'。 'names' 和 'formats' 的值應該分別是長度相同的字段名稱列表和dtype規范列表。 可選的 'offsets' 值應該是整數字節偏移量的列表,結構中的每個字段都有一個偏移量。 如果沒有給出 'offsets',則自動確定偏移量。 可選的 'itemsize' 值應該是一個描述dtype的總大小(以字節為單位)的整數,它必須足夠大以包含所有字段。
>>> np.dtype({'names': ['col1', 'col2'], 'formats': ['i4','f4']}) dtype([('col1', '<i4'), ('col2', '<f4')]) >>> np.dtype({'names': ['col1', 'col2'], ... 'formats': ['i4','f4'], ... 'offsets': [0, 4], ... 'itemsize': 12}) dtype({'names':['col1','col2'], 'formats':['<i4','<f4'], 'offsets':[0,4], 'itemsize':12})
可以選擇偏移使得字段重疊,但這意味着分配給一個字段可能破壞任何重疊字段的數據。 作為一個例外,
numpy.object
類型的字段不能與其他字段重疊,因為存在破壞內部對象指針然后解除引用的風險。可選的 “aligned” 值可以設置為True,以使自動偏移計算使用對齊的偏移(請參閱自動字節偏移和對齊),就好像numpy.dtype的'align'關鍵字參數已設置為True。
可選的 “titles” 值應該是與 “names” 長度相同的標題列表,請參閱的字段標題。
-
字段名稱的字典
不鼓勵使用這種形式的規范,但也在此列出,因為較舊的numpy代碼可能會使用它。字典的鍵是字段名稱,值是指定類型和偏移量的元組:
>>> np.dtype=({'col1': ('i1',0), 'col2': ('f4',1)}) dtype([(('col1'), 'i1'), (('col2'), '>f4')])
不鼓勵使用這種形式,因為Python的字典類型在Python3.6之前沒有保留Python版本中的順序,並且結構化dtype中字段的順序具有意義。字段標題可以通過使用3元組來指定,請參見下面的內容.
# 操作和顯示結構化數據類型
可以在dtype對象的names
屬性中找到結構化數據類型的字段名稱列表:
>>> d = np.dtype([('x', 'i8'), ('y', 'f4')]) >>> d.names ('x', 'y')
可以通過使用相同長度的字符串序列分配 names
屬性來修改字段名稱。
dtype對象還有一個類似字典的屬性fields,其鍵是字段名稱(和Field Titles,見下文),其值是包含每個字段的dtype和byte偏移量的元組。
>>> d.fields mappingproxy({'x': (dtype('int64'), 0), 'y': (dtype('float32'), 8)})
對於非結構化數組,names
和fields
屬性都是 None
。
如果可能,結構化數據類型的字符串表示形式為“元組列表”的形式,否則numpy將回退到使用更通用的字典的形式。
# 自動字節偏移和對齊
Numpy使用兩種方法中的一個來自動確定字節字節偏移量和結構化數據類型的整體項目大小,具體取決於是否將align = True
指定為numpy.dtype
的關鍵字參數。
默認情況下(align = False
),numpy將字段打包在一起,使得每個字段從前一個字段結束的字節偏移開始,並且字段在內存中是連續的。
>>> def print_offsets(d): ... print("offsets:", [d.fields[name][1] for name in d.names]) ... print("itemsize:", d.itemsize) >>> print_offsets(np.dtype('u1,u1,i4,u1,i8,u2')) offsets: [0, 1, 2, 6, 7, 15] itemsize: 17
如果設置align = True
,numpy將以與許多C編譯器填充C結構相同的方式填充結構。 在某些情況下,對齊結構可以提高性能,但代價是增加了數據的大小。 在字段之間插入填充字節,使得每個字段的字節偏移量將是該字段對齊的倍數,對於簡單數據類型,通常等於字段的字節大小,請參閱“PyArray_Descr.alignment”。 該結構還將添加尾隨填充,以使其itemsize是最大字段對齊的倍數。
>>> print_offsets(np.dtype('u1,u1,i4,u1,i8,u2', align=True)) offsets: [0, 1, 4, 8, 16, 24] itemsize: 32
請注意,盡管默認情況下幾乎所有現代C編譯器都以這種方式填充,但C結構中的填充依賴於C實現,因此不能保證此內存布局與C程序中相應結構的內容完全匹配。 為了獲得確切的對應關系,可能需要在numpy或C這邊進行一些工作。
如果在基於字典的dtype規范中使用可選的offsets
鍵指定了偏移量,設置align = True
將檢查每個字段的偏移量是否為其大小的倍數,項大小是否為最大字段大小的倍數,如果不是,則引發異常。
如果結構化數組的字段和項目大小的偏移滿足對齊條件,則數組將設置 ALIGNED
標志。
便捷函數numpy.lib.recfunctions.repack_fields
將對齊的dtype或數組轉換為已打包的dtype或數組,反之亦然。它接受dtype或結構化ndarray作為參數,並返回一個帶有重新打包的字段的副本,無論是否有填充字節。
# 字段標題
除了字段名稱之外,字段還可以具有關聯的標題,備用名稱,有時用作字段的附加說明或別名。 標題可用於索引數組,就像字段名一樣。
要在使用dtype規范的list-of-tuples形式時添加標題,可以將字段名稱指定為兩個字符串的元組而不是單個字符串,它們分別是字段的標題和字段名稱。 例如:
>>> np.dtype([(('my title', 'name'), 'f4')])
當使用第一種形式的基於字典的規范時,標題
可以作為額外的“標題”作為鍵提供,如上所述。 當使用第二個(不鼓勵的)基於字典的規范時,可以通過提供3元素元組(數據類型,偏移量,標題)
而不是通常的2元素元組來提供標題:
>>> np.dtype({'name': ('i4', 0, 'my title')})
如果使用了標題,dtype.field
字典將包含作為鍵的標題。這意味着具有標題的字段將在字段字典中表示兩次。這些字段的元組值還有第三個元素,字段標題。因此,由於name
屬性保留了字段順序,而field
屬性可能不能,建議使用dtype的name
屬性迭代dtype的字段,該屬性不會列出標題,如下所示:
>>> for name in d.names: ... print(d.fields[name][:2])
# 聯合類型
結構化數據類型在numpy中實現,默認情況下具有基類型numpy.void
,但是可以使用數據類型對象中描述的dtype規范的(base_dtype,dtype)
形式將其他numpy類型解釋為結構化類型。 這里,base_dtype
是所需的底層dtype
,字段和標志將從dtype復制。 這個dtype類似於C中的'union'。
# 結構化數組的索引和分配
# 將數據分配給結構化數組
有許多方法可以為結構化數組賦值:使用python元組、使用標量值或使用其他結構化數組。
# 使用Python原生類型(元組)來賦值
將值賦給結構化數組的最簡單方法是使用python元組。 每個賦值應該是一個長度等於數組中字段數的元組,而不是列表或數組,因為它們將觸發numpy的廣播規則。 元組的元素從左到右分配給數組的連續字段:
>>> x = np.array([(1,2,3),(4,5,6)], dtype='i8,f4,f8') >>> x[1] = (7,8,9) >>> x array([(1, 2., 3.), (7, 8., 9.)], dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '<f8')])
# 通過標量賦值
分配給結構化元素的標量將分配給所有字段。 將標量分配給結構化數組時,或者將非結構化數組分配給結構化數組時,會發生這種情況:
>>> x = np.zeros(2, dtype='i8,f4,?,S1') >>> x[:] = 3 >>> x array([(3, 3.0, True, b'3'), (3, 3.0, True, b'3')], dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '?'), ('f3', 'S1')]) >>> x[:] = np.arange(2) >>> x array([(0, 0.0, False, b'0'), (1, 1.0, True, b'1')], dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '?'), ('f3', 'S1')])
結構化數組也可以分配給非結構化數組,但前提是結構化數據類型只有一個字段:
>>> twofield = np.zeros(2, dtype=[('A', 'i4'), ('B', 'i4')]) >>> onefield = np.zeros(2, dtype=[('A', 'i4')]) >>> nostruct = np.zeros(2, dtype='i4') >>> nostruct[:] = twofield ValueError: Can't cast from structure to non-structure, except if the structure only has a single field. >>> nostruct[:] = onefield >>> nostruct array([0, 0], dtype=int32)
# 來自其他結構化數組的賦值
兩個結構化數組之間的分配就像源元素已轉換為元組然后分配給目標元素一樣。 也就是說,源陣列的第一個字段分配給目標數組的第一個字段,第二個字段同樣分配,依此類推,而不管字段名稱如何。 具有不同數量的字段的結構化數組不能彼此分配。 未包含在任何字段中的目標結構的字節不受影響。
>>> a = np.zeros(3, dtype=[('a', 'i8'), ('b', 'f4'), ('c', 'S3')]) >>> b = np.ones(3, dtype=[('x', 'f4'), ('y', 'S3'), ('z', 'O')]) >>> b[:] = a >>> b array([(0.0, b'0.0', b''), (0.0, b'0.0', b''), (0.0, b'0.0', b'')], dtype=[('x', '<f4'), ('y', 'S3'), ('z', 'O')])
# 子陣列的賦值
分配給子陣列的字段時,首先將指定的值廣播到子陣列的形狀。
# 索引結構化數組
# 訪問單個字段
可以通過使用字段名稱索引數組來訪問和修改結構化數組的各個字段。
>>> x = np.array([(1,2),(3,4)], dtype=[('foo', 'i8'), ('bar', 'f4')]) >>> x['foo'] array([1, 3]) >>> x['foo'] = 10 >>> x array([(10, 2.), (10, 4.)], dtype=[('foo', '<i8'), ('bar', '<f4')])
可以通過使用字段名稱索引數組來訪問和修改結構化數組的各個字段。
>>> y = x['bar'] >>> y[:] = 10 >>> x array([(10, 5.), (10, 5.)], dtype=[('foo', '<i8'), ('bar', '<f4')])
此視圖與索引字段具有相同的dtype和itemsize,因此它通常是非結構化數組,但嵌套結構除外。
>>> y.dtype, y.shape, y.strides (dtype('float32'), (2,), (12,))
# 訪問多個字段
可以索引並分配具有多字段索引的結構化數組,其中索引是字段名稱列表
多字段索引的說明將從Numpy 1.14升級Numpy 1.15。
在Numpy 1.15中,使用多字段索引進行索引的結果將是原始數組的視圖,如下所示:
>>> a = np.zeros(3, dtype=[('a', 'i4'), ('b', 'i4'), ('c', 'f4')]) >>> a[['a', 'c']] array([(0, 0.), (0, 0.), (0, 0.)], dtype={'names':['a','c'], 'formats':['<i4','<f4'], 'offsets':[0,8], 'itemsize':12})
對視圖的賦值會修改原始數組。 視圖的字段將按索引編號的順序排列。 請注意,與單字段索引不同,視圖的dtype與原始數組具有相同的項目大小,並且具有與原始數組中相同的偏移量的字段,並且僅缺少未編入索引的字段。
在Numpy 1.14中,索引具有多字段索引的數組將返回上述結果的副本(對於1.15),但將字段打包在內存中,就好像通過了numpy.lib.recFunctions.repack_field
。這是Numpy 1.7到1.13的行為。
Numpy 1.15中的新行為導致在未索引字段的位置出現額外的“填充”字節。您將需要更新所有的代碼,這取決於具有“打包”布局的數據。例如下面的代碼:
>>> a[['a','c']].view('i8') # will fail in Numpy 1.15 ValueError: When changing to a smaller dtype, its size must be a divisor of the size of original dtype
需要升級。這段代碼從Numpy 1.12開始引發了 FutureWarning
的錯誤
以下是修復性建議,下面這段代碼在Numpy 1.14和Numpy 1.15中的作用相同:
>>> from numpy.lib.recfunctions import repack_fields >>> repack_fields(a[['a','c']]).view('i8') # supported 1.14 and 1.15 array([0, 0, 0])
賦值給具有多字段索引的數組在Numpy 1.14和Numpy 1.15中的作用相同。在兩個版本中,賦值都將修改原始數組:
>>> a[['a', 'c']] = (2, 3) >>> a array([(2, 0, 3.0), (2, 0, 3.0), (2, 0, 3.0)], dtype=[('a', '<i8'), ('b', '<i4'), ('c', '<f8')])
這遵循上述結構化陣列賦值規則。 例如,這意味着可以使用適當的多字段索引交換兩個字段的值:
>>> a[['a', 'c']] = a[['c', 'a']]
# 用整數索引獲取結構化標量
索引結構化數組的單個元素(帶有整數索引)返回結構化標量:
>>> x = np.array([(1, 2., 3.)], dtype='i,f,f') >>> scalar = x[0] >>> scalar (1, 2., 3.) >>> type(scalar) numpy.void
與其他數值標量不同的是,結構化標量是可變的,並且像原始數組中的視圖一樣,因此修改標量將修改原始數組。結構化標量還支持按字段名進行訪問和賦值:
>>> x = np.array([(1,2),(3,4)], dtype=[('foo', 'i8'), ('bar', 'f4')]) >>> s = x[0] >>> s['bar'] = 100 >>> x array([(1, 100.), (3, 4.)], dtype=[('foo', '<i8'), ('bar', '<f4')])
與元組類似,結構化標量也可以用整數索引:
>>> scalar = np.array([(1, 2., 3.)], dtype='i,f,f')[0] >>> scalar[0] 1 >>> scalar[1] = 4
因此,元組可能被認為是原生Python中等同於numpy的結構化的類型,就像原生python整數相當於numpy的整數類型。 可以通過調用ndarray.item將結構化標量轉換為元組:
>>> scalar.item(), type(scalar.item()) ((1, 2.0, 3.0), tuple)
# 查看包含對象的結構化數組
為了防止在`numpy.Object
類型的字段中阻塞對象指針,numpy目前不允許包含對象的結構化數組的視圖。
# 結構比較
如果兩個空結構數組的dtype相等,則測試數組的相等性將生成一個具有原始數組的維度的布爾數組,元素設置為True,其中相應結構的所有字段都相等。如果字段名稱、dtype和標題相同,而忽略endianness,且字段的順序相同,則結構化dtype是相等的:
>>> a = np.zeros(2, dtype=[('a', 'i4'), ('b', 'i4')]) >>> b = np.ones(2, dtype=[('a', 'i4'), ('b', 'i4')]) >>> a == b array([False, False])
目前,如果兩個void結構化數組的dtypes不相等,則比較失敗返回標量值“False”。 從numpy 1.10開始不推薦使用這種方式,並且這種方式將來會引發錯誤或執行元素比較。
The <
and >
operators always return False
when comparing void structured arrays, and arithmetic and bitwise operations are not supported.
在比較空結構數組時,<
和 >
操作符總是返回 False
,並且不支持算術和位運算符。
# 記錄數組
作為一個可選的方便的選項,numpy提供了一個ndarray子類 numpy.recarray
,以及 numpy.rec
子模塊中的相關幫助函數,它允許通過屬性訪問結構化數組的字段,而不僅僅是通過索引。記錄數組還使用一種特殊的數據類型 numpy.Record
,它允許通過屬性對從數組獲得的結構化標量進行字段訪問。
創建記錄數組的最簡單方法是使用 numpy.rec.array
, 像下面這樣:
>>> recordarr = np.rec.array([(1,2.,'Hello'),(2,3.,"World")], ... dtype=[('foo', 'i4'),('bar', 'f4'), ('baz', 'S10')]) >>> recordarr.bar array([ 2., 3.], dtype=float32) >>> recordarr[1:2] rec.array([(2, 3.0, 'World')], dtype=[('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')]) >>> recordarr[1:2].foo array([2], dtype=int32) >>> recordarr.foo[1:2] array([2], dtype=int32) >>> recordarr[1].baz 'World'
numpy.rec.array
可以將各種參數轉換為記錄數組,包括結構化數組:
>>> arr = array([(1,2.,'Hello'),(2,3.,"World")], ... dtype=[('foo', 'i4'), ('bar', 'f4'), ('baz', 'S10')]) >>> recordarr = np.rec.array(arr)
numpy.rec模塊提供了許多其他便捷的函數來創建記錄數組,請參閱記錄數組創建API。
可以使用適當的視圖獲取結構化數組的記錄數組表示:
>>> arr = np.array([(1,2.,'Hello'),(2,3.,"World")], ... dtype=[('foo', 'i4'),('bar', 'f4'), ('baz', 'a10')]) >>> recordarr = arr.view(dtype=dtype((np.record, arr.dtype)), ... type=np.recarray)
為方便起見,查看類型為np.recarray
的ndarray會自動轉換為np.record
數據類型,因此dtype可以不在視圖之外:
>>> recordarr = arr.view(np.recarray) >>> recordarr.dtype dtype((numpy.record, [('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')]))
要返回普通的ndarray,必須重置dtype和type。 以下視圖是這樣做的,考慮到recordarr不是結構化類型的異常情況:
>>> arr2 = recordarr.view(recordarr.dtype.fields or recordarr.dtype, np.ndarray)
如果字段具有結構化類型,則返回由index或by屬性訪問的記錄數組字段作為記錄數組,否則返回普通ndarray。
>>> recordarr = np.rec.array([('Hello', (1,2)),("World", (3,4))], ... dtype=[('foo', 'S6'),('bar', [('A', int), ('B', int)])]) >>> type(recordarr.foo) <type 'numpy.ndarray'> >>> type(recordarr.bar) <class 'numpy.core.records.recarray'>
請注意,如果字段與ndarray屬性具有相同的名稱,則ndarray屬性優先。 這些字段將無法通過屬性訪問,但仍可通過索引訪問。
子類化ndarray
# 介紹
子類化ndarray相對簡單,但與其他Python對象相比,它有一些復雜性。 在這個頁面上,我們解釋了允許你子類化ndarray的機制,以及實現子類的含義。
# ndarrays和對象創建
ndarray的子類化很復雜,因為ndarray類的新實例可以以三種不同的方式出現。 這些是:
- 顯式構造函數調用 - 如
MySubClass(params)
。 這是創建Python實例的常用途徑。 - 查看轉換 - 將現有的ndarray轉換為給定的子類。
- 從模板創建新實例-從模板實例創建新實例。示例包括從子類數組返回片、從uFuncs創建返回類型和復制數組。有關更多詳細信息,請參見從模板創建。
最后兩個是ndarrays的特征 - 為了支持數組切片之類的東西。 子類化ndarray的復雜性是由於numpy必須支持后兩種實例創建路徑的機制。
# 視圖投影
視圖投影是標准的ndarray機制,通過它您可以獲取任何子類的ndarray,並將該數組的視圖作為另一個(指定的)子類返回:
>>> import numpy as np >>> # create a completely useless ndarray subclass >>> class C(np.ndarray): pass >>> # create a standard ndarray >>> arr = np.zeros((3,)) >>> # take a view of it, as our useless subclass >>> c_arr = arr.view(C) >>> type(c_arr) <class 'C'>
# 從模版創建
當numpy發現它需要從模板實例創建新實例時,ndarray子類的新實例也可以通過與視圖轉換非常相似的機制來實現。 這個情況的最明顯的時候是你正為子類陣列切片的時候。例如:
>>> v = c_arr[1:] >>> type(v) # the view is of type 'C' <class 'C'> >>> v is c_arr # but it's a new instance False
切片是原始 C_ARR
數據的視圖。因此,當我們從ndarray獲取視圖時,我們返回一個新的ndarray,它屬於同一個類,指向原始的數據。
在使用ndarray時,我們還需要這樣的視圖,比如復制數組(C_arr.Copy()
)、創建ufunc輸出數組(關於uFunc函數和其他函數,也請參閱arraywarp___ )和簡化方法(比如 C_arr.Means()
)。
# 視圖投影和從模版創建的關系
這些路徑都使用相同的機制。我們在這里進行區分,因為它們會為您的方法產生不同的輸入。 具體來說,View轉換意味着您已從ndarray的任何潛在子類創建了數組類型的新實例。從模板創建新意味着您已從預先存在的實例創建了類的新實例,例如,允許您跨特定於您的子類的屬性進行復制。
# 子類化的含義
如果我們繼承ndarray,我們不僅需要處理數組類型的顯式構造,還需要處理視圖投影或從模板創建。NumPy有這樣的機制,這種機制使子類略微不標准。
ndarray用於支持視圖和子類中的new-from-template(從模版創建)的機制有兩個方面。
第一個是使用ndarray .__ new__
方法進行對象初始化的主要工作,而不是更常用的__init__
方法。 第二個是使用__array_finalize__
方法,允許子類在創建視圖和模板中的新實例后進行內存清除。
# 關於在Python中的 __new__
和 __init__
的簡短入門
__ new__
是一個標准的Python方法,如果存在,在創建類實例時在__init__
之前調用。 有關更多詳細信息,請參閱 python new文檔。
例如,請思考以下Python代碼:
class C(object): def __new__(cls, *args): print('Cls in __new__:', cls) print('Args in __new__:', args) return object.__new__(cls, *args) def __init__(self, *args): print('type(self) in __init__:', type(self)) print('Args in __init__:', args)
思考后我們可以得到:
>>> c = C('hello') Cls in __new__: <class 'C'> Args in __new__: ('hello',) type(self) in __init__: <class 'C'> Args in __init__: ('hello',)
當我們調用C('hello')
時,__ new__
方法將自己的類作為第一個參數,並傳遞參數,即字符串'hello'
。 在python調用 __new__
之后,它通常(見下文)調用我們的 __init__
方法,將__new__
的輸出作為第一個參數(現在是一個類實例),然后傳遞參數。
正如你所看到的,對象可以在__new__`
方法或__init__
方法中初始化,或兩者兼而有之,實際上ndarray沒有__init__
方法,因為所有的初始化都是 在__new__
方法中完成。
為什么要使用__new__
而不是通常的__init__
? 因為在某些情況下,對於ndarray,我們希望能夠返回其他類的對象。 考慮以下:
class D(C): def __new__(cls, *args): print('D cls is:', cls) print('D args in __new__:', args) return C.__new__(C, *args) def __init__(self, *args): # we never get here print('In D __init__')
實踐后:
>>> obj = D('hello') D cls is: <class 'D'> D args in __new__: ('hello',) Cls in __new__: <class 'C'> Args in __new__: ('hello',) >>> type(obj) <class 'C'>
C
的定義與之前相同,但對於D
,__new__
方法返回類C
而不是D
的實例。 請注意,D
的__init__
方法不會被調用。 通常,當__new__
方法返回除定義它的類之外的類的對象時,不調用該類的__init__
方法。
這就是ndarray類的子類如何能夠返回保留類類型的視圖。 在觀察時,標准的ndarray機器創建了新的ndarray對象,例如:
obj = ndarray.__new__(subtype, shape, ...
其中subdtype
是子類。 因此,返回的視圖與子類屬於同一類,而不是類ndarray
。
這解決了返回相同類型視圖的問題,但現在我們遇到了一個新問題。 ndarray的機制可以用這種方式設置類,在它的標准方法中獲取視圖,但是ndarray__new__
方法不知道我們在自己的__new__
方法中做了什么來設置屬性, 等等。 (旁白 - 為什么不調用obj = subdtype .__ new __(...
那么?因為我們可能沒有一個帶有相同調用特征的__new__`
方法)。
# __array_finalize__
的作用
__array_finalize__
是numpy提供的機制,允許子類處理創建新實例的各種方法。
請記住,子類實例可以通過以下三種方式實現:
- 顯式構造函數調用(
obj = MySubClass(params)
)。 這將調用通常的MySubClass .__ new__
方法,然后再調用(如果存在)MySubClass .__ init__
。 - 視圖投影。
- 從模板創建。
我們的MySubClass .__ new__
方法僅在顯式構造函數調用的情況下被調用,因此我們不能依賴於MySubClass .__ new__
或MySubClass .__ init__
來處理視圖投影和“從模板創建”。 事實證明,對於所有三種對象創建方法都會調用MySubClass .__ array_finalize__
,所以這就是我們的對象從內部創建理通常會發生的情況。
- 對於顯式構造函數調用,我們的子類需要創建自己的類的新ndarray實例。 在實踐中,這意味着我們作為代碼的編寫者,需要調用
ndarray .__ new __(MySubClass,...)
,一個類的層次結構會調用super(MySubClass,cls) .__ new __(cls,...
),或查看現有數組的視圖投影(見下文) - 對於視圖投影和“從模板創建”,相當於
ndarray .__ new __(MySubClass,...
在C級的調用。
“array_finalize收到的參數因上面三種實例創建方法而異。
以下代碼允許我們查看調用順序和參數:
import numpy as np class C(np.ndarray): def __new__(cls, *args, **kwargs): print('In __new__ with class %s' % cls) return super(C, cls).__new__(cls, *args, **kwargs) def __init__(self, *args, **kwargs): # in practice you probably will not need or want an __init__ # method for your subclass print('In __init__ with class %s' % self.__class__) def __array_finalize__(self, obj): print('In array_finalize:') print(' self type is %s' % type(self)) print(' obj type is %s' % type(obj))
現在看:
>>> # Explicit constructor >>> c = C((10,)) In __new__ with class <class 'C'> In array_finalize: self type is <class 'C'> obj type is <type 'NoneType'> In __init__ with class <class 'C'> >>> # View casting >>> a = np.arange(10) >>> cast_a = a.view(C) In array_finalize: self type is <class 'C'> obj type is <type 'numpy.ndarray'> >>> # Slicing (example of new-from-template) >>> cv = c[:1] In array_finalize: self type is <class 'C'> obj type is <class 'C'>
__array_finalize__
的特征是:
def __array_finalize__(self, obj):
可以看到super
調用,它轉到ndarray .__ new__
,將__array_finalize__
傳遞給我們自己的類(self
)的新對象以及來自的對象 視圖已被采用(obj
)。 從上面的輸出中可以看出,self
始終是我們子類的新創建的實例,而obj
的類型對於三個實例創建方法是不同的:
- 當從顯式構造函數調用時,“obj”是“None”。
- 當從視圖轉換調用時,
obj
可以是ndarray的任何子類的實例,包括我們自己的子類。 - 當在新模板中調用時,
obj
是我們自己子類的另一個實例,我們可以用它來更新的self
實例。
因為__array_finalize__
是唯一始終看到正在創建新實例的方法,所以它是填充新對象屬性的實例的默認值以及其他任務的合適的位置。
通過示例可能更清楚。
# 簡單示例 - 向ndarray添加額外屬性
import numpy as np class InfoArray(np.ndarray): def __new__(subtype, shape, dtype=float, buffer=None, offset=0, strides=None, order=None, info=None): # Create the ndarray instance of our type, given the usual # ndarray input arguments. This will call the standard # ndarray constructor, but return an object of our type. # It also triggers a call to InfoArray.__array_finalize__ obj = super(InfoArray, subtype).__new__(subtype, shape, dtype, buffer, offset, strides, order) # set the new 'info' attribute to the value passed obj.info = info # Finally, we must return the newly created object: return obj def __array_finalize__(self, obj): # ``self`` is a new object resulting from # ndarray.__new__(InfoArray, ...), therefore it only has # attributes that the ndarray.__new__ constructor gave it - # i.e. those of a standard ndarray. # # We could have got to the ndarray.__new__ call in 3 ways: # From an explicit constructor - e.g. InfoArray(): # obj is None # (we're in the middle of the InfoArray.__new__ # constructor, and self.info will be set when we return to # InfoArray.__new__) if obj is None: return # From view casting - e.g arr.view(InfoArray): # obj is arr # (type(obj) can be InfoArray) # From new-from-template - e.g infoarr[:3] # type(obj) is InfoArray # # Note that it is here, rather than in the __new__ method, # that we set the default value for 'info', because this # method sees all creation of default objects - with the # InfoArray.__new__ constructor, but also with # arr.view(InfoArray). self.info = getattr(obj, 'info', None) # We do not need to return anything
使用該對象如下所示:
>>> obj = InfoArray(shape=(3,)) # explicit constructor >>> type(obj) <class 'InfoArray'> >>> obj.info is None True >>> obj = InfoArray(shape=(3,), info='information') >>> obj.info 'information' >>> v = obj[1:] # new-from-template - here - slicing >>> type(v) <class 'InfoArray'> >>> v.info 'information' >>> arr = np.arange(10) >>> cast_arr = arr.view(InfoArray) # view casting >>> type(cast_arr) <class 'InfoArray'> >>> cast_arr.info is None True
這個類不是很有用,因為它與裸ndarray對象具有相同的構造函數,包括傳入緩沖區和形狀等等。我們可能更偏向於希望構造函數能夠將已經構成的ndarray類型通過常用的numpy的np.array
來調用並返回一個對象。
# 更真實的示例 - 添加到現有數組的屬性
這是一個類,它采用已經存在的標准ndarray,轉換為我們的類型,並添加一個額外的屬性。
import numpy as np class RealisticInfoArray(np.ndarray): def __new__(cls, input_array, info=None): # Input array is an already formed ndarray instance # We first cast to be our class type obj = np.asarray(input_array).view(cls) # add the new attribute to the created instance obj.info = info # Finally, we must return the newly created object: return obj def __array_finalize__(self, obj): # see InfoArray.__array_finalize__ for comments if obj is None: return self.info = getattr(obj, 'info', None)
所以:
>>> arr = np.arange(5) >>> obj = RealisticInfoArray(arr, info='information') >>> type(obj) <class 'RealisticInfoArray'> >>> obj.info 'information' >>> v = obj[1:] >>> type(v) <class 'RealisticInfoArray'> >>> v.info 'information'
# ufuncs的__array_ufunc__
版本1.13中的新功能。
子類可以通過重寫默認的ndarray.__arrayufunc_
方法來重寫在其上執行numpy uFunc函數時的行為。此方法將代替ufunc執行,如果未實現所請求的操作,則應返回操作結果或NotImplemented`
。
__array_ufunc__
的特征是:
def __array_ufunc__(ufunc, method, *inputs, **kwargs): - *ufunc* 是被調用的ufunc對象。 - *method* 是一個字符串,指示如何調用Ufunc。“__call__” 表示它是直接調用的,或者是下面的其中一個:`methods <ufuncs.methods>`:“reduce”,“accumulate”,“reduceat”,“outer” 或 “at” 等屬性。 - *inputs* 是 “ufunc” 的類型為元組的輸入參數。 - *kwargs* A包含傳遞給函數的任何可選或關鍵字參數。 這包括任何``out``參數,並且它們總是包含在元組中。
典型的實現將轉換任何作為自己類的實例的輸入或輸出,使用super()
方法將所有內容傳遞給超類,最后在可能的反向轉換后返回結果。 從core / tests / test_umath.py
中的測試用例test_ufunc_override_with_super
獲取的示例如下。
input numpy as np class A(np.ndarray): def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): args = [] in_no = [] for i, input_ in enumerate(inputs): if isinstance(input_, A): in_no.append(i) args.append(input_.view(np.ndarray)) else: args.append(input_) outputs = kwargs.pop('out', None) out_no = [] if outputs: out_args = [] for j, output in enumerate(outputs): if isinstance(output, A): out_no.append(j) out_args.append(output.view(np.ndarray)) else: out_args.append(output) kwargs['out'] = tuple(out_args) else: outputs = (None,) * ufunc.nout info = {} if in_no: info['inputs'] = in_no if out_no: info['outputs'] = out_no results = super(A, self).__array_ufunc__(ufunc, method, *args, **kwargs) if results is NotImplemented: return NotImplemented if method == 'at': if isinstance(inputs[0], A): inputs[0].info = info return if ufunc.nout == 1: results = (results,) results = tuple((np.asarray(result).view(A) if output is None else output) for result, output in zip(results, outputs)) if results and isinstance(results[0], A): results[0].info = info return results[0] if len(results) == 1 else results
所以,這個類實際上沒有做任何有趣的事情:它只是將它自己的任何實例轉換為常規的ndarray(否則,我們會得到無限的遞歸!),並添加一個info
字典,告訴哪些輸入和輸出它轉換。因此,例如:
>>> a = np.arange(5.).view(A) >>> b = np.sin(a) >>> b.info {'inputs': [0]} >>> b = np.sin(np.arange(5.), out=(a,)) >>> b.info {'outputs': [0]} >>> a = np.arange(5.).view(A) >>> b = np.ones(1).view(A) >>> c = a + b >>> c.info {'inputs': [0, 1]} >>> a += b >>> a.info {'inputs': [0, 1], 'outputs': [0]}
注意,另一種方法是使用 getattr(ufunc, methods)(*inputs, **kwargs)
而不是調用 super
。對於此示例,結果將是相同的,但如果另一個運算數也定義 __array_ufunc__
則會存在差異。 例如,假設我們執行 np.add(a, b)
,其中b是另一個重寫功能的b類的實例。如果你在示例中調用 super
, ndarray .__ array_ufunc__
會注意到b有一個覆蓋,這意味着它無法評估結果本身。因此,它將返回NotImplemented,我們的類A
也將返回。然后,控制權將傳遞給b
,它知道如何處理我們並生成結果,或者不知道並返回NotImplemented,從而引發TypeError
。
如果相反,我們用 getattr(ufunc, method)
替換我們的 Super
調用,那么我們實際上執行了np.add(a.view(np.ndarray), b)
。同樣,b.arrayufunc_
將被調用,但現在它將一個ndarray作為另一個參數。很可能,它將知道如何處理這個問題,並將B
類的一個新實例返回給我們。我們的示例類並不是為處理這個問題而設置的,但是,如果要使用___array_`___ufunc_
重新實現 MaskedArray
,那么它很可能是最好的方法。
最后要注意:如果走super
的路線適合給定的類,使用它的一個優點是它有助於構造類層次結構。例如,假設我們的其他類B
在其__array_ufunc__
實現中也使用了super
,我們創建了一個依賴於它們的類C
,即class C (A,B)
(為簡單起見,不是另一個__array_ufunc__
覆蓋)。 那么C
實例上的任何ufunc都會傳遞給A .__ array_ufunc__
,A
中的super
調用將轉到B .__ array_ufunc__
,並且 B
中的super
調用將轉到ndarray .__ array_ufunc__
,從而允許A
和`
進行協作。
# __array_wrap__
用於ufuncs和其他函數
在numpy 1.13之前,ufuncs的行為只能使用__array_wrap__`
和__array_prepare__`
進行調整。這兩個允許一個更改ufunc的輸出類型,但是,與前兩者相反,__array_ufunc__`
,它不允許對輸入進行任何更改。它希望最終棄能棄用這些功能,但是__array_wrap__
也被其他numpy函數和方法使用,比如squeeze
,所以目前仍需要完整的功能。
從概念上講,__array_wrap__`
“包含動作”是允許子類設置返回值的類型並更新屬性和元數據。讓我們用一個例子來說明這是如何工作的。 首先,我們返回更簡單的示例子類,但使用不同的名稱和一些print語句:
import numpy as np class MySubClass(np.ndarray): def __new__(cls, input_array, info=None): obj = np.asarray(input_array).view(cls) obj.info = info return obj def __array_finalize__(self, obj): print('In __array_finalize__:') print(' self is %s' % repr(self)) print(' obj is %s' % repr(obj)) if obj is None: return self.info = getattr(obj, 'info', None) def __array_wrap__(self, out_arr, context=None): print('In __array_wrap__:') print(' self is %s' % repr(self)) print(' arr is %s' % repr(out_arr)) # then just call the parent return super(MySubClass, self).__array_wrap__(self, out_arr, context)
我們在新數組的實例上運行ufunc:
>>> obj = MySubClass(np.arange(5), info='spam') In __array_finalize__: self is MySubClass([0, 1, 2, 3, 4]) obj is array([0, 1, 2, 3, 4]) >>> arr2 = np.arange(5)+1 >>> ret = np.add(arr2, obj) In __array_wrap__: self is MySubClass([0, 1, 2, 3, 4]) arr is array([1, 3, 5, 7, 9]) In __array_finalize__: self is MySubClass([1, 3, 5, 7, 9]) obj is MySubClass([0, 1, 2, 3, 4]) >>> ret MySubClass([1, 3, 5, 7, 9]) >>> ret.info 'spam'
請注意,ufunc (np.add
) 調用了__array_wack__``方法,其參數 ``self`` 作為 ``obj``,``out_arr
為該加法的(ndarray)結果。反過來,默認的 __array_wirp_
(ndarray.arraray_wirp_
) 已將結果轉換為類MySubClass,名為 _array_radline_`
- 因此復制了info
屬性。這一切都發生在C級。
但是,我們可以做任何我們想做的事:
class SillySubClass(np.ndarray): def __array_wrap__(self, arr, context=None): return 'I lost your data' >>> arr1 = np.arange(5) >>> obj = arr1.view(SillySubClass) >>> arr2 = np.arange(5) >>> ret = np.multiply(obj, arr2) >>> ret 'I lost your data'
因此,通過為我們的子類定義一個特定的__array_wrap__
方法,我們可以調整ufuncs的輸出。 __array_wrap__
方法需要self
,然后是一個參數 - 這是ufunc的結果 - 和一個可選的參數上下文。 ufuncs將此參數作為3元素元組返回:( ufunc的名稱,ufunc的參數,ufunc的域),但不是由其他numpy函數設置的。 但是,如上所述,可以這樣做,__ array_wrap__
應返回其包含類的實例。 有關實現,請參閱masked數組子類。
除了在退出ufunc時調用的 __array_wrap__
之外,還存在一個 __array_prepare__
方法,該方法在創建輸出數組之后但在執行任何計算之前,在進入ufunc的過程中被調用。默認實現除了傳遞數組之外什么都不做。__array_prepare__
不應該嘗試訪問數組數據或調整數組大小,它的目的是設置輸出數組類型,更新屬性和元數據,並根據在計算開始之前需要的輸入執行任何檢查。與 __array_wrap__
一樣,__array_prepare__
必須返回一個ndarray或其子類,或引發一個錯誤。
# 額外的坑 - 自定義__del__
方法 和 ndarray.base
darray解決的問題之一是跟蹤ndarray的內存所有權和它們的視圖。考慮這樣一個例子:我們創建了一個ndarray,arr
,並用 v=arr[1:]
取了一個切片。這兩個對象看到的是相同的內存。NumPy使用 base
屬性跟蹤特定數組或視圖的數據來源:
>>> # A normal ndarray, that owns its own data >>> arr = np.zeros((4,)) >>> # In this case, base is None >>> arr.base is None True >>> # We take a view >>> v1 = arr[1:] >>> # base now points to the array that it derived from >>> v1.base is arr True >>> # Take a view of a view >>> v2 = v1[1:] >>> # base points to the view it derived from >>> v2.base is v1 True
一般來說,如果數組擁有自己的內存,就像在這種情況下的 arr
一樣,那么 arr.base
將是None - 這方面有一些例外 - 更多細節請參見 Numpy 的書籍。
base
屬性可以告訴我們是否有視圖或原始數組。 如果我們需要知道在刪除子類數組時是否進行某些特定的清理,這反過來會很有用。 例如,如果刪除原始數組,我們可能只想進行清理,而不是視圖。 有關它如何工作的示例,請查看numpy.core
中的memmap
類。
# 子類和下游兼容性
當對ndarray
進行子類化或創建模仿ndarray
接口的duck-types時,你有責任決定你的API與numpy的對齊方式。 為方便起見,許多具有相應ndarray
方法的numpy函數(例如,sum
,mean
,take
,reshape
)都會檢查第一個參數,看是否一個函數有一個同名的方法。如果是,則調用該方法,反之則將參數強制轉換為numpy數組。
例如,如果你希望子類或duck-type與numpy的sum函數兼容,則此對象的sum`
方法的方法特征應如下所示:
def sum(self, axis=None, dtype=None, out=None, keepdims=False): ...
這是np.sum
的完全相同的方法特征,所以現在如果用戶在這個對象上調用np.sum
,numpy將調用該對象自己的sum
方法並傳入這些參數,在特征上枚舉,並且不會引起任何錯誤,因為他們的特征彼此完全兼容。
但是,如果您決定偏離相關特征並執行以下操作:
def sum(self, axis=None, dtype=None): ...
這個對象不再與np.sum
兼容,因為如果你調用np.sum
,它將傳遞意外的參數out
和keepdims
,導致引發TypeError的錯誤。
如果你希望保持與numpy及其后續版本(可能會添加新的關鍵字參數)的兼容性,但又不想顯示所有numpy的參數,那么你的函數的特征應該接受 ** kwargs。 例如:
def sum(self, axis=None, dtype=None, **unused_kwargs): ...
此對象現在再次與np.sum
兼容,因為任何無關的參數(即不是axis
或dtype
的關鍵字)將被隱藏在** unused_kwargs
參數中。
0