Home > Net >  ggplot2: use colour / shape / ... inside and outside of aes() in a flexible plotting function
ggplot2: use colour / shape / ... inside and outside of aes() in a flexible plotting function

Time:09-21

The goal

I'd like to have a function that processes some input X and produces a gglot graph using geom_point.

That function should allow to map columns of X to various aesthetics inside aes() (via arguments .shapefac, .colfac, etc.), but also allow to set e.g. point colour and shape manually (e.g. colour = "tomato"), outside of aes().

The problem

If .shapefac or .colfac arguments are provided, the aesthetics get overridden by the specification of shape and colour, respectively. This is explained e.g. here.

What I've tried

One workaround would be to remove the shape and colour arguments, and allow them to be specified using ... in geom_point. This works, but needs some advanced R knowledge for a user of such a function.

The question

Does anybody know if there is

  • a way to change the default setting for the shape and colour arguments outside of aes() in geom_point?

  • a way to override the specification of the shape and colour arguments, if an aesthetic is used?

  • another way to reach the goal stated above, without using ...?

Any hints or explanations are much appreciated!

The following function and plots hopefully illustrate the problem.

define function

library(mlbench)
data("Ionosphere")

cplot <- function(X, v = 1:ncol(X), .shapefac = NULL, .colfac = NULL, shape = 21, colour = "black",
                  center = TRUE, scale = FALSE, x = 1, y = 2, plot = TRUE) {
  
  library(ggplot2)
  # some code processing X to Y
  d.pca <- prcomp(X, center = center, scale. = scale)
  Y <- data.frame(X, d.pca$x)
  v <- round(100 * (d.pca$sdev^2 / sum(d.pca$sdev^2)), 2)
  
  # plot PCA
  p <- ggplot(Y, aes_string(x = paste0("PC",x), y = paste0("PC",y)))  
    geom_point(aes_string(shape = .shapefac, colour = .colfac),
               shape = shape, color = colour)  
    labs(x = paste0("PC ", x, " (", v[x], "%)"),
         y = paste0("PC ", x, " (", v[y], "%)"))  
    theme_bw()
  if (plot) print(p)
  invisible(p)
}

setting colour and shape arguments outside aes()

This works as it should.

cplot(Ionosphere, v = 3:34, colour = "tomato", shape = 4)

setting shape inside aes() when shape outside aes() has a default of 21

The default shape = 21 outside aes() overrides the shape aesthetic.

cplot(Ionosphere, v = 3:34, .shapefac = "Class")

setting colour inside aes() when colour outside aes() has a default of "black"

The default colour = "black" outside aes() overrides the colour aesthetic.

cplot(Ionosphere, v = 3:34, .colfac = "Class")

failed trials to set default shape and colour to NULLor NA

# results in an empty plot (shape = NA)
cplot(Ionosphere, v = 3:34, .shapefac = "Class", shape = NA)
#> Warning: Removed 351 rows containing missing values (geom_point).

# results in an error
cplot(Ionosphere, v = 3:34, .shapefac = "Class", shape = NULL)
#> Error: Aesthetics must be either length 1 or the same as the data (351): shape

Created on 2021-09-20 by the reprex package (v2.0.1)

CodePudding user response:

One option to achieve your desired result may look like so:

  1. If the aesthetics are provided set the color and/or shape params to NULL
  2. Make use of modifyList to construct a list of arguments to be passed to geom_point which includes the mapping and the non-NULL parameters. Making use modifyList will drop any NULL.
  3. Make use of do.call to call geom_point with the list of arguments.

Note: I slightly changed your function to select only numeric columns for the PCA.

library(mlbench)
library(ggplot2)

data(Ionosphere)

cplot <- function(X, .shapefac = NULL, .colfac = NULL, shape = 21, colour = "black",
                  center = TRUE, scale = FALSE, x = 1, y = 2, plot = TRUE) {
  
  col_numeric <- unlist(lapply(X, is.numeric))
  
  # some code processing X to Y
  d.pca <- prcomp(X[, col_numeric], center = center, scale. = scale)
  Y <- data.frame(X, d.pca$x)
  v <- round(100 * (d.pca$sdev^2 / sum(d.pca$sdev^2)), 2)
  
  colour <- if (is.null(.colfac)) colour
  shape <- if (is.null(.shapefac)) shape
  
  mapping <- aes_string(shape = .shapefac, colour = .colfac)
  args <- modifyList(list(mapping = mapping), list(color = colour, shape = shape))
  
  geom <- do.call("geom_point", args)
  
  p <- ggplot(Y, aes_string(x = paste0("PC", x), y = paste0("PC", y)))  
    geom  
    labs(
      x = paste0("PC ", x, " (", v[x], "%)"),
      y = paste0("PC ", x, " (", v[y], "%)")
    )  
    theme_bw()
  if (plot) print(p)
  invisible(p)
}

cplot(Ionosphere, colour = "tomato", shape = 4)

cplot(Ionosphere, .shapefac = "Class")

cplot(Ionosphere, .colfac = "Class")

cplot(Ionosphere, .colfac = "Class", .shapefac = "Class")

cplot(Ionosphere, .shapefac = "Class", shape = NULL)

  • Related