[英]Removing duplicates in a huge .csv file
我有这种格式的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分钟,而且还有很长的路要走。
速度较慢的主要原因是您正在读取文件中每一行的整个文件(或它的重复副本)。 因此,如果有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
配方具有一个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)
但是在幕后,这只是跟踪最后一行,而不是所有行的集合。 这样不仅速度更快,而且还节省了大量内存。
既然(我认为)我可以更好地理解您的需求,那么您实际上想要摆脱的不只是具有相同testname
, vid
和tstamp
的第一行,而是除具有相同testname
和vid
所有行之外的所有行。 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)
如果将这些懒惰的迭代器转换为列表,以便我们可以对其进行反转,则将占用过多的内存,这可能是多次通过的最快方法:反转磁盘上的文件,然后过滤反转的文件,然后再次反转。 这确实意味着要多写两次文件,但是比起OS的虚拟内存在磁盘上来回交换数百次(或者您的程序仅因MemoryError
失败)要MemoryError
。
如果你愿意做的工作,就不会那么难写一个后退文件迭代器,其内容从端的缓冲区和分裂的新行,并产生相同的方式, file
/ io.Whatever
对象一样。 但是除非您确实需要它,否则我不会打扰。
如果您确实需要从文件中重复读取特定的行号,则行linecache
模块通常会linecache
加快速度。 当然,它的速度远不及完全不重新读取,但是比读取和解析数千个换行符要好得多。
您还浪费时间在内部循环中重复一些工作。 例如,您调用line2.split(",")
三次,而不是将其拆分一次并将值存储在变量中,这将是其速度的三倍。 3倍恒定增益远不及二次至线性增益那么重要,但是当通过使您的代码更简单,更易读而免费提供它时,不妨采用它。
对于这么大的文件大小(〜15MB),Pandas将是绝佳的选择。 像这样:
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文件,其中包含超过590万行。 请注意,重复检查可以是条件操作,也可以是要匹配以进行重复检查的字段子集。 熊猫确实提供了许多现成的功能。 这里的文件
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.