2017年8月の時点で、Pandas DataFame.apply()は残念ながらまだシングルコアでの動作に制限されています。つまり、マルチコアマシンは、実行時に計算時間の大部分を浪費しますdf.apply(myfunc, axis=1)
。
すべてのコアを使用して、データフレームに並列で適用を実行するにはどうすればよいですか?
2017年8月の時点で、Pandas DataFame.apply()は残念ながらまだシングルコアでの動作に制限されています。つまり、マルチコアマシンは、実行時に計算時間の大部分を浪費しますdf.apply(myfunc, axis=1)
。
すべてのコアを使用して、データフレームに並列で適用を実行するにはどうすればよいですか?
回答:
swifter
パッケージを使用できます。
pip install swifter
これはパンダのプラグインとしてapply
機能し、関数を再利用できます。
import swifter
def some_function(data):
return data * 10
data['out'] = data['in'].swifter.apply(some_function)
ベクトル化されている(上記の例のように)かどうかに関係なく、関数を並列化する最も効率的な方法を自動的に判断します。
その他の例とパフォーマンスの比較は、GitHubで入手できます。パッケージは開発中のため、APIが変更される可能性があることに注意してください。
また、これは文字列列に対しては自動的に機能しないことにも注意してください。文字列を使用する場合、Swifterは「単純な」パンダapply
にフォールバックしますが、これは並列ではありません。この場合、使用dask
を強制してもパフォーマンスは向上しません。データセットを手動で分割し、を使用multiprocessing
して並列化することをお勧めします。
allow_dask_on_strings(enable=True)
ように追加します。df.swifter.allow_dask_on_strings(enable=True).apply(some_function)
ソース:github.com/jmcarpenter2/swifter/issues/45
最も簡単な方法は、Daskのmap_partitionsを使用することです。これらのインポートが必要です(必要になりますpip install dask
):
import pandas as pd
import dask.dataframe as dd
from dask.multiprocessing import get
そして構文は
data = <your_pandas_dataframe>
ddata = dd.from_pandas(data, npartitions=30)
def myfunc(x,y,z, ...): return <whatever>
res = ddata.map_partitions(lambda df: df.apply((lambda row: myfunc(*row)), axis=1)).compute(get=get)
(16コアの場合、30が適切なパーティション数だと思います)。完全を期すために、私は自分のマシン(16コア)の違いを測定しました。
data = pd.DataFrame()
data['col1'] = np.random.normal(size = 1500000)
data['col2'] = np.random.normal(size = 1500000)
ddata = dd.from_pandas(data, npartitions=30)
def myfunc(x,y): return y*(x**2+1)
def apply_myfunc_to_DF(df): return df.apply((lambda row: myfunc(*row)), axis=1)
def pandas_apply(): return apply_myfunc_to_DF(data)
def dask_apply(): return ddata.map_partitions(apply_myfunc_to_DF).compute(get=get)
def vectorized(): return myfunc(data['col1'], data['col2'] )
t_pds = timeit.Timer(lambda: pandas_apply())
print(t_pds.timeit(number=1))
28.16970546543598
t_dsk = timeit.Timer(lambda: dask_apply())
print(t_dsk.timeit(number=1))
2.708152851089835
t_vec = timeit.Timer(lambda: vectorized())
print(t_vec.timeit(number=1))
0.010668013244867325
寄付10スピードアップの要因パンダから行くと、パーティションに適用DASKに適用されます。もちろん、ベクトル化できる関数がある場合は、そうする必要があります。この場合、関数(y*(x**2+1)
)は簡単にベクトル化されますが、ベクトル化できないものはたくさんあります。
The get= keyword has been deprecated. Please use the scheduler= keyword instead with the name of the desired scheduler like 'threads' or 'processes'
ValueError: cannot reindex from a duplicate axis
。これを回避するには、で重複したインデックスを削除するdf = df[~df.index.duplicated()]
か、でインデックスをリセットする必要がありますdf.reset_index(inplace=True)
。
pandarallel
代わりに試すことができます:すべてのCPUでパンダ操作を並列化するためのシンプルで効率的なツール(LinuxおよびmacOS)
from pandarallel import pandarallel
from math import sin
pandarallel.initialize()
# FORBIDDEN
df.parallel_apply(lambda x: sin(x**2), axis=1)
# ALLOWED
def func(x):
return sin(x**2)
df.parallel_apply(func, axis=1)
ネイティブのPythonを使い続けたい場合:
import multiprocessing as mp
with mp.Pool(mp.cpu_count()) as pool:
df['newcol'] = pool.map(f, df['col'])
関数をデータフレームのf
列col
に並行して適用しますdf
ValueError: Length of values does not match length of index
から取得しまし__setitem__
たpandas/core/frame.py
。私が何か間違ったことをしたのか、またはへの割り当てdf['newcol']
がスレッドセーフではないのかわかりません。
これは、パンダが適用されるsklearnベーストランスフォーマーの例です。
import multiprocessing as mp
from sklearn.base import TransformerMixin, BaseEstimator
class ParllelTransformer(BaseEstimator, TransformerMixin):
def __init__(self,
n_jobs=1):
"""
n_jobs - parallel jobs to run
"""
self.variety = variety
self.user_abbrevs = user_abbrevs
self.n_jobs = n_jobs
def fit(self, X, y=None):
return self
def transform(self, X, *_):
X_copy = X.copy()
cores = mp.cpu_count()
partitions = 1
if self.n_jobs <= -1:
partitions = cores
elif self.n_jobs <= 0:
partitions = 1
else:
partitions = min(self.n_jobs, cores)
if partitions == 1:
# transform sequentially
return X_copy.apply(self._transform_one)
# splitting data into batches
data_split = np.array_split(X_copy, partitions)
pool = mp.Pool(cores)
# Here reduce function - concationation of transformed batches
data = pd.concat(
pool.map(self._preprocess_part, data_split)
)
pool.close()
pool.join()
return data
def _transform_part(self, df_part):
return df_part.apply(self._transform_one)
def _transform_one(self, line):
# some kind of transformations here
return line
詳細については、https: //towardsdatascience.com/4-easy-steps-to-improve-your-machine-learning-code-performance-88a0b0eeffa8を参照してください