web-dev-qa-db-ja.com

pandas groupby操作を実行するためのより高速な代替手段

名前(person_name)、曜日、色(shirt_color)を列とするデータセットがあります。

一人一人が特定の日に特定の色のシャツを着ています。日数は任意です。

例えば。入力:

name  day  color
----------------
John   1   White
John   2   White
John   3   Blue
John   4   Blue
John   5   White
Tom    2   White
Tom    3   Blue
Tom    4   Blue
Tom    5   Black
Jerry  1   Black
Jerry  2   Black
Jerry  4   Black
Jerry  5   White

各人が最もよく使用する色を見つける必要があります。

例えば。結果:

name    color
-------------
Jerry   Black
John    White
Tom     Blue

結果を取得するために次の操作を実行しています。これは正常に動作しますが、非常に低速です。

most_frquent_list = [[name, group.color.mode()[0]] 
                        for name, group in data.groupby('name')]
most_frquent_df = pd.DataFrame(most_frquent_list, columns=['name', 'color'])

ここで、500万の一意の名前を持つデータセットがあるとします。上記の操作を実行するための最良/最速の方法は何ですか?

6
astrobiologist

Numpyの_numpy.add.at_および_pandas.factorize_

これは高速であることを目的としています。ただし、読みやすいように整理しようとしました。

_i, r = pd.factorize(df.name)
j, c = pd.factorize(df.color)
n, m = len(r), len(c)

b = np.zeros((n, m), dtype=np.int64)

np.add.at(b, (i, j), 1)
pd.Series(c[b.argmax(1)], r)

John     White
Tom       Blue
Jerry    Black
dtype: object
_

groupbysize、およびidxmax

_df.groupby(['name', 'color']).size().unstack().idxmax(1)

name
Jerry    Black
John     White
Tom       Blue
dtype: object

name
Jerry    Black
John     White
Tom       Blue
Name: color, dtype: object
_

Counter

¯\_(ツ)_/¯

_from collections import Counter

df.groupby('name').color.apply(lambda c: Counter(c).most_common(1)[0][0])

name
Jerry    Black
John     White
Tom       Blue
Name: color, dtype: object
_
6
piRSquared

pd.Series.modeからのソリューション

df.groupby('name').color.apply(pd.Series.mode).reset_index(level=1,drop=True)
Out[281]: 
name
Jerry    Black
John     White
Tom       Blue
Name: color, dtype: object
4
YO and BEN_W

transform(max)で2つのグループ化を行うのはどうですか?

df = df.groupby(["name", "color"], as_index=False, sort=False).count()
idx = df.groupby("name", sort=False).transform(max)["day"] == df["day"]
df = df[idx][["name", "color"]].reset_index(drop=True)

出力:

    name  color
0   John  White
1    Tom   Blue
2  Jerry  Black
2

@piRSquaredの_pd.factorize_および_np.add.at_ ansに似ています。

文字列を列にエンコードします

_i, r = pd.factorize(df.name)
j, c = pd.factorize(df.color)
n, m = len(r), len(c)
b = np.zeros((n, m), dtype=np.int64)
_

しかし、これを行う代わりに:

_np.add.at(b, (i, j), 1)
max_columns_after_add_at = b.argmax(1)
_

Jited関数を使用して_max_columns_after_add_at_を取得し、同じループでat atを追加して最大値を見つけます。

_@nb.jit(nopython=True, cache=True)
def add_at(x, rows, cols, val):
    max_vals = np.zeros((x.shape[0], ), np.int64)
    max_inds = np.zeros((x.shape[0], ), np.int64)
    for i in range(len(rows)):
        r = rows[i]
        c = cols[i]
        x[r, c]+=1
        if(x[r, c] > max_vals[r]):
            max_vals[r] = x[r, c]
            max_inds[r] = c
    return max_inds
_

そして最後にデータフレームを取得し、

_ans = pd.Series(c[max_columns_after_add_at], r)
_

したがって、違いはargmax(axis=1) after np.add.at()の方法です。

タイミング分析

_import numpy as np
import numba as nb
m = 100000
n = 100000
rows = np.random.randint(low = 0, high = m, size=10000)
cols = np.random.randint(low = 0, high = n, size=10000)
_

したがって、この:

_%%time
x = np.zeros((m,n))
np.add.at(x, (rows, cols), 1)
maxs = x.argmax(1)
_

与える:

CPU時間:ユーザー12.4秒、システム:38秒、合計:50.4秒ウォール時間:50.5秒

この

_%%time
x = np.zeros((m,n))
maxs2 = add_at(x, rows, cols, 1)
_

与える

CPU時間:ユーザー108ミリ秒、システム:39.4秒、合計:39.5秒ウォール時間:38.4秒

0
Deepak Saini