如何为geom_smooth的gam平滑曲线设置最大拐点数量避免过拟合?
问题描述
我用geom_smooth的GAM平滑方法做数据集趋势可视化,但多个站点出现过拟合(比如第一个分面的红色数据点拟合过度)。想调整代码限制最大拐点数量(比如2或3个),聚焦整体趋势。
后续测试调整s(x, bs = "cs")的k值:我制作了包含二次段和线性段的示例数据集,目标是捕捉x≈5和x≈10处的两个主要拐点。测试k=2到6及无约束情况时:
- k=2触发警告,被R自动增至3;
- k=5才开始捕捉到目标拐点,但
k=5的含义不明确,还额外出现x≈16处的小拐点; - k=6及无约束时曲线贴合数据点,在我的数据集里会严重过拟合。
目前仍不清楚如何指定拐点数量。
原始代码
ggplot() + stat_summary(fun = "mean", data = df, aes(x = year, y = morpho, group = interaction(year, group), col = group), shape = 1, alpha = 0.4) + geom_smooth(method = "gam", se = TRUE, formula = y ~ s(x, bs = "cs"), data = df, aes(x = year, y = morpho, group = group, col = group)) + facet_grid(. ~ site) + theme_bw() + theme(legend.position = "bottom", legend.direction = "horizontal", axis.text.x = element_text(angle = 60, vjust = 1, hjust=1))
测试代码
library(ggpubr) library(ggplot2) # 注:原代码中library(ggplot)为错误写法,应为ggplot2 df <- data.frame( x1 = c(1:19), y1 = c(1.1, 4, 6.9, 8.5, 10, 7.2, 3.7, 2.2, 1, #quadratic 0.4, 1.6, 2.5, 3.7, 4.6, 5.8, 6.7, 7.9, 8.8, 10)) #linear pk <- ggplot(data = df, aes(y = y1, x = x1)) + geom_point(col = "red") + theme_bw() # vary k from 2 to not specified ggarrange(nrow = 3, ncol = 2, pk + geom_smooth(method = "gam", formula = y ~ s(x, bs = "cs", k = 2)) + ggtitle("k=2"), pk + geom_smooth(method = "gam", formula = y ~ s(x, bs = "cs", k = 3)) + ggtitle("k=3"), pk + geom_smooth(method = "gam", formula = y ~ s(x, bs = "cs", k = 4)) + ggtitle("k=4"), pk + geom_smooth(method = "gam", formula = y ~ s(x, bs = "cs", k = 5)) + ggtitle("k=5"), pk + geom_smooth(method = "gam", formula = y ~ s(x, bs = "cs", k = 6)) + ggtitle("k=6"), pk + geom_smooth(method = "gam", formula = y ~ s(x, bs = "cs")) + ggtitle("k unconstrained"))
解决方案
GAM的s()函数中k是样条基函数数量,并非直接对应拐点数量,拐点数量与基函数数量相关但无绝对对应关系。要限制拐点数量,可采用以下几种直接方法:
1. 用分段模型精准控制拐点
若明确知道拐点位置(比如示例中的x=5和x=10),直接使用分段线性/多项式模型,完全可控拐点位置与数量:
# 示例:指定x=5、x=10两个拐点的分段模型 geom_smooth(method = "lm", formula = y ~ poly(x, 2, raw=TRUE)*(x<=5) + poly(x,1,raw=TRUE)*(x>5 & x<=10) + poly(x,1,raw=TRUE)*(x>10), se=TRUE)
2. 调整GAM惩罚项+限制k值
若仍想使用GAM,不要仅调整k,结合惩罚项增强平滑度:
- 先将
k设为略大于目标拐点对应的基函数数(比如要2个拐点,k=4足够,立方样条的拐点数量≈k-2); - 增加
gamma参数增强惩罚,抑制多余小拐点:
geom_smooth(method = "gam", formula = y ~ s(x, bs = "cs", k=4, gamma=2), se=TRUE)
gamma值越大,惩罚力度越强,曲线越平滑。
3. 替换样条类型
用bs="cr"(立方回归样条)替代bs="cs"(循环样条),cr基函数更简洁,不易产生多余拐点,配合小k值使用:
geom_smooth(method = "gam", formula = y ~ s(x, bs = "cr", k=4), se=TRUE)
修改后的示例代码
针对测试数据集,实现精准控制拐点的两种方案:
library(ggpubr) library(ggplot2) df <- data.frame( x1 = c(1:19), y1 = c(1.1, 4, 6.9, 8.5, 10, 7.2, 3.7, 2.2, 1, #quadratic 0.4, 1.6, 2.5, 3.7, 4.6, 5.8, 6.7, 7.9, 8.8, 10)) #linear pk <- ggplot(data = df, aes(y = y1, x = x1)) + geom_point(col = "red") + theme_bw() # 方案1:分段模型指定2个拐点 pk_segment <- pk + geom_smooth(method = "lm", formula = y ~ poly(x, 2, raw=TRUE)*(x<=5) + poly(x,1,raw=TRUE)*(x>5 & x<=10) + poly(x,1,raw=TRUE)*(x>10), se=TRUE, color="blue") + ggtitle("分段模型(指定2个拐点)") # 方案2:惩罚GAM限制多余拐点 pk_gam <- pk + geom_smooth(method = "gam", formula = y ~ s(x, bs = "cr", k=4, gamma=2), se=TRUE, color="green") + ggtitle("惩罚GAM(抑制多余拐点)") ggarrange(pk_segment, pk_gam, nrow=1, ncol=2)
关键说明
- GAM的
k值:基函数数量,立方样条的拐点数量≈k-2,但受惩罚项影响并非绝对; - 若能明确拐点位置,分段模型是最精准的选择;
- 若使用GAM,优先调整
gamma增强惩罚,再配合小k值,比单纯调k更有效。
内容的提问来源于stack exchange,提问作者tnt
相关产品推荐
相关产品推荐

