使用R实现prophet模型,拟合和预测疾病发生率数据
Prophet 是由 Facebook 开发的一种用于时间序列预测的开源算法。与传统的 ARIMA 模型不同,Prophet 采用了一种更加灵活和易于使用的建模方法。下面我来详细解释一下 Prophet 模型的主要特点有加法模型、灵活的趋势建模、季节性建模、节假日效应、易用性、鲁棒性等等。
library(prophet)
library(ggplot2)
library(dplyr)
library(zoo)
library(Metrics)
library(moments)
# 读取数据
df <- read.csv('data.csv')
df$ds <- as.Date(df$ds, format = "%Y/%m/%d")
# 检查并处理缺失值
missing_count <- sum(is.na(df$y))
if(missing_count > 0) {
cat(sprintf("发现%d个缺失值,使用线性插值填充\n", missing_count))
df$y <- na.approx(df$y, na.rm = FALSE)
}
# 检测和处理异常值
mean_val <- mean(df$y, na.rm = TRUE)
sd_val <- sd(df$y, na.rm = TRUE)
outliers <- df$y[abs(df$y - mean_val) > 3*sd_val]
if(length(outliers) > 0) {
cat(sprintf("发现%d个异常值\n", length(outliers)))
df$y[df$y > mean_val + 3*sd_val] <- mean_val + 3*sd_val
df$y[df$y < mean_val - 3*sd_val] <- mean_val - 3*sd_val
}
发现39个异常值
# 检查是否需要对数变换
skewness <- moments::skewness(df$y)
log_transform <- FALSE
if(skewness > 1) {
cat("数据存在偏态,进行对数变换\n")
df$y <- log1p(df$y)
log_transform <- TRUE
}
# 设置节假日
chinese_holidays <- data.frame(
holiday = 'chinese_new_year',
ds = as.Date(c('2016-02-07','2016-02-08','2016-02-09',
'2017-01-28','2017-01-29','2017-01-30',
'2018-02-16','2018-02-17','2018-02-18',
'2019-02-05','2019-02-06','2019-02-07',
'2020-01-25','2020-01-26','2020-01-27',
'2021-02-12','2021-02-13','2021-02-14',
'2022-02-01','2022-02-02','2022-02-03',
'2023-01-22','2023-01-23','2023-01-24',
'2024-02-01','2024-02-11','2024-02-12')),
lower_window = -1,
upper_window = 3
)
model <- prophet(
df,
holidays = chinese_holidays,
yearly.seasonality = TRUE,
weekly.seasonality = TRUE,
daily.seasonality = FALSE,
changepoint.prior.scale = 0.05, # 控制趋势灵活性
seasonality.prior.scale = 10.0, # 控制季节性强度
holidays.prior.scale = 10.0 # 控制节假日效应强度
)
future <- make_future_dataframe(model, periods = 365)
forecast <- predict(model, future)
# 如果进行了对数变换,需要转换回原始尺度
if(log_transform) {
forecast$yhat <- expm1(forecast$yhat)
forecast$yhat_lower <- expm1(forecast$yhat_lower)
forecast$yhat_upper <- expm1(forecast$yhat_upper)
df$y <- expm1(df$y)
}
# 计算误差指标
actual <- df$y
predicted <- forecast$yhat[1:length(actual)]
mae <- mean(abs(actual - predicted))
rmse <- sqrt(mean((actual - predicted)^2))
mape <- mean(abs((actual - predicted) / actual)) * 100
cat(sprintf("\n模型评估指标:
MAE: %.2f
RMSE: %.2f
MAPE: %.2f%%\n",
mae, rmse, mape))
模型评估指标:
MAE: 0.37
RMSE: 0.52
MAPE: 4.44%
MAE为0.37说明预测值平均偏离实际值不到半个单位,表现相当不错。RMSE略大于MAE说明存在一些较大的预测误差,但差距不大 MAPE (平均绝对百分比误差) = 4.44;MAPE小于5%通常被认为是很好的预测精度。三个指标都显示模型具有很好的预测精度。
cv_results <- cross_validation(model, initial = 730,
period = 365, horizon = 365,
units = 'days')
cv_metrics <- performance_metrics(cv_results)
cv_summary <- data.frame(
Metric = c("MAE", "RMSE", "MAPE"),
Mean = c(
mean(cv_metrics$mae),
mean(cv_metrics$rmse),
mean(cv_metrics$mape)
),
SD = c(
sd(cv_metrics$mae),
sd(cv_metrics$rmse),
sd(cv_metrics$mape)
)
)
# 打印格式化结果
print(format(cv_summary, digits = 2))
Metric Mean SD
1 MAE 0.553 0.164
2 RMSE 0.678 0.180
3 MAPE 0.068 0.019
所有指标的标准差相对较小,说明模型在不同时期的预测性能比较稳定。MAE 和 RMSE 的差距合理,说明没有特别极端的预测偏差。
# 预测结果图
p <- plot(model, forecast) +
ggtitle("Prophet 时间序列预测") +
xlab("日期") +
ylab("值") +
theme_minimal() +
theme(
plot.title = element_text(size = 16, face = "bold"),
axis.title = element_text(size = 12),
axis.text = element_text(size = 10)
)
# 高亮节假日
if (!is.null(model$holidays)) {
holidays_df <- model$holidays
p <- p + geom_vline(data = holidays_df,
aes(xintercept = as.numeric(ds)),
color = "red",
linetype = "dashed",
alpha = 0.5)
}
# 显示预测区间
p <- p + geom_ribbon(aes(ymin = yhat_lower, ymax = yhat_upper),
alpha = 0.2,
fill = "blue")
print(p)
# 绘制季节性分解图
p2 <- prophet_plot_components(model, forecast)
趋势项:2016-2020年间有轻微波动,2020年达到峰值后开始下降,预测未来趋势继续下滑。 节假日效应:春节期间有明显的负向效应,效应强度相对稳定。 周度效应(weekly):周二、周三最低,周四到周六逐渐上升。 年度效应(yearly):显示明显的季节性模式,7-12月为高峰期,4-6月为低谷期。
# 绘制残差分析图
residuals <- actual - predicted
p3 <- ggplot() +
geom_point(aes(x = df$ds, y = residuals)) +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(title = "残差时间序列图",
x = "日期",
y = "残差") +
theme_minimal()
print(p3)
# 残差分布图
p4 <- ggplot() +
geom_histogram(aes(x = residuals, y = ..density..), bins = 30) +
geom_density(aes(x = residuals)) +
labs(title = "残差分布图",
x = "残差",
y = "密度") +
theme_minimal()
print(p4)
残差分布图分析:呈现钟形曲线,接近正态分布,这表明模型预测误差分布合理,没有明显的系统性偏差。 残差时间序列图分析:残差在0线附近随机分布,没有明显的趋势或模式,残差的波动强度相对稳定。
metrics <- data.frame(
metric = c("MAE", "RMSE", "MAPE"),
value = c(mae, rmse, mape)
)
# 创建包含所有评估指标的数据框
metrics_combined <- data.frame(
Metric = c("MAE", "RMSE", "MAPE"),
Training = c(mae, rmse, mape),
CV_Mean = c(mean(cv_metrics$mae), mean(cv_metrics$rmse), mean(cv_metrics$mape)),
CV_SD = c(sd(cv_metrics$mae), sd(cv_metrics$rmse), sd(cv_metrics$mape))
)
# 打印评估指标结果
print(format(metrics_combined, digits = 2))
Metric Training CV_Mean CV_SD
1 MAE 0.37 0.553 0.164
2 RMSE 0.52 0.678 0.180
3 MAPE 4.44 0.068 0.019
模型拟合质量好,预测误差分布合理;并且成功捕捉了数据的主要季节性模式,交叉验证结果显示模型具有良好的泛化能力,但是MAPE指标的训练集和交叉验证集差异较大,可以考虑实际应用中在节假日效应中加入其他重要节日。