NumPy與輸入輸出
使用genfromtxt導(dǎo)入數(shù)據(jù)
NumPy提供了幾個(gè)函數(shù)來根據(jù)表格數(shù)據(jù)創(chuàng)建數(shù)組。我們將重點(diǎn)放在genfromtxt
函數(shù)上。
In a nutshell, genfromtxt
runs two main loops. 第一個(gè)循環(huán)以字符串序列轉(zhuǎn)換文件的每一行。第二個(gè)循環(huán)將每個(gè)字符串轉(zhuǎn)換為適當(dāng)?shù)臄?shù)據(jù)類型。這種機(jī)制比單一循環(huán)慢,但提供了更多的靈活性。特別的, genfromtxt
考慮到缺失值的情況, 其他更簡(jiǎn)單的方法如loadtxt
無法做到這點(diǎn).
注意
舉例時(shí),我們將使用以下約定:
>>> import numpy as np
>>> from io import BytesIO
定義輸入
genfromtxt
的唯一強(qiáng)制參數(shù)是數(shù)據(jù)的來源。它可以是一個(gè)字符串,一串字符串或一個(gè)生成器。如果提供了單個(gè)字符串,則假定它是本地或遠(yuǎn)程文件的名稱,或者帶有read
方法的開放文件類對(duì)象,例如文件或StringIO.StringIO
對(duì)象。如果提供了字符串列表或生成器返回字符串,則每個(gè)字符串在文件中被視為一行。當(dāng)傳遞遠(yuǎn)程文件的URL時(shí),該文件將自動(dòng)下載到當(dāng)前目錄并打開。
識(shí)別的文件類型是文本文件和檔案。目前,該功能可識(shí)別gzip
和bz2
(bzip2)檔案。歸檔文件的類型由文件的擴(kuò)展名決定:如果文件名以'.gz'
結(jié)尾,則需要一個(gè)gzip
歸檔文件;如果它以'bz2'
結(jié)尾,則假定bzip2
存檔。
將行拆分為列
delimiter
參數(shù)
一旦文件被定義并打開進(jìn)行讀取,genfromtxt
會(huì)將每個(gè)非空行分割為一串字符串。 空的或注釋的行只是略過。 delimiter
關(guān)鍵字用于定義拆分應(yīng)該如何進(jìn)行。
通常,單個(gè)字符標(biāo)記列之間的分隔。例如,逗號(hào)分隔文件(CSV)使用逗號(hào)(,
)或分號(hào)(;
)作為分隔符:
>>> data = "1, 2, 3\n4, 5, 6"
>>> np.genfromtxt(BytesIO(data), delimiter=",")
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
另一個(gè)常用的分隔符是"\t",即制表符。但是,我們不限于單個(gè)字符,任何字符串都可以。默認(rèn)情況下,genfromtxt
假定delimiter=None
,這意味著該行沿著空白區(qū)域(包括制表符)分割,并且連續(xù)的空白區(qū)域被視為單個(gè)空白區(qū)域。
或者,我們可能正在處理一個(gè)固定寬度的文件,其中列被定義為給定數(shù)量的字符。在這種情況下,我們需要將delimiter
設(shè)置為單個(gè)整數(shù)(如果所有列的大小相同)或整數(shù)序列(如果列的大小可能不同):
>>> 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
參數(shù)
默認(rèn)情況下,當(dāng)一行被分解為一系列字符串時(shí),單個(gè)條目不會(huì)被剝離前導(dǎo)空白或尾隨空白。通過將可選參數(shù)autostrip設(shè)置為值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
參數(shù)
可選參數(shù)comments
用于定義標(biāo)記注釋開始的字符串。默認(rèn)情況下,genfromtxt
假定comments='#'
。評(píng)論標(biāo)記可能發(fā)生在線上的任何地方。評(píng)論標(biāo)記之后的任何字符都會(huì)被忽略:
>>> 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.]]
注意
這種行為有一個(gè)明顯的例外:如果可選參數(shù)names=True
,則會(huì)檢查第一條注釋行的名稱。
跳過直線并選擇列
skip_header
和skip_footer
參數(shù)
文件中存在標(biāo)題可能會(huì)妨礙數(shù)據(jù)處理。在這種情況下,我們需要使用skip_header
可選參數(shù)。此參數(shù)的值必須是一個(gè)整數(shù),與執(zhí)行任何其他操作之前在文件開頭跳過的行數(shù)相對(duì)應(yīng)。同樣,我們可以使用skip_footer
屬性跳過文件的最后一行n
,并給它一個(gè)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.])
默認(rèn)情況下,skip_header=0
和skip_footer=0
,這意味著不會(huì)跳過任何行。
usecols
參數(shù)
在某些情況下,我們對(duì)數(shù)據(jù)的所有列不感興趣,但只有其中的一小部分。我們可以用usecols
參數(shù)選擇要導(dǎo)入的列。該參數(shù)接受與要導(dǎo)入的列的索引相對(duì)應(yīng)的單個(gè)整數(shù)或整數(shù)序列。請(qǐng)記住,按照慣例,第一列的索引為0。負(fù)整數(shù)的行為與常規(guī)Python負(fù)向索引相同。
例如,如果我們只想導(dǎo)入第一列和最后一列,我們可以使用usecols =(0, -1)
:
>>> data = "1 2 3\n4 5 6"
>>> np.genfromtxt(BytesIO(data), usecols=(0, -1))
array([[ 1., 3.],
[ 4., 6.]])
如果列有名稱,我們也可以通過將它們的名稱提供給usecols
參數(shù)來選擇要導(dǎo)入哪些列,可以將其作為字符串序列或逗號(hào)分隔字符串:
>>> 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')])
選擇數(shù)據(jù)的類型
控制我們從文件中讀取的字符串序列如何轉(zhuǎn)換為其他類型的主要方法是設(shè)置dtype
參數(shù)。這個(gè)參數(shù)的可接受值是:
- 單一類型,如
dtype=float
。除非使用names
參數(shù)將名稱與每個(gè)列關(guān)聯(lián)(見下文),否則輸出將是給定dtype的2D格式。請(qǐng)注意,dtype=float
是genfromtxt
的默認(rèn)值。 - 一系列類型,如
dtype =(int, float, float)
。 - 逗號(hào)分隔的字符串,例如
dtype="i4,f8,|S3"
。 - 一個(gè)包含兩個(gè)鍵
'names'
和'formats'
的字典。 - a sequence of tuples
(name, type)
, such asdtype=[('A', int), ('B', float)]
. - 現(xiàn)有的
numpy.dtype
對(duì)象。 - 特殊值
None
。在這種情況下,列的類型將根據(jù)數(shù)據(jù)本身確定(見下文)。
在所有情況下,除了第一種情況,輸出將是一個(gè)帶有結(jié)構(gòu)化dtype的一維數(shù)組。這個(gè)dtype與序列中的項(xiàng)目一樣多。字段名稱由names
關(guān)鍵字定義。
當(dāng)dtype=None
時(shí),每列的類型由其數(shù)據(jù)迭代確定。我們首先檢查一個(gè)字符串是否可以轉(zhuǎn)換為布爾值(也就是說,如果字符串在小寫字母中匹配true
或false
);然后是否可以將其轉(zhuǎn)換為整數(shù),然后轉(zhuǎn)換為浮點(diǎn)數(shù),然后轉(zhuǎn)換為復(fù)數(shù)并最終轉(zhuǎn)換為字符串。通過修改StringConverter
類的默認(rèn)映射器可以更改此行為。
為方便起見,提供了dtype=None選項(xiàng)。但是,它明顯比顯式設(shè)置dtype要慢。
設(shè)置名稱
names
參數(shù)
處理表格數(shù)據(jù)時(shí)的一種自然方法是為每列分配一個(gè)名稱。如前所述,第一種可能性是使用明確的結(jié)構(gòu)化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')])
另一種更簡(jiǎn)單的可能性是將names
關(guān)鍵字與一系列字符串或逗號(hào)分隔的字符串一起使用:
>>> 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')])
在上面的例子中,我們使用了默認(rèn)情況下dtype=float
的事實(shí)。通過給出一個(gè)名稱序列,我們強(qiáng)制輸出到一個(gè)結(jié)構(gòu)化的dtype。
我們有時(shí)可能需要從數(shù)據(jù)本身定義列名。在這種情況下,我們必須使用names
關(guān)鍵字的值為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
的默認(rèn)值為None
。如果我們給關(guān)鍵字賦予任何其他值,新名稱將覆蓋我們可能用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
參數(shù)
如果 names=None
的時(shí)候,只是預(yù)計(jì)會(huì)有一個(gè)結(jié)構(gòu)化的dtype,它的名稱將使用標(biāo)準(zhǔn)的NumPy默認(rèn)值 "f%i"
來定義,會(huì)產(chǎn)生例如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的長(zhǎng)度,缺少的名稱將使用此默認(rèn)模板進(jìn)行定義:
>>> 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
參數(shù)覆蓋此默認(rèn)值,該參數(shù)采用任何格式字符串:
>>> 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')])
注意!
我們需要記住,僅當(dāng)預(yù)期一些名稱但未定義時(shí)才使用defaultfmt
。
驗(yàn)證名稱
具有結(jié)構(gòu)化dtype的NumPy數(shù)組也可以被視為recarray
,其中可以像訪問屬性一樣訪問字段。因此,我們可能需要確保字段名稱不包含任何空格或無效字符,或者它不對(duì)應(yīng)于標(biāo)準(zhǔn)屬性的名稱(如size
或shape
),這會(huì)混淆解釋者。genfromtxt
接受三個(gè)可選參數(shù),這些參數(shù)可以更好地控制名稱:
deletechars
- 給出一個(gè)字符串,將所有必須從名稱中刪除的字符組合在一起。默認(rèn)情況下,無效字符是~!@#$%^&*()-=+~\|]}[{';: /?.>,<
excludelist
- 給出要排除的名稱列表,如return
,file
,print
...如果其中一個(gè)輸入名稱是該列表的一部分,則會(huì)附加一個(gè)下劃線字符('_'
)。case_sensitive
- 是否區(qū)分大小寫(case_sensitive=True
),轉(zhuǎn)換為大寫(case_sensitive=False
或case_sensitive='upper'
)或小寫(case_sensitive='lower'
)。
調(diào)整轉(zhuǎn)換
converters
參數(shù)
通常,定義一個(gè)dtype足以定義字符串序列必須如何轉(zhuǎn)換。但是,有時(shí)可能需要一些額外的控制。例如,我們可能希望確保格式為YYYY/MM/DD
的日期轉(zhuǎn)換為datetime
對(duì)象,或者像xx%
正確轉(zhuǎn)換為0到1之間的浮點(diǎn)數(shù)。在這種情況下,我們應(yīng)該使用converters
參數(shù)定義轉(zhuǎn)換函數(shù)。
該參數(shù)的值通常是以列索引或列名稱作為關(guān)鍵字的字典,并且轉(zhuǎn)換函數(shù)作為值。這些轉(zhuǎn)換函數(shù)可以是實(shí)際函數(shù)或lambda函數(shù)。無論如何,它們只應(yīng)接受一個(gè)字符串作為輸入,并只輸出所需類型的單個(gè)元素。
在以下示例中,第二列從代表百分比的字符串轉(zhuǎn)換為0和1之間的浮點(diǎn)數(shù):
>>> 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')])
我們需要記住,默認(rèn)情況下,dtype=float
。因此,對(duì)于第二列期望浮點(diǎn)數(shù)。但是,字符串'2.3%'
和'78.9%
無法轉(zhuǎn)換為浮點(diǎn)數(shù),我們最終改為使用np.nan
?,F(xiàn)在讓我們使用一個(gè)轉(zhuǎn)換器:
>>> # 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"
)作為關(guān)鍵字而不是其索引(1)的名稱,可以獲得相同的結(jié)果:
>>> # 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')])
轉(zhuǎn)換器也可以用來為缺少的條目提供默認(rèn)值。在以下示例中,如果字符串為空,則轉(zhuǎn)換器convert
會(huì)將已剝離的字符串轉(zhuǎn)換為相應(yīng)的浮點(diǎn)型或轉(zhuǎn)換為-999。我們需要明確地從空白處去除字符串,因?yàn)樗⑽茨J(rèn)完成:
>>> 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.]])
使用缺失值和填充值
我們嘗試導(dǎo)入的數(shù)據(jù)集中可能缺少一些條目。在前面的例子中,我們使用轉(zhuǎn)換器將空字符串轉(zhuǎn)換為浮點(diǎn)。但是,用戶定義的轉(zhuǎn)換器可能會(huì)很快變得繁瑣,難以管理。
genfromtxt
函數(shù)提供了另外兩種補(bǔ)充機(jī)制:missing_values
參數(shù)用于識(shí)別丟失的數(shù)據(jù),第二個(gè)參數(shù)filling_values
用于處理這些缺失的數(shù)據(jù)。
missing_values
默認(rèn)情況下,任何空字符串都被標(biāo)記為缺失。我們也可以考慮更復(fù)雜的字符串,比如"N/A"
或"???"
代表丟失或無效的數(shù)據(jù)。missing_values
參數(shù)接受三種值:
- 單個(gè)字符串或逗號(hào)分隔的字符串 - 該字符串將用作所有列缺失數(shù)據(jù)的標(biāo)記
- 字符串 - 在這種情況下,每個(gè)項(xiàng)目都按順序與列關(guān)聯(lián)。
- 字典類型 - 字典的值是字符串或字符串序列。相應(yīng)的鍵可以是列索引(整數(shù))或列名稱(字符串)。另外,可以使用特殊鍵None來定義適用于所有列的默認(rèn)值。
filling_values
我們知道如何識(shí)別丟失的數(shù)據(jù),但我們?nèi)匀恍枰獮檫@些丟失的條目提供一個(gè)值。默認(rèn)情況下,根據(jù)此表根據(jù)預(yù)期的dtype確定此值:
我們知道如何識(shí)別丟失的數(shù)據(jù),但我們?nèi)匀恍枰獮檫@些丟失的條目提供一個(gè)值。默認(rèn)情況下,根據(jù)此表根據(jù)預(yù)期的dtype確定此值:
預(yù)期類型 | 默認(rèn) |
---|---|
bool |
False |
int |
-1 |
float |
np.nan |
complex |
np.nan+0j |
string |
'???' |
通過filling_values
可選參數(shù),我們可以更好地控制缺失值的轉(zhuǎn)換。像missing_values
一樣,此參數(shù)接受不同類型的值:
- 單個(gè)值 - 這將是所有列的默認(rèn)值
- 類數(shù)組類型 - 每個(gè)條目都是相應(yīng)列的默認(rèn)值
- 字典類型 - 每個(gè)鍵可以是列索引或列名稱,并且相應(yīng)的值應(yīng)該是單個(gè)對(duì)象。我們可以使用特殊鍵None為所有列定義默認(rèn)值。
在下面的例子中,我們假設(shè)缺少的值在第一列中用"N/A"
標(biāo)記,并由"???"
在第三欄。如果它們出現(xiàn)在第一列和第二列中,我們希望將這些缺失值轉(zhuǎn)換為0,如果它們出現(xiàn)在最后一列中,則將它們轉(zhuǎn)換為-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
我們也可能想通過構(gòu)造一個(gè)布爾掩碼來跟蹤丟失數(shù)據(jù)的發(fā)生,其中True
條目缺少數(shù)據(jù),否則False
。為此,我們只需將可選參數(shù)usemask
設(shè)置為True
(默認(rèn)值為False
)。輸出數(shù)組將成為MaskedArray
。
快捷方式函數(shù)
除了 genfromtxt
之外,numpy.lib.io模塊還提供了幾個(gè)從genfromtxt
派生的方便函數(shù)。這些函數(shù)的工作方式與原始函數(shù)相同,但它們具有不同的默認(rèn)值。
- recfromtxt - 返回標(biāo)準(zhǔn) numpy.recarray
- (如果
usemask=False
)或 MaskedRecords數(shù)組(如果usemaske=True
)。默認(rèn)dtype是dtype=None
,意味著將自動(dòng)確定每列的類型。 - recfromcsv - 類似 recfromtxt,但有默認(rèn)值
delimiter=","
。
作者:柯廣的網(wǎng)絡(luò)日志 ? NumPy與輸入輸出
微信公眾號(hào):Java大數(shù)據(jù)與數(shù)據(jù)倉庫