web-dev-qa-db-fra.com

Stockage d'objets ggplot dans une liste à partir de la boucle dans R

Mon problème est similaire à celui-ci ; lorsque je génère des objets de tracé (dans ce cas des histogrammes) en boucle, il semble que tous soient écrasés par le tracé le plus récent.

Pour déboguer, dans la boucle, j'imprime l'index et le tracé généré, qui apparaissent tous les deux correctement. Mais quand je regarde les tracés stockés dans la liste, ils sont tous identiques sauf pour l'étiquette.

(J'utilise Multiplot pour créer une image composite, mais vous obtenez le même résultat si vous print (myplots[[1]]) à print(myplots[[4]]) une à la fois.)

Parce que j'ai déjà une trame de données attachée (contrairement à l'affiche du problème similaire), je ne sais pas comment résoudre le problème.

(btw, les classes de colonnes sont des facteurs dans le jeu de données d'origine que je rapproche ici, mais le même problème se produit si elles sont entières)

Voici un exemple reproductible:

library(ggplot2)
source("http://peterhaschke.com/Code/multiplot.R") #load multiplot function

#make sample data
col1 <- c(2, 4, 1, 2, 5, 1, 2, 0, 1, 4, 4, 3, 5, 2, 4, 3, 3, 6, 5, 3, 6, 4, 3, 4, 4, 3, 4, 
          2, 4, 3, 3, 5, 3, 5, 5, 0, 0, 3, 3, 6, 5, 4, 4, 1, 3, 3, 2, 0, 5, 3, 6, 6, 2, 3, 
          3, 1, 5, 3, 4, 6)
col2 <- c(2, 4, 4, 0, 4, 4, 4, 4, 1, 4, 4, 3, 5, 0, 4, 5, 3, 6, 5, 3, 6, 4, 4, 2, 4, 4, 4, 
          1, 1, 2, 2, 3, 3, 5, 0, 3, 4, 2, 4, 5, 5, 4, 4, 2, 3, 5, 2, 6, 5, 2, 4, 6, 3, 3, 
          3, 1, 4, 3, 5, 4)
col3 <- c(2, 5, 4, 1, 4, 2, 3, 0, 1, 3, 4, 2, 5, 1, 4, 3, 4, 6, 3, 4, 6, 4, 1, 3, 5, 4, 3, 
          2, 1, 3, 2, 2, 2, 4, 0, 1, 4, 4, 3, 5, 3, 2, 5, 2, 3, 3, 4, 2, 4, 2, 4, 5, 1, 3, 
          3, 3, 4, 3, 5, 4)
col4 <- c(2, 5, 2, 1, 4, 1, 3, 4, 1, 3, 5, 2, 4, 3, 5, 3, 4, 6, 3, 4, 6, 4, 3, 2, 5, 5, 4,
          2, 3, 2, 2, 3, 3, 4, 0, 1, 4, 3, 3, 5, 4, 4, 4, 3, 3, 5, 4, 3, 5, 3, 6, 6, 4, 2, 
          3, 3, 4, 4, 4, 6)
data2 <- data.frame(col1,col2,col3,col4)
data2[,1:4] <- lapply(data2[,1:4], as.factor)
colnames(data2)<- c("A","B","C", "D")

#generate plots
myplots <- list()  # new empty list
for (i in 1:4) {
  p1 <- ggplot(data=data.frame(data2),aes(x=data2[ ,i]))+ 
    geom_histogram(fill="lightgreen") +
    xlab(colnames(data2)[ i])
  print(i)
  print(p1)
  myplots[[i]] <- p1  # add each plot into plot list
}
multiplot(plotlist = myplots, cols = 4)

Quand je regarde un résumé d'un objet de tracé dans la liste de tracé, voici ce que je vois

> summary(myplots[[1]])
data: A, B, C, D [60x4]
mapping:  x = data2[, i]
faceting: facet_null() 
-----------------------------------
geom_histogram: fill = lightgreen 
stat_bin:  
position_stack: (width = NULL, height = NULL)

Je pense que mapping: x = data2[, i] est le problème, mais je suis perplexe! Je ne peux pas publier d'images, vous devrez donc exécuter mon exemple et regarder les graphiques si mon explication du problème prête à confusion.

Merci!

19
LizPS

En plus de l'autre excellente réponse, voici une solution qui utilise une évaluation d'apparence "normale" plutôt que eval. Comme les boucles for n'ont pas de portée de variable distincte (c'est-à-dire qu'elles sont exécutées dans l'environnement actuel), nous devons utiliser local pour encapsuler le bloc for; en outre, nous devons faire de i une variable locale - ce que nous pouvons faire en la réaffectant à son propre nom1:

myplots <- vector('list', ncol(data2))

for (i in seq_along(data2)) {
    message(i)
    myplots[[i]] <- local({
        i <- i
        p1 <- ggplot(data2, aes(x = data2[[i]])) +
            geom_histogram(fill = "lightgreen") +
            xlab(colnames(data2)[i])
        print(p1)
    })
}

Cependant, une manière tout à fait plus propre consiste à renoncer entièrement à la boucle for et à utiliser les fonctions de liste pour construire le résultat. Cela fonctionne de plusieurs manières possibles. Ce qui suit est le plus simple à mon avis:

plot_data_column = function (data, column) {
    ggplot(data2, aes_string(x = column)) +
        geom_histogram(fill = "lightgreen") +
        xlab(column)
}

myplots <- lapply(colnames(data2), plot_data_column, data = data2)

Cela présente de nombreux avantages: la plupart du temps, c'est plus simple et cela n'encombrera pas l'environnement (avec la variable de boucle i).


1 Cela peut sembler déroutant: pourquoi i <- i a un effet? - Parce qu'en effectuant l'affectation, nous créons une nouvelle variable locale avec le même nom que la variable dans la portée externe. Nous aurions pu également utiliser un nom différent, par exemple local_i <- i.

39
Konrad Rudolph

En raison de toutes les citations d'expressions qui sont transmises, le i qui est évalué à la fin de la boucle est ce que i se trouve être à ce moment, ce qui est sa valeur finale. Vous pouvez contourner ce problème en eval(substitute(la valeur correcte à chaque itération.

myplots <- list()  # new empty list
for (i in 1:4) {
    p1 <- eval(substitute(
        ggplot(data=data.frame(data2),aes(x=data2[ ,i]))+ 
          geom_histogram(fill="lightgreen") +
          xlab(colnames(data2)[ i])
    ,list(i = i)))
    print(i)
    print(p1)
    myplots[[i]] <- p1  # add each plot into plot list
}
multiplot(plotlist = myplots, cols = 4)
7
jenesaisquoi