【问题标题】:ggplot2: How to plot an orthogonal regression line?ggplot2:如何绘制正交回归线?
【发布时间】:2015-01-15 17:40:15
【问题描述】:

我已经在两个不同的视觉感知测试中测试了大量参与者样本——现在,我想看看这两个测试的表现在多大程度上相关。

为了可视化相关性,我使用 ggplot() 在 R 中绘制了一个散点图,并拟合了一条回归线(使用 stat_smooth())。但是,由于我的 xy 变量都是性能度量,因此在拟合回归线时我需要将它们都考虑在内 - 因此,我不能使用简单的线性回归(使用 stat_smooth(method="lm")),而是需要拟合正交回归(或总最小二乘)。我该怎么做呢?

我知道我可以在stat_smooth() 中指定formula,但我不知道要使用什么公式。据我了解,预设方法(lm, glm, gam, loess, rlm)均不适用。

【问题讨论】:

  • 您可以将模型中的slopeintercept 传递给geom_abline,或者您可以使用here 显示的方法来创建自己的方法

标签: r ggplot2 regression


【解决方案1】:

事实证明,您可以从 (x,y) 上的主成分分析中提取斜率和截距,如 here 所示。这稍微简单一点,在基础 R 中运行,并给出与在 MethComp 中使用 Deming(...) 相同的结果。

# same `x and `y` as @user20650's answer
df  <- data.frame(y, x)
pca <- prcomp(~x+y, df)
slp <- with(pca, rotation[2,1] / rotation[1,1])
int <- with(pca, center[2] - slp*center[1])

ggplot(df, aes(x,y)) + 
  geom_point() + 
  stat_smooth(method=lm, color="green", se=FALSE) +
  geom_abline(slope=slp, intercept=int, color="blue")

【讨论】:

  • 很好的方法,同时避免额外的包。另一个问题,也许更多是美学问题,是如何将geom_abline() 的长度限制为数据,例如stat_smooth()?目前geom_abline() 一直延伸到整个绘图,无论数据点是否一直延伸到绘图。
  • 一种方法是使用geom_segment。您知道数据中 x 范围的最小值和最大值,因此使用斜率和截距计算这些限制处的 y 值,然后使用geom_segment 绘制线。或者你可以在下面的函数f 中用漂亮的prcomp 方法替换Deming 函数。
【解决方案2】:

注意:不熟悉这种方法

我认为您应该能够将slopeintercept 传递给geom_abline 以生成拟合线。或者,您可以定义自己的方法来传递给stat_smooth(如链接smooth.Pspline wrapper for stat_smooth (in ggplot2) 所示)。我使用了MethComp 包中的Deming 函数,如链接How to calculate Total least squares in R? (Orthogonal regression) 所建议的那样。

library(MethComp)
library(ggplot2)

# Sample data and model (from ?Deming example) 
set.seed(1)
M <- runif(100,0,5)
# Measurements:
x <-         M + rnorm(100)
y <- 2 + 3 * M + rnorm(100,sd=2)

# Deming regression
mod <- Deming(x,y)

# Define functions to pass to stat_smooth - see mnel's answer at link for details
# Defined the Deming model output as class Deming to define the predict method
# I only used the intercept and slope for predictions - is this correct?
f <- function(formula,data,SDR=2,...){
        M <- model.frame(formula, data)
        d <- Deming(x =M[,2],y =M[,1], sdr=SDR)[1:2]
        class(d) <- "Deming"
        d  
        }

# an s3 method for predictdf (called within stat_smooth)
predictdf.Deming <- function(model, xseq, se, level) {
                         pred <- model %*% t(cbind(1, xseq) )
                         data.frame(x = xseq, y = c(pred))
                         }

ggplot(data.frame(x,y), aes(x, y)) + geom_point() + 
          stat_smooth(method = f, se= FALSE, colour='red', formula=y~x, SDR=1) +  
          geom_abline(intercept=mod[1], slope=mod[2], colour='blue') +
          stat_smooth(method = "lm", se= FALSE, colour='green', formula = y~x)

因此将截距和斜率传递给geom_abline 会产生相同的拟合线(如预期的那样)。所以如果这是正确的方法,那么 imo 更容易使用它。

【讨论】:

    【解决方案3】:

    MethComp 包似乎不再维护(已从 CRAN 中删除)。 Russel88/COEF 允许使用 stat_/geom_summarymethod="tls" 添加正交回归线。

    基于此和wikipedia:Deming_regression,我创建了以下函数,允许使用除 1 以外的噪声比:

    
    deming.fit <- function(x, y, noise_ratio = sd(y)/sd(x)) {
      if(missing(noise_ratio) || is.null(noise_ratio)) noise_ratio <- eval(formals(sys.function(0))$noise_ratio) # this is just a complicated way to write `sd(y)/sd(x)`
      delta <-  noise_ratio^2
      x_name <- deparse(substitute(x))
    
      s_yy <- var(y)
      s_xx <- var(x)
      s_xy <- cov(x, y)
      beta1 <- (s_yy - delta*s_xx + sqrt((s_yy - delta*s_xx)^2 + 4*delta*s_xy^2)) / (2*s_xy)
      beta0 <- mean(y) - beta1 * mean(x) 
    
      res <- c(beta0 = beta0, beta1 = beta1)
      names(res) <- c("(Intercept)", x_name)
      class(res) <- "Deming"
      res
    }
    
    deming <- function(formula, data, R = 100, noise_ratio = NULL, ...){
      ret <- boot::boot(
        data = model.frame(formula, data), 
        statistic = function(data, ind) {
          data <- data[ind, ]
          args <- rlang::parse_exprs(colnames(data))
          names(args) <- c("y", "x")
          rlang::eval_tidy(rlang::expr(deming.fit(!!!args, noise_ratio = noise_ratio)), data, env = rlang::current_env())
        },
        R=R
      )
      class(ret) <- c("Deming", class(ret))
      ret  
    }
    
    predictdf.Deming <- function(model, xseq, se, level) {
      pred <- as.vector(tcrossprod(model$t0, cbind(1, xseq)))
      if(se) {
        preds <- tcrossprod(model$t, cbind(1, xseq))
        data.frame(
          x = xseq,
          y = pred,
          ymin = apply(preds, 2, function(x) quantile(x, probs = (1-level)/2)),
          ymax = apply(preds, 2, function(x) quantile(x, probs = 1-((1-level)/2)))
        )
      } else {
        return(data.frame(x = xseq, y = pred))
      }
    }
    
    # unrelated hlper function to create a nicer plot:
    fix_plot_limits <- function(p) p + coord_cartesian(xlim=ggplot_build(p)$layout$panel_params[[1]]$x.range, ylim=ggplot_build(p)$layout$panel_params[[1]]$y.range)
    
    

    演示:

    library(ggplot2)
    
    #devtools::install_github("Russel88/COEF")
    library(COEF)
    
    fix_plot_limits(
        ggplot(data.frame(x = (1:5) + rnorm(100), y = (1:5) + rnorm(100)*2), mapping = aes(x=x, y=y)) +
          geom_point()
        ) +
      geom_smooth(method=deming, aes(color="deming"), method.args = list(noise_ratio=2)) +
      geom_smooth(method=lm, aes(color="lm")) +
      geom_smooth(method = COEF::tls, aes(color="tls"))
    

    reprex package (v0.3.0) 于 2019 年 12 月 4 日创建

    【讨论】:

    • 您知道为什么在noise_ratio = 1 处使用您的函数计算的置信区间与COEF::tls 方法产生的置信区间略有不同吗?附:美丽的答案,因为它是唯一包含置信区间的答案。谢谢!
    • 它们是用 bootstrap 估计的,因此是随机的,您可以尝试增加 bootstrap 样本的数量 (R) 并检查这是否有助于使它们更相似!让我知道是否存在系统差异!
    • 哦,你是对的!运行之间的间隔略有变化。谢谢。
    • 作者不鼓励像这样扩展 geom_smooth (github.com/tidyverse/ggplot2/issues/3132)(但我不确定替代方案是什么)。要解决 predictdf y ggplot2 的非导出问题,可以在您的包的 .on.load 中使用 registerS3method("predictdf", "YourClass", yourpackage:::predictdf.YourClass, envir = environment(ggplot2:::predictdf))
    猜你喜欢
    • 2014-07-06
    • 1970-01-01
    • 2013-06-05
    • 2017-07-02
    • 1970-01-01
    • 2016-05-12
    • 2019-10-23
    • 1970-01-01
    • 2018-05-27
    相关资源
    最近更新 更多