pandas 多进程并发 与python加速

1. 背景

        在大规模数据之间完成一些操作,往往会浪费大量的时间,为了充分利用软硬件资源,演化出了2种主流的优化方式,即“向量化” 和“并行化” 。

2. swifter

        swifter 是一款用于给使用在 pandas DataFrame 或者 Series 上的 function 进行加速的包,它综合使用了“向量化” 和“并行化”方式。
安装:

pip install -U pandas # upgrade pandas
pip install swifter # first time installation
pip install -U swifter # upgrade to latest version if already installed
或 conda 安装
conda install -c conda-forge swifter

  

2.1 一个demo

import pandas as pd
import swifter

df = pd.DataFrame({'x': [1, 2, 3, 4], 'y': [5, 6, 7, 8]})
# runs on single core
df['x2'] = df['x'].apply(lambda x: x**2)
# runs on multiple cores
df['x2'] = df['x'].swifter.apply(lambda x: x**2)
# use swifter apply on whole dataframe
df['agg'] = df.swifter.apply(lambda x: x.sum() - x.min())

# use swifter apply on specific columns
df['outCol'] = df[['col1', 'col2']].swifter.apply(my_func)
df['outCol'] = df[['col1', 'col2', 'col3']].swifter.apply(my_func,
             positional_arg, keyword_arg=keyword_argval)

2.2 swifter 提效原理

1、它会判断apply中的函数是否能被向量化vectorization,如果可以,那么他就会自动选择向量化后函数的进行应用(此时是效果最好的);

2、如果apply的函数无法向量化,则自动选择使用 dask parallel processingsimple pandas apply 中较快的一种;

3、在分组apply的场景下,swifter也能达到更好的效果。

注意:并行化在小规模的数据集上可能达不到预期的效果,所以并行化操作是根据应用场景酌情使用的,而向量化不管数据集规模的大小都能带一些性能的提升。

pandas 多进程并发 与python加速_第1张图片

        可以看到Swifter的个特点,即无论数据大小如何,使用向量化效果几乎总是更好;如果数据量较小,那么普通 Pandas 操作有最佳速度,直到数据足够大为止;一旦超过阈值,并行处理就会是处理更快。

3. 多进程 pandarallel

        pandarallel 和 pandas 无缝衔接,是实现多线程的一个非常友好的工具。
安装:pip3 install pandarallel

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
from pandarallel import pandarallel
# shm_size_mb  分配的内存空间大小
# nb_workers  调用的核数
pandarallel.initialize(nb_workers=10, use_memory_fs=False, progress_bar=True)

def func(x):
    return x**3
df = pd.DataFrame(np.random.rand(1000,1000))

调用

# 处理一行
df.parallel_apply(func, axis=1)  
# 按列处理
df['col1'].parallel_apply(func)

下面的这些pandas原来的方法都有对应的pandarallel的并行的实现。

pandas 多进程并发 与python加速_第2张图片

参考:pandas apply 并行处理的几种方法_parallel_apply-CSDN博客

4. joblib

from math import sqrt
from joblib import Parallel, delayed
def test():
    start = time.time()
    result = Parallel(n_jobs=8)(delayed(sqrt)(i**2) for i in range(10000))
    # results = Parallel(n_jobs=8)(delayed(key_func)(group) for name, group in tqdm(data_grouped))    
    end = time.time()
    print(end-end)

11

5. multiprocessing

import multiprocessing as mp
with mp.Pool(mp.cpu_count()) as pool:
    df['newcol'] = pool.map(fun, df['col'])

multiprocessing.cpu_count()  # 返回系统的CPU数量。

该数量不同于当前进程可以使用的CPU数量。可用的CPU数量可以由 len(os.sched_getaffinity(0)) 方法获得。

6. modin

        Modin后端使用dask(dask类似pandas库,可以实现并行读取运行)或者ray,是个支持分布式运行的类pandas库。它通过更改一行代码 import modin.pandas as pd 就可以优化 pandas,常用的内置的read_csv、concat、apply都有不错的加速。

pandas 多进程并发 与python加速_第3张图片

注:并行处理的开销会使小数据集的处理速度变慢。

!pip install modin
import modin.pandas as pd

my_dict = {'a': np.random.randn(10000000),
           'b': np.random.randn(10000000),
           'N': np.random.randint(100, 10000, (10000000)),
           'x':  np.random.randint(1, 1000, (10000000))}
df = pd.DataFrame(my_dict)
df_new = pd.concat([df for _ in range(25)])

耗时0.6s,普通pandas 耗时3s,提速5倍!

7. python加速

7.1 numexpr

        numexpr 是一个对NumPy计算式进行的性能优化。其使用简单,只需要将原来的numpy语句使用双引号框起来,并使用evaluate方法调用即可。经验上看,数据有上万条+ 使用numexpr才比较优效果,对于简单运算使用numexpr可能会更慢。

import numexpr as ne
a = np.linspace(0,1000,1000) 
ne.evaluate('a**10')

相比numpy能提速5倍。

7.2 numba

        numba 使用行业标准的LLVM编译器库在运行时将 Python 函数转换为优化的机器代码。Python 中 numba 编译的数值算法可以接近 C 或 FORTRAN 的速度。numba很简单,其内置的函数本身是个装饰器,只需在自己定义好的函数前面加个@nb.方法就行,简单快捷!

numba 引擎在处理百万+大量数据点时表现出色。

# pip install numba
import numba as nb
import numpy as np

# 用numba加速的求和函数
@nb.jit()
def nb_sum(a):
    Sum = 0
    for i in range(len(a)):
        Sum += a[i]
    return Sum

a = np.linspace(0, 1000, 1000) # 创建一个长度为1000的数组
nb_sum(a) 

        numba 甚至比号称最接近C语言速度运行的numpy还要快5倍+,对于python求和速度快了几百倍。

        此外,numba还支持GPU加速、矢量化加速方法,可以进一步达到更高的性能。

from numba import cuda
cuda.select_device(1)

@cuda.jit
def CudaSquare(x):
    i, j = cuda.grid(2)
    x[i][j] *= x[i][j]

# numba的矢量化加速
from math import sin
@nb.vectorize()
def nb_vec_sin(a):
    return sin(a)

7.3 cupy

        CuPy 是一个借助 CUDA GPU 库在英伟达 GPU 上实现 Numpy 数组的库。基于 Numpy 数组的实现,GPU 自身具有的多个 CUDA 核心可以促成更好的并行加速。

# pip install cupy
import numpy as np
import cupy as cp
x_gpu = cp.ones((1000,1000,1000))

        CuPy 实现了 10.5 倍的加速,随着数据量的猛增,CuPy的性能提升会更为明显。

7.4 Cython优化

        Cython是一个基于C语言的Python 编译器,在一些计算量大的程序中,可以Cython来实现相当大的加速。通过在Ipython加入 Cython 魔术函数%load_ext Cython,如下示例就可以加速了一倍。进一步再借助更高级的cython语句,还是可以比Python快个几十上百倍。

%%cython
def f_plain(x):
    return x * (x - 1)

def integrate_f_plain(a, b, N):
    s = 0
    dx = (b - a) / N
    for i in range(N):
        s += f_plain(a + i * dx)
    return s * dx

8. pandas其他使用技巧

参考:https://pandas.pydata.org/pandas-docs/stable/user_guide/enhancingperf.html

8.1 按行迭代优化

        在新版的pandas中,使用itertuples函数按行对dataframe进行迭代,而不是iterrows函数,可提速几十倍。

res = []
for row in df.itertuples():
    temp = getattr(row, 'a')
    res.append(temp*temp)
df['a2'] = res

for index,row in df.iterrows():
    temp = row['a']
    a2.append(temp*temp)
df['a2'] = res    

8.2 apply、applymap优化

        当对于每行执行类似的操作时,用循环逐行处理效率很低。这时可以用apply或applymap搭配函数操作,其中apply是可用于逐行计算,而applymap可以做更细粒度的逐个元素的计算。

# 列a、列b逐行进行某一函数计算
df['a3']=df.apply( lambda row: row['a']*row['b'],axis=1)
# 逐个元素保留两位小数
df.applymap(lambda x: "%.2f" % x)

8.3 聚合函数agg优化

        对于某列将进行聚合后,使用内置的函数比自定义函数效率更高,如下示例速度加速3倍。

df.groupby("x")['a'].agg(lambda x:x.sum())
df.groupby("x")['a'].agg(sum)
df.groupby("x")['a'].agg(np.sum)

8.4 文件操作

        pandas读取文件,pkl格式的数据的读取速度最快,其次是hdf格式的数据,再次是读取csv格式数据,而xlsx的读取是比较慢的。但是存取csv有个好处是,这个数据格式通用性更好,占用内存硬盘资源也比较少。此外,对于大文件,csv还可以对文件分块、选定某几列、指定数据类型做读取。
 

pandas 多进程并发 与python加速_第4张图片

8.5 pandas.eval

        pandas.eval 基于numexpr,使用eval表达式的一个经验是数据超过 1W 行的情况下使用会有明显优化效果。

import pandas as pd 
nrows, ncols = 20000, 100
df1, df2, df3, df4 = [pd.DataFrame(np.random.randn(nrows, ncols)) for _ in range(4)]
pd.eval("df1 + df2 + df3 + df4")

你可能感兴趣的:(pandas,pandas)