Problem
- I want to add pipes to ALL THE THINGS.
ggplot2
is a thingggplot2
doesn’t use pipes- Add pipe support to
ggplot2
- Write a blog post.
- ???
- Profit
Note: for keen followers, this is a rehash of my hack that was the pipify
package.
ggplot without pipes. :(
Using the +
character is soooo early 2010’s.
ggplot(mtcars) +
geom_point(aes(mpg, cyl))
Create add_geom_point
as a pipe-able geom_point
:)
Let’s write a wrapper around geom_point
so that I can use it in a pipe
#-----------------------------------------------------------------------------
#' A wrapper around 'geom_point' that is pipe-aware
#'
#' @param lhs the left-hand side of the addition e.g. "ggplot(mtcars)"
#' @param ... the arguments to be passed to the 'geom_point' function
#-----------------------------------------------------------------------------
add_geom_point <- function(lhs, ...) {
`+`(lhs, geom_point(...))
}
ggplot(mtcars) %>%
add_geom_point(aes(mpg, cyl))
Create pipe-able versions of any ggplot2 function
Instead of writing all the wrappers for all the gglot functions manually, I wrote a function to create a pipe-aware function out of a plus-aware function.
#-----------------------------------------------------------------------------
#' Create a pipe-aware func from any plus-aware function
#'
#' Assigns the new function in the global environemnt
#'
#' @param plus_aware_func_name character. e.g. "geom_point"
#'
#-----------------------------------------------------------------------------
create_pipe_aware_func <- function(plus_aware_func_name) {
pipe_aware_func_name <- paste0("add_", plus_aware_func_name)
assign(
pipe_aware_func_name,
function(lhs, ...) {
func <- get(plus_aware_func_name)
`+`(lhs, func(...))
},
envir = globalenv()
)
}
Use this function to create add_geom_line()
and use it.
create_pipe_aware_func("geom_line") # This will create 'add_geom_line()' in the global environment
ggplot(mtcars) %>%
add_geom_point(aes(mpg, cyl)) %>%
add_geom_line(aes(mpg, cyl))
Create add_*
as pipe-able versions of all ggplot functions :) :) :)
Now that I can generate a pipe-aware function for a single ggplot function, I’ll do the whole of the ggplot package:
- Find all ggplot2 function names of interest
- Create pipe-aware wrapper function for each one
ls('package:ggplot2') %>%
purrr::keep(~is.function(get(.x))) %>%
purrr::keep(~grepl('^(geom_|scale_|stat_|coord_|annot|xlim|ylim|theme_|facet_)', .x)) %>%
purrr::walk(create_pipe_aware_func)
ggplot(mtcars) %>%
add_geom_point(aes(mpg, cyl, colour=as.factor(am))) %>%
add_facet_grid(~vs) %>%
add_theme_bw()
Evil version
And now instead of adding the add_
prefix in front of the function names, just
mask the original ggplot2 functions in the global environment and use the original
function names with pipes.
Note: This will break in all sorts of wonderful ways!
#-----------------------------------------------------------------------------
#' Create a pipe-aware func from any plus-aware function
#'
#' Creates the pipe-aware function of the same name in the global environment
#'
#' @param plus_aware_func_name character. e.g. "geom_point"
#'
#-----------------------------------------------------------------------------
create_pipe_aware_func_evil <- function(plus_aware_func_name) {
assign(
plus_aware_func_name,
function(lhs, ...) {
func <- get(plus_aware_func_name, envir = as.environment('package:ggplot2'))
`+`(lhs, func(...))
},
pos = globalenv()
)
}
ls('package:ggplot2') %>%
purrr::keep(~is.function(get(.x))) %>%
purrr::keep(~grepl('^(geom_|stat_|coord_|annot|xlim|ylim|theme_|facet_)', .x)) %>%
purrr::walk(create_pipe_aware_func_evil)
ggplot(mtcars) %>%
geom_point(aes(mpg, cyl, colour=as.factor(am))) %>%
facet_grid(~vs) %>%
theme_bw()
Conclusion
A solution to a problem that nobody really has.