iMind Developers Blog

iMind開発者ブログ

pandasのファイル出力形式まとめ

概要

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, 記事投稿