Pandas性能优化实战
🗒️Pandas性能优化实战
type
status
date
slug
summary
tags
category
icon
password
这篇文章是基于互联网上已有的一些pandas优化方法,结合我的需求和应用场景,对已有的方法做了一些优化和适配。

Review

https://zhuanlan.zhihu.com/p/97012199https://zhuanlan.zhihu.com/p/81554435https://zhuanlan.zhihu.com/p/80686466http://flypython.com/ml/142.htmlhttp://flypython.com/ml/139.htmlhttps://cloud.tencent.com/developer/article/1507275https://cloud.tencent.com/developer/article/1557965https://blog.csdn.net/mutex86/article/details/79670034
这些文章里面提到的几种常见的优化思路和方向:
  1. 迭代优化:对传统的for循环进行加速,但加速效果有限
  1. apply方法:借助pandas自身增强并行性,加速效果更好,但仍然比较差
  1. agg方法:可以用内置的函数,加速效果很好,但建议使用内置函数,难以自定义
  1. 向量化方法:加速效果堪称离谱,但要求向量维度一样才能计算,一般只能用在单表内
且互联网上的demo往往局限于在一张dataframe里进行列操作,例如https://zhuanlan.zhihu.com/p/80686466,而对于多表联合操作的处理并没有较好的资料,这也是本文的意义所在。

应用场景

输入:data_main_market,data_industry_finance, data_finance_fitered三张表
希望处理的流程:根据data_main_marketdata_finance_filtered两张表,对data_industry_finance表中对应的行的一个键值进行修改
能够实现业务但处理速度很慢的代码:
这样的代码没有问题可以跑,业务上也很好,但运行效率太低。在这样的代码下预计跑完全量数据集需要接近一个月,时间成本不能接受。(如果半个小时就跑完了我也就不用优化了)
可以看到,这段代码的逻辑比较复杂,需要从多张表中获取数据进行聚合计算,然后再对一张大表中的特定位置进行修改。pandas一般只能使用一颗核心,for循环的并行度也不高,所以就开始优化了。
先来定位优化的目标。这一部分通过把函数打开,然后使用jupyter的%%timeit魔法函数进行时间测试。
首先来看两重循环的前后顺序是否有关。因为pandas的groupby看起来明显比for高级,所以考虑会不会groupby需要的计算更多,所以应该先groupby再for。然而在测试了时间之后,发现这里并没有优化的意义。对两重循环测试时间,先groupby的时间在800ms左右,而先for的时间在930ms左右。因此循环本身带来的时间开销区别并不大。尽管如此,还是存在优化的空间,但也需要寻找其他原因。
向下看,对于每一个企业指标,op_data的获取都是一样的,所以可以把这次查询提前到第三重for循环前面,可以节省大量时间。
然后是value的计算,这里没什么好优化的。我已经使用了pandas向量化的方法,也用了内置的sum函数。
最后是数据的修改。这一步是比较耗时间的,需要对多个条件进行筛选,然后修改数据。本来希望采用:
的方式节省前面几次筛选,但这样操作的时间并没有什么减少。如果不加上对年份的筛选,速度确实可以提升很多,但&操作需要大量的逻辑运算,且从形式上看,这也已经是pandas并行化的书写方式了。
这么一顿操作下来,有意义的也只是把op_data放在了第二层循环中,优化效果并不好。

引入apply

从上面的代码中可以看到,最内层的循环其实是可以并行化运行的。所有这个group中的企业其实可以并行处理,也都是同样的处理逻辑。
但常见的apply教程是在func的位置给一个lambda函数,或者是定义好的函数。但事实上我需要传入多个参数给内部的func,这时候该怎么办呢?
其中最关键的在于:
提炼一下:
有点类似于偏导的感觉。func1return值是一个pd.Series,可以赋值给一列。

并行化

已经实现了apply,就免不了尝试一下其他的魔改版pandas包。由于pandas默认是使用单核进行运算,有大神就开发了各种各样的提高pandas并行性的包。一些互联网上的教程:
其中提到了modin包和pandarallel,都可以尝试
注意:pandarallel仅适用于linux和macos,windows可以借助wsl搭建linux环境
这可能是由于windows的子进程不能创建子进程导致的
尝试修改代码:

算法复杂度分析

我写了一个for循环,然后手动计时,可视化结果如下
notion image
限定截距=0,线性函数拟合效果不错,R^2高达0.9889
做方差分析:
notion image
在alpha=0.05的情况下,P-value很小,可以接受原假设,即可以接受总体为线性函数,也就是算法复杂度达到了O(n),已经很不错啦

Final Thoughts

接下来的优化思路:
  • 虽然已经达到了线性时间复杂度,但依然可以优化每一步的执行时间来获得时间长的节省。
  • 目前的考虑是把backend换成mysql,用更专业的数据库来代替操作pandas的dataframe
  • 使用docker,简简单单搭建一个mysql,然后用dataframe.to_sql方法还挺好用
  • 这就是接下来的故事啦~

Update

换用了mysql之后,效率并没有提高,甚至时间还多了50%。查询资料后发现pandas的dataframe的实现是相对比较高效的,而mysql还需要经过一些中间件的处理,延迟相对更高。因此在数据量还不是很大的时候,还是老老实实的就存在dataframe里面就好。
 
 
聊一聊元宇宙WSL2迁移
  • Utterance