Numpy 修煉之道 (12)—— genfromtxt函式
定義輸入
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 56"
>>> np.genfromtxt(BytesIO(data), usecols=(0, -1))
array([[ 1., 3.],[ 4., 6.]])
如果列具有名稱,我們還可以通過將其名稱作為字串序列或逗號分隔字串的形式,將其名稱指定給usecols引數來選擇要匯入的列:
忽略某些行或某些列
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 56"
>>> 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'的字典。
· 元組的序列(名稱, 型別),例如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("1 2 3\n 45 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 45 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("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 45 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("1 2 3\n 45 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 45 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 45 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("%"))/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.]])
使用 missing 和
filling values
在我們嘗試匯入的資料集中可能會丟失某些條目。在前面的示例中,我們使用轉換器將空字串轉換為浮點數。然而,使用者定義的轉換器可能迅速地變得難以管理。
genfromtxt函式提供了另外兩個補充機制:missing_values引數用於識別丟失的資料,第二個引數filling_values這些丟失的資料。
missing_values
預設情況下,任何空字串都標記為缺少。我們還可以考慮更復雜的字串,例如"N/A"或"???"以表示丟失或無效的資料。missing_values引數接受三種類型的值:
· 一個字串或逗號分隔的字串
· 此字串將用作所有列的缺少資料的標記
· 字串序列
· 在這種情況下,每個專案按順序與列相關聯。
· 一本字典
· 字典的值是字串或字串序列。相應的鍵可以是列索引(整數)或列名(字串)。此外,特殊鍵None可用於定義適用於所有列的預設值。
filling_values
我們知道如何識別丟失的資料,但我們仍然需要為這些丟失的條目提供一個值。預設情況下,此值根據此表從預期的dtype確定:
預期型別
預設
bool False
int -1
float np.nan
complex np.nan+0.j
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。
Shortcut
functions
除了genfromtxt,numpy.lib.io模組提供了從genfromtxt派生的幾個方便函式。這些函式的工作方式與原始函式相同,但它們具有不同的預設值。
· ndfromtxt
· 始終設定usemask=False。輸出始終為標準numpy.ndarray。
· mafromtxt
· 始終設定usemask=True。輸出始終為MaskedArray
· recfromtxt
· 返回標準numpy.recarray(if usemask=False)或MaskedRecords陣列(如果usemaske=True。預設dtype為dtype=None,表示每個列的型別將自動確定。
· recfromcsv
· 類似於recfromtxt,但使用預設的delimiter=","。
腦洞科技棧專注於人工智慧與量化投資領域