删除巨大的.csv文件中的重复项

时间:2014-01-14 06:17:49

标签: python duplicates duplicate-removal

我有一个这种格式的csv文件

testname unitname time data
test1 1 20131211220159 123123
test1 1 20131211220159 12345
test1 1 20131211230180 1234 

我正在尝试从此文件中删除所有旧数据,并仅保留具有最新时间戳的数据。(应删除前两个abovv,因为上一个时间戳大于前两个时间戳)。我想保留所有测试数据,除非以后重复相同的测试和相同的单元。输入文件按时间排序(因此旧数据会在下面显示)。

该文件大约是15 Mb。(output_Temp.csv)。我把它复制为output_temp2.​​csv

这就是我所拥有的。

file1=open("output_temp.csv","r")
file2=open("output_temp2.csv","r")
file3=open("output.csv","w")

flag=0
linecounter=0


for line in file1:
    testname=line[0]
    vid=line[1]
    tstamp=line[2]
    file2.seek(0) #reset
    for i in range(linecounter):
        file2.readline() #came down to the line #
    for line2 in file2:
        if testname==line2.split(",")[0] and vid==line2.split(",")[1] and tstamp!=line2.split(",")[2]:
            flag==1
            print line
        if flag==1:
            break

    if flag==0:
        file3.write(line)
    linecounter=linecounter+1 #going down is ok dont go up.
    flag=0

这需要很长时间才能完成,我认为它可能没问题,但实际上每100kb花费10分钟,我还有很长的路要走。

2 个答案:

答案 0 :(得分:4)

这个问题很慢的主要原因是你正在为文件中的每一行读取整个文件(或者更确切地说是它的副本)。所以,如果有10000行,你读10000行10000次,意味着总行数为10000000!

如果你有足够的内存来保存到目前为止读取的行,那么有一个非常简单的解决方案:将目前为止看到的行存储在一个集合中。 (或者,更确切地说,对于每一行,存储三个键的元组,这些元组被认为是重复的。)对于每一行,如果它已经在集合中,则跳过它;否则,处理它并将其添加到集合中。

例如:

seen = set()
for line in infile:
    testname, vid, tstamp = line.split(",", 3)[:3]
    if (testname, vid, tstamp) in seen:
        continue
    seen.add((testname, vid, tstamp))
    outfile.write(line)

文档中的itertools recipes有一个函数unique_everseen,可以让你更好地包装它:

def keyfunc(line):
    return tuple(line.split(",", 3)[:3])
for line in unique_everseen(infile, key=keyfunc):
    outfile.write(line)

如果集合占用太多内存,你总是可以在dict上假装一个集合,你可以使用dbm模块在​​数据库顶部伪造一个dict,这样做会很好在记忆中保持足够的工作以使事情快速但不足以引起问题。唯一的问题是dbm键必须是字符串,而不是三个字符串的元组...但你可以随时将它们连接起来(或重新join它们)而不是分裂,然后你有一个字符串


我假设当你说文件是“已排序”时,你的意思是时间戳,而不是关键列。也就是说,不能保证两个重复的行将紧挨着彼此。如果有,这更容易。如果你使用itertools配方,它可能看起来更容易;您只是将everseen替换为justseen

def keyfunc(line):
    return tuple(line.split(",", 3)[:3])
for line in unique_justseen(infile, key=keyfunc):
    outfile.write(line)

但是在幕后,这只是跟踪最后一行,而不是一组所有行。这不仅更快,而且还节省了大量内存。


现在(我认为)我更了解你的要求,你真正想要摆脱的不仅仅是具有相同testnamevid和{{1}的第一行除了具有最高tstamp的行之外,所有行都具有相同的testnamevid。由于文件按tstamp的升序排序,这意味着您可以完全忽略tstamp;你只想要每个人的最后一场比赛。

这意味着tstamp技巧不起作用 - 我们不能跳过第一个,因为我们还不知道后来的那个。

如果我们只是向后迭代文件,那就可以解决问题。它还会使你的内存使用量增加一倍(因为除了集合之外,你还要保留一个列表,这样你就可以按相反的顺序堆叠所有这些行)。但如果这是可以接受的,那很简单:

everseen

如果将这些惰性迭代器转换为列表以便我们可以反转它们需要太多内存,那么执行多次传递可能是最快的:反转磁盘上的文件,然后过滤反转的文件,然后再将其反转。它确实意味着两个额外的文件写入,但这可能比你的操作系统的虚拟内存与磁盘交换数百次(或者你的程序只是失败了def keyfunc(line): return tuple(line.split(",", 2)[:2]) for line in reversed(list(unique_everseen(reversed(list(infile)), key=keyfunc))): outfile.write(line) )要好得多。

如果你愿意做这项工作,那么 很难写出一个反向文件迭代器,它从末尾读取缓冲区并在换行符上拆分并产生相同的方式MemoryError / file对象可以。但除非你需要它,否则我不会打扰。


如果您 需要重复读取文件中的特定行号,linecache模块通常会加快速度。当然,远不及重新阅读那么快,但比阅读和解析成千上万的新行要好得多。

你也浪费时间在内循环中重复一些工作。例如,您调用io.Whatever三次,而不是仅将其拆分一次并将值存储在变量中,这将是速度的三倍。 3倍恒定增益远不如二次到线性增益那么重要,但是当它通过使代码更简单和更易读而免费提供时,也可以采用它。

答案 1 :(得分:2)

对于这么大的文件大小(~15MB),熊猫将是绝佳的选择。 像这样:

import pandas as pd
raw_data = pd.read_csv()
clean_data = raw_data.drop_duplicates()
clean_data.to_csv('/path/to/clean_csv.csv')

我能够使用上面的代码片段在不到一秒的时间内处理大小为151MB的CSV文件,其中包含超过5.9百万行。 请注意,重复检查可以是条件操作,也可以是要进行重复检查的字段子集。 熊猫确实提供了很多这些功能。文档here