Pythonで一次元リストをテキストファイル(CSV)に書き出す方法の比較

Pythonで一次元リストをテキストファイルに書き出す方法はいろいろあるのですが、どれが一番速いのか思いつく限り試してみました。

# 前提

一次元の文字列のリスト

```python
>>> list_ = [element1, element2, ... elementhoge]
```
を、テキストファイルで

```
element1
element2
...
elementhoge
```
というように**1行1要素**で書き出したいとします。

# 結論
**ファイルに書き出す文字列をまとめて生成してから出力した方が速い！！**


# 試した方法 
1: `for`と`open.write()`で1行ずつ繰り返し書いていく  

  ```python
  with open("text_for_write.txt", 'wt') as f:
    for ele in list_:
      f.write(ele+'\n')
  ```

2: `open.writelines()`を使う

  ```python
  list1 = [x+'\n' for x in list_]
  with open("text_writelines.txt", 'wt') as f:
    f.writelines(list1)
  ```
**3: 書き込む文字列をあらかじめ生成して`f.write()`でまとめて書き込む**

  ```python
  str_ = '\n'.join(list_)
  with open("text_write_str.txt", 'wt') as f:
    f.write(str_)
  ```

4: pythonのcsvモジュールの`csvwriter.writerow()`を繰り返し用いる

  ```python
  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()`を用いる

  ```python
  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()`で書き込む

  ```python
  numpy.savetxt("text_numpy_savetext.txt", list_, fmt='%s', delimiter=',')
  ```

7: pandas.Seriesに変換してから`Series.to_csv()`で書き込む

  ```python
  Series(list_).to_csv("text_pandas_to_csv.txt", index=False)
  ```

8: dask.dataframeに変換してから`dask.dataframe.to_csv()`で書き込む

  ```python
  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)`で書き込む

```python
  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](https://github.com/smn-ailab/elecho-qiita/blob/master/list_file/for_write.py) |
| 2. `open.writelines()`      |       4.191 | [GitHub](https://github.com/smn-ailab/elecho-qiita/blob/master/list_file/writelines.py) |
| **3. 文字列を生成して`open.write()`** |    **0.656** | [GitHub](https://github.com/smn-ailab/elecho-qiita/blob/master/list_file/write_str.py) |
| 4. `for`+`csv.writerow()`   |       6.647 | [GitHub](https://github.com/smn-ailab/elecho-qiita/blob/master/list_file/csv_writerow.py) |
| 5. `csv.writerows()`        |      10.866 | [GitHub](https://github.com/smn-ailab/elecho-qiita/blob/master/list_file/csv_writerows.py) |
| 6. `numpy.savetext()`       |      19.472 | [GitHub](https://github.com/smn-ailab/elecho-qiita/blob/master/list_file/numpy_savetext.py) |
| 7. `Series.to_csv()`        |      11.374 | [GitHub](https://github.com/smn-ailab/elecho-qiita/blob/master/list_file/pandas_to_csv.py) |
| 8. `dask.dataframe.to_csv()`|      13.742 | [GitHub](https://github.com/smn-ailab/elecho-qiita/blob/master/list_file/dask_series_entire.py) |
| 9. `dask.dataframe.to_csv(get=dask.multiprocessing.get)`|      68.008 | [GitHub](https://github.com/smn-ailab/elecho-qiita/blob/master/list_file/dask_series_entire_multiprocessing.py) |

※ daskの場合、dask.dataframeの内部分割数(`npartitons`)を増やしてもほとんど早くならなかった（というよりも遅くなった）です。

## 考察

意外なことに、高級なメソッドを使わずに書き込む文字列を手動で生成して書き込む方法が一番速かったです。

あまり詳しく調べていないのでなんとも言えないのですが、多分ほかの方法は1行ごとに書き込んでいて、バッファを有効利用できていないのかなと予想されます。
特に、リスト全体を書き込む系のメソッドはまとめて書き込むという利点を生かして何か最適化をしているのでは、と思っていましたが、どうやら特段何もやっていなさそうでした。

それと、ライブラリに付属しているファイルIOのメソッドが遅かったです。
daskというpandas.DataFrameなどをうまく扱ってくれるパッケージを試してみたものの、単純なファイル書き出しでは意味がなかったようです。（内部的にpandasのto_csv()を使っているみたいなので仕方がないのかな）

# まとめ
一次元リストならスクリプト側で文字列を生成するのもラクなので、自分で文字列を生成して書き込んでしまった方がよさそうです。
文字列生成は見栄えが悪くてヤダって場合は、普通にopen.write()などを使うのが丸そうです。

# おわりに
CSV読み込みについては、
[PythonでCSVを高速＆省メモリに読みたい - tkm2261's blog](http://yutori-datascience.hatenablog.com/entry/2017/08/03/230047)
で網羅的に実験されていて非常に参考になります。（メモリ使用量も考慮されています。）

今回は1次元リストの書き込みについて実験しましたが、機会があればDataFrameの書き込みでの比較もやって見たいと思います。