应用背景:
工作需要将表格按某些条件拆分成多个工作薄,且一个工作薄中再按某些条件存放多个工作表,且保留格式,例如颜色,合并单元格,字体大小等等,主要用于表头的复制时保留格式。
难点:
1.将数据保存到同一工作薄的不同工作表。
2.复制excel表中的某些内容并保留对应的格式。
实现目标:完美地模拟“将一个总表数据,筛选负责人拆分成对应的负责人表,且在每一个表内分成库存和备货两张子表,并对表头进行处理”
多个dataframe需要写入同一个excel时,每次使用df.to_excel(文件名)的形式去写,系统都会重新创建一个新的文件。也就意味着前面的文件会被覆盖掉,你得到的只能是最后一个df写入的结果文件
通过创建一个ExcelWriter对象,可以解决上面的问题
import pandas as pd
writer = pd.ExcelWriter(os.path.join(os.getcwd(), '自定义.xlsx'))
df1.to_excel(writer, sheet_name='自定义sheet_name')#startcol=**, startrow=**)
df2.to_excel(writer, sheet_name='自定义sheet_name')#startcol=**, startrow=**)
df3.to_excel(writer, sheet_name='自定义sheet_name')#startcol=**, startrow=**)
...
writer.save()# 写入硬盘
#不传sheet_name参数时,默认多个dataframe会写入同一个xlsx文件的同一个sheet里
#startrow, startcol 不传时默认dataframe在excel里在sheet1里其实行列都是一
#需注意,python和excel的索引起始数字不同,如果startrow=2, startcol=4,不是从Excel第二行第四列开始写,而是从Excel第三行第五列开始写
#注意一定要用write.save(),不然生成的文件是没有对应的内容的
原文链接:
https://blog.csdn.net/weixin_42130167/article/details/89705581
上文中使用pandas进行数据处理的时候,输出的表格都是不带格式的,甚至于在读取存在“合并单元格”的数据的时候,还会出现一些难以处理的空值(基于个人理解,excel表格中的合并单元格,显示的值是取第一项的值,其他都只是空值)。
而实际工作中,领导要求的表格都是要好看的格式以及一些表头,颜色等等,因此使用pandas来对特定的表头格式进行完整的复制粘贴。
import easygui as eg
import os
import openpyxl
import copy
import pandas as pd
代码如下:
def colwidth(s0, s1):
for i, row in enumerate(s0.iter_cols()):
# i = chr(i+97).upper()
i = convertToTitle(i+1)
lk = s0.column_dimensions[i].width
if lk == 0:
lk = 8.38
s1.column_dimensions[i].width = lk
其中s0是待复制excel表,s1是待粘贴excel表,都是openpyxl的Worksheet对象,即工作表对象。
enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for循环当中。
openpyxl读取一个sheet所有列数据除了用cols属性,也可以用iter_cols()方法,返回值是生成器,该方法也可以指定读取行和列,和iter_rows不同的是他按照列来输出数据。
综上所述,enumerate(s0.iter_cols())生成的是一个下标对应列的生成器,i默认初始等于0,对应的是’A列’,可通过chr()将数字对应转化为字母(用于下文中column_dimensions的引用)。
但是chr()对于生成”两位数的字母“即不能生成’AA’,而实际上的excel表可能会超过’AA’列,就会产生等同于’超出索引值’效果的报错,不符合实际需求。
>>>i = 0 #生成器生成的列中'A'对应的下标是0
>>>i = chr(i+97).upper()
>>>i
'A'
>>>i = 26
>>>a = chr(i+97).upper()
>>>a
'{'
此时可以看到当i超出25(26个字母,因此最后一项是25)时,对应生成的不是’AA’,而是chr()本身编码中对应的值。
解决方法:
def convertToTitle(n):
# n为添入的数值,此时1对应的是'A',0无对应,所以在应用时,注意赋予的n是否要+1
"""
:type n: int
:rtype: str
"""
rStr = ""
while n != 0:
res = n % 26
if res == 0:
res = 26
n -= 26
rStr = chr(ord('A') + res - 1) + rStr
n = n // 26
return rStr
# 返回字母
此时,数字下标就和excel表格中的列一一对应上了。
代码如下:
def rowheight(s0, s1):
for i in range(1, s0.max_row+1):
hg = s0.row_dimensions[i].height
if hg == 0:
hg = 15
s1.row_dimensions[i].height = hg
行的序号由纯数字组成,不存在字母与数字转化间的问题,但在使用range的时候注意与实际行数的对应关系。
def copycell(s0, s1, min_row=None, max_row=None, min_col=None, max_col=None):
for i, row in enumerate(s0.iter_rows(min_row=min_row, max_row=max_row, min_col=min_col, max_col=max_col)):
for j, cell in enumerate(row):
s1.cell(i+1, j+1, cell.value)
s1.cell(i+1, j+1).number_format = cell.number_format
s1.cell(i+1, j+1).alignment = copy.copy(cell.alignment)
s1.cell(i+1, j+1).font = copy.copy(cell.font)
s1.cell(i+1, j+1).border = copy.copy(cell.border)
s1.cell(i+1, j+1).fill = copy.copy(cell.fill)
其中,i代表第几列,row代表这一列下的所有单元格cell对象组成元组。通过enumerate(),将row中的所有单元格及其对应的行号列出。
iter_rows()和iter_cols()的参数都有(min_row=None, max_row=None, min_col=None, max_col=None),因此可以通过参数设置来圈定我们想要复制粘贴的区域,例如,我想复制前两行,那我就将max_row=2,以此来达成表头的复制粘贴的效果。
import easygui as eg
import os
import openpyxl
import copy
import pandas as pd
# 用于数字与excel列字母之间的转换
def convertToTitle(n):
"""
:type n: int
:rtype: str
"""
rStr = ""
while n != 0:
res = n % 26
if res == 0:
res = 26
n -= 26
rStr = chr(ord('A') + res - 1) + rStr
n = n // 26
return rStr
# 复制列宽
def colwidth(s0, s1):
for k, co in enumerate(s0.iter_cols()):
k = convertToTitle(k+1)
lk = s0.column_dimensions[k].width
if lk == 0:
lk = 8.38
s1.column_dimensions[k].width = lk
# 复制行宽
def rowheight(s0, s1):
for i in range(1, s0.max_row+1):
hg = s0.row_dimensions[i].height
if hg == 0:
hg = 15
s1.row_dimensions[i].height = hg
# 复制单元格
def copycell(s0, s1, min_row=None, max_row=None, min_col=None, max_col=None):
for i, row in enumerate(s0.iter_rows(min_row=min_row, max_row=max_row, min_col=min_col, max_col=max_col)):
for j, cell in enumerate(row):
s1.cell(i+1, j+1, cell.value)
s1.cell(i+1, j+1).number_format = cell.number_format
s1.cell(i+1, j+1).alignment = copy.copy(cell.alignment)
s1.cell(i+1, j+1).font = copy.copy(cell.font)
s1.cell(i+1, j+1).border = copy.copy(cell.border)
s1.cell(i+1, j+1).fill = copy.copy(cell.fill)
# 执行复制
def copyfile(sht, sht1):
colwidth(sht, sht1)
rowheight(sht, sht1)
copycell(sht, sht1)
for merg in sht.merged_cells:
sht1.merge_cells(str(merg))
# 进行数据处理后复制表头
def screen_data(fn):
df = pd.read_excel(fn, header=1)
g = os.path.dirname(fn)
# 把要复制的表头和要处理的表格放在同一个目录内
header_file = os.path.join(g, '产品总表_表头.xlsx')
wb = openpyxl.load_workbook(header_file)
name_list = list(df['负责人'].drop_duplicates('first'))
for name in name_list:
file_name = os.path.join(g, name + '库存及备货表.xlsx')
writer = pd.ExcelWriter(file_name)
# 进行数据处理,生成对应的表
for sheet_name in wb.sheetnames:
header_df = pd.read_excel(header_file, header=1, sheet_name=sheet_name)
header = header_df.columns
mid_df = pd.DataFrame(df[df['负责人'] == name], columns=header)
mid_df.to_excel(writer, sheet_name=sheet_name, index=False, startrow=1)
# 一定要先保存并且关闭,否则无法生成有效的excel表
writer.save()
writer.close()
# 进行对应工作薄的各个工作表的表头的复制
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
wb1 = openpyxl.load_workbook(file_name)
ws1 = wb1[sheet_name]
copyfile(ws, ws1)
wb1.save(file_name)
if __name__ == '__main__':
ff = eg.fileopenbox('选择文件', '选择文件')
screen_data(ff)
本博文在解决“python 如何将多个DataFrame保存到不同工作表”,“python 如何复制粘贴excel表中的格式到另一个excel表” 这类的问题时应该都能提供些帮助,或者是直接可套用(伸手党)的代码。因为在长期的自学自用的过程中,知道了百度的重要性,更知道了上网找资料时的心态(能直接套用就最好了),大多数时候都不需要了解原理,只需要解决当前问题即可,因此对于关键的功能点都有单独拎出描述,望能帮助读者解决问题。
本人非专业的IT工作人员,各类参数调整与自身实际工作时的应用问题,可在评论中提出,一起讨论解决,但我不一定能解决,请见谅。
这是第一次写一篇正式的博文,如有不足之处,可指出改正,谢谢。