csv 加速python拆分过程

798qvoo8  于 2023-01-28  发布在  Python
关注(0)|答案(4)|浏览(109)

我有一个非常大的4+ GB大小的文本文件,我有一个脚本,根据第一个昏迷之前的字符将文件拆分为小文件。例如:16,....行转到16.csv,61,....行转到61.csv.不幸的是,这个脚本运行了很长时间,我猜是因为写出方法的原因。有什么方法可以加速脚本吗?

import pandas as pd
import csv

with open (r"updates//merged_lst.csv",encoding="utf8", errors='ignore') as f:
    r = f.readlines()

for i in range(len(r)):
    row = r[i]
    letter = r[i].split(',')[0]
    filename = r"import//"+letter.upper()+".csv"
    with open(filename,'a',encoding="utf8", errors='ignore') as f:
        f.write(row)
osh3o9ms

osh3o9ms1#

我不确定这是否真的有很大的不同,或者瓶颈是否在其他地方,但我不会为输入中的每一行打开和关闭输出文件,而是将每个输出文件打开一次并重用它。
为了同时打开多个文件并使用正确的文件进行写入,我会将它们放入字典中,使用letter作为键:

files = {}

for i in range(len(r)):
    row = r[i]
    letter = r[i].split(',')[0]
    if letter not in files:
        filename = r"import//"+letter.upper()+".csv"
        files[letter] = open(filename,'a',encoding="utf8", errors='ignore')
    f = files[letter]
    f.write(row)

(除了检查if letter not in files,您还可以使用files.setdefault。)
你必须在最后关闭它们以确保内容被写入磁盘。或者在循环中手动执行此操作:

for f in files.values():
    f.close()

或者您仍然可以使用上下文管理器(with语句),请参见JonSG's answer

4sup72z8

4sup72z82#

17个打开的文件看起来并不是太多以至于无法管理。我会尝试通过contextlib.ExitStack()构建上下文字典。这将允许您有一个干净和可管理的方式来保持输出文件打开,这样您就不会不断地重新打开它们,因为这可能会使事情变慢,或者至少没有帮助。
注意:我简化了你的文件名只是为了让事情看起来更容易,所以如果你尝试这样做,你会想修复它。

import contextlib

with contextlib.ExitStack() as stack:
    opened_files = {
        "in": stack.enter_context(open("in.csv", "rt", encoding="utf8", errors="ignore"))
    }
    for row in opened_files["in"]:
        key = row.split(",")[0]
        target = opened_files.setdefault(key, open(f"out/{key}.csv", "a", encoding="utf8", errors="ignore"))
        target.write(row)

如果你想把字典限制在输出文件中,你甚至可以这样做:

import contextlib

with contextlib.ExitStack() as stack:
    opened_files = {}
    for row in stack.enter_context(open("in.csv", "rt", encoding="utf8", errors="ignore")):
        key = row.split(",")[0]
        target = opened_files.setdefault(key, open(f"out/{key}.csv", "a", encoding="utf8", errors="ignore"))
        target.write(row)
hjqgdpho

hjqgdpho3#

下面是您的原始代码、mkrieger1、我的贡献和iohans的Pandas解决方案的一些性能比较,因为:Don't use Pandas to iterate rows.
要对这些进行基准测试:
1.我生成了三个示例输入CSV文件,每个文件看起来如下所示:

1,5
8,1
8,7
5,10
9,2
4,3
7,6
10,7
...,...

两列都是从1到10的随机整数。三个示例CSV的行大小会增长:

CSV          Row count
----   ---------------
10e5      100_000 rows
10e6    1_000_000 rows
10e7   10_000_000 rows

1.我用了你的密码,mkrieger1的,还有我的:

Version   Description                                   
-------   ----------------------------------------------
ver1      OP's version, baseline                        
ver2      mkrieger1's version, with dict of output files
ver3      my version, use csv module, read incrementally
ver4      iohans's Pandas solution

以下是结果:
| 版本|大小|真实值(s)|用户|系统|内存(MB)|
| - ------|- ------|- ------|- ------|- ------|- ------|
| 版本1||||||
| | 10e5|四点零四分|0.44|一点八三|十二时十九分|
| | 10e6|二十三块九七|四点二九|十七点七九分|八十一点四九分|
| | 10e7|二一四点七四|四十三点五四分|一百六十七点三五|小行星702.08|
| 版本2||||||
| | 10e5|0.03分|0.02分|0.0分|十二时十九分|
| | 10e6|0.2分|0.18|0.01分|八十一点九一分|
| | 10e7|一点八二|1.7岁|0.11|小行星702.91|
| 版本3||||||
| | 10e5|0.03分|0.03分|0.0分|七点四十七分|
| | 10e6|0.23|0.22|0.0分|七点七七|
| | 10e7|二、三|二点二七分|0.03分|八点五六分|
| 版本4||||||
| | 10e5|十二点五|十一时零五分|三点二八分|五十四点八五分|
| | 10e6|一百二十一点八九|九十九点二|二十四点五四分|八十块八一|
| | 10e7|1020人以上|不适用|不适用|不适用|

  • 你的(ver1)花了很多时间进行系统调用(大概是为了重新打开输出文件);我还怀疑需要一些(足够的?)时间来查找每个文件的末尾,以便"append"工作
  • mkrieger1(ver2)在时间上提供了最大的性能改进。由于只打开一次输出文件,该版本在sys调用上花费的时间少了很多。不过,令人好奇的是,sys调用在不断增加。
  • mine(ver3)在内存方面提供了最大的改进;如果您的内存有限,我怀疑以前的版本也会造成时间损失,因为压力过大的内存会被分页到磁盘。

我们如何使内存使用接近于零?
正如一些评论所建议的,通过增量读取输入并决定在读取时对每行执行什么操作:

import csv

def get_fname(num: str) -> str:
    return f"output_{num:>02}.csv"

out_files = []  # keep track of opened files, to close at end
writers = {}  # csv writers for individually numbered output files

with open(input_csv, newline="") as f_in:
    reader = csv.reader(f_in)

    for row in reader:
        num = row[0]
        if num not in writers:
            f = open(get_fname(num), "w", newline="")
            out_files.append(f)
            writers[num] = csv.writer(f, lineterminator="\n")

        writers[num].writerow(row)

for f in out_files:
    f.close()

我也使用csv模块,因为它可以正确处理CSV格式。如果你知道100%,你的CSV文件没有嵌入换行符,如:

Co1,Col2,Col3
16,"A column, with 
a newline",98.6

那么你可以一行一行地读/写,然后在你看到的第一个逗号上拆分,但是,即使你知道这一点,无论如何,仅仅使用csv模块也不会有太多的时间损失。

  • "Pandas解决方案"(版本4)在迭代行Don't use Pandas to iterate rows时永远不会有好的性能。我在17分钟后中止了10e7的运行......趋势表明它可能会在大约20分钟内完成。也许一个更好的调优Pandas解决方案可以比慢6倍做得更好,但它永远不会比逐行读取CSV(作为文本或使用csv模块)更好。
btqmn9zl

btqmn9zl4#

阅读文件的节并使用to_csv可以加快这个脚本的速度。这个例子一次读取500,000行的大文件。

import pandas as pd
    
r = pd.read_csv(r"updates//merged_lst.csv", chunksize=500000, encoding="utf8", errors='ignore')

for chunk in r:
    for index, row in chunk.iterrows():
        letter = row[0].split(',')[0]
        f = r"import//"+letter.upper()+".csv"
        chunk.loc[index:index].to_csv(f, mode='a', header=False, index=False)

相关问题