截至2017年8月,不幸的是,Pandas DataFame.apply()仅限于使用单核,这意味着当您运行df.apply(myfunc, axis=1)
时,多核机器将浪费大部分计算时间。
如何使用所有核心并行运行应用于数据帧?
答案 0 :(得分:63)
最简单的方法是使用Dask's 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
从pandas获得 10倍加速因子适用于分区上的dask应用。当然,如果你有一个可以向量化的函数,你应该 - 在这种情况下函数(y*(x**2+1)
)被简单地向量化,但是有很多东西是不可能矢量化的。
答案 1 :(得分:29)
您可以使用swifter
软件包:
pip install swifter
它作为熊猫的插件,允许您重用apply
函数:
import swifter
def some_function(data):
return data * 10
data['out'] = data['in'].swifter.apply(some_function)
无论是否进行矢量化(如上例所示),它都会自动找出最有效的方法来并行化功能。
More examples和performance comparison在GitHub上可用。请注意,该软件包正在积极开发中,因此API可能会更改。
答案 2 :(得分:8)
您可以尝试使用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)
答案 3 :(得分:3)
只想给Dask一个更新的答案
import dask.dataframe as dd
def your_func(row):
#do something
return row
ddf = dd.from_pandas(df, npartitions=30) # find your own number of partitions
ddf_update = ddf.apply(your_func, axis=1).compute()
在我的 100,000 条记录中,没有 Dask:
CPU 时间:用户 6 分 32 秒,系统:100 毫秒,总计:6 分 32 秒 挂墙时间:6 分 32 秒
使用 Dask:
CPU 时间:用户 5.19 秒,系统:784 毫秒,总计:5.98 秒 挂墙时间:1分3秒
答案 4 :(得分:2)
要使用所有(物理或逻辑)内核,可以尝试使用mapply
作为swifter
和pandarallel
的替代方案。
您可以在初始化时设置核心数量(以及分块行为):
import pandas as pd
import mapply
mapply.init(n_workers=-1)
...
df.mapply(myfunc, axis=1)
默认情况下(n_workers=-1
),程序包使用系统上所有可用的物理CPU。如果您的系统使用超线程(通常会显示两倍的物理CPU),mapply
将产生一个额外的工作程序,以将多处理池的优先级设置为高于系统上的其他进程。
根据您对all your cores
的定义,也可以改用所有逻辑核心(请注意,像这样的CPU绑定进程将在争夺物理CPU,这可能会降低您的运行速度):
import multiprocessing
n_workers = multiprocessing.cpu_count()
# or more explicit
import psutil
n_workers = psutil.cpu_count(logical=True)
答案 5 :(得分:1)
这里是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
答案 6 :(得分:1)
如果要保留在本机python中:
import multiprocessing as mp
pool = mp.Pool(mp.cpu_count())
df['newcol'] = pool.map(f, df['col'])
pool.terminate()
pool.join()
将以并行方式将功能f
应用于数据帧col
的列df
答案 7 :(得分:0)
由于问题是“ 如何使用所有内核并行运行数据帧上的应用程序?”,因此答案也可以是modin
。您可以并行运行所有内核,尽管实时性更差。
请参见https://github.com/modin-project/modin。它在dask
或ray
的顶部运行。他们说:“ Modin是为1MB至1TB +的数据集设计的DataFrame。”我尝试过:pip3 install "modin"[ray]"
。 Modin与Pandas的对比是-六个核心需要12秒,而核心需要6秒。
答案 8 :(得分:0)
这里还有一个使用 Joblib 和一些来自 scikit-learn 的帮助代码。轻量级(如果你已经有了 scikit-learn),如果你更喜欢更好地控制它正在做什么,那么很好,因为 joblib 很容易被破解。
from joblib import parallel_backend, Parallel, delayed, effective_n_jobs
from sklearn.utils import gen_even_slices
from sklearn.utils.validation import _num_samples
def parallel_apply(df, func, n_jobs= -1, **kwargs):
""" Pandas apply in parallel using joblib.
Uses sklearn.utils to partition input evenly.
Args:
df: Pandas DataFrame, Series, or any other object that supports slicing and apply.
func: Callable to apply
n_jobs: Desired number of workers. Default value -1 means use all available cores.
**kwargs: Any additional parameters will be supplied to the apply function
Returns:
Same as for normal Pandas DataFrame.apply()
"""
if effective_n_jobs(n_jobs) == 1:
return df.apply(func, **kwargs)
else:
ret = Parallel(n_jobs=n_jobs)(
delayed(type(df).apply)(df[s], func, **kwargs)
for s in gen_even_slices(_num_samples(df), effective_n_jobs(n_jobs)))
return pd.concat(ret)
用法:result = parallel_apply(my_dataframe, my_func)
答案 9 :(得分:0)
代替
df["new"] = df["old"].map(fun)
做
from joblib import Parallel, delayed
df["new"] = Parallel(n_jobs=-1, verbose=10)(delayed(fun)(i) for i in df["old"])
对我来说这比
略有改进import multiprocessing as mp
with mp.Pool(mp.cpu_count()) as pool:
df["new"] = pool.map(fun, df["old"])
如果作业非常小,您会收到进度指示和自动批处理。