我们来创建一个针对电商平台日志数据时序预测程序。这个程序将模拟生成包含访问量(如PV或UV)的时序日志数据,并使用ARIMA和Prophet两种模型对未来流量进行预测。

重要提示

  1. Prophet库安装prophet库的安装可能需要C++编译器和一些额外依赖。如果安装遇到问题,请参考其官方文档。如果无法安装,程序会自动跳过Prophet部分。
  2. pmdarima库安装:为了实现ARIMA模型的自动调参,我们使用pmdarima库。如果未安装,程序会回退到使用固定参数的statsmodels ARIMA。

import pandas as pd
import numpy as np
from sklearn.metrics import mean_absolute_error, mean_squared_error
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings("ignore")

# --- 尝试导入可选库 ---
PROPHET_AVAILABLE = False
PMDARIMA_AVAILABLE = False

try:
    from prophet import Prophet
    PROPHET_AVAILABLE = True
    print("Info: Prophet库已导入,将包含Prophet模型预测。")
except ImportError:
    print("Warning: Prophet库未导入。将跳过Prophet模型部分。")

try:
    from pmdarima import auto_arima
    PMDARIMA_AVAILABLE = True
    print("Info: pmdarima库已导入,ARIMA模型将自动选择参数。")
except ImportError:
    print("Warning: pmdarima库未导入。ARIMA模型将使用固定参数(2,1,2)。")
    from statsmodels.tsa.arima.model import ARIMA # 基础ARIMA

# --- 配置 ---
# 模拟数据参数
NUM_DAYS_HISTORY = 730  # 历史数据天数 (例如2年)
FORECAST_HORIZON = 30   # 预测未来天数
REPORT_PREFIX = '电商日志时序预测报告'
RANDOM_SEED = 42
np.random.seed(RANDOM_SEED)

# --- 1. 数据生成 ---

def generate_sample_traffic_data(n_days):
    """
    生成模拟的电商平台日访问量(如PV)时序数据。
    包含趋势、多重季节性(周、年)、事件效应和噪声。
    """
    print("--- 正在生成模拟电商日志流量数据 ---")
    start_date = datetime.now() - timedelta(days=n_days-1)
    dates = pd.date_range(start=start_date, periods=n_days, freq='D')
    
    # 1. 基础水平和长期趋势
    base_level = 50000
    trend_slope = np.random.uniform(50, 150) # 每天平均增长50-150个PV
    trend = np.arange(n_days) * trend_slope
    
    # 2. 季节性
    # 周度季节性 (周末流量高)
    day_of_week = pd.Series(dates).dt.dayofweek
    weekly_effect = np.where(day_of_week.isin([5, 6]), 1.2, 1.0) # 周六、日流量增加20%
    
    # 年度季节性 (简化为正弦波,例如夏季流量可能更高)
    doy = pd.Series(dates).dt.dayofyear
    annual_effect = 1 + 0.1 * np.sin(2 * np.pi * (doy - 80) / 365.25) # 从3月21日(春分)开始计算,峰值在夏至
    
    # 3. 特定事件/促销效应 (模拟几个大促节日)
    event_effect = np.ones(n_days)
    # 618 (6月18日)
    june_18_indices = [i for i, date in enumerate(dates) if date.month == 6 and date.day == 18]
    for idx in june_18_indices:
        event_effect[idx] *= np.random.uniform(3.0, 5.0) # 流量翻3-5倍
        if idx > 0: event_effect[idx-1] *= np.random.uniform(1.5, 2.5) # 前一天预热
        if idx < n_days-1: event_effect[idx+1] *= np.random.uniform(1.2, 1.8) # 后一天返场
    
    # 双11 (11月11日)
    nov_11_indices = [i for i, date in enumerate(dates) if date.month == 11 and date.day == 11]
    for idx in nov_11_indices:
        event_effect[idx] *= np.random.uniform(5.0, 8.0) # 流量翻5-8倍
        if idx > 0: event_effect[idx-1] *= np.random.uniform(2.0, 4.0)
        if idx < n_days-1: event_effect[idx+1] *= np.random.uniform(1.5, 2.5)
        
    # 4. 随机噪声 (使用泊松噪声更符合计数数据特性,但这里用正态简化)
    noise = np.random.normal(1.0, 0.05, n_days) # 5%的随机波动
    
    # 5. 组合所有成分
    traffic = base_level + trend
    traffic = traffic * weekly_effect * annual_effect * event_effect * noise
    traffic = np.maximum(traffic, 0) # 流量不能为负
    
    df = pd.DataFrame({
        'date': dates,
        'value': np.round(traffic, 0).astype(int) # 模拟PV/UV等整数指标
    })
    
    csv_filename = f'{REPORT_PREFIX}_模拟流量数据.csv'
    df.to_csv(csv_filename, index=False, encoding='utf-8-sig')
    print(f"模拟流量数据已生成并保存至: {csv_filename}")
    return df

# --- 2. 数据预处理与EDA ---

def preprocess_and_eda(df):
    """数据预处理和探索性数据分析"""
    print("\n--- 正在进行数据预处理与EDA ---")
    df_processed = df.copy()
    df_processed.set_index('date', inplace=True)
    df_processed.sort_index(inplace=True)
    
    # 检查缺失值
    if df_processed.isnull().sum().sum() > 0:
        print("警告: 发现缺失值,正在进行前向填充...")
        df_processed['value'].fillna(method='ffill', inplace=True)
    
    # 绘制时间序列图
    plt.figure(figsize=(15, 6))
    plt.plot(df_processed.index, df_processed['value'], linewidth=1, label='历史流量')
    plt.title('电商平台日志流量时间序列')
    plt.xlabel('日期')
    plt.ylabel('流量指标 (模拟PV)')
    plt.legend()
    plt.grid(True)
    ts_plot_path = f'{REPORT_PREFIX}_流量时间序列图.png'
    plt.savefig(ts_plot_path, bbox_inches='tight')
    plt.close()
    print(f"流量时间序列图已保存至: {ts_plot_path}")
    
    return df_processed

# --- 3. ARIMA 模型预测 ---

def forecast_with_arima(df, forecast_periods):
    """使用ARIMA模型进行预测"""
    print("\n--- 正在使用ARIMA模型进行预测 ---")
    
    train_size = int(len(df) * 0.85)
    train, test = df.iloc[:train_size], df.iloc[train_size:]
    
    # 使用auto_arima自动选择最佳参数,或使用固定参数
    if PMDARIMA_AVAILABLE:
        print("使用auto_arima自动选择ARIMA参数...")
        # seasonal=True 对于年度数据效果好,但计算慢。这里简化处理。
        model_auto = auto_arima(train['value'], start_p=1, max_p=5, start_q=1, max_q=5,
                                d=1, max_d=2, trace=False, error_action='ignore', 
                                suppress_warnings=True, stepwise=True, seasonal=False)
        print(f"auto_arima选择的最佳模型: ARIMA{model_auto.order}")
        model = model_auto
    else:
        print("pmdarima不可用,使用固定参数 ARIMA(2,1,2)...")
        model = ARIMA(train['value'], order=(2,1,2))
        
    # 拟合模型
    model_fit = model.fit() if not PMDARIMA_AVAILABLE else model
    
    # 预测测试集用于评估
    if len(test) > 0:
        test_forecast = model_fit.forecast(steps=len(test))
        test_forecast = np.maximum(test_forecast, 0) # 确保预测值非负
    
    # 预测未来
    future_forecast = model_fit.forecast(steps=forecast_periods)
    future_forecast = np.maximum(future_forecast, 0)
    future_dates = pd.date_range(start=df.index[-1] + timedelta(days=1), periods=forecast_periods, freq='D')
    
    # 评估 (仅在有测试集时)
    metrics = {}
    if len(test) > 0:
        metrics['MAE'] = mean_absolute_error(test['value'], test_forecast)
        metrics['RMSE'] = np.sqrt(mean_squared_error(test['value'], test_forecast))
        print(f"ARIMA模型在测试集上的表现:")
        for k, v in metrics.items():
            print(f"  - {k}: {v:.2f}")
    
    # 绘制预测结果
    plt.figure(figsize=(15, 6))
    plt.plot(df.index, df['value'], label='历史流量', linewidth=1)
    if len(test) > 0:
        plt.plot(test.index, test_forecast, label='ARIMA测试集预测', linestyle='--', alpha=0.8)
    plt.plot(future_dates, future_forecast, label=f'ARIMA未来{forecast_periods}天预测', linestyle='-.', marker='o', markersize=4)
    plt.axvline(x=df.index[train_size-1], color='black', linestyle=':', alpha=0.7, label='训练/测试分割点')
    plt.title('ARIMA模型流量预测')
    plt.xlabel('日期')
    plt.ylabel('流量指标 (模拟PV)')
    plt.legend()
    plt.grid(True)
    arima_plot_path = f'{REPORT_PREFIX}_ARIMA预测图.png'
    plt.savefig(arima_plot_path, bbox_inches='tight')
    plt.close()
    print(f"ARIMA预测图已保存至: {arima_plot_path}")
    
    results = {
        'model_name': 'ARIMA',
        'predictions': future_forecast,
        'future_dates': future_dates,
        'metrics': metrics,
        'plot_path': arima_plot_path
    }
    return results

# --- 4. Prophet 模型预测 ---

def forecast_with_prophet(df, forecast_periods):
    """使用Prophet模型进行预测"""
    if not PROPHET_AVAILABLE:
        print("Prophet模型跳过,因为库未导入。")
        return None
        
    print("\n--- 正在使用Prophet模型进行预测 ---")
    
    # Prophet需要特定的列名
    df_prophet = df.reset_index()[['date', 'value']].rename(columns={'date': 'ds', 'value': 'y'})
    
    # 创建并拟合模型
    model = Prophet(
        daily_seasonality=False, # 数据是日粒度,关闭内置的日季节性
        yearly_seasonality='auto',
        weekly_seasonality='auto',
        seasonality_mode='multiplicative' # 对于有明显比例变化的季节性更合适
    )
    # 可以在这里添加自定义节假日
    # model.add_country_holidays(country_name='CN')
    
    model.fit(df_prophet)
    
    # 创建未来日期框架
    future = model.make_future_dataframe(periods=forecast_periods)
    
    # 进行预测
    forecast = model.predict(future)
    
    # 绘制预测结果
    fig1 = model.plot(forecast, figsize=(15, 6))
    plt.title('Prophet模型流量预测')
    prophet_plot_path = f'{REPORT_PREFIX}_Prophet预测图.png'
    plt.savefig(prophet_plot_path, bbox_inches='tight')
    plt.close(fig1)
    
    # 绘制模型成分 (趋势、季节性等)
    fig2 = model.plot_components(forecast, figsize=(12, 10))
    prophet_comp_plot_path = f'{REPORT_PREFIX}_Prophet成分图.png'
    plt.savefig(prophet_comp_plot_path, bbox_inches='tight')
    plt.close(fig2)
    print(f"Prophet预测图已保存至: {prophet_plot_path}")
    print(f"Prophet成分图已保存至: {prophet_comp_plot_path}")
    
    # 提取未来预测值
    future_forecast_df = forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail(forecast_periods)
    future_forecast_df['yhat'] = np.maximum(future_forecast_df['yhat'], 0)
    future_forecast_df['yhat_lower'] = np.maximum(future_forecast_df['yhat_lower'], 0)
    
    results = {
        'model_name': 'Prophet',
        'forecast_df': future_forecast_df,
        'metrics': {}, # Prophet有内置交叉验证,这里简化
        'plot_path': prophet_plot_path,
        'comp_plot_path': prophet_comp_plot_path
    }
    return results

# --- 5. 报告生成 ---

def generate_forecast_report(arima_res, prophet_res, forecast_horizon):
    """生成最终的时序预测分析报告"""
    print("\n--- 正在生成电商日志时序预测报告 ---")
    from datetime import datetime
    report_filename = f"{REPORT_PREFIX}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
    
    with open(report_filename, 'w', encoding='utf-8') as f:
        f.write("=" * 60 + "\n")
        f.write("           电商平台日志数据时序预测分析报告\n")
        f.write(f"              生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
        f.write("=" * 60 + "\n\n")

        f.write("--- 1. 项目概述 ---\n")
        f.write("本报告旨在通过对电商平台历史日志数据(模拟流量指标)的分析,预测未来一段时间的流量趋势。\n")
        f.write("此预测可为服务器资源规划、带宽管理、营销活动安排等提供数据支持。\n\n")

        f.write("--- 2. 数据概览 ---\n")
        f.write(f"数据来源: 模拟生成的电商平台日访问量(PV)数据。\n")
        f.write(f"数据规模: {NUM_DAYS_HISTORY} 天的历史记录。\n")
        f.write("数据特征: 包含长期增长趋势、周度季节性、年度季节性以及'618'、'双11'等大促事件效应。\n")
        f.write("原始数据已保存为 CSV 文件。\n\n")

        f.write("--- 3. 探索性数据分析 (EDA) ---\n")
        f.write("对时间序列数据进行了可视化分析,以识别其核心模式:\n")
        f.write("- 趋势: 数据显示总体呈稳定上升趋势。\n")
        f.write("- 季节性: 存在显著的周度(周末高峰)和年度周期性波动。\n")
        f.write("- 事件效应: 可以清晰观察到'618'、'双11'等大促节日带来的流量巨大峰值。\n")
        f.write("流量时间序列图已生成。\n\n")

        f.write("--- 4. 预测模型 ---\n")
        f.write("采用了两种主流的时间序列预测模型进行建模与对比:\n")
        f.write("1. ARIMA (自回归积分滑动平均模型): \n")
        f.write("   - 一种基于统计学的经典模型。\n")
        f.write("   - 适用于单变量、平稳或可转化为平稳的时间序列。\n")
        f.write("   - 优点是模型可解释性强。\n")
        f.write("2. Prophet (由Facebook开发): \n")
        f.write("   - 一种灵活且强大的模型,特别擅长处理具有强季节性、历史数据缺失和趋势突变的序列。\n")
        f.write("   - 内置对节假日的支持。\n")
        f.write("   - 优点是易于使用且鲁棒性好。\n\n")
        
        f.write("--- 5. ARIMA模型预测结果 ---\n")
        if arima_res:
            f.write(f"模型: {arima_res['model_name']}\n")
            if arima_res['metrics']:
                f.write("模型评估 (在测试集上):\n")
                for metric, value in arima_res['metrics'].items():
                    f.write(f"  - {metric}: {value:.2f}\n")
            f.write(f"未来{forecast_horizon}天预测值预览:\n")
            forecast_df_arima = pd.DataFrame({'date': arima_res['future_dates'], 'predicted_value': arima_res['predictions']})
            f.write(forecast_df_arima.round(2).to_string(index=False))
            f.write(f"\n预测图表: {arima_res['plot_path']}\n\n")
        else:
            f.write("ARIMA模型执行失败或被跳过。\n\n")

        f.write("--- 6. Prophet模型预测结果 ---\n")
        if prophet_res:
            f.write(f"模型: {prophet_res['model_name']}\n")
            f.write(f"未来{forecast_horizon}天预测值预览 (包含不确定性区间):\n")
            forecast_summary = prophet_res['forecast_df'].rename(columns={
                'ds': 'date', 'yhat': 'predicted_value', 
                'yhat_lower': 'lower_bound', 'yhat_upper': 'upper_bound'
            })
            f.write(forecast_summary.round(2).to_string(index=False))
            f.write(f"\n预测图表: {prophet_res['plot_path']}\n")
            f.write(f"趋势与季节性成分图: {prophet_res['comp_plot_path']}\n\n")
        else:
            f.write("Prophet模型因库未导入而被跳过。\n\n")

        f.write("--- 7. 结论与建议 ---\n")
        f.write("1. 趋势预判: 两种模型均预测未来流量将延续当前的增长趋势。\n")
        f.write("2. 风险管理: 预测区间(尤其是Prophet)提供了不确定性估计,有助于评估风险。\n")
        f.write("3. 资源规划: \n")
        f.write("   - 根据预测的流量峰值(如未来大促期)提前扩容服务器和带宽。\n")
        f.write("   - 在预测流量较低的时期进行系统维护和更新。\n")
        f.write("4. 营销策略: 将营销活动与预测的流量高峰相结合,以最大化效果。\n")
        f.write("5. 模型迭代: 定期使用最新的日志数据重新训练模型,以保持预测的时效性和准确性。\n\n")

        f.write("=" * 60 + "\n")
        f.write("                         报告结束\n")
        f.write("=" * 60 + "\n")

    print(f"电商日志时序预测报告已生成: {report_filename}")

# --- 主函数 ---

def main():
    """主函数"""
    # 1. 生成数据
    df_traffic = generate_sample_traffic_data(NUM_DAYS_HISTORY)
    
    # 2. 数据预处理与EDA
    df_processed = preprocess_and_eda(df_traffic)
    
    # 3. ARIMA预测
    arima_results = forecast_with_arima(df_processed, FORECAST_HORIZON)
    
    # 4. Prophet预测
    prophet_results = forecast_with_prophet(df_processed, FORECAST_HORIZON)
    
    # 5. 生成报告
    generate_forecast_report(arima_results, prophet_results, FORECAST_HORIZON)
    
    print("\n电商日志数据时序预测分析流程完成。")

if __name__ == "__main__":
    main()