pandasのDataFrameのそれぞれの列について最頻値を計算する

Table of Contents

  1. DataFrameで列ごとに最頻値を計算する
    1. 結論
    2. 準備
    3. df.mode()
    4. df.mode() (categoricalデータに変換)
    5. scipy.stats.mode()
    6. statistics.mode()
    7. NumPy
    8. collection.Counter
  2. おまけ

DataFrameで列ごとに最頻値を計算する

というわけで測ってみました。
計算環境は

  OS: macOS (x86_64-apple-darwin18.7.0)
  CPU: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz

で、Pythonとパッケージのバージョンは

 python: 3.8.5
 numpy: 1.19.2
 pandas: 1.1.2
 scipy: 1.5.2

です。

結論

scipy.stats.mode()が速そう。df.mode()はとても遅いので注意。

準備

import numpy as np
import pandas as pd
import statistics
from collections import Counter
import scipy.stats as stats

今回使うデータフレーム

nrow = 100
ncol = 400_000
df = pd.DataFrame(np.random.choice([-1,0,1],(nrow,ncol)))

df.mode()

2分15秒。遅いですね。ここからスタートです。

%time df.mode().iloc[0]

CPU times: user 2min 15s, sys: 1.53 s, total: 2min 17s
Wall time: 2min 18s

0        -1.0
1        -1.0
2         1.0
3         0.0
4         0.0
         ... 
399995    0.0
399996    0.0
399997    1.0
399998   -1.0
399999   -1.0
Name: 0, Length: 400000, dtype: float64

df.mode() (categoricalデータに変換)

pandasのコードを読む限りcategoricalだと速くなるとのこと。ただし、残念ながら今回のケースでは逆に遅くなりました。。。2分29秒。

dfcat = df.astype("category")
%time dfcat.mode().iloc[0]

CPU times: user 2min 29s, sys: 1.73 s, total: 2min 31s
Wall time: 2min 32s

0        -1
1        -1
2         1
3         0
4         0
         ..
399995    0
399996    0
399997    1
399998   -1
399999   -1
Name: 0, Length: 400000, dtype: category
Categories (3, int64): [-1, 0, 1]

scipy.stats.mode()

冒頭の結論にも書きましたが、これが一番速そうです。11秒。

%time stats.mode(df.values, axis=0).mode

CPU times: user 10.8 s, sys: 87.2 ms, total: 10.9 s
Wall time: 11 s
array([[-1, -1,  1, ...,  1, -1, -1]])

statistics.mode()

Pythonの標準ライブラリのstatisticsです。15秒。SciPyをインストールしたくない場合これがいいかもしれません。

%time df.apply(statistics.mode, axis=0)

CPU times: user 14.8 s, sys: 96.2 ms, total: 14.9 s
Wall time: 15 s

0        -1
1        -1
2         1
3         0
4         0
         ..
399995    0
399996    0
399997    1
399998   -1
399999   -1
Length: 400000, dtype: int64

NumPy

NumPyにはmode関数がないのでunique()とargmax()を組み合わせて自分で作ります。
22秒。

def numpymode(arr):
    unq = np.unique(arr, return_counts=True)
    return unq[0][unq[1].argmax()]

%time df.apply(numpymode, axis=0)

CPU times: user 23.6 s, sys: 177 ms, total: 23.7 s
Wall time: 23.9 s

0        -1
1        -1
2         1
3         0
4         0
         ..
399995    0
399996    0
399997    1
399998   -1
399999   -1
Length: 400000, dtype: int64

collection.Counter

標準ライブラリcollectionからCounterを使用。15秒。

%time df.apply(lambda sr: Counter(sr).most_common()[0][0], axis=0)

CPU times: user 17.2 s, sys: 116 ms, total: 17.3 s
Wall time: 17.6 s

0        -1
1        -1
2         1
3         0
4         0
         ..
399995    0
399996    0
399997    1
399998   -1
399999   -1
Length: 400000, dtype: int64

おまけ

Juliaでもやってみましょう。
mode関数はJuliaの標準ライブラリには無いのでStatsBase.jlを使います。ちなみに下記コード中のsample関数もStatsBase.jlのものです。

using DataFrames
using StatsBase
nrow = 100
ncol = 400_000
df = DataFrame(sample([-1,0,1], (nrow, ncol)))
first(df, 5)  # データ形式の確認

5×400000 DataFrame. Omitted printing of 399991 columns
│ Row │ x1    │ x2    │ x3    │ x4    │ x5    │ x6    │ x7    │ x8    │ x9    │
│     │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │
├─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤
│ 1   │ -1    │ 0     │ 1     │ 1     │ 1     │ -1    │ 1     │ -1    │ -1    │
│ 2   │ 0     │ 1     │ 1     │ -1    │ 0     │ 1     │ 0     │ 0     │ 1     │
│ 3   │ 1     │ 1     │ 0     │ -1    │ 1     │ -1    │ 0     │ 0     │ 0     │
│ 4   │ -1    │ 0     │ 0     │ 1     │ 1     │ 0     │ -1    │ -1    │ 0     │
│ 5   │ 0     │ 0     │ -1    │ -1    │ 1     │ -1    │ 0     │ -1    │ 1     │

@time mapcols(mode, df)

  0.919510 seconds (2.40 M allocations: 333.428 MiB, 4.83% gc time)
1×400000 DataFrame. Omitted printing of 399991 columns
│ Row │ x1    │ x2    │ x3    │ x4    │ x5    │ x6    │ x7    │ x8    │ x9    │
│     │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │
├─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤
│ 1   │ -1    │ 0     │ 1     │ -1    │ 1     │ 0     │ 0     │ -1    │ 1     │

約1秒。さすがに速いですね。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です