Mikko
Mikko

Reputation: 7755

Assign ggplot2 layers to string and evaluate if needed

Background

I am trying to speed up a function for an R package that plots high-resolution maps using ggplot2. The function basemap plots a map of Svalbard using shapefiles that are large (15 Mb each). The map is created using two different layers of shapefiles, land and glaciers, and a set of "definitions", such as theme, axis scales and grid lines. Plotting the map, as the code is written now, takes about 30 seconds.

My idea is that, if I could break the layers in code-pieces and allow the user a possibility not to plot glaciers, the function would perform twice as fast. If I, in addition, could assign the ggplot2 syntax as text string and evaluate only the strings that are needed, R would not waste time making ggplot2 objects that are not used by the function.

I know how to do this using if else statements, but I would like to avoid writing the definitions several times. It seems to be possible to assign scale_x_continuous() to an object, but assigning scale_x_continuous() + scale_y_continuous() produces an error:

Error in scale_x_continuous() + scale_y_continuous() : non-numeric argument to binary operator

Question

How do I assign ggplot2 axis definitions to a text string and paste them together with data layers?

Example

I use the iris dataset as an example, so that you do not have to download the PlotSvalbard package that is large and quite messy at the moment. Note that I know how to map color to a variable. The point here is just to illustrate the shapefiles as two subsets of the iris dataset:

library(ggplot2)
data(iris)

## Datasets to imitate the shapefile layers
ds1 <- subset(iris, Species == "setosa")
ds2 <- subset(iris, Species == "versicolor")

## Example how the basemap function works at the moment:

ggplot() + geom_point(data = ds1, aes(x = Sepal.Length, y = Sepal.Width), color = "red") +
geom_point(data = ds2, aes(x = Sepal.Length, y = Sepal.Width), color = "blue") +
scale_x_continuous("Longitude", breaks = seq(3, 8, by = 0.5)) +
scale_y_continuous("Latitude", breaks = seq(1,5, by = 1)) + theme_bw()

## Now I would like to plot ds2 only if user defines "keep.glaciers = TRUE"

land <- ggplot() + geom_point(data = ds1, aes(x = Sepal.Length, 
y = Sepal.Width), color = "red")

glacier <- geom_point(data = ds2, aes(x = Sepal.Length, y = Sepal.Width),
 color = "blue")

def <- scale_x_continuous("Longitude", breaks = seq(3, 8, by = 0.5)) +
scale_y_continuous("Latitude", breaks = seq(1,5, by = 1)) + theme_bw() ## Error!
# Error in +scale_x_continuous("Longitude", breaks = seq(3, 8, by = 0.5)) :
#  invalid argument to unary operator

## The ultimate goal:
keep.glaciers = TRUE

if(keep.glaciers) {
  land + glacier + def # error, see above
} else {
  land + def
}

Upvotes: 2

Views: 260

Answers (2)

Mikko
Mikko

Reputation: 7755

You can assign ggplot2 layers to strings and evaluate the strings after pasting them together:

map_layers <- function(layer) {

   switch(layer, 
    land = 'ggplot() + geom_point(data = ds1, aes(x = Sepal.Length, y = Sepal.Width), color = "red")',
    glacier = 'geom_point(data = ds2, aes(x = Sepal.Length, y = Sepal.Width), color = "blue")',
    def = 'scale_x_continuous("Longitude", breaks = seq(3, 8, by = 0.5)) + scale_y_continuous("Latitude", breaks = seq(1,5, by = 1)) + theme_bw()',
    stop(paste("map layer", layer, "not found"))
)
}

plot_glaciers <- function(keep.glaciers = TRUE) {

    if(keep.glaciers) {
      eval(parse(text=paste(map_layers("land"), map_layers("glacier"),
 map_layers("def"), sep = " + ")))
        } else {
      eval(parse(text=paste(map_layers("land"), map_layers("def"), sep = " + ")))
    }
}

plot_glaciers()
plot_glaciers(FALSE)

Using the nested switch approach allows you to make "modules" that you can easily paste together in if else statements of the plot function making the code easier to read, if you want to pass many map layers to the plot function.

Upvotes: 1

Peter
Peter

Reputation: 7790

You can keep layers in a list and use the + operator with ggplot.

For example, the function plot_glaciers keeps the land, def, and glacier layers in a list. A if statement controls the construction of the glacier layer. The function will return the ggplot object with the wanted layers.

plot_glaciers <- function(keep_glaciers = TRUE) {
  layers <- vector('list')

  layers$land    <- geom_point(data = ds1, aes(x = Sepal.Length, y = Sepal.Width), color = "red")
  layers$def     <- list(scale_x_continuous("Longitude", breaks = seq(3, 8, by = 0.5)),
                    scale_y_continuous("Latitude", breaks = seq(1,5, by = 1)),
                    theme_bw())

  if (keep_glaciers) {
    layers$glacier <- geom_point(data = ds2, aes(x = Sepal.Length, y = Sepal.Width), color = "blue")
  } 

  ggplot() + layers
}

plot_glaciers(TRUE)
plot_glaciers(FALSE)

Upvotes: 5

Related Questions