9

我在 CSV 文件中写入了大量数字,并且只需要加载该数组的一部分。从概念上讲,我想调用np.genfromtxt()然后对结果数组进行行切片,但是

  1. 文件太大,可能无法放入 RAM
  2. 相关行数可能很少,因此无需解析每一行。

MATLAB 具有textscan()可以获取文件描述符并仅读取文件块的函数。NumPy 中有类似的东西吗?

现在,我定义了以下函数,它只读取满足给定条件的行:

def genfromtxt_cond(fname, cond=(lambda str: True)):
  res = []
  with open(fname) as file:
    for line in file:
      if cond(line):
        res.append([float(s) for s in line.split()])

  return np.array(res, dtype=np.float64)

这个解决方案有几个问题:

  • 不通用:仅支持浮点类型,同时genfromtxt检测类型,可能因列而异;还缺少值、转换器、跳过等;
  • 效率不高:当条件困难时,每行可能会被解析两次,使用的数据结构和读取缓冲也可能不是最优的;
  • 需要写代码。

是否有实现过滤的标准函数,或 MATLAB 的一些对应物textscan

4

3 回答 3

16

我可以想到两种方法来提供您要求的一些功能:

  1. 要以块 / 或 n 行 / 等的方式读取文件:
    您可以将 a 传递generatornumpy.genfromtxt以及numpy.loadtxt。通过这种方式,您可以高效地从文本文件中加载大型数据集,同时保留这两个函数的所有便捷解析功能。

  2. 要仅从与可以表示为正则表达式的标准匹配的行中读取数据:
    您可以使用numpy.fromregex并使用 aregular expression精确定义应加载输入文件中给定行中的哪些标记。与模式不匹配的行将被忽略。

为了说明这两种方法,我将使用我的研究背景中的一个例子。
我经常需要加载具有以下结构的文件:

6
 generated by VMD
  CM         5.420501        3.880814        6.988216
  HM1        5.645992        2.839786        7.044024
  HM2        5.707437        4.336298        7.926170
  HM3        4.279596        4.059821        7.029471
  OD1        3.587806        6.069084        8.018103
  OD2        4.504519        4.977242        9.709150
6
 generated by VMD
  CM         5.421396        3.878586        6.989128
  HM1        5.639769        2.841884        7.045364
  HM2        5.707584        4.343513        7.928119
  HM3        4.277448        4.057222        7.022429
  OD1        3.588119        6.069086        8.017814

这些文件可能很大(GB),我只对数字数据感兴趣。所有数据块都具有相同的大小——6在这个例子中——它们总是被两行分隔。所以stride块的 是8

使用第一种方法:

首先,我将定义一个过滤掉不需要的行的生成器:

def filter_lines(f, stride):
    for i, line in enumerate(f):
        if i%stride and (i-1)%stride:
            yield line

然后我打开文件,创建一个filter_lines-generator(这里我需要知道stride),然后将该生成器传递给genfromtxt

with open(fname) as f:
    data = np.genfromtxt(filter_lines(f, 8),
                         dtype='f',
                         usecols=(1, 2, 3))

这就像轻而易举。请注意,我可以用来usecols删除数据的第一列。同样,您可以使用所有其他功能genfromtxt——检测类型、从列到列的不同类型、缺失值、转换器等。

在此示例data.shape(204000, 3),原始文件由272000行组成。

在这里,generator它用于过滤均匀跨度的线条,但同样可以想象它根据(简单)标准过滤掉不均匀的线条块。

使用第二种方法:

这是regexp我要使用的:

regexp = r'\s+\w+' + r'\s+([-.0-9]+)' * 3 + r'\s*\n'

组——即内部()——定义要从给定行中提取的标记。接下来,fromregex完成这项工作并忽略与模式不匹配的行:

data = np.fromregex(fname, regexp, dtype='f')

结果与第一种方法完全相同。

于 2013-02-09T20:29:38.577 回答
1

如果您传递类型列表(格式条件),使用 try 块并使用 yield 来使用 genfromtxt 作为生成器,我们应该能够复制textscan().

def genfromtext(fname, formatTypes):
    with open(fname, 'r') as file:
        for line in file:
            try:
                line = line.split(',')  # Do you care about line anymore?
                r = []
                for type, cell in zip(formatTypes, line):
                    r.append(type(cell))
            except:
                pass  # Fail silently on this line since we hit an error
            yield r

编辑:我忘记了 except 块。它现在运行正常,您可以像这样使用 genfromtext 作为生成器(使用我坐在身边的随机 CSV 日志):

>>> a = genfromtext('log.txt', [str, str, str, int])
>>> a.next()
['10.10.9.45', ' 2013/01/17 16:29:26', '00:00:36', 0]
>>> a.next()
['10.10.9.45', ' 2013/01/17 16:22:20', '00:08:14', 0]
>>> a.next()
['10.10.9.45', ' 2013/01/17 16:31:05', '00:00:11', 3]

我可能应该注意到,我正在使用zip将逗号分割线和 formatSpec 压缩在一起,它将对两个列表进行元组化(当其中一个列表用完项目时停止),因此我们可以一起迭代它们,避免依赖于循环len(line)或类似的东西。

于 2013-02-01T12:49:21.673 回答
0

试图向OP展示评论。

def fread(name, cond):
    with open(name) as file:
        for line in file:
            if cond(line):
                yield line.split()

def a_genfromtxt_cond(fname, cond=(lambda str: True)):
    """Seems to work without need to convert to float."""
    return np.array(list(fread(fname, cond)), dtype=np.float64)

def b_genfromtxt_cond(fname, cond=(lambda str: True)):
    r = [[int(float(i)) for i in l] for l in fread(fname, cond)]
    return np.array(r, dtype=np.integer)


a = a_genfromtxt_cond("tar.data")
print a
aa = b_genfromtxt_cond("tar.data")
print aa

输出

[[ 1.   2.3  4.5]
 [ 4.7  9.2  6.7]
 [ 4.7  1.8  4.3]]
[[1 2 4]
 [4 9 6]
 [4 1 4]]
于 2013-02-01T14:52:29.623 回答