Reputation: 7755
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
How do I assign ggplot2 axis definitions to a text string and paste them together with data layers?
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
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
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