Pythonで一次元リストをテキストファイルに書き出す方法はいろいろあるのですが、どれが一番速いのか思いつく限り試してみました。
前提
一次元の文字列のリスト
>>> list_ = [element1, element2, ... elementhoge]
を、テキストファイルで
element1
element2
...
elementhoge
というように1行1要素で書き出したいとします。
結論
ファイルに書き出す文字列をまとめて生成してから出力した方が速い!!
試した方法
1: for
とopen.write()
で1行ずつ繰り返し書いていく
with open("text_for_write.txt", 'wt') as f:
for ele in list_:
f.write(ele+'\n')
2: open.writelines()
を使う
list1 = [x+'\n' for x in list_]
with open("text_writelines.txt", 'wt') as f:
f.writelines(list1)
3: 書き込む文字列をあらかじめ生成してf.write()
でまとめて書き込む
str_ = '\n'.join(list_)
with open("text_write_str.txt", 'wt') as f:
f.write(str_)
4: pythonのcsvモジュールのcsvwriter.writerow()
を繰り返し用いる
with open("text_csv_writerow.txt", 'wt') as f:
writer = csv.writer(f)
for ele in list_:
writer.writerow([ele])
5: pythonのcsvモジュールのcsvwriter.writerows()
を用いる
list1 = [[x] for x in list_]
with open("text_csv_writerows.txt", 'wt') as f:
writer = csv.writer(f)
writer.writerows(list1)
6: numpyの1次元配列に変換してからnumpy.savetext()
で書き込む
numpy.savetxt("text_numpy_savetext.txt", list_, fmt='%s', delimiter=',')
7: pandas.Seriesに変換してからSeries.to_csv()
で書き込む
Series(list_).to_csv("text_pandas_to_csv.txt", index=False)
8: dask.dataframeに変換してからdask.dataframe.to_csv()
で書き込む
import dask.dataframe as dd
pds = dd.from_pandas(Series(list_), npartitions=1)
pds.to_csv(["dask_series_entire.txt"], index=False)
9: dask.dataframeに変換してから、並列処理オプションをつけてdask.dataframe.to_csv(get=dask.multiprocessing.get)
で書き込む
import dask.dataframe as dd
import dask.multiprocessing
pds = dd.from_pandas(Series(list_), npartitions=1)
pds.to_csv(["dask_series_entire_multiprocessing.txt"], index=False, get=dask.multiprocessing.get)
※注:実は1行1要素という書式は、(カンマが含まれていないくせに)CSVの書式を満たしています。なので、書き出しファイルの拡張子.txt
部分を.csv
に変えれば正真正銘のCSVファイルになります。
実験
環境
Macbook Pro(2017)
CPU: 2.3 GHz Intel Core i5
メモリ: 8 GB
Python: 3.6.4
実験方法
10,000,000個の要素からなる1次元文字列リストをテキストファイルに書き出す。
リストの中身は"0"~"9,999,999"までの整数文字列。
文字列をクオートしない。
結果
方法 | 実行時間(測定3回の平均)[s] | 実験コード |
---|---|---|
1. for +open.write()
|
4.027 | GitHub |
2. open.writelines()
|
4.191 | GitHub |
3. 文字列を生成してopen.write() |
0.656 | GitHub |
4. for +csv.writerow()
|
6.647 | GitHub |
5. csv.writerows()
|
10.866 | GitHub |
6. numpy.savetext()
|
19.472 | GitHub |
7. Series.to_csv()
|
11.374 | GitHub |
8. dask.dataframe.to_csv()
|
13.742 | GitHub |
9. dask.dataframe.to_csv(get=dask.multiprocessing.get)
|
68.008 | GitHub |
※ daskの場合、dask.dataframeの内部分割数(npartitons
)を増やしてもほとんど早くならなかった(というよりも遅くなった)です。
考察
意外なことに、高級なメソッドを使わずに書き込む文字列を手動で生成して書き込む方法が一番速かったです。
あまり詳しく調べていないのでなんとも言えないのですが、多分ほかの方法は1行ごとに書き込んでいて、バッファを有効利用できていないのかなと予想されます。
特に、リスト全体を書き込む系のメソッドはまとめて書き込むという利点を生かして何か最適化をしているのでは、と思っていましたが、どうやら特段何もやっていなさそうでした。
それと、ライブラリに付属しているファイルIOのメソッドが遅かったです。
daskというpandas.DataFrameなどをうまく扱ってくれるパッケージを試してみたものの、単純なファイル書き出しでは意味がなかったようです。(内部的にpandasのto_csv()を使っているみたいなので仕方がないのかな)
まとめ
一次元リストならスクリプト側で文字列を生成するのもラクなので、自分で文字列を生成して書き込んでしまった方がよさそうです。
文字列生成は見栄えが悪くてヤダって場合は、普通にopen.write()などを使うのが丸そうです。
おわりに
CSV読み込みについては、
PythonでCSVを高速&省メモリに読みたい - tkm2261's blog
で網羅的に実験されていて非常に参考になります。(メモリ使用量も考慮されています。)
今回は1次元リストの書き込みについて実験しましたが、機会があればDataFrameの書き込みでの比較もやって見たいと思います。