2013-01-11 107 views
1

我经常处理包含少量列(通常少于10个)和多达数千万行的ascii表。他们看起来像Python熊猫:读文件跳过评论

176.792 -2.30523 0.430772 32016 1 1 2 
177.042 -1.87729 0.430562 32016 1 1 1 
177.047 -1.54957 0.431853 31136 1 1 1 
... 
177.403 -0.657246 0.432905 31152 1 1 1 

我有大量的Python代码,读取,操纵和保存文件。我一直使用numpy.loadtxtnumpy.savetxt来做到这一点。但是numpy.loadtxt需要至少5-6Gb的RAM来读取1Gb ASCII文件。

昨天我发现了熊猫,它解决了我几乎所有的问题:pandas.read_tablenumpy.savetxt一起提高了我的脚本的执行速度(2)3或4,同时非常有效地提高内存。

一切都很好,直到我尝试读入包含一些注释行开头的文件时。文档字符串(v = 0.10.1.dev_f73128e)告诉我,行注释不受支持,并且可能会发生。我认为这会很好:我非常喜欢排除numpy.loadtxt中的评论。 有没有关于如何可用的想法?也很高兴有可能跳过这些行(该文件指出,他们将作为empy返回)

不知道我的文件中有多少注释行(我处理数以千计的注释行来自不同的人) ,因为现在我打开该文件,计算行开始在文件开头评论数:

def n_comments(fn, comment): 
    with open(fname, 'r') as f: 
     n_lines = 0 
     pattern = re.compile("^\s*{0}".format(comment)) 
     for l in f: 
      if pattern.search(l) is None: 
       break 
      else: 
       n_lines += 1 
    return n_lines 

然后

pandas.read_table(fname, skiprows=n_comments(fname, '#'), header=None, sep='\s') 

有没有什么更好的方法(也许内大熊猫)去做吧?

最后,在发布之前,我看了一下pandas.io.parsers.py的代码,了解pandas.read_table如何工作,但我迷路了。任何人都可以指向我实现阅读文件的地方吗?

感谢

EDIT2:我想获得一些改善摆脱一些@ThorstenKranz第二实施FileWrapperif的,但没有得到几乎没有改善

class FileWrapper(file): 
    def __init__(self, comment_literal, *args): 
     super(FileWrapper, self).__init__(*args) 
     self._comment_literal = comment_literal 
     self._next = self._next_comment 

    def next(self): 
     return self._next() 

    def _next_comment(self): 
     while True: 
      line = super(FileWrapper, self).next() 
      if not line.strip()[0] == self._comment_literal: 
       self._next = self._next_no_comment 
       return line 
    def _next_no_comment(self): 
     return super(FileWrapper, self).next() 
+0

对于记录:编辑以包含从文件起始处开始对已注释行数进行计数的功能 –

回答

3

read_csvread_tablecomment选项,将跳过从注释字符,直到一行的末尾开始字节。如果需要跳过整行行,这不太正确,因为解析器会认为它看到的行中没有字段,最终看到有效的数据行并且感到困惑。

我建议使用您的解决方法来确定在文件中手动跳过的行数。这将是不错的,它使自动跳过线当整个行是注释的一个选项:

https://github.com/pydata/pandas/issues/2685

实现这口井将需要浸泡到C标记生成器代码。这听起来并不像听起来那么糟糕。

+0

如果可以,我会帮忙的 –

2

我找到了通过创建一个继承类的紧凑解决方案file

import pandas as pd 

class FileWrapper(file): 
    def __init__(self, comment_literal, *args): 
     super(FileWrapper, self).__init__(*args) 
     self._comment_literal = comment_literal 

    def next(self): 
     while True: 
      line = super(FileWrapper, self).next() 
      if not line.startswith(self._comment_literal): 
       return line 

df = pd.read_table(FileWrapper("#", "14276661.txt", "r"), delimiter=" ", header=None) 

Atm,pandas(0.8.1)仅使用.next()-方法遍历文件类对象。我们可以重载这个方法,并且只返回那些不以专用注释文字开头的行,在我的示例"#"中。

对于输入文件:

176.792 -2.30523 0.430772 32016 1 1 2 
# 177.042 -1.87729 0.430562 32016 1 1 1 
177.047 -1.54957 0.431853 31136 1 1 1 
177.403 -0.657246 0.432905 31152 1 1 1 

我们得到

>>> df 
     X.1  X.2  X.3 X.4 X.5 X.6 X.7 
0 176.792 -2.305230 0.430772 32016 1 1 2 
1 177.047 -1.549570 0.431853 31136 1 1 1 
2 177.403 -0.657246 0.432905 31152 1 1 1 

176.792 -2.30523 0.430772 32016 1 1 2 
177.042 -1.87729 0.430562 32016 1 1 1 
177.047 -1.54957 0.431853 31136 1 1 1 
177.403 -0.657246 0.432905 31152 1 1 1 

我们得到

>>> df 
     X.1  X.2  X.3 X.4 X.5 X.6 X.7 
0 176.792 -2.305230 0.430772 32016 1 1 2 
1 177.042 -1.877290 0.430562 32016 1 1 1 
2 177.047 -1.549570 0.431853 31136 1 1 1 
3 177.403 -0.657246 0.432905 31152 1 1 1 

而不是继承,你也可以使用代表团,这取决于你的味道。

编辑 我尝试了许多其他方法来提高性能。虽然这是一项艰巨的工作。我试图

  • 线程:在一个线程中低级别的IO操作和大块,将它分成行读取文件进取,排队这些从队列只得到next()
  • 一样多处理
  • 类似的,多线程的方法,但使用readlines(size_hint)
  • mmap从文件中读取

前面三种方法都是令人惊讶的速度较慢,所以没有好处。使用mmap显着改善了性能。下面是代码:

class FileWrapper(file): 
    def __init__(self, comment_literal, *args): 
     super(FileWrapper, self).__init__(*args) 
     self._comment_literal = comment_literal 
     self._in_comment = True 
     self._prepare() 

    def __iter__(self): 
     return self 

    def next(self): 
     if self._in_comment: 
      while True: 
       line = self._get_next_line() 
       if line == "": 
        raise StopIteration() 
       if not line[0] == self._comment_literal: 
        self._in_comment = False 
        return line 
     line = self._get_next_line() 
     if line == "": 
      raise StopIteration() 
     return line 

    def _get_next_line(self): 
     return super(FileWrapper, self).next() 

    def _prepare(self): 
     pass 

class MmapWrapper(file): 
    def __init__(self, fd, comment_literal = "#"): 
     self._mm = mmap.mmap(fd, 0, prot=mmap.PROT_READ) 
     self._comment_literal = comment_literal 
     self._in_comment = True 

    def __iter__(self): 
     return self #iter(self._mm.readline, "")#self 

    def next(self): 
     if self._in_comment: 
      while True: 
       line = self._mm.readline() 
       if line == "": 
        raise StopIteration() 
       if not line[0] == self._comment_literal: 
        self._in_comment = False 
        return line 
     line = self._mm.readline() 
     if line == "": 
      raise StopIteration() 
     return line 

if __name__ == "__main__": 
    t0 = time.time()  
    for i in range(10):  
     with open("1gram-d_1.txt", "r+b") as f: 
      df1 = pd.read_table(MmapWrapper(f.fileno()), delimiter="\t", header=None) 
    print "mmap:", time.time()-t0 

    t0 = time.time()  
    for i in range(10):  
     df2 = pd.read_table(FileWrapper("#", "1gram-d_1.txt", "r"), delimiter="\t", header=None) 
    print "Unbuffered:", time.time()-t0 

    print (df1==df2).mean() 

给作为输出

mmap: 35.3251504898 
Unbuffered: 41.3274121284 
X.1 1 
X.2 1 
X.3 1 
X.4 1 

我还实施意见,检查,直到第一个非注释行只找到。这符合您的解决方案并进一步提高了性能。

对于mmap s,虽然存在一些限制。如果文件大小很大,请确保有足够的RAM。如果您正在使用32位操作系统,则无法读取大于2GB的文件。

+0

这不是读取OP所描述大小文件的高性能解决方案。 –

+1

另外,升级时间,man =) –

+0

感谢您的解决方案。 阅读100次文件426227行(6评论),因为这里需要2m36.088s(实时)。与我的解决方法1m55.929s一样。 –