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

如何在ggplot中限制loess线置信区间阴影的范围

Fixing Loess Confidence Interval Below Zero in ggplot2

I’ve run into this exact issue before—when working with non-negative variables like height, weight, or abundance, seeing loess confidence bands dip below zero just doesn’t make sense. Here are two solid solutions to fix it:

Option 1: Visually Truncate the Confidence Interval (Quick Fix)

The problem with using scale_y_continuous(limits = c(0, 200)) is that it filters out any data points (and parts of the confidence interval) that fall below 0. Instead, use coord_cartesian() to crop the plot display without removing data:

library(ggplot2)
df <- as.data.frame(rep(1:7, each = 5))
df[,2] <- c(0,1,5,0,6,0,7,2,9,1,1,18,4,2,34,8,18,24,56,12,12,18,24,63,48, 40,70,53,75,98,145,176,59,98,165)
names(df) <- c("x", "y")

ggplot(df, aes(x=x, y=y)) + 
  geom_point() + 
  geom_smooth(method = "loess") + 
  coord_cartesian(ylim = c(0, 200))  # This crops instead of filtering

This keeps the full loess fit (including the parts below 0) but only shows everything from y=0 upwards. The confidence interval shadow will be cleanly cut off at 0 instead of disappearing entirely.

Option 2: Force the Confidence Interval to Stay Above Zero (Model-Level Fix)

If you want the loess fit itself to never generate values below 0, you’ll need to manually fit the model and adjust the confidence bounds before plotting. Here’s how:

  1. Fit the loess model and get predictions with standard errors
  2. Calculate the confidence interval, then clamp the lower bound to 0
  3. Plot using the adjusted data
library(ggplot2)

# Same data as before
df <- as.data.frame(rep(1:7, each = 5))
df[,2] <- c(0,1,5,0,6,0,7,2,9,1,1,18,4,2,34,8,18,24,56,12,12,18,24,63,48, 40,70,53,75,98,145,176,59,98,165)
names(df) <- c("x", "y")

# Fit loess and get predictions with SE
loess_fit <- loess(y ~ x, data = df)
preds <- predict(loess_fit, se = TRUE)

# Create a dataframe for plotting, clamping lower CI to 0
pred_df <- data.frame(
  x = seq(min(df$x), max(df$x), length.out = 100),  # Smooth x sequence for plotting
  y_pred = predict(loess_fit, newdata = data.frame(x = seq(min(df$x), max(df$x), length.out = 100))),
  se = predict(loess_fit, newdata = data.frame(x = seq(min(df$x), max(df$x), length.out = 100)), se = TRUE)$se.fit
) %>%
  mutate(
    y_low = pmax(y_pred - 1.96 * se, 0),  # Ensure lower bound never goes below 0
    y_high = y_pred + 1.96 * se
  )

# Plot with adjusted CI bounds
ggplot(df, aes(x = x, y = y)) +
  geom_point() +
  geom_ribbon(data = pred_df, aes(y = y_pred, ymin = y_low, ymax = y_high), alpha = 0.2) +
  geom_line(data = pred_df, aes(y = y_pred), color = "#00BFC4") +
  scale_y_continuous(limits = c(0, 200))

This approach ensures that neither the fit line nor the confidence interval ever goes below 0, which is more statistically appropriate for non-negative variables.

Which Option Should You Choose?

  • Use Option 1 if you just need a quick visual fix and don’t mind that the underlying model still generates negative values (they’re just hidden).
  • Use Option 2 if you want the fit to strictly adhere to your variable’s non-negative nature, making the plot more accurate to your data’s constraints.

内容的提问来源于stack exchange,提问作者James

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:29:24