合并两个excel表格工具
时间:2023-04-29 14:07:00
前言
有时候我们需要合并两个相关的表格。比如表1有考生号, 姓名, 语文成绩字段2有考生号, 姓名, 数学成绩 “考生号, 姓名, 语文成绩, 数学成绩。

示例
原来a, b表格
导出后:
代码
main.py
""" Author: Andy Dennis Date: 2022.7.29 Details: 其中ComBoPicker代码来自https://blog.csdn.net/weixin_45774074/article/details/123293411 """ from tkinter import * import tkinter as tk from ComBoPicker import Combopicker# 导入
自定义下拉多选框 import tkinter.font as tf from tkinter import filedialog, messagebox import traceback import pandas as pd main_xlsx = '' aux_xlsx = '' join_df = None # 选择主表 def select_main_xlsx(): global main_xlsx global join_df fp = filedialog.askopenfilename(title=u请选择主表) print(fp) try: if fp is not None and len(fp) > 0: if fp.split('.')[-1] not in ['xls', 'xlsx']: tk.messagebox.showerror('error', '请选择xls/xlsx的文件') return main_xlsx = fp lb1['text'] = fp join_df = None print('main_xlsx: ', main_xlx) lb5['text'] = '暂无提示' # tk.messagebox.showinfo('success', '选择原始目录成功') else: tk.messagebox.showerror('error', '请选择主表失败') except: tk.messagebox.showerror('error', '请选择主表失败') print(traceback.format_exc()) # 选择副表 def select_aux_xlsx(): global aux_xlsx global join_df fp = filedialog.askopenfilename(title=u'请选择副表') print(fp) try: if fp is not None and len(fp) > 0: if fp.split('.')[-1] not in ['xls', 'xlsx']: tk.messagebox.showerror('error', '请选择xls/xlsx的文件') return aux_xlsx = fp lb2['text'] = fp join_df = None print('aux_xlsx: ', aux_xlsx) lb5['text'] = '暂无提示' # tk.messagebox.showinfo('success', '选择原始目录成功') else: tk.messagebox.showerror('error', '请选择副表失败') except: tk.messagebox.showerror('error', '请选择副表失败') print(traceback.format_exc()) # 删除下拉框并新建一个 def destroy_and_new_one(values): global COMBOPICKER1 global window COMBOPICKER1.destroy() COMBOPICKER1 = Combopicker(window, values=values, entrywidth=50) COMBOPICKER1.place(x=100, y=160) def join_xlsx(): global COMBOPICKER1 global join_df if main_xlsx == '' or aux_xlsx == '': tk.messagebox.showerror('error', '请先选择好主表和副表') return main_df = pd.read_excel(main_xlsx) aux_df = pd.read_excel(aux_xlsx) main_df_cols = main_df.columns aux_df_cols = aux_df.columns print('main_df_cols: ', main_df_cols) print('aux_df_cols: ', aux_df_cols) join_cols_set = set(main_df_cols) & set(aux_df_cols) # 主键, 这里我直接让相同的字段名(列名)作为主键了 key_cols = [item for item in main_df_cols if item in join_cols_set] print('key_cols: ', key_cols) union_cols = [item for item in main_df_cols] # 添加附表其他列 for item in aux_df_cols: if item not in union_cols: union_cols.append(item) print('union_cols: ', union_cols) join_df = pd.merge(main_df, aux_df, on=key_cols, how='left') print('join_df:\n', join_df) destroy_and_new_one(union_cols) lb5['text'] = '暂无提示' def save_a_excel(save_df): fp = filedialog.asksaveasfilename(defaultextension='合并后的文件',filetypes=[("excel文件", ".xlsx")]) print(fp) try: if fp is not None and len(fp) > 0: if fp.split('.')[-1] not in ['xls', 'xlsx']: tk.messagebox.showerror('error', '请保存为xls/xlsx格式的文件') return save_df.to_excel(fp, index=False,header=True) # tk.messagebox.showinfo('success', '选择原始目录成功') else: tk.messagebox.showerror('error', '保存合并的表格失败') except: tk.messagebox.showerror('error', '保存合并的表格失败') print(traceback.format_exc()) def save_xlsx(): global join_df if main_xlsx == '' or aux_xlsx == '': tk.messagebox.showerror('error', '请先选择好主表和副表') return if join_df is None: tk.messagebox.showerror('error', '请先点击合并表格按钮') return choose_items = COMBOPICKER1.get() print(choose_items) if len(choose_items) == 0: tk.messagebox.showerror('error', '请选择要导出的列名') return else: choose_items = choose_items.split(',') union_cols = join_df.columns save_cols = [item for item in union_cols if item in choose_items] print('导出列: ', save_cols) lb5['text'] = '导出列: ' + ' '.join(save_cols) save_df = join_df[save_cols] print(save_df) save_a_excel(save_df) if __name__ == "__main__": window = Tk() # 标题 window.title('合并XLSX小软件') window.geometry("500x300") # 不允许重新设置大小 window.resizable('true', 'true') bt1 = tk.Button(window, text='选择主表', width=12, height=1, font=tf.Font(size=12), command=select_main_xlsx) bt1.place(x=60, y=24) bt2 = tk.Button(window, text='选择副表', width=12, height=1, font=tf.Font(size=12), command=select_aux_xlsx) bt2.place(x=220, y=24) lb1 = tk.Label(window, text='未选择主表', font=tf.Font(size=10)) lb1.place(x=10, y=68) lb2 = tk.Label(window, text='未选择副表', font=tf.Font(size=10)) lb2.place(x=10, y=90) bt3 = tk.Button(window, text='合并表格', width=18, height=1, font=tf.Font(size=12), command=join_xlsx) bt3.place(x=120, y=120) lb4 = tk.Label(window, text='导出字段:', font=tf.Font(size=10)) lb4.place(x=10, y=160) COMBOPICKER1 = Combopicker(window, values=['请读取表格', '读取表格'], entrywidth=50) # COMBOPICKER1.pack(anchor="w") COMBOPICKER1.place(x=100, y=160) bt4 = tk.Button(window, text='导出合并后的表格', width=18, height=1, font=tf.Font(size=12), command=save_xlsx) bt4.place(x=120, y=200) lb5 = tk.Label(window, text='暂无提示', font=tf.Font(size=10)) lb5.place(x=10, y=240) window.mainloop()
ComBoPicker.py
''' 引用自https://blog.csdn.net/weixin_45774074/article/details/123293411 自定义多选下拉列表 '''
import tkinter.font as tkFont
import tkinter.ttk as ttk
from tkinter import *
class Picker(ttk.Frame):
def __init__(self, master=None,activebackground='#b1dcfb',values=[],entry_wid=None,activeforeground='black', selectbackground='#003eff', selectforeground='white', command=None, borderwidth=1, relief="solid"):
self._selected_item = None
self._values = values
self._entry_wid = entry_wid
self._sel_bg = selectbackground
self._sel_fg = selectforeground
self._act_bg = activebackground
self._act_fg = activeforeground
self._command = command
ttk.Frame.__init__(self, master, borderwidth=borderwidth, relief=relief)
self.bind("" , lambda event:self.event_generate('<>' ))
self.bind("" , lambda event:self.event_generate('<>' ))
self._font = tkFont.Font()
self.dict_checkbutton = {
}
self.dict_checkbutton_var = {
}
self.dict_intvar_item = {
}
for index,item in enumerate(self._values):
self.dict_intvar_item[item] = IntVar()
self.dict_checkbutton[item] = ttk.Checkbutton(self, text = item, variable=self.dict_intvar_item[item],command=lambda ITEM = item:self._command(ITEM))
self.dict_checkbutton[item].grid(row=index, column=0, sticky=NSEW)
self.dict_intvar_item[item].set(0)
class Combopicker(ttk.Entry, Picker):
def __init__(self, master, values= [] ,entryvar=None, entrywidth=None, entrystyle=None, onselect=None,activebackground='#b1dcfb', activeforeground='black', selectbackground='#003eff', selectforeground='white', borderwidth=1, relief="solid"):
self.values=values
if entryvar is not None:
self.entry_var = entryvar
else:
self.entry_var = StringVar()
entry_config = {
}
if entrywidth is not None:
entry_config["width"] = entrywidth
if entrystyle is not None:
entry_config["style"] = entrystyle
ttk.Entry.__init__(self, master, textvariable=self.entry_var, **entry_config, state = "readonly")
self._is_menuoptions_visible = False
self.picker_frame = Picker(self.winfo_toplevel(), values=values,entry_wid = self.entry_var,activebackground=activebackground, activeforeground=activeforeground, selectbackground=selectbackground, selectforeground=selectforeground, command=self._on_selected_check)
self.bind_all("<1>", self._on_click, "+")
self.bind("" , lambda event: self.hide_picker())
@property
def current_value(self):
try:
value = self.entry_var.get()
return value
except ValueError:
return None
@current_value.setter
def current_value(self, INDEX):
self.entry_var.set(self.values.index(INDEX))
def _on_selected_check(self, SELECTED):
value = []
if self.entry_var.get() != "" and self.entry_var.get() != None:
temp_value = self.entry_var.get()
value = temp_value.split(",")
if str(SELECTED) in value:
value.remove(str(SELECTED))
else:
value.append(str(SELECTED))
value.sort()
temp_value = ""
for index,item in enumerate(value):
if item!= "":
if index != 0:
temp_value += ","
temp_value += str(item)
self.entry_var.set(temp_value)
def _on_click(self, event):
str_widget = str(event.widget)
if str_widget == str(self):
if not self._is_menuoptions_visible:
self.show_picker()
else:
if not str_widget.startswith(str(self.picker_frame)) and self._is_menuoptions_visible:
self.hide_picker()
def show_picker(self):
if not self._is_menuoptions_visible:
self.picker_frame.place(in_=self, relx=0, rely=1, relwidth=1 )
self.picker_frame.lift()
self._is_menuoptions_visible = True
def hide_picker(self):
if self._is_menuoptions_visible:
self.picker_frame.place_forget()
self._is_menuoptions_visible = False