Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stack points one above the other

Tags:

plot

r

ggplot2

To prevent over-plotting, I would like an alternative to jitter that simply stacks points one on top of the other around the mean (offsets them vertically by increments of given amount rather than add a random value as jitter does).

From this:

example <- data.frame(x=c(1,1,1,2,2,2,2), y=c(1,1,1,2,2,2,2))

qplot(data=example,x=x, y=y)

What I would like is something like this:

example image

This should probably be possible with stat_bindot().

Any suggestions?

like image 870
Etienne Low-Décarie Avatar asked Sep 12 '13 21:09

Etienne Low-Décarie


2 Answers

None of the built in positions quite do this, and geom_dotplot isn't quite right either because it only works in one dimension. I've cobbled together a new position which does the right sort of thing, but requires manual tweaking to get everything right.

library("proto")
PositionNudge <- proto(ggplot2:::Position, {
  objname <- "nudge"
  adjust <- function(., data) {
    trans_x <- function(x) {
      lx <- length(x)
      if (lx > 1) {
        x + .$width*(seq_len(lx) - ((lx+1)/2))
      } else {
        x
      }
    }
    trans_y <- function(y) {
      ly <- length(y)
      if (ly > 1) {
        y + .$height*(seq_len(ly) - ((ly+1)/2))
      } else {
        y
      }
    }
    ddply(data, .(group), transform_position, trans_x=trans_x, trans_y=trans_y)
  }
})
position_nudge <- function(width = 0, height = 0) {
  PositionNudge$new(width = width, height = height)
}

I called it nudge, because the the other things I though of were already taken (stack, dodge)

To use it

ggplot(example, aes(x, y)) +
  geom_point(aes(group=interaction(x,y)), size=5, 
             position=position_nudge(height=0.03))

enter image description here

You can also nudge in the horizontal direction

ggplot(example, aes(x, y)) +
  geom_point(aes(group=interaction(x,y)), size=5, 
             position=position_nudge(width=0.03))

enter image description here

You have to specify the group interaction as an aesthetic to the geom_point and the exact width/height needed to be passed to the position_nudge depend on the size of the points and the size of the output. For example, with a 4x6 output, you need

ggplot(example, aes(x, y)) +
  geom_point(aes(group=interaction(x,y)), size=10, 
                 position=position_nudge(height=0.13))

enter image description here

The value of 0.13 was just trial and error until it looked right. Some of the code and things learned from geom_dotplot could probably be re-used here to make this more robust. Also, I've not tested this with any data other than the example given, so it may break it some interesting ways. But it is, if nothing else, a start.

like image 67
Brian Diggs Avatar answered Nov 09 '22 09:11

Brian Diggs


Far from the sophistication of @Brian Diggs' nice answer, but a quick alternative where I use the 'dodge data' in the object created by geom_dotplot to generate new y values. Some manual tweeking is needed to get the scaling of 'dodge' right.

# create ggplot object with geom_dotplot
g1 <- ggplot(example, aes(x = x, y = y)) +
  geom_dotplot(stackdir = "center")

# grab dodge values from plot object
dodge <- ggplot_build(g1)[["data"]][[1]]$stackpos

# create new y values that are adjusted by 'dodge'
example$y2 <- example$y + dodge * 0.05

# plot using new y values
ggplot(example, aes(x = x, y = y2)) +
  geom_point(size = 5)

enter image description here

# seems to work decently on grouped data as well
df2 <- rbind(example, example)
df2$grp <- rep(c("a", "b"), each = 7)
df2

g2 <- ggplot(df2, aes(x = x, y = y, colour = grp)) +
  geom_dotplot(stackdir = "center", stackgroups = FALSE)

dodge <- ggplot_build(g2)[["data"]][[1]]$stackpos

df2$y2 <- df2$y + dodge * 0.04

ggplot(df2, aes(x = x, y = y2, colour = grp)) +
  geom_point(size = 5, position = position_dodge(width = 0.05))

enter image description here

like image 29
Henrik Avatar answered Nov 09 '22 08:11

Henrik