大きなcsvファイルを扱う時はPythonのpickleが超便利

大きなcsvファイルを扱う時はPythonのpickleが超便利

python panadas のデータフレームはかなり便利で、

pythonでcsvファイルを扱う時、pandasで読み込むわけですが、

csvファイルのサイズが大きくなってくると読み込むだけでかなり時間がかかるようになってきます。

例えば、300.9 MBのドル円のohlcvのデータ(2002年5月6日から2020年6月4日)を読み込むのに

⇩こういう奴

Datetime,Volume,Open,High,Low,Close
2002-05-06 20:58:00,1,127.150,127.150,127.150,127.150
2002-05-07 20:59:00,1,127.920,127.920,127.920,127.920
2002-05-08 20:59:00,1,128.920,128.920,128.920,128.920
2002-05-09 20:59:00,1,128.380,128.380,128.380,128.380
2002-05-10 20:59:00,1,127.630,127.630,127.630,127.630
2002-05-10 22:10:00,1,127.620,127.620,127.620,127.620

2019年のmacbook proで1.021秒かかります。

昔と比べればストレージがSSDになったこともあり、かなり速いのですが、それでもちょっとタイムラグを感じます。

様々な条件でのバックテストなどこのデータを何度も読み込む必要がある場合、散り積もで、かなりの時間を浪費してしまうわけです。

じゃあどうするのか?

オススメはPythonの機能であるpickleを使うことです。

pickle とはピクルス(漬物)の単数形です。

Pythonのデータをそのままバイナリとして保存できてしまう便利機能です。

クラスのインスタンスなどオブジェクトであれば、なんでも保存できてしまいます。

便利なのが、Pandasのデータフレームをそのままpickle化してファイルとして保存できることです。

pickle化すれば、csvファイルを読み込む時のようなファイルを読んで、データフレームに変換して、といった時間がかからないので、かなり高速に読み込めるようになります。

今回は

  • Pandasでcsvからデータフレームに変換する場合
  • ダンプした(オブジェクトをpickleファイルに変換)物を読込む場合
  • redisというメモリにロードできるデータベースに保存したpickleバイナリを読込む場合

を試しました。

読込むPythonライブラリ

import redis
import pandas
from time import time
import pickle

redisデータベースを使用するのredisライブラリが必要です。

pandasのデータフレームを利用するのでpandasが必要です。

Python標準ライブラリtimeで時間を計測します。

Python標準ライブラリpickleでデータフレームをpickle化します。

時間を計測する関数

def calctime(func):
    start = time()
    r = func()
    return {'value': r, 'time': time()-start}

 

Pandasでcsvからデータフレームに変換する場合

csv = calctime(lambda: pandas.read_csv(
    'USD_JPY_20020506-20200604_M1.csv', parse_dates=['Datetime'], index_col='Datetime'))
print('普通にread_csvすると{}秒かかりました'.format(csv['time']))
#普通にread_csvすると4.523083686828613秒かかりました

lambdaにしているのはcalctimeの引数に関数を渡す必要があるから

4.523秒かかりました。

ダンプした(オブジェクトをpickleファイルに変換)物を読込む場合

######################################################################
#プロトコル1
csv = pandas.read_csv('USD_JPY_20020506-20200604_M1.csv',
                      parse_dates=['Datetime'], index_col='Datetime')

# バイト列をファイルにダンプ
pickle.dump(csv, open('USD_JPY_20020506-20200604_M1.csv.pkl', 'wb'), protocol=1)

# ダンプしたファイルを再度読み込み
pickled = calctime(lambda: pickle.load(
    open('USD_JPY_20020506-20200604_M1.csv.pkl', 'rb')))
print('pickleでloadすると{}秒かかりました'.format(pickled['time']))

######################################################################
#プロトコル2
csv = pandas.read_csv('USD_JPY_20020506-20200604_M1.csv',
                      parse_dates=['Datetime'], index_col='Datetime')

# バイト列をファイルにダンプ
pickle.dump(csv, open('USD_JPY_20020506-20200604_M1.csv.pkl', 'wb'), protocol=2)

# ダンプしたファイルを再度読み込み
pickled = calctime(lambda: pickle.load(
    open('USD_JPY_20020506-20200604_M1.csv.pkl', 'rb')))
print('pickleでloadすると{}秒かかりました'.format(pickled['time']))
######################################################################
#プロトコル3
csv = pandas.read_csv('USD_JPY_20020506-20200604_M1.csv',
                      parse_dates=['Datetime'], index_col='Datetime')

# バイト列をファイルにダンプ
pickle.dump(csv, open('USD_JPY_20020506-20200604_M1.csv.pkl', 'wb'), protocol=3)

# ダンプしたファイルを再度読み込み
pickled = calctime(lambda: pickle.load(
    open('USD_JPY_20020506-20200604_M1.csv.pkl', 'rb')))
print('pickleでloadすると{}秒かかりました'.format(pickled['time']))
######################################################################
#プロトコル4
csv = pandas.read_csv('USD_JPY_20020506-20200604_M1.csv',
                      parse_dates=['Datetime'], index_col='Datetime')

# バイト列をファイルにダンプ
pickle.dump(csv, open('USD_JPY_20020506-20200604_M1.csv.pkl', 'wb'), protocol=4)

# ダンプしたファイルを再度読み込み
pickled = calctime(lambda: pickle.load(
    open('USD_JPY_20020506-20200604_M1.csv.pkl', 'rb')))
print('pickleでloadすると{}秒かかりました'.format(pickled['time']))


######################################################################
#プロトコル5

csv = pandas.read_csv('USD_JPY_20020506-20200604_M1.csv',
                      parse_dates=['Datetime'], index_col='Datetime')

# バイト列をファイルにダンプ
pickle.dump(csv, open('USD_JPY_20020506-20200604_M1.csv.pkl', 'wb'), protocol=5)

# ダンプしたファイルを再度読み込み
pickled = calctime(lambda: pickle.load(
    open('USD_JPY_20020506-20200604_M1.csv.pkl', 'rb')))
print('pickleでloadすると{}秒かかりました'.format(pickled['time']))
プロトコル1 1.021803855895996秒
プロトコル2 0.9553539752960205秒
プロトコル3 0.0537259578704834秒
プロトコル4 0.053254127502441406秒
プロトコル5 0.05703282356262207秒

redisに保存したpickleバイナリを読込む場合

csv = pandas.read_csv('USD_JPY_20020506-20200604_M1.csv',
                      parse_dates=['Datetime'], index_col='Datetime')

# バイト列をファイルにダンプ
dumped = pickle.dumps(csv, protocol=4)

r = redis.Redis(host='localhost', port=6379, db=0)
r.set('USD_JPY_20020506-20200604_M1.csv.pkl', dumped)

# ダンプしたファイルを再度読み込み
pickled = calctime(lambda: pickle.loads(
    r.get('USD_JPY_20020506-20200604_M1.csv.pkl')))
print('redis上のpickleバイナリをloadすると{}秒かかりました'.format(pickled['time']))
redis上のpickleバイナリをロードすると 0.6771352291107178秒

redisが遅くなったのは多分、macbook proのストレージがSSDだからかなと思います。

redisを使うメリットはデータをメモリに格納してそこから取り出すということなので、メモリなので読み込みがめちゃくちゃ速いという点です。

ただし、redisデータベースにアクセスするわけなので、そのオーバーヘッドで少し遅くなるわけです。

pickleで保存しているデータはSSDに保存されているので、SSDもかなり読み込みが速いんだと思います。そこを考えると、redisデータベースを介した結果、その分遅くなったのかなと思います。

コメント