用Python写一个Excel汇总和比对小程序

用Python写一个Excel汇总和比对小程序


前言

         最近由于工作需要,要多次比对两张Excel表里面的内容变化(有相同的行列索引),并将变化的单元格标注出来,所以想自己写个比对程序出来。
         虽然网上有类似工具,Python也有company库用来比对,但是都不怎么符合我的要求,索引还是自己写一个。这里我不详细介绍所有代码,重点介绍一下比对的思路和开发过程中遇到的一些坑。

一、错误思路

         我需要的是比对每一个单元格里面的内容是否发生变化,所以一开始我的想法就是根据行列索引遍历每个单元格进行比对,就像下面这样:

import openpyxl
import time

wb1 = openpyxl.load_workbook("./cs/1.xlsm")
wb2 = openpyxl.load_workbook("./cs/2.xlsm")

sheet1 = wb1.active
sheet2 = wb2.active

nrows = sheet1.max_row
ncols = sheet1.max_column
print(nrows)
print(ncols)

num = 1

start = time.time()
for row in range(1,nrows):
    for col in range(1,ncols):
        if sheet1.cell(row,col).value == sheet2.cell(row,col).value: #这里两张表是复制的,所以用相等
            # print("旧:%s---新:%s"%(sheet1.cell(row,col).value,sheet2.cell(row,col).value)) 
            pass
        print(num)
        num += 1
print(time.time()-start)

在这里插入图片描述
         我用的Excel表数据是3000行32列的,可以看出总共遍历循环96000次,用时1s多,但是我每次比对数据至少在120000条左右,也就是说我光循环比对至少40s左右,这还不算读取和最后写入的时间,效率太低。
         而且,这种方法还要一个前提,我必须提前将数据表处理好,使每一行都相互对应,如果出现新增和删减就没法得出结果,所有这种方法显然行不通。

二、使用Pandas的矢量化操作加速

1.先明白pandas处理大型数据集的一些经验法则

  • 尝试尽可能使用矢量化操作,而不是在df 中解决for x的问题。如果你的代码是许多for循环,那么它可能更适合使用本机Python数据结构,因为Pandas会带来很多开销。
  • 如果你有更复杂的操作,其中矢量化根本不可能或太难以有效地解决,请使用.apply方法。
  • 如果必须循环遍历数组(确实发生了这种情况),请使用.iterrows()或.itertuples()来提高速度和语法。
  • Pandas有很多可选性,几乎总有几种方法可以从A到B。请注意这一点,比较不同方法的执行方式,并选择在项目环境中效果最佳的路线。
  • 一旦建立了数据清理脚本,就可以通过使用HDFStore存储中间结果来避免重新处理。
  • 将NumPy集成到Pandas操作中通常可以提高速度并简化语法。

这里建议去看看《还在抱怨pandas运行速度慢?这几个方法会颠覆你的看法》这篇文章,对pandas 使用有很大启发。

2.正确的比对思路

1. 首先我们需要清楚要找出的不一样的内容包括哪些

这里假如有新旧两个表,且这两个表中的列名完全一致,也都有相同的行索引【唯一标识码】,相同的【唯一标识码】对应相同的数据。

在这里插入图片描述
因为涉及到增加和减少,那么两个表中就可能出现三种情况:

  • 数据减少了,那么旧表有新表没有的【唯一标识码】
  • 数据增加了,那么新表有旧表没有的【唯一标识码】
  • 【唯一标识码】相同的数据,里面内容发生变化了

2.得到两个表之间的交集、差集

import pandas as pd
import time

df1 = pd.read_excel("./cs/1.xlsm",index_col="唯一标识码")
df2 = pd.read_excel("./cs/2.xlsm",index_col="唯一标识码")

#将【唯一标识码】列设置为索引
df1.index = df1["唯一标识码"]
df2.index = df2["唯一标识码"]

index1 = df1.index
index2 = df2.index

jiaoJi = list(set(index1).intersection(set(index2)))  #两个列表中相同的项
chaJi1 = list(set(index1).difference(set(index2)))    #旧表不同于新表的数据,既旧表有,新表没有
chaJi2 = list(set(index2).difference(set(index1)))    #新表不同于旧表的数据,既新表有,旧表没有

print(jiaoJi[:10])
print(chaJi1[:10])
print(chaJi2[:10])

1.xlsx表中【唯一标识码】从1-3000,2.xlsx表中【唯一标识码】从1501-4500。

在这里插入图片描述
得出差集1和差集2就可以很容易通过行索引得出新增和减少的数据了

df_chaji1 = df1.loc[chaJi1]
df_chaji2 = df2.loc[chaJi2]
print(df_chaji1)
print(df_chaji2)

在这里插入图片描述

3.重点:【唯一标识码】相同部分怎么判断里面内容是否发生变化

1.举个例子

这里先给大家举个简单的案列,这里有如下的表格数据,我们要找出列1和列2数据不同的地方

在这里插入图片描述

df = pd.read_excel("./test.xlsx",index_col="唯一标识码")
res = df["列1"] == df["列2"]
print(res)

在这里插入图片描述
大家可以发现,在dataframe数据可以直接使用列与列进行比较,返回bool值,很方便。
那么我们两个表中的数据怎么进行列与列的比较呢?

2.对两个表的列名进行处理

首先,我们将两个表的列名进行重设,表1的列名就是【列名-1】,表2的列名就是【列-2】

columns = df1.columns.tolist()
columns1 = list(map(lambda x: x+"-1",columns))
columns2 = list(map(lambda x: x+"-2",columns))

df_jiaoji1 = df1.loc[jiaoJi]
df_jiaoji2 = df2.loc[jiaoJi]

df_jiaoji1.columns = columns1
df_jiaoji2.columns = columns2

print(df_jiaoji1)
print(df_jiaoji2)

在这里插入图片描述

3.将两个表进行横向合并
df_jiaoji_total = pd.concat([df_jiaoji1,df_jiaoji2],axis=1)  #axis=1表示横向合并
print(df_jiaoji_total[["分类编码-1","分类编码-2"]])

在这里插入图片描述
怎么样,看到这里知道我为什么要举上面的例子了把,就是为了能通过列直接比较。思路清楚了那就直接上代码:

4.通过dataframe直接进行相同列名的数据比对
start = time.time()
#比对所有同种类型的列的数据是否相等,得到一个bool值的series的迭代对象,并保存到ser_list列表中
ser_list = map(lambda x,y : df_jiaoji_total[x] == df_jiaoji_total[y] ,self.columns1,self.columns2)

#将每个series转换成dataframe数据类型,并用最早的列名进行设置(也可以不用,但是为了方便观察结果还是设置)
df_list = map(lambda x,y : x.to_frame(y),ser_list,self.columns)

#横向拼接所有的dataframe数据,数据为TRUE表示这个单元格比对结果相同,False表示不同
compare_df = pd.concat(df_list,axis=1)

#过滤所有数据都是TRUE的行的索引值,表示比对后前后结果相同,不需要记录
compare_index = list(filter(lambda x : bool(1-all(compare_df.loc[x])) , compare_df.index))

#根据最终得到的索引值可以确定哪些数据比对结果存在不同
#compare_bool用于定位哪些单元格出现差异
#compare_result则是存在差异的原始数据
self.compare_bool = compare_df.loc[compare_index]
self.compare_result = self.df2.loc[compare_index]

#将compare_bool的行、列索引设置成序号类型,方便写入时定位单位元格位置
nrows = compare_bool.shape[0]
ncols = compare_bool.shape[1]
compare_bool.index = range(nrows)
compare_bool.columns = range(ncols)

print(compare_bool)
print(compare_result)
print(time.time()-start)

在这里插入图片描述
大家可以很明显发现,遍历确定1500行31列的数据差异只需要0.25s,相较于之前的1.2s快了不知多少,而且在数据量越大的情况下越明显,有兴趣的可以测试一下。

5.数据的写入

这里我用openpyxl库来进行写入,主要是设置单元格格式比较简单,因为我们要将变化的数据的单元格填充为黄色

import openpyxl
from openpyxl.styles import PatternFill
start = time.time()
wb = openpyxl.Workbook()
ws_jiaoJi = wb.create_sheet("相同资产标识码内容发生变化",0)
# #创建单元格样式,填充为黄色,solid表示填充实色,不加不显示
fill = PatternFill("solid",fgColor="FFFF00")

#先把列名写入
ws_jiaoJi.append(columns)

#用compare_bool作为遍历对象,如果值为FALSE,表示数据发生变化了,单元格就填充为黄色
for i,r in compare_bool.iterrows():  
    for x,y in enumerate(r):
        if y == False:
            ws_jiaoJi.cell(row=i+2,column=x+1).fill = fill
        #用compare_result来遍历写入具体数据
        ws_jiaoJi.cell(row=i+2,column=x+1,value=compare_result.iloc[i,x])

wb.save("./compare.xlsx")
 
print(time.time()-start)

在这里插入图片描述
可以看到正确将比对结果输出为Excel表了

在这里插入图片描述
但这里不得不吐槽一下,写入的速度是真的慢,希望有高人指点一下可以怎么加快。
另外,如果大家还有更好的办法,希望可以不吝赐教。

源码下载

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

锦秀汐潮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值