分类 Python 下的文章

分析一下仓储费用的最优化问题

假设我的半年总销量是500件货物,每次找厂家订货大约需要80元订货手续费用(包括电话、出差、合同等),仓库费用0.4元/件/月,假设我的日销售数量是平均的,如果设单次进货量为X的话,总费用包括订货手续费用和仓储费用,总费用的变化趋势是怎样的?X的值多少最合适?
微信图片_20250926110730_4964_28.png

一、先明确核心参数与总费用公式

首先统一时间、成本单位,避免计算偏差:

核心参数具体数值与说明
半年总需求量(D)500件(日销量平均,即半年内均匀消耗,日销量=500÷180≈2.78件/天,无需精确到日,按半年维度计算即可)
单次订货成本(S)80元/次(固定成本,与进货量X无关,包括手续、差旅等)
单位仓储成本(H)0.4元/件/月 → 半年仓储成本=0.4×6=2.4元/件(仓储成本与库存持有时间、数量正相关,需换算为半年单位)
单次进货量(决策变量X)每次向厂家订X件,半年内订货次数=总需求量÷进货量=D/X=500/X次

总费用(TC)的构成公式

总费用=订货成本+仓储成本,两者此消彼长,需找到平衡点:

  1. 订货成本:半年内订货次数×单次订货成本 = (D/X)×S = (500/X)×80 = 40000/X
    (逻辑:进货量X越小,订货次数越多,订货成本越高;反之则越低)
  2. 仓储成本:平均库存量×单位半年仓储成本 = (X/2)×H = (X/2)×2.4 = 1.2X
    (关键逻辑:货物均匀消耗——进货时库存为X,逐渐卖到0,再订下一批,因此平均库存始终是X/2,而非X;仓储成本与平均库存正相关,X越大,平均库存越高,仓储成本越高)

最终总费用公式:
TC = 40000/X + 1.2X

基于此得出以下脚本

import tkinter as tk
from tkinter import ttk, messagebox
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import math
from matplotlib.figure import Figure

class InventoryOptimizer:
    def __init__(self, root):
        # 设置中文字体支持
        plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
        
        self.root = root
        self.root.title("仓储费用优化计算器")
        self.root.geometry("900x700")
        self.root.resizable(True, True)
        
        # 创建主框架
        main_frame = ttk.Frame(root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 创建输入框架
        input_frame = ttk.LabelFrame(main_frame, text="输入参数", padding="10")
        input_frame.pack(fill=tk.X, pady=(0, 10))
        
        # 输入字段配置
        input_fields = [
            ("总销售数量(件):", "total_quantity", "500"),
            ("销售周期(月):", "sales_months", "6"),
            ("单次订货手续费(元):", "order_cost", "80"),
            ("单件单月库存费用(元):", "storage_cost", "0.4")
        ]
        
        # 存储输入变量
        self.vars = {}
        
        # 创建输入控件
        for i, (label_text, var_name, default) in enumerate(input_fields):
            ttk.Label(input_frame, text=label_text).grid(
                row=i, column=0, padx=5, pady=5, sticky=tk.W
            )
            self.vars[var_name] = tk.StringVar(value=default)
            ttk.Entry(input_frame, textvariable=self.vars[var_name], width=20).grid(
                row=i, column=1, padx=5, pady=5, sticky=tk.W
            )
        
        # 计算按钮
        btn_frame = ttk.Frame(input_frame)
        btn_frame.grid(row=len(input_fields), column=0, columnspan=2, pady=10)
        ttk.Button(btn_frame, text="计算最优方案", command=self.calculate).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="重置", command=self.reset).pack(side=tk.LEFT, padx=5)
        
        # 创建结果框架
        result_frame = ttk.LabelFrame(main_frame, text="计算结果", padding="10")
        result_frame.pack(fill=tk.BOTH, expand=True)
        
        # 结果面板
        result_paned = ttk.PanedWindow(result_frame, orient=tk.VERTICAL)
        result_paned.pack(fill=tk.BOTH, expand=True)
        
        # 文本结果区域
        text_frame = ttk.Frame(result_paned, height=150)
        result_paned.add(text_frame, weight=1)
        
        self.result_text = tk.Text(text_frame, wrap=tk.WORD, padx=5, pady=5)
        self.result_text.pack(fill=tk.BOTH, expand=True)
        self.result_text.config(state=tk.DISABLED)
        
        # 图表区域
        chart_frame = ttk.Frame(result_paned)
        result_paned.add(chart_frame, weight=3)
        
        # 创建图表
        self.fig = Figure(figsize=(8, 4), dpi=100)
        self.ax = self.fig.add_subplot(111)
        self.canvas = FigureCanvasTkAgg(self.fig, master=chart_frame)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        
    def calculate(self):
        """计算最优订货量并显示结果"""
        try:
            # 获取输入参数
            D = float(self.vars["total_quantity"].get())  # 总需求量
            T = float(self.vars["sales_months"].get())   # 销售月数
            S = float(self.vars["order_cost"].get())     # 单次订货成本
            H_monthly = float(self.vars["storage_cost"].get())  # 单件单月库存成本
            H = H_monthly * T  # 单件总周期库存成本
            
            # 计算最优订货量 (EOQ模型)
            eoq = math.sqrt((2 * D * S) / H)
            optimal_order_quantity = round(eoq)
            
            # 计算相关成本
            order_count = D / eoq  # 订货次数
            optimal_order_cost = order_count * S  # 最优订货成本
            optimal_storage_cost = (eoq / 2) * H  # 最优库存成本
            total_cost = optimal_order_cost + optimal_storage_cost  # 总成本
            
            # 生成图表数据
            x_min = max(1, int(eoq * 0.3))  # 避免x值过小
            x_max = int(eoq * 2)
            x_values = np.linspace(x_min, x_max, 100)  # 生成X值范围
            
            # 计算不同订货量下的成本
            order_costs = (D / x_values) * S  # 订货成本
            storage_costs = (x_values / 2) * H  # 库存成本
            total_costs = order_costs + storage_costs  # 总成本
            
            # 清空之前的图表
            self.ax.clear()
            
            # 绘制成本曲线
            self.ax.plot(x_values, order_costs, label='订货成本', color='blue', linewidth=2)
            self.ax.plot(x_values, storage_costs, label='库存成本', color='green', linewidth=2)
            self.ax.plot(x_values, total_costs, label='总成本', color='red', linewidth=2)
            
            # 标记最优订货量
            self.ax.axvline(x=eoq, color='purple', linestyle='--', 
                           label=f'最优订货量: {optimal_order_quantity}件')
            
            # 图表设置
            self.ax.set_xlabel('单次订货量 (件)')
            self.ax.set_ylabel('成本 (元)')
            self.ax.set_title('库存成本优化分析')
            self.ax.legend()
            self.ax.grid(True, linestyle='--', alpha=0.7)
            self.fig.tight_layout()
            
            # 更新画布
            self.canvas.draw()
            
            # 显示文本结果
            self.result_text.config(state=tk.NORMAL)
            self.result_text.delete(1.0, tk.END)
            
            result = "仓储费用优化分析结果:\n\n"
            result += f"1. 最优单次订货量:{optimal_order_quantity} 件\n"
            result += f"2. 预计订货次数:{order_count:.1f} 次\n"
            result += f"3. 订货总成本:{optimal_order_cost:.2f} 元\n"
            result += f"4. 库存总成本:{optimal_storage_cost:.2f} 元\n"
            result += f"5. 总费用:{total_cost:.2f} 元\n\n"
            result += "分析:\n"
            result += "- 当订货量小于最优值时,订货成本占主导,总费用随订货量增加而降低\n"
            result += "- 当订货量大于最优值时,库存成本占主导,总费用随订货量增加而上升\n"
            result += "- 最优订货量处,订货成本与库存成本基本相等,总费用最低"
            
            self.result_text.insert(tk.END, result)
            self.result_text.config(state=tk.DISABLED)
            
        except ValueError:
            messagebox.showerror("输入错误", "请确保所有输入都是有效的数字")
        except Exception as e:
            messagebox.showerror("计算错误", f"计算过程中出现错误: {str(e)}")
    
    def reset(self):
        """重置输入和结果"""
        for var in self.vars.values():
            var.set("")
        
        self.result_text.config(state=tk.NORMAL)
        self.result_text.delete(1.0, tk.END)
        self.result_text.config(state=tk.DISABLED)
        
        self.ax.clear()
        self.canvas.draw()

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

分享一个Python自动化案例

背景

  • 渠道正在开展一系列共性的市场活动
  • 我们需要总结上一阶段的活动
  • 统计结果以及结算费用
  • 事先通过销售总监安排了每场活动的小结,
  • 计划写一个脚本自动汇总小结生成月度报告
import pandas as pd
import os
import fnmatch


def extract_summary_info(excel_path):
    try:
        # 读取文件
        excel_file = pd.ExcelFile(excel_path)
        # 获取所有表名
        sheet_names = excel_file.sheet_names
        if not sheet_names:
            raise ValueError(f"文件 {excel_path} 中没有工作表")

        # 获取指定工作表中的数据
        df = excel_file.parse(sheet_names[0])

        # 提取所需内容
        activity_topic = df.loc[1, '嘉智联渠道市场推广活动报告']
        jzl_person_in_charge = df.loc[1, 'Unnamed: 8']
        activity_location = df.loc[4, '嘉智联渠道市场推广活动报告']
        activity_time = df.loc[4, 'Unnamed: 5']
        organizer = df.loc[7, '嘉智联渠道市场推广活动报告']
        contact_person = df.loc[7, 'Unnamed: 7']
        contact_number = df.loc[8, 'Unnamed: 8']

        # 活动议程部分,从第 12 行(index=11)到第 15 行(index=14)获取时间和议程信息
        agenda_rows = df.loc[11:14, ['嘉智联渠道市场推广活动报告', 'Unnamed: 2', 'Unnamed: 7']]
        agenda_rows.columns = ['开始时间', '议程', '负责担当']
        activity_agenda = ';'.join([
            f"{row['开始时间']} - {row['议程']}({row['负责担当']})" if pd.notna(row['负责担当']) 
            else f"{row['开始时间']} - {row['议程']}" for _, row in agenda_rows.iterrows()
        ])
        activity_summary = df.loc[35, '嘉智联渠道市场推广活动报告']

        # 创建汇总数据的 DataFrame
        data = {
            '活动主题': [activity_topic],
            '嘉智联担当': [jzl_person_in_charge],
            '活动地点': [activity_location],
            '活动时间': [activity_time],
            '主办单位': [organizer],
            '联系人': [contact_person],
            '联系电话': [contact_number],
            '活动议程': [activity_agenda],
            '活动小结': [activity_summary]
        }

        result_df = pd.DataFrame(data)
        return result_df
    except Exception as e:
        print(f"处理文件 {excel_path} 时出现错误: {e}")
        return pd.DataFrame()


def find_excel_files(root_folder):
    excel_files = []
    for root, dirs, files in os.walk(root_folder):
        for file in files:
            if fnmatch.fnmatch(file, '*.xlsx') or fnmatch.fnmatch(file, '*.xls'):
                excel_files.append(os.path.join(root, file))
    return excel_files


def main(file_list, result_file_path):
    dfs = []
    excel_files = file_list
    for excel_file in excel_files:
        result_df = extract_summary_info(excel_file)
        dfs.append(result_df)

    # 循环结束后一次性合并 DataFrame
    if dfs:
        result = pd.concat(dfs, ignore_index=True)
        result.to_excel(result_file_path, index=False)
        print(result)
    else:
        print("没有找到有效的数据")


if __name__ == '__main__':
    path = r".\会议资料2507"
    main(find_excel_files(path), r".\会议汇总2507.xlsx")

完美,俺又可以愉快的喝茶了~

根据历史销售数据训练预测模型

评估以下模型,找出契合度最高的

  • '移动平均'
  • '自回归模型 (AR)'
  • 'ARIMA 模型'
  • '简单指数平滑法'
  • '季节性 ARIMA 模型 (SARIMAX)'

根据预测模型预测下一阶段的销售趋势

ps.想要用Rust重写,但评估了Rust的学习曲线后,果断放弃。以后有时间再考虑吧~


import pandas as pd
from rich.logging import RichHandler
from statsmodels.tsa.ar_model import AutoReg
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
from statsmodels.tsa.statespace.sarimax import SARIMAX
from prophet import Prophet
from sklearn.metrics import mean_squared_error
import warnings
import logging
from time_counter import timeit  # 导入本地的 time_counter.py 中的 timeit 装饰器
import itertools
import numpy as np
from statsmodels.tsa.seasonal import STL  # 新增导入

logging.basicConfig(
    level="NOTSET",
    format="%(message)s",
    datefmt="[%X]",
    handlers=[RichHandler()]
)

log = logging.getLogger("rich")

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 忽略警告
warnings.filterwarnings("ignore")

# 配置参数
DATA_FILE = '20210401_20250430.xlsx'
DATE_COLUMN = 'Created On(Delivery)'
PRODUCT_NAME_COLUMN = 'Material Short Text'
QUANTITY_COLUMN = '发货单数量'
START_DATE = '2021-01-01'
FREQ = 'MS'

# 将 methods 字典定义为全局变量
methods = {
    '移动平均': lambda data: data.rolling(window=3).mean().iloc[-1],
    '自回归模型 (AR)': lambda data: AutoReg(data, lags=3).fit().forecast(steps=5),
    'ARIMA 模型': lambda data: ARIMA(data, order=(1, 1, 1)).fit().forecast(steps=5),
    '简单指数平滑法': lambda data: SimpleExpSmoothing(data).fit().forecast(steps=5),
    '季节性 ARIMA 模型 (SARIMAX)': lambda data: SARIMAX(data, order=(1, 1, 1), seasonal_order=(1, 1, 1, 12)).fit().forecast(steps=5),
    'Prophet 模型': lambda data: Prophet().fit(pd.DataFrame({'ds': pd.date_range(start=START_DATE, periods=len(data), freq=FREQ), 'y': data})).make_future_dataframe(periods=5, freq=FREQ).tail(5)['yhat']
}

@timeit
def read_and_preprocess_data(product_name):
    """
    读取并预处理数据
    :param product_name: 产品名称
    :return: 预处理后的数据
    """
    try:
        logging.info(f"正在读取 Excel 文件: {DATA_FILE}")
        df = pd.read_excel(DATA_FILE)
        logging.info("Excel 文件读取完成")
        logging.info(f"读取到的数据总行数: {len(df)}")
    except FileNotFoundError:
        logging.error(f"未找到文件: {DATA_FILE}")
        return None

    df[PRODUCT_NAME_COLUMN] = df[PRODUCT_NAME_COLUMN].str.strip().str.upper()
    product_name = product_name.strip().upper()

    logging.info(f"正在筛选 {PRODUCT_NAME_COLUMN} 为 {product_name} 的数据...")
    filtered_df = df[df[PRODUCT_NAME_COLUMN] == product_name].copy()
    logging.info(f"筛选后的数据总行数: {len(filtered_df)}")
    if len(filtered_df) == 0:
        logging.error(f"未找到 {product_name} 相关数据,请检查数据文件。")
        return None
    logging.info("数据筛选完成")

    try:
        logging.info(f"正在将 {DATE_COLUMN} 转换为日期序列...")
        filtered_df[DATE_COLUMN] = pd.to_datetime(filtered_df[DATE_COLUMN])
        logging.info("日期序列转换完成")
    except ValueError:
        logging.error(f"无法将 {DATE_COLUMN} 转换为日期序列,请检查数据格式。")
        return None

    # 提取年 - 月信息
    filtered_df['YearMonth'] = filtered_df[DATE_COLUMN].dt.to_period('M')

    logging.info("正在提取年、月信息...")
    filtered_df['Year'] = filtered_df[DATE_COLUMN].dt.year
    filtered_df['Month'] = filtered_df[DATE_COLUMN].dt.month
    logging.info("年、月信息提取完成")

    logging.info("正在汇总年、月的发货单数量并转换为整数...")
    aggregated_df = filtered_df.groupby(['YearMonth', 'Year', 'Month'])[QUANTITY_COLUMN].sum().astype(int).reset_index()
    aggregated_df.rename(columns={QUANTITY_COLUMN: 'Actual'}, inplace=True)
    logging.info("发货单数量汇总完成")

    # 检查日期连续性(补充缺失月份)
    logging.info("正在检查日期连续性并补充缺失月份...")
    full_date_range = pd.date_range(start=START_DATE, end=aggregated_df['YearMonth'].dt.to_timestamp().max(), freq=FREQ)
    aggregated_df['YearMonth'] = aggregated_df['YearMonth'].dt.to_timestamp()
    aggregated_df = aggregated_df.set_index('YearMonth').reindex(full_date_range).fillna(0).reset_index()
    aggregated_df.rename(columns={'index': 'YearMonth'}, inplace=True)
    aggregated_df['Year'] = aggregated_df['YearMonth'].dt.year
    aggregated_df['Month'] = aggregated_df['YearMonth'].dt.month
    logging.info("日期连续性检查和缺失月份补充完成")

    # 新增季节性分解代码
    stl = STL(aggregated_df['Actual'], period=12).fit()
    aggregated_df = aggregated_df.assign(
        trend=stl.trend,
        seasonal=stl.seasonal,
        residual=stl.resid
    )
    logging.info("季节性分解完成")

    return aggregated_df

@timeit
def train_and_predict(method_name, train_data, steps):
    """
    训练模型并进行预测
    :param method_name: 预测方法名称
    :param train_data: 训练数据
    :param steps: 预测步数
    :return: 预测结果
    """
    if method_name == '移动平均':
        return [methods[method_name](train_data)] * steps
    elif method_name == '自回归模型 (AR)':
        # 定义参数范围
        lags_range = range(1, 10)
        best_aic = float('inf')
        best_lags = None
        for lags in lags_range:
            try:
                model = AutoReg(train_data, lags=lags).fit()
                if model.aic < best_aic:
                    best_aic = model.aic
                    best_lags = lags
            except:
                continue
        model = AutoReg(train_data, lags=best_lags).fit()
        return model.forecast(steps=steps)
    elif method_name == 'ARIMA 模型':
        # 定义参数范围
        p = d = q = range(0, 2)
        pdq = list(itertools.product(p, d, q))
        best_aic = float('inf')
        best_pdq = None
        for param in pdq:
            try:
                model = ARIMA(train_data, order=param).fit()
                if model.aic < best_aic:
                    best_aic = model.aic
                    best_pdq = param
            except:
                continue
        model = ARIMA(train_data, order=best_pdq).fit()
        return model.forecast(steps=steps)
    elif method_name == '简单指数平滑法':
        # 定义参数范围
        smoothing_levels = np.linspace(0.1, 1, 10)
        best_mse = float('inf')
        best_smoothing_level = None
        for smoothing_level in smoothing_levels:
            try:
                model = SimpleExpSmoothing(train_data).fit(smoothing_level=smoothing_level)
                forecast = model.forecast(steps=steps)
                mse = mean_squared_error(train_data, forecast[:len(train_data)])
                if mse < best_mse:
                    best_mse = mse
                    best_smoothing_level = smoothing_level
            except:
                continue
        model = SimpleExpSmoothing(train_data).fit(smoothing_level=best_smoothing_level)
        return model.forecast(steps=steps)
    elif method_name == '季节性 ARIMA 模型 (SARIMAX)':
        # 定义参数范围
        p = d = q = range(0, 2)
        P = D = Q = range(0, 2)
        pdq = list(itertools.product(p, d, q))
        seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(P, D, Q))]
        best_aic = float('inf')
        best_pdq = None
        best_seasonal_pdq = None
        for param in pdq:
            for param_seasonal in seasonal_pdq:
                try:
                    model = SARIMAX(train_data, order=param, seasonal_order=param_seasonal).fit()
                    if model.aic < best_aic:
                        best_aic = model.aic
                        best_pdq = param
                        best_seasonal_pdq = param_seasonal
                except:
                    continue
        model = SARIMAX(train_data, order=best_pdq, seasonal_order=best_seasonal_pdq).fit()
        return model.forecast(steps=steps)
    elif method_name == 'Prophet 模型':
        train_df = pd.DataFrame({'ds': pd.date_range(start=START_DATE, periods=len(train_data), freq=FREQ), 'y': train_data})
        model = Prophet()
        model.fit(train_df)
        future = model.make_future_dataframe(periods=steps, freq=FREQ)
        return model.predict(future)['yhat'][-steps:]

@timeit
def evaluate_forecasting_methods(aggregated_df, train_size):
    """
    评估不同的预测方法
    :param aggregated_df: 汇总后的数据
    :param train_size: 训练集大小
    :return: 最佳预测方法和最佳均方误差
    """
    test_size = len(aggregated_df) - train_size
    train_data = aggregated_df['Actual'].iloc[:train_size]
    test_data = aggregated_df['Actual'].iloc[train_size:]
    logging.info(f"训练集数据量: {len(train_data)},测试集数据量: {len(test_data)}")
    logging.info("训练集和测试集划分完成")

    logging.info("开始评估每种预测方法...")
    best_method = None
    best_mse = float('inf')
    for method_name in methods:
        logging.info(f"正在使用 {method_name} 方法进行预测...")
        try:
            forecast = train_and_predict(method_name, train_data, test_size)
            mse = mean_squared_error(test_data, forecast)
            logging.info(f"{method_name} 方法的均方误差 (MSE): {mse}")
            if mse < best_mse:
                best_mse = mse
                best_method = method_name
        except Exception as e:
            logging.error(f"{method_name} 计算时出现错误: {e}")
    logging.info("预测方法评估完成")
    return best_method, best_mse

def generate_future_dates(aggregated_df, forecast_steps):
    """
    生成未来多个月的年和月信息
    :param aggregated_df: 汇总后的数据
    :param forecast_steps: 预测步数
    :return: 未来日期列表
    """
    last_year = aggregated_df['Year'].iloc[-1]
    last_month = aggregated_df['Month'].iloc[-1]
    future_dates = []
    for i in range(1, forecast_steps + 1):
        next_month = (last_month + i) % 12
        if next_month == 0:
            next_month = 12
        next_year = last_year + (last_month + i - 1) // 12
        future_dates.append((next_year, next_month))
    return future_dates

@timeit
def make_final_forecast(aggregated_df, best_method, product_name, forecast_steps):
    if best_method:
        print(f"正在使用 {best_method} 方法对未来 {forecast_steps} 个月度周期进行预测...")
        if best_method == 'Prophet 模型':
            train_df = pd.DataFrame({'ds': pd.date_range(start='2021-01-01', periods=len(aggregated_df['Actual']), freq='MS'), 'y': aggregated_df['Actual']})
            model = Prophet()
            model.fit(train_df)
            future = model.make_future_dataframe(periods=forecast_steps, freq='MS')
            final_forecast = model.predict(future)['yhat'][-forecast_steps:]
        else:
            final_forecast = (methods[best_method])(aggregated_df['Actual'])
        # 处理移动平均方法返回单一数值的情况
        if best_method == '移动平均':
            final_forecast = [final_forecast] * forecast_steps
        # 将预测结果转换为整数类型
        final_forecast = pd.Series(final_forecast).astype(int)

        # 获取最后一个已知月份的年和月
        last_year = aggregated_df['Year'].iloc[-1]
        last_month = aggregated_df['Month'].iloc[-1]

        # 生成未来多个月的年和月信息
        future_dates = []
        for i in range(1, forecast_steps + 1):
            next_month = (last_month + i) % 12
            if next_month == 0:
                next_month = 12
            next_year = last_year + (last_month + i - 1) // 12
            future_dates.append((next_year, next_month))

        # 创建一个包含预测结果和对应年月的 DataFrame
        forecast_df = pd.DataFrame({
            'Year': [year for year, _ in future_dates],
            'Month': [month for _, month in future_dates],
            'Actual': [None] * forecast_steps,  # 预测部分实际值为空
            'Forecast': final_forecast
        })

        # 合并历史数据和预测数据
        combined_df = pd.concat([aggregated_df, forecast_df], ignore_index=True)

        # 打印包含年和月信息的预测结果
        print(f"未来 {forecast_steps} 个月度周期的预测结果:")
        for (year, month), forecast in zip(future_dates, final_forecast):
            print(f"{year}年{month}月: {forecast}")

        # 保存为 CSV 文件,文件名包含 product_name
        csv_filename = f'{product_name}_forecast.csv'
        combined_df.to_csv(csv_filename, index=False)
        print(f"预测结果已保存到 {csv_filename}")

if __name__ == "__main__":
    product_name = input("请输入要预测的产品名称: ").strip()
    # product_name = 'IUP27 硒鼓'  # 可修改为其他产品名称
    forecast_steps = 12  # 预测周期
    train_percentage_minmax = (0.5, 0.9)  # 训练集百分比的最小值和最大值,这里以 70% 到 90% 为例
    aggregated_df = read_and_preprocess_data(product_name)

    best_overall_mse = float('inf')
    best_overall_method = None
    best_train_percentage = None

    # 遍历不同的训练集百分比
    for train_percentage in [i / 100 for i in range(int(train_percentage_minmax[0] * 100), int(train_percentage_minmax[1] * 100) + 1)]:
        train_size = int(len(aggregated_df) * train_percentage)
        print(f"\n正在评估训练集百分比为 {train_percentage * 100}% 的情况...")
        best_method, best_mse = evaluate_forecasting_methods(aggregated_df, train_size)
        if best_mse < best_overall_mse:
            best_overall_mse = best_mse
            best_overall_method = best_method
            best_train_percentage = train_percentage

    print(f"\n最佳整体训练集百分比: {best_train_percentage * 100}%")
    print(f"最佳整体预测方法: {best_overall_method},均方误差: {best_overall_mse}")
    best_train_size = int(len(aggregated_df) * best_train_percentage)
    make_final_forecast(aggregated_df, best_overall_method, product_name, forecast_steps)