😍

1000倍の速度差が出てしまうコードの違い - Python PandasとJulia Dataframesの例

2022/03/26に公開

Python Pandasを使われている方々にとってみれば既に知っている方法だとは思いますが,効率のいい反復処理のやり方が紹介されていたのをたまたまみつけたので,同じことをJuliaでも試してみてPython Pandasの一番速い方法と比べてみました。

元記事はこちらです。Python Pandasのみの結果です。
https://mlabonne.github.io/blog/iterating-over-rows-pandas-dataframe/

ここで紹介されていたのは2つの列(src_bytes, dst_bytes)を加算する速度がやり方によって大きく変わることです。

  1. Iterrows
  2. For loop with .loc or .iloc
  3. Apply
  4. Itertuples
  5. List comprehensions
  6. Pandas vectorization
  7. NumPy vectorization

既にご存知の方も多いと思いますが、私の環境で(Mac mini M1 8GB, Python 3.9.7, Pandas 1.4.1)記事同様に7つの方法をやってみました。解説は元記事でされているので詳細は省いてコードと結果だけを表示します。

Python Pandas

今回使用するデータ

import pandas as pd
import numpy as np

df = pd.read_csv('https://raw.githubusercontent.com/mlabonne/how-to-data-science/main/data/nslkdd_test.txt')

Iterrows

%%timeit -n 10
total = []
for index, row in df.iterrows():
    total.append(row['src_bytes'] + row['dst_bytes'])

iterrows

For loop with .loc

%%timeit -n 10
total = []
for index in range(len(df)):
    total.append(df['src_bytes'].loc[index] + df['dst_bytes'].loc[index])

Apply

%%timeit -n 10
df.apply(lambda row: row['src_bytes'] + row['dst_bytes'], axis=1).to_list()

Itertuples

%%timeit -n 10
total = []
for row in df.itertuples():
    total.append(row[5] + row[6])

List comprehension

%%timeit -n 100
[src + dst for src, dst in zip(df['src_bytes'], df['dst_bytes'])]

Pandas vectorization

%%timeit -n 1000
(df['src_bytes'] + df['dst_bytes']).to_list()

NumPy vectorization

%%timeit -n 1000
(df['src_bytes'].to_numpy() + df['dst_bytes'].to_numpy()).tolist()

元記事にも書いてありますが、私の環境でも442msが433μsと1000倍も短縮されました。結論から言えば、List Comprehensionを使うようにし、出来るならVecotrization(ベクトル化)しろって事ですね。


Julia DataFrames

ふと同じことをJuliaでやったらどうなるかと思い試してみました。同じ環境(Mac mini M1 8GB, Julia 1.6.5, DataFrames 1.3.2)で同じデータでの比較です。

using DataFrames
using CSV
using HTTP
using BenchmarkTools

url = "https://raw.githubusercontent.com/mlabonne/how-to-data-science/main/data/nslkdd_test.txt"
df = CSV.File(HTTP.get(url).body) |> DataFrame

Eachrow (= iterrows)

total=[]
@benchmark for row in eachrow(df)
    push!(total, row["src_bytes"].+row["dst_bytes"])
end

中央値をとれば22msです

List comprehension

@benchmark [(src+dst) for (src, dst) in zip(df[!,"src_bytes"],df[!,"dst_bytes"])]

Julia way

@benchmark [df[!,"src_bytes"]+df[!,"dst_bytes"]]

最終的に22msから8.9μsまで1000倍以上短縮されました。

Julia DataFramesでもPython Pandasでも、やり方次第で1000倍も違うとは、本当に知っておかないともったいないですね

まとめ

言語 方法 時間
Python Iterrows 442 ms
Python For loop with .loc or .iloc 217 ms
Python Apply 163 ms
Python Itertuples 64.4 ms
Python List comprehension 3.72 ms
Python Pandas vectorization 466 μs
Python NumPy vectorization 433 μs
Julia Eachrow 22.6 ms
Julia List comprehension 13.1 μs
Julia Julia Dataframe 8.9 μs

Discussion