Anthony
Anthony

Reputation: 2296

R ggplot2 facet_grid-like results but with independent columns

I'd like to plot a few measurements on different individuals in two treatment groups. I'd like to show two columns, one for the first treatment, one for the second treatment. Each column would have a plot for each individual in that group. Something like this:

require(ggplot2)
t <- seq(from=0, to=10, length.out=100)
ids <- c(1, 17, 22, 4, 55, 74, 88)
treatment <- c('A', 'A', 'A', 'B', 'B', 'B', 'B')
df <- NULL
for (i in 1:length(ids))
    df <- rbind(df, data.frame(time=t, treatment=treatment[i], id=ids[i], value=rnorm(length(t))))
ggplot(df, aes(y=value, x=time)) +
    geom_line() +
    facet_grid(id ~ treatment, scale='free_y')

which produces

Resulting plot

The IDs will almost certainly not line up so you get a number of empty graphs in the middle. The IDs don't correspond to the same individuals anyway, so lining them up is not necessary. I'd like for the ID to be independent in the two columns. Is there any convenient way to do this in ggplot without resorting to anything overly hacky or am I stuck looking at different plots side by side? I could give them "pseudo" IDs which would remove the gaps but removes the utility of the row labels.

Upvotes: 3

Views: 1006

Answers (2)

Mark Peterson
Mark Peterson

Reputation: 9570

This is just a hair hacky, but you can generate a new variable with both the group and the id, then facet_wrap on that instead of using facet_grid. The only hacky part is making sure that you have the same number of individuals in each group (here, adding place holders for the empty ones)

# Generate a label with the individual and id
df$label <-
  paste(df$treatment
        , df$id
        , sep = ": ")

# Count the number of individuals in each treatment
counts <-
  by(df$id, df$treatment, function(x){length(unique(x))})


# For each group, check how many there are
# If it is less than the max, add a dummy row as a placeholder
for(i in names(counts)){
  if(counts[i] < max(counts)){
    df <- rbind(df,data.frame(time=0, treatment=i, id=NA, value=0, label= paste(i, "holder", 1:(max(counts) - counts[i]))))
  }
}

# Plot the result
ggplot(df, aes(y=value, x=time)) +
  geom_line() +
  # Facet on the contstructed label
  facet_wrap(~label
             , scale='free_y'
             # Make sure that you put them in columns, not rows
             , dir = "v"
             # Set the number of columns to be the number of groups
             , ncol = length(unique(df$treatment)))

enter image description here

If you really want to keep the labels on the side, you can construct a joint label that says which individual is from each group. The label construction is a bit hacky, but I think it should be flexible. Note, you probably don't want to run this after you have included the extra rows constructed above.

theIndOrders <-
  split(df$id,df$treatment) %>%
  lapply(function(x){
    1:(length(unique(x))) %>%
      setNames(sort(unique(x)))
  }) %>%
  unlist

myLabels <-
  split(names(theIndOrders), theIndOrders) %>%
  sapply(paste, collapse = "; ")


df$group <-
  theIndOrders[paste(df$treatment,df$id, sep = ".")]

df$myLab <-
  myLabels[df$group]


ggplot(df, aes(y=value, x=time)) +
  geom_line() +
  # Facet on the contstructed label
  facet_grid(myLab ~ treatment)

enter image description here

Upvotes: 2

hrbrmstr
hrbrmstr

Reputation: 78832

This may be in your definition of "hacky" but:

chart <- function(x) {
  ggplot(x, aes(y=value, x=time)) +
    geom_line() +
    facet_grid(id ~ treatment, scale='free_y')
}

dplyr::group_by(df, treatment) %>% 
  do(plt=chart(.)) -> plts

gridExtra::grid.arrange(grobs = plts$plt, ncol=2)

Alternately:

map(unique(df$treatment), function(x) {
  ggplot(dplyr::filter(df, treatment==x), aes(y=value, x=time)) +
    geom_line() +
    facet_grid(id ~ treatment, scale='free_y')
}) -> plts

gridExtra::grid.arrange(grobs = plts, ncol=2))

Output:

Results of the first code snippet

Upvotes: 2

Related Questions