# 个人月度支出分析工具:轻松掌握你的财务状况


背景介绍

在当今快节奏的生活中,合理管理个人财务变得越来越重要。许多人有记录支出的习惯,但往往缺乏有效的工具来分析这些数据。为了解决这个问题,我开发了一款个人月度支出分析工具,帮助用户直观了解自己的月度支出结构,从而更好地规划财务。

思路分析

这款工具的核心功能包括:
1. 图形界面:使用Python的tkinter库创建用户友好的界面
2. 数据解析:读取CSV格式的支出记录,统计各分类支出
3. 可视化展示:使用matplotlib生成饼图,直观展示支出占比
4. 结果导出:将统计结果保存为新的CSV文件
5. 错误处理:对无效数据和操作提供友好提示

工具的工作流程是:用户选择CSV文件 → 系统解析并统计数据 → 显示统计表格和饼图 → 用户可导出结果。

代码实现

以下是完整的Python代码实现:

import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import csv
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from collections import defaultdict
import os

class ExpenseAnalyzer:
    def __init__(self, root):
        self.root = root
        self.root.title("个人月度支出分析工具")
        self.root.geometry("900x700")

        # 初始化变量
        self.csv_path = ""
        self.analysis_result = []

        # 创建GUI组件
        self.create_widgets()

    def create_widgets(self):
        # 选择文件按钮
        self.select_btn = ttk.Button(self.root, text="选择支出CSV文件", command=self.select_file)
        self.select_btn.pack(pady=10)

        # 显示当前选择的文件路径
        self.file_label = ttk.Label(self.root, text="未选择文件")
        self.file_label.pack(pady=5)

        # 统计结果表格
        self.result_frame = ttk.Frame(self.root)
        self.result_frame.pack(pady=10, fill=tk.BOTH, expand=True)

        # 表格标题
        self.table_title = ttk.Label(self.result_frame, text="支出统计结果", font=("Arial", 12, "bold"))
        self.table_title.pack(pady=5)

        # 表格
        self.tree = ttk.Treeview(self.result_frame, columns=("分类", "总金额(元)", "占比"), show="headings")
        self.tree.heading("分类", text="分类")
        self.tree.heading("总金额(元)", text="总金额(元)")
        self.tree.heading("占比", text="占比")

        # 设置列宽
        self.tree.column("分类", width=100)
        self.tree.column("总金额(元)", width=100)
        self.tree.column("占比", width=100)

        self.tree.pack(pady=5, fill=tk.BOTH, expand=True)

        # 饼图区域
        self.chart_frame = ttk.Frame(self.root)
        self.chart_frame.pack(pady=10, fill=tk.BOTH, expand=True)

        # 导出按钮
        self.export_btn = ttk.Button(self.root, text="导出统计结果", command=self.export_results, state=tk.DISABLED)
        self.export_btn.pack(pady=10)

    def select_file(self):
        """选择CSV文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")],
            title="选择支出记录CSV文件"
        )

        if file_path:
            self.csv_path = file_path
            self.file_label.config(text=f"当前文件: {os.path.basename(file_path)}")
            self.analyze_and_display()

    def analyze_and_display(self):
        """分析支出数据并显示结果"""
        try:
            # 清空之前的结果
            for item in self.tree.get_children():
                self.tree.delete(item)

            # 分析数据
            result = self.analyze_expenses(self.csv_path)

            if isinstance(result, str):  # 如果返回的是错误信息
                messagebox.showerror("错误", result)
                return

            self.analysis_result = result

            # 显示统计结果
            for row in result:
                self.tree.insert("", tk.END, values=row)

            # 生成饼图
            self.generate_pie_chart()

            # 启用导出按钮
            self.export_btn.config(state=tk.NORMAL)

        except Exception as e:
            messagebox.showerror("错误", f"处理数据时发生错误: {str(e)}")

    def analyze_expenses(self, csv_path):
        """分析支出数据"""
        category_total = defaultdict(float)
        total_expense = 0.0

        try:
            with open(csv_path, 'r', encoding='utf-8') as f:
                reader = csv.DictReader(f)

                # 检查必要的字段
                required_fields = ['日期', '分类', '金额']
                for field in required_fields:
                    if field not in reader.fieldnames:
                        raise ValueError(f"CSV文件缺少必要字段: {field}")

                for row in reader:
                    # 获取分类和金额
                    category = row['分类'].strip()
                    amount_str = row['金额'].strip()

                    if not category:
                        raise ValueError("分类字段不能为空")

                    try:
                        amount = float(amount_str)
                    except ValueError:
                        raise ValueError(f"金额 '{amount_str}' 不是有效的数字")

                    category_total[category] += amount
                    total_expense += amount

            # 如果没有数据
            if total_expense == 0:
                raise ValueError("CSV文件中没有有效的支出记录")

            # 计算占比并整理结果
            result = []
            for cat, amt in category_total.items():
                ratio = (amt / total_expense) * 100
                result.append((cat, round(amt, 1), f"{round(ratio, 1)}%"))

            # 添加总支出行
            result.append(("总支出", round(total_expense, 1), "100%"))

            return result

        except FileNotFoundError:
            return "文件未找到"
        except Exception as e:
            return f"解析错误: {str(e)}"

    def generate_pie_chart(self):
        """生成饼图"""
        # 清空之前的图表
        for widget in self.chart_frame.winfo_children():
            widget.destroy()

        # 准备数据(排除总支出行)
        categories = []
        amounts = []
        percentages = []

        for row in self.analysis_result[:-1]:  # 排除最后一行总支出
            categories.append(row[0])
            amounts.append(row[1])
            percentages.append(row[2])

        # 创建饼图
        fig, ax = plt.subplots(figsize=(6, 4), dpi=100)
        wedges, texts, autotexts = ax.pie(amounts, labels=categories, autopct='%1.1f%%', 
                                          startangle=90, textprops={'fontsize': 10})

        # 设置标题
        ax.set_title("月度支出占比", fontsize=12)

        # 调整布局
        plt.tight_layout()

        # 将matplotlib图表嵌入到tkinter窗口
        canvas = FigureCanvasTkAgg(fig, master=self.chart_frame)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

    def export_results(self):
        """导出统计结果为CSV文件"""
        if not self.analysis_result:
            messagebox.showwarning("警告", "没有可导出的统计结果")
            return

        # 获取原始文件名和路径
        original_dir = os.path.dirname(self.csv_path)
        original_name = os.path.basename(self.csv_path)
        base_name, ext = os.path.splitext(original_name)
        default_name = f"{base_name}_统计结果.csv"

        # 选择保存路径
        save_path = filedialog.asksaveasfilename(
            defaultextension=".csv",
            filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")],
            initialfile=default_name,
            initialdir=original_dir,
            title="保存统计结果"
        )

        if save_path:
            try:
                with open(save_path, 'w', encoding='utf-8', newline='') as f:
                    writer = csv.writer(f)
                    # 写入表头
                    writer.writerow(["分类", "总金额", "占比"])
                    # 写入数据
                    for row in self.analysis_result:
                        writer.writerow(row)

                messagebox.showinfo("成功", f"统计结果已导出到:\n{save_path}")

            except Exception as e:
                messagebox.showerror("错误", f"导出失败: {str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = ExpenseAnalyzer(root)
    root.mainloop()

总结

这款个人月度支出分析工具提供了一个直观、易用的方式来分析个人财务状况。通过简单的CSV文件导入,用户可以快速了解自己的支出结构,识别主要的支出类别,并通过饼图直观地看到各项支出的占比。工具还支持将分析结果导出为新的CSV文件,方便用户进一步处理或存档。

该工具的主要特点包括:
1. 用户友好的界面:通过tkinter创建直观的图形界面
2. 强大的数据处理:能够解析CSV格式的支出记录并进行统计分析
3. 直观的可视化:使用饼图清晰展示支出占比
4. 灵活的导出功能:支持将统计结果保存为CSV文件
5. 完善的错误处理:对各种异常情况提供友好提示

无论是个人财务管理新手还是有经验的用户,这款工具都能帮助你更好地理解和管理自己的财务状况,为制定合理的预算和储蓄计划提供数据支持。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注