NumPy 提供了多种从表格数据创建数组的函数,本文重点介绍 genfromtxt。
简而言之,genfromtxt 执行两轮主要循环:第一轮把文件中的每一行转换成字符串序列;第二轮再把每个字符串转换成合适的数据类型。两轮循环比单轮循环慢,但更加灵活。尤其是,它能处理缺失数据,而更快、更简单的 loadtxt 等函数无法以同样方式处理这些情况。
下面的示例统一使用:
>>> import numpy as np
>>> from io import StringIO
定义输入
genfromtxt 唯一必需的参数是数据来源。它可以是字符串、字符串列表、生成器,或一个已经打开、提供 read 方法的类文件对象,例如文件或 io.StringIO。
如果提供单个字符串,该字符串被视为本地或远程文件名。如果提供字符串列表或返回字符串的生成器,每个字符串被视为文件中的一行。传入远程文件 URL 时,函数会自动将文件下载到当前目录并打开。
可识别的文件类型包括文本文件与压缩文件。目前支持 gzip 和 bz2,也就是 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。分隔符并不限于单个字符,也可以是任意字符串。默认 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 定义标记注释开始的字符串,默认值为 '#'。注释标记可以出现在一行中的任意位置,标记之后的所有字符都会被忽略:
>>> 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,函数会检查首个用于读取列名的注释行。
跳过行与选择列
skip_header 和 skip_footer
文件头可能妨碍数据处理。使用 skip_header 可以在其他操作开始前,跳过文件开头指定数量的行,其值必须是整数。类似地,将 skip_footer 设为 n,可以跳过文件末尾的 n 行:
>>> data = "\n".join(str(i) for i in range(10))
>>> np.genfromtxt(StringIO(data),)
array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
>>> np.genfromtxt(StringIO(data),
... skip_header=3, skip_footer=5)
array([3., 4.])
两个参数默认都为 0,即不跳过任何行。
usecols 参数
如果只需要部分列,可通过 usecols 选择要导入的列。参数可以是单个整数,也可以是对应列索引的整数序列。第一列的索引是 0,负整数的含义与普通 Python 负索引相同。
例如,只读取第一列和最后一列:
>>> data = "1 2 3\n4 5 6"
>>> np.genfromtxt(StringIO(data), usecols=(0, -1))
array([[1., 3.],
[4., 6.]])
如果列有名称,也可以按列名选择:传入字符串序列,或者用逗号分隔的字符串。
>>> 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=np.float64。输出通常是指定类型的二维数组,除非使用names为各列命名。np.float64也是genfromtxt的默认类型。 - 类型序列,例如
dtype=(np.int_, np.float64, np.float64)。 - 逗号分隔的类型字符串,例如
dtype="i4,f8,|U3"。 - 包含
'names'与'formats'两个键的字典。 (name, type)元组序列,例如dtype=[('A', np.int_), ('B', np.float64)]。- 已有的
numpy.dtype对象。 - 特殊值
None,表示从数据本身推断每列类型。
除第一种情况外,输出是具有结构化 dtype 的一维数组。该 dtype 的字段数与序列项数相同,字段名通过 names 指定。
使用 dtype=None 时,会逐步尝试推断每列类型:先判断字符串能否转换为布尔值,也就是是否对应小写 true 或 false;再尝试整数、浮点数、复数,最后才使用字符串。
dtype=None 很方便,但明显慢于显式指定类型。
设置列名
names 参数
处理表格时,通常会为每列分配名称。一种方法是显式使用结构化 dtype:
>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, dtype=[(_, np.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=np.float64。提供名称序列会强制输出使用结构化 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', np.int_), ('b', np.float64), ('c', np.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",得到 f0、f1 等:
>>> data = StringIO("1 2 3\n 4 5 6")
>>> np.genfromtxt(data, dtype=(np.int_, np.float64, np.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=(np.int_, np.float64, np.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=(np.int_, np.float64, np.int_), defaultfmt="var_%02i")
array([(1, 2., 3), (4, 5., 6)],
dtype=[('var_00', '<i8'), ('var_01', '<f8'), ('var_02', '<i8')])
注意:defaultfmt 只有在需要名称、但某些名称未定义时才会使用。
列名合法性
结构化 NumPy 数组也可以视为 recarray,此时可像访问属性一样访问字段。因此,应避免字段名中包含空格或无效字符,也要避免与 size、shape 等标准属性重名,以免产生混淆。
genfromtxt 提供三个参数进一步控制字段名:
deletechars:字符串中列出需要从字段名删除的全部字符。默认无效字符包括~!@#$%^&*()-=+~\|]}[{';: /?.>,<等原文所列标点。excludelist:指定排除的名称列表,例如return、file、print。如果输入名称在列表中,会在末尾追加下划线_。case_sensitive:设为True保留大小写;设为False或'upper'转为大写;设为'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 是 np.float64,所以第二列也被视为浮点数。但 ' 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。由于默认不会清除空白,这里必须显式调用 strip():
>>> 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"、"???" 等字符串视为缺失或无效数据。参数接受:
- 字符串或逗号分隔的字符串:作为所有列的缺失标记。
- 字符串序列:各项按顺序对应各列。
- 字典:值为字符串或字符串序列;键为列索引或列名。特殊键
None可定义适用于全部列的默认标记。
filling_values
识别缺失项之后,还要为这些位置提供值。默认填充值取决于预期 dtype:
| 预期类型 | 默认填充值 |
|---|---|
bool | False |
int | -1 |
float | np.nan |
complex | np.nan+0j |
| 字符串 | '???' |
filling_values 可进一步控制填充,接受:
- 单个值:作为全部列的默认值。
- 值序列:每一项作为对应列的默认值。
- 字典:键为列索引或列名,值为单个对象。特殊键
None可定义全部列的默认值。
下面假设第一列用 "N/A" 标记缺失,第三列用 "???" 标记缺失。希望第一、第二列的缺失项填 0,最后一列填 -999:
>>> data = "N/A, 2, 3\n4, ,???"
>>> kwargs = dict(delimiter=",",
... dtype=np.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。
原文版权声明:© Copyright 2008-2026, NumPy Developers。许可信息见 NumPy License。
原文:Importing data with genfromtxt。本文为该文档的中文译文,示例代码保留原文。











暂无评论内容