将多个CSV文件的列合并为一个文件
我有一堆CSV文件(下面的例子只有两个)。每个CSV文件有6列。我想打开每个CSV文件,复制前两列,然后把它们作为新列添加到一个已有的CSV文件中。
到目前为止,我做了:
import csv
f = open('combined.csv')
data = [item for item in csv.reader(f)]
f.close()
for x in range(1,3): #example has 2 csv files, this will be automated
n=0
while n<2:
f=open(str(x)+".csv")
new_column=[item[n] for item in csv.reader(f)]
f.close()
#print d
new_data = []
for i, item in enumerate(data):
try:
item.append(new_column[i])
print i
except IndexError, e:
item.append("")
new_data.append(item)
f = open('combined.csv', 'w')
csv.writer(f).writerows(new_data)
f.close()
n=n+1
这个方法能用,虽然看起来不太好,但确实能工作。不过,我有三个小烦恼:
我每个CSV文件要打开两次(每列一次),这实在不太优雅。
当我打印
combined.csv
文件时,每行后面会多出一个空行?我必须提供一个
combined.csv
文件,它的行数至少要和我可能有的最大文件一样多。因为我并不知道这个数字到底是多少,这让我很烦。
像往常一样,任何帮助都非常感谢!!
应要求,1.csv的内容是(模拟数据)
1,a
2,b
3,c
4,d
2.csv的内容是
5,e
6,f
7,g
8,h
9,i
合并后的combined.csv文件应该是这样的
1,a,5,e
2,b,6,f
3,c,7,g
4,d,8,h
,,9,i
4 个回答
这是我写的一个程序,用来解决你的问题。它创建了一个类,这个类保存了每个要读取的CSV文件的信息,包括你想要的哪些列。然后,它简单地列出了要读取的CSV文件,并从每个文件中读取一行。
因为你提到需要持续返回行,直到所有输入文件都读取完,所以对于已经到达末尾的输入文件,它会返回一些占位符值。它会一直读取行,直到所有的输入文件都处理完。
另外,这个程序一次只需要在内存中保存一行数据。所以即使是很大的CSV文件,它也能处理,而不需要太多内存。
最开始我用-1作为缺失数据的占位符。现在我看到你添加了一个例子,你只是想要没有值。我已经把程序从使用-1改成了在没有数据时使用空字符串。
设计这个程序时的一个目标是让它可以扩展。目前你需要前两列,但如果将来你需要从某个文件中提取第0、3和7列呢?所以每个文件都有一个列的列表,指定要提取哪些列。
我实际上没有写代码来将输出文件重命名为原始文件名,但这很容易添加。
理想情况下,这整个程序应该封装成一个类,你可以遍历这个类的实例,得到一行数据,这行数据是由所有输入文件的列组合而成的。我没有花额外的时间去做这件事,但如果你打算长期使用这个程序,可能想要这样做。另外,我也没有关闭任何输入文件,因为我认为程序在写完输出文件后就会结束,所有文件会自动关闭;但理想情况下,我们应该在使用完文件后关闭它们!
import csv
fname_in = "combined.csv"
fname_out = "combined.tmp"
lst_other_fnames = [str(x) + ".csv" for x in range(1, 3)]
no_data = ''
def _no_data_list(columns):
return [no_data for _ in columns]
class DataCsvFile(object):
def __init__(self, fname, columns=None):
self.fname = fname
self.f = open(fname)
self.reader = csv.reader(self.f)
self.columns = columns
self.done = False
def next_columns(self):
if self.done:
return _no_data_list(self.columns)
try:
item = next(self.reader)
except StopIteration:
self.done = True
return _no_data_list(self.columns)
return [item[i] for i in self.columns]
# want all columns from original file
data_csv_files = [DataCsvFile(fname_in, range(5))]
# build list of filenames and columns: want first two columns from each
data_csv_files.extend(DataCsvFile(fname, range(2)) for fname in lst_other_fnames)
with open(fname_out, "w") as out_f:
writer = csv.writer(out_f)
while True:
values = []
for df in data_csv_files:
columns = df.next_columns()
values.extend(columns)
if not all(df.done for df in data_csv_files):
writer.writerow(values)
else:
break
现在,似乎每当有人在Python中处理数据时,几乎都得给出一个基于pandas的解决方案。所以这是我的解决方案:
import pandas as pd
to_merge = ['{}.csv'.format(i) for i in range(4)]
dfs = []
for filename in to_merge:
# read the csv, making sure the first two columns are str
df = pd.read_csv(filename, header=None, converters={0: str, 1: str})
# throw away all but the first two columns
df = df.ix[:,:1]
# change the column names so they won't collide during concatenation
df.columns = [filename + str(cname) for cname in df.columns]
dfs.append(df)
# concatenate them horizontally
merged = pd.concat(dfs,axis=1)
# write it out
merged.to_csv("merged.csv", header=None, index=None)
对于这些文件
~/coding/pand/merge$ cat 0.csv
0,a,6,5,3,7
~/coding/pand/merge$ cat 1.csv
1,b,7,6,7,0
2,c,0,1,8,7
3,d,6,8,4,5
4,e,8,4,2,4
~/coding/pand/merge$ cat 2.csv
5,f,6,2,9,1
6,g,0,3,2,7
7,h,6,5,1,9
~/coding/pand/merge$ cat 3.csv
8,i,9,1,7,1
9,j,0,9,3,9
得到的结果是
In [21]: !cat merged.csv
0,a,1,b,5,f,8,i
,,2,c,6,g,9,j
,,3,d,7,h,,
,,4,e,,,,
In [22]: pd.read_csv("merged.csv", header=None)
Out[22]:
0 1 2 3 4 5 6 7
0 0 a 1 b 5 f 8 i
1 NaN NaN 2 c 6 g 9 j
2 NaN NaN 3 d 7 h NaN NaN
3 NaN NaN 4 e NaN NaN NaN NaN
我认为这是正确的对齐方式。
import csv
import itertools as IT
filenames = ['1.csv', '2.csv']
handles = [open(filename, 'rb') for filename in filenames]
readers = [csv.reader(f, delimiter=',') for f in handles]
with open('combined.csv', 'wb') as h:
writer = csv.writer(h, delimiter=',', lineterminator='\n', )
for rows in IT.izip_longest(*readers, fillvalue=['']*2):
combined_row = []
for row in rows:
row = row[:2] # select the columns you want
if len(row) == 2:
combined_row.extend(row)
else:
combined_row.extend(['']*2)#This extends two empty columns
writer.writerow(combined_row)
for f in handles:
f.close()
这一行代码 for rows in IT.izip_longest(*readers, fillvalue=['']*2):
可以通过一个例子来理解:
In [1]: import itertools as IT
In [2]: readers = [(1,2,3), ('a','b','c','d'), (10,20,30,40)]
In [3]: list(IT.izip_longest(readers[0], readers[1], readers[2]))
Out[3]: [(1, 'a', 10), (2, 'b', 20), (3, 'c', 30), (None, 'd', 40)]
你可以看到,IT.izip_longest 的工作方式和 zip
很像,但它不会在最短的列表用完时就停止,而是会一直运行到最长的列表用完为止。默认情况下,它会用 None
来填补缺失的项。
那么如果 readers
中有超过3个项目会发生什么呢?
我们可能想写
list(IT.izip_longest(readers[0], readers[1], readers[2], ...))
但这样写起来很麻烦,而且如果我们事先不知道 len(readers)
的值,我们甚至无法把省略号(...
)替换成具体的内容。
Python 有一个解决方案:星号(也叫参数解包)语法:
In [4]: list(IT.izip_longest(*readers))
Out[4]: [(1, 'a', 10), (2, 'b', 20), (3, 'c', 30), (None, 'd', 40)]
注意结果 Out[4]
和结果 Out[3]
是完全一样的。
*readers
告诉 Python 要把 readers
中的项目拆开,作为单独的参数传递给 IT.izip_longest
。这就是 Python 如何让我们向一个函数传递任意数量的参数的方式。