官方文檔
Enthought offical tutorial: numpy.genfromtxt
A very common file format for data file is comma-separated values (CSV), or related formats such as TSV (tab-separated values). To read data from such files into Numpy arrays we can use the numpy.genfromtxt function.
案例說明
我們以數字示波器采集的實驗產生的三角波 (triangular waveform) 為例,它是包含數據信息的表頭,以 .txt 格式存儲的文本文件。
Type: raw Points: 16200 Count: 1 ... X Units: second Y Units: Volt XY Data: 2.4000000E-008, 1.4349E-002 2.4000123E-008, 1.6005E-002 2.4000247E-008, 1.5455E-002 2.4000370E-008, 1.5702E-002 2.4000494E-008, 1.5147E-002 ...
之前,在Python科學計算——File I/O中提到了兩種方法讀取上述的數據,它們共同點是將數據存儲在列表中,正如開頭所說,列表在處理大量數據時是非常緩慢的。那么,我們就來看一看 numpy.genfromtxt 如何大顯身手。
代碼示例
為了得到我們需要的有用數據,我們有兩個硬的要求: (1) 跳過表頭信息;(2) 區分橫縱坐標。
import numpy as np data = np.genfromtxt('waveform.txt',delimiter=',',skip_header=18)
**delimiter: the str used to separate data. 橫縱坐標以 ',' 分割,因此給 delimiter 傳入 ','。skip_header: ** the number of lines to skip at the beginning of the file. 有用數據是從19行開始的,因此給 skip_header 傳入 18。
print(data[0:3,0], data[0:3,1])
因為讀入的是二維數據,因此利用 numpy 二維數據的切片方式 (Index slicing) 輸出各自的前三個數據驗證是否讀取正確:
[ 2.40000000e-08 2.40001230e-08 2.40002470e-08] [ 0.014349 0.016005 0.015455]
對數據進行歸一化處理后,調用 Matplotlib 畫圖命令,就可得到圖像如下:
import matplotlib.pyplot as plt fig, axes = plt.subplots(figsize=(8,6)) axes.plot(x, y, 'r', linewidth=3) axes.set_xlabel('Time(ps)') axes.set_ylabel('Amplitude[a.u.]') fig.savefig("triangular.png", dpi=600)
補充
numpy.genformtxt( ) 函數提供了眾多的入參,實現不同格式數據的讀取,詳情可參考:numpy.genfromtxt
此外,numpy 中還提供了將數據存儲為 CSV 格式的函數 numpy.savetxt( ),詳情可參考:numpy.savetxt
Stay hungry, Stay foolish. -- Steve Jobs
定義輸入
genfromtxt
的唯一強制參數是數據的源。它可以是字符串,字符串列表或生成器。如果提供了單個字符串,則假定它是本地或遠程文件或具有read
方法的打開的類文件對象的名稱,例如文件或StringIO.StringIO
對象。如果提供了字符串列表或返回字符串的生成器,則每個字符串在文件中被視為一行。當傳遞遠程文件的URL時,文件將自動下載到當前目錄並打開。
識別的文件類型是文本文件和歸檔。目前,該函數識別gzip
和bz2
(bzip2)歸檔。歸檔的類型從文件的擴展名確定:如果文件名以'.gz'
結尾,則需要一個gzip
歸檔;如果以'bz2'
結尾,則假設存在一個bzip2
檔案。
將行拆分為列
delimiter 參數
一旦文件被定義並打開閱讀,genfromtxt
將每個非空行拆分為一個字符串序列。剛剛跳過空行或注釋行。delimiter
關鍵字用於定義拆分應如何進行。
通常,單個字符標記列之間的間隔。例如,逗號分隔文件(CSV)使用逗號(,
)或分號(;
)作為分隔符:
import numpy as np from io import BytesIO data = b"1, 2, 3\n4, 5, 6" np.genfromtxt(BytesIO(data), delimiter=",") Out[334]: array([[1., 2., 3.], [4., 5., 6.]])
另一個常見的分隔符是"\t"
,表格字符。但是,我們不限於單個字符,任何字符串都會做。默認情況下,genfromtxt
假定delimiter=None
,表示該行沿白色空格(包括制表符)分割,並且連續的空格被視為單個白色空格。
或者,我們可能處理固定寬度的文件,其中列被定義為給定數量的字符。在這種情況下,我們需要將delimiter
設置為單個整數(如果所有列具有相同的大小)或整數序列(如果列可以具有不同的大小):
data = b"1, 2, 3\n4, 5, 6" np.genfromtxt(BytesIO(data), delimiter=",") Out[334]: array([[1., 2., 3.], [4., 5., 6.]]) data = b" 1 2 3\n 4 5 67\n890123 4" np.genfromtxt(BytesIO(data), delimiter=3) Out[336]: array([[ 1., 2., 3.], [ 4., 5., 67.], [890., 123., 4.]]) data = B"123456789\n 4 7 9\n 4567 9" np.genfromtxt(BytesIO(data), delimiter=(4, 3, 2)) Out[338]: array([[1234., 567., 89.], [ 4., 7., 9.], [ 4., 567., 9.]])
autostrip參數
默認情況下,當一行被分解為一系列字符串時,各個條目不會被刪除前導或尾隨的空格。通過將可選參數autostrip
設置為True
的值,可以覆蓋此行為:
data = b"1, abc , 2\n 3, xxx, 4" np.genfromtxt(BytesIO(data), delimiter=",", dtype="|S5") Out[340]: array([[b'1', b' abc ', b' 2'], [b'3', b' xxx', b' 4']], dtype='|S5') np.genfromtxt(BytesIO(data), delimiter=",", dtype="|S5", autostrip=True) Out[341]: array([[b'1', b'abc', b'2'], [b'3', b'xxx', b'4']], dtype='|S5')
omments參數
可選參數comments
用於定義標記注釋開始的字符串。默認情況下,genfromtxt
假設為comments='#'
。注釋標記可以出現在該行的任何地方。忽略注釋標記后的任何字符:
data = b"""# # 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=",") Out[345]: array([[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.encode())) array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]) >>> np.genfromtxt(BytesIO(data.encode()), ... skip_header=3, skip_footer=5) array([ 3., 4.])
默認情況下,skip_header=0
和skip_footer=0
,表示不跳過任何行。
usecols 參數
在某些情況下,我們對數據的所有列不感興趣,但只對其中的幾個列感興趣。我們可以使用usecols
參數選擇要導入哪些列。此參數接受單個整數或對應於要導入的列的索引的整數序列。記住,按照慣例,第一列的索引為0。負整數的行為與常規Python負指數相同。
例如,如果我們只想導入第一列和最后一列,可以使用usecols =(0, -1)
:
>>> data = b"1 2 3\n4 5 6" >>> np.genfromtxt(BytesIO(data), usecols=(0, -1)) array([[ 1., 3.], [ 4., 6.]])
如果列具有名稱,我們還可以通過將其名稱作為字符串序列或逗號分隔字符串的形式,將其名稱指定給usecols
參數來選擇要導入的列:
>>> data = b"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'
的字典。 -
元組的序列
(名稱, 類型)
,例如dtype = [('A', t4 > int), ('B', float)]
。 -
現有的
numpy.dtype
對象。 -
特殊值
None
。在這種情況下,列的類型將從數據本身確定(見下文)。
在所有情況下,但第一個,輸出將是具有結構化dtype的1D數組。此dtype具有與序列中的項目一樣多的字段。字段名稱使用names
關鍵字定義。
當dtype=None
時,每個列的類型從其數據中迭代確定。我們首先檢查字符串是否可以轉換為布爾值(即,如果字符串在小寫字符串中匹配true
或false
);那么它是否可以轉換為整數,然后到一個float,然后到一個復雜,最終到一個字符串。可以通過修改StringConverter
類的默認映射器來更改此行為。
為方便起見,提供了選項dtype=None
。但是,它明顯慢於明確設置dtype。
設置 names
names 參數
處理表格數據時的一種自然方法是為每個列分配一個名稱。第一種可能性是使用顯式結構化dtype,如前所述:
>>> data = BytesIO(b"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(b"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。
我們有時可能需要從數據本身定義列名稱。在這種情況下,我們必須使用值True
的names
關鍵字。然后將從第一行(在skip_header
之后)讀取名稱,即使行被注釋掉:
>>> data = BytesIO(b"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(b"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 參數
If names=None
but a structured dtype is expected, names are defined with the standard NumPy default of "f%i"
, yielding names like f0
, f1
and so forth:
>>> data = BytesIO(b"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(b"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(b"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
僅在預期某些名稱但未定義時使用。
Validating names
具有結構化dtype的NumPy數組也可以視為recarray
,其中可以像訪問屬性一樣訪問字段。因此,我們可能需要確保字段名稱不包含任何空格或無效字符,或者不符合標准屬性的名稱(例如size
或shape
),這將會混淆解釋器。genfromtxt
接受三個可選參數,對名稱提供更精細的控制:
-
deletechars
-
提供一個字符串,組合必須從名稱中刪除的所有字符。默認情況下,無效字符為
〜!@#$%^&amp; *() - = +〜\ |]} [{';: /?.& &lt;
。 -
excludelist
-
提供要排除的名稱列表,例如
return
,file
,print
...如果輸入名稱之一是此列表的一部分,將在其后面添加下划線字符('_'
)。 -
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(b"%")))/100. >>> data = b"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 = b"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.]])
使用 missing 和 filling values
在我們嘗試導入的數據集中可能會丟失某些條目。在前面的示例中,我們使用轉換器將空字符串轉換為浮點數。然而,用戶定義的轉換器可能迅速地變得難以管理。
genfromtxt
函數提供了另外兩個補充機制:missing_values
參數用於識別丟失的數據,第二個參數filling_values
這些丟失的數據。
missing_values
默認情況下,任何空字符串都標記為缺少。我們還可以考慮更復雜的字符串,例如"N/A"
或"???"
以表示丟失或無效的數據。missing_values
參數接受三種類型的值:
-
一個字符串或逗號分隔的字符串
-
此字符串將用作所有列的缺少數據的標記
-
字符串序列
-
在這種情況下,每個項目按順序與列相關聯。
-
一本字典
字典的值是字符串或字符串序列。相應的鍵可以是列索引(整數)或列名(字符串)。此外,特殊鍵None
可用於定義適用於所有列的默認值。
filling_values
我們知道如何識別丟失的數據,但我們仍然需要為這些丟失的條目提供一個值。默認情況下,此值根據此表從預期的dtype確定:
預期類型
默認
|
|
|
|
|
|
|
|
|
|
我們可以使用filling_values
可選參數對缺失值的轉換進行更精細的控制。像missing_values
一樣,此參數接受不同類型的值:
-
單個值
-
這將是所有列的默認值
-
一個值序列
-
每個條目將是相應列的默認值
-
一本字典
每個鍵可以是列索引或列名,並且相應的值應該是單個對象。我們可以使用特殊鍵None
為所有列定義默認值。
在下面的例子中,我們假設缺少的值在第一列中用"N/A"
標記,"???"
在第三列。我們希望將這些缺失值轉換為0,如果它們出現在第一列和第二列中,則轉換為-999,如果它們出現在最后一列中:
>>> data = b"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
。
Shortcut functions
除了genfromtxt
,numpy.lib.io
模塊提供了從genfromtxt
派生的幾個方便函數。這些函數的工作方式與原始函數相同,但它們具有不同的默認值。
ndfromtxt
- 始終設置
usemask=False
。輸出始終為標准numpy.ndarray
。
mafromtxt
- 始終設置
usemask=True
。輸出始終為MaskedArray
recfromtxt
- 返回標准
numpy.recarray
(ifusemask=False
)或MaskedRecords
數組(如果usemaske=True
。默認dtype為dtype=None
,表示每個列的類型將自動確定。
recfromcsv
- 類似於
recfromtxt
,但使用默認的delimiter=","
。