You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 02:40:40