Pandas、Numpy性能优化秘籍(全)

pandas、numpy是Python数据科学中非常常用的库,numpy是Python的数值计算扩展,专门用来处理矩阵,它的运算效率比列表更高效。pandas是基于numpy的数据处理工具,能更方便的操作大型表格类型的数据集。

但是,随着数据量的剧增,有时numpy和pandas的速度就成瓶颈。如下我们会介绍一些优化秘籍:里面包含 代码上面的优化,以及可以无脑使用的性能优化扩展包。

1、NumExpr

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

import numexpr as ne

import numpy as np

a = np.linspace(0,1000,1000) 

print('# numpy十次幂计算')
%timeit a**10

print('# numexpr十次幂计算')
%timeit ne.evaluate('a**10')

2、Numba

Numba 使用行业标准的LLVM编译器库在运行时将 Python 函数转换为优化的机器代码。Python 中 Numba 编译的数值算法可以接近 C 或 FORTRAN 的速度。

如果在你的数据处理过程涉及到了大量的数值计算,那么使用numba可以大大加快代码的运行效率(一般来说,Numba 引擎在处理大量数据点 如 1 百万+ 时表现出色)。numba使用起来也很简单,因为numba内置的函数本身是个装饰器,所以只要在自己定义好的函数前面加个@nb.方法就行,简单快捷!

# pip install numba

import numba as nb

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

# 没用numba加速的求和函数
def py_sum(a):
    Sum = 0
    for i in range(len(a)):
        Sum += a[i]
    return Sum

import numpy as np
a = np.linspace(0,1000,1000) # 创建一个长度为1000的数组
print('# python求和函数')
%timeit sum(a) 
print('# 没加速的for循环求和函数')
%timeit py_sum(a)
print('# numba加速的for循环求和函数')
%timeit nb_sum(a) 
print('# numpy求和函数')
%timeit np.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)

3、CuPy

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

# pip install cupy
import numpy as np
import cupy as cp
import time

### numpy
s = time.time()
x_cpu = np.ones((1000,1000,1000))
e = time.time()
print(e - s)

### CuPy 
s = time.time()
x_gpu = cp.ones((1000,1000,1000))
e = time.time()
print(e - s)

上述代码,Numpy 创建(1000, 1000, 1000)的数组用了 1.68 秒,而 CuPy 仅用了 0.16 秒,实现了 10.5 倍的加速。随着数据量的猛增,CuPy的性能提升会更为明显。

4、pandas使用技巧

更多pandas性能提升技巧请戳官方文档:https://pandas.pydata.org/pandas-docs/stable/user_guide/enhancingperf.html

4.1 按行迭代优化

我们按行对dataframe进行迭代,一般我们会用iterrows这个函数。在新版的pandas中,提供了一个更快的itertuples函数,如下可以看到速度快了几十倍。

import pandas as pd
import numpy as np
import time
df = pd.DataFrame({'a': np.random.randn(100000),
                     'b': np.random.randn(100000),
                    'N': np.random.randint(100, 1000, (100000)),
                   'x':  np.random.randint(1, 10, (100000))})

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


4.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)

4.3 聚合函数agg优化

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

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

%timeit  df.groupby("x")['a'].agg(sum)

%timeit  df.groupby("x")['a'].agg(np.sum)

4.4 文件操作

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


4.5 pandas.eval

pandas.eval 是基于第一节提到的numexpr,pandas也是基于numpy开发的,numexpr同样可以被用来对pandas加速)。使用eval表达式的一个经验是数据超过 10,000 行的情况下使用会有明显优化效果。

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

print('pd')
%timeit df1 + df2 + df3 + df4
print('pd.eval')
%timeit pd.eval("df1 + df2 + df3 + df4")

5、Cython优化

Cython是一个基于C语言的Python 编译器,在一些计算量大的程序中,可以Cython来实现相当大的加速。考虑大部分人可能都不太了解复杂的cython语句,下面介绍下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

6、swifter

swifter是pandas的插件,可以直接在pandas的数据上操作。Swifter的优化方法检验计算是否可以矢量化或者并行化处理,以提高性能。如常见的apply就可以通过swifter并行处理。

import pandas as pd
import swifter

df.swifter.apply(lambda x: x.sum() - x.min())

7、Modin

Modin后端使用dask或者ray(dask是类似pandas库的功能,可以实现并行读取运行),是个支持分布式运行的类pandas库,简单通过更改一行代码import modin.pandas as pd就可以优化 pandas,常用的内置的read_csv、concat、apply都有不错的加速。注:并行处理的开销会使小数据集的处理速度变慢。

!pip install modin
import pandas
import modin.pandas as pd
import time

## pandas

pandas_df = pandas.DataFrame({'a': np.random.randn(10000000),
                     'b': np.random.randn(10000000),
                    'N': np.random.randint(100, 10000, (10000000)),
                   'x':  np.random.randint(1, 1000, (10000000))})



start = time.time()

big_pandas_df = pandas.concat([pandas_df for _ in range(25)])

end = time.time()
pandas_duration = end - start
print("Time to concat with pandas: {} seconds".format(round(pandas_duration, 3)))

#### modin.pandas
modin_df = pd.DataFrame({'a': np.random.randn(10000000),
                     'b': np.random.randn(10000000),
                    'N': np.random.randint(100, 10000, (10000000)),
                   'x':  np.random.randint(1, 1000, (10000000))})

start = time.time()
big_modin_df = pd.concat([modin_df for _ in range(25)])

end = time.time()
modin_duration = end - start
print("Time to concat with Modin: {} seconds".format(round(modin_duration, 3)))

print("Modin is {}x faster than pandas at `concat`!".format(round(pandas_duration / modin_duration, 2)))

(END)

文章首发公众号“算法进阶”,欢迎关注。公众号阅读原文可访问文章相关代码及资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容