{"id":62,"date":"2025-10-08T12:54:10","date_gmt":"2025-10-08T04:54:10","guid":{"rendered":"https:\/\/blog.genan.top\/?p=62"},"modified":"2025-10-08T12:54:10","modified_gmt":"2025-10-08T04:54:10","slug":"%e5%9f%ba%e4%ba%8epython%e7%9a%84excel%e8%a1%a8%e6%a0%bc%e8%87%aa%e5%8a%a8%e6%b1%87%e6%80%bb%e5%af%b9%e6%af%94%e8%84%9a%e6%9c%ac","status":"publish","type":"post","link":"https:\/\/blog.genan.top\/?p=62","title":{"rendered":"\u57fa\u4e8ePython\u7684Excel\u8868\u683c\u81ea\u52a8\u6c47\u603b\u5bf9\u6bd4\u811a\u672c"},"content":{"rendered":"\n<p>\uff08\u6ce8\uff1a\u6b64\u811a\u672c\u7531AI\u751f\u6210\uff09<\/p>\n\n\n\n<p>\u529f\u80fd\u4ecb\u7ecd\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5bf9\u8868\u683c\u4e2d\u7684\u6570\u636e\u8fdb\u884c\u6392\u5e8f\u6c47\u603b\uff1a\u8868\u683c\u4e2d\u6709\u5bb6\u5e97\u7684\u591a\u4e2a\u65e5\u671f\u7684\u9500\u552e\u91d1\u989d\u6570\u636e\uff0c\u6b64\u811a\u672c\u4f1a\u5c06\u6570\u636e\u6309\u7167\u65e5\u671f\u6392\u5217\uff0c\u7136\u540e\u5c06\u76f8\u540c\u95e8\u5e97\u7684\u6570\u636e\u6c47\u603b\u5728\u4e00\u8d77\uff0c\u6c47\u603b\u65b9\u5f0f\u6709\u201c\u6c42\u548c\u2018\u3001\u201d\u6700\u5927\u503c\u201c\u3001&#8221;\u6700\u5c0f\u503c&#8217;\u7b49\u3002<\/li>\n\n\n\n<li>\u6570\u636e\u5bf9\u6bd4\u529f\u80fd\uff1a\u811a\u672c\u8fd0\u884c\u540e\u53ef\u4ee5\u5bfc\u5165\u4e00\u4e2a\u6216\u8005\u4e24\u4e2aexcel\u8868\uff0c\u5bfc\u5165\u4e24\u4e2aexcel\u8868\u540e\uff0c\u53ef\u4ee5\u5b9e\u73b0\u6570\u636e\u4e4b\u95f4\u7684\u6bd4\u5bf9\uff0c\u9996\u5148\u9009\u62e9\u201d\u6bd4\u5bf9\u57fa\u51c6\u5217\u201c\uff0c\u4ee5\u6b64\u4e3a\u4f9d\u636e\u53ef\u4f7f\u4e24\u5f20\u8868\u7684\u6570\u636e\u5bf9\u4e0a\uff0c\u7136\u540e\u518d\u9009\u62e9\u201d\u6bd4\u5bf9\u5217\u201c\uff0c\u5f00\u59cb\u5bf9\u6bd4\u6570\u636e\u3002<\/li>\n\n\n\n<li>\u5728\u811a\u672c\u7684\u4e0b\u65b9\u53ef\u4ee5\u770b\u5230\u201d\u6c47\u603b\u7ed3\u679c\u201c\u3001\u2019\u5bf9\u6bd4\u7ed3\u679c\u2018\u548c\u2019\u5dee\u5f02\u8be6\u60c5\u2018\u3002\u8fd8\u53ef\u5bfc\u51fa\u7ed3\u679c\u3002<\/li>\n<\/ul>\n\n\n\n<p>\u8fd0\u884c\u811a\u672c\u524d\uff0c\u4f60\u53ef\u80fd\u9700\u8981\u7684\u5305\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import tkinter as tk\nfrom tkinter import ttk, filedialog, messagebox\nimport pandas as pd\nfrom pandas import ExcelFile\nimport os<\/code><\/pre>\n\n\n\n<p>\u4ee5\u4e0b\u662f\u5b8c\u6574\u4ee3\u7801\uff0c\u8c03\u6574\u597d\u7f16\u8bd1\u73af\u5883\u548c\u6240\u9700\u8981\u7684\u5305\u540e\uff0c\u53ef\u76f4\u63a5\u8fd0\u884c\u4f7f\u7528\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import tkinter as tk\nfrom tkinter import ttk, filedialog, messagebox\nimport pandas as pd\nimport numpy as np\nfrom pandas import ExcelFile\nimport os\n\n\nclass ExcelGroupAnalyzer:\n    def __init__(self, root):\n        self.root = root\n        self.root.title(\"Excel\u6570\u636e\u5206\u7ec4\u6c47\u603b\u4e0e\u6bd4\u5bf9\u5de5\u5177\")\n        self.root.geometry(\"1200x800\")\n\n        self.file_path = None\n        self.compare_file_path = None\n        self.sheet_names = &#91;]\n        self.compare_sheet_names = &#91;]\n        self.current_df = None\n        self.compare_df = None\n        self.current_sheet = None\n        self.compare_sheet = None\n\n        self.setup_ui()\n\n    def setup_ui(self):\n        \"\"\"\u8bbe\u7f6e\u7528\u6237\u754c\u9762\"\"\"\n        # \u4e3b\u6846\u67b6\n        main_frame = ttk.Frame(self.root, padding=\"10\")\n        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))\n\n        # \u6587\u4ef6\u9009\u62e9\u533a\u57df\n        file_frame = ttk.LabelFrame(main_frame, text=\"\u6587\u4ef6\u9009\u62e9\", padding=\"5\")\n        file_frame.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=5)\n\n        ttk.Button(file_frame, text=\"\u9009\u62e9\u4e3bExcel\u6587\u4ef6\", command=self.load_file).grid(row=0, column=0, padx=5)\n        self.file_label = ttk.Label(file_frame, text=\"\u672a\u9009\u62e9\u6587\u4ef6\")\n        self.file_label.grid(row=0, column=1, padx=5)\n\n        ttk.Button(file_frame, text=\"\u9009\u62e9\u6bd4\u5bf9Excel\u6587\u4ef6\", command=self.load_compare_file).grid(row=0, column=2, padx=5)\n        self.compare_file_label = ttk.Label(file_frame, text=\"\u672a\u9009\u62e9\u6bd4\u5bf9\u6587\u4ef6\")\n        self.compare_file_label.grid(row=0, column=3, padx=5)\n\n        # \u5de5\u4f5c\u8868\u9009\u62e9\u533a\u57df\n        sheet_frame = ttk.LabelFrame(main_frame, text=\"\u5de5\u4f5c\u8868\u9009\u62e9\", padding=\"5\")\n        sheet_frame.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=5)\n\n        ttk.Label(sheet_frame, text=\"\u4e3b\u5de5\u4f5c\u8868:\").grid(row=0, column=0, padx=5)\n        self.sheet_combo = ttk.Combobox(sheet_frame, state=\"readonly\", width=20)\n        self.sheet_combo.grid(row=0, column=1, padx=5)\n        self.sheet_combo.bind('&lt;&lt;ComboboxSelected>>', self.on_sheet_select)\n\n        ttk.Label(sheet_frame, text=\"\u6bd4\u5bf9\u5de5\u4f5c\u8868:\").grid(row=0, column=2, padx=5)\n        self.compare_sheet_combo = ttk.Combobox(sheet_frame, state=\"readonly\", width=20)\n        self.compare_sheet_combo.grid(row=0, column=3, padx=5)\n        self.compare_sheet_combo.bind('&lt;&lt;ComboboxSelected>>', self.on_compare_sheet_select)\n\n        # \u6570\u636e\u663e\u793a\u533a\u57df\uff08\u4f7f\u7528Notebook\u5b9e\u73b0\u6807\u7b7e\u9875\uff09\n        self.notebook = ttk.Notebook(main_frame)\n        self.notebook.grid(row=2, column=0, columnspan=4, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)\n\n        # \u4e3b\u6570\u636e\u6807\u7b7e\u9875\n        main_data_frame = ttk.Frame(self.notebook)\n        self.notebook.add(main_data_frame, text=\"\u4e3b\u6570\u636e\u9884\u89c8\")\n        self.setup_data_treeview(main_data_frame, \"main\")\n\n        # \u6bd4\u5bf9\u6570\u636e\u6807\u7b7e\u9875\n        compare_data_frame = ttk.Frame(self.notebook)\n        self.notebook.add(compare_data_frame, text=\"\u6bd4\u5bf9\u6570\u636e\u9884\u89c8\")\n        self.setup_data_treeview(compare_data_frame, \"compare\")\n\n        # \u5206\u7ec4\u8bbe\u7f6e\u533a\u57df\n        group_frame = ttk.LabelFrame(main_frame, text=\"\u5206\u7ec4\u8bbe\u7f6e\", padding=\"5\")\n        group_frame.grid(row=3, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=5)\n\n        ttk.Label(group_frame, text=\"\u57fa\u51c6\u5217(\u5206\u7ec4\u4f9d\u636e):\").grid(row=0, column=0, padx=5)\n        self.group_combo = ttk.Combobox(group_frame, state=\"readonly\", width=15)\n        self.group_combo.grid(row=0, column=1, padx=5)\n\n        ttk.Label(group_frame, text=\"\u6c47\u603b\u5217:\").grid(row=0, column=2, padx=5)\n        self.sum_combo = ttk.Combobox(group_frame, state=\"readonly\", width=15)\n        self.sum_combo.grid(row=0, column=3, padx=5)\n\n        ttk.Label(group_frame, text=\"\u6c47\u603b\u65b9\u5f0f:\").grid(row=0, column=4, padx=5)\n        self.agg_combo = ttk.Combobox(group_frame, values=&#91;'\u6c42\u548c', '\u5e73\u5747\u503c', '\u6700\u5927\u503c', '\u6700\u5c0f\u503c', '\u8ba1\u6570'],\n                                      state=\"readonly\", width=10)\n        self.agg_combo.set('\u6c42\u548c')\n        self.agg_combo.grid(row=0, column=5, padx=5)\n\n        # \u6bd4\u5bf9\u8bbe\u7f6e\u533a\u57df\n        compare_setting_frame = ttk.LabelFrame(main_frame, text=\"\u6bd4\u5bf9\u8bbe\u7f6e\", padding=\"5\")\n        compare_setting_frame.grid(row=4, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=5)\n\n        ttk.Label(compare_setting_frame, text=\"\u6bd4\u5bf9\u57fa\u51c6\u5217:\").grid(row=0, column=0, padx=5)\n        self.compare_group_combo = ttk.Combobox(compare_setting_frame, state=\"readonly\", width=15)\n        self.compare_group_combo.grid(row=0, column=1, padx=5)\n\n        ttk.Label(compare_setting_frame, text=\"\u6bd4\u5bf9\u6c47\u603b\u5217:\").grid(row=0, column=2, padx=5)\n        self.compare_sum_combo = ttk.Combobox(compare_setting_frame, state=\"readonly\", width=15)\n        self.compare_sum_combo.grid(row=0, column=3, padx=5)\n\n        ttk.Label(compare_setting_frame, text=\"\u5bb9\u5dee\u8303\u56f4:\").grid(row=0, column=4, padx=5)\n        self.tolerance_entry = ttk.Entry(compare_setting_frame, width=10)\n        self.tolerance_entry.insert(0, \"0.01\")\n        self.tolerance_entry.grid(row=0, column=5, padx=5)\n\n        # \u64cd\u4f5c\u6309\u94ae\u533a\u57df\n        button_frame = ttk.Frame(main_frame)\n        button_frame.grid(row=5, column=0, columnspan=4, pady=10)\n\n        ttk.Button(button_frame, text=\"\u6267\u884c\u5206\u7ec4\u6c47\u603b\", command=self.execute_grouping).grid(row=0, column=0, padx=5)\n        ttk.Button(button_frame, text=\"\u6267\u884c\u6570\u636e\u6bd4\u5bf9\", command=self.execute_comparison).grid(row=0, column=1, padx=5)\n        ttk.Button(button_frame, text=\"\u5bfc\u51fa\u7ed3\u679c\", command=self.export_result).grid(row=0, column=2, padx=5)\n        ttk.Button(button_frame, text=\"\u663e\u793a\u539f\u59cb\u6570\u636e\", command=self.show_original_data).grid(row=0, column=3, padx=5)\n\n        # \u7ed3\u679c\u663e\u793a\u533a\u57df\uff08\u4f7f\u7528Notebook\uff09\n        result_notebook = ttk.Notebook(main_frame)\n        result_notebook.grid(row=6, column=0, columnspan=4, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)\n\n        # \u6c47\u603b\u7ed3\u679c\u6807\u7b7e\u9875\n        summary_frame = ttk.Frame(result_notebook)\n        result_notebook.add(summary_frame, text=\"\u6c47\u603b\u7ed3\u679c\")\n        self.setup_result_treeview(summary_frame, \"summary\")\n\n        # \u6bd4\u5bf9\u7ed3\u679c\u6807\u7b7e\u9875\n        compare_result_frame = ttk.Frame(result_notebook)\n        result_notebook.add(compare_result_frame, text=\"\u6bd4\u5bf9\u7ed3\u679c\")\n        self.setup_result_treeview(compare_result_frame, \"compare\")\n\n        # \u5dee\u5f02\u8be6\u60c5\u6807\u7b7e\u9875\n        diff_frame = ttk.Frame(result_notebook)\n        result_notebook.add(diff_frame, text=\"\u5dee\u5f02\u8be6\u60c5\")\n        self.setup_result_treeview(diff_frame, \"diff\")\n\n        # \u914d\u7f6e\u4e3b\u6846\u67b6\u7684\u7f51\u683c\u6743\u91cd\n        main_frame.columnconfigure(0, weight=1)\n        main_frame.rowconfigure(2, weight=1)\n        main_frame.rowconfigure(6, weight=1)\n\n        self.root.columnconfigure(0, weight=1)\n        self.root.rowconfigure(0, weight=1)\n\n    def setup_data_treeview(self, parent, tree_type):\n        \"\"\"\u8bbe\u7f6e\u6570\u636e\u9884\u89c8\u7684Treeview\"\"\"\n        tree = ttk.Treeview(parent, show='headings', height=8)\n        scrollbar_y = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=tree.yview)\n        scrollbar_x = ttk.Scrollbar(parent, orient=tk.HORIZONTAL, command=tree.xview)\n        tree.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)\n\n        tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))\n        scrollbar_y.grid(row=0, column=1, sticky=(tk.N, tk.S))\n        scrollbar_x.grid(row=1, column=0, sticky=(tk.W, tk.E))\n\n        parent.columnconfigure(0, weight=1)\n        parent.rowconfigure(0, weight=1)\n\n        if tree_type == \"main\":\n            self.tree = tree\n        else:\n            self.compare_tree = tree\n\n    def setup_result_treeview(self, parent, tree_type):\n        \"\"\"\u8bbe\u7f6e\u7ed3\u679c\u663e\u793a\u7684Treeview\"\"\"\n        tree = ttk.Treeview(parent, show='headings', height=10)\n        scrollbar_y = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=tree.yview)\n        scrollbar_x = ttk.Scrollbar(parent, orient=tk.HORIZONTAL, command=tree.xview)\n        tree.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)\n\n        tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))\n        scrollbar_y.grid(row=0, column=1, sticky=(tk.N, tk.S))\n        scrollbar_x.grid(row=1, column=0, sticky=(tk.W, tk.E))\n\n        parent.columnconfigure(0, weight=1)\n        parent.rowconfigure(0, weight=1)\n\n        if tree_type == \"summary\":\n            self.result_tree = tree\n        elif tree_type == \"compare\":\n            self.compare_result_tree = tree\n        else:\n            self.diff_tree = tree\n\n    def load_file(self):\n        \"\"\"\u52a0\u8f7d\u4e3bExcel\u6587\u4ef6\"\"\"\n        file_path = filedialog.askopenfilename(\n            title=\"\u9009\u62e9\u4e3bExcel\u6587\u4ef6\",\n            filetypes=&#91;(\"Excel files\", \"*.xlsx *.xls\"), (\"All files\", \"*.*\")]\n        )\n\n        if file_path:\n            try:\n                self.file_path = file_path\n                self.file_label.config(text=os.path.basename(file_path))\n\n                # \u83b7\u53d6\u6240\u6709\u5de5\u4f5c\u8868\u540d\u79f0\n                excel_file = ExcelFile(file_path)\n                self.sheet_names = excel_file.sheet_names\n                self.sheet_combo&#91;'values'] = self.sheet_names\n\n                if self.sheet_names:\n                    self.sheet_combo.set(self.sheet_names&#91;0])\n                    self.load_sheet_data(self.sheet_names&#91;0])\n\n            except Exception as e:\n                messagebox.showerror(\"\u9519\u8bef\", f\"\u8bfb\u53d6\u6587\u4ef6\u5931\u8d25: {str(e)}\")\n\n    def load_compare_file(self):\n        \"\"\"\u52a0\u8f7d\u6bd4\u5bf9Excel\u6587\u4ef6\"\"\"\n        file_path = filedialog.askopenfilename(\n            title=\"\u9009\u62e9\u6bd4\u5bf9Excel\u6587\u4ef6\",\n            filetypes=&#91;(\"Excel files\", \"*.xlsx *.xls\"), (\"All files\", \"*.*\")]\n        )\n\n        if file_path:\n            try:\n                self.compare_file_path = file_path\n                self.compare_file_label.config(text=os.path.basename(file_path))\n\n                # \u83b7\u53d6\u6240\u6709\u5de5\u4f5c\u8868\u540d\u79f0\n                excel_file = ExcelFile(file_path)\n                self.compare_sheet_names = excel_file.sheet_names\n                self.compare_sheet_combo&#91;'values'] = self.compare_sheet_names\n\n                if self.compare_sheet_names:\n                    self.compare_sheet_combo.set(self.compare_sheet_names&#91;0])\n                    # \u4fee\u590d\uff1a\u65b9\u6cd5\u540d\u62fc\u5199\u9519\u8bef\n                    self.load_compare_sheet_data(self.compare_sheet_names&#91;0])\n\n            except Exception as e:\n                messagebox.showerror(\"\u9519\u8bef\", f\"\u8bfb\u53d6\u6bd4\u5bf9\u6587\u4ef6\u5931\u8d25: {str(e)}\")\n\n    def load_compare_sheet_data(self, sheet_name):\n        \"\"\"\u52a0\u8f7d\u6bd4\u5bf9\u5de5\u4f5c\u8868\u7684\u6570\u636e - \u4fee\u590d\u65b9\u6cd5\u540d\"\"\"\n        self.load_sheet_data(sheet_name, \"compare\")\n\n    def load_sheet_data(self, sheet_name, tree_type=\"main\"):\n        \"\"\"\u52a0\u8f7d\u6307\u5b9a\u5de5\u4f5c\u8868\u7684\u6570\u636e\"\"\"\n        try:\n            if tree_type == \"main\":\n                file_path = self.file_path\n                df = pd.read_excel(file_path, sheet_name=sheet_name)\n                self.current_df = df\n                self.current_sheet = sheet_name\n                tree = self.tree\n            else:\n                file_path = self.compare_file_path\n                df = pd.read_excel(file_path, sheet_name=sheet_name)\n                self.compare_df = df\n                self.compare_sheet = sheet_name\n                tree = self.compare_tree\n\n            # \u6e05\u7a7aTreeview\n            for item in tree.get_children():\n                tree.delete(item)\n            tree&#91;'columns'] = &#91;]\n\n            # \u8bbe\u7f6e\u5217\n            columns = list(df.columns)\n            tree&#91;'columns'] = columns\n\n            # \u914d\u7f6e\u5217\u6807\u9898\n            for col in columns:\n                tree.heading(col, text=col)\n                tree.column(col, width=100, minwidth=50)\n\n            # \u63d2\u5165\u6570\u636e\uff08\u9650\u5236\u663e\u793a\u884c\u6570\uff0c\u907f\u514d\u754c\u9762\u5361\u987f\uff09\n            display_rows = min(100, len(df))  # \u6700\u591a\u663e\u793a100\u884c\n            for i in range(display_rows):\n                row = df.iloc&#91;i]\n                values = &#91;str(x) if not pd.isna(x) else \"\" for x in row]\n                tree.insert(\"\", \"end\", values=values)\n\n            if len(df) > display_rows:\n                tree.insert(\"\", \"end\",\n                            values=&#91;f\"... \u8fd8\u6709 {len(df) - display_rows} \u884c\u6570\u636e ...\"] + &#91;\"\"] * (len(columns) - 1))\n\n            # \u66f4\u65b0\u7ec4\u5408\u6846\n            if tree_type == \"main\":\n                self.group_combo&#91;'values'] = columns\n                self.sum_combo&#91;'values'] = columns\n\n                if columns:\n                    self.group_combo.set(columns&#91;0])\n                    if len(columns) > 1:\n                        self.sum_combo.set(columns&#91;1])\n                    else:\n                        self.sum_combo.set(columns&#91;0])\n            else:\n                self.compare_group_combo&#91;'values'] = columns\n                self.compare_sum_combo&#91;'values'] = columns\n\n                if columns:\n                    self.compare_group_combo.set(columns&#91;0])\n                    if len(columns) > 1:\n                        self.compare_sum_combo.set(columns&#91;1])\n                    else:\n                        self.compare_sum_combo.set(columns&#91;0])\n\n            messagebox.showinfo(\"\u6210\u529f\", f\"\u5df2\u52a0\u8f7d\u5de5\u4f5c\u8868: {sheet_name}\\n\u6570\u636e\u884c\u6570: {len(df)}\")\n\n        except Exception as e:\n            messagebox.showerror(\"\u9519\u8bef\", f\"\u52a0\u8f7d\u5de5\u4f5c\u8868\u5931\u8d25: {str(e)}\")\n\n    def on_sheet_select(self, event):\n        \"\"\"\u4e3b\u5de5\u4f5c\u8868\u9009\u62e9\u4e8b\u4ef6\"\"\"\n        sheet_name = self.sheet_combo.get()\n        if sheet_name:\n            self.load_sheet_data(sheet_name, \"main\")\n\n    def on_compare_sheet_select(self, event):\n        \"\"\"\u6bd4\u5bf9\u5de5\u4f5c\u8868\u9009\u62e9\u4e8b\u4ef6\"\"\"\n        sheet_name = self.compare_sheet_combo.get()\n        if sheet_name:\n            self.load_sheet_data(sheet_name, \"compare\")\n\n    def execute_grouping(self):\n        \"\"\"\u6267\u884c\u5206\u7ec4\u6c47\u603b\"\"\"\n        if self.current_df is None:\n            messagebox.showwarning(\"\u8b66\u544a\", \"\u8bf7\u5148\u52a0\u8f7d\u4e3b\u6570\u636e\")\n            return\n\n        group_col = self.group_combo.get()\n        sum_col = self.sum_combo.get()\n        agg_method = self.agg_combo.get()\n\n        if not group_col or not sum_col:\n            messagebox.showwarning(\"\u8b66\u544a\", \"\u8bf7\u9009\u62e9\u57fa\u51c6\u5217\u548c\u6c47\u603b\u5217\")\n            return\n\n        try:\n            # \u68c0\u67e5\u5217\u662f\u5426\u5b58\u5728\n            if group_col not in self.current_df.columns or sum_col not in self.current_df.columns:\n                messagebox.showerror(\"\u9519\u8bef\", \"\u9009\u62e9\u7684\u5217\u4e0d\u5b58\u5728\")\n                return\n\n            # \u6620\u5c04\u6c47\u603b\u65b9\u6cd5\n            agg_map = {\n                '\u6c42\u548c': 'sum',\n                '\u5e73\u5747\u503c': 'mean',\n                '\u6700\u5927\u503c': 'max',\n                '\u6700\u5c0f\u503c': 'min',\n                '\u8ba1\u6570': 'count'\n            }\n\n            # \u6267\u884c\u5206\u7ec4\u6c47\u603b\n            if agg_method == '\u8ba1\u6570':\n                grouped = self.current_df.groupby(group_col).agg({\n                    sum_col: agg_map&#91;agg_method]\n                }).round(2)\n            else:\n                self.current_df&#91;sum_col] = pd.to_numeric(self.current_df&#91;sum_col], errors='coerce')\n                grouped = self.current_df.groupby(group_col).agg({\n                    sum_col: agg_map&#91;agg_method]\n                }).round(2)\n\n            # \u91cd\u7f6e\u7d22\u5f15\n            grouped = grouped.reset_index()\n\n            # \u663e\u793a\u7ed3\u679c\n            self.display_result(grouped, group_col, sum_col, agg_method, \"summary\")\n\n        except Exception as e:\n            messagebox.showerror(\"\u9519\u8bef\", f\"\u5206\u7ec4\u6c47\u603b\u5931\u8d25: {str(e)}\")\n\n    def execute_comparison(self):\n        \"\"\"\u6267\u884c\u6570\u636e\u6bd4\u5bf9\"\"\"\n        if self.current_df is None or self.compare_df is None:\n            messagebox.showwarning(\"\u8b66\u544a\", \"\u8bf7\u5148\u52a0\u8f7d\u4e3b\u6570\u636e\u548c\u6bd4\u5bf9\u6570\u636e\")\n            return\n\n        main_group_col = self.group_combo.get()\n        main_sum_col = self.sum_combo.get()\n        compare_group_col = self.compare_group_combo.get()\n        compare_sum_col = self.compare_sum_combo.get()\n        agg_method = self.agg_combo.get()\n\n        if not all(&#91;main_group_col, main_sum_col, compare_group_col, compare_sum_col]):\n            messagebox.showwarning(\"\u8b66\u544a\", \"\u8bf7\u5b8c\u6574\u8bbe\u7f6e\u6bd4\u5bf9\u53c2\u6570\")\n            return\n\n        try:\n            # \u83b7\u53d6\u5bb9\u5dee\n            tolerance = float(self.tolerance_entry.get())\n\n            # \u5bf9\u4e3b\u6570\u636e\u8fdb\u884c\u5206\u7ec4\u6c47\u603b\n            self.current_df&#91;main_sum_col] = pd.to_numeric(self.current_df&#91;main_sum_col], errors='coerce')\n            main_grouped = self.current_df.groupby(main_group_col).agg({\n                main_sum_col: 'sum'\n            }).round(2).reset_index()\n\n            # \u5bf9\u6bd4\u5bf9\u6570\u636e\u8fdb\u884c\u5206\u7ec4\u6c47\u603b\n            self.compare_df&#91;compare_sum_col] = pd.to_numeric(self.compare_df&#91;compare_sum_col], errors='coerce')\n            compare_grouped = self.compare_df.groupby(compare_group_col).agg({\n                compare_sum_col: 'sum'\n            }).round(2).reset_index()\n\n            # \u4fee\u590d\uff1a\u786e\u4fdd\u5408\u5e76\u5217\u7684\u6570\u636e\u7c7b\u578b\u4e00\u81f4\n            main_grouped&#91;main_group_col] = main_grouped&#91;main_group_col].astype(str)\n            compare_grouped&#91;compare_group_col] = compare_grouped&#91;compare_group_col].astype(str)\n\n            # \u5408\u5e76\u4e24\u4e2a\u7ed3\u679c\n            merged = pd.merge(main_grouped, compare_grouped,\n                              left_on=main_group_col, right_on=compare_group_col,\n                              how='outer', suffixes=('_\u4e3b\u6570\u636e', '_\u6bd4\u5bf9\u6570\u636e'), indicator=True)\n\n            # \u5904\u7406\u5408\u5e76\u540e\u7684\u6570\u636e\n            # \u586b\u5145NaN\u503c\u4e3a0\uff0c\u4fbf\u4e8e\u8ba1\u7b97\u5dee\u5f02\n            merged&#91;f'{main_sum_col}_\u4e3b\u6570\u636e'] = merged&#91;f'{main_sum_col}_\u4e3b\u6570\u636e'].fillna(0)\n            merged&#91;f'{compare_sum_col}_\u6bd4\u5bf9\u6570\u636e'] = merged&#91;f'{compare_sum_col}_\u6bd4\u5bf9\u6570\u636e'].fillna(0)\n\n            # \u8ba1\u7b97\u5dee\u5f02\n            merged&#91;'\u5dee\u5f02'] = merged&#91;f'{main_sum_col}_\u4e3b\u6570\u636e'] - merged&#91;f'{compare_sum_col}_\u6bd4\u5bf9\u6570\u636e']\n            merged&#91;'\u5dee\u5f02\u7edd\u5bf9\u503c'] = abs(merged&#91;'\u5dee\u5f02'])\n            merged&#91;'\u662f\u5426\u4e00\u81f4'] = merged&#91;'\u5dee\u5f02\u7edd\u5bf9\u503c'] &lt;= tolerance\n\n            # \u91cd\u547d\u540d\u5408\u5e76\u6307\u793a\u5217\n            merged = merged.rename(columns={'_merge': '\u6570\u636e\u6765\u6e90'})\n            merged&#91;'\u6570\u636e\u6765\u6e90'] = merged&#91;'\u6570\u636e\u6765\u6e90'].map({\n                'left_only': '\u4ec5\u4e3b\u6570\u636e\u5b58\u5728',\n                'right_only': '\u4ec5\u6bd4\u5bf9\u6570\u636e\u5b58\u5728',\n                'both': '\u4e24\u8fb9\u90fd\u5b58\u5728'\n            })\n\n            # \u663e\u793a\u6bd4\u5bf9\u7ed3\u679c\n            self.display_result(merged, main_group_col, main_sum_col, agg_method, \"compare\")\n\n            # \u663e\u793a\u5dee\u5f02\u8be6\u60c5\n            diff_data = merged&#91;~merged&#91;'\u662f\u5426\u4e00\u81f4']].copy()\n            if not diff_data.empty:\n                self.display_result(diff_data, main_group_col, main_sum_col, agg_method, \"diff\")\n                messagebox.showinfo(\"\u6bd4\u5bf9\u5b8c\u6210\",\n                                    f\"\u6bd4\u5bf9\u5b8c\u6210\uff01\\n\"\n                                    f\"\u603b\u8bb0\u5f55\u6570: {len(merged)}\\n\"\n                                    f\"\u4e00\u81f4\u8bb0\u5f55: {len(merged&#91;merged&#91;'\u662f\u5426\u4e00\u81f4']])}\\n\"\n                                    f\"\u5dee\u5f02\u8bb0\u5f55: {len(diff_data)}\\n\"\n                                    f\"\u4ec5\u4e3b\u6570\u636e\u5b58\u5728: {len(merged&#91;merged&#91;'\u6570\u636e\u6765\u6e90'] == '\u4ec5\u4e3b\u6570\u636e\u5b58\u5728'])}\\n\"\n                                    f\"\u4ec5\u6bd4\u5bf9\u6570\u636e\u5b58\u5728: {len(merged&#91;merged&#91;'\u6570\u636e\u6765\u6e90'] == '\u4ec5\u6bd4\u5bf9\u6570\u636e\u5b58\u5728'])}\")\n            else:\n                self.display_result(pd.DataFrame(), main_group_col, main_sum_col, agg_method, \"diff\")\n                messagebox.showinfo(\"\u6bd4\u5bf9\u5b8c\u6210\", \"\u6bd4\u5bf9\u5b8c\u6210\uff01\u6240\u6709\u6570\u636e\u4e00\u81f4\uff01\")\n\n            # \u4fdd\u5b58\u6bd4\u5bf9\u7ed3\u679c\n            self.comparison_result = merged\n\n        except Exception as e:\n            messagebox.showerror(\"\u9519\u8bef\", f\"\u6570\u636e\u6bd4\u5bf9\u5931\u8d25: {str(e)}\")\n\n    def display_result(self, result_df, group_col, sum_col, agg_method, result_type):\n        \"\"\"\u663e\u793a\u7ed3\u679c\"\"\"\n        if result_type == \"summary\":\n            tree = self.result_tree\n            title = \"\u6c47\u603b\u7ed3\u679c\"\n        elif result_type == \"compare\":\n            tree = self.compare_result_tree\n            title = \"\u6bd4\u5bf9\u7ed3\u679c\"\n        else:\n            tree = self.diff_tree\n            title = \"\u5dee\u5f02\u8be6\u60c5\"\n\n        # \u6e05\u7a7aTreeview\n        for item in tree.get_children():\n            tree.delete(item)\n        tree&#91;'columns'] = &#91;]\n\n        if result_df.empty:\n            tree&#91;'columns'] = &#91;'\u63d0\u793a']\n            tree.heading('\u63d0\u793a', text='\u63d0\u793a')\n            tree.column('\u63d0\u793a', width=300)\n            tree.insert(\"\", \"end\", values=&#91;\"\u6ca1\u6709\u6570\u636e\"])\n            return\n\n        # \u8bbe\u7f6e\u5217\n        columns = list(result_df.columns)\n        tree&#91;'columns'] = columns\n\n        # \u914d\u7f6e\u5217\u6807\u9898\n        for col in columns:\n            display_name = col\n            tree.heading(col, text=display_name)\n            # \u6839\u636e\u5217\u5185\u5bb9\u8c03\u6574\u5217\u5bbd\n            if any(isinstance(x, (int, float)) for x in result_df&#91;col].head() if not pd.isna(x)):\n                tree.column(col, width=120, minwidth=80)\n            else:\n                tree.column(col, width=150, minwidth=100)\n\n        # \u63d2\u5165\u6570\u636e\n        for i, row in result_df.iterrows():\n            values = &#91;]\n            for x in row:\n                if isinstance(x, (int, float)) and not isinstance(x, bool):\n                    values.append(f\"{x:.2f}\")\n                else:\n                    values.append(str(x) if not pd.isna(x) else \"\")\n            tree.insert(\"\", \"end\", values=values)\n\n        # \u4fdd\u5b58\u7ed3\u679c\u7528\u4e8e\u5bfc\u51fa\n        if result_type == \"summary\":\n            self.summary_result = result_df\n        elif result_type == \"compare\":\n            self.comparison_result = result_df\n\n    def export_result(self):\n        \"\"\"\u5bfc\u51fa\u7ed3\u679c\u5230Excel\"\"\"\n        if not hasattr(self, 'summary_result') and not hasattr(self, 'comparison_result'):\n            messagebox.showwarning(\"\u8b66\u544a\", \"\u6ca1\u6709\u53ef\u5bfc\u51fa\u7684\u7ed3\u679c\u6570\u636e\")\n            return\n\n        file_path = filedialog.asksaveasfilename(\n            title=\"\u4fdd\u5b58\u7ed3\u679c\",\n            defaultextension=\".xlsx\",\n            filetypes=&#91;(\"Excel files\", \"*.xlsx\"), (\"All files\", \"*.*\")]\n        )\n\n        if file_path:\n            try:\n                with pd.ExcelWriter(file_path, engine='openpyxl') as writer:\n                    if hasattr(self, 'summary_result'):\n                        self.summary_result.to_excel(writer, sheet_name='\u6c47\u603b\u7ed3\u679c', index=False)\n\n                    if hasattr(self, 'comparison_result'):\n                        self.comparison_result.to_excel(writer, sheet_name='\u6bd4\u5bf9\u7ed3\u679c', index=False)\n\n                    if hasattr(self, 'current_df'):\n                        self.current_df.to_excel(writer, sheet_name='\u4e3b\u539f\u59cb\u6570\u636e', index=False)\n\n                    if hasattr(self, 'compare_df'):\n                        self.compare_df.to_excel(writer, sheet_name='\u6bd4\u5bf9\u539f\u59cb\u6570\u636e', index=False)\n\n                messagebox.showinfo(\"\u6210\u529f\", f\"\u7ed3\u679c\u5df2\u5bfc\u51fa\u5230: {file_path}\")\n            except Exception as e:\n                messagebox.showerror(\"\u9519\u8bef\", f\"\u5bfc\u51fa\u5931\u8d25: {str(e)}\")\n\n    def show_original_data(self):\n        \"\"\"\u663e\u793a\u539f\u59cb\u6570\u636e\"\"\"\n        if self.current_df is not None:\n            self.load_sheet_data(self.current_sheet, \"main\")\n        if hasattr(self, 'compare_df') and self.compare_df is not None:\n            self.load_sheet_data(self.compare_sheet, \"compare\")\n\n\ndef main():\n    root = tk.Tk()\n    app = ExcelGroupAnalyzer(root)\n    root.mainloop()\n\n\nif __name__ == \"__main__\":\n    main()<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\uff08\u6ce8\uff1a\u6b64\u811a\u672c\u7531AI\u751f\u6210\uff09 \u529f\u80fd\u4ecb\u7ecd\uff1a \u8fd0\u884c\u811a\u672c\u524d\uff0c\u4f60\u53ef\u80fd\u9700\u8981\u7684\u5305\uff1a \u4ee5\u4e0b\u662f\u5b8c\u6574\u4ee3\u7801\uff0c\u8c03\u6574\u597d\u7f16\u8bd1\u73af\u5883\u548c\u6240\u9700\u8981\u7684\u5305\u540e\uff0c\u53ef\u76f4\u63a5\u8fd0\u884c\u4f7f\u7528\uff1a<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-62","post","type-post","status-publish","format-standard","hentry","category-3"],"_links":{"self":[{"href":"https:\/\/blog.genan.top\/index.php?rest_route=\/wp\/v2\/posts\/62","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.genan.top\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.genan.top\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.genan.top\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.genan.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=62"}],"version-history":[{"count":1,"href":"https:\/\/blog.genan.top\/index.php?rest_route=\/wp\/v2\/posts\/62\/revisions"}],"predecessor-version":[{"id":63,"href":"https:\/\/blog.genan.top\/index.php?rest_route=\/wp\/v2\/posts\/62\/revisions\/63"}],"wp:attachment":[{"href":"https:\/\/blog.genan.top\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=62"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.genan.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=62"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.genan.top\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=62"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}