考虑到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。我将发布当前代码作为可能的答案。寻找使用本地磁盘在我的服务器(大量内核,大量内存)上运行它的最快时间。
答案 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)
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分钟。不错,比熊猫快几个数量级。