使用Facebook Prophet时如何加入Continent变量进行异常检测
解决方案:按「日期+大洲」维度实现异常检测
核心思路是拆分数据集为「每个Value列 + 每个大洲」的组合,分别建模预测,全程保留大洲标识,最终将同Value的所有大洲结果整合输出,以下是修改后的完整代码:
1. 读取数据与预处理(重构数据拆分逻辑)
import pandas as pd from prophet import Prophet # 读取原始数据 df_question = pd.read_excel("question.xlsx", sheet_name="Sheet2", engine='openpyxl') # 预处理:清理数值格式(去掉百分号、替换逗号为小数点) for col in df_question.columns[2:]: # 处理带百分号的列 if df_question[col].astype(str).str.contains('%').any(): df_question[col] = df_question[col].astype(str).str.replace('%', '').str.replace(',', '.').astype(float) else: df_question[col] = df_question[col].astype(str).str.replace(',', '.').astype(float) # 重构数据集:按「Value列 + 大洲」拆分 datas = [] names_with_continent = [] # 存储"Value_大洲"的标识,用于后续区分 for col in df_question.columns[2:]: value_name = col # 按大洲分组,每个大洲生成一个数据集 for continent, group in df_question.groupby('Continent'): subset = group[['Continent', 'Date', col]].copy() # 重命名列适配Prophet格式 subset.columns = ["Site", "ds", "y"] datas.append(subset) names_with_continent.append(f"{value_name}_{continent}")
2. 建模函数修改(保留大洲信息)
# 设置显示选项 pd.set_option("display.max_columns", None) pd.set_option("display.max_rows", None) pd.set_option("display.float_format", lambda x: "%.3f" % x) pd.set_option("display.expand_frame_repr", False) pd.set_option("display.width", 500) def fit_predict_model(dataframe): # 初始化Prophet模型 m = Prophet(yearly_seasonality=True, daily_seasonality=True) m.add_country_holidays(country_name="TR") # 拟合当前大洲的模型 m = m.fit(dataframe[['ds', 'y']]) # 生成预测结果并加入实际值、大洲标识 forecast = m.predict(dataframe[['ds', 'y']]) forecast["fact"] = dataframe["y"].reset_index(drop=True) forecast["Site"] = dataframe["Site"].iloc[0] # 每个数据集对应单个大洲,直接取第一个值 return forecast # 批量生成所有「Value+大洲」的预测结果 preds = [] for data in datas: preds.append(fit_predict_model(data))
3. 可视化(区分大洲与Value)
pd.options.plotting.backend = "plotly" for i, pred in enumerate(preds): # 图表标题包含Value和大洲信息 fig = pred.plot(x='ds', y=["yhat_lower", "fact", "yhat_upper", "yhat"]) fig.update_layout(title=f"{names_with_continent[i]} - 预测与实际值对比") # 添加大洲标识注释 fig.add_annotation(text=f"大洲: {pred['Site'].iloc[0]}", xref="paper", yref="paper", x=0.95, y=0.95, showarrow=False) fig.show()
4. 异常检测(加入大洲维度)
def detect_anomalies(forecast): forecasted = forecast[["ds", "Site", "trend", "yhat", "yhat_lower", "yhat_upper", "fact"]].copy() # 标记异常值 forecasted["anomaly"] = 0 forecasted.loc[forecasted["fact"] > forecasted["yhat_upper"], "anomaly"] = 1 forecasted.loc[forecasted["fact"] < forecasted["yhat_lower"], "anomaly"] = -1 # 计算异常重要性 forecasted["importance"] = 0.0 forecasted.loc[forecasted["anomaly"] == 1, "importance"] = \ (forecasted["fact"] - forecasted["yhat_upper"]) / forecasted["fact"] forecasted.loc[forecasted["anomaly"] == -1, "importance"] = \ (forecasted["yhat_lower"] - forecasted["fact"]) / forecasted["fact"] return forecasted # 批量生成异常检测结果 preds1 = [] for pred in preds: preds1.append(detect_anomalies(pred))
5. 结果输出(按Value分Sheet,Sheet内包含所有大洲数据)
# 先将同Value的结果合并,方便按Value分Sheet输出 result_dict = {} for name, res in zip(names_with_continent, preds1): value_name = name.split('_')[0] if value_name not in result_dict: result_dict[value_name] = [] result_dict[value_name].append(res) # 将合并后的结果写入Excel with pd.ExcelWriter(r'C:\Users\user\OneDrive\Belgeler\anomaly\question_results.xlsx') as writer: for value_name, res_list in result_dict.items(): # 合并同Value下所有大洲的结果 combined_df = pd.concat(res_list, ignore_index=True) # 按日期和大洲排序 combined_df = combined_df.sort_values(by=['ds', 'Site']) # 写入Sheet,Sheet名为"ValueX_Results" combined_df.to_excel(writer, sheet_name=f"{value_name}_Results", index=False)
关键修改点说明
- 数据拆分:从原来仅按Value列拆分,改为按「Value列+大洲」的组合拆分,确保每个模型只针对单个大洲的单个Value数据,避免不同大洲的数据干扰。
- 保留大洲标识:在建模、预测、异常检测的全流程中,始终携带
Site(大洲)列,确保最终结果能关联到具体大洲。 - 可视化优化:图表标题和注释明确标注对应的Value和大洲,便于区分不同维度的结果。
- 输出调整:同Value的所有大洲结果合并到同一个Sheet中,方便按「日期+大洲」筛选分析,同时保持原有的按Value分Sheet的逻辑。
内容的提问来源于stack exchange,提问作者Arnve
相关产品推荐
相关产品推荐

