概要
pandasにはcsvやpickle、parquetなど様々な形式でのデータ出力が用意されている。
各出力形式で実際にデータを出力して結果や実行時間を確認してみる。
実行時間はipython上で%%timeを用いて計測。小数点以下はround。記事の最後に実行時間と出力サイズを一覧でまとめています。
バージョン情報
- Python 3.7.2
- pandas==0.24.1
pandasは古いバージョンだと一部フォーマットに対応していないことがあります。pandasはどんどん便利になっていく。
出力可能な形式
下記の形式などが用意されている。
関数名 | フォーマット |
---|---|
to_csv | csv |
to_pickle | pickle |
to_parquet | parquet |
to_hdf | HDF5 |
to_sql | RDBへの書き込み |
to_excel | Excel |
to_json | JSON |
to_html | HTML |
to_feather | binary feather |
to_latex | LaTex |
to_stata | Stata dta |
to_msgpack | MessagePack |
サンプルデータ
np.int型のカラムを2つ, np.float型のカラムを3つ, 100文字の文字列のカラムを1つ、計6つのカラムを持つDataFrameを作る。
write時の実行時間の差も計測したいので1000万行で生成してみる。この処理は数分かかります。
import string import pandas as pd import numpy as np df = pd.DataFrame(index=range(10000000)) # intのカラム df['int1'] = np.random.randint(low=0, high=100, size=10000000) df['int2'] = np.random.randint(low=1, high=12, size=10000000) # floatのカラム df['float1'] = np.random.random(size=10000000) df['float2'] = np.random.random(size=10000000) df['float3'] = np.random.random(size=10000000) # 文字列のカラム df['str1'] = [''.join(x) for x in np.random.choice(list(string.ascii_letters), size=(10000000, 100))]
DataFrameの内容
df.head(5)
int1 int2 float1 float2 float3 str1 0 4 4 0.919511 0.544673 0.377326 VvcZdpoeukIB... 1 20 11 0.753088 0.177636 0.129909 XWASMLSGhoyw... 2 82 3 0.977499 0.363776 0.657033 ybhigZZbxEJV... 3 42 4 0.162754 0.945174 0.279460 ZePbSsMywJtM... 4 9 8 0.984594 0.353096 0.973451 EkXmmrTPvoZm...
ランダム文字列なので圧縮はちょっと効きづらいと思われる。後から思ったのだけどもっと圧縮効率高そうなデータにした方が良かった気がする。
メモリ使用量の確認
df.info(memory_usage='deep')
<class 'pandas.core.frame.DataFrame'> RangeIndex: 10000000 entries, 0 to 9999999 Data columns (total 6 columns): int1 int64 int2 int64 float1 float64 float2 float64 float3 float64 str1 object dtypes: float64(3), int64(2), object(1) memory usage: 1.8 GB
容量は1.8GB。
to_csv
to_csvでCSVファイルを出力する。
# 無圧縮の場合 df.to_csv('foo.csv') # gzip圧縮 df.to_csv('foo.csv.gz', compression='gzip')
実行方法 | ファイルサイズ | 実行時間 |
---|---|---|
無圧縮 | 1.6GB | 71秒 |
gzip | 1.1GB | 180秒 |
データサイズが大きくなるとto_csvでは辛くなってくる。
to_pickle
to_pickleで保存する。
# 無圧縮 df.to_pickle('foo.pkl') # gzip圧縮 df.to_pickle('foo.pkl.gz', compression='gzip') # 平文出力 → gzipコマンド df.to_pickle('foo.pkl') subprocess.call(['gzip', 'foo.pkl'])
実行方法 | ファイルサイズ | 実行時間 | 備考 |
---|---|---|---|
無圧縮 | 1.4GB | 6秒 | 速い |
gzip | 948MB | 166秒 | 遅い |
gzipコマンド | 948MB | 39秒 | gzipコマンドの方がだいぶ速い |
平文なら高速。
to_parquet
parquet。列指向はPandasとは相性が良いはず。
事前にpyarrowとzstd圧縮を使う場合はzstandardを入れる。
$ conda install -c conda-forge pyarrow $ conda install -c conda-forge zstandard
# デフォルトではSnappy圧縮 df.to_parquet('foo.parquet') # gzip圧縮 df.to_parquet('foo.parquet.gz', compression='gzip') # zstd圧縮 df.to_parquet('foo.parquet.zstd', compression='zstd')
実行方法 | ファイルサイズ | 実行時間 | 備考 |
---|---|---|---|
デフォルト(snappy) | 1.3GB | 4秒 | 速い |
gzip | 957MB | 48秒 | 遅い |
zstd | 951MB | 6秒 | 速くて圧縮効率も良い |
parquet優秀。但し依存ライブラリが多かったり大容量のファイルを扱う際にpyarrowでエラーが出てfastparquetに切り替えたり等、少し癖がある。
to_hdf
HDF形式での出力。
事前にhdf5とpytablesを入れておく。
$ conda install hdf5 pytables
modeはデフォルトがappendになっているのでwriteにしておく。
# デフォルトはcomplevelが0で無圧縮になる df.to_hdf('foo.h5', 'key', mode='w') # complevelを指定、compressionのデフォルトはzlib df.to_hdf('foo.h5', 'key', mode='w', complevel=5) # lzo圧縮 df.to_hdf('foo.h5', 'key', mode='w', complevel=5, complib='lzo')
実行方法 | ファイルサイズ | 実行時間 |
---|---|---|
無圧縮 | 1.5GB | 7秒 |
zlib | 1.2GB | 11秒 |
lzo | 1.2GB | 7秒 |
速いしappendもできるし何かと使い勝手がいい。
to_sql
RDBに保存する。SQLiteに入れてみる。PostgreSQLとMySQLはdockerのlatestをデフォルトのまま利用し特にチューニングはしていない。
# sqliteに入れる import sqlite3 conn = sqlite3.connect('foo.db') df.to_sql("foo_table", conn, if_exists="replace") # postgresqlに入れる from sqlalchemy import create_engine engine = create_engine("postgresql://postgres:hoge@localhost:5432/fuga") df.to_sql("fuga", engine, if_exists="replace") # mysqlに入れる from sqlalchemy import create_engine engine = create_engine("mysql://root:hoge@localhost:3306/fuga") df.to_sql("fuga", engine, if_exists="replace")
実行方法 | ファイルサイズ | 実行時間 | 備考 |
---|---|---|---|
sqlite | 1.6GB | 25秒 | |
postgresql | - | 1232秒 | データサイズは未計測 |
mysql | - | 444秒 | データサイズは未計測 |
sqliteはCSV出力より少し多い程度のデータサイズで実行時間も短いので、多少大きいデータでも要件によっては十分実用に耐えうるかもしれない。
to_excel
全件で実行しようとしたらメモリ20GB以上喰ってうちのPCでは動かなかったので1/10で実行。
df.head(int(len(df) / 10)).to_excel('foo.xlsx')
実行方法 | ファイルサイズ | 実行時間 |
---|---|---|
- | - | 1760秒 |
実行時間は176秒なので線形に増えると仮定して1760秒とする。
to_json
JSON出力。
# 無圧縮 df.to_json('foo.json') # gzip圧縮 df.to_json('foo.json', compression='gzip')
実行方法 | ファイルサイズ | 実行時間 |
---|---|---|
無圧縮 | 2.0GB | 13秒 |
gzip | 1.1GB | 311秒 |
to_html
to_htmlは出力する前にすべてメモリに溜め込むようでメモリを15GBほど消費していた。
df.to_html('foo.html')
実行方法 | ファイルサイズ | 実行時間 |
---|---|---|
- | 2.2GB | 998秒 |
1000万レコードのHTMLなんて誰も使わないと思うけどメモリが足りれば一応動くことは動く。
to_feather
feather形式。カラムナでArrowなのでparquetに近い。
# デフォルトは確かlz4圧縮 df.to_feather('foo.feather')
実行方法 | ファイルサイズ | 実行時間 |
---|---|---|
- | 1.4GB | 4秒 |
to_latex
latex。to_htmlと同じくメモリに一時的にデータを乗せるのでメモリ使用量は10GBを超える。
df.to_latex('foo.tex')
実行方法 | ファイルサイズ | 実行時間 |
---|---|---|
- | 1.2GB | 172秒 |
to_stata
stata。
df.to_stata('foo.dta')
実行方法 | ファイルサイズ | 実行時間 |
---|---|---|
- | 1.3GB | 24秒 |
to_msgpack
MessagePack。
# 無圧縮 df.to_msgpack('foo.mpac') # zlib圧縮 df.to_msgpack('foo.mpac', compress='zlib')
実行方法 | ファイルサイズ | 実行時間 |
---|---|---|
無圧縮 | 1.4GB | 4秒 |
zlib | 1.2GB | 20秒 |
実行時間まとめ
実行方法 | 時間(秒) | ファイルサイズ | 所感 |
---|---|---|---|
to_csv(無圧縮) | 71 | 1.6GB | |
to_csv(gzip) | 180 | 1.1GB | よく使うけど大きいデータだと重い |
to_pickle(無圧縮) | 6 | 1.4GB | |
to_pickle(gzip) | 166 | 1.4GB | |
to_parquet(snappy) | 4 | 1.3GB | |
to_parquet(gzip) | 48 | 957MB | |
to_parquet(zstd) | 6 | 951MB | 優秀 |
to_hdf(無圧縮) | 7 | 1.5GB | |
to_hdf(zlib) | 10 | 1.2GB | |
to_hdf(lzo) | 7 | 1.2GB | 便利 |
to_sql(sqlite) | 25 | 1.6GB | たまに使う |
to_sql(postgresql) | 1232 | ? | データサイズ不明 |
to_sql(mysql) | 444 | ? | データサイズ不明 |
to_excel | 1760(?) | ? | 1/10サンプリングの10倍で計算 |
to_json(無圧縮) | 13 | 2.0GB | |
to_json(gzip) | 311 | 1.1GB | |
to_html | 998 | 2.2GB | |
to_feather | 4 | 1.4GB | |
to_latex | 352 | 1.2GB | |
to_stata | 24 | 1.3GB | |
to_msgpack(無圧縮) | 4 | 1.4GB | |
to_msgpack(zlib) | 20 | 1.2GB |
改定履歴
Author: Masato Watanabe, Date: 2019-04-12, 記事投稿