使用Numpy进行I/O操作
NumPy 提供了几个函数来从表格数据创建数组。我们在这里专注于 genfromtxt 函数。
导包
import numpy as np
from io import StringIO
【1】定义输入
genfromtxt的唯一强制性参数是数据的来源。它可以是字符串、字符串列表、生成器或具有read方法的打开文件类对象,例如文件或 io.StringIO对象。如果提供了单个字符串,则假定它是本地或远程文件的名称。如果提供了字符串列表或返回字符串的生成器,则每个字符串将被视为文件中的一行。传递远程文件的 URL 时,该文件会自动下载到当前目录并打开。
可识别的文件类型是文本文件和存档。目前,该函数可识别 gzip 和 bz2) 存档。bzip2存档的类型由文件的扩展名确定:如果文件名以’.gz’结尾,则需要 gzip 存档;如果它以’bz2’结尾,则假定为 bzip2 存档。
【2】将行拆分为列
① delimiter 参数
delimiter关键字用于定义拆分应如何进行。
data = u"1,2,3\n4,5,6"
print(data)
1,2,3
4,5,6
print(np.genfromtxt(StringIO(data), delimiter=','))
[[1. 2. 3.]
[4. 5. 6.]]
另一个常见的分隔符是"\t",即制表字符。但是,我们不限于单个字符,任何字符串都可以。默认情况下,genfromtxt 假定 delimiter=None,这意味着该行沿空格(包括制表符)拆分,并且连续的空格被视为单个空格。
或者,我们可以处理一个固定宽度的文件,其中列被定义为给定数量的字符。在这种情况下,我们需要将delimiter设置为单个整数(如果所有列的大小相同)或整数序列(如果列可以具有不同的大小):
data = u" 1 2 3\n 4 5 67\n890123 4"
print(data)
1 2 3
4 5 67
890123 4
print(np.genfromtxt(StringIO(data), delimiter=3))
[[ 1. 2. 3.]
[ 4. 5. 67.]
[890. 123. 4.]]
data = u"123456789\n 4 7 9\n 4567 9"
print(data)
123456789
4 7 9
4567 9
print(np.genfromtxt(StringIO(data), delimiter=(4, 3, 2)))
[[1234. 567. 89.]
[ 4. 7. 9.]
[ 4. 567. 9.]]
② autostrip参数
默认情况下,当一行分解为一系列字符串时,各个条目不会去除前导空格或尾随空格。可以通过将可选参数autostrip设置为值 True 来覆盖此行为:
data = u"1, abc , 2\n 3, xxx, 4"
print(data)
1, abc , 2
3, xxx, 4
# 没有autostrip
print(np.genfromtxt(StringIO(data), delimiter=',', dtype="|U5"))
[['1' ' abc ' ' 2']
['3' ' xxx' ' 4']]
可以看到这样转来有空格“不干净”
# 用上autostrip
print(np.genfromtxt(StringIO(data), delimiter=',', dtype="|U5", autostrip=True))
[['1' 'abc' '2']
['3' 'xxx' '4']]
这样看起来好多了
③ comments参数
可选参数comments用于定义标记注释开头的字符串。默认情况下,genfromtxt 假定 comments=’#’。注释标记可能出现在行中的任何位置。注释标记后出现的任何字符都将被忽略:
data = u"""#
# 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
"""
print(np.genfromtxt(StringIO(data), comments="#", delimiter=','))
[[1. 2.]
[3. 4.]
[5. 6.]
[7. 8.]
[9. 0.]]
【注意】版本 1.7.0 中的新功能: 当注释设置为None时,不会将任何行视为comments。
【3】跳过行和选择列
① skip_header和skip_footer
文件中存在标头可能会阻碍数据处理。在这种情况下,我们需要使用 skip_header 可选参数。此参数的值必须是一个整数,对应于在执行任何其他操作之前要在文件开头跳过的行数。同样,我们可以通过使用 skip_footer 属性并为其指定值 n 来跳过文件的最后 n 行
data = u"\n".join(str(i) for i in range(10))
print(data)
0
1
2
3
4
5
6
7
8
9
print(np.genfromtxt(StringIO(data), ))
[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
默认情况下,skip_header=0 和 skip_footer=0,这意味着不会跳过任何行。
print(np.genfromtxt(StringIO(data), skip_header=3, skip_footer=5))
[3. 4.]
② usecols 参数
在某些情况下,我们不是对数据的所有列都感兴趣,而只对其中的几列感兴趣。
例如,如果我们只想导入第一列和最后一列,我们可以使用 usecols=(0, -1):
data = u"1 2 3\n4 5 6"
print(data)
1 2 3
4 5 6
print(np.genfromtxt(StringIO(data), usecols=(0, -1)))
[[1. 3.]
[4. 6.]]
如果列有名称,我们还可以通过为 usecols 参数命名来选择要导入的列
data = u"1 2 3\n4 5 6"
print(data)
1 2 3
4 5 6
print(np.genfromtxt(StringIO(data), names="a,b,c", usecols=("a", "c")))
[(1., 3.) (4., 6.)]
print(np.genfromtxt(StringIO(data), names="a,b,c", usecols=("a,c")))
[(1., 3.) (4., 6.)]
【4】选择数据类型
控制如何将我们从文件中读取的字符串序列转换为其他类型的主要方法是设置 dtype 参数。此参数的可接受值为:
- 单个类型,例如 dtype=float。
- 类型序列,例如 dtype=(int, float, float).
- 逗号分隔的字符串,例如 dtype=“i4,f8,|U3”.
- 具有两个键’names’”和’formats’的字典.
- 元组序列((name, type)例如 dtype=[(‘A’, int), (‘B’, float)].
- 现有的 numpy.dtype 对象。
- 特殊值 None。在这种情况下,列的类型将由数据本身确定
【5】设置名称
① names 参数
处理表格数据时的一种自然方法是为每列分配一个名称。
data = StringIO("1 2 3\n 4 5 6")
print(data)
<_io.StringIO object at 0x00000217D2591310>
print(np.genfromtxt(data, dtype=[(_, int) for _ in "abc"]))
[(1, 2, 3) (4, 5, 6)]
print(np.genfromtxt(data, dtype=[(_, int) for _ in "abc"]).dtype)
[('a', '<i4'), ('b', '<i4'), ('c', '<i4')]
C:\Users\DingJiaxiong\AppData\Local\Temp\ipykernel_6888\903119735.py:1: UserWarning: genfromtxt: Empty input file: "<_io.StringIO object at 0x00000217D2591310>"
print(np.genfromtxt(data, dtype=[(_, int) for _ in "abc"]).dtype)
另一种更简单的方法是将 names 关键字与一系列字符串或逗号分隔的字符串一起使用:
data = StringIO("1 2 3\n 4 5 6")
print(data)
<_io.StringIO object at 0x00000217D2591550>
print(np.genfromtxt(data, names="A,B,C"))
[(1., 2., 3.) (4., 5., 6.)]
print(np.genfromtxt(data, names="A,B,C").dtype)
[('A', '<f8'), ('B', '<f8'), ('C', '<f8')]
C:\Users\DingJiaxiong\AppData\Local\Temp\ipykernel_6888\3894872847.py:1: UserWarning: genfromtxt: Empty input file: "<_io.StringIO object at 0x00000217D2591550>"
print(np.genfromtxt(data, names="A,B,C").dtype)
我们有时可能需要从数据本身定义列名。【使用值为 True 的 names 关键字。】
data = StringIO("So it goes\n#a b c\n1 2 3\n 4 5 6")
print(np.genfromtxt(data, skip_header=1, names=True))
[(1., 2., 3.) (4., 5., 6.)]
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', '<i4'), ('B', '<f8'), ('C', '<i4')])
② defaultfmt 参数
如果 names=None 但需要结构化 dtype,则使用标准 NumPy 默认值"f%i"定义名称,生成 f0、f1 等名称
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', '<i4'), ('f1', '<f8'), ('f2', '<i4')])
同样,如果我们没有给出足够的名称来匹配 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', '<i4'), ('f0', '<f8'), ('f1', '<i4')])
我们可以用 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', '<i4'), ('var_01', '<f8'), ('var_02', '<i4')])
需要记住,defaultfmt 仅在预期但未定义某些名称时使用。
③ 验证名称
具有结构化 dtype 的 NumPy 数组也可以被视为 recarray,其中字段可以像属性一样访问。出于这个原因,我们可能需要确保字段名称不包含任何空格或无效字符,或者它与标准属性的名称(如size或shape)不对应,这会混淆解释器。genfromtxt 接受三个可选参数,这些参数提供了对名称的更精细控制:
-
deletechars
提供一个字符串,其中包含必须从名称中删除的所有字符。默认情况下,无效字符是|]}[{’;: /?.>,<. -
excludelist
给出要排除的名称列表,例如return、file、print…如果其中一个输入名称是此列表的一部分,则会在其后附加下划线字符 (’’_’)。 -
case_sensitive
名称是否应区分大小写 (case_sensitive=True)、转换为大写 (case_sensitive=False 或 case_sensitive=‘upper’) 或小写 (case_sensitive='upper’case_sensitive=‘lower’).
【6】调整转换
① converters 参数
通常,定义 dtype 足以定义必须如何转换字符串序列。但是,有时可能需要一些额外的控制。例如,我们可能希望确保将YYYY/MM/DD 的日期转换为datetime对象,或者将 xx% 等字符串正确转换为介于 0 和 1 之间的浮点数。在这种情况下,我们应该使用 converters 参数定义转换函数。
在下面的示例中,第二列从表示百分比的字符串转换为介于 0 和 1 之间的浮点数:
convertfunc = lambda x: float(x.strip(b"%")) / 100.
print(convertfunc)
<function <lambda> at 0x00000217D25AF9D0>
data = u"1, 2.3%, 45.\n6, 78.9%, 0"
print(data)
1, 2.3%, 45.
6, 78.9%, 0
names = ("i", "p", "n")
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。现在让我们使用转换器:
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),可以获得相同的结果:
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 = u"1, , 3\n 4, 5, 6"
print(data)
1, , 3
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 用于处理这些缺失的数据。
1 missing_values
默认情况下,任何空字符串都标记为缺失。我们还可以考虑更复杂的字符串,例如““N/A"或”???"来表示丢失或无效的数据。missing_values参数接受三种类型的值:
- 字符串或逗号分隔的字符串
此字符串将用作所有列缺少数据的标记 - 字符串序列
在这种情况下,每个项目都按顺序关联到一列。 - 字典
字典的值是字符串或字符串序列。相应的键可以是列索引(整数)或列名(字符串)。此外,特殊键 None 可用于定义适用于所有列的默认值。
2 filling_values
我们知道如何识别缺失的数据,但我们仍然需要为这些缺失的条目提供一个值。默认情况下,此值是根据下表从预期的 dtype 确定的:
我们可以使用filling_values可选参数更精细地控制缺失值的转换。与missing_values一样,此参数接受不同类型的值:
-
单个值
这将是所有列的默认值 -
值序列
每个条目将是对应列的默认值 -
字典
每个键可以是列索引或列名,对应的值应该是单个对象。我们可以使用特殊键 None 为所有列定义默认值。
在下面的示例中,我们假设缺失值在第一列中标记为““N/A”,在第三列中用"???"标记。如果这些缺失值出现在第一列和第二列中,我们希望将它们转换为 0,如果它们出现在最后一列中,我们希望将其转换为 -999:
data = u"N/A, 2, 3\n4, ,???"
print(data)
N/A, 2, 3
4, ,???
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', '<i4'), ('b', '<i4'), ('c', '<i4')])
3 usemask
我们可能还希望通过构造布尔掩码来跟踪丢失数据的发生,在缺少数据的地方使用 True 条目,否则使用 False 条目。为此,我们只需要将可选参数 usemask 设置为 True(默认值为 False)。然后,输出数组将是一个MaskedArray.
【7】快捷键功能
除了 genfromtxt 之外,numpy.lib.npyio 模块还提供了几个从 genfromtxt 派生的便捷函数。这些函数的工作方式与原始函数相同,但它们具有不同的默认值。
numpy.lib.npyio.recfromtxt
返回标准 numpy.recarray (if usemask=False) 或 numpy.ma.mrecords.MaskedRecords 数组(if usemaske=True)。usemask=False默认的 dtype 为 dtype=None,这意味着将自动确定每列的类型。
numpy.lib.npyio.recfromcsv
像numpy.lib.npyio.recfromtxt,但使用默认delimiter=",".