pandasのapplyが遅い原因とベクトル化で速くした話

仕事で大量のデータを扱うときがあります。pandasは本当に便利な道具ですが、複雑な条件で計算をしたいとき、つい頼ってしまうのがapplyメソッドではないでしょうか。私も以前はExcelの関数のような感覚で、一行ずつ丁寧に処理してくれるapplyを愛用していました。しかし扱うデータが数万件、数十万件と増えていくと、思わぬ落とし穴に直面することになったのです。

最初は順調に動いていたスクリプトが、ある日を境に全く終わらなくなりました。画面が固まったようになり、パソコンのファンが激しく回る音だけが響きます。実はpandasのapplyには、使い方を間違えると処理速度が劇的に低下するという弱点がありました。この遅さの正体を知らないまま使い続けると、せっかくの自動化が逆効果になってしまいます。

そこで今回は、applyが遅すぎて業務が止まった私自身の失敗をもとに、原因と解決策を共有します。NumPyを使ったベクトル化という手法に切り替えることで、処理時間を驚くほど短縮できました。もしあなたの書いたPythonコードが重いと感じているなら、ぜひ最後までお付き合いください。

df.apply(axis=1)の処理時間問題

月次の売上データを集計していたときのことです。会社から渡されたCSVファイルには数十万行のデータがありました。私は特定の条件に基づいてラベルを付ける作業をしていたのです。優良顧客や一般顧客といった分類ですね。そこで私は使い慣れたdf.apply(axis=1)を使いました。一行ごとにif文で判定する関数を適用したわけです。最初は数千行のテストデータで動かしていました。そのため数秒で終わる処理に満足していたのです。

ところが、本番の全データを読み込ませて実行した瞬間、状況は一変しました。プログラムを実行してから数分が経過しても、コンソールには何も表示されません。まるでパソコンがフリーズしてしまったかのような沈黙が続きます。私は何か無限ループでも書いたかなと思いました。そして冷や汗をかいたのです。結局その処理が終わるまでに10分以上の時間がかかりました。締め切りが迫る中で胃が痛くなるような思いをしたのです。

今回扱っていたデータは約35万行ありました。テスト時に動かしていたのはたった数千行で、桁が二つも違っていたのが、あとから振り返るとそもそもの原因だったと分かったのです。

日本語インターフェースのJupyter Notebook上の画面です。セルの実行

pandas.applyの遅延:Pythonの苦手な処…

pandasのapplyは、これほどまでに時間がかかる理由があります。実はdf.apply(axis=1)を実行すると、pandasは裏側でPythonのforループとほぼ同じ動きをしているのです。一行ずつデータを取り出して関数を呼び出し、結果を戻すという作業を、データの行数分だけ繰り返します。

Pythonという言語はこの処理が苦手です。一つひとつの要素を順番に処理するのがあまり得意ではありません。特にデータフレームから一行ずつSeriesを作成します。それを関数に渡す処理は問題を含んでいました。ここで非常に大きなオーバーヘッドが発生するのです。これが数万回や数十万回と繰り返されることになります。一回あたりはわずかな時間でも、回数が桁違いに増えれば、その分だけ待ち時間も膨らんでしまうのです。せっかく高速なライブラリを使っているのに残念なことですね。中身は手書きのループと変わらない状態でした。

データ一括処理「ベクトル化

これに対してpandasやNumPyが得意な分野があります。それはベクトル化と呼ばれる処理です。これはデータをバラバラに処理するわけではありません。列全体をひとつの塊として一気に計算する手法を指します。こうした考え方は、独学だとなかなか気づきにくいところでもあります。

ここで一度立ち止まって考えてみてください

Pythonや自動化スキルを体系的に習得して、ITエンジニアとしてのキャリアを切り開きたい方には「Enjoy Tech!(エンジョイテック)」が選択肢のひとつです。現役エンジニアのサポートで、未経験から実践的なスキルを身につけられます。

プログラミングスクール Enjoy Tech!(エンジョイテック) →

np.whereでapplyの遅さを爆速解消

applyの遅さを解消する強力な武器が存在しますね。そのひとつがNumPyのnp.whereです。これは条件を満たせばAでそれ以外はBという処理を行ってくれます。これを列全体に対して一括で行うための関数ですね。たとえば売上金額が1万円以上なら高単価とします。そうでなければ通常というラベルを付けるとしましょう。applyなら関数を定義して一行ずつ判定します。しかしnp.whereなら一行のコードで済んでしまうのです。

np.whereを用いた書き方はとても短いですね。条件と結果の文字を記述するだけで処理できます。これですべての行に対する判定が一瞬で完了する仕組みですね。これはPythonのループを使わない処理方法となります。メモリ上のデータを一括で操作しているため実現できるのですね。初めてこの書き方を試したときのことを振り返ってみましょう。私はあまりの速さに本当に正しく計算されているのか疑いました。そのように驚いてしまったほどだったのです。

`np.where`、体感ゼロの高速化

今まで関数を定義してapplyで回していた条件判定がありました。これをnp.whereに書き換えて実行してみたのです。エンターキーを押した瞬間に処理が完了しました。出力結果が表示されたときは自分の目を疑ったほどです。以前は席を離れて待つほどだった処理が、コードを数行書き換えるだけでほぼ体感時間ゼロになったのですから、驚くのも無理はないと思います。

日本語の文字が含まれるPythonコードの比較画面です。左側に長いapplyの関

np.whereの多段階条件の課題とnp.select

もし条件が2つだけならnp.whereで十分です。しかし実務ではもっと複雑なケースも多いですよね。5万円以上ならAで3万円以上ならBとします。さらに1万円以上ならCでそれ以外はDという具合です。といった多段階の条件分岐が存在するはずですね。これをnp.whereで書こうとすると問題が起きます。入れ子構造が深くなりすぎてしまうのです。その結果コードが読みにくくなってしまうでしょう。そんなときに便利な関数が用意されています。同じNumPyライブラリに含まれるnp.selectという関数です。

np.selectは条件のリストを作成します。さらにそれに対応する値のリストも用意するのです。これらを別々に渡す仕組みになっていました。これにより複雑なロジックをきれいに整理できます。非常に見通しの良い形でベクトル化できるのです。具体的には条件リストに条件式を順番に並べてみましょう。そして選択肢リストにランクAなどのラベルを並べていく形です。

顧客ランク修正を容易にするリスト管理

複雑な顧客ランク付けのロジックを書き換えたことがあります。その際条件リストと値リストの個数が合わず、エラーを出してしまった経験もあります。最初は戸惑いましたが、リストの形式で条件を管理できるのは便利でした。後からランクEを追加してと言われたときも、リストに一行付け加えるだけで対応できて、その修正の楽さに驚いたのを覚えています。

日本語で条件リストなどのコメントが書かれています。np.selectを利用したコ

apply高速化の秘策 raw=True

ここまでベクトル化のメリットをお伝えしてきました。しかしどうしてもapplyを使う場面もあります。たとえば特殊な外部ライブラリの関数を適用する場合です。または列全体の演算として表現できないロジックがありました。このような複雑なケースが該当するでしょう。そんなときでも、諦めるのはまだ早いです。applyの引数にraw=Trueを追加してみてください。これだけで処理を少し軽くできる可能性があります。

pandas apply高速化とraw=Trueの選択

通常apply(axis=1)で行を処理するとします。するとpandasは各行をSeriesに変換するのです。この便利なオブジェクトに関数を渡していました。このSeriesへの変換処理が、実はかなりの時間を食っているわけです。ここでraw=Trueを指定してみましょう。pandasはSeriesを使用しなくなります。シンプルなNumPy配列としてデータを渡してくれるのですね。NumPy配列は余計な機能が削ぎ落とされていました。その分データの受け渡しにかかる負荷が減ります。こうして無駄なオーバーヘッドが削減されるのです。

関数の中でのデータへのアクセス方法が変わります。列名のように名前で指定できなくなるでしょう。代わりにインデックス番号で指定する必要が出てきます。少しコードの書きやすさは犠牲になりますね。しかし実行時間が大きく改善されます。3割から5割ほど短縮されることも珍しくありません。

計測で実感するコード高速化の圧倒的効果

改善策を試したら結果を確認しましょう。実際にどれくらい速くなったのかを数値で確認するのが一番です。自分の書いたコードが目に見えて速くなるのをご実感いただけます。これはプログラミングをしていて楽しい瞬間と言えますね。最もやりがいを感じる瞬間のひとつでもあります。Pythonではtimeモジュールを使うことが可能でした。Jupyter Notebookのマジックコマンドも便利でしょう。これらを使うことで簡単に処理時間を計測することができます。

apply/np.whereの速度差と計測による最適化

私が以前計測したときの結果をお伝えします。10万件のデータに対する条件判定を行いました。applyはおよそ8秒かかっていたのに対し、大きな差が出ます。np.whereは0.1秒に満たない結果が出ました。数値で見るとその差は数十倍に達します。場合によっては数百倍に達することもありますね。この圧倒的な差を知ってしまうと意識が変わるでしょう。もう安易にapplyを使おうとは思わなくなるはずです。計測結果を上司やチームメンバーに見せてみましょう。そうすればコード改善の価値を客観的に伝えることもできますね。

日本語インターフェースのグラフ描画ライブラリで作成されました。処理時間の比較を示

また計測を習慣にすると良いことがあります。どの処理がボトルネックなのか分かるのですね。状況を冷静に判断できるようになるはずです。

pandas処理の最適化順序

最後にチェックすべき順番を整理しておきましょう。pandasの処理が遅いと感じたときに役立ちます。

まず確認したいのは、列同士の単純な計算で済まないかという点です。df[‘A’] + df[‘B’]のような直接の演算が最も速くかつ読みやすい方法なので、特別な関数を呼び出す前に確認してみてください。

次に検討するのがベクトル化です。今回紹介したnp.whereやnp.selectでif文の条件分岐を置き換えれば、ほとんどの場合はこれで対応できます。データ件数が万単位を超える場合は特に、このステップを飛ばさないようにしましょう。

それでもどうしても独自の関数が必要な場合にのみ、applyを検討してください。その際もraw=Trueオプションや、処理対象データの絞り込みで行数を減らせないか確認したいところです。より速い方法から順番に検討していく癖をつければ、次に同じことで焦る回数はだいぶ減ると思います。

日本語の文字で書かれた高速化チェックリストです。付箋のようにデスクトップの端に貼

Python業務効率化、…

Pythonでの業務効率化はとても強力です。一度コツを掴めばこれほど心強い味方はありません。applyの遅さに悩まされていた日々を乗り越えましょう。まだ試行錯誤の途中ですが、以前よりは落ち着いて対応できるようになりました。今回の内容が、あなたの毎日の業務を少しでも楽にする助けになれば幸いです。

無料プレゼント

Excel業務を自動化する前に確認するチェックリスト(PDF)

自動化していい作業かどうか、VBAかPythonか、最初に避けるべき落とし穴。実務でよく迷うポイントを1枚にまとめました。メールアドレスだけで受け取れます。

無料でチェックリストを受け取る

¥980 ミニキット

コピペで動かせる3スクリプト+自動化チェックリスト

最新ファイルの自動選択・部署名ゆれの正規化・CSV文字コード確認の3本セット。今週の作業を1つだけ楽にするための最小キットです。

ミニキットを見る(¥980)

関連リンクとチェックリスト

関連書籍

Python・pandas関連書籍を、手元で見返せる形にしておきたい場合

pandasのエラー対応は、似た症状を何度も見返すことが多いです。データ前処理や型変換を手元で確認できる本を1冊置いておくと、調べ直しの時間を減らしやすいです。

Python・pandas関連書籍をAmazonで探す

学習サービスとアンケート

このスキルを活かしてさらに前へ進むなら

PythonやExcel自動化スキルを持ったまま、ITエンジニアとして転職したい方には「EBAエデュケーション」が選択肢です。企業が求めるエンジニア像に合わせたカリキュラムで、実務直結のスキルを習得できます。

ITエンジニア転職・EBAエデュケーション →

[アンケート] この記事は役に立ちましたか?


1問だけ回答する