非常に大きな(混合データ型の)DataFrame
のグループに関数を適用する必要があり、複数のコアを利用したい場合があります。
グループからイテレータを作成してマルチプロセッシングモジュールを使用できますが、すべてのグループと関数の結果をプロセス間のメッセージングのためにピクルする必要があるため、効率的ではありません。
酸洗いを回避したり、DataFrame
のコピーを完全に回避したりする方法はありますか?マルチプロセッシングモジュールの共有メモリ関数はnumpy
配列に制限されているようです。他のオプションはありますか?
上記のコメントから、これはpandas
で計画されているようです(興味深い外観の rosetta
プロジェクト もありました)。
ただし、すべての並列機能がpandas
に組み込まれるまで、 pandas
+ OpenMP を使用してcython
に効率的でメモリをコピーしない並列拡張を直接書き込むのは非常に簡単であることに気付きましたC++。
並列groupby-sumを作成する短い例を次に示します。この使用法は次のようなものです。
import pandas as pd
import para_group_demo
df = pd.DataFrame({'a': [1, 2, 1, 2, 1, 1, 0], 'b': range(7)})
print para_group_demo.sum(df.a, df.b)
そして出力は:
sum
key
0 6
1 11
2 4
注間違いなく、この単純な例の機能は最終的にpandas
の一部になるでしょう。ただし、C++でしばらく並列化する方が自然な場合もあります。これをpandas
に組み合わせるのがいかに簡単かを認識することが重要です。
これを行うために、コードが続く単純な単一ソースファイル拡張を作成しました。
それはいくつかのインポートとタイプ定義から始まります
from libc.stdint cimport int64_t, uint64_t
from libcpp.vector cimport vector
from libcpp.unordered_map cimport unordered_map
cimport cython
from cython.operator cimport dereference as deref, preincrement as inc
from cython.parallel import prange
import pandas as pd
ctypedef unordered_map[int64_t, uint64_t] counts_t
ctypedef unordered_map[int64_t, uint64_t].iterator counts_it_t
ctypedef vector[counts_t] counts_vec_t
C++ unordered_map
タイプは単一のスレッドによる合計用であり、vector
はすべてのスレッドによる合計用です。
次に、関数sum
に移ります。高速アクセスのために 型付きメモリビュー で始まります。
def sum(crit, vals):
cdef int64_t[:] crit_view = crit.values
cdef int64_t[:] vals_view = vals.values
関数は、スレッド(ここでは4にハードコードされています)に半等しく分割し、各スレッドにその範囲のエントリを合計させることで続行します。
cdef uint64_t num_threads = 4
cdef uint64_t l = len(crit)
cdef uint64_t s = l / num_threads + 1
cdef uint64_t i, j, e
cdef counts_vec_t counts
counts = counts_vec_t(num_threads)
counts.resize(num_threads)
with cython.boundscheck(False):
for i in prange(num_threads, nogil=True):
j = i * s
e = j + s
if e > l:
e = l
while j < e:
counts[i][crit_view[j]] += vals_view[j]
inc(j)
スレッドが完了すると、関数は(異なる範囲からの)すべての結果を1つのunordered_map
にマージします。
cdef counts_t total
cdef counts_it_t it, e_it
for i in range(num_threads):
it = counts[i].begin()
e_it = counts[i].end()
while it != e_it:
total[deref(it).first] += deref(it).second
inc(it)
あとは、DataFrame
を作成して結果を返すだけです。
key, sum_ = [], []
it = total.begin()
e_it = total.end()
while it != e_it:
key.append(deref(it).first)
sum_.append(deref(it).second)
inc(it)
df = pd.DataFrame({'key': key, 'sum': sum_})
df.set_index('key', inplace=True)
return df