如何快速将大型DataFrame输出到CSV文件?

37

对于 Python / Pandas,我发现 df.to_csv(fname) 函数的速度大约为每分钟 ~1 百万行。有时我可以通过以下方法提高运行效率达到 7 倍:

def df2csv(df,fname,myformats=[],sep=','):
  """
    # function is faster than to_csv
    # 7 times faster for numbers if formats are specified, 
    # 2 times faster for strings.
    # Note - be careful. It doesn't add quotes and doesn't check
    # for quotes or separators inside elements
    # We've seen output time going down from 45 min to 6 min 
    # on a simple numeric 4-col dataframe with 45 million rows.
  """
  if len(df.columns) <= 0:
    return
  Nd = len(df.columns)
  Nd_1 = Nd - 1
  formats = myformats[:] # take a copy to modify it
  Nf = len(formats)
  # make sure we have formats for all columns
  if Nf < Nd:
    for ii in range(Nf,Nd):
      coltype = df[df.columns[ii]].dtype
      ff = '%s'
      if coltype == np.int64:
        ff = '%d'
      elif coltype == np.float64:
        ff = '%f'
      formats.append(ff)
  fh=open(fname,'w')
  fh.write(','.join(df.columns) + '\n')
  for row in df.itertuples(index=False):
    ss = ''
    for ii in xrange(Nd):
      ss += formats[ii] % row[ii]
      if ii < Nd_1:
        ss += sep
    fh.write(ss+'\n')
  fh.close()

aa=DataFrame({'A':range(1000000)})
aa['B'] = aa.A + 1.0
aa['C'] = aa.A + 2.0
aa['D'] = aa.A + 3.0

timeit -r1 -n1 aa.to_csv('junk1')    # 52.9 sec
timeit -r1 -n1 df2csv(aa,'junk3',myformats=['%d','%.1f','%.1f','%.1f']) #  7.5 sec

注意:性能提升取决于数据类型。但至少在我的测试中,to_csv() 总是比未优化的 Python 表现得慢得多。
如果我有一个 4500 万行的 CSV 文件,则:
aa = read_csv(infile)  #  1.5 min
aa.to_csv(outfile)     # 45 min
df2csv(aa,...)         # ~6 min

问题:

What are the ways to make the output even faster?
What's wrong with to_csv() ? Why is it soooo slow ?

注意:我的测试是在 Linux 服务器的本地驱动器上使用 pandas 0.9.1 进行的。

我最近使用了内置的to_excel和to_csv DataFrame方法,从一个DataFrame groupby中批量导出约1.7K个报告。其中一部分报告(特别是大文件)出现了损坏。我对这些内置过程非常怀疑,计划为我的工作流程自行编写导出函数。 - David Marx
2
@DavidMarx 能否提供一个损坏文件、DataFrame 和你的代码的示例呢?这将极大地帮助我们调试问题。谢谢。 - Chang She
这是我为办公室建立的一个工具。我会看看能否使用不涉及工作场所敏感信息的数据来复制该问题。 - David Marx
5个回答

15

Lev. Pandas已经重写了to_csv,以提高本地速度。该过程现在受到i/o限制,解决了许多微妙的dtype问题和引号情况。以下是我们在即将发布的0.11版本中与0.10.1的性能比较结果。数据以ms为单位,比值越低越好。

Results:
                                            t_head  t_baseline      ratio
name                                                                     
frame_to_csv2 (100k) rows                 190.5260   2244.4260     0.0849
write_csv_standard  (10k rows)             38.1940    234.2570     0.1630
frame_to_csv_mixed  (10k rows, mixed)     369.0670   1123.0412     0.3286
frame_to_csv (3k rows, wide)              112.2720    226.7549     0.4951

因此,对于单个数据类型(例如浮点数),如果不太宽,则吞吐量约为20M行/分钟,这是您从上面的示例中得出的结果。

In [12]: df = pd.DataFrame({'A' : np.array(np.arange(45000000),dtype='float64')}) 
In [13]: df['B'] = df['A'] + 1.0   
In [14]: df['C'] = df['A'] + 2.0
In [15]: df['D'] = df['A'] + 2.0
In [16]: %timeit -n 1 -r 1 df.to_csv('test.csv')
1 loops, best of 1: 119 s per loop

2
对于这种类型的数据集,如果您需要查询,通常最好将其存储在HDF5中,参见http://pandas.pydata.org/pandas-docs/dev/io.html#hdf5-pytables和http://pandas.pydata.org/pandas-docs/dev/cookbook.html#hdfstore。 - Jeff
1
我正在尝试输出一个100,000 * 100,000的数据框,但是这需要非常漫长的时间... =( - alvas
我合并了90多个csv文件,每个文件选择特定的4列,只有3个文件超过200,000KB,其他文件低于平均10,000KB,有些文件是零行,然后进行to_csv操作,花费了17分钟。结果csv文件大小为200,000KB,这个时间合理吗?我该如何优化?@Jeff - Erik Johnsson

12

对于这种情况,2019年最好只使用numpy。看一下时间:

aa.to_csv('pandas_to_csv', index=False)
# 6.47 s

df2csv(aa,'code_from_question', myformats=['%d','%.1f','%.1f','%.1f'])
# 4.59 s

from numpy import savetxt

savetxt(
    'numpy_savetxt', aa.values, fmt='%d,%.1f,%.1f,%.1f',
    header=','.join(aa.columns), comments=''
)
# 3.5 s
所以使用numpy可以将时间缩短一半。当然,这是以降低灵活性为代价的(与aa.to_csv相比)。使用Python 3.7,pandas 0.23.4和numpy 1.15.2进行基准测试(xrange已被range替换,以使问题中发布的函数在Python 3中正常工作)。如果需要包含索引,则savetxt将正常工作-只需传递df.reset_index().values并相应地调整格式字符串即可。2021更新:正如评论中指出的那样,pandas的性能有了很大提高。savetxt仍然是最快的选项,但仅仅比aa.to_csv()快一点:使用pandas 1.3.0和numpy 1.20.3进行基准测试时,aa.to_csv()花费2.64秒,而savetxt花费2.53秒。问题中的代码(df2csv)花费2.98秒,成为现今最慢的选项。你的情况可能会有所不同-2021年的测试是在SSD上进行的,并且具有非常快的CPU,而2019年我使用的是HDD和较慢的CPU。

我遇到了以下错误:ValueError: fmt格式中的%格式数量不正确:%d,%.1f,%.1f,%.1f - Negative Correlation
确保您拥有与列数相同的格式化字符串。该答案可以直接使用问题中的测试用例,但对于其他数据框,需要进行调整。 - krassowski
4
就目前而言,pandas 的 to_csv 函数(版本为0.23.4)的性能已经不比 np.savetxt 差了太多:timeit("aa.to_csv('temp.csv', index=False)", globals=globals(), number=5) 耗时 37.9 秒,而 timeit("np.savetxt('temp.csv', aa.values, fmt='%d,%.1f,%.1f,%.1f', header=','.join(aa.columns), comments='')", globals=globals(), number=5) 耗时 33.7 秒。 - HHest
我更新了答案。我想知道 SSD 和 HDD 会有多大的差别。 - krassowski

5

使用分块大小。我发现这样做会有很大的区别。如果你有足够的内存,使用好的分块大小(行数)将数据读入内存,然后一次性写入。


1
默认的块大小是1还是其他值?你使用什么块大小?即使将块大小设置为500000,对我来说似乎没有任何区别。 - wordsforthewise
2
找到了,它可能是1... https://github.com/pandas-dev/pandas/blob/v0.20.3/pandas/io/formats/format.py#L1555 - wordsforthewise
5
@wordsforthewise 如果列数> 100000,则chunksize仅为1,否则为100000 //列数。 - Matt
2
尝试将1600万行写入CSV。使用chunksize=1000000,将写入速度提高了10倍。使用c5.9xlarge EC2实例。 - aamir23

3

今天早些时候我也有同样的问题。使用to_csv使我的数据框花费了1小时27分钟。

我发现了一个叫做pyarrow的包,将时间缩短到了约10分钟。这对我来说似乎是最直接的解决方案。

使用方法如下:

#install with conda then import
import pyarrow as pa
import pyarrow.csv as csv

#convert format - "old_pd_dataframe" is your "aa".
new_pa_dataframe = pa.Table.from_pandas(old_pd_dataframe)

#write csv
csv.write_csv(new_pa_dataframe, 'output.csv')

2
你的 df_to_csv 函数非常好,但它做了很多假设,并且不适用于一般情况。
如果它对你有用,那很好,但请注意它不是一个通用解决方案。CSV 可以包含逗号,那么如果有这个元组需要写入,会发生什么呢?('a,b','c') Python 的 csv 模块会对该值进行引用,以避免产生混淆,并在任何值中存在引号时转义引号。当然,生成适用于所有情况的内容要慢得多。但我想你只有一堆数字。
你可以尝试这个,看看它是否更快:
#data is a tuple containing tuples

for row in data:
    for col in xrange(len(row)):
        f.write('%d' % row[col])
        if col < len(row)-1:
            f.write(',')
    f.write('\n')

我不确定这样做是否更快。如果不是,那是因为过多的系统调用,所以你可以使用StringIO来代替直接输出,然后每隔一段时间将其转储到真实文件中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接