【问题标题】:Add header to CSV without loading CSV在不加载 CSV 的情况下将标题添加到 CSV
【发布时间】:2017-06-18 08:17:02
【问题描述】:

有没有办法在不将 CSV 加载到 python 内存中的情况下向 CSV 添加标题行?我有一个 18GB 的​​ CSV 我想添加一个标题,我见过的所有方法都需要将 CSV 加载到内存中,这显然是不可行的。

【问题讨论】:

  • 你试过DictWriter的writeheader方法了吗? (docs.python.org/3/library/csv.html) 我不知道确切的答案,只是一个想法:如果你用 'a' 打开文件并尝试使用 writeheader,它会起作用吗?如果出现问题,最好尝试使用原始文件的副本。
  • 查看更新后的答案以比较 3 种方法的速度。

标签: python csv


【解决方案1】:

您将需要重写整个文件。最简单的就是不用python

echo 'col1, col2, col2,... ' > out.csv
cat in.csv >> out.csv

基于 Python 的解决方案将在更高级别上运行,并且速度会慢很多。毕竟18GB是很多数据。更好地使用操作系统功能,这将是最快的。

【讨论】:

  • 如果您不想离开 Python,您可以随时将其封装在 subprocess.call() 调用中。或者甚至像@maximilian-peters 的回答那样用 Python 编写第一行代码。
  • 这实际上是最快的解决方案,请参阅另一个答案中的基准。
  • 感谢@MaximilianPeters 花时间进行比较
【解决方案2】:

只需使用 csv 模块迭代行的事实,因此它永远不会将整个文件加载到内存中

import csv

with open("huge_csv.csv") as fr, open("huge_output.csv","w",newline='') as fw:
    cr = csv.reader(fr)
    cw = csv.writer(fw)
    cw.writerow(["title1","title2","title3"])
    cw.writerows(cr)

使用writerows 确保非常好的速度。内存就留在这里了。一切都是逐行完成的。由于数据已正确处理,您甚至可以更改输出文件中的分隔符和/或引号。

【讨论】:

    【解决方案3】:

    以下是对具有 10^6 行和 10 列 (n=50) 的约 200 MB CSV 文件的三种建议解决方案的比较。 对于较大和较小的文件(10 MB 到 8 GB),该比率大致相同。

    cp:shutil:csv_reader 1:10:55

    即使用内置的 cp 函数比使用 Python 的 csv 模块快大约 55 倍。

    电脑:

    • 普通硬盘
    • Python 3.5.2 64 位
    • Ubuntu 16.04
    • i7-3770


    import csv
    import random
    import shutil
    import time
    import subprocess
    
    rows = 1 * 10**3
    cols = 10
    repeats = 50
    
    shell_script = '/tmp/csv.sh'
    input_csv = '/tmp/temp.csv'
    output_csv = '/tmp/huge_output.csv'
    col_titles = ['titles_' + str(i) for i in range(cols)]
    
    with open(shell_script, 'w') as f:
        f.write("#!/bin/bash\necho '{0}' > {1}\ncat {2} >> {1}".format(','.join(col_titles), output_csv, input_csv))
    
    with open(shell_script, 'w') as f:
        f.write("echo '{0}' > {1}\ncat {2} >> {1}".format(','.join(col_titles), output_csv, input_csv))
    subprocess.call(['chmod', '+x', shell_script])
    
    run_times = dict([
        ('csv_writer', list()),
        ('external', list()),
        ('shutil', list())
    ])
    
    def random_csv():
        with open(input_csv, 'w') as csvfile:
            csv_writer = csv.writer(csvfile, delimiter=',')
            for i in range(rows):
                csv_writer.writerow([str(random.random()) for i in range(cols)])
        with open(output_csv, 'w'):
            pass
    
    for r in range(repeats):
        random_csv()
        #http://stackoverflow.com/a/41982368/2776376
        start_time = time.time()
        with open(input_csv) as fr, open(output_csv, "w", newline='') as fw:
            cr = csv.reader(fr)
            cw = csv.writer(fw)
            cw.writerow(col_titles)
            cw.writerows(cr)
        run_times['csv_writer'].append(time.time() - start_time)
    
        random_csv()
        #http://stackoverflow.com/a/41982383/2776376
        start_time = time.time()
        subprocess.call(['bash', shell_script])
        run_times['external'].append(time.time() - start_time)
    
        random_csv()
        #http://stackoverflow.com/a/41982383/2776376
        start_time = time.time()
        with open('header.txt', 'w') as header_file:
            header_file.write(','.join(col_titles))
    
        with open(output_csv, 'w') as new_file:
            with open('header.txt', 'r') as header_file, open(input_csv, 'r') as main_file:
                shutil.copyfileobj(header_file, new_file)
                shutil.copyfileobj(main_file, new_file)
        run_times['shutil'].append(time.time() - start_time)
    
        print('#'*20)
        for key in run_times:
            print('{0}: {1:.2f} seconds'.format(key, run_times[key][-1]))
    
    print('#'*20)
    print('Averages')
    for key in run_times:
        print('{0}: {1:.2f} seconds'.format(key, sum(run_times[key])/len(run_times[key])))
    

    如果您真的想在 Python 中执行此操作,可以先创建头文件,然后通过 shutil.copyfileobj 将其与您的第二个文件合并。

    import shutil
    with open('header.txt', 'w') as header_file:
        header_file.write('col1;col2;col3')
    
    with open('new_file.csv', 'w') as new_file:
        with open('header.txt', 'r') as header_file, open('main.csv', 'r') as main_file:
            shutil.copyfileobj(header_file, new_file)
            shutil.copyfileobj(main_file, new_file)
    

    【讨论】:

    • 这个基准不可能是正确的。在 0.13 秒内完成意味着以 1538 MB/s 的速度同时读取和写入。 “常规硬盘”无法做到这一点。可能需要刷新文件系统。
    • @Wups:感谢您的检查!我将运行基准测试并尝试刷新所有内容。
    猜你喜欢
    • 2015-12-02
    • 1970-01-01
    • 2020-08-04
    • 1970-01-01
    • 2016-12-31
    • 1970-01-01
    • 2018-08-01
    • 2021-06-27
    • 2020-11-08
    相关资源
    最近更新 更多