NumPy读取文件,带有过滤线

时间:2013-02-01 11:56:27

标签: python input numpy large-files bigdata

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

  1. 文件太大,可能无法容纳在RAM中
  2. 相关行的数量可能很小,因此无需解析每一行。
  3. 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)
    

    此解决方案存在一些问题:

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

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

3 个答案:

答案 0 :(得分:16)

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

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

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

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

    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 - 它们始终由两行分隔。所以块的stride8

    使用第一种方法:

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

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

    然后我打开文件,创建一个filter_lines - 生成器(这里我需要知道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')
    

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

答案 1 :(得分: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

编辑:我忘记了除了块。它现在运行正常,您可以使用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)或类似的循环。

答案 2 :(得分: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]]