使用 genfromtxt# 匯入資料

NumPy 提供了幾個函數來從表格資料建立陣列。我們在這裡著重於 genfromtxt 函數。

簡而言之,genfromtxt 執行兩個主要迴圈。第一個迴圈將檔案的每一行轉換為字串序列。第二個迴圈將每個字串轉換為適當的資料類型。這種機制比單一迴圈慢,但提供更大的彈性。特別是,當其他更快更簡單的函數(如 loadtxt)無法處理遺失資料時,genfromtxt 能夠將遺失資料納入考量。

注意

在給出範例時,我們將使用以下慣例

>>> import numpy as np
>>> from io import StringIO

定義輸入#

genfromtxt 的唯一強制性引數是資料來源。它可以是字串、字串列表、產生器或具有 read 方法的開啟檔案類物件,例如檔案或 io.StringIO 物件。如果提供單一字串,則假定它是本機或遠端檔案的名稱。如果提供字串列表或傳回字串的產生器,則每個字串都視為檔案中的一行。當傳遞遠端檔案的 URL 時,該檔案會自動下載到目前目錄並開啟。

可辨識的檔案類型為文字檔和封存檔。目前,該函數可辨識 gzipbz2 (bzip2) 封存檔。封存檔的類型是從檔案的副檔名判斷:如果檔名以 '.gz' 結尾,則預期為 gzip 封存檔;如果它以 'bz2' 結尾,則假定為 bzip2 封存檔。

將行分割成欄位#

delimiter 引數#

一旦檔案被定義並開啟以供讀取,genfromtxt 會將每個非空行分割成字串序列。空白行或註解行會被跳過。delimiter 關鍵字用於定義應如何進行分割。

通常,單一字元標記欄位之間的分隔。例如,逗號分隔檔案 (CSV) 使用逗號 (,) 或分號 (;) 作為分隔符

>>> data = "1, 2, 3\n4, 5, 6"
>>> np.genfromtxt(StringIO(data), delimiter=",")
array([[1.,  2.,  3.],
       [4.,  5.,  6.]])

另一個常見的分隔符是 "\t",即 Tab 字元。但是,我們不限於單一字元,任何字串都可以。預設情況下,genfromtxt 假定 delimiter=None,這表示該行沿著空白字元(包括 Tab 字元)分割,並且連續的空白字元被視為單一空白字元。

或者,我們可能正在處理固定寬度的檔案,其中欄位被定義為給定的字元數。在這種情況下,我們需要將 delimiter 設定為單一整數(如果所有欄位都具有相同的大小)或整數序列(如果欄位可以具有不同的大小)

>>> data = "  1  2  3\n  4  5 67\n890123  4"
>>> np.genfromtxt(StringIO(data), delimiter=3)
array([[  1.,    2.,    3.],
       [  4.,    5.,   67.],
       [890.,  123.,    4.]])
>>> data = "123456789\n   4  7 9\n   4567 9"
>>> np.genfromtxt(StringIO(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(StringIO(data), delimiter=",", dtype="|U5")
array([['1', ' abc ', ' 2'],
       ['3', ' xxx', ' 4']], dtype='<U5')
>>> # With autostrip
>>> np.genfromtxt(StringIO(data), delimiter=",", dtype="|U5", autostrip=True)
array([['1', 'abc', '2'],
       ['3', 'xxx', '4']], dtype='<U5')

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(StringIO(data), comments="#", delimiter=",")
array([[1., 2.],
       [3., 4.],
       [5., 6.],
       [7., 8.],
       [9., 0.]])

注意

此行為有一個值得注意的例外:如果選用引數 names=True,則將檢查第一個註解行以取得名稱。

跳過行並選擇欄位#

usecols 引數#

在某些情況下,我們對資料的所有欄位不感興趣,而只對其中的一些感興趣。我們可以透過 usecols 引數選擇要匯入的欄位。此引數接受單一整數或對應於要匯入欄位索引的整數序列。請記住,依照慣例,第一欄的索引為 0。負整數的行為與一般 Python 負索引相同。

例如,如果我們只想匯入第一欄和最後一欄,我們可以使用 usecols=(0, -1)

>>> data = "1 2 3\n4 5 6"
>>> np.genfromtxt(StringIO(data), usecols=(0, -1))
array([[1.,  3.],
       [4.,  6.]])

如果欄位有名稱,我們也可以透過將欄位名稱提供給 usecols 引數來選擇要匯入的欄位,可以作為字串序列或逗號分隔字串

>>> data = "1 2 3\n4 5 6"
>>> np.genfromtxt(StringIO(data),
...               names="a, b, c", usecols=("a", "c"))
array([(1., 3.), (4., 6.)], dtype=[('a', '<f8'), ('c', '<f8')])
>>> np.genfromtxt(StringIO(data),
...               names="a, b, c", usecols=("a, c"))
    array([(1., 3.), (4., 6.)], dtype=[('a', '<f8'), ('c', '<f8')])

選擇資料類型#

控制如何將從檔案讀取的字串序列轉換為其他類型的主要方法是設定 dtype 引數。此引數的可接受值為

  • 單一類型,例如 dtype=float。輸出將為 2D,並具有給定的 dtype,除非已使用 names 引數為每個欄位關聯名稱(請參閱下文)。請注意,dtype=floatgenfromtxt 的預設值。

  • 類型序列,例如 dtype=(int, float, float)

  • 逗號分隔字串,例如 dtype="i4,f8,|U3"

  • 具有兩個鍵 'names''formats' 的字典。

  • 元組序列 (name, type),例如 dtype=[('A', int), ('B', float)]

  • 現有的 numpy.dtype 物件。

  • 特殊值 None。在這種情況下,欄位的類型將從資料本身決定(請參閱下文)。

在除了第一種情況之外的所有情況下,輸出都將是一個具有結構化 dtype 的 1D 陣列。此 dtype 具有與序列中項目一樣多的欄位。欄位名稱使用 names 關鍵字定義。

dtype=None 時,每個欄位的類型會從其資料迭代決定。我們先檢查字串是否可以轉換為布林值(也就是說,字串是否與小寫的 truefalse 匹配);然後檢查它是否可以轉換為整數,然後是浮點數,然後是複數,最後是字串。

提供選項 dtype=None 是為了方便起見。但是,它明顯比明確設定 dtype 慢。

設定名稱#

names 引數#

處理表格資料時,一種自然的方法是為每個欄位分配一個名稱。第一種可能性是使用明確的結構化 dtype,如先前所述

>>> data = StringIO("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 = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, names="A, B, C")
array([(1., 2., 3.), (4., 5., 6.)],
      dtype=[('A', '<f8'), ('B', '<f8'), ('C', '<f8')])

在上面的範例中,我們使用了預設 dtype=float 的事實。透過給定名稱序列,我們強制輸出為結構化 dtype。

有時我們可能需要從資料本身定義欄位名稱。在這種情況下,我們必須將 names 關鍵字與值 True 一起使用。然後將從第一行(在 skip_header 行之後)讀取名稱,即使該行被註解掉

>>> data = StringIO("So it goes\n#a b c\n1 2 3\n 4 5 6")
>>> np.genfromtxt(data, skip_header=1, names=True)
array([(1., 2., 3.), (4., 5., 6.)],
      dtype=[('a', '<f8'), ('b', '<f8'), ('c', '<f8')])

names 的預設值為 None。如果我們為關鍵字提供任何其他值,則新名稱將覆寫我們可能已使用 dtype 定義的欄位名稱

>>> data = StringIO("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., 3), (4, 5., 6)],
      dtype=[('A', '<i8'), ('B', '<f8'), ('C', '<i8')])

defaultfmt 引數#

如果 names=None 但預期為結構化 dtype,則名稱會使用 NumPy 標準預設值 "f%i" 定義,產生類似 f0f1 等名稱

>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, dtype=(int, float, int))
array([(1, 2., 3), (4, 5., 6)],
      dtype=[('f0', '<i8'), ('f1', '<f8'), ('f2', '<i8')])

同樣地,如果我們沒有提供足夠的名稱來匹配 dtype 的長度,則遺失的名稱將使用此預設範本定義

>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, dtype=(int, float, int), names="a")
array([(1, 2., 3), (4, 5., 6)],
      dtype=[('a', '<i8'), ('f0', '<f8'), ('f1', '<i8')])

我們可以透過 defaultfmt 引數覆寫此預設值,該引數接受任何格式字串

>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, dtype=(int, float, int), defaultfmt="var_%02i")
array([(1, 2., 3), (4, 5., 6)],
      dtype=[('var_00', '<i8'), ('var_01', '<f8'), ('var_02', '<i8')])

注意

我們需要記住,只有在預期某些名稱但未定義時,才會使用 defaultfmt

驗證名稱#

具有結構化 dtype 的 NumPy 陣列也可以視為 recarray,其中欄位可以像屬性一樣存取。因此,我們可能需要確保欄位名稱不包含任何空格或無效字元,或者它不對應於標準屬性的名稱(如 sizeshape),這會混淆直譯器。genfromtxt 接受三個選用引數,這些引數提供對名稱更精細的控制

deletechars

給定一個字串,組合所有必須從名稱中刪除的字元。預設情況下,無效字元為 ~!@#$%^&*()-=+~\|]}[{';: /?.>,<

excludelist

給定要排除的名稱列表,例如 returnfileprint... 如果輸入名稱之一是此列表的一部分,則會在後面附加底線字元 ('_')。

case_sensitive

名稱是否應區分大小寫 (case_sensitive=True)、轉換為大寫 (case_sensitive=Falsecase_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(StringIO(data), delimiter=",", names=names)
array([(1., nan, 45.), (6., nan, 0.)],
      dtype=[('i', '<f8'), ('p', '<f8'), ('n', '<f8')])

我們需要記住,預設情況下,dtype=float。因此,第二欄預期為浮點數。但是,字串 ' 2.3%'' 78.9%' 無法轉換為浮點數,最終我們得到 np.nan 而不是浮點數。現在讓我們使用轉換器

>>> # Converted case ...
>>> np.genfromtxt(StringIO(data), delimiter=",", names=names,
...               converters={1: convertfunc})
array([(1., 0.023, 45.), (6., 0.789, 0.)],
      dtype=[('i', '<f8'), ('p', '<f8'), ('n', '<f8')])

可以使用第二欄的名稱 ("p") 作為鍵而不是其索引 (1) 來獲得相同的結果

>>> # Using a name for the converter ...
>>> np.genfromtxt(StringIO(data), delimiter=",", names=names,
...               converters={"p": convertfunc})
array([(1., 0.023, 45.), (6., 0.789, 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(StringIO(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 決定

預期類型

預設值

bool

False

int

-1

0

float

np.nan

complex

np.nan+0j

'???'

string

""

我們可以透過 filling_values 選用引數更精細地控制遺失值的轉換。與 missing_values 類似,此引數接受不同類型的值

單一值

這將是所有欄位的預設值

字典

值序列

每個條目都將是相應欄位的預設值

>>> 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(StringIO(data), **kwargs)
array([(0, 2, 3), (4, 0, -999)],
      dtype=[('a', '<i8'), ('b', '<i8'), ('c', '<i8')])

字典

每個鍵可以是欄位索引或欄位名稱,對應的值應該是單一物件。我們可以使用特殊鍵 None 來定義所有欄位的預設值。

在以下範例中,我們假設遺失值在第一欄中標記為 "N/A",在第三欄中標記為 "???"。我們希望將這些遺失值轉換為 0(如果它們出現在第一欄和第二欄中),如果它們出現在最後一欄中,則轉換為 -999