2009-09-04 35 views
19

Estoy tratando de graficar datos de tipo celosía con GGPLOT2 y luego superponer una distribución normal sobre los datos de muestra para ilustrar qué tan fuera de lo normal son los datos subyacentes. Me gustaría tener el dist normal encima para tener la misma media y stdev que el panel.usando stat_function y facet_wrap juntos en GGPLOT2 en R

He aquí un ejemplo:

library(ggplot2) 

#make some example data 
dd<-data.frame(matrix(rnorm(144, mean=2, sd=2),72,2),c(rep("A",24),rep("B",24),rep("C",24))) 
colnames(dd) <- c("x_value", "Predicted_value", "State_CD") 

#This works 
pg <- ggplot(dd) + geom_density(aes(x=Predicted_value)) + facet_wrap(~State_CD) 
print(pg) 

que todas las obras grandes y produce un gráfico agradable de tres paneles de los datos. ¿Cómo agrego el dist normal en la parte superior? Parece que yo usaría stat_function, pero esto no funciona:

#this fails 
pg <- ggplot(dd) + geom_density(aes(x=Predicted_value)) + stat_function(fun=dnorm) + facet_wrap(~State_CD) 
print(pg) 

Parece ser que el stat_function no se lleva bien con la función de facet_wrap. ¿Cómo logro que estos dos jueguen bien?

------------ EDITAR ---------

traté de integrar las ideas de dos de las respuestas a continuación y estoy todavía no existe:

utilizando una combinación de las dos respuestas que puedo hackear esto:

library(ggplot) 
library(plyr) 

#make some example data 
dd<-data.frame(matrix(rnorm(108, mean=2, sd=2),36,2),c(rep("A",24),rep("B",24),rep("C",24))) 
colnames(dd) <- c("x_value", "Predicted_value", "State_CD") 

DevMeanSt <- ddply(dd, c("State_CD"), function(df)mean(df$Predicted_value)) 
colnames(DevMeanSt) <- c("State_CD", "mean") 
DevSdSt <- ddply(dd, c("State_CD"), function(df)sd(df$Predicted_value)) 
colnames(DevSdSt) <- c("State_CD", "sd") 
DevStatsSt <- merge(DevMeanSt, DevSdSt) 

pg <- ggplot(dd, aes(x=Predicted_value)) 
pg <- pg + geom_density() 
pg <- pg + stat_function(fun=dnorm, colour='red', args=list(mean=DevStatsSt$mean, sd=DevStatsSt$sd)) 
pg <- pg + facet_wrap(~State_CD) 
print(pg) 

el cual está muy cerca ... excepto algo está mal con el trazado normal de dist:

enter image description here

¿Qué estoy haciendo mal aquí?

+6

En el futuro, ¿podría utilizar nombres de variables con cualquiera de los casos mixtos _o_ pone de relieve, pero no ambos. ¡Me está matando! – hadley

+0

ok ok, ese es un buen punto. :) –

+0

Moví mi "respuesta" al área de preguntas. Debería haberlo puesto allí para comenzar. Mis disculpas a quienes hicieron comentarios porque no se transfirieron. Seré más considerado acerca de cómo hago eso en el futuro. –

Respuesta

34

stat_function está diseñado para superponer la misma función en cada panel. (No hay una manera obvia de hacer coincidir los parámetros de la función con los diferentes paneles).

Como Ian sugiere, la mejor manera es generar las curvas normales de ti mismo, y trazarlos como separada conjunto de datos (aquí es donde usted iba mal antes - la fusión simplemente no tiene sentido para este ejemplo y, si si miras con cuidado, verás que es por eso que obtienes el extraño patrón de dientes de sierra).

Así es como me gustaría ir sobre la solución del problema:

dd <- data.frame(
    predicted = rnorm(72, mean = 2, sd = 2), 
    state = rep(c("A", "B", "C"), each = 24) 
) 

grid <- with(dd, seq(min(predicted), max(predicted), length = 100)) 
normaldens <- ddply(dd, "state", function(df) { 
    data.frame( 
    predicted = grid, 
    density = dnorm(grid, mean(df$predicted), sd(df$predicted)) 
) 
}) 

ggplot(dd, aes(predicted)) + 
    geom_density() + 
    geom_line(aes(y = density), data = normaldens, colour = "red") + 
    facet_wrap(~ state) 

enter image description here

+0

Eso tiene mucho sentido después de que lo expliques. No fue intuitivo para mí que stat_function se diseñó para curvas simples. Solo asumí que lo estaba haciendo mal. Gracias por tomarse el tiempo para dar un ejemplo, es increíblemente útil. –

+0

¿Sigue siendo el caso que 'stat_function' no puede proporcionar una curva diferente para cada panel? Parece que uno simplemente querría pasar una lista de funciones con nombre, y unir esos nombres a la variable categórica dada a 'facet_wrap', o bien proporcionar la función como un argumento en la tabla de datos original? – cboettig

+0

@cboettig no y es poco probable que lo haga. Una lista con nombre no funcionaría para múltiples variables de facetado. – hadley

3

Creo que debe proporcionar más información. Esto parece funcionar:

pg <- ggplot(dd, aes(Predicted_value)) ## need aesthetics in the ggplot 
pg <- pg + geom_density() 
## gotta provide the arguments of the dnorm 
pg <- pg + stat_function(fun=dnorm, colour='red',    
      args=list(mean=mean(dd$Predicted_value), sd=sd(dd$Predicted_value))) 
## wrap it! 
pg <- pg + facet_wrap(~State_CD) 
pg 

Proporcionamos los mismos parámetros mean y sd para cada panel. Conseguir medios de panel específico y las desviaciones estándar se deja como ejercicio para el lector *;)

'*' En otras palabras, no está seguro de cómo se puede hacer ...

+1

Los medios específicos del panel y las desviaciones estándar se pueden obtener utilizando la biblioteca (plyr) y ddply (dd,. (State_CD), resúmen, ...) – Nova

+1

Buen esfuerzo, pero ... OP declaró en la parte superior '' "Yo ​​lo haría me gusta tener el dist normal en la parte superior para tener la misma media y stdev que el panel. "' – PatrickT

1

Creo que su mejor apuesta es dibuja la línea manualmente con geom_line.

dd<-data.frame(matrix(rnorm(144, mean=2, sd=2),72,2),c(rep("A",24),rep("B",24),rep("C",24))) 
colnames(dd) <- c("x_value", "Predicted_value", "State_CD") 
dd$Predicted_value<-dd$Predicted_value*as.numeric(dd$State_CD) #make different by state 

##Calculate means and standard deviations by level 
means<-as.numeric(by(dd[,2],dd$State_CD,mean)) 
sds<-as.numeric(by(dd[,2],dd$State_CD,sd)) 

##Create evenly spaced evaluation points +/- 3 standard deviations away from the mean 
dd$vals<-0 
for(i in 1:length(levels(dd$State_CD))){ 
    dd$vals[dd$State_CD==levels(dd$State_CD)[i]]<-seq(from=means[i]-3*sds[i], 
          to=means[i]+3*sds[i], 
          length.out=sum(dd$State_CD==levels(dd$State_CD)[i])) 
} 
##Create normal density points 
dd$norm<-with(dd,dnorm(vals,means[as.numeric(State_CD)], 
         sds[as.numeric(State_CD)])) 


pg <- ggplot(dd, aes(Predicted_value)) 
pg <- pg + geom_density() 
pg <- pg + geom_line(aes(x=vals,y=norm),colour="red") #Add in normal distribution 
pg <- pg + facet_wrap(~State_CD,scales="free") 
pg 
1

Si no desea generar la distribución normal de línea gráfica "a mano", todavía utilizan stat_function, y mostrar gráficos uno al lado del otro - entonces podría considerar usar la función "multiplot" publicada en "Cookbook for R" como una alternativa a facet_wrap. Puede copiar el código de lote múltiple en su proyecto from here.

Después de copiar el código, haga lo siguiente:

# Some fake data (copied from hadley's answer) 
dd <- data.frame(
    predicted = rnorm(72, mean = 2, sd = 2), 
    state = rep(c("A", "B", "C"), each = 24) 
) 

# Split the data by state, apply a function on each member that converts it into a 
# plot object, and return the result as a vector. 
plots <- lapply(split(dd,dd$state),FUN=function(state_slice){ 
    # The code here is the plot code generation. You can do anything you would 
    # normally do for a single plot, such as calling stat_function, and you do this 
    # one slice at a time. 
    ggplot(state_slice, aes(predicted)) + 
    geom_density() + 
    stat_function(fun=dnorm, 
        args=list(mean=mean(state_slice$predicted), 
          sd=sd(state_slice$predicted)), 
        color="red") 
}) 

# Finally, present the plots on 3 columns. 
multiplot(plotlist = plots, cols=3) 

enter image description here

Cuestiones relacionadas