最快的I / O在Python中组合异构CSV文件的有效方法

时间:2015-03-30 00:17:45

标签: python csv optimization io

考虑到10个1MB的csv文件,每个文件的布局略有不同,我需要将它们组合成具有相同标题的规范化单个文件。空字符串适用于空值。

列的示例:

1. FIELD1, FIELD2, FIELD3
2. FIELD2, FIELD1, FIELD3
3. FIELD1, FIELD3, FIELD4
4. FIELD3, FIELD4, FIELD5, FIELD6
5. FIELD2

输出看起来像(虽然顺序不重要,我的代码将它们按顺序发现):

FIELD1, FIELD2, FIELD3, FIELD4, FIELD5, FIELD6

所以基本上字段可以按任何顺序排列,字段可能丢失,或者之前看不到的新字段。所有必须包含在输出文件中。不需要连接,最后部件中的数据行数必须等于输出中的行数。

将所有10MB读入内存即可。以某种方式使用100MB来做它不会。如果需要,您可以一次打开所有文件。大量的文件,可用的内存,但它将针对NAS运行,因此它需要高效(不是太多的NAS操作)。

我现在的方法是将每个文件读入列列表,在发现新列时构建新列列表,然后将其全部写入单个文件。我希望有人有一些更聪明的东西,因为我是这个过程的瓶颈,所以任何缓解都是有帮助的。

如果有人想尝试,我有样本文件here。我将发布当前代码作为可能的答案。寻找使用本地磁盘在我的服务器(大量内核,大量内存)上运行它的最快时间。

5 个答案:

答案 0 :(得分:6)

使用csv.DictReader()csv.DictWriter() objects的两遍方法。 Pass one收集所有文件中使用的标题集,然后根据标题传递两个副本,然后复制数据。

收集标题就像访问阅读器对象上的fieldnames属性一样简单就足够了:

import csv
import glob

files = []
readers = []
fields = set()

try:
    for filename in glob.glob('in*.csv'):
        try:
            fileobj = open(filename, 'rb')
        except IOError:
            print "Failed to open {}".format(filename)
            continue
        files.append(fileobj)  # for later closing

        reader = csv.DictReader(fileobj)
        fields.update(reader.fieldnames)  # reads the first row
        readers.append(reader)

    with open('result.csv', 'wb') as outf:
        writer = csv.DictWriter(outf, fieldnames=sorted(fields))
        writer.writeheader()
        for reader in readers:
            # copy across rows; missing fields will be left blank
            for row in reader:
                writer.writerow(row)
finally:
    # close out open file objects
    for fileobj in files:
        fileobj.close()

每个阅读器都会生成一个包含所有字段子集的字典,但是DictWriter将使用restval参数的值(默认为''时省略,就像我在这里做的那样)来填充在每个丢失密钥的值中。

我在这里假设Python 2;如果这是Python 3,您可以使用ExitStack()来管理读者的打开文件;从文件模式中省略b,并为所有打开的调用添加newline=''参数,以便将新行处理留给CSV模块。

上面的代码只使用缓冲区来读写行;行一次大部分从一个打开的阅读器一次一行地移动到写入者。

很遗憾,我们无法使用writer.writerows(reader)DictWriter.writerows() implementation首先将reader中的所有内容转换为列表列表,然后再将其传递给基础csv.writer.writerows()方法,请参阅{ {3}}在Python bug跟踪器中。

答案 1 :(得分:3)

使用pandas库和concat函数

import pandas
import glob
df = pandas.concat([pandas.read_csv(x) for x in glob.glob("in*.csv")])
df.to_csv("output.csv")

答案 2 :(得分:2)

这是使用标准库模块的简单解决方案。这是Python 3.对Python 2使用备用注释的with行:

import csv
import glob

rows = []
fields = set()

for filename in glob.glob('in*.csv'):
    #with open(filename,'rb') as f:
    with open(filename,newline='') as f:
        r = csv.DictReader(f)
        rows.extend(row for row in r)
        fields.update(r.fieldnames)

#with open('result.csv','wb') as f:
with open('result.csv','w',newline='') as f:
    w = csv.DictWriter(f,fieldnames=fields)
    w.writeheader()
    w.writerows(rows)

修改

根据评论,添加文件名和行号:

import csv
import glob

rows = []
fields = set(['filename','lineno'])

for filename in glob.glob('in*.csv'):
    with open(filename,newline='') as f:
        r = csv.DictReader(f)
        for lineno,row in enumerate(r,1):
            row.update({'filename':filename,'lineno':lineno})
            rows.append(row)
        fields.update(r.fieldnames)

with open('result.csv','w',newline='') as f:
    w = csv.DictWriter(f,fieldnames=fields)
    w.writeheader()
    w.writerows(rows)

原版在我的系统上耗时8.8秒。此更新耗时10.6秒。

另请注意,如果您在传递到fields之前订购DictWriter,则可以按所需顺序排列。

答案 3 :(得分:1)

它不是超短或任何东西,但基本上我正在将它们读入列存储然后将它们全部写出来。我希望能有更快的速度,相同的速度,相同的i / o和更少的内存也是好的......但更快是最重要的。

import csv
from os.path import join
from collections import OrderedDict


# Accumulators
#columnstore = OrderedDict of tuples ( Data List, Starting rowcount)
columnstore = OrderedDict()
total_rowcount = 0

def flush_to_merged_csv(merged_filename,delimiter):

    with open(merged_filename,'w') as f:
        writer = csv.writer(f, delimiter=bytes(delimiter) )

        # Write the header first for all columns
        writer.writerow(columnstore.keys())

        # Write each row
        for rowidx in range(0,total_rowcount):

            # Assemble row from columnstore
            row = []
            for col in columnstore.keys():
                if columnstore[col][1] <= rowidx:
                    row.append(columnstore[col][0][rowidx - columnstore[col][1]])
                else:
                    row.append('')

            writer.writerow(row)


def combine(location, files, mergefile, delimiter):
    global total_rowcount

    for filename in files:

        with open(join(location,filename),'rb') as f:
            file_rowcount = 0
            reader = csv.reader( f, delimiter=bytes(delimiter) )

            # Get the column names.
            # Normalize the names (all upper, strip)
            columns = [ x.strip().upper() for x in reader.next() ]


            # Columnstore maintenance. Add new columns to columnstore
            for col in columns:
                if not columnstore.has_key(col):
                    columnstore[col] = ( [], total_rowcount )


            # Loop throught the remaining file, adding each cell to the proper columnstore
            for row in reader:
                field_count = len(row)
                total_rowcount += 1

                # Add the columns that exist to the columnstore.
                for columnidx in range(0,len(columns)):
                    # Handle missing trailing fields as empty
                    if columnidx >= field_count:
                        columnstore[columns[columnidx]][0].append('')
                    else:
                        columnstore[columns[columnidx]][0].append(row[columnidx])

                # Add emptry strings to any columnstores that don't exist in this file to keep them all in sync
                for colname in set(columnstore.keys()) - set(columns):
                    columnstore[colname][0].append('')

    flush_to_merged_csv(join(location,mergefile),delimiter)

combine( './', ['in1.csv','in2.csv','in3.csv','in4.csv','in5.csv','in6.csv','in7.csv','in8.csv','in9.csv','in10.csv'],'output.csv',',')

答案 4 :(得分:1)

@ MartijnPieter的回答非常有帮助,但是由于在阅读内容后阅读标题后保持文件打开,它在~255个文件崩溃(我找到了)。我需要组合~32,000个文件,所以重写了他的代码,以免崩溃。我还选择将其拆分为两个函数,以便我可以分析列标题。

def collectColumnNamesInCSVs():
    fields = set()

    for filename in glob.glob('//path//to//files/*.csv'):
        try:
            fileobj = open(filename, 'rb')
        except IOError:
            print "Failed to open {}".format(filename)
            continue

        reader = csv.DictReader(fileobj)
        fields.update(reader.fieldnames)  # reads the first row
        fileobj.close()

    return fields


def combineCSVs(fields):
    with open('result.csv', 'wb') as outf:
        writer = csv.DictWriter(outf, fieldnames=sorted(fields))
        writer.writeheader()

        for filename in glob.glob('//path//to//files/*.csv'):
            try:
                fileobj = open(filename, 'rb')
            except IOError:
                print "Failed to open {}".format(filename)
                continue

            reader = csv.DictReader(fileobj)

            for row in reader:
                writer.writerow(row)

            fileobj.close()

    outf.close()

当打开非常杂乱的各种各样的CSV(<1k - 700k;每个20-60个混合列;总共约130个标题)时,第二个阶段在1.4GHz MacBook Air上每1000个文件花费约1分钟。不错,比熊猫快几个数量级。