web-dev-qa-db-fra.com

qqnorm et qqline dans ggplot2

Disons avoir un modèle linéaire LM pour lequel je veux un tracé qq des résidus. Normalement, j'utiliserais les graphiques de base R:

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

Je peux comprendre comment obtenir la partie qqnorm de l'intrigue, mais je n'arrive pas à gérer la qqline:

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

Je soupçonne que je manque quelque chose d'assez fondamental, mais il semble qu'il devrait y avoir un moyen facile de faire cela.

EDIT: Merci beaucoup pour la solution ci-dessous. J'ai modifié le code (très légèrement) pour extraire les informations du modèle linéaire afin que le tracé fonctionne comme le tracé de commodité dans le package graphique de base 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)
}
51
Peter

Le code suivant vous donnera l'intrigue que vous voulez. Le paquetage ggplot ne semble pas contenir de code pour calculer les paramètres de la ligne de commande qqline, donc je ne sais pas s'il est possible de réaliser un tel tracé dans une ligne (compréhensible).

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)

}
50
Aaron

Vous pouvez également ajouter des intervalles de confiance/bandes de confiance avec cette fonction (Parties du code copiées à partir de 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
}

Exemple:

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

enter image description here

21
Rentrop

Depuis la version 2.0, ggplot2 a une interface d'extension bien documentée. ainsi nous pouvons maintenant écrire facilement une nouvelle statistique pour qqline (ce que j'ai fait pour la première fois, donc les améliorations sont bienvenue ):

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, ...))
}

Ceci généralise également la distribution (exactement comme le fait stat_qq) et peut être utilisé comme suit:

> 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))

(Malheureusement, étant donné que qqline est sur une couche séparée, je ne pouvais pas trouver un moyen de "réutiliser" les paramètres de distribution, mais cela ne devrait constituer qu'un problème mineur.)

11
phg

Le diagnostic standard Q-Q pour les modèles linéaires des quantiles des normalisés résiduels par rapport aux quantiles théoriques de N (0,1). @ La fonction ggQQ de Peter trace les résidus. L'extrait ci-dessous corrige cela et ajoute quelques modifications esthétiques pour que l'intrigue ressemble davantage à ce que l'on obtiendrait de 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)
}

Exemple d'utilisation:

# 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))
11
jlhoward

Pourquoi pas le suivant?

Étant donné un vecteur, disons, 

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)

Mais il semble étrange que nous devions utiliser une fonction graphique traditionnelle pour soutenir ggplot2.

Ne pouvons-nous pas obtenir le même effet d’une manière ou d’une autre en commençant par le vecteur pour lequel nous voulons le tracé de quantile, puis en appliquant les fonctions "stat" et "geom" appropriées dans ggplot2?

Hadley Wickham surveille-t-il ces publications? Peut-être qu'il peut nous montrer un meilleur moyen.

9
Jacob Wegelin

Avec la dernière version de ggplot2 (> = 3.0), la nouvelle fonction stat_qq_line est implémentée ( https://github.com/tidyverse/ggplot2/blob/master/NEWS.md ) et une ligne qq pour les résidus de modèle peut être ajouté avec:

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

rstandard(model) est nécessaire pour obtenir le résidu normalisé. (crédit @jlhoward et @qwr)

Si vous obtenez une "erreur dans stat_qq_line (): impossible de trouver la fonction" stat_qq_line "', votre version de ggplot2 est trop ancienne et vous pouvez la réparer en mettant à jour votre paquet ggplot2: install.packages("ggplot2").

6
LmW.

Vous pourriez voler une page aux anciens qui ont fait cela avec du papier à probabilité normale. Un regard attentif sur un graphique ggplot () + stat_qq () suggère qu’une ligne de référence peut être ajoutée avec geom_abline (), comme ceci

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

ggplot(df, aes(sample=y)) +
  stat_qq() +
  geom_abline(intercept=mean(df$y), slope = sd(df$y))
4
Mike Anderson

ggplot2 v.3.0.0 a maintenant une statistique qqline. De la page d'aide:

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

! ggplot2 v3.0.0 Exemple de statistiques équivalentes à qqnorm plus abline ] 1

0
Richard Careaga