pythonパンダのgroupby
の時間を改善したい。次のコードがあります:
df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len)
目的は、クライアントが1か月に締結している契約の数をカウントし、この情報を新しい列(Nbcontrats
)に追加することです。
Client
:クライアントコードMonth
:データ抽出の月Contrat
:契約番号時間を改善したい。以下では、実際のデータのサブセットのみを使用しています。
%timeit df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len)
1 loops, best of 3: 391 ms per loop
df.shape
Out[309]: (7464, 61)
どうすれば実行時間を改善できますか?
とともに DataFrameGroupBy.size
方法:
df.set_index(['Client', 'Month'], inplace=True)
df['Nbcontrats'] = df.groupby(level=(0,1)).size()
df.reset_index(inplace=True)
ほとんどの作業は、結果をソースDataFrameの列に割り当てることです。
続行する1つの方法は次のとおりです。
関連する列(['Client', 'Month']
)を入力データフレームからNumPy配列にスライスします。後でNumPy配列で動作するように最適化されたNumPy関数を使用するため、これは主にパフォーマンス重視のアイデアです。
2つの列のデータを['Client', 'Month']
から単一の1D
配列に変換します。これは、2つの列の要素をペアと見なした場合と同等の線形インデックスになります。したがって、'Client'
の要素は行のインデックスを表し、'Month'
の要素は列のインデックスであると想定できます。これは、2D
から1D
に移動するようなものです。しかし、問題は、そのようなマッピングを実行するための2Dグリッドの形状を決定することです。すべてのペアをカバーするために、1つの安全な仮定は、Pythonの0ベースのインデックス付けのために、各列に沿った最大値より1つ大きい寸法の2Dグリッドを仮定することです。したがって、線形インデックスを取得します。
次に、とりわけ一意性に基づいて、各線形インデックスにタグを付けます。これは、代わりにgrouby
で取得されたキーに対応すると思います。また、その1D配列の全長に沿って各グループ/一意キーの数を取得する必要があります。最後に、これらのタグを使用してカウントにインデックスを付けると、要素ごとにそれぞれのカウントがマッピングされます。
それはそれについての全体的な考えです!これが実装です-
# Save relevant columns as a NumPy array for performing NumPy operations afterwards
arr_slice = df[['Client', 'Month']].values
# Get linear indices equivalent of those columns
lidx = np.ravel_multi_index(arr_slice.T,arr_slice.max(0)+1)
# Get unique IDs corresponding to each linear index (i.e. group) and grouped counts
unq,unqtags,counts = np.unique(lidx,return_inverse=True,return_counts=True)
# Index counts with the unique tags to map across all elements with the counts
df["Nbcontrats"] = counts[unqtags]
ランタイムテスト
1)関数を定義する:
def original_app(df):
df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len)
def vectorized_app(df):
arr_slice = df[['Client', 'Month']].values
lidx = np.ravel_multi_index(arr_slice.T,arr_slice.max(0)+1)
unq,unqtags,counts = np.unique(lidx,return_inverse=True,return_counts=True)
df["Nbcontrats"] = counts[unqtags]
2)結果を確認します:
In [143]: # Let's create a dataframe with 100 unique IDs and of length 10000
...: arr = np.random.randint(0,100,(10000,3))
...: df = pd.DataFrame(arr,columns=['Client','Month','Contrat'])
...: df1 = df.copy()
...:
...: # Run the function on the inputs
...: original_app(df)
...: vectorized_app(df1)
...:
In [144]: np.allclose(df["Nbcontrats"],df1["Nbcontrats"])
Out[144]: True
3)最後にそれらの時間を計ります:
In [145]: # Let's create a dataframe with 100 unique IDs and of length 10000
...: arr = np.random.randint(0,100,(10000,3))
...: df = pd.DataFrame(arr,columns=['Client','Month','Contrat'])
...: df1 = df.copy()
...:
In [146]: %timeit original_app(df)
1 loops, best of 3: 645 ms per loop
In [147]: %timeit vectorized_app(df1)
100 loops, best of 3: 2.62 ms per loop