tkdahai 发表于 2023-5-17 20:20

闲来无事,写个给表格分组或合并的小工具

玩python 时间也不长,有时办公要用到EXCEL里不断重复的分组,合并数据,ctrl+v,ctrl+c ,搞的手指发麻,于是就抽空搞了这么个小工具,用的是pandas,网上,同时也借用了一些大佬的作品,揉合而成。放在这与大家共勉,一起学习,bug不断,欢迎留下脚印。
      
from PyQt5.QtNetwork import QLocalSocket, QLocalServer# 实例化程式,只许一个运行
from PyQt5 import QtCore
from PyQt5.QtGui import QTextCursor, QColor
from PyQt5.QtWidgets import *
#from PyQt5.QtWidgets import QApplication, QFileDialog
import sys,os
import pandas as pd
from PyQt5.QtWidgets import QApplication, QComboBox, QDialog, QVBoxLayout, QLabel, QPushButton,QFileDialog
from win32com.client import Dispatch
import openpyxl
class ComboCheckBox(QComboBox):
    def __init__(self, items: list):
         """
         initial function
         :param items: the items of the list
         """
         super(ComboCheckBox, self).__init__()
         self.items = ["全选"] + items# items list
         self.box_list = []# selected items
         self.text = QLineEdit()# use to selected items
         self.state = 0# use to record state

         q = QListWidget()
         for i in range(len(self.items)):
             self.box_list.append(QCheckBox())
             self.box_list.setText(self.items)
             item = QListWidgetItem(q)
             q.setItemWidget(item, self.box_list)
             if i == 0:
               self.box_list.stateChanged.connect(self.all_selected)
             else:
               self.box_list.stateChanged.connect(self.show_selected)

         q.setStyleSheet("font-size: 30px; font-weight: bold; height: 50px; margin-left: 15px")
         #self.setStyleSheet("width: 300px; height: 50px; font-size: 21px; font-weight: bold")
         self.text.setReadOnly(True)
         self.setLineEdit(self.text)
         self.setModel(q.model())
         self.setView(q)

    def all_selected(self):
         """
         decide whether to check all
         :return:
         """
         # change state
         if self.state == 0:
             self.state = 1
             for i in range(1, len(self.items)):
               self.box_list.setChecked(True)
         else:
             self.state = 0
             for i in range(1, len(self.items)):
               self.box_list.setChecked(False)
         self.show_selected()

    def get_selected(self) -> list:
         """
         get selected items
         :return:
         """
         ret = []
         for i in range(1, len(self.items)):
             if self.box_list.isChecked():
               ret.append(self.box_list.text())
         return ret

    def show_selected(self):
         """
         show selected items
         :return:
         """
         self.text.clear()
         ret = '; '.join(self.get_selected())
         self.text.setText(ret)
class MyDialog(QDialog):
    def __init__(self):
      #定义列名列表大A-Z
      col_list = list(map(chr, range(65, 91)))
      self.file_name = ''
      self.output = 'output.xlsx'# 输出OUT表


      #self.win__dir = os.environ['HOMEPATH'] + '//' + 'Desktop'# 打开文件默认活动桌面
       # RC = read.read_config(self=read)# 传总参,导入配置文件,为后面的参数做基础
      # 传参,导入压缩图片类和其参数(参数读取自配置文件)
      # self.Result = result.Compress_img(RC.get('api', 'rate'), RC.get('api', 'quality'), RC.get('api', 'way'))
      # self.url = RC.get('api', 'url')#从配置文件提参数get示例
      ####sytext水印文字
      # sytext = RC.get('sylogo','sytime')+'\n'+RC.get('sylogo', 'syadd')+'\n'+RC.get('sylogo', 'syen')
      super().__init__()
      """控件的创建和布局"""
      ########1。先创建按键筐
      #self.lineEdit = QLineEdit("")# 路经显示
      self.textEdit = QTextEdit()# 富文本显示
      ###################################
      self.pushExcel = QLabel("Excel表:")
      #self.pushExcel.setMinimumWidth(100)
      self.lineExcel = QLineEdit("")# 路经显示
      #self.lineExcel.setMinimumWidth(100)
      self.selExcel = QPushButton("请选择")
      #self.selExcel.setMinimumWidth(100)
      ################################
      self.pushGroup = QLabel("分组列: ")
      #self.pushGroup.setMinimumWidth(50)
      #self.listGroup = QLineEdit("")# 路经显示
      self.listGroup = ComboCheckBox(col_list)
       # self.listGroup.setMinimumWidth(300)
      ########################
      self.pushMerge = QLabel("合并列: ")
      #self.pushMerge.setMinimumWidth(50)
      #self.listMerge = QLineEdit("")# 路经显示
      self.listMerge = ComboCheckBox(col_list)
      #self.listMerge.setMinimumWidth(50)
      #########################
      self.pushGO = QPushButton("开始处理")
      #self.pushGO.setMinimumWidth(50)
      self.pushSee = QPushButton("结果查询")
      #self.pushSee.setMinimumWidth(50)

      ######################2.创建第一排水平表单布局
      self.hflo0 = QFormLayout()# 创建水平表单布局
      self.hflo1 = QFormLayout()# 创建水平表单布局
      self.hflo2 = QFormLayout()# 创建水平表单布局

      ######################2.1创建第二排水平表单布局
      self.hflo11 = QFormLayout()# 创建水平表单布局
      self.hflo12 = QFormLayout()# 创建水平表单布局
      ######################2.1创建第三排水平表单布局
      self.hflo21 = QFormLayout()# 创建水平表单布局
      self.hflo22 = QFormLayout()# 创建水平表单布局
      ######################2.1创建第四排水平表单布局
      self.hflo31 = QFormLayout()# 创建水平表单布局
      self.hflo32 = QFormLayout()# 创建水平表单布局
      ###############3.将第一排水平表单和按键结合
      self.hflo0.addRow(self.pushExcel)
      self.hflo1.addRow(self.lineExcel)
      self.hflo2.addRow(self.selExcel)

      ###############3.1将第二排水平表单和按键结合
      self.hflo11.addRow(self.pushGroup)
      self.hflo12.addRow(self.listGroup)
      ###############3.1将第三排水平表单和按键结合
      self.hflo21.addRow(self.pushMerge)
      self.hflo22.addRow(self.listMerge)
      ###############3.1将第4排水平表单和按键结合
      self.hflo31.addRow(self.pushGO)
      self.hflo32.addRow(self.pushSee)
      ################4.创建垂直表单布局 路经显示和富文本
      self.vflo = QFormLayout()# 创建垂直表单布局 路经显示和富文本
      #self.vflo.addRow(self.lineEdit)
      self.vflo.addRow(self.textEdit)
      ################5. 添加水平布局第一排(也可以再加一排)
      self.hbox = QHBoxLayout()
      self.hbox.addLayout(self.hflo0)
      self.hbox.addLayout(self.hflo1)
      self.hbox.addLayout(self.hflo2)
      self.hbox.setSpacing(0)
      ################5.1 添加水平布局第二排(也可以再加一排)
      self.hbox2 = QHBoxLayout()
      self.hbox2.addLayout(self.hflo11)
      self.hbox2.addLayout(self.hflo12)
      self.hbox2.setSpacing(0)
      ################5.1 添加水平布局第三排(也可以再加一排)
      self.hbox3 = QHBoxLayout()
      self.hbox3.addLayout(self.hflo21)
      self.hbox3.addLayout(self.hflo22)
      self.hbox3.setSpacing(0)
      ################5.1 添加水平布局第4排(也可以再加一排)
      self.hbox4 = QHBoxLayout()
      self.hbox4.addLayout(self.hflo31)
      self.hbox4.addLayout(self.hflo32)
      self.hbox4.setSpacing(0)
      ###############6. 添加垂直布局
      self.vbox = QVBoxLayout()
      self.vbox.addLayout(self.vflo)
      # self.vbox.addLayout( self.vfl1 )
      ###################### 添加布局
      vlayout = QVBoxLayout()# 整个程序的灵魂,将QVBoxLayout改成QHBoxLayout可以改变hbox和vbox的布局从垂直布局到水平布局
      vlayout.addLayout(self.hbox)# 组合第一排
      vlayout.addLayout(self.hbox2)# 组合第二排
      vlayout.addLayout(self.hbox3)# 组合第二排
      vlayout.addLayout(self.hbox4)# 组合第二排
      vlayout.addLayout(self.vbox)# 组合第三列
      self.setLayout(vlayout)
      self.textEdit.setFontPointSize(15)# 设置富文本的字体大小
      self.textEdit.setReadOnly(False)# 设置为只读、可写
      self.textEdit.setTextBackgroundColor(QColor(80, 80, 80))# 设置文字背景色
      self.textEdit.setTextColor(QColor(200, 220, 180))# 设置文字颜色
      ################################################
      """信号绑定"""###信号绑定必须也函数一一对应,否则不显示窗口
      self.selExcel.clicked.connect(self.choose_file)# 选择文件
      self.pushGO.clicked.connect(self.load_file)#执行文件
      self.pushSee.clicked.connect(self.load_See)#查绚结果
      # # self.jsGO.clicked.connect(self.__onClickejyGO)    #极速录单
      # self.chromedebug.clicked.connect(self.__chromedebug)# 谷歌浏览器
      # self.pushIMGSY.clicked.connect(self.__pushIMGSY)# 图片压缩
      # #self.lineEdit.clicked.connect(self.__lineEdit)# 图片压缩
      # self.lineEdit.returnPressed.connect(self.__lineEdit)#路经显示回车激活函数

    #####################################################################
    def choose_file(self):
      try:
            '''选择EXCEL文件'''
            options = QFileDialog.Options()
            options |= QFileDialog.DontUseNativeDialog
            file_name, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "Excel files (*.xlsx)", options=options)
            if file_name:
               # print(file_name)
                self.lineExcel.setText(file_name)
                self.file_name = file_name
                df = pd.read_excel(self.file_name)
                # 读取原表的列名并生成列表
                list_df = list(df)
                # 以原列列表为基础,生成大写字母的同量列表
                list_dfmap = list(map(chr, range(65, 65 + len(list_df))))
                # 交两个列表合并生成新的对应字典,方便后面以大写字母调用列名
                Clist = dict(zip(list_dfmap, list_df))
                print(Clist)
                self.textEdit.setText('提示:从下面的对应关系上选择分组与合并列\n'+ str(Clist))
      except:
            print('文件选择出错')




    def load_file(self):
      try:
            self.close_excel_file(self.output)
            '''加载EXCEL文件,写入列表名'''
            print('开始载入列名')
            # 读取Excel文件
            if len(self.file_name) <=0:
                print('请选择需要处理的表格文件')
                QMessageBox.information(self, '提示', "请选择需要处理的表格文件")
            else:
                print(self.file_name)
                #读取表格内容,并将Dataframe利用applymap(str)函数全部转为字符串格式,以防报错
                df = pd.read_excel(self.file_name).applymap(str)
                # 读取原表的列名并生成列表
                list_df = list(df)
                # 以原列列表为基础,生成大写字母的同量列表
                list_dfmap = list(map(chr, range(65, 65 + len(list_df))))
                # 交两个列表合并生成新的对应字典,方便后面以大写字母调用列名
                Clist = dict(zip(list_dfmap, list_df))
                print(Clist)
                #读取分组的列名大写字母
                see_Group = self.listGroup.get_selected()
                # 读取合并的列名大写字母
                see_Merge = self.listMerge.get_selected()
                # 跟据用户选择的分组,合并列的大写字木,在字母列名的字典里读取出来,生成各自列表
                hb_name = list(Clist.get(i) for i in see_Group)
                hb_value = list(Clist.get(i) for i in see_Merge)
                print(hb_name)
                print(hb_value)
                #检查分组和合并两个列表是否有交集,并返回交集
                list_c = list(set(see_Group) & set(see_Merge))
                print(list_c)
                # 通过交集列表长度判断,如果有交集则返回提示信息,否则执行后面操作
                if len(list_c) >= 1:
                  QMessageBox.information(self, '提示', "分组与合并列不能有重合{}".format(list_c))
                else:
                  print('开始进行分组合并操作')
                  if len(see_Group) >=1:
                        print('分组列非空,执行下面代码')
                        if len(see_Merge) >=1:
                            print('合并列非空,执行分组合并任务')
                            # 生成合并字典式,value_y为合并里面的代码,只能用字典
                            value_y = {'one': lambda x: '、'.join(set(x))}
                            # print(value_y)
                            print(value_y)
                            # 通常用来初始化字典, 设置value的默认值,
                            value = dict.fromkeys(hb_value, value_y.get('one'))
                            # 按照hb_name列进行分组,并将hb_value里的所有列分别合并为一个逗号分隔的字符串,同时去重,转换为DataFrame
                         #   result = df.groupby(hb_name).agg(value)
                            result = df.groupby(hb_name).agg(value)
                            # 将原数据按名子分组
                            df1 = df.drop_duplicates(subset=hb_name)
                            # 将result与原始数据按照name列进行合并,保留其他列的内容
                            result = pd.merge(df1.drop(columns=hb_value).drop_duplicates(), result, on=hb_name)
                            # 按最原表的列顺序展示新表,list_df为原表的列表
                            result = result
                            print(result)
                            result.to_excel(self.output, index=False)
                            QMessageBox.information(self, '提示', "以{}分组,以{}合并完成".format(hb_name,hb_value))

                        else:
                            print('合并列为空,单执行分组任务')
                            # 将原数据按名子分组
                            hb_Group = df.drop_duplicates(subset=hb_name)
                            print(hb_Group)
                            hb_Group.to_excel(self.output, index=False)
                            QMessageBox.information(self, '提示', "以{}分组完成".format(hb_name))
                  else:
                        print('分组列为空,请选择')
                        QMessageBox.information(self, '提示', "分组列为空,请选择")
      except:
            print('执行分组合并出错')

    def load_See(self):
      try:
            #查询结果
            print("查询结果开始")
            # 打开查询到的数据表格
            print(self.output)

            os.startfile(self.output)
            #self.close_excel_file(self.output)# 关掉OUT表

      except:
            print('查询结果出错')
    def close_excel_file(self,closeexcel):
      #关掉指定的表格文件
      xlApp = Dispatch('Excel.Application')
      xlApp.DisplayAlerts = False# 设置不显示警告和消息框
      # xlBook = xlApp.Workbooks.Open(file)
      workbooks_n = xlApp.Workbooks.Count
      print(f'已打开工作簿的数量为:{workbooks_n}个')

      if workbooks_n < 0: return

      for i in range(1, workbooks_n + 1):# 工作簿索引从1开始
            path_ = xlApp.Workbooks(i).Path
            name_ = xlApp.Workbooks(i).Name
            path = path_ + "\\backup_" + name_
            # print(f'第{i}个excel的文件路径为:{path}')
            if (closeexcel in path):
                xlApp.Workbooks(i).Close() #关闭当前打开的文件,不保存文件
               # xlApp.Workbooks(i).Activate()
                #xlApp.Workbooks(i).SaveAs(path)
                #xlApp.Workbooks(path).Close()

      # xlApp.Quit()#关闭所有打开的excel文件
      del xlApp

if __name__ == "__main__":
    try:

      app = QApplication(sys.argv)
      serverName = 'testEvaluatinoServer601'
      socket = QLocalSocket()
      socket.connectToServer(serverName)
      # 如果连接成功,表明server已经存在,当前已有实例在运行
      if socket.waitForConnected(601):
            app.quit()

      else:
            localServer = QLocalServer()# 没有实例运行,创建服务器
            localServer.listen(serverName)
            dialog = MyDialog()
            dialog.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)# 窗口置顶
            dialog.show()
            dialog.setWindowTitle('Excel分组合并 by.182294401')

            # dialog.move( int( rc.read_config() ), int( rc.read_config() ) )# 窗口靠右
          #dialog.resize(600, 500)# 显示框大小
            sys.exit(app.exec_())
            pass
    except:
      pass

keithu 发表于 2023-5-17 23:16

有没有一个浅显的功能说明?

yu520 发表于 2023-5-18 08:39

这个是怎么使用,来个功能简介

abc023119 发表于 2023-5-18 09:12

来个成品 感谢

lingwushexi 发表于 2023-5-18 09:20

感谢分享{:1_921:}

pangdundun 发表于 2023-5-18 13:52

咳,拿来主义了解下PyQt

tkdahai 发表于 2023-5-19 20:19

keithu 发表于 2023-5-17 23:16
有没有一个浅显的功能说明?

首先选择需要操作的表格,1.按列名分组,可以一列也可以多列,2.在分组的基础上,合并需要的列的数据,并以顿号分开,同时去掉重复数据。

tkdahai 发表于 2023-5-19 20:52

分享成品下截地址:https://share.weiyun.com/XilMJc4u

isunfly 发表于 2024-8-25 11:03

收藏一下,后续使用
页: [1]
查看完整版本: 闲来无事,写个给表格分组或合并的小工具