Pandas 使数据帧应用()使用所有内核?
截至2017年8月,Pandas仍然只能使用单核,这意味着当您运行Pandas 使数据帧应用()使用所有内核?,pandas,dask,Pandas,Dask,截至2017年8月,Pandas仍然只能使用单核,这意味着当您运行df.apply(myfunc,axis=1)时,多核机器将浪费大部分计算时间 如何使用所有内核在数据帧上并行运行apply 最简单的方法是使用。您需要这些导入(您需要pip安装dask): 语法是 data = <your_pandas_dataframe> ddata = dd.from_pandas(data, npartitions=30) def myfunc(x,y,z, ...): return <
df.apply(myfunc,axis=1)
时,多核机器将浪费大部分计算时间
如何使用所有内核在数据帧上并行运行apply 最简单的方法是使用。您需要这些导入(您需要
pip安装dask
):
语法是
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)
28.16970546543598
2.708152851089835
0.010668013244867325
在分区上从pandas apply到dask apply,给出10个加速因子。当然,如果你有一个可以向量化的函数,你应该——在这种情况下,函数(y*(x**2+1)
)是简单的向量化,但是有很多东西是不可能向量化的 您可以使用以下软件包:
(请注意,您可能希望在virtualenv中使用此选项,以避免与已安装的依赖项发生版本冲突。)
Swifter作为pandas的插件,允许您重用apply
功能:
import swifter
def some_function(data):
return data * 10
data['out'] = data['in'].swifter.apply(some_function)
它将自动找出最有效的方法来并行化函数,不管它是否被矢量化(如上面的例子所示)
在GitHub上提供了。请注意,该软件包正在积极开发中,因此API可能会更改
还请注意,这适用于字符串列。当使用字符串时,Swifter将退回到一个“简单的”应用程序
应用程序,它将不是并行的。在这种情况下,即使强制它使用dask
也无法提高性能,您最好还是手动拆分数据集,然后重新编译。您可以尝试pandarallel
:这是一个简单而有效的工具,可以在所有CPU(Linux和macOS)上并行化pandas操作
- 并行化有成本(实例化新进程、通过共享内存发送数据等),因此只有在并行化的计算量足够大的情况下,并行化才有效。对于很少的数据量,使用并行并不总是值得的
- 应用的函数不应是lambda函数
请参见这里是sklearn base transformer的一个示例,在该示例中,变压器是并联的
将多处理导入为mp
从sklearn.base导入TransformerMixin,BaseEstimator
类并联变压器(基本估计器、变压器):
定义初始化(自我,
n_作业=1):
"""
n_作业-要运行的并行作业
"""
多样性
self.user_abbrevs=用户_abbrevs
self.n_jobs=n_jobs
def配合(自身、X、y=无):
回归自我
def变换(self,X,*.):
X_copy=X.copy()
cores=mp.cpu\u计数()
分区=1
如果self.n_jobs如果您想继续使用本机python:
import multiprocessing as mp
with mp.Pool(mp.cpu_count()) as pool:
df['newcol'] = pool.map(f, df['col'])
将函数f
以并行方式应用于数据帧df
的列col
,要使用所有(物理或逻辑)内核,您可以尝试使用更快的和泛并行
您可以在初始化时设置内核数量(以及分块行为):
将熊猫作为pd导入
导入映射层
init(n_workers=-1)
...
df.mapply(myfunc,轴=1)
默认情况下(n\u workers=-1
),包使用系统上可用的所有物理CPU。如果您的系统使用超线程(通常是物理CPU显示量的两倍),mapply
将产生一个额外的工作线程,使多处理池优先于系统上的其他进程
根据您对所有内核的定义,您也可以使用所有逻辑内核(注意,这样CPU绑定的进程将争夺物理CPU,这可能会降低您的操作速度):
导入多处理
n\u workers=多处理.cpu\u计数()
#或者更明确
导入psutil
n_workers=psutil.cpu_计数(逻辑=True)
因为问题是“如何使用所有核心在数据帧上并行运行apply?”,所以答案也可以是使用modin
。您可以并行运行所有内核,但实时性较差
看。它在dask
或ray
的顶部运行。他们说“Modin是为1MB到1TB+的数据集设计的数据帧。”我试过:pip3安装“Modin”[ray]”
。Modin与pandas在六核上的时间是-12秒,而不是6秒。只是想给出一个更新答案
在我的100000条记录中,没有Dask:
CPU时间:用户6分钟32秒,系统100毫秒,总计6分钟32秒
墙时间:6分钟32秒
使用Dask:
CPU时间:用户5.19秒,系统:784毫秒,总计:5.98秒
墙时间:1分钟3秒这里是另一个使用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\u apply(我的数据帧,我的函数)
而不是
df["new"] = df["old"].map(fun)
做
对我来说,这是一个轻微的改善
import multiprocessing as mp
with mp.Pool(mp.cpu_count()) as pool:
df["new"] = pool.map(fun, df["old"])
当作业非常小时,您会得到进度指示和自动批处理。很高兴知道,谢谢您的发布。您能解释一下为什么选择30个分区吗?更改此值时性能会发生变化吗?@AndrewL我假设每个分区由一个单独的进程提供服务,对于16个内核,我假设16个或32个进程可以同时运行。我试过了,性能似乎可以提高到32个分区,但进一步提高没有任何好处。我假设四核机器需要8个分区,等等。请注意,我确实注意到16和32之间有一些改进,所以我想你真的需要2x$NUM_处理器唯一的问题是get=关键字已被弃用。请将scheduler=关键字改为所需计划程序的名称,如“thread”
import swifter
def some_function(data):
return data * 10
data['out'] = data['in'].swifter.apply(some_function)
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)
import multiprocessing as mp
with mp.Pool(mp.cpu_count()) as pool:
df['newcol'] = pool.map(f, df['col'])
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()
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)
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"])