Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ggplot2 2.0 new stat_ function: setting default scale for given aesthetics

I try to use the new functionality of ggplot2 in R that allows creating our own stat_ functions. I'm creating a simple one to compute and plot an interpolated surface between points arranged on a 2d array.

I would like to create a stat_topo() requiring x, y, and val aesthetics, plotting a simple geom_raster of interpolated val mapped to fill.

library(ggplot2)
library(dplyr)
library(akima)

cpt_grp <- function(data, scales) {
  #interpolate data in 2D
  itrp <- akima::interp(data$x,data$y,data$val,linear=F,extrap=T)
  out <- expand.grid(x=itrp$x, y=itrp$y,KEEP.OUT.ATTRS = F)%>%
    mutate(fill=as.vector(itrp$z))
  # str(out)
  return(out)
}

StatTopo <- ggproto("StatTopo", Stat,
                    compute_group = cpt_grp,
                    required_aes = c("x","y","val")
)
stat_topo <- function(mapping = NULL, data = NULL, geom = "raster",
                       position = "identity", na.rm = FALSE, show.legend = NA, 
                       inherit.aes = TRUE, ...) {
  layer(
    stat = StatTopo, data = data, mapping = mapping, geom = geom, 
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )
}

set.seed(1)
nchan <- 30
d <- data.frame(val = rnorm(nchan), # some random values to be mapped to fill color
         x = 1:nchan*cos(1:nchan), # the x and y position of the points to interpolate
         y = 1:nchan*sin(1:nchan))
plot(d$x,d$y)

ggplot(d,aes(x=x,y=y,val=val)) +
  stat_topo() +
  geom_point()

When I run this, I get the following error:

Error: numerical color values must be >= 0, found -1

I understand that this is because somehow the scale of the fill aesthetic is set to discrete.

If I enter this:

ggplot(d,aes(x=x,y=y,val=val)) +
  stat_topo() +
  scale_fill_continuous() +
  geom_point()

I get what I wanted: the expected raster with a continuous color scale, which I want the stat_ to do by default...

enter image description here

So I guess the question is: How can I prevent ggplot from setting a discrete scale here, and ideally set a default scale within the call to my new stat_ function.

like image 270
Max Avatar asked Jan 08 '16 15:01

Max


2 Answers

Apparently, when creating a new variable inside a stat_ function, one needs to explicitly associate it to the aesthetic it will be mapped to with the parameter default_aes = aes(fill = ..fill..) within the ggproto definition.

This is telling ggplot that it is a calculated aesthetic and it will pick a scale based on the data type.

So here we need to define the stat_ as follows:

cpt_grp <- function(data, scales) {
  # interpolate data in 2D
  itrp <- akima::interp(data$x,data$y,data$val,linear=F,extrap=T)
  out <- expand.grid(x=itrp$x, y=itrp$y,KEEP.OUT.ATTRS = F)%>%
    mutate(fill=as.vector(itrp$z))
  # str(out)
  return(out)
}

StatTopo <- ggproto("StatTopo", Stat,
                    compute_group = cpt_grp,
                    required_aes = c("x","y","val"),
                    default_aes = aes(fill = ..fill..)
)

stat_topo <- function(mapping = NULL, data = NULL, geom = "raster",
                      position = "identity", na.rm = FALSE, show.legend = NA, 
                      inherit.aes = TRUE, ...) {
  layer(
    stat = StatTopo, data = data, mapping = mapping, geom = geom, 
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)    
  )
}

Then the following code:

set.seed(1)
nchan <- 30
d <- data.frame(val = rnorm(nchan),
                x = 1:nchan*cos(1:nchan),
                y = 1:nchan*sin(1:nchan))
ggplot(d,aes(x=x,y=y,val=val)) +
  stat_topo() +
  geom_point()

Produces as expected:

The result of stat_topo

Without the need to specify a scale_ manually, but leaving the possibility to adapt the scale easily as usual with e.g. scale_fill_gradient2(low = 'blue',mid='white',high='red')

I got this answer here: https://github.com/hadley/ggplot2/issues/1481

like image 109
Max Avatar answered Nov 02 '22 20:11

Max


Okay, slept on it, and had an idea, and I think this might do what you want. In your stat_topo layer function instead of the ggproto I returned a list with it as the first element and then added to that list another ggproto with a call to scale_fill_continuous().

library(ggplot2)
library(dplyr)
library(akima)

cpt_grp <- function(data, scales) {
  #interpolate data in 2D
  itrp <- akima::interp(data$x,data$y,data$val,linear=F,extrap=T)
  out <- expand.grid(x=itrp$x, y=itrp$y,KEEP.OUT.ATTRS = F)%>%
    mutate(fill=as.vector(itrp$z))
  return(out)
}
StatTopo <- ggproto("StatTopo", Stat,
                    compute_group = cpt_grp,
                    required_aes = c("x","y","val")
)
stat_topo <- function(mapping = NULL, data = NULL, geom = "raster",
                      position = "identity", na.rm = FALSE, show.legend = NA, 
                      inherit.aes = TRUE, ...) {
  list(
    layer(
      stat = StatTopo, data = data, mapping = mapping, geom = geom, 
      position = position, show.legend = show.legend, inherit.aes = inherit.aes,
      params = list(na.rm = na.rm )
    ),
    scale_fill_continuous()
  )
}
set.seed(1)
nchan <- 30
d <- data.frame(val = rnorm(nchan), # some random values to be mapped to fill color
                x = 1:nchan*cos(1:nchan), # the x and y position of interp points
                y = 1:nchan*sin(1:nchan))
 ggplot(d,aes(x=x,y=y,val=val)) +
   stat_topo() +
   geom_point()

yielding the same picture as above.

like image 26
Mike Wise Avatar answered Nov 02 '22 20:11

Mike Wise