qqnorm и qqline в ggplot2

Скажем, есть линейная модель LM, что мне нужен график остатков qq. Обычно я бы использовал базовую графику R:

qqnorm(residuals(LM), ylab="Residuals")
qqline(residuals(LM))

Я могу понять, как получить часть графика qqnorm, но я не могу управлять qqline:

ggplot(LM, aes(sample=.resid)) +
    stat_qq()

Я подозреваю, что мне не хватает чего-то довольно простого, но, похоже, должен быть простой способ сделать это.

РЕДАКТИРОВАТЬ: Большое спасибо за решение, приведенное ниже. Я изменил код (очень немного), чтобы извлечь информацию из линейной модели, чтобы график работал так же, как график удобства в базовом графическом пакете R.

ggQQ <- function(LM) # argument: a linear model
{
    y <- quantile(LM$resid[!is.na(LM$resid)], c(0.25, 0.75))
    x <- qnorm(c(0.25, 0.75))
    slope <- diff(y)/diff(x)
    int <- y[1L] - slope * x[1L]
    p <- ggplot(LM, aes(sample=.resid)) +
        stat_qq(alpha = 0.5) +
        geom_abline(slope = slope, intercept = int, color="blue")

    return(p)
}

person Peter    schedule 05.12.2010    source источник
comment
Примечание. Для остатков ggplot нужны стандартизованные остатки. См. Ответ jlhoward stackoverflow.com/a/19990107/3163618   -  person qwr    schedule 25.10.2018


Ответы (8)


Следующий код даст вам нужный сюжет. Пакет ggplot, похоже, не содержит кода для расчета параметров qqline, поэтому я не знаю, возможно ли получить такой график в (понятном) однострочном.

qqplot.data <- function (vec) # argument: vector of numbers
{
  # following four lines from base R's qqline()
  y <- quantile(vec[!is.na(vec)], c(0.25, 0.75))
  x <- qnorm(c(0.25, 0.75))
  slope <- diff(y)/diff(x)
  int <- y[1L] - slope * x[1L]

  d <- data.frame(resids = vec)

  ggplot(d, aes(sample = resids)) + stat_qq() + geom_abline(slope = slope, intercept = int)

}
person Aaron    schedule 05.12.2010
comment
Прекрасно работает! Я позволил себе немного изменить код, чтобы извлечь вектор прямо из линейной модели. Конечно, ваше решение будет работать с данными, не имеющими формы линейной модели, но я подумал, что кому-то еще может понадобиться удобная функция для построения qqplot из LM. - person Peter; 05.12.2010

Вы также можете добавить доверительные интервалы / доверительные интервалы с помощью этой функции (части кода скопированы из car:::qqPlot)

gg_qq <- function(x, distribution = "norm", ..., line.estimate = NULL, conf = 0.95,
                  labels = names(x)){
  q.function <- eval(parse(text = paste0("q", distribution)))
  d.function <- eval(parse(text = paste0("d", distribution)))
  x <- na.omit(x)
  ord <- order(x)
  n <- length(x)
  P <- ppoints(length(x))
  df <- data.frame(ord.x = x[ord], z = q.function(P, ...))

  if(is.null(line.estimate)){
    Q.x <- quantile(df$ord.x, c(0.25, 0.75))
    Q.z <- q.function(c(0.25, 0.75), ...)
    b <- diff(Q.x)/diff(Q.z)
    coef <- c(Q.x[1] - b * Q.z[1], b)
  } else {
    coef <- coef(line.estimate(ord.x ~ z))
  }

  zz <- qnorm(1 - (1 - conf)/2)
  SE <- (coef[2]/d.function(df$z)) * sqrt(P * (1 - P)/n)
  fit.value <- coef[1] + coef[2] * df$z
  df$upper <- fit.value + zz * SE
  df$lower <- fit.value - zz * SE

  if(!is.null(labels)){ 
    df$label <- ifelse(df$ord.x > df$upper | df$ord.x < df$lower, labels[ord],"")
    }

  p <- ggplot(df, aes(x=z, y=ord.x)) +
    geom_point() + 
    geom_abline(intercept = coef[1], slope = coef[2]) +
    geom_ribbon(aes(ymin = lower, ymax = upper), alpha=0.2) 
  if(!is.null(labels)) p <- p + geom_text( aes(label = label))
  print(p)
  coef
}

Пример:

Animals2 <- data(Animals2, package = "robustbase")
mod.lm <- lm(log(Animals2$brain) ~ log(Animals2$body))
x <- rstudent(mod.lm)
gg_qq(x)

введите описание изображения здесь

person Rentrop    schedule 28.11.2014
comment
Это очень полезно. Вы не задумывались о размещении своего скрипта на Github? Было бы неплохо правильно разместить свой код, - person Hip Hop Physician; 26.09.2015
comment
gist.github.com/rentrop/d39a8406ad8af2a1066c нравится? Даже несмотря на то, что я не знаю, почему вы не можете разместить ТАК ... - person Rentrop; 26.09.2015
comment
Большое спасибо! Полагаю, я слегка оговорился, что я имел в виду, было бы неплохо разместить это на Github, чтобы я мог добавить его как часть сценария R (вместо того, чтобы найти способ склеить сообщение о переполнении стека). - person Hip Hop Physician; 28.09.2015

Начиная с версии 3.0, stat_qq_line эквивалент приведенного ниже попал в официальный ggplot2 код .


Старая версия:

Начиная с версии 2.0, ggplot2 имеет хорошо документированный интерфейс для расширения; так что теперь мы можем легко написать новую статистику для qqline отдельно (что я сделал впервые, поэтому улучшения следующие: добро пожаловать):

qq.line <- function(data, qf, na.rm) {
    # from stackoverflow.com/a/4357932/1346276
    q.sample <- quantile(data, c(0.25, 0.75), na.rm = na.rm)
    q.theory <- qf(c(0.25, 0.75))
    slope <- diff(q.sample) / diff(q.theory)
    intercept <- q.sample[1] - slope * q.theory[1]

    list(slope = slope, intercept = intercept)
}

StatQQLine <- ggproto("StatQQLine", Stat,
    # http://docs.ggplot2.org/current/vignettes/extending-ggplot2.html
    # https://github.com/hadley/ggplot2/blob/master/R/stat-qq.r
    
    required_aes = c('sample'),
    
    compute_group = function(data, scales,
                             distribution = stats::qnorm,
                             dparams = list(),
                             na.rm = FALSE) {
        qf <- function(p) do.call(distribution, c(list(p = p), dparams))
        
        n <- length(data$sample)
        theoretical <- qf(stats::ppoints(n))
        qq <- qq.line(data$sample, qf = qf, na.rm = na.rm)
        line <- qq$intercept + theoretical * qq$slope

        data.frame(x = theoretical, y = line)
    } 
)

stat_qqline <- function(mapping = NULL, data = NULL, geom = "line",
                        position = "identity", ...,
                        distribution = stats::qnorm,
                        dparams = list(),
                        na.rm = FALSE,
                        show.legend = NA, 
                        inherit.aes = TRUE) {
    layer(stat = StatQQLine, data = data, mapping = mapping, geom = geom,
          position = position, show.legend = show.legend, inherit.aes = inherit.aes,
          params = list(distribution = distribution,
                        dparams = dparams,
                        na.rm = na.rm, ...))
}

Это также обобщает распределение (точно так же, как stat_qq) и может использоваться следующим образом:

> test.data <- data.frame(sample=rnorm(100, 10, 2)) # normal distribution
> test.data.2 <- data.frame(sample=rt(100, df=2))   # t distribution
> ggplot(test.data, aes(sample=sample)) + stat_qq() + stat_qqline()
> ggplot(test.data.2, aes(sample=sample)) + stat_qq(distribution=qt, dparams=list(df=2)) +
+   stat_qqline(distribution=qt, dparams=list(df=2))

(К сожалению, поскольку qqline находится на отдельном уровне, я не смог найти способ повторно использовать параметры распределения, но это должно быть лишь незначительной проблемой.)

person phipsgabler    schedule 23.02.2016

Стандартная Q-Q-диагностика для линейных моделей строит графики квантилей стандартизованных остатков по сравнению с теоретическими квантилями N (0,1). Функция ggQQ @ Peter отображает остатки. Приведенный ниже фрагмент исправляет это и добавляет несколько косметических изменений, чтобы сделать сюжет более похожим на то, что можно было бы получить от plot(lm(...)).

ggQQ = function(lm) {
  # extract standardized residuals from the fit
  d <- data.frame(std.resid = rstandard(lm))
  # calculate 1Q/4Q line
  y <- quantile(d$std.resid[!is.na(d$std.resid)], c(0.25, 0.75))
  x <- qnorm(c(0.25, 0.75))
  slope <- diff(y)/diff(x)
  int <- y[1L] - slope * x[1L]

  p <- ggplot(data=d, aes(sample=std.resid)) +
    stat_qq(shape=1, size=3) +           # open circles
    labs(title="Normal Q-Q",             # plot title
         x="Theoretical Quantiles",      # x-axis label
         y="Standardized Residuals") +   # y-axis label
    geom_abline(slope = slope, intercept = int, linetype="dashed")  # dashed reference line
  return(p)
}

Пример использования:

# sample data (y = x + N(0,1), x in [1,100])
df <- data.frame(cbind(x=c(1:100),y=c(1:100+rnorm(100))))
ggQQ(lm(y~x,data=df))
person jlhoward    schedule 14.11.2013

Почему не следующее?

По некоторому вектору, скажем,

myresiduals <- rnorm(100) ^ 2

ggplot(data=as.data.frame(qqnorm( myresiduals , plot=F)), mapping=aes(x=x, y=y)) + 
    geom_point() + geom_smooth(method="lm", se=FALSE)

Но кажется странным, что нам приходится использовать традиционную графическую функцию для поддержки ggplot2.

Разве мы не можем каким-то образом получить тот же эффект, начав с вектора, для которого нам нужен график квантилей, а затем применив соответствующие функции «stat» и «geom» в ggplot2?

Контролирует ли Хэдли Уикхэм эти сообщения? Может, он покажет нам лучший путь.

person Jacob Wegelin    schedule 08.02.2011
comment
диаграмма рассеяния похожа на диаграмму q-q функции qqnorm (), но линия, добавленная geom_smooth, не такая же, как линия, заданная qqline (). С другой стороны, решения, данные Аароном и @jlhoward, дают графики, аналогичные базовым R. Можете ли вы прокомментировать, если это мои данные, из-за которых они плохо себя ведут. - person ktyagi; 16.04.2014

В последней версии ggplot2 (> = 3.0) реализована новая функция stat_qq_line (https://github.com/tidyverse/ggplot2/blob/master/NEWS.md) и строку qq для остатков модели можно добавить с помощью:

library(ggplot2)
model <- lm(mpg ~ wt, data=mtcars)
ggplot(model, aes(sample = rstandard(model))) + geom_qq() + stat_qq_line()

rstandard(model) необходим для получения стандартизированной невязки. (кредит @jlhoward и @qwr)

Если вы получаете сообщение «Ошибка в stat_qq_line (): не удалось найти функцию« stat_qq_line »», ваша версия ggplot2 слишком старая, и вы можете исправить ее, обновив свой пакет ggplot2: install.packages("ggplot2").

person LmW.    schedule 02.05.2018
comment
Теперь он выпущен в стабильной версии ggplot2 3.0.0. Таким образом, теперь вы можете получить эту функцию из версии CRAN. - person Droplet; 27.07.2018
comment
Ggplot ожидает стандартизированных остатков, как описывает jlhoward. Так что используйте rstandard(model) вместо .resid - person qwr; 12.10.2018

Вы можете украсть страницу у старожилов, которые проделывали все это с обычной вероятностной бумагой. Внимательный взгляд на график ggplot () + stat_qq () предполагает, что опорная линия может быть добавлена ​​с помощью geom_abline (), как это

df <- data.frame( y=rpois(100, 4) )

ggplot(df, aes(sample=y)) +
  stat_qq() +
  geom_abline(intercept=mean(df$y), slope = sd(df$y))
person Mike Anderson    schedule 14.02.2018
comment
Разве график Q-Q с выборкой и теоретическими квантилями не должен иметь контрольную линию y = x? - person qwr; 06.02.2019

ggplot2 v.3.0.0 теперь имеет статистику qqline. На странице справки:

df <- data.frame(y = rt(200, df = 5))
p <- ggplot(df, aes(sample = y))
p + stat_qq() + stat_qq_line()

! ggplot2 v3.0.0 Пример статистики, эквивалентный qqnorm плюс abline] 1

person Richard Careaga    schedule 11.07.2018