如何加快读取多个文件并将数据放入数据帧?

时间:2017-02-10 11:10:20

标签: python regex performance parsing pandas

我有许多文本文件,比如50,我需要阅读大量的数据帧。目前,我正在使用以下步骤。

  1. 阅读每个文件并检查标签是什么。我需要的信息通常包含在前几行中。对于文件的其余部分,只重复相同的标签,每次都会列出不同类型的数据。
  2. 使用这些标签创建数据框。
  3. 再次读取文件并使用值填充数据框。
  4. 将该数据帧与主数据帧连接。
  5. 这适用于100 KB大小的文件 - 几分钟,但在50 MB时,它只需要几个小时,并且不实用。

    如何优化代码?特别是 -

    1. 如何确定哪些功能花费的时间最多,哪些需要优化?是文件的读物吗?是写入数据帧吗?我的计划在哪里花费时间?
    2. 我应该考虑多线程还是多处理?
    3. 我可以改进算法吗?
      • 或许可以一次性读取整个文件,而不是逐行,
      • 按块/整个文件解析数据,而不是逐行解析,
      • 以数据/一次性的方式将数据分配给数据框,而不是逐行分配。
    4. 我还能做些什么来让我的代码执行得更快吗?
    5. 这是一个示例代码。我自己的代码有点复杂,因为文本文件更复杂,因此我必须使用大约10个正则表达式和多个while循环来读取数据并将其分配到正确数组中的正确位置。为了保持MWE简单,我还没有在MWE的输入文件中使用重复标签,所以我希望我无缘无故地读取文件两次。我希望这是有道理的!

      import re
      import pandas as pd
      
      df = pd.DataFrame()
      paths = ["../gitignore/test1.txt", "../gitignore/test2.txt"]
      reg_ex = re.compile('^(.+) (.+)\n')
      # read all files to determine what indices are available
      for path in paths:
          file_obj = open(path, 'r')
          print file_obj.readlines()
      
      ['a 1\n', 'b 2\n', 'end']
      ['c 3\n', 'd 4\n', 'end']
      
      indices = []
      for path in paths:
          index = []
          with open(path, 'r') as file_obj:
              line = True
              while line:
                  try:
                      line = file_obj.readline()
                      match = reg_ex.match(line)
                      index += match.group(1)
                  except AttributeError:
                      pass
          indices.append(index)
      # read files again and put data into a master dataframe
      for path, index in zip(paths, indices):
          subset_df = pd.DataFrame(index=index, columns=["Number"])
          with open(path, 'r') as file_obj:
              line = True
              while line:
                  try:
                      line = file_obj.readline()
                      match = reg_ex.match(line)
                      subset_df.loc[[match.group(1)]] = match.group(2)
                  except AttributeError:
                      pass
          df = pd.concat([df, subset_df]).sort_index()
      print df
      
        Number
      a      1
      b      2
      c      3
      d      4
      

      我的输入文件:

      test1.txt的

      a 1
      b 2
      end
      

      的test2.txt

      c 3
      d 4
      end
      

10 个答案:

答案 0 :(得分:16)

在拔出多处理锤之前,您的第一步应该是进行一些分析。使用cProfile快速查看以确定哪些功能需要很长时间。不幸的是,如果你的行都在一个函数调用中,它们将显示为库调用。 line_profiler更好,但需要更多的设置时间。

请注意。如果使用ipython,您可以使用%timeit(timeit模块的magic命令)和%prun(profile模块的magic命令)来为语句和函数计时。谷歌搜索会显示一些指南。

熊猫是一个很棒的图书馆,但是我偶尔也会因为结果糟糕而使用它。特别要注意append()/ concat()操作。这可能是你的瓶颈,但你应该确定一下。通常,如果您不需要执行索引/列对齐,则numpy.vstack()和numpy.hstack()操作会更快。在你的情况下,看起来你可以使用系列或1-D numpy ndarray,这可以节省时间。

顺便说一句,python中的try块通常比检查无效条件慢10倍或更多,因此在将每个行绑定到循环中时确保你绝对需要它。这可能是时间的另一个障碍;我想你在try.group(1)失败的情况下卡住了try块来检查AttributeError。我会先检查一下有效的比赛。

即使是这些小修改也应该足以让你的程序在尝试像多处理这样繁琐的事情之前运行得更快。那些Python库非常棒,但却带来了一系列新的挑战。

答案 1 :(得分:12)

我已多次使用它,因为它是多处理的一个特别简单的实现。

import pandas as pd
from multiprocessing import Pool

def reader(filename):
    return pd.read_excel(filename)

def main():
    pool = Pool(4) # number of cores you want to use
    file_list = [file1.xlsx, file2.xlsx, file3.xlsx, ...]
    df_list = pool.map(reader, file_list) #creates a list of the loaded df's
    df = pd.concat(df_list) # concatenates all the df's into a single df

if __name__ == '__main__':
    main()

使用此功能,您应该能够大幅提高程序的速度,而无需过多的工作。如果您不知道自己拥有多少处理器,可以通过拉起shell并输入

进行检查
echo %NUMBER_OF_PROCESSORS%

编辑:为了让这次运行更快,请考虑将文件更改为csvs并使用pandas函数pandas.read_csv

答案 2 :(得分:3)

首先,如果您多次阅读该文件,似乎这将成为瓶颈。尝试将文件读入1个字符串对象,然后多次使用cStringIO

其次,在读取所有文件之前,您没有真正显示构建索引的任何理由。即使你这样做,为什么你使用Pandas进行IO?看起来您可以在常规python数据结构(可能使用__slots__)中构建它,然后将其放在主数据帧中。如果在读取文件Y之前不需要文件X索引(正如第二个循环似乎建议的那样),您只需要遍历文件一次。

第三,您可以在字符串上使用简单的split / strip来提取空格分隔的标记,或者如果它更复杂(有字符串引号等),请使用{{1}来自Python标准库的模块。在您展示如何实际构建数据之前,很难提出与此相关的修复方法。

到目前为止您所展示的内容可以通过简单的

快速完成
CSV

这是我在没有预先分配磁盘空间的虚拟机上运行时的时间差异(生成的文件大小约为24MB):

for path in paths:
    data = []
    with open(path, 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = line.strip().split()
            except ValueError:
                pass
            data.append(d1, int(d2)))
    index, values = zip(*data)
    subset_df = pd.DataFrame({"Number": pd.Series(values, index=index)})

结果时间是:

import pandas as pd
from random import randint
from itertools import combinations
from posix import fsync


outfile = "indexValueInput"

for suffix in ('1', '2'):
    with open(outfile+"_" + suffix, 'w') as f:
        for i, label in enumerate(combinations([chr(i) for i in range(ord('a'), ord('z')+1)], 8)) :
            val = randint(1, 1000000)
            print >>f, "%s %d" % (''.join(label), val)
            if i > 3999999:
                break
        print >>f, "end"
        fsync(f.fileno())

def readWithPandas():
    data = []
    with open(outfile + "_2", 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = str.split(line.strip())
            except ValueError:
                pass
            data.append((d1, int(d2)))
    index, values = zip(*data)
    subset_df = pd.DataFrame({"Numbers": pd.Series(values, index=index)})

def readWithoutPandas():
    data = []
    with open(outfile+"_1", 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = str.split(line.strip())
            except ValueError:
                pass
            data.append((d1, int(d2)))
    index, values = zip(*data)

def time_func(func, *args):
    import time
    print "timing function", str(func.func_name)
    tStart = time.clock()
    func(*args)
    tEnd = time.clock()
    print "%f seconds " % (tEnd - tStart)

time_func(readWithoutPandas)
time_func(readWithPandas)

您可以使用索引构建尝试这些函数,并查看时间差异。几乎可以肯定,减速来自多个磁盘读取。由于Pandas会花时间从字典中构建您的数据帧,因此在将数据传递给Pandas之前,最好先弄清楚如何在纯Python中构建索引。但要读取数据和在1个磁盘读取中建立索引。

我想另一个警告是,如果从代码内部打印,则需要花费大量时间。将纯文本写入tty所花费的时间使读取/写入磁盘所需的时间相形见绌。

答案 3 :(得分:2)

一般python注意事项:

首先,关于时间测量,您可以使用这样的片段:

from time import time, sleep


class Timer(object):
    def __init__(self):
        self.last = time()


    def __call__(self):
        old = self.last
        self.last = time()
        return self.last - old

    @property
    def elapsed(self):
        return time() - self.last



timer = Timer()

sleep(2)
print timer.elapsed
print timer()
sleep(1)
print timer()

然后你可以多次对运行代码进行基准测试,并检查差异。

关于此,我在线评论:

with open(path, 'r') as file_obj:
    line = True
    while line: #iterate on realdines instead.
        try:
            line = file_obj.readline()
            match = reg_ex.match(line)
            index += match.group(1)
            #if match:
            #    index.extend(match.group(1)) # or extend

        except AttributeError:
            pass

你以前的代码不是真的pythonic,你可能想尝试/除。 然后只尝试在最小可能的行上进行操作。

同样的通知适用于第二段代码。

如果您需要多次读取相同的文件。你可以使用StringIO将它们存储在RAM中,或者更容易保留一个只读过一次的{path:content}字典。

已知Python正则表达式很慢,您的数据看起来非常简单,您可以考虑在输入行上使用split和strip方法。

 striped=[l.split() for l in [c.strip() for c in file_desc.readlines()] if l] 

我建议您阅读此内容:https://gist.github.com/JeffPaine/6213790对应视频在此https://www.youtube.com/watch?v=OSGv2VnC0go

答案 4 :(得分:1)

您可以导入多处理模型并使用工作进程池同时打开多个文件作为文件对象,从而加快代码的加载部分。要测试时间,请导入datetime函数并使用以下代码:

import datetime
start=datetime.datetime.now()

#part of your code goes here

execTime1=datetime.datetime.now()
print(execTime1-start)

#the next part of your code goes here

execTime2=datetime.datetime.now()
print(execTime2-execTime1)

至于只读取一次文件,考虑使用另一个多处理脚本在每个文件中构建一个行列表,这样就可以在没有文件I / O操作的情况下检查匹配。

答案 5 :(得分:1)

首先,为您的脚本使用分析器(see this question)。准确分析哪个部分消耗更多时间。看看您是否可以优化它。

其次,我觉得I / O操作文件读取很可能是瓶颈。它可以使用并发方法进行优化。我建议同时读取文件并创建数据框。每个线程都可以将新创建​​的数据帧推送到队列。主线程监视队列可以从队列中获取数据帧并将其与主数据帧合并。

希望这会有所帮助。

答案 6 :(得分:1)

1为文件创建一个输出模板(如结果数据框应该有列A,B C)

2读取每个文件,将其转换为输出模板(在步骤1中建立)并保存文件,如temp_idxx.csv,这可以并行完成:)

3将这些temp_idxx.csv文件连接成一个大型文件并删除临时文件

这个程序的优点是它可以并行运行,并且它不会占用所有内存 缺点是创建输出格式并坚持使用它和磁盘空间使用

答案 7 :(得分:1)

使用pd.read_csv将文件直接读入pandas数据框。创建subset_df。使用skipfooter等方法跳过您知道不需要的文件末尾的行。还有许多方法可以替换您正在使用的一些正则表达式循环函数,例如error_bad_lines和skip_blank_lines。

然后使用pandas提供的工具清理不需要的数据。

这将允许您阅读打开并只读取一次文件。

答案 8 :(得分:1)

您的代码不会按照您的描述进行操作。

  

问题:1。阅读每个文件并检查标签是什么。我需要的信息通常包含在前几行中。

但是你读了整个文件,不仅仅是几行。 这导致读取文件两次

  

问题:2。再次读取文件并使用值填充数据框。

你一次又一次地覆盖循环中的df['a'|'b'|'c'|'d'],这是无用的 我相信这不是你想要的。
这适用于问题中给出的数据,但如果您必须处理n值则不适用。

具有不同逻辑的提案:

data = {}
for path in paths:
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                if match.group(1) not in data:
                    data[ match.group(1) ] = []

                data[match.group(1)].append( match.group(2) )
            except AttributeError:
                pass

print('data=%s' % data)
df = pd.DataFrame.from_dict(data, orient='index').sort_index()
df.rename(index=str, columns={0: "Number"}, inplace=True)  

输出

data={'b': ['2'], 'a': ['1'], 'd': ['4'], 'c': ['3']}
<class 'pandas.core.frame.DataFrame'>
Index: 4 entries, a to d
Data columns (total 1 columns):
Number    4 non-null object
dtypes: object(1)
memory usage: 32.0+ bytes
  Number
a      1
b      2
c      3
d      4  

时间表

             Code from Q:   to_dict_from_dict
    4 values 0:00:00.033071 0:00:00.022146
 1000 values 0:00:08.267750 0:00:05.536500
10000 values 0:01:22.677500 0:00:55.365000

使用Python测试:3.4.2 - pandas:0.19.2 - re:2.2.1

答案 9 :(得分:1)

事实证明,首先创建一个空白的DataFrame,搜索索引以找到一行数据的正确位置,然后只更新DataFrame的那一行是一个非常耗时的过程。

更快的方法是将输入文件的内容读入原始数据结构,例如列表列表或dicts列表,然后将其转换为DataFrame。

当您正在阅读的所有数据都在同一列中时使用列表。否则,使用dicts明确说明每个数据位应该到哪一列。

1月18日更新:这与How to parse complex text files using Python?相关联我还写了blog article explaining how to parse complex files to beginners