pandas、numpy、tensorの処理スピードを比較してみた
(2021/3/21 追記)こちらの記事を拝見し、GPU使用時の時間の計測方法を修整しました。
併せてCPU使用時の時間もその際のものに訂正しています。
今回は pandas(DataFrame)、numpy(nddaray)、tensor(GPUなし/あり)でどのくらい処理スピードが異なるのか比較してみました。
ECサイトのデータセットを数値データのみに加工して使用しています。
環境はGoogle Colab、GPUは Tesla T4、条件統一のため型はすべてfloat64とし、tensor化にはpytorchを使用しています。
コードはこちら。
CustomerID、InvoiceNoでfor分を回し、各CustomerID、InvoiceNoごとの売上 UnitPrice × Quantity を計算して辞書型として保存する処理をおこなっています。
pandas、numpy、tensorの違い
一般的にPandasはindexやcolumns名を保持させたりと扱えるデータの幅が広いのですが、処理が遅い傾向にあるようです。
一方、numpyは関数呼び出しのオーバーヘッドが発生しない分、処理が早い傾向にあるようです。
tensorはnumpyとよく似ているが、GPUを演算に使用できるようです。
pandasのデータセットと処理
for i in tqdm(df["CustomerID"].unique()[~np.isnan(df["CustomerID"].unique())]):
for j in df[df["CustomerID"]==i]["InvoiceNo"].unique():
sale_dict[f"{i}_{j}_sale"] = df[(df["CustomerID"]==i) & (df["InvoiceNo"]==j)]["Quantity"] * df[(df["CustomerID"]==i) & (df["InvoiceNo"]==j)]["UnitPrice"]
numpyのデータセットと処理
array([[5.36365e+05, 1.78500e+04, 2.55000e+00, 6.00000e+00],
[5.36365e+05, 1.78500e+04, 3.39000e+00, 6.00000e+00],
[5.36365e+05, 1.78500e+04, 2.75000e+00, 8.00000e+00],
...,
[5.81587e+05, 1.26800e+04, 4.15000e+00, 4.00000e+00],
[5.81587e+05, 1.26800e+04, 4.15000e+00, 4.00000e+00],
[5.81587e+05, 1.26800e+04, 4.95000e+00, 3.00000e+00]])
for i in tqdm(np.unique(df_np[:, 1][~np.isnan(df_np[:, 1])])):
for j in np.unique(df_np[df_np[:, 1]==i][:, 0]):
sale_dict[f"{i}_{j}_sale"] = df_np[(df_np[:, 1]==i) & (df_np[:, 0]==j)][:, 3] * df_np[(df_np[:, 1]==i) & (df_np[:, 0]==j)][:, 2]
tensor(GPUなし)のデータセットと処理
tensor([[5.3636e+05, 1.7850e+04, 2.5500e+00, 6.0000e+00],
[5.3636e+05, 1.7850e+04, 3.3900e+00, 6.0000e+00],
[5.3636e+05, 1.7850e+04, 2.7500e+00, 8.0000e+00],
...,
[5.8159e+05, 1.2680e+04, 4.1500e+00, 4.0000e+00],
[5.8159e+05, 1.2680e+04, 4.1500e+00, 4.0000e+00],
[5.8159e+05, 1.2680e+04, 4.9500e+00, 3.0000e+00]], dtype=torch.float64)
for i in tqdm(df_tensor[:, 1][~torch.isnan(df_tensor[:, 1])].unique()):
for j in df_tensor[df_tensor[:, 1]==i][:, 0].unique():
sale_dict[f"{i}_{j}_sale"] = df_tensor[(df_tensor[:, 1]==i) & (df_tensor[:, 0]==j)][:, 3] * df_tensor[(df_tensor[:, 1]==i) & (df_tensor[:, 0]==j)][:, 2]
tensor(GPUあり)のデータセットと処理
tensor([[5.3636e+05, 1.7850e+04, 2.5500e+00, 6.0000e+00],
[5.3636e+05, 1.7850e+04, 3.3900e+00, 6.0000e+00],
[5.3636e+05, 1.7850e+04, 2.7500e+00, 8.0000e+00],
...,
[5.8159e+05, 1.2680e+04, 4.1500e+00, 4.0000e+00],
[5.8159e+05, 1.2680e+04, 4.1500e+00, 4.0000e+00],
[5.8159e+05, 1.2680e+04, 4.9500e+00, 3.0000e+00]], device='cuda:0',
dtype=torch.float64)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
df_tensor = df_tensor.to(device)
# 以下はGPUなしと共通
for i in tqdm(df_tensor[:, 1][~torch.isnan(df_tensor[:, 1])].unique()):
for j in df_tensor[df_tensor[:, 1]==i][:, 0].unique():
sale_dict[f"{i}_{j}_sale"] = df_tensor[(df_tensor[:, 1]==i) & (df_tensor[:, 0]==j)][:, 3] * df_tensor[(df_tensor[:, 1]==i) & (df_tensor[:, 0]==j)][:, 2]
まとめ
結果、tensor(GPU)が圧倒的に処理が早くなりました。
また、今回の処理ではnumpyに比べpandasのほうが処理が早くなりました。
pandas | numpy | tensor(GPUなし) | tensor(GPUあり修正前) | tensor(GPUあり修正後) | |
---|---|---|---|---|---|
処理時間 | N/A | ||||
処理時間(再検証) | 約118秒 | 約154秒 | 約177秒 | 約7.8秒 | 約7.7秒 |
(2021/3/21 追記)Google Colab 上でやっているせいか、データセットに対する処理の負荷のせいなのか、それほど変わらなかったようです。
今後も色んな条件で試してみようと思います。
参考 pandas、numpy、tensor 処理の違い
今回、pandas、numpy、tensorで同じ処理を行いましたが、それぞれのコードの違いを比較しました。
# 各列のユニーク値の取得
df["xxx"].unique() # pandas、NaNが複数あっても1つとしてカウント
np.unique(df[:, x]) # numpy、NaNが複数あると個別にカウントされる
df[:, x].unique() # tensor、NaNが複数あると個別にカウントされる
# NaNを除外するためのbool判定
~df["xxx"].isnull() # pandas
~np.isnan(df[:, x]) # numpy
~torch.isnan(df[:, x]) # tensor
今回のブログには使用していませんが、配列と同じ項目が含まれているかどうかの判定もご紹介します。
Pytorchにはisin()が存在しないようなので、少し複雑な処理が必要のようです。
df["xxx"].isin([配列]) # pandas
np.isin(df[:, x], [配列]) # numpy
(df[:, x, None] == torch.tensor([配列])).any(-1) # tensor
以上になります、最後までお読みいただきありがとうございました。
Discussion