Reputation: 2296
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
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
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)))
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)
Upvotes: 2
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:
Upvotes: 2