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 <

截至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 <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"])