【Python】PandasのDataFrameで特定の行を爆速で更新する方法

【Python】PandasのDataFrameで特定の行を爆速で更新する方法

すみません、タイトルはちょっと誇張表現含んでます。

あまりpandasに慣れていない人が書いていたと思われるコードで実行すると、
数十分かかる処理が1秒以下で終わるようになるという事はざらにあります。

pandasは便利ではあるのですが、何も考えずに書くとPythonという言語の特性やpandasのデメリットばかりを享受するようなコードになりがちです。
本来の実力をpandasに発揮してもらえるようになったらいいなあという記事になります。

環境

Python: 3.7.4(Anaconda環境)

numpy:1.16.5

pandas:0.25.1

実行時間の計測はjupyterで「%%timeit」を使用しています。

この記事で早くなる()内容

・特定のindexを指定してデータの更新を行いたい。
・更新を行われるデータは事前に全て用意されている
・かつ全部同じデータではない。

ような場合が対象になるかと思います。
別のシステムからまとめてデータを取得した際や、外れ値を持つようなデータを補正する際などに使われるのではないでしょうか。

 

テスト用のデータを生成

置き換えが行われるデータ

1万行のデータをランダム生成してみました。

apple_price orange_price melon_price banana_price mango_price
0 143 119 251 38 221
1 71 86 219 34 209
2 111 129 186 48 221
3 95 120 200 67 234
4 148 124 172 68 265
9995 152 105 150 56 254
9996 125 119 301 39 267
9997 80 134 245 64 227
9998 124 107 165 42 204
9999 113 114 215 25 259

10000 rows × 5 columns

更新するデータ

 

問題だったコード

便利だからといって、とりあえずdf.iterrowsを使うのは罪。

df.iterrowsを使うとループの度にSeriesが生成される(らしい)ので非常に低速になる可能性を孕んでいます。
いや便利なメソッドですよ。でも速度が欲しいとね…。

改善案たち

①df.itterrowsを取り除く(初期の1.78倍高速)

これだけで実行時間は半分くらいになります。iterrowsは怖いですねぇ。

でも、locを乱発することは遅いと聞くのでそれもなくしたい。

②ボツ

終わってますね…。次だ次。。。

③locやpandasループは遅いのでndarrayに変換して回す(初期の1.87倍高速)

ソースを覗いて見たらpandasのDataFrameやSeriesのループは、各ループごとに値を取り出すだけでなくデータ型の判定(+変換)が行われるので便利だが遅いようです。

若干ですが改善しました。

やはりlocを減らすことは大切なようですが、代入先を指定する際にlocを使ってるので道半ばですね…。

④ループからpandas排除(ndarrayで更新→列に代入)(初期の952倍高速)

もうpandas抜きでやろうぜって例。locないと速い…。すごいぜ…。

⑤冷静になってpandasのupdate文を使う(初期の1,248倍高速)

そもそもupdateメソッドで特定のindexの値を全部置き換えられるので、それを使う。
pandasはすごいんです。可読性も高くなりやすいし。

自分でちょっと工夫するくらいなら、組み込みの関数を使うのが無難。簡単で早い!

⑥numpyだけかつループなしで更新してみる(初期の5,084倍高速)

今回の最終系(このくらいなら良いけど複雑な処理になると、可読性結構下がりそう)。
nd_arrayに変換した上で、ループを使わずに変換。ループなしかつnumpyだけで処理すると凄い早い。

まとめ

事前に更新データ(全データではなく一部のindexの更新)を用意する方が高速な場合は、pandasのupdate文を使ってあげましょう。

どうしても早くしたい場合は、numpyに変換してから処理を回してあげると良さそうです。
可読性は下がり記述量は増えますが、速度は良い感じです。

最後に

高速化はバグを生みやすかったり無限に時間取られたりするので、どうしても必要な時もしくは最後にやりましょう!!!

 

Pyhonカテゴリの最新記事