20
votes

A ggplot2-challenged latticist needs help: What's the syntax to request variable per-facet breaks in a histogram?

library(ggplot2)
d = data.frame(x=c(rnorm(100,10,0.1),rnorm(100,20,0.1)),par=rep(letters[1:2],each=100))
# Note: breaks have different length by par
breaks = list(a=seq(9,11,by=0.1),b=seq(19,21,by=0.2))
ggplot(d, aes(x=x) ) + 
  geom_histogram() + ### Here the ~breaks should be added
  facet_wrap(~ par,  scales="free")

As pointed out by jucor, here some more solutions.

On special request, and to show why I am not a great ggplot fan, the lattice version

library(lattice)
d = data.frame(x=c(rnorm(100,10,0.1),rnorm(100,20,0.1)),par=rep(letters[1:2],each=100))
# Note: breaks have different length by par
myBreaks = list(a=seq(8,12,by=0.1),b=seq(18,22,by=0.2))
histogram(~x|par,data=d,
          panel = function(x,breaks,...){
            # I don't know of a generic way to get the 
            # grouping variable with histogram, so 
            # this is not very generic
            par = levels(d$par)[which.packet()]
            breaks = myBreaks[[par]]
            panel.histogram(x,breaks=breaks,...)
          },
          breaks=NULL, # important to force per-panel compute
          scales=list(x=list(relation="free")))

enter image description here

4
As a latticist defender ..can you share please the lattice version..agstudy
You could do the binning outside of ggplot2, e.g. using hist in combination with ddply or data.table or ..., and make barplots.Roland
Yes, that looks like the method to go. Since this is for a package of another author, it would required some work. github.com/xfim/ggmcmc/pull/17Dieter Menne

4 Answers

16
votes

Here is one alternative:

hls <- mapply(function(x, b) geom_histogram(data = x, breaks = b), 
              dlply(d, .(par)), myBreaks)
ggplot(d, aes(x=x)) + hls + facet_wrap(~par, scales = "free_x")

enter image description here

If you need to shrink the range of x, then

hls <- mapply(function(x, b) {
  rng <- range(x$x)
  bb <- c(rng[1], b[rng[1] <= b & b <= rng[2]], rng[2])
  geom_histogram(data = x, breaks = bb, colour = "white")
}, dlply(d, .(par)), myBreaks)

ggplot(d, aes(x=x)) + hls + facet_wrap(~par, scales = "free_x")

enter image description here

5
votes

I don't think that it is possible to give different break points in each facet.

As workaround you can make two plots and then with grid.arrange() function from library gridExtra put them together. To set break points in geom_histogram() use binwidth= and set one value for width of bin.

p1<-ggplot(subset(d,par=="a"), aes(x=x) ) + 
  geom_histogram(binwidth=0.1) +
  facet_wrap(~ par)

p2<-ggplot(subset(d,par=="b"), aes(x=x) ) + 
  geom_histogram(binwidth=0.2) +
  facet_wrap(~ par)
library(gridExtra)
grid.arrange(p1,p2,ncol=2)

enter image description here

4
votes

Following on from Didzis example:

ggplot(dat=d, aes(x=x, y=..ncount..)) +
  geom_histogram(data = d[d$par == "a",], binwidth=0.1) +
  geom_histogram(data = d[d$par == "b",], binwidth=0.01) +  
  facet_grid(.~ par, scales="free")

EDIT: This works for more levels but of course there are already better solutions

# More facets
d <- data.frame(x=c(rnorm(200,10,0.1),rnorm(200,20,0.1)),par=rep(letters[1:4],each=100))

# vector of binwidths same length as number of facets - need a nicer way to calculate these
my.width=c(0.5,0.25,0.1,0.01)

out<-lapply(1:length(my.width),function(.i) data.frame(par=levels(d$par)[.i],ggplot2:::bin(d$x[d$par==levels(d$par)[.i]],binwidth=my.width[.i])))

my.df<-do.call(rbind , out)

ggplot(my.df) + geom_histogram(aes(x, y = density, width = width), stat =  "identity") + facet_wrap(~par,scales="free")

from https://groups.google.com/forum/?fromgroups=#!searchin/ggplot2/bin$20histogram$20by$20facet/ggplot2/xlqRIFPP-zE/CgfigIkgAAkJ

3
votes

It is not, strictly speaking, possible to give different breaks in the different facets. But you can get the same effect by having a different layer for each facet (much as in user20650's answer), but mostly automating the multiple geom_histogram calls:

d <- data.frame(x=c(rnorm(100,10,0.1),rnorm(100,20,0.1)),
                par=rep(letters[1:2],each=100))
breaks <- list(a=seq(9,11,by=0.1),b=seq(19,21,by=0.2))

ggplot(d, aes(x=x)) +
  mapply(function(d, b) {geom_histogram(data=d, breaks=b)}, 
         split(d, d$par), breaks) +
  facet_wrap(~ par,  scales="free_x")

enter image description here

The mapply call creates a list of geom_histograms which can be added to the plot. The tricky part is that you have to manually split the data (split(d, d$par)) into the data that goes into each facet.