截至2017年8月,不幸的是,Pandas DataFame.apply()仍限于使用单核,这意味着多核计算机在运行时将浪费其大部分计算时间df.apply(myfunc, axis=1)
。
您如何使用所有核心并行运行一个数据帧上的应用程序?
截至2017年8月,不幸的是,Pandas DataFame.apply()仍限于使用单核,这意味着多核计算机在运行时将浪费其大部分计算时间df.apply(myfunc, axis=1)
。
您如何使用所有核心并行运行一个数据帧上的应用程序?
Answers:
您可以使用该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将回退到“简单”的Pandas 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