使Pandas DataFrame apply()使用所有内核?


98

截至2017年8月,不幸的是,Pandas DataFame.apply()仍限于使用单核,这意味着多核计算机在运行时将浪费其大部分计算时间df.apply(myfunc, axis=1)

您如何使用所有核心并行运行一个数据帧上的应用程序?

Answers:


73

您可以使用该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


1
我们纯粹出于好奇,是否有一种方法可以限制并行应用时使用的内核数量?我有一台共享服务器,因此如果我抓住所有32个内核,没人会高兴。
Maksim Khaitovich '18

1
@MaximHaytovich我不知道。Swifter在后台使用dask,所以也许它遵循以下设置:stackoverflow.com/a/40633117/435093-否则,我建议在GitHub上发布一个问题。作者反应非常快。
slhck

@slhck谢谢!会多挖一点。无论如何,它似乎都无法在Windows服务器上工作-只是挂起了在玩具任务上没有做的任何事情
Maksim Khaitovich 18/09/5

- :你可以帮我回答这个stackoverflow.com/questions/53561794/...
ak3191

2
对于字符串,只需allow_dask_on_strings(enable=True)像这样添加:df.swifter.allow_dask_on_strings(enable=True).apply(some_function) 来源:github.com/jmcarpenter2/swifter/issues/45
Sumit Sidana

99

最简单的方法是使用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))被简单地向量化,但是还有很多事情无法向量化。


2
很高兴知道,感谢您的发布。您能解释一下为什么选择30个分区吗?更改此值时性能会改变吗?
安德鲁·L

2
@AndrewL我假设每个分区由一个单独的进程提供服务,并且使用16个内核,我假设16个或32个进程可以同时运行。我尝试了一下,性能似乎最多可以改善32个分区,但是进一步增加并没有任何好处。我假设使用四核计算机,您将需要8个分区,等等。请注意,我的确注意到16和32之间有一些改进,所以我认为您确实希望2x $ NUM_PROCESSORS
Roko Mijic

8
唯一的事情是The get= keyword has been deprecated. Please use the scheduler= keyword instead with the name of the desired scheduler like 'threads' or 'processes'
凌晨1点56分的话

4
对于dask v0.20.0及更高版本,请使用ddata.map_partitions(lambda df:df.apply((lambda row:myfunc(* row)),axis = 1))。compute(scheduler ='processes'),或其他调度程序选项。当前代码抛出“ TypeError:get =关键字已被删除。请使用scheduler =关键字,而不是所需的调度程序的名称,例如'threads'或'processes'”
嘲笑

1
确保在执行此操作之前,数据帧不会抛出任何重复索引ValueError: cannot reindex from a duplicate axis。要解决此问题,您应该通过删除重复的索引,df = df[~df.index.duplicated()]或者通过来重置索引df.reset_index(inplace=True)
哈比卜·卡巴斯蒂安

23

您可以试试看pandarallel:一个简单高效的工具,可在所有CPU上并行化熊猫操作(在Linux和macOS上)

  • 并行化具有成本(初始化新进程,通过共享内存发送数据等),因此,只有在要进行并行化的计算量足够高的情况下,并行化才有效。对于很少的数据,使用并行化并不总是值得的。
  • 应用的函数不应是lambda函数。
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)

参见https://github.com/nalepae/pandarallel


您好,我无法解决一个问题,使用pandarallel出现错误:AttributeError:无法腌制本地对象'prepare_worker。<locals> .closure。<locals> .wrapper'。你能帮我吗?
Alex Cam

@Alex Sry我不是那个模块的开发者。您的代码是什么样的?您可以尝试将您的“内部函数”声明为全局吗?(只是猜测)
G_KOBELIEF

@AlexCam您的函数应在其他函数之外定义,以便python可以对其进行腌制以进行多处理
Kenan

@G_KOBELIEF使用Python> 3.6,我们可以将lambda函数与pandaparallel一起使用
user110244

16

如果要保留在本机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']不是线程安全的。
嘎嘎声

2
您可以将pool.map写入中间的temp_result列表,以允许检查长度是否与df匹配,然后执行df ['newcol'] = temp_result?
奥利维尔·克鲁奇

您的意思是创建新列?你会用什么?
Olivier Cruchant

是的,将映射结果分配给数据框的新列。map不会返回发送给函数f的每个块的结果的列表吗?那么,当您将其分配给“ newcol”列时会发生什么呢?使用Pandas和Python 3
Mina

实际上,它的工作真的很顺利!你试过了吗?它创建一个与df长度相同,与发送顺序相同的列表。它实际上以并行方式执行c2 = f(c1)。在python中没有更简单的方法可以进行多进程。从性能角度来看,Ray似乎也可以做得很好(但是据我所知,它还不那么成熟,安装并不总是很顺利
Olivier Cruchant

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

有关更多信息,请参见https://towardsdatascience.com/4-easy-steps-to-improve-your-machine-learning-code-performance-88a0b0eeffa8

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.