概要
今回は、辞書型: { 'hoge': 'ほげ' }
みたいなデータをdataframe
の末尾に追加するときの速度比較を行いました。
なんと、実装方法によって所要時間に1.5倍以上の差がありました!!!(゜д゜;)
そんなもんじゃなかったです。400倍近い差が出ました(2019/05/13追記)
※最初の投稿の後、間違いに気づき次第ちょこちょこ修正しています。
経緯
この記事を見て、私も試してみようと思い立ちました。
Pythonを速くしたいときにやったこと
色々試行錯誤されています。
内容説明
速度計測のため、ひたすら同じ処理を繰り返します。
今回比較する処理は、dataframeの末尾に辞書型のデータを連結する処理です。
やり方は5パターン考えてみました。
-
1レコードずつdataframeに追加
1.some_dataframe.loc[index] = 辞書
2.some_datafrmae.iloc[index] = 辞書
=> エラーになるため以下略
3.some_dataframe.append(pd.Series(辞書), ignore_index=True)
4.pd.concat([some_dataframe, pd.Dataframe(辞書, index=index)])
-
辞書型配列を作って、配列をまとめてdataframe化(2019/05/13追加)
5.pd.concat([some_dataframe, pd.from_dict(辞書型配列)])
1が一番わかりやすい!
3, 4, 5 はちょっとややこしいですね...!
どれが早いのでしょうか!!?(>_<)
バージョン
module | version |
---|---|
pandas | '0.19.2' or '0.24.2' どちらでも同様の結果 |
ソースコード
from time import time
import pandas as pd
# 連結しまくる辞書
some_dict = { 'hoge': 'ほげ', 'kindness': 'やさしさが大事' }
def loc_method():
some_df = pd.DataFrame([], columns=some_dict.keys())
for i in range(3000):
some_df.loc[i] = some_dict
return some_df
def append_series_method():
some_df = pd.DataFrame([], columns=some_dict.keys())
for i in range(3000):
some_df = some_df.append(pd.Series(some_dict), ignore_index=True)
return some_df
def concat_method():
some_df = pd.DataFrame([], columns=some_dict.keys())
for i in range(3000):
some_df = pd.concat([some_df, pd.DataFrame(some_dict, index=[i])])
return some_df
def from_dict_method():
some_df = pd.DataFrame([], columns=some_dict.keys())
dict_array = []
for i in range(3000):
dict_array.append(some_dict)
some_df = pd.concat([some_df, pd.DataFrame.from_dict(dict_array)])
return some_df
測定結果
# locで結合した場合
start = time()
some_df = loc_method()
elapsed_time = time() - start
print('経過時間: {}sec'.format(elapsed_time))
>> 経過時間: 20.205236673355103sec
# appendで結合した場合
start = time()
some_df = append_series_method()
elapsed_time = time() - start
print('経過時間: {}sec'.format(elapsed_time))
>> 経過時間: 17.460779666900635sec
# concatで結合した場合
start = time()
some_df = concat_method()
elapsed_time = time() - start
print('経過時間: {}sec'.format(elapsed_time))
>> 経過時間: 11.338160991668701sec
# from_dictで辞書型配列をまとめてdataframe化し、その後くっつけた場合
start = time()
some_df = from_dict_method()
elapsed_time = time() - start
print('経過時間: {}sec'.format(elapsed_time))
>> 経過時間: 0.03905987739562988sec !!!!!
( д) ゚ ゚
まさかのややこしそうな方が圧倒的に早いという結果に....!
(一見して一番遅そう...)
多分、dataframe同士の方がやり取りも楽なんでしょうね!(適当)
(2019/05/13追記)
今までのは何だったんでしょう.....
1レコードずつdataframeに連結するのでなく、可能な限り辞書型を配列としてまとめておいて一気にdataframe化し、最後に1回だけ連結すると計算コストが低いみたいです。
どうしても1レコードずつdataframe化する必要があるなら、4のconcat
で連結する処理を使うのがよさそうです。
補足
処理結果について
上の二つの処理では、得られる結果(some_df)が若干違いますのでご注意ください。
今回は速度比較が目的だったため、得られる結果が異なっていても大きな問題ではないと考え、そのままにしています。
どの処理を選んでも同じ結果が得られるようにソースコードを修正しました。
しゅーるですな...。
番外編(2019/10/12追記)
1行ずつの追加ではありませんが、こっちの方が速度早いみたいでした。
内部処理の見直しが必要になりますが、速度面で不満を抱えているのであれば、使用してみる価値はあると思います。
from time import time
import numpy as np
import pandas as pd
# 連結しまくる辞書
some_dict = { 'hoge': 'ほげ', 'kindness': 'やさしさが大事' }
def use_frompyfunc():
df = pd.DataFrame([], columns=some_dict.keys())
def func_add(target):
return target
function = np.frompyfunc(func_add, 1, 1)
df['hoge'] = function(np.full(shape=3000, fill_value='ほげ'))
df['kindness'] = function(np.full(shape=3000, fill_value='やさしさが大事'))
return df
start = time()
some_df = use_frompyfunc()
elapsed_time = time() - start
print('経過時間: {}sec'.format(elapsed_time))
>> 経過時間: 0.025362730026245117sec
参考: [Python3] numpy, series, dataframeで、1列のデータをまとめて比較したい(loopを使わずに)