2

I have the following test data:

structure(list(r = c(5.44702625911984, 6.3431860464319, 2.89023592667928, 
6.66260769449341, 7.5521021076857, 5.50645078944005, 6.70001850525037, 
7.39615449137166, 5.96032231142622, 7.88929821115731, 9.45119299499902, 
6.13534105776075, 7.79397401855071, 5.24488870603935, 4.53178061905952, 
5.80573244536445, 10.1194252475799, 12.5794215385996, 7.47503723957468, 
7.8682648760597, 15.7540766770233, 14.9800818974568, 14.5672865569748, 
9.5347507057429, 18.6791666362954, 10.4588651710497, 15.2076130678251, 
10.5052588219606, 13.1314628288852, 12.8384811800557, 10.9978569438483, 
10.0197995395016, 10.1479274794689, 12.5864754383382, 10.7985399338233, 
11.1100572430765, 10.756576992292, 9.17309427876051, 10.0441987112265, 
10.0652520950654), f1 = structure(c(2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L), .Label = c("H", "L"), class = "factor"), f2 = structure(c(1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L), class = "factor", .Label = c("Joe", 
"Sally"))), .Names = c("r", "f1", "f2"), row.names = c(NA, -40L
), class = "data.frame")

And the following function which should draw the points (it does) and connect the means of each group (it doesn't):

testFunc <- function(formula = NULL, data = NULL) {
    res <- as.character(formula[[2]])
    fac1 <- as.character(formula[[3]][2])
    fac2 <- as.character(formula[[3]][3])

    # Specify the data & aesthetics     
    p <- ggplot(data, aes_string(x = fac1, y = res, color = fac2,
        group = fac2))

    # Now add points
    p <- p + geom_point() # works fine if we stop here

    # Due to a bug in ggplot2_0.9.3, we must calc some quantities
    # and put them in a separate data frame for a new aesthetic
    avg <- aggregate(data[,res] ~ data[,fac1]*data[, fac2], data, FUN = mean)
    names(avg) <- c("factor1", "factor2", "mean")   
    p <- p + geom_line(aes_string(x = 'factor1', y = 'mean', group = 'factor2'), data = avg)
    }

When I run it:

ex <- testFunc(formula = r ~ f1*f2, data = td)
print(ex)

I get this error:

Error in eval(expr, envir, enclos) : object 'f2' not found

I seem to always have these scoping problems, any advice? I thought I was following the appropriate work-around for the bug. Using aes instead of aes_string, or not quoting the variable names in geom_line doesn't fix it. Thanks.

Bryan Hanson
  • 6,055
  • 4
  • 41
  • 78

2 Answers2

1

When I run into trouble with inherited aesthetics, I always go back and remove anything from the main ggplot() call that doesn't need to be there. Try this:

testFunc <- function(formula = NULL, data = NULL) {
    res <- as.character(formula[[2]])
    fac1 <- as.character(formula[[3]][2])
    fac2 <- as.character(formula[[3]][3])

    # Now add points
    p <- ggplot() + geom_point(data = data, aes_string(x = fac1, y = res, color = fac2,
        group = fac2)) # works fine if we stop here

    # Due to a bug in ggplot2_0.9.3, we must calc some quantities
    # and put them in a separate data frame for a new aesthetic
    avg <- aggregate(data[,res] ~ data[,fac1]*data[, fac2], data, FUN = mean)
    names(avg) <- c("factor1", "factor2", "mean")   
    p <- p + geom_line(aes_string(x = 'factor1', y = 'mean', group = 'factor2'), data = avg)
    }
joran
  • 169,992
  • 32
  • 429
  • 468
  • Thank you Joran. Works of course. But why? I'd guess just calling `ggplot()` alone/empty allows one to separate the `geom_point` aesthetic from the `geom_line` aesthetic cleanly, and doesn't pollute the initial `proto` object - is that the way to think about it? Thanks so much for the insight. – Bryan Hanson Jan 15 '13 at 23:34
  • Basically, yes. Anything included in `ggplot()` will be passed on down to each layer, unless specifically overridden. My guess is that in this case something's going wrong with `aes_string`'s attempt to stomp out the originally set aesthetics of x,y and group in `ggplot`. It could possibly be a bug, but it could also be just an limitation of `aes_string` that's unavoidable. – joran Jan 15 '13 at 23:39
  • Your analysis is right, but the problem is the color aesthetic which is set in the original ggplot call but then is not overridden in the `geom_line` aesthetics that use the `avg` data (which doesn't have an `f2` variable). So you can leave `data, aes_string(x = fac1, y = res, group = fac2)` in the original `ggplot` call and just put `aes_string(color = fac2)` in the `geom_point` call and that will suffice. – Brian Diggs Jan 16 '13 at 00:16
  • Thanks Brian, understood. The test function works well now, but the real function has many options and potential layers (some discrete, some continuous). I'm getting a `Discrete value supplied to continuous scale` error on the real function. So that's the next task. Unfortunately, it might be tough to make a MWE out of it. – Bryan Hanson Jan 16 '13 at 00:55
  • Thanks @BrianDiggs, I totally missed the color/group aesthetic difference between the two layers. Need to have my eyes checked. – joran Jan 16 '13 at 01:38
0

I had to make two changes to get your function to work. First, there's a bug in ggplot2 0.9.3 (and previous) where the evaluation environment of aes() defaults to the global environment. The workaround: to make it evaluate in the calling environment (for example, your function's environment) use ggplot(..., environment = environment().

The other change was in the last line, where you have p <- p + geom_line(..., data=avg). The key there is that you must turn off the inheritance of aesthetics from the main ggplot call, because your avg data set doesn't have the fac2 column. To do that, use geom_line(..., inherit.aes=FALSE).

testFunc <- function(formula = NULL, data = NULL) {
  res <- as.character(formula[[2]])
  fac1 <- as.character(formula[[3]][2])
  fac2 <- as.character(formula[[3]][3])

  # Specify the data & aesthetics     
  p <- ggplot(data, aes_string(x = fac1, y = res, color = fac2, group = fac2),
    environment = environment())

  # Now add points
  p <- p + geom_point()

  # Due to a bug in ggplot2_0.9.3, we must calc some quantities
  # and put them in a separate data frame for a new aesthetic
  avg <- aggregate(data[,res] ~ data[,fac1]*data[, fac2], data, FUN = mean)
  names(avg) <- c("factor1", "factor2", "mean")   
  p + geom_line(aes_string(x = 'factor1', y = 'mean', group = 'factor2'), 
    inherit.aes = FALSE, data = avg)
}

ex <- testFunc(formula = r ~ f1*f2, data = td)
print(ex)
wch
  • 4,069
  • 2
  • 28
  • 36