使用 genfromtxt 导入数据#

NumPy 提供了几个函数用于从表格数据创建数组.我们在这里关注 genfromtxt 函数.

简而言之,:func:genfromtxt 运行两个主要的循环.第一个循环将文件的每一行转换为一个字符串序列.第二个循环将每个字符串转换为适当的数据类型.这种机制比单个循环慢,但提供了更多的灵活性.特别是,:func:genfromtxt 能够考虑缺失数据,而其他更快、更简单的函数如 loadtxt 则不能.

备注

在给出示例时,我们将使用以下约定:

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

定义输入#

genfromtxt 的唯一强制参数是数据的来源.它可以是一个字符串、一个字符串列表、一个生成器或一个具有 read 方法的类文件对象,例如文件或 io.StringIO 对象.如果提供单个字符串,则假定它是本地或远程文件的名称.如果提供字符串列表或返回字符串的生成器,则每个字符串都被视为文件中的一行.当传递远程文件的URL时,该文件会自动下载到当前目录并打开.

识别的文件类型是文本文件和归档文件.目前,该功能识别 gzipbz2 (bzip2) 归档文件.归档文件的类型根据文件扩展名确定:如果文件名以 '.gz' 结尾,则预期为 gzip 归档文件;如果以 'bz2' 结尾,则假定为 bzip2 归档文件.

将行分割成列#

delimiter 参数#

一旦文件被定义并打开以供读取,:func:genfromtxt 将每个非空行拆分为一系列字符串.空行或注释行将被跳过.``delimiter`` 关键字用于定义拆分应如何进行.

通常,一个单独的字符标记了列之间的分隔.例如,逗号分隔文件(CSV)使用逗号(,)或分号(;)作为分隔符:

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

另一个常见的分隔符是 "\t",即制表符.然而,我们不仅限于单个字符,任何字符串都可以.默认情况下,:func:genfromtxt 假设 delimiter=None,这意味着行按空白字符(包括制表符)分割,并且连续的空白字符被视为单个空白字符.

或者,我们可能正在处理一个固定宽度的文件,其中列被定义为给定数量的字符.在这种情况下,我们需要将 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 用于定义一个字符串,该字符串标记注释的开始.默认情况下,:func: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.]])

在 1.7.0 版本加入: comments 设置为 None 时,没有行被视为注释.

备注

这种行为有一个显著的例外:如果可选参数 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 .输出将是具有给定 dtype 的二维数组,除非每个列都与 names 参数(见下文)一起使用名称进行了关联.请注意,``dtype=float`` 是 genfromtxt 的默认值.

  • 一系列类型,例如 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#

我们知道如何识别缺失的数据,但我们仍然需要为这些缺失的条目提供一个值.默认情况下,这个值是根据预期的数据类型从这个表中确定的:

预期类型

默认

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

usemask#

我们可能还希望通过构建一个布尔掩码来跟踪缺失数据的出现,其中数据缺失的地方用 True 条目表示,否则用 False 表示.为此,我们只需将可选参数 usemask 设置为 True``(默认值是 ``False).输出数组将是一个 MaskedArray.