web-dev-qa-db-ja.com

python dask DataFrame、(簡単に並列化可能な)行のサポートが適用されますか?

最近、 dask モジュールを見つけました。これは、使いやすいpython並列処理モジュールです。大きなセールスポイントです。私にとっては、パンダで動作するということです。

マニュアルページを少し読んだ後、この簡単に並列化できるタスクを実行する方法が見つかりません。

ts.apply(func) # for pandas series
df.apply(func, axis = 1) # for pandas DF row apply

現時点では、これを暗闇の中で達成するために、

ddf.assign(A=lambda df: df.apply(func, axis=1)).compute() # dask DataFrame

これはsyntaxい構文であり、実際には完全に遅いです

df.apply(func, axis = 1) # for pandas DF row apply

なにか提案を?

編集:マップ機能を@MRocklinに感謝します。それは普通のpandas適用よりも遅いようです。これはpandas GILリリース問題に関連していますか、それとも間違っていますか?

import dask.dataframe as dd
s = pd.Series([10000]*120)
ds = dd.from_pandas(s, npartitions = 3)

def slow_func(k):
    A = np.random.normal(size = k) # k = 10000
    s = 0
    for a in A:
        if a > 0:
            s += 1
        else:
            s -= 1
    return s

s.apply(slow_func) # 0.43 sec
ds.map(slow_func).compute() # 2.04 sec
34
jf328

map_partitions

関数をデータフレームのすべてのパーティションに適用するには、map_partitions 関数。

df.map_partitions(func, columns=...)

Funcには一度にデータセットの一部のみが与えられ、pandas apply(これは、おそらく並列処理を行いたい場合には望まないでしょう。)

map/apply

mapを使用して、一連の関数を行単位でマッピングできます。

df.mycolumn.map(func)

applyを使用して、データフレーム全体で関数を行ごとにマッピングできます

df.apply(func, axis=1)

スレッドとプロセス

バージョン0.6.0以降dask.dataframesはスレッドと並列化します。カスタムPython関数はスレッドベースの並列処理のメリットをあまり受けません。代わりにプロセスを試すことができます

df = dd.read_csv(...)

df.map_partitions(func, columns=...).compute(scheduler='processes')

ただし、applyは避けてください

ただし、カスタムのPython関数、PandasとDaskの両方で)を使用してapplyを実際に避ける必要があります。これは多くの場合、パフォーマンスの低下の原因です。 。ベクトル化された方法で操作を行う方法を見つけた場合、Pandasコードが100倍高速になり、dask.dataframeがまったく不要になる可能性があります。

numbaを検討する

特定の問題については、 numba を検討してください。これにより、パフォーマンスが大幅に向上します。

In [1]: import numpy as np
In [2]: import pandas as pd
In [3]: s = pd.Series([10000]*120)

In [4]: %paste
def slow_func(k):
    A = np.random.normal(size = k) # k = 10000
    s = 0
    for a in A:
        if a > 0:
            s += 1
        else:
            s -= 1
    return s
## -- End pasted text --

In [5]: %time _ = s.apply(slow_func)
CPU times: user 345 ms, sys: 3.28 ms, total: 348 ms
Wall time: 347 ms

In [6]: import numba
In [7]: fast_func = numba.jit(slow_func)

In [8]: %time _ = s.apply(fast_func)  # First time incurs compilation overhead
CPU times: user 179 ms, sys: 0 ns, total: 179 ms
Wall time: 175 ms

In [9]: %time _ = s.apply(fast_func)  # Subsequent times are all gain
CPU times: user 68.8 ms, sys: 27 µs, total: 68.8 ms
Wall time: 68.7 ms

免責事項、私はnumbadaskの両方を作成し、多くのpandas開発者を雇用している会社で働いています。

55
MRocklin

V dask.dataframe。applyは、map_partitions

@insert_meta_param_description(pad=12)
def apply(self, func, convert_dtype=True, meta=no_default, args=(), **kwds):
    """ Parallel version of pandas.Series.apply
    ...
    """
    if meta is no_default:
        msg = ("`meta` is not specified, inferred from partial data. "
               "Please provide `meta` if the result is unexpected.\n"
               "  Before: .apply(func)\n"
               "  After:  .apply(func, meta={'x': 'f8', 'y': 'f8'}) for dataframe result\n"
               "  or:     .apply(func, meta=('x', 'f8'))            for series result")
        warnings.warn(msg)

        meta = _emulate(M.apply, self._meta_nonempty, func,
                        convert_dtype=convert_dtype,
                        args=args, **kwds)

    return map_partitions(M.apply, self, func,
                          convert_dtype, args, meta=meta, **kwds)
2