实战,python处理excel比vba快100倍,媳妇连连夸赞今晚不用再跪搓衣板(代码片段)

朝阳区靓仔_James 朝阳区靓仔_James     2023-01-29     311

关键词:

背景

最近经历了一次把vb脚本改造成python脚本,并获得性能提升数倍的过程,当然,这个过程也不是一帆风顺,中间也经历了一些波折,但是,也收获了一波新的认知。正好最近有时间,姑且写下来记录一下。

什么是VB

话说现在的年轻人,听说过这个编程语言的应该不多了。VB是一种由微软公司开发的包含协助开发环境的事件驱动编程语言。从任何标准来说,VB都是世界上使用人数最多的语言,它源自于BASIC编程语言,也属于高级语言的一种了。只是现在各大应用场景以及被Java、Go、Python等编程语言瓜分一空,VB基本很少人知道了。

什么是VBA

而VBA和VB又有点差别,Visual Basic for Applications(VBA)是Visual Basic的一种宏语言,是微软开发出来在其桌面应用程序中执行通用的自动化(OLE)任务的编程语言。主要能用来扩展Windows的应用程式功能,特别是Microsoft Office软件,比如excel、powerpoint、word等。

故事的开端

而本次故事的场景,就是在excel中编写vba宏脚本,而这个场景的需求,则来源于笔者的媳妇。笔者的媳妇平时的工作大部分时间都是跟excel打交道,也就是很多人口中的“表姐”,因此excel的各种高级操作比如vlookup、数据透视等,也算是应用的炉火纯青了。

可偏偏事不如人愿,企业中的业务总是会越来越复杂,老板的要求也会越来越高,渐渐地,有一些需求我媳妇用她炉火纯青的技巧也搞不动了。于是她把希望寄托在了我这个廉价劳动力身上,毕竟传说中的搞IT的,可是什么都能干的。

于是大概从几年前,我开始陆陆续续用vba写宏,帮助媳妇处理类似复杂的数据计算问题,说到这里,我翻了翻我的朋友圈,竟然有据可查:2017年就开始了!有图为证:

也就是从那个时候开始,媳妇搞不定的复杂数据处理问题,就扔给我用vba来搞。要知道,对于一个写惯Java语言的人来说,对vba这种语言真的是一百种不习惯,尤其是那个土得掉渣的开发环境,话不多说,上图:

有没有一种年代复古风的感觉!这还是最新版本的,老版本的连调试功能都没有,任何问题都得默念加各种打日志排查,更不用说高级点IDE都具备的自动补全、提示、重构等功能了,所以,用这个玩意写代码的效率那真是一言难尽。

就这样被媳妇的需求折磨了几年,好在这几年的需求也没复杂到哪里去,一路也就忍过来了。可最近一次媳妇扔过来的需求,可着实把我可累了一把。

详细的需求就不说了,大概就是对一个excel的两个sheet进行计算,其中一个sheet将近1万行,两外一个sheet数据量倒不多300多行,但是格式比较复杂,各种合并和拆分单元格(见下图),而要计算的需求复杂度相比之前也上升了一个台阶。

拿到需求后,我还是按照惯例用vba来写,大概耗费了一个周末的时间搞定了,虽然交了差。但是面对未来可能越来越复杂的需求,我的心里打了鼓,vba的开发效率和复杂数据处理需求的矛盾越来越突出,而且这次写的脚本,性能上也问题很大,整个处理过程耗时10分钟之巨,如下图所示:

作为一个自认优秀且有良心的搞IT的,怎么能够忍受这种开发效率和运行效率,二话不说,我要优化它!

怎么优化呢?话说在大数据处理领域,Python可算是TIOBE排行榜上,数一数二的利器了,尤其是在AI大热的背景下,Python在TIOBE排行榜上的地位是逐渐蹿升,除了大数据领域,Python在web开发、Excel办公、科学计算和数据可视化等方面也表现优秀。好了,就用Python搞!

Python优化过程

大概的优化思路是这样的:用Python的xlwings库来处理excel数据的读写,但数据的计算就不用它直接搞了,效率会比较低,而是用Pandas库在内存中进行数据的复杂计算,然后将计算后的结果写回excel

思路其实很简单,但实操的过程却不是完全一帆风顺,接下来就是整个优化的过程

第一版优化

因为用Pandas把数据读到内存后,是一个DataFrame,我们可以很容易的拿到这个DataFrame的行数和列数,类似一个数组一样可以方便的遍历,因此第一版的实现,使用的是标准的遍历的方法来实现,核心代码如下:

读取excel

import pandas as pd
import xlwings as xw

#要处理的文件路径
fpath = "datas/joyce/DS_format_bak.xlsm"

#把CP和DS两个sheet的数据分别读入pandas的dataframe
cp_df = pd.read_excel(fpath,sheet_name="CP",header=[0])
ds_df = pd.read_excel(fpath,sheet_name="DS",header=[0,1])
......

标准遍历方法

for j in range(len(cp_df)):
    
    cp_measure = cp_df.loc[j,'Measure']
    cp_item_group = cp_df.loc[j,'Item Group']
    
    if cp_measure == "Total Publish Demand":
        
        for i in range(len(ds_df)):
            #如果cp和ds的item_group值相同
            if cp_item_group == ds_df.loc[i,('Total','Capabity')]:
            
......

写入excel

#保存结果到excel       
app = xw.App(visible=False,add_book=False)

ds_format_workbook = app.books.open(fpath)
ds_format_workbook.sheets["DS"].range("A3").expand().options(index=False).value = ds_df 

ds_format_workbook.save()
ds_format_workbook.close()
app.quit()
......

说到这里插一句,大家还记得我前面提到的那个各种拆分和合并单元格的复杂格式吗,这种格式在Pandas里又叫多层索引(MultiIndex),这种结构下数据的查询和操作,比普通的表格要复杂,大概处理代码类似下面:

#用元组的方式来定位某一列
ds_total_capabity1 = ds_df.loc[k,('Total','Capabity.1')]
#
#获取多层索引某一层数据的方法
ds_month = ds_df.columns.get_level_values(0)[k]
ds_datatime = ds_df.columns.get_level_values(1)[k]
......

因为这个话题跟本文章无关,这里就不展开了,有兴趣大家自己去学习了解。

这一版写完后,信心满满地执行脚本,但是立马被现实浇了一盆冷水,执行时间竟然要555秒,也就是9分多钟,并没有比vba快多少,如下图:

为什么会这样!Python不是号称数据处理利器吗。我们仔细看一下打印的日志输出,可以看到主要的瓶颈在循环计算这块,耗时469+42 = 517秒,基本所有时间都用在这里。当然,从日志也可以看到,读写excel的性能也一般,但并不是性能瓶颈。对于性能优化的一般准则是:数据驱动+二八原则,也即通过数据分析发现瓶颈,即占用80%耗时的地方,然后有针对性地优化该瓶颈。

内存中的循环计算为什么这么慢呢?遇事不决问度娘,通过一番搜索,终于让我找到一个官方解释,原来DataFrame(数据帧)是具有行和列的Pandas对象(objects),如果使用循环,则将遍历整个对象,Python无法利用任何内置函数,而且速度非常慢,建议用Pandas内置函数:iterrows(),iterrows()为每行返回一个Series,因此将DataFrame迭代为一对索引,将感兴趣的列作为Series进行迭代,这使其比标准循环更快。

既然官方这么说,那我们还怀疑什么,那就试试呗。

第二版优化

有了解决方案,那就好办了,无非就是把代码里所有用到标准循环的地方,改成用iterrows(),改动的地方代码如下:

#根据CP和DS表的Item_group值做lookup,计算DS表的Delta值
for index_i,cp_row in cp_df.iterrows():
    
    #获取CP表的Item_group和siteid值
    cp_item_group = cp_row['Item Group']
    siteid = cp_row['SITEID']
    key = cp_item_group + "-" + siteid  
        
    for index_j,ds_row in ds_df.iterrows():
        
        #获取DS表的Item_group值
        ds_item_group = ds_row[('Total','Capabity')]
        
        if ds_item_group != "" and cp_item_group == ds_item_group :
           
            iner_iter_df = ds_df.loc[index_j:index_j+5]
        ......

改完后执行,果然,效率提升了一些,见下图:

整体耗时337秒,也就是5分多钟,比前一版提升40%,看起来还不错。但是,作为一名优秀的IT人,不能满足于既有的成绩,要不断追求极致。于是,就有了第三版优化。

第三版优化

其实第三版优化的思路,还是追求更快地遍历效率,Pandas除了iterrows()之外,据说还有一个更快的apply()方法,能够对DataFrame的每一行逐行应用自定义函数,且遍历性能更好。于是,第三版的核心代码如下:

def Cal_Delta_Loi_Iter_In_Cp(data):
    global cal_delta_loi_cp_row
    cal_delta_loi_cp_row = data
    #获取CP表的Item_group和siteid值
    global cp_item_group
    cp_item_group = cal_delta_loi_cp_row['Item Group']
    siteid = cal_delta_loi_cp_row['SITEID']
    global key 
    key = cp_item_group + "-" + siteid
    ds_df.apply(Cal_Delta_Loi_Iter_In_Ds,axis=1)
    
#开始计算Delta和LOI值
cp_df.apply(Cal_Delta_Loi_Iter_In_Cp,axis=1)
......

按apply()改完代码再次执行,这次执行效率果然又上了一个台阶,如下图:

整体耗时147秒,也即2分多钟,相比上一版再次提升56%,Very Done!

小小总结一下

优化到这里,我们可以看到,使用Python的Pandas类库,并且使用较高性能的内置函数,能够很大程度提升数据处理的性能。但是,我们从前面打印出的日志也能看到,Python提供的xlwings库,在读写excel方面的性能缺很难说优秀,相比vba来说更是差了一大截。

VBA虽然数据结构少,数据计算速度慢,但访问自己Excel的Sheet,Range,Cell等对象却速度飞快,这就是一体化产品的优势。VBA读取Excel的Range,Cell等操作是通过底层的API直接读取数据的,而不是通过微软统一的外部开发接口。所以Python的各种开源和商用的Excel处理类库如果和VBA来比较读写Excel格子里面的数据,都是处于劣势的(至少是不占优势的)。

因此,Python处理Excel的时候,就要把Excel一次性地读取数据到Python的数据结构中,而不是大量调用Excel里的对象,不要说频繁地写入Excel,就是频繁地读取Excel里面的某些单元格也是效率较低的。

关于Python技术储备

学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

二、Python必备开发工具

三、精品Python学习书籍

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

四、Python视频合集

观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

五、实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

六、Python练习题

检查学习结果。

七、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

这份完整版的Python全套学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

填充整数列表比填充整数向量快 100 倍

】填充整数列表比填充整数向量快100倍【英文标题】:Populatingalistofintegersis100timesfasterthanpopulatingavectorofintegers【发布时间】:2014-02-1711:29:32【问题描述】:我将填充整数列表与整数向量所花费的时间进行比较。每个向量和列表都... 查看详情

python代码加速100倍,针对excel自动化处理的加速实战!(代码片段)

并发vs并行说到并发编程,我们先来澄清一下并发(Concurrency)和并行(Parallelism)这两个概念,因为这个两个概念的含义是不同的。并行(Parallelism)指的就是在同一时刻,有两个或两个以上的任务的代码在处理器... 查看详情

Double.IsNaN 测试快 100 倍?

...7:42【问题描述】:我在.NETSourceCode中发现了这个:它声称比System.Double.IsNaN快100倍。是否有理由不使用此功能而不是System.Double.IsNaN?[StructLayout(LayoutKind.Explicit)]privates 查看详情

protobufprotocol-buffers序列化数据gobspicklingstringxml用c实现的cpickle比pickle快1000倍(代码片段)

 场景:浏览器请求--->python数据生成--->python-生成excel--->浏览器下载excel目标:重构为浏览器请求--->python数据生成--->golang-生成excel--->浏览器下载excel二阶目标:后端全部golang实现 https://developers.google.com/protocol... 查看详情

点进来你就知道,98%程序员都不清楚的小秘密:为何getelementsbytagname()比queryselectorall()快100倍

getElementsByTagName()和querySelectorAll()最大的区别在于返回值的不同: getElementsByTagName() 方法返回一个动态的(live) NodeList,而querySelectorAll() 返回的是一个静态的(static) NodeList.理解这一点是非常必要的.动态NodeList这是 查看详情

qvector与qmap查找效率实战(qmap快n倍)

因为项目使用QVector,太慢了,听说QMap比QVector查找时快,所以写一个小程序试试;从30000个数据中找5000个程序运行截图如下:QVector QMap 一样的数据,找一样的数代码如下图:widget.h#ifndefWIDGET_H#defineWIDGET_H#include<QWidget>... 查看详情

荷兰研究人员提突破性想法:用红外线传输网络_比wifi快100倍

...当然也有因同时连接人数过多速度卡成狗的,但和免费相比这些都可以忍受了吧)。在光纤入户,独享10M、20M、30M甚至100M网络带宽下,你以为Wi-Fi的速度就很快了吗?  大错特错!Wi-Fi本身就是一种卡网速的存在。最近荷兰埃... 查看详情

比正则快m倍以上!python替换字符串的新姿势!(代码片段)

FlashText算法是由VikashSingh于2017年发表的大规模关键词替换算法,这个算法的时间复杂度仅由文本长度(N)决定,算法时间复杂度为O(N)而对于正则表达式的替换,算法时间复杂度还需要考虑被替换的关键词数量(M),因此时... 查看详情

纯 C++ 代码比内联汇编程序快 10 倍。为啥?

】纯C++代码比内联汇编程序快10倍。为啥?【英文标题】:PlainC++Code10timesfasterthaninlineassembler.Why?纯C++代码比内联汇编程序快10倍。为什么?【发布时间】:2015-09-0321:26:29【问题描述】:这两个代码sn-ps做同样的事情:将两个浮点... 查看详情

为啥“中位数”比使用统计包的“平均值”快 2 倍?

】为啥“中位数”比使用统计包的“平均值”快2倍?【英文标题】:Howcome"median"is2xfasterthan"mean"usingstatisticspackage?为什么“中位数”比使用统计包的“平均值”快2倍?【发布时间】:2016-10-2712:41:14【问题描述】:... 查看详情

一个比springboot快44倍的java框架

...0c;Java干货及时送达最近看到一个框架,官方号称可以比SpringBoot快44倍,居然这么牛逼,有这么神奇吗?今天带大家来认识一下。这个框架名叫:light-4j。官网简介:Afast,lightweightandmoreproductivemicroservicesframewo... 查看详情

比excel好用100倍!半小时开发酷炫可视化大屏,这款工具太牛了

前几天遇到一个朋友,也是做数据分析的,不管是多杂乱的数据,他也能从中分析出个所以然来。但他做的可视化图表,可以说就像是上个年代的东西,实在是不太美观。他也很苦恼,问我有没有什么好用... 查看详情

Go Iterator 从 Bigquery 读取 100 万行,比 Java 或 kotlin 慢 10 倍?

...ry并索引Elasticsearch中的一些字段。这将是一个一次性的批处理作业。由于团队拥有Java知识, 查看详情

一个比springboot快44倍的java框架

...副处级干部选哪个?最近看到一个框架,官方号称可以比SpringBoot快44倍,居然这么牛逼,有这么神奇吗?今天带大家来认识一下。这个框架名叫:light-4j。官网简介:Afast,lightweightandmoreproductivemicroservicesfra 查看详情

Java 使用数组比 C++ 中的 std::vector 快 8 倍。我做错了啥?

】Java使用数组比C++中的std::vector快8倍。我做错了啥?【英文标题】:Java8timesfasterwitharraysthanstd::vectorinC++.WhatdidIdowrong?Java使用数组比C++中的std::vector快8倍。我做错了什么?【发布时间】:2015-06-2119:01:53【问题描述】:我有以下Java... 查看详情

julia入门学习教程(代码片段)

有一门语言,它看起来像Python,感觉起来像Lisp,运行起来又像C一样快速,他就是Julia。近年来,Julia语言已然成为编程界的新宠,尤其在科学计算和人工智能领域炙手可热。据JuliaComputing的宣传,在七项基础算法的测试中,Julia... 查看详情

为什么(我的)java比c++快25倍?(代码片段)

当我想将它与C++进行比较时,我正在玩Java中的一些FizzBu​​zz代码。我在Java中运行下面的代码,在C++中基本上使用相同的代码。JavaEclipseIDE运行时:11秒C++代码阻止IDE运行时:202秒(比Java慢大约18倍)C++VisualStudioIDE运行时:281秒... 查看详情

比标准attention快197倍!meta推出多头注意力机制“九头蛇”

...觉任务上“大展身手”,但还有一个问题。那就是在处理大图像上计算比较费劲。比如面对一个1080p的图时,它会有超过60%的计算量都耗费在了创建和应用注意矩阵上。究其原因,主要是因为自注意力头的数量是token... 查看详情