2014-10-07 14 views
7

Zusammenfassung: Wenn ich eine "for" -Schleife zum Hinzufügen von Layern zu einem Violin-Plot (in ggplot) verwende, wird nur die Ebene hinzugefügt, die durch die letzte Loop-Iteration erstellt wurde . In explizitem Code, der den von der Schleife erzeugten Code nachahmt, werden jedoch alle Ebenen hinzugefügt."for" -Schleife fügt nur die finale ggplot-Ebene hinzu

Details: Ich versuche Violinendiagramme mit überlappenden Layern zu erstellen, um zu zeigen, inwieweit sich die geschätzten Verteilungen für mehrere Umfragefragen stratifiziert nach Ort oder nicht überschneiden. Ich möchte in der Lage sein, eine beliebige Anzahl von Orten einzuschließen, so dass ich für jeden Ort eine Spalte nach Datenrahmen habe und versuche, eine "for" -Schleife zu verwenden, um eine ggplot-Ebene pro Ort zu erzeugen. Die Schleife fügt jedoch nur die Ebene aus der letzten Iteration der Schleife hinzu.

Dieser Code zeigt das Problem, und einige Ansätze vorgeschlagen, die fehlgeschlagen:

library(ggplot2) 

# Create a dataframe with 500 random normal values for responses to 3 survey questions from two cities 
topic <- c("Poverty %","Mean Age","% Smokers") 
place <- c("Chicago","Miami") 
n <- 500 
mean <- c(35, 40,58, 50, 25,20) 
var <- c(7, 1.5, 3, .25, .5, 1) 
df <- data.frame(topic=rep(topic,rep(n,length(topic))) 
       ,c(rnorm(n,mean[1],var[1]),rnorm(n,mean[3],var[3]),rnorm(n,mean[5],var[5])) 
       ,c(rnorm(n,mean[2],var[2]),rnorm(n,mean[4],var[4]),rnorm(n,mean[6],var[6])) 
       ) 
names(df)[2:dim(df)[2]] <- place # Name those last two columns with the corresponding place name. 
head(df) 

# This "for" loop seems to only execute the final loop (i.e., where p=3) 
g <- ggplot(df, aes(factor(topic), df[,2])) 
for (p in 2:dim(df)[2]) { 
    g <- g + geom_violin(aes(y = df[,p], colour = place[p-1]), alpha = 0.3) 
} 
g 

# But mimicing what the for loop does in explicit code works fine, resulting in both "place"s being displayed in the graph. 
g <- ggplot(df, aes(factor(topic), df[,2])) 
g <- g + geom_violin(aes(y = df[,2], colour = place[2-1]), alpha = 0.3) 
g <- g + geom_violin(aes(y = df[,3], colour = place[3-1]), alpha = 0.3) 
g 

## per http://stackoverflow.com/questions/18444620/set-layers-in-ggplot2-via-loop , I tried 
g <- ggplot(df, aes(factor(topic), df[,2])) 
for (p in 2:dim(df)[2]) { 
    df1 <- df[,c(1,p)] 
    g <- g + geom_violin(aes(y = df1[,2], colour = place[p-1]), alpha = 0.3) 
} 
g 
# but got the same undesired result 

# per http://stackoverflow.com/questions/15987367/how-to-add-layers-in-ggplot-using-a-for-loop , I tried 
g <- ggplot(df, aes(factor(topic), df[,2])) 
for (p in names(df)[-1]) { 
    cat(p,"\n") 
    g <- g + geom_violin(aes_string(y = p, colour = p), alpha = 0.3) # produced this error: Error in unit(tic_pos.c, "mm") : 'x' and 'units' must have length > 0 
    # g <- g + geom_violin(aes_string(y = p   ), alpha = 0.3) # produced this error: Error: stat_ydensity requires the following missing aesthetics: y 
} 
g 
# but that failed to produce any graphic, per the errors noted in the "for" loop above 
+1

, warum Sie nicht 'melt' den Datenrahmen zu langes Format? – baptiste

Antwort

8

Der Grund dies geschieht ist wegen ggplot 's "faule Auswertung". Dies ist ein häufiges Problem, wenn ggplot auf diese Weise verwendet wird (die Layer separat in einer Schleife erstellen, anstatt ggplot für Sie zu haben, wie in @ hrbrmstrs Lösung).

ggplot speichert die Argumente aes(...) als Ausdrücke, und nur wertet sie aus, wenn die Handlung wiedergegeben wird. Also, in Loops, so etwas wie

aes(y = df[,p], colour = place[p-1]) 

wird wie gespeichert und ausgewertet, wenn Sie den Plot machen, nachdem die Schleife abgeschlossen ist. An diesem Punkt ist p = 3, so dass alle Plots mit p = 3 gerendert werden.

So ist der „richtige“ Weg, dies zu tun, ist melt(...) im reshape2 Paket zu verwenden, so dass Ihre Daten von weiten zu lang-Format konvertieren, und lassen Sie ggplot für Sie die Ebene verwalten. Ich setze "richtig" in Anführungszeichen, weil es in diesem speziellen Fall eine Subtilität gibt. Beim Berechnen der Verteilungen für die Violinen unter Verwendung des geschmolzenen Datenrahmens verwendet ggplot die Gesamtsumme (sowohl für Chicago als auch für Miami) als die Skala. Wenn Sie Geigen basierend auf Frequenz individuell skaliert wollen, müssen Sie Schleifen verwenden (leider).

Der Weg um die Lazy Evaluation Problem ist es, einen Verweis auf den Loop-Index in der data=... Definition. Dies ist nicht als Ausdruck gespeichert, die tatsächlichen Daten werden in der Plot-Definition gespeichert.So könnten Sie dies tun:

g <- ggplot(df,aes(x=topic)) 
for (p in 2:length(df)) { 
    gg.data <- data.frame(topic=df$topic,value=df[,p],city=names(df)[p]) 
    g <- g + geom_violin(data=gg.data,aes(y=value, color=city)) 
} 
g 

die das gleiche Ergebnis wie bei Ihnen gibt. Beachten Sie, dass der Index nicht in aes(...) angezeigt wird.


Update: Ein Hinweis zu scale="width" (in einem Kommentar erwähnt). Dies hat zur Folge, dass alle Violinen dieselbe Breite haben (siehe unten), was nicht die gleiche Skalierung wie im ursprünglichen Code von OP ist. IMO das ist keine gute Möglichkeit, die Daten zu visualisieren, da es darauf hindeutet, dass es viel mehr Daten in der Chicago-Gruppe gibt.

ggplot(gg) +geom_violin(aes(x=topic,y=value,color=variable), 
         alpha=0.3,position="identity",scale="width") 

+0

Danke. Ich schätze die Erklärung, wie diese Fremdartigkeit mit Schleifen und ggplot passiert. Jetzt verstehe ich. Ich dachte, es könnte so etwas sein - ich habe versucht, einen Befehl zu finden, der den Plot als letzten Schritt jeder Schleife zeichnen würde (wie nur "g"), aber nichts, was ich versuchte, funktionierte. Ihr Schleifencode ist was ich brauchte. – user3799203

2

vermeiden Sie einfach die for Schleife dann. Wie wäre es lapply statt:

g <- g + lapply(2:ncol(df), function(p) { 
    geom_violin(aes(y = df[,p], colour = place[p-1]), alpha = 0.3) 
}) 

EDIT: Das ist wirklich nicht funktioniert. Ich hatte p <- 2 in meinem Arbeitsbereich, bevor es ausgeführt wurde, und dann produzierte es ein Diagramm mit nur den Chicago-Daten. Auf jeden Fall sollte das Prinzip noch arbeiten (obwohl melt wahrscheinlich eine bessere Option ist):

g <- ggplot(df, aes(x=factor(topic))) 
g + lapply(place, function(p) { 
    geom_violin(aes_string(y = p), alpha = 0.3, color = which(p==place)) 
}) 
+0

Haben Sie das versucht? Wenn ich OPs für die Schleife mit diesem ersetze, bekomme ich: 'Fehler in' [.data.frame' (df,, p): Objekt 'p' nicht gefunden '. Wenn ich OPs zuerst für eine Schleife ausführen lasse (was eine Variable "p" erzeugt), dann führe das "lapply (...)" aus. Ich bekomme das gleiche Ergebnis wie OP. – jlhoward

+0

Das hat nicht für mich funktioniert. Ich habe den gleichen Graphen wie mit meinem schlechten Loop-Code. – user3799203

2

Sie tun können, es w/oa Schleife:

df.2 <- melt(df) 
gg <- ggplot(df.2, aes(x=topic, y=value)) 
gg <- gg + geom_violin(position="identity", aes(color=variable), alpha=0.3) 
gg 

enter image description here

+0

Dies erzeugt nicht das gleiche Diagramm wie der "erfolgreiche" Versuch von OP, da die Violinen beim Erstellen von zwei Ebenen separat vs. beim Gruppieren nach "Variable" unterschiedlich skaliert werden. Auch sollte erwähnen, dass OP "reshape2" dafür laden müsste. – jlhoward

+0

Sehr elegant. Wenn ich bei dieser Methode die Option 'scale = "width"' aes verwende, spielt die von jlhoward erwähnte Gruppierung statt der individuellen Skalierung keine Rolle. – user3799203

Verwandte Themen