グループ化されたパンダのDataFrameに効率的に関数を効率的に適用する


89

DataFrame(混合データ型の)非常に大きなグループに関数を適用する必要があることが多く、複数のコアを利用したいと考えています。

グループからイテレータを作成してマルチプロセッシングモジュールを使用できますが、すべてのグループと関数の結果をプロセス間のメッセージングのためにピクルする必要があるため、効率的ではありません。

酸洗いを回避する方法、またはDataFrame完全なコピーを回避する方法はありますか?マルチプロセッシングモジュールの共有メモリ機能はnumpy配列に限定されているようです。他のオプションはありますか?


私の知る限り、任意のオブジェクトを共有する方法はありません。酸洗いには、マルチプロセッシングによる利益よりもはるかに長い時間がかかるのではないかと思います。おそらく、相対的な酸洗い時間を短縮するために、各プロセスに対してより大きなワークパッケージを作成する可能性を探す必要があります。別の可能性は、グループを作成するときにマルチプロセッシングを使用することです。
Sebastian Werk 2013年

3
私はそのようなことをしますが、UWSGI、Flask、およびプリフォークを使用します。pandasデータフレームをプロセスにロードし、x回フォークして(共有メモリオブジェクトにして)、結果を連結する別のpythonプロセスからそれらのプロセスを呼び出します。atm私はJSONを通信プロセスとして使用していますが、これは来ています(まだ非常に実験的です):pandas.pydata.org/pandas-docs/dev/io.html#msgpack-experimental
Carst

ちなみに、チャンクでHDF5を見たことがありますか?(HDF5は同時書き込み用に保存されませんが、個別のファイルに保存して、最後に連結することもできます)
Carst

7
これは0.14を対象としています。この問題を参照してください:github.com/pydata/pandas/issues/5751
Jeff

4
@Jeffが0.15にプッシュされました=(
pyCthon

回答:


12

上記のコメントから、これはpandasしばらくの間計画されているようです(私が気付いたばかりの興味深い見た目のrosettaプロジェクトもあります)。

ただし、すべての並列機能がに組み込まれるまでpandas、+ OpenMPとC ++ をpandas直接使用して効率的でメモリをコピーしない並列拡張を簡単に作成できることに気付きました。cython

並列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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.