Python处理Excel比Vba快100倍,媳妇连连夸赞今晚不用再跪搓衣板----python实战

news2024/11/16 5:49:56

最近经历了一次把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里面的某些单元格也是效率较低的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/98497.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

水一篇,VB+python实现智能聊天机器人案例

1.分工 理论上单python也能写,但是做gui开发,python要用到thinter库/qt库,稍微麻烦一点。这个案例是python做json截取,VB做gui开发截取json字符。 2.准备工作 编写生成file_controlv2.dll并注册,编写speaker.vbs,准备…

java实现获取当前日期、农历、周

大家好,我是雄雄。 前言 大家先看下面的一段话: 今天是:2022年12月18日,星期日,农历十一月廿五,早安🌞🌞🌞 1.讣告 | 我国著名眼科专家兰绪达在南昌逝世,享…

Linux 多线程(附带线程池代码加注释)

目录 01. Linux线程概念 01.1 什么是线程 01.1.1 轻量级进程ID与进程ID之间的区别 01.1.2 总结(重点) 01.2 线程的优点 01.3 线程的缺点 01.4 线程异常 01.5 线程用途 02. Linux进程VS线程 02.1 进程和线程 02.2 关于多线程和多进程编程 03…

Pytorch中的卷积与反卷积(conv2d和convTranspose2d)

卷积 卷积是特征提取的常用操作,卷积可以改变图片的通道和大小,相比全连接操作,卷积可以减少计算量,并且充分融合图像的局部特征。 import torch import torch.nn as nnx torch.randn(1,1,4,4) model nn.Conv2d(in_channels1,o…

Spring MVC学习 | 注解配置Spring MVC总结

文章目录一、注解配置Spring MVC1.1 初始化类1.2 Spring MVC配置类1.3 完整配置过程二、总结2.1 常用组件2.2 执行流程学习视频🎥:https://www.bilibili.com/video/BV1Ry4y1574R 一、注解配置Spring MVC 1.1 初始化类 🔑注解配置的原理 在…

非零基础自学Golang 第10章 错误处理 10.1 错误处理的方式 10.2 自定义错误

非零基础自学Golang 文章目录非零基础自学Golang第10章 错误处理10.1 错误处理的方式10.2 自定义错误10.2.1 错误类型10.2.2 创建错误10.2.3 自定义错误格式第10章 错误处理 我们在编写程序时,为了加强程序的健壮性,往往会考虑到对程序中可能出现的错误…

大数据必学Java基础(一百一十三):监听器概念引入

文章目录 监听器概念引入 一、什么是监听器? 二、监听器怎么分类?

SQL - MySQL深分页

一、MySQL深分页问题 我们在日常开发中,查询数据量比较大的时候,后端基本都会通过前端,移动端传过来的页码,每页数据行数,通过SQL中的 limit 进行分页,如果查询页数比较小的时候,不会出现太大问…

【有营养的算法笔记】 二分+排序/堆 求解矩阵中战斗力最弱的 K 行

👑作者主页:进击的安度因 🏠学习社区:进击的安度因(个人社区) 📖专栏链接:有营养的算法笔记 ✉️分类专栏:题解 文章目录一、题目描述二、思路及代码实现1. 二分 排序2.…

【学习笔记】JDK源码学习之Vector(附带面试题)

【学习笔记】JDK源码学习之Vector(附带面试题) 什么是 Vector ?它的作用是什么?它的底层由什么组成?是否是线程安全的? 老样子,跟着上面的问题,我们层层深入了解 Vector 吧。 1、…

Linux——linux面试题

cat a.txt | cut -d "/" -f 3 | sort | uniq -c |sort -nrgrep ESTABLISHED | awk -F " " {print $5} |cut -d ":" -f 1 | sort |uniq -c | sort -nr找回mysql的root用户的密码 首先,进入到/etc/my.cnf,插入一句skip-gra…

Apache Hudi Timeline

Timeline | Apache Hudi Hudi维护了在不同时刻在表上执行的所有操作的时间线,这有助于提供表的即时视图,同时也有效地支持按到达顺序检索数据。Hudi的核心是维护表上在不同的即时时间(instants)执行的所有操作的时间轴&#xff08…

windows下配置chrome浏览器驱动的详细攻略

要想使用python去爬取互联网上的数据,尤其是要模拟登录操作。那么selenium包肯定是绕不过的。 selenium包本质上就是通过后台驱动的方式驱动浏览器去。以驱动chrome浏览器为例,搭建环境如下: 1、查看本机chrome浏览器的版本。 方式是&#x…

第三十二章 linux-模块的加载过程二

第三十二章 linux-模块的加载过程二 文章目录第三十二章 linux-模块的加载过程二HDR视图的第二次改写模块导出的符号HDR视图的第二次改写 在这次改写中,HDR视图中绝大多数的section会被搬移到新的内存空间中,之后会根据这些section新的内存地址再次改写…

[附源码]计算机毕业设计Python“小世界”私人空间(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等等…

知到/智慧树——程序设计基础(C语言)进阶篇

目录 第一章测试 第二章测试 第三章测试 第四章测试 第五章测试 第一章测试 第1部分总题数: 10 1 【单选题】 (10分) 在C语言中,将属于不同类型的数据作为一个整体来处理时,常用( )。 A. 简单变量 B. 数组类型数据 C. 结…

论文投稿指南——中文核心期刊推荐(力学)

【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…

10.union all、N天连续登录

有日志如下,请写出代码求得所有用户和活跃用户的总数及平均年龄。(活跃用户指连续两天都有访问记录的用户) 数据准备 最后需完成的结果表 步骤1,所有用户的总数及平均年龄 (1). 将数据去重 with t1 as (select distinctuser_i…

如何使用交换机、路由器及防火墙进行组网以及他们之间的功能和区别

如何使用交换机、路由器及防火墙进行组网以及他们之间的功能和区别。 几乎大部分网络都有交换机、路由器和防火墙这三种基本设备,因此这三种设备对于网络而言非常重要,很多人对这三种设备的使用容易弄混。 一般网络部署: 或者抽象为这种部署模式: 几乎每个网络都有交换…

别再写jsp了,Thymeleaf它不香吗?

啥是 Thymeleaf在学 Thymeleaf 之前我们先看一下使用 jsp 开发遇到的主要问题&#xff1a;jsp 的痛点1.页面包含大量 java 代码&#xff0c;代码太混乱<% page contentType"text/html;charsetUTF-8" language"java" %> <html> <head> &l…