This is an automated email from the git hooks/post-receive script. tille pushed a commit to branch master in repository r-cran-magrittr.
commit 1364d65257ee6bca8728a2946cffb2c2c26b2384 Author: Andreas Tille <[email protected]> Date: Fri Oct 20 11:05:17 2017 +0200 New upstream version 1.5 --- DESCRIPTION | 22 ++ LICENSE | 2 + MD5 | 42 +++ NAMESPACE | 40 +++ R/aliases.R | 183 ++++++++++++ R/debug_pipe.R | 46 ++++ R/first_type.R | 17 ++ R/freduce.R | 22 ++ R/function_type.R | 17 ++ R/functions.R | 33 +++ R/getters.R | 25 ++ R/is_something.R | 66 +++++ R/magrittr.R | 43 +++ R/pipe.R | 269 ++++++++++++++++++ R/split_chain.R | 45 +++ R/wrap_function.R | 26 ++ README.md | 152 ++++++++++ build/vignette.rds | Bin 0 -> 209 bytes debian/README.test | 9 - debian/changelog | 18 -- debian/compat | 1 - debian/control | 24 -- debian/copyright | 35 --- debian/docs | 3 - debian/rules | 6 - debian/source/format | 1 - debian/tests/control | 3 - debian/tests/run-unit-test | 11 - debian/watch | 2 - inst/doc/magrittr.R | 78 ++++++ inst/doc/magrittr.Rmd | 212 ++++++++++++++ inst/doc/magrittr.html | 443 ++++++++++++++++++++++++++++++ man/aliases.Rd | 83 ++++++ man/compound.Rd | 50 ++++ man/debug_fseq.Rd | 23 ++ man/debug_pipe.Rd | 18 ++ man/exposition.Rd | 34 +++ man/freduce.Rd | 20 ++ man/fseq.Rd | 24 ++ man/functions.Rd | 18 ++ man/magrittr.Rd | 47 ++++ man/pipe.Rd | 125 +++++++++ man/print.fseq.Rd | 19 ++ man/tee.Rd | 31 +++ tests/test-all.R | 2 + tests/testthat/test-aliases.R | 41 +++ tests/testthat/test-anonymous-functions.r | 82 ++++++ tests/testthat/test-compound.R | 22 ++ tests/testthat/test-fseq.r | 10 + tests/testthat/test-multiple-arguments.r | 49 ++++ tests/testthat/test-single-argument.r | 18 ++ tests/testthat/test-tee.r | 11 + vignettes/magrittr.Rmd | 212 ++++++++++++++ vignettes/magrittr.jpg | Bin 0 -> 94344 bytes 54 files changed, 2722 insertions(+), 113 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..ba3d9d8 --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,22 @@ +Package: magrittr +Type: Package +Title: A Forward-Pipe Operator for R +Version: 1.5 +Author: Stefan Milton Bache <[email protected]> and + Hadley Wickham <[email protected]> +Maintainer: Stefan Milton Bache <[email protected]> +Description: Provides a mechanism for chaining commands with a + new forward-pipe operator, %>%. This operator will forward a + value, or the result of an expression, into the next function + call/expression. There is flexible support for the type + of right-hand side expressions. For more information, see + package vignette. + To quote Rene Magritte, "Ceci n'est pas un pipe." +Suggests: testthat, knitr +VignetteBuilder: knitr +License: MIT + file LICENSE +ByteCompile: Yes +Packaged: 2014-11-22 08:50:53 UTC; shb +NeedsCompilation: no +Repository: CRAN +Date/Publication: 2014-11-22 19:15:57 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d1e79f4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2014 +COPYRIGHT HOLDER: Stefan Milton Bache and Hadley Wickham \ No newline at end of file diff --git a/MD5 b/MD5 new file mode 100644 index 0000000..55f07ed --- /dev/null +++ b/MD5 @@ -0,0 +1,42 @@ +9c0b8a1b0db74f48c62fd20cead4ee9d *DESCRIPTION +a085c7c24ade7b2da1aabe7068a47a49 *LICENSE +4713058135841cd36e63debfcbdee1b3 *NAMESPACE +339b223c3e7543f9ff84612ef9020d48 *R/aliases.R +4c559680ce313887c9a4a030e7288d08 *R/debug_pipe.R +c57bee0db55713b32a479a9eddf42126 *R/first_type.R +085fecfbcdf1709260ba6022f886085d *R/freduce.R +2f1141b88747f0eae21efa41d4312308 *R/function_type.R +70b7526a11508ac16ebcef4aec6b1ef1 *R/functions.R +73f90406e59484f27461cd6d9fa8befa *R/getters.R +cdea686298324dff73d97579be426f6c *R/is_something.R +f74ea4cf8f6afd8474a13695c56d6ad8 *R/magrittr.R +58671c36b9290b95797166e59a7f8773 *R/pipe.R +925c408caac6b5555bf7ca19f3fe5d14 *R/split_chain.R +761fbdcdf240c19efc40b266c657527a *R/wrap_function.R +c0a8b34896112d5cd8e5ef7b500c4d3c *README.md +e6afd78ac4185a0808a2f1a5ba183961 *build/vignette.rds +083617b1c3cac5f5926325c536019ea0 *inst/doc/magrittr.R +3d83e4728a3669156481faa4e48aec0d *inst/doc/magrittr.Rmd +d412378d3f4b00c0fbc78a3bbd82d7ac *inst/doc/magrittr.html +9b61084fde0047a022938c17e5dd28ac *man/aliases.Rd +25168a657341fa779300e16adad8d2a8 *man/compound.Rd +bb6ddcb2585511a2b11bcf255a5cb34d *man/debug_fseq.Rd +1a90577e8606d5254c62ee607228e6e1 *man/debug_pipe.Rd +cad11ff20cac264dd14f4f0b2ca51c1f *man/exposition.Rd +14d43d276195d127b4bb3b920a3fe5a3 *man/freduce.Rd +4d05a1e25179e4b5c8b54d5076ee843e *man/fseq.Rd +b67091c9e405ac51d223799a0ebf95e4 *man/functions.Rd +2b4d1b41136b536f467c0ec3ef6a0df3 *man/magrittr.Rd +a92ef98880933848688ccb9b0a2c1c48 *man/pipe.Rd +705c8b3737616993c1e4d39df1f6b184 *man/print.fseq.Rd +3c49d7774ce9f965413c0bfcf6eef55f *man/tee.Rd +955e258593aa6ff551e639514066bfb0 *tests/test-all.R +1240dcc13fb6eb83d1d09d00fe6f8a2c *tests/testthat/test-aliases.R +c34ba775b25e1b4ba15d465faa82fd6c *tests/testthat/test-anonymous-functions.r +67d71033b672e1d1e3f429837cbe05bc *tests/testthat/test-compound.R +7a15de588a5ce2c952a20b406d0278fb *tests/testthat/test-fseq.r +7ce9041aa19d5ccd29c7938877bbc25c *tests/testthat/test-multiple-arguments.r +668a7efdfa43fe9e4fb42da69251e932 *tests/testthat/test-single-argument.r +b71f24b1fdb5cfa83c7276def4534ce9 *tests/testthat/test-tee.r +3d83e4728a3669156481faa4e48aec0d *vignettes/magrittr.Rmd +561cc58e6362ebd922d69884e825c808 *vignettes/magrittr.jpg diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..8862b2c --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,40 @@ +# Generated by roxygen2 (4.0.2): do not edit by hand + +S3method("[",fseq) +S3method("[[",fseq) +S3method(print,fseq) +export("%$%") +export("%<>%") +export("%>%") +export("%T>%") +export("n'est pas") +export(add) +export(and) +export(debug_fseq) +export(debug_pipe) +export(divide_by) +export(divide_by_int) +export(equals) +export(extract) +export(extract2) +export(freduce) +export(functions) +export(inset) +export(inset2) +export(is_greater_than) +export(is_in) +export(is_less_than) +export(is_weakly_greater_than) +export(is_weakly_less_than) +export(mod) +export(multiply_by) +export(multiply_by_matrix) +export(not) +export(or) +export(raise_to_power) +export(set_colnames) +export(set_names) +export(set_rownames) +export(subtract) +export(undebug_fseq) +export(use_series) diff --git a/R/aliases.R b/R/aliases.R new file mode 100644 index 0000000..28a1054 --- /dev/null +++ b/R/aliases.R @@ -0,0 +1,183 @@ +#' Aliases +#' +#' magrittr provides a series of aliases which can be more pleasant to use +#' when composing chains using the \code{\%>\%} operator. +#' +#' Currently implemented aliases are +#' \tabular{ll}{ +#' \code{extract} \tab \code{`[`} \cr +#' \code{extract2} \tab \code{`[[`} \cr +#' \code{inset} \tab \code{`[<-`} \cr +#' \code{inset2} \tab \code{`[[<-`} \cr +#' \code{use_series} \tab \code{`$`} \cr +#' \code{add} \tab \code{`+`} \cr +#' \code{subtract} \tab \code{`-`} \cr +#' \code{multiply_by} \tab \code{`*`} \cr +#' \code{raise_to_power} \tab \code{`^`} \cr +#' \code{multiply_by_matrix} \tab \code{`\%*\%`} \cr +#' \code{divide_by} \tab \code{`/`} \cr +#' \code{divide_by_int} \tab \code{`\%/\%`} \cr +#' \code{mod} \tab \code{`\%\%`} \cr +#' \code{is_in} \tab \code{`\%in\%`} \cr +#' \code{and} \tab \code{`&`} \cr +#' \code{or} \tab \code{`|`} \cr +#' \code{equals} \tab \code{`==`} \cr +#' \code{is_greater_than} \tab \code{`>`} \cr +#' \code{is_weakly_greater_than} \tab \code{`>=`} \cr +#' \code{is_less_than} \tab \code{`<`} \cr +#' \code{is_weakly_less_than} \tab \code{`<=`} \cr +#' \code{not} (\code{`n'est pas`}) \tab \code{`!`} \cr +#' \code{set_colnames} \tab \code{`colnames<-`} \cr +#' \code{set_rownames} \tab \code{`rownames<-`} \cr +#' \code{set_names} \tab \code{`names<-`} \cr +#' } +#' +#' @usage NULL +#' @export +#' @rdname aliases +#' @name extract +#' @examples +#' iris %>% +#' extract(, 1:4) %>% +#' head +#' +#' good.times <- +#' Sys.Date() %>% +#' as.POSIXct %>% +#' seq(by = "15 mins", length.out = 100) %>% +#' data.frame(timestamp = .) +#' +#' good.times$quarter <- +#' good.times %>% +#' use_series(timestamp) %>% +#' format("%M") %>% +#' as.numeric %>% +#' divide_by_int(15) %>% +#' add(1) +extract <- `[` + +#' @rdname aliases +#' @usage NULL +#' @export +extract2 <- `[[` + +#' @rdname aliases +#' @usage NULL +#' @export +use_series <- `$` + +#' @rdname aliases +#' @usage NULL +#' @export +add <- `+` + +#' @rdname aliases +#' @usage NULL +#' @export +subtract <- `-` + +#' @rdname aliases +#' @usage NULL +#' @export +multiply_by <- `*` + +#' @rdname aliases +#' @usage NULL +#' @export +multiply_by_matrix <- `%*%` + + +#' @rdname aliases +#' @usage NULL +#' @export +divide_by <- `/` + +#' @rdname aliases +#' @usage NULL +#' @export +divide_by_int <- `%/%` + +#' @rdname aliases +#' @usage NULL +#' @export +raise_to_power <- `^` + +#' @rdname aliases +#' @usage NULL +#' @export +and <- `&` + +#' @rdname aliases +#' @usage NULL +#' @export +or <- `|` + +#' @rdname aliases +#' @usage NULL +#' @export +mod <- `%%` + +#' @rdname aliases +#' @usage NULL +#' @export +is_in <- `%in%` + +#' @rdname aliases +#' @usage NULL +#' @export +equals <- `==` + +#' @rdname aliases +#' @usage NULL +#' @export +is_greater_than <- `>` + +#' @rdname aliases +#' @usage NULL +#' @export +is_weakly_greater_than <- `>=` + +#' @rdname aliases +#' @usage NULL +#' @export +is_less_than <- `<` + +#' @rdname aliases +#' @usage NULL +#' @export +is_weakly_less_than <- `<=` + +#' @rdname aliases +#' @usage NULL +#' @export +not <- `!` + +#' @rdname aliases +#' @usage NULL +#' @export +`n'est pas` <- `!` + +#' @rdname aliases +#' @usage NULL +#' @export +set_colnames <- `colnames<-` + +#' @rdname aliases +#' @usage NULL +#' @export +set_rownames <- `rownames<-` + +#' @rdname aliases +#' @usage NULL +#' @export +set_names <- `names<-` + +#' @rdname aliases +#' @usage NULL +#' @export +inset <- `[<-` + +#' @rdname aliases +#' @usage NULL +#' @export +inset2 <- `[[<-` diff --git a/R/debug_pipe.R b/R/debug_pipe.R new file mode 100644 index 0000000..9607f9d --- /dev/null +++ b/R/debug_pipe.R @@ -0,0 +1,46 @@ +#' Debugging function for magrittr pipelines. +#' +#' This function is a wrapper around \code{browser}, which makes it +#' easier to debug at certain places in a magrittr pipe chain. +#' +#' @param x a value +#' @return x +#' +#' @export +debug_pipe <- function(x) +{ + browser() + x +} + +#' Debugging function for functional sequences. +#' +#' This is a utility function for marking functions in a functional +#' sequence for debbuging. +#' +#' @param fseq a functional sequence. +#' @param ... indices of functions to debug. +#' @return \code{invisible(NULL)}. +#' +#' @export +debug_fseq <- function(fseq, ...) +{ + is_valid_index <- function(i) i %in% 1:length(functions(fseq)) + + indices <- list(...) + if (!any(vapply(indices, is.numeric, logical(1))) || + !any(vapply(indices, is_valid_index, logical(1)))) + stop("Index or indices invalid.", call. = FALSE) + + invisible(lapply(indices, function(i) debug(functions(fseq)[[i]]))) +} + +#' @rdname debug_fseq +#' @export +undebug_fseq <- function(fseq) +{ + for (i in 1:length(functions(fseq))) + if (isdebugged(functions(fseq)[[i]])) + undebug(functions(fseq)[[i]]) +} + \ No newline at end of file diff --git a/R/first_type.R b/R/first_type.R new file mode 100644 index 0000000..a74513f --- /dev/null +++ b/R/first_type.R @@ -0,0 +1,17 @@ +# Determine whether an expression is of the type that needs a first argument. +# +# @param a non-evaluated expression. +# @return logical - TRUE if expr is of "first-argument" type, FALSE otherwise. +is_first <- function(expr) +{ + !any(vapply(expr[-1], identical, logical(1), quote(.))) +} + +# Prepare a magrittr rhs of "first-argument" type. +# +# @param a an expression which passes \code{is_first} +# @return an expression prepared for functional sequence construction. +prepare_first <- function(expr) +{ + as.call(c(expr[[1L]], quote(.), as.list(expr[-1L]))) +} \ No newline at end of file diff --git a/R/freduce.R b/R/freduce.R new file mode 100644 index 0000000..e5fe978 --- /dev/null +++ b/R/freduce.R @@ -0,0 +1,22 @@ +#' Apply a list of functions sequentially +#' +#' This function applies the first function to \code{value}, then the +#' next function to the result of the previous function call, etc. +#' +#' @param value initial value. +#' @param function_list a list of functions. +#' @return The result after applying each function in turn. +#' +#' +#' @export +freduce <- function(value, function_list) +{ + k <- length(function_list) + if (k > 1) { + for (i in 1:(k - 1L)) { + value <- function_list[[i]](value) + } + } + value <- withVisible(function_list[[k]](value)) + if (value[["visible"]]) value[["value"]] else invisible(value[["value"]]) +} diff --git a/R/function_type.R b/R/function_type.R new file mode 100644 index 0000000..a971c8e --- /dev/null +++ b/R/function_type.R @@ -0,0 +1,17 @@ +# Determine whether an expression counts as a function in a magrittr chain. +# +# @param a non-evaluated expression. +# @return logical - TRUE if expr represents a function, FALSE otherwise. +is_function <- function(expr) +{ + is.symbol(expr) || is.function(expr) +} + +# Prepare a magrittr rhs of funtion type +# +# @param a an expression which passes \code{is_function} +# @return an expression prepared for functional sequence construction. +prepare_function <- function(f) +{ + as.call(list(f, quote(.))) +} diff --git a/R/functions.R b/R/functions.R new file mode 100644 index 0000000..ed2ec46 --- /dev/null +++ b/R/functions.R @@ -0,0 +1,33 @@ +#' Extract the function list from a functional sequence. +#' +#' This can be used to extract the list of functions inside a functional +#' sequence created with a chain like \code{. \%>\% foo \%>\% bar}. +#' +#' @param fseq A functional sequence ala magrittr. +#' @return a list of functions +#' +#' @export +functions <- function(fseq) +{ + if (!"fseq" %in% class(fseq)) + stop("Object is not a functional sequence.", call. = FALSE) + environment(fseq)[["_function_list"]] +} + +#' Print method for functional sequence. +#' +#' @param x A functional sequence object +#' @param ... not used. +#' @return x +#' +#' @export +print.fseq <- function(x, ...) +{ + flist <- functions(x) + + cat("Functional sequence with the following components:\n\n") + lapply(1:length(flist), + function(i) cat(" ", i, ". ", deparse(body(flist[[i]])), "\n", sep = "")) + cat("\nUse 'functions' to extract the individual functions.", "\n") + x +} diff --git a/R/getters.R b/R/getters.R new file mode 100644 index 0000000..d12a875 --- /dev/null +++ b/R/getters.R @@ -0,0 +1,25 @@ +#' Extract function(s) from a functional sequence. +#' +#' Functional sequences can be subset using single or double brackets. +#' A single-bracket subset results in a new functional sequence, and +#' a double-bracket subset results in a single function. +#' +#' @rdname fseq +#' @param x A functional sequence +#' @param ... index/indices. For double brackets, the index must be of length 1. +#' @return A function or functional sequence. +#' @export +`[[.fseq` <- function(x, ...) +{ + functions(x)[[...]] +} + +#' @rdname fseq +#' @export +`[.fseq` <- function(x, ...) +{ + y <- x + environment(y) <- new.env(parent = parent.env(environment(x))) + environment(y)[["_function_list"]] <- functions(x)[...] + y +} diff --git a/R/is_something.R b/R/is_something.R new file mode 100644 index 0000000..6bc9b2b --- /dev/null +++ b/R/is_something.R @@ -0,0 +1,66 @@ +# Check whether a symbol is a valid magrittr pipe. +# +# @param pipe A quoted symbol +# @return logical - TRUE if a valid magrittr pipe, FALSE otherwise. +is_pipe <- function(pipe) +{ + identical(pipe, quote(`%>%`)) || + identical(pipe, quote(`%T>%`)) || + identical(pipe, quote(`%<>%`)) || + identical(pipe, quote(`%$%`)) +} + +# Determine whether an non-evaluated call is parenthesized +# +# @param a non-evaluated expression +# @retun logical - TRUE if expression is parenthesized, FALSE otherwise. +is_parenthesized <- function(expr) +{ + is.call(expr) && identical(expr[[1]], quote(`(`)) +} + +# Check whether a pipe is a tee. +# +# @param pipe A (quoted) pipe +# @return logical - TRUE if pipe is a tee, FALSE otherwise. +is_tee <- function(pipe) +{ + identical(pipe, quote(`%T>%`)) +} + +# Check whether a pipe is the dollar pipe. +# +# @param pipe A (quoted) pipe +# @return logical - TRUE if pipe is the dollar pipe, FALSE otherwise. +is_dollar <- function(pipe) +{ + identical(pipe, quote(`%$%`)) +} + +# Check whether a pipe is the compound assignment pipe operator +# +# @param pipe A (quoted) pipe +# @return logical - TRUE if pipe is the compound assignment pipe, +# otherwise FALSE. +is_compound_pipe <- function(pipe) +{ + identical(pipe, quote(`%<>%`)) +} + +# Check whether expression is enclosed in curly braces. +# +# @param expr An expression to be tested. +# @return logical - TRUE if expr is enclosed in `{`, FALSE otherwise. +is_funexpr <- function(expr) +{ + is.call(expr) && identical(expr[[1]], quote(`{`)) +} + +# Check whether a symbol is the magrittr placeholder. +# +# @param symbol A (quoted) symbol +# @return logical - TRUE if symbol is the magrittr placeholder, FALSE otherwise. +is_placeholder <- function(symbol) +{ + identical(symbol, quote(.)) +} diff --git a/R/magrittr.R b/R/magrittr.R new file mode 100644 index 0000000..f88eb19 --- /dev/null +++ b/R/magrittr.R @@ -0,0 +1,43 @@ +#' magrittr - Ceci n'est pas un pipe +#' +#' The magrittr package offers a set of operators which promote semantics +#' that will improve your code by +#' \itemize{ +#' \item structuring sequences of data operations left-to-right +#' (as opposed to from the inside and out), +#' \item avoiding nested function calls, +#' \item minimizing the need for local variables and function definitions, and +#' \item making it easy to add steps anywhere in the sequence of operations. +#' } +#' The operators pipe their left-hand side values forward into expressions that +#' appear on the right-hand side, i.e. one can replace \code{f(x)} with +#' \code{x \%>\% f}, where \code{\%>\%} is the (main) pipe-operator. +#' \cr\cr +#' Consider the example below. Four operations are performed to +#' arrive at the desired data set, and they are written in a natural order: +#' the same as the order of execution. Also, no temporary variables are needed. +#' If yet another operation is required, it is straight-forward to add to the +#' sequence of operations whereever it may be needed. +#' \cr\cr +#' For a more detailed introduction see the vignette +#' (\code{vignette("magrittr")}) or the documentation pages for the +#' available operators:\cr +#' \tabular{ll}{ +#' \code{\link{\%>\%}} \tab forward-pipe operator.\cr +#' \code{\link{\%T>\%}} \tab tee operator.\cr +#' \code{\link{\%<>\%}} \tab compound assignment pipe-operator.\cr +#' \code{\link{\%$\%}} \tab exposition pipe-operator.\cr +#' } +#' +#' @examples +#' \dontrun{ +#' +#' the_data <- +#' read.csv('/path/to/data/file.csv') %>% +#' subset(variable_a > x) %>% +#' transform(variable_c = variable_a/veraiable_b) %>% +#' head(100) +#' } +#' @docType package +#' @name magrittr +NULL \ No newline at end of file diff --git a/R/pipe.R b/R/pipe.R new file mode 100644 index 0000000..86c544c --- /dev/null +++ b/R/pipe.R @@ -0,0 +1,269 @@ +# Create a pipe operator. +# +# This function is used to create all the magrittr pipe operators. +pipe <- function() +{ + function(lhs, rhs) + { + # the parent environment + parent <- parent.frame() + + # the environment in which to evaluate pipeline + env <- new.env(parent = parent) + + # split the pipeline/chain into its parts. + chain_parts <- split_chain(match.call(), env = env) + + pipes <- chain_parts[["pipes"]] # the pipe operators. + rhss <- chain_parts[["rhss" ]] # the right-hand sides. + lhs <- chain_parts[["lhs" ]] # the left-hand side. + + # Create the list of functions defined by the right-hand sides. + env[["_function_list"]] <- + lapply(1:length(rhss), + function(i) wrap_function(rhss[[i]], pipes[[i]], parent)) + + # Create a function which applies each of the above functions in turn. + env[["_fseq"]] <- + `class<-`(eval(quote(function(value) freduce(value, `_function_list`)), + env, env), c("fseq", "function")) + + # make freduce available to the resulting function + # even if magrittr is not loaded. + env[["freduce"]] <- freduce + + # Result depends on the left-hand side. + if (is_placeholder(lhs)) { + # return the function itself. + env[["_fseq"]] + } else { + # evaluate the LHS + env[["_lhs"]] <- eval(lhs, parent, parent) + + # compute the result by applying the function to the LHS + result <- withVisible(eval(quote(`_fseq`(`_lhs`)), env, env)) + + # If compound assignment pipe operator is used, assign result + if (is_compound_pipe(pipes[[1L]])) { + eval(call("<-", lhs, result[["value"]]), parent, parent) + # Otherwise, return it. + } else { + if (result[["visible"]]) + result[["value"]] + else + invisible(result[["value"]]) + } + } + } +} + +#' magrittr forward-pipe operator +#' +#' Pipe an object forward into a function or call expression. +#' +#' @param lhs A value or the magrittr placeholder. +#' @param rhs A function call using the magrittr semantics. +#' @details +#' \bold{Using \code{\%>\%} with unary function calls}\cr +#' When functions require only one argument, \code{x \%>\% f} is equivalent +#' to \code{f(x)} (not exactly equivalent; see technical note below.) +#' \cr\cr +#' \bold{Placing \code{lhs} as the first argument in \code{rhs} call}\cr +#' The default behavior of \code{\%>\%} when multiple arguments are required +#' in the \code{rhs} call, is to place \code{lhs} as the first argument, i.e. +#' \code{x \%>\% f(y)} is equivalent to \code{f(x, y)}. +#' \cr\cr +#' \bold{Placing \code{lhs} elsewhere in \code{rhs} call}\cr +#' Often you will want \code{lhs} to the \code{rhs} call at another position than the first. +#' For this purpose you can use the dot (\code{.}) as placeholder. For example, +#' \code{y \%>\% f(x, .)} is equivalent to \code{f(x, y)} and +#' \code{z \%>\% f(x, y, arg = .)} is equivalent to \code{f(x, y, arg = z)}. +#' \cr\cr +#' \bold{Using the dot for secondary purposes}\cr +#' Often, some attribute or property of \code{lhs} is desired in the \code{rhs} call in +#' addition to the value of \code{lhs} itself, e.g. the number of rows or columns. +#' It is perfectly valid to use the dot placeholder several times in the \code{rhs} +#' call, but by design the behavior is slightly different when using it inside +#' nested function calls. In particular, if the placeholder is only used +#' in a nested function call, \code{lhs} will also be placed as the first argument! +#' The reason for this is that in most use-cases this produces the most readable +#' code. For example, \code{iris \%>\% subset(1:nrow(.) \%\% 2 == 0)} is +#' equivalent to \code{iris \%>\% subset(., 1:nrow(.) \%\% 2 == 0)} but +#' slightly more compact. It is possible to overrule this behavior by enclosing +#' the \code{rhs} in braces. For example, \code{1:10 \%>\% {c(min(.), max(.))}} is +#' equivalent to \code{c(min(1:10), max(1:10))}. +#' \cr\cr +#' \bold{Using \%>\% with call- or function-producing \code{rhs}}\cr +#' It is possible to force evaluation of \code{rhs} before the piping of \code{lhs} takes +#' place. This is useful when \code{rhs} produces the relevant call or function. +#' To evaluate \code{rhs} first, enclose it in parentheses, i.e. +#' \code{a \%>\% (function(x) x^2)}, and \code{1:10 \%>\% (call("sum"))}. +#' Another example where this is relevant is for reference class methods +#' which are accessed using the \code{$} operator, where one would do +#' \code{x \%>\% (rc$f)}, and not \code{x \%>\% rc$f}. +#' \cr\cr +#' \bold{Using lambda expressions with \code{\%>\%}}\cr +#' Each \code{rhs} is essentially a one-expression body of a unary function. +#' Therefore defining lambdas in magrittr is very natural, and as +#' the definitions of regular functions: if more than a single expression +#' is needed one encloses the body in a pair of braces, \code{\{ rhs \}}. +#' However, note that within braces there are no "first-argument rule": +#' it will be exactly like writing a unary function where the argument name is +#' "\code{.}" (the dot). +#' \cr\cr +#' \bold{Using the dot-place holder as \code{lhs}}\cr +#' When the dot is used as \code{lhs}, the result will be a functional sequence, +#' i.e. a function which applies the entire chain of right-hand sides in turn +#' to its input. See the examples. +#' +#' @section Technical notes: +#' The magrittr pipe operators use non-standard evaluation. They capture +#' their inputs and examines them to figure out how to proceed. First a function +#' is produced from all of the individual right-hand side expressions, and +#' then the result is obtained by applying this function to the left-hand side. +#' For most purposes, one can disregard the subtle aspects of magrittr's +#' evaluation, but some functions may capture their calling environment, +#' and thus using the operators will not be exactly equivalent to the +#' "standard call" without pipe-operators. +#' \cr\cr +#' Another note is that special attention is advised when using non-magrittr +#' operators in a pipe-chain (\code{+, -, $,} etc.), as operator precedence will impact how the +#' chain is evaluated. In general it is advised to use the aliases provided +#' by magrittr. +#' +#' @seealso \code{\link{\%<>\%}}, \code{\link{\%T>\%}}, \code{\link{\%$\%}} +#' +#' @examples +#' # Basic use: +#' iris %>% head +#' +#' # Use with lhs as first argument +#' iris %>% head(10) +#' +#' # Using the dot place-holder +#' "Ceci n'est pas une pipe" %>% gsub("une", "un", .) +#' +#' # When dot is nested, lhs is still placed first: +#' sample(1:10) %>% paste0(LETTERS[.]) +#' +#' # This can be avoided: +#' rnorm(100) %>% {c(min(.), mean(.), max(.))} %>% floor +#' +#' # Lambda expressions: +#' iris %>% +#' { +#' size <- sample(1:10, size = 1) +#' rbind(head(., size), tail(., size)) +#' } +#' +#' # renaming in lambdas: +#' iris %>% +#' { +#' my_data <- . +#' size <- sample(1:10, size = 1) +#' rbind(head(my_data, size), tail(my_data, size)) +#' } +#' +#' # Building unary functions with %>% +#' trig_fest <- . %>% tan %>% cos %>% sin +#' +#' 1:10 %>% trig_fest +#' trig_fest(1:10) +#' +#' @rdname pipe +#' @export +`%>%` <- pipe() + +#' magrittr compound assignment pipe-operator +#' +#' Pipe an object forward into a function or call expression and update the +#' \code{lhs} object with the resulting value. +#' +#' @param lhs An object which serves both as the initial value and as target. +#' @param rhs a function call using the magrittr semantics. +#' +#' @details The compound assignment pipe-operator, \code{\%<>\%}, is used to +#' update a value by first piping it into one or more \code{rhs} expressions, and +#' then assigning the result. For example, \code{some_object \%<>\% +#' foo \%>\% bar} is equivalent to \code{some_object <- some_object \%>\% foo +#' \%>\% bar}. It must be the first pipe-operator in a chain, but otherwise it +#' works like \code{\link{\%>\%}}. +#' +#' @seealso \code{\link{\%>\%}}, \code{\link{\%T>\%}}, \code{\link{\%$\%}} +#' +#' @examples +#' iris$Sepal.Length %<>% sqrt +#' +#' x <- rnorm(100) +#' +#' x %<>% abs %>% sort +#' +#' is_weekend <- function(day) +#' { +#' # day could be e.g. character a valid representation +#' day %<>% as.Date +#' +#' result <- day %>% format("%u") %>% as.numeric %>% is_greater_than(5) +#' +#' if (result) +#' message(day %>% paste("is a weekend!")) +#' else +#' message(day %>% paste("is not a weekend!")) +#' +#' invisible(result) +#' } +#' +#' @rdname compound +#' @export +`%<>%` <- pipe() + +#' magrittr tee operator +#' +#' Pipe a value forward into a function- or call expression and return the +#' original value instead of the result. This is useful when an expression +#' is used for its side-effect, say plotting or printing. +#' +#' @param lhs A value or the magrittr placeholder. +#' @param rhs A function call using the magrittr semantics. +#' +#' @details The tee operator works like \code{\link{\%>\%}}, except the +#' return value is \code{lhs} itself, and not the result of \code{rhs} function/expression. +#' +#' @seealso \code{\link{\%>\%}}, \code{\link{\%<>\%}}, \code{\link{\%$\%}} +#' +#' @examples +#' rnorm(200) %>% +#' matrix(ncol = 2) %T>% +#' plot %>% # plot usually does not return anything. +#' colSums +#' +#' @rdname tee +#' @export +`%T>%` <- pipe() + +#' magrittr exposition pipe-operator +#' +#' Expose the names in \code{lhs} to the \code{rhs} expression. This is useful when functions +#' do not have a built-in data argument. +#' +#' @param lhs A list, environment, or a data.frame. +#' @param rhs An expression where the names in lhs is available. +#' +#' @details Some functions, e.g. \code{lm} and \code{aggregate}, have a +#' data argument, which allows the direct use of names inside the data as part +#' of the call. This operator exposes the contents of the left-hand side object +#' to the expression on the right to give a similar benefit, see the examples. + +#' @seealso \code{\link{\%>\%}}, \code{\link{\%<>\%}}, \code{\link{\%$\%}} +#' +#' @examples +#' iris %>% +#' subset(Sepal.Length > mean(Sepal.Length)) %$% +#' cor(Sepal.Length, Sepal.Width) +#' +#' data.frame(z = rnorm(100)) %$% +#' ts.plot(z) +#' +#' @rdname exposition +#' @export +`%$%` <- pipe() diff --git a/R/split_chain.R b/R/split_chain.R new file mode 100644 index 0000000..0775405 --- /dev/null +++ b/R/split_chain.R @@ -0,0 +1,45 @@ +# Split a chain expression into its components. +# +# This function splits a chain of pipe calls into its components: its +# left-hand side, a sequnce of right-hand sides, and the individual pipe +# components. +# +# @param expr a non-evaluated pipe-line expression. +# @param env an environment in which to evaluate rhs parts. +# @return a list with components \code{lhs}, \code{rhss}, and \code{pipes}. +split_chain <- function(expr, env) +{ + # lists for holding the right-hand sides and the pipe operators. + rhss <- list() + pipes <- list() + + # Process the call, splitting it at each valid magrittr pipe operator. + i <- 1L + while(is.call(expr) && is_pipe(expr[[1L]])) { + pipes[[i]] <- expr[[1L]] + rhs <- expr[[3L]] + + if (is_parenthesized(rhs)) + rhs <- eval(rhs, env, env) + + rhss[[i]] <- + if (is_dollar(pipes[[i]]) || is_funexpr(rhs)) + rhs + else if (is_function(rhs)) + prepare_function(rhs) + else if (is_first(rhs)) + prepare_first(rhs) + else + rhs + + # Make sure no anonymous functions without parentheses are used. + if (is.call(rhss[[i]]) && identical(rhss[[i]][[1L]], quote(`function`))) + stop("Anonymous functions myst be parenthesized", call. = FALSE) + + expr <- expr[[2L]] + i <- i + 1L + } + + # return the components; expr will now hold the left-most left-hand side. + list(rhss = rev(rhss), pipes = rev(pipes), lhs = expr) +} diff --git a/R/wrap_function.R b/R/wrap_function.R new file mode 100644 index 0000000..88ad5f0 --- /dev/null +++ b/R/wrap_function.R @@ -0,0 +1,26 @@ +# Wrap an expression in a function +# +# This function takes the "body" part of a function and wraps it in +# a function. The return value depends on whether the function is created +# for its side effect with the tee operator. If the operator is \code{\%$\%} +# then the expression will be evaluated in a \code{with(., )} statement. +# +# @param body an expression which will serve as function body in single-argument +# function with an argument names \code{.} (a dot) +# @param pipe a quoted magrittr pipe, which determines how the function is made. +# @param env The environment in which to contruct the function. + +# @details Currently, the only distinction made is whether the pipe is a tee +# or not. +# +# @return a function of a single argument, named \code{.}. +wrap_function <- function(body, pipe, env) +{ + + if (is_tee(pipe)) { + body <- call("{", body, quote(.)) + } else if (is_dollar(pipe)) { + body <- substitute(with(., b), list(b = body)) + } + eval(call("function", as.pairlist(alist(.=)), body), env, env) +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9e061d5 --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +magrittr - Ceci n'est pas un pipe. +==================================== + +[](https://travis-ci.org/smbache/magrittr) + +# Introduction + +The magrittr package offers a set of operators which promote semantics +that will improve your code by + +* structuring sequences of data operations left-to-right (as opposed to + from the inside and out), +* avoiding nested function calls, +* minimizing the need for local variables and function definitions, and +* making it easy to add steps anywhere in the sequence of operations. + +The operators pipe their left-hand side values forward into expressions that +appear on the right-hand side, i.e. one can replace `f(x)` with +`x %>% f`, where `%>%` is the (main) pipe-operator. When coupling +several function calls with the pipe-operator, the benefit will become +more apparent. Consider this pseudo example + + + the_data <- + read.csv('/path/to/data/file.csv') %>% + subset(variable_a > x) %>% + transform(variable_c = variable_a/variable_b) %>% + head(100) + +Four operations are performed to +arrive at the desired data set, and they are written in a natural order: +the same as the order of execution. Also, no temporary variables are needed. +If yet another operation is required, it is straight-forward to add to the +sequence of operations wherever it may be needed. + +# Installation + +To install the current development version use devtools: + + devtools::install_github("smbache/magrittr") + +To install the CRAN version: + + install.packages("magrittr") + +# Features + +### Basic piping: + + * `x %>% f` is equivalent to `f(x)` + * `x %>% f(y)` is equivalent to `f(x, y)` + * `x %>% f %>% g %>% h` is equivalent to `h(g(f(x)))` + +### The argument placeholder + + * `x %>% f(y, .)` is equivalent to `f(y, x)` + * `x %>% f(y, z = .)` is equivalent to `f(y, z = x)` + +### Re-using the placeholder for attributes + +It is straight-forward to use the placeholder several times +in a right-hand side expression. However, when the placeholder +only appears in a nested expressions magrittr will still apply +the first-argument rule. The reason is that in most cases this +results more clean code. + +`x %>% f(y = nrow(.), z = ncol(.))` is equivalent to + `f(x, y = nrow(x), z = nrow(x))` + +The behavior can be +overruled by enclosing the right-hand side in braces: + +`x %>% {f(y = nrow(.), z = ncol(.))}` is equivalent to + `f(y = nrow(x), z = nrow(x))` + +### More advanced right-hand sides and lambdas +To define a unary function on the fly in the pipeline, enclose the +body of such function in braces, and refer to the argument as +`.`, e.g. + + + iris %>% + { + n <- sample(1:10, size = 1) + H <- head(., n) + T <- tail(., n) + rbind(H, T) + } %>% + summary + +### Building (unary) functions + +Any pipeline starting with the `.` will return a function which can later +be used to apply the pipeline to values. Building functions in magrittr +is therefore similar to building other values. + + + f <- . %>% cos %>% sin + # is equivalent to + f <- function(.) sin(cos(.)) + + +### Tee operations +Some right-hand sides are used for their side effect (e.g. plotting, +printing to a file, etc) and it may be convenient to be able to +subsequently continue the pipeline. The "tee" operator, `%T>%` +can be used for this purpose and works exactly like `%>%`, except it +returns the left-hand side value, rather than the potential result +of the right-hand side operation: + + rnorm(200) %>% + matrix(ncol = 2) %T>% + plot %>% # plot usually does not return anything. + colSums + +### Pipe with exposition of variables +Many functions accept a data argument, e.g. `lm` and `aggregate`, which +is very useful in a pipeline where data is first processed and then passed +into such a function. There are also functions that do not have a data +argument, for which it is useful to expose the variables in the data. +This is done with the `%$%` operator: + + iris %>% + subset(Sepal.Length > mean(Sepal.Length)) %$% + cor(Sepal.Length, Sepal.Width) + + data.frame(z = rnorm(100)) %$% + ts.plot(z) + +### Compound assignment pipe operations +There is also a pipe operator which can be used as shorthand notation +in situations where the left-hand side is being "overwritten": + + iris$Sepal.Length <- + iris$Sepal.Length %>% + sqrt + +To avoid the repetition of the left-hand side immediately after the assignment +operator, use the `%<>%` operator: + + iris$Sepal.Length %<>% sqrt + +This operator works exactly like `%>%`, except the pipeline assigns the result +rather than returning it. It must be the first pipe operator in a longer chain. + +# Further information +For more detail, see the package vignette + + vignette("magrittr") + + + \ No newline at end of file diff --git a/build/vignette.rds b/build/vignette.rds new file mode 100644 index 0000000..e247070 Binary files /dev/null and b/build/vignette.rds differ diff --git a/debian/README.test b/debian/README.test deleted file mode 100644 index 8d70ca3..0000000 --- a/debian/README.test +++ /dev/null @@ -1,9 +0,0 @@ -Notes on how this package can be tested. -──────────────────────────────────────── - -This package can be tested by running the provided test: - -cd tests -LC_ALL=C R --no-save < testthat.R - -in order to confirm its integrity. diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index e91dccf..0000000 --- a/debian/changelog +++ /dev/null @@ -1,18 +0,0 @@ -r-cran-magrittr (1.5-3) unstable; urgency=medium - - * Add missing test dependency r-cran-testthat - - -- Andreas Tille <[email protected]> Fri, 29 Apr 2016 09:52:18 +0200 - -r-cran-magrittr (1.5-2) unstable; urgency=medium - - * Fixed autopkgtest - * cme fix dpkg-control - - -- Andreas Tille <[email protected]> Thu, 28 Apr 2016 11:45:07 +0200 - -r-cran-magrittr (1.5-1) unstable; urgency=medium - - * Initial upload (Closes: #793686) - - -- Andreas Tille <[email protected]> Sun, 26 Jul 2015 17:51:55 +0200 diff --git a/debian/compat b/debian/compat deleted file mode 100644 index ec63514..0000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/debian/control b/debian/control deleted file mode 100644 index 22a12fa..0000000 --- a/debian/control +++ /dev/null @@ -1,24 +0,0 @@ -Source: r-cran-magrittr -Maintainer: Debian Med Packaging Team <[email protected]> -Uploaders: Andreas Tille <[email protected]> -Section: gnu-r -Priority: optional -Build-Depends: debhelper (>= 9), - cdbs, - r-base-dev -Standards-Version: 3.9.8 -Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/R/r-cran-magrittr/ -Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/R/r-cran-magrittr/ -Homepage: http://cran.r-project.org/web/packages/R.oo - -Package: r-cran-magrittr -Architecture: any -Depends: ${shlibs:Depends}, - ${misc:Depends}, - ${R:Depends} -Recommends: r-cran-crayon -Description: GNU R forward-pipe operator - Provides a mechanism for chaining commands with a new forward-pipe - operator, %>%. This operator will forward a value, or the result of an - expression, into the next function call/expression. There is flexible - support for the type of right-hand side expressions. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index 976fb64..0000000 --- a/debian/copyright +++ /dev/null @@ -1,35 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Contact: Stefan Milton Bache <[email protected]> -Upstream-Name: magrittr -Source: http://cran.r-project.org/web/packages/magrittr/ - -Files: * -Copyright: 2007-2015 Stefan Milton Bache <[email protected]> - Hadley Wickham -License: MIT - -Files: debian/* -Copyright: 2015 Andreas Tille <[email protected]> -License: MIT - -License: MIT - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - . - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - . - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - diff --git a/debian/docs b/debian/docs deleted file mode 100644 index 960011c..0000000 --- a/debian/docs +++ /dev/null @@ -1,3 +0,0 @@ -tests -debian/README.test -debian/tests/run-unit-test diff --git a/debian/rules b/debian/rules deleted file mode 100755 index 5000db0..0000000 --- a/debian/rules +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make -f - -include /usr/share/R/debian/r-cran.mk - -install/$(package):: - rm -rf debian/$(package)/usr/lib/R/site-library/$(cranName)/LICENSE diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 163aaf8..0000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/debian/tests/control b/debian/tests/control deleted file mode 100644 index 233afaf..0000000 --- a/debian/tests/control +++ /dev/null @@ -1,3 +0,0 @@ -Tests: run-unit-test -Depends: @, r-cran-testthat, r-cran-crayon -Restrictions: allow-stderr diff --git a/debian/tests/run-unit-test b/debian/tests/run-unit-test deleted file mode 100644 index 3149ed8..0000000 --- a/debian/tests/run-unit-test +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -e - -pkg=r-cran-magrittr -if [ "$ADTTMP" = "" ] ; then - ADTTMP=`mktemp -d /tmp/${pkg}-test.XXXXXX` -fi -cd $ADTTMP -cp -a /usr/share/doc/${pkg}/tests/* $ADTTMP -find . -name "*.gz" -exec gunzip \{\} \; -LC_ALL=C R --no-save < test-all.R -rm -fr $ADTTMP/* diff --git a/debian/watch b/debian/watch deleted file mode 100644 index 94d4138..0000000 --- a/debian/watch +++ /dev/null @@ -1,2 +0,0 @@ -version=3 -http://cran.r-project.org/src/contrib/magrittr_([\d.-]*)\.tar\.gz diff --git a/inst/doc/magrittr.R b/inst/doc/magrittr.R new file mode 100644 index 0000000..61637c3 --- /dev/null +++ b/inst/doc/magrittr.R @@ -0,0 +1,78 @@ +## ----, echo = FALSE, message = FALSE------------------------------------- +library(magrittr) +options(scipen = 3) +knitr::opts_chunk$set( + comment = NA, + error = FALSE, + tidy = FALSE) + +## ------------------------------------------------------------------------ +library(magrittr) + +car_data <- + mtcars %>% + subset(hp > 100) %>% + aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>% + transform(kpl = mpg %>% multiply_by(0.4251)) %>% + print + +## ------------------------------------------------------------------------ +car_data <- + transform(aggregate(. ~ cyl, + data = subset(mtcars, hp > 100), + FUN = function(x) round(mean(x, 2))), + kpl = mpg*0.4251) + +## ----, eval = FALSE------------------------------------------------------ +# car_data %>% +# (function(x) { +# if (nrow(x) > 2) +# rbind(head(x, 1), tail(x, 1)) +# else x +# }) + +## ------------------------------------------------------------------------ +car_data %>% +{ + if (nrow(.) > 0) + rbind(head(., 1), tail(., 1)) + else . +} + +## ------------------------------------------------------------------------ +1:10 %>% (substitute(f(), list(f = sum))) + +## ----, fig.keep='none'--------------------------------------------------- +rnorm(200) %>% +matrix(ncol = 2) %T>% +plot %>% # plot usually does not return anything. +colSums + +## ----, eval = FALSE------------------------------------------------------ +# iris %>% +# subset(Sepal.Length > mean(Sepal.Length)) %$% +# cor(Sepal.Length, Sepal.Width) +# +# data.frame(z = rnorm(100)) %$% +# ts.plot(z) + +## ----, eval = FALSE------------------------------------------------------ +# iris$Sepal.Length %<>% sqrt + +## ------------------------------------------------------------------------ +rnorm(1000) %>% +multiply_by(5) %>% +add(5) %>% +{ + cat("Mean:", mean(.), + "Variance:", var(.), "\n") + head(.) +} + +## ----, results = 'hide'-------------------------------------------------- +rnorm(100) %>% `*`(5) %>% `+`(5) %>% +{ + cat("Mean:", mean(.), "Variance:", var(.), "\n") + head(.) +} + diff --git a/inst/doc/magrittr.Rmd b/inst/doc/magrittr.Rmd new file mode 100644 index 0000000..919ec22 --- /dev/null +++ b/inst/doc/magrittr.Rmd @@ -0,0 +1,212 @@ +<!-- +%\VignetteEngine{knitr} +%\VignetteIndexEntry{Introducing magrittr} +--> + +```{r, echo = FALSE, message = FALSE} +library(magrittr) +options(scipen = 3) +knitr::opts_chunk$set( + comment = NA, + error = FALSE, + tidy = FALSE) +``` + + + +*This version: November, 2014. Stefan Milton Bache* + +# Abstract + +*The magrittr* (to be pronounced with a sophisticated french accent) is +a package with two aims: to decrease development time and to improve +readability and maintainability of code. Or even shortr: to make your code smokin' (puff puff)! + +To archive its humble aims, *magrittr* (remember the accent) provides a new +"pipe"-like operator, `%>%`, with which you may pipe a value forward into an +expression or function call; something along the lines of ` x %>% f `, rather +than ` f(x)`. This is not an unknown feature +elsewhere; a prime example is the `|>` operator used extensively in `F#` +(to say the least) and indeed this -- along with Unix pipes -- served as a +motivation for developing the magrittr package. + +This vignette describes the main features of *magrittr* and demonstrates +some features which has been added since the initial release. + +# Introduction and basics + +At first encounter, you may wonder whether an operator such as `%>%` can really +be all that beneficial; but as you may notice, it semantically changes your +code in a way that makes it more intuitive to both read and write. + +Consider the following example, in which the `mtcars` dataset shipped with +R is munged a little. +```{r} +library(magrittr) + +car_data <- + mtcars %>% + subset(hp > 100) %>% + aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>% + transform(kpl = mpg %>% multiply_by(0.4251)) %>% + print +``` +We start with a value, here `mtcars` (a `data.frame`). Based on this, we +first extract a subset, then we aggregate the information based on the number +of cylinders, and then we transform the dataset by adding a variable +for kilometers per liter as supplement to miles per gallon. Finally we print +the result before assigning it. +Note how the code is arranged in the logical +order of how you think about the task: data->transform->aggregate, which +is also the same order as the code will execute. It's like a recipe -- easy to +read, easy to follow! + +A horrific alternative would be to write +```{r} +car_data <- + transform(aggregate(. ~ cyl, + data = subset(mtcars, hp > 100), + FUN = function(x) round(mean(x, 2))), + kpl = mpg*0.4251) +``` +There is a lot more clutter with parentheses, and the mental task of deciphering +the code is more challenging---in particular if you did not write it yourself. + +Note also how "building" a function on the fly for use in `aggregate` is very +simple in *magrittr*: rather than an actual value as left-hand side in +pipeline, just use the placeholder. This is also very useful in R's +`*apply` family of functions. + +Granted: you may make the second example better, perhaps throw in a few temporary +variables (which is often avoided to some degree when using *magrittr*), +but one often sees cluttered lines like the ones presented. + +And here is another selling point. Suppose I want to quickly want to add +another step somewhere in the process. This is very easy in the +to do in the pipeline version, but a little more challenging in the +"standard" example. + +The combined example shows a few neat features of the pipe (which it is not): + +1. By default the left-hand side (LHS) will be *piped in* as the first argument of +the function appearing on the right-hand side (RHS). This is the case in the +`subset` and `transform` expressions. +2. `%>%` may be used in a nested fashion, e.g. it may appear in expressions within +arguments. This is used in the `mpg` to `kpl` conversion. +3. When the LHS is needed at a position other than the first, one can use +the dot,`'.'`, as placeholder. This is used in the `aggregate` expression. +4. The dot in e.g. a formula is *not* confused with a placeholder, which is +utilized in the `aggregate` expression. +5. Whenever only *one* argument is needed, the LHS, then one can omit the +empty parentheses. This is used in the call to `print` (which also returns its +argument). Here, `LHS %>% print()`, or even `LHS %>% print(.)` would also work. +6. A pipeline with a dot (`.`) as LHS will create a unary function. This is +used to define the aggregator function. + +One feature, which was not utilized above is piping into *anonymous functions*, +or *lambdas*. This is possible using standard function definitions, e.g. +```{r, eval = FALSE} +car_data %>% +(function(x) { + if (nrow(x) > 2) + rbind(head(x, 1), tail(x, 1)) + else x +}) +``` +However, *magrittr* also allows a short-hand notation: +```{r} +car_data %>% +{ + if (nrow(.) > 0) + rbind(head(., 1), tail(., 1)) + else . +} +``` +Since all right-hand sides are really "body expressions" of unary functions, this +is only the natural extension the simple right-hand side expressions. Of course +longer and more complex functions can be made using this approach. + +In the first example the anonymous function is enclosed in parentheses. +Whenever you want to use a function- or call-generating statement as right-hand side, +parentheses are used to evaluate the right-hand side before piping takes place. + +Another, less useful example is: +```{r} +1:10 %>% (substitute(f(), list(f = sum))) +``` + +# Additional pipe operators +*magrittr* also provides three related pipe operators. These are not as +common as `%>%` but they become useful in special cases. + +The "tee" operator, `%T>%` works like `%>%`, except it returns the left-hand +side value, and not the result of the right-hand side operation. +This is useful when a step in a pipeline is used for its side-effect (printing, +plotting, logging, etc.). As an example (where the actual plot is omitted here): +```{r, fig.keep='none'} +rnorm(200) %>% +matrix(ncol = 2) %T>% +plot %>% # plot usually does not return anything. +colSums +``` + +The "exposition" pipe operator, `%$%` exposes the names within the left-hand side +object to the right-hand side expression. Essentially, it is a short-hand for +using the `with` functions (and the same left-hand side objects are accepted). +This operator is handy when functions do not themselves have a data argument, as for +example `lm` and `aggregate` do. Here are a few examples as illustration: + +```{r, eval = FALSE} +iris %>% + subset(Sepal.Length > mean(Sepal.Length)) %$% + cor(Sepal.Length, Sepal.Width) + +data.frame(z = rnorm(100)) %$% + ts.plot(z) +``` + +Finally, the compound assignment pipe operator `%<>%` can be used as the first pipe +in a chain. The effect will be that the result of the pipeline is assigned to the +left-hand side object, rather than returning the result as usual. It is essentially +shorthand notation for expressions like `foo <- foo %>% bar %>% baz`, which +boils down to `foo %<>% bar %>% baz`. Another example is + +```{r, eval = FALSE} +iris$Sepal.Length %<>% sqrt +``` + +The `%<>%` can be used whenever `expr <- ...` makes sense, e.g. + +* `x %<>% foo %>% bar` +* `x[1:10] %<>% foo %>% bar` +* `x$baz %<>% foo %>% bar` + +# Aliases + +In addition to the `%>%`-operator, *magrittr* provides some aliases for other +operators which make operations such as addition or multiplication fit well +into the *magrittr*-syntax. As an example, consider: +```{r} +rnorm(1000) %>% +multiply_by(5) %>% +add(5) %>% +{ + cat("Mean:", mean(.), + "Variance:", var(.), "\n") + head(.) +} +``` +which could be written in more compact form as +```{r, results = 'hide'} +rnorm(100) %>% `*`(5) %>% `+`(5) %>% +{ + cat("Mean:", mean(.), "Variance:", var(.), "\n") + head(.) +} +``` +To see a list of the aliases, execute e.g. `?multiply_by`. + +# Development +The *magrittr* package is also available in a development version at the +GitHub development page: +[github.com/smbache/magrittr](http://github.com/smbache/magrittr). diff --git a/inst/doc/magrittr.html b/inst/doc/magrittr.html new file mode 100644 index 0000000..c743d7d --- /dev/null +++ b/inst/doc/magrittr.html @@ -0,0 +1,443 @@ +<!DOCTYPE html> +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + +<title>Abstract</title> + +<script type="text/javascript"> +window.onload = function() { + var imgs = document.getElementsByTagName('img'), i, img; + for (i = 0; i < imgs.length; i++) { + img = imgs[i]; + // center an image if it is the only element of its parent + if (img.parentElement.childElementCount === 1) + img.parentElement.style.textAlign = 'center'; + } +}; +</script> + +<!-- Styles for R syntax highlighter --> +<style type="text/css"> + pre .operator, + pre .paren { + color: rgb(104, 118, 135) + } + + pre .literal { + color: #990073 + } + + pre .number { + color: #099; + } + + pre .comment { + color: #998; + font-style: italic + } + + pre .keyword { + color: #900; + font-weight: bold + } + + pre .identifier { + color: rgb(0, 0, 0); + } + + pre .string { + color: #d14; + } +</style> + +<!-- R syntax highlighter --> +<script type="text/javascript"> +var hljs=new function(){function m(p){return p.replace(/&/gm,"&").replace(/</gm,"<")}function f(r,q,p){return RegExp(q,"m"+(r.cI?"i":"")+(p?"g":""))}function b(r){for(var p=0;p<r.childNodes.length;p++){var q=r.childNodes[p];if(q.nodeName=="CODE"){return q}if(!(q.nodeType==3&&q.nodeValue.match(/\s+/))){break}}}function h(t,s){var p="";for(var r=0;r<t.childNodes.length;r++){if(t.childNodes[r].nodeType==3){var q=t.childNodes[r].nodeValue;if(s){q=q.replace(/\n/g,"")}p+=q}else{if(t.chi [...] +hljs.initHighlightingOnLoad(); +</script> + + + +<style type="text/css"> +body, td { + font-family: sans-serif; + background-color: white; + font-size: 13px; +} + +body { + max-width: 800px; + margin: auto; + padding: 1em; + line-height: 20px; +} + +tt, code, pre { + font-family: 'DejaVu Sans Mono', 'Droid Sans Mono', 'Lucida Console', Consolas, Monaco, monospace; +} + +h1 { + font-size:2.2em; +} + +h2 { + font-size:1.8em; +} + +h3 { + font-size:1.4em; +} + +h4 { + font-size:1.0em; +} + +h5 { + font-size:0.9em; +} + +h6 { + font-size:0.8em; +} + +a:visited { + color: rgb(50%, 0%, 50%); +} + +pre, img { + max-width: 100%; +} +pre { + overflow-x: auto; +} +pre code { + display: block; padding: 0.5em; +} + +code { + font-size: 92%; + border: 1px solid #ccc; +} + +code[class] { + background-color: #F8F8F8; +} + +table, td, th { + border: none; +} + +blockquote { + color:#666666; + margin:0; + padding-left: 1em; + border-left: 0.5em #EEE solid; +} + +hr { + height: 0px; + border-bottom: none; + border-top-width: thin; + border-top-style: dotted; + border-top-color: #999999; +} + +@media print { + * { + background: transparent !important; + color: black !important; + filter:none !important; + -ms-filter: none !important; + } + + body { + font-size:12pt; + max-width:100%; + } + + a, a:visited { + text-decoration: underline; + } + + hr { + visibility: hidden; + page-break-before: always; + } + + pre, blockquote { + padding-right: 1em; + page-break-inside: avoid; + } + + tr, img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + @page :left { + margin: 15mm 20mm 15mm 10mm; + } + + @page :right { + margin: 15mm 10mm 15mm 20mm; + } + + p, h2, h3 { + orphans: 3; widows: 3; + } + + h2, h3 { + page-break-after: avoid; + } +} +</style> + + + +</head> + +<body> +<!-- +%\VignetteEngine{knitr} +%\VignetteIndexEntry{Introducing magrittr} +--> + +<p><img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAVUDIAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1 [...] + +<p><em>This version: November, 2014. Stefan Milton Bache</em> </p> + +<h1>Abstract</h1> + +<p><em>The magrittr</em> (to be pronounced with a sophisticated french accent) is +a package with two aims: to decrease development time and to improve +readability and maintainability of code. Or even shortr: to make your code smokin' (puff puff)!</p> + +<p>To archive its humble aims, <em>magrittr</em> (remember the accent) provides a new +“pipe”-like operator, <code>%>%</code>, with which you may pipe a value forward into an +expression or function call; something along the lines of <code>x %>% f</code>, rather +than <code>f(x)</code>. This is not an unknown feature +elsewhere; a prime example is the <code>|></code> operator used extensively in <code>F#</code> +(to say the least) and indeed this – along with Unix pipes – served as a +motivation for developing the magrittr package.</p> + +<p>This vignette describes the main features of <em>magrittr</em> and demonstrates +some features which has been added since the initial release.</p> + +<h1>Introduction and basics</h1> + +<p>At first encounter, you may wonder whether an operator such as <code>%>%</code> can really +be all that beneficial; but as you may notice, it semantically changes your +code in a way that makes it more intuitive to both read and write.</p> + +<p>Consider the following example, in which the <code>mtcars</code> dataset shipped with +R is munged a little.</p> + +<pre><code class="r">library(magrittr) + +car_data <- + mtcars %>% + subset(hp > 100) %>% + aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>% + transform(kpl = mpg %>% multiply_by(0.4251)) %>% + print +</code></pre> + +<pre><code> cyl mpg disp hp drat wt qsec vs am gear carb kpl +1 4 25.90 108.0 111.0 3.94 2.15 17.75 1.00 1.00 4.50 2.00 11.010 +2 6 19.74 183.3 122.3 3.59 3.12 17.98 0.57 0.43 3.86 3.43 8.391 +3 8 15.10 353.1 209.2 3.23 4.00 16.77 0.00 0.14 3.29 3.50 6.419 +</code></pre> + +<p>We start with a value, here <code>mtcars</code> (a <code>data.frame</code>). Based on this, we +first extract a subset, then we aggregate the information based on the number +of cylinders, and then we transform the dataset by adding a variable +for kilometers per liter as supplement to miles per gallon. Finally we print +the result before assigning it. +Note how the code is arranged in the logical +order of how you think about the task: data->transform->aggregate, which +is also the same order as the code will execute. It's like a recipe – easy to +read, easy to follow!</p> + +<p>A horrific alternative would be to write</p> + +<pre><code class="r">car_data <- + transform(aggregate(. ~ cyl, + data = subset(mtcars, hp > 100), + FUN = function(x) round(mean(x, 2))), + kpl = mpg*0.4251) +</code></pre> + +<p>There is a lot more clutter with parentheses, and the mental task of deciphering +the code is more challenging—in particular if you did not write it yourself.</p> + +<p>Note also how “building” a function on the fly for use in <code>aggregate</code> is very +simple in <em>magrittr</em>: rather than an actual value as left-hand side in +pipeline, just use the placeholder. This is also very useful in R's +<code>*apply</code> family of functions.</p> + +<p>Granted: you may make the second example better, perhaps throw in a few temporary +variables (which is often avoided to some degree when using <em>magrittr</em>), +but one often sees cluttered lines like the ones presented. </p> + +<p>And here is another selling point. Suppose I want to quickly want to add +another step somewhere in the process. This is very easy in the +to do in the pipeline version, but a little more challenging in the +“standard” example.</p> + +<p>The combined example shows a few neat features of the pipe (which it is not):</p> + +<ol> +<li>By default the left-hand side (LHS) will be <em>piped in</em> as the first argument of +the function appearing on the right-hand side (RHS). This is the case in the +<code>subset</code> and <code>transform</code> expressions.</li> +<li><code>%>%</code> may be used in a nested fashion, e.g. it may appear in expressions within +arguments. This is used in the <code>mpg</code> to <code>kpl</code> conversion.</li> +<li>When the LHS is needed at a position other than the first, one can use +the dot,<code>'.'</code>, as placeholder. This is used in the <code>aggregate</code> expression.</li> +<li>The dot in e.g. a formula is <em>not</em> confused with a placeholder, which is +utilized in the <code>aggregate</code> expression.</li> +<li>Whenever only <em>one</em> argument is needed, the LHS, then one can omit the +empty parentheses. This is used in the call to <code>print</code> (which also returns its +argument). Here, <code>LHS %>% print()</code>, or even <code>LHS %>% print(.)</code> would also work.</li> +<li>A pipeline with a dot (<code>.</code>) as LHS will create a unary function. This is +used to define the aggregator function.</li> +</ol> + +<p>One feature, which was not utilized above is piping into <em>anonymous functions</em>, +or <em>lambdas</em>. This is possible using standard function definitions, e.g.</p> + +<pre><code class="r">car_data %>% +(function(x) { + if (nrow(x) > 2) + rbind(head(x, 1), tail(x, 1)) + else x +}) +</code></pre> + +<p>However, <em>magrittr</em> also allows a short-hand notation:</p> + +<pre><code class="r">car_data %>% +{ + if (nrow(.) > 0) + rbind(head(., 1), tail(., 1)) + else . +} +</code></pre> + +<pre><code> cyl mpg disp hp drat wt qsec vs am gear carb kpl +1 4 26 108 111 4 2 18 1 1 4 2 11.053 +3 8 15 350 192 3 4 17 0 0 3 4 6.377 +</code></pre> + +<p>Since all right-hand sides are really “body expressions” of unary functions, this +is only the natural extension the simple right-hand side expressions. Of course +longer and more complex functions can be made using this approach.</p> + +<p>In the first example the anonymous function is enclosed in parentheses. +Whenever you want to use a function- or call-generating statement as right-hand side, +parentheses are used to evaluate the right-hand side before piping takes place.</p> + +<p>Another, less useful example is:</p> + +<pre><code class="r">1:10 %>% (substitute(f(), list(f = sum))) +</code></pre> + +<pre><code>[1] 55 +</code></pre> + +<h1>Additional pipe operators</h1> + +<p><em>magrittr</em> also provides three related pipe operators. These are not as +common as <code>%>%</code> but they become useful in special cases. </p> + +<p>The “tee” operator, <code>%T>%</code> works like <code>%>%</code>, except it returns the left-hand +side value, and not the result of the right-hand side operation. +This is useful when a step in a pipeline is used for its side-effect (printing, +plotting, logging, etc.). As an example (where the actual plot is omitted here):</p> + +<pre><code class="r">rnorm(200) %>% +matrix(ncol = 2) %T>% +plot %>% # plot usually does not return anything. +colSums +</code></pre> + +<pre><code>[1] 6.916 -1.605 +</code></pre> + +<p>The “exposition” pipe operator, <code>%$%</code> exposes the names within the left-hand side +object to the right-hand side expression. Essentially, it is a short-hand for +using the <code>with</code> functions (and the same left-hand side objects are accepted). +This operator is handy when functions do not themselves have a data argument, as for +example <code>lm</code> and <code>aggregate</code> do. Here are a few examples as illustration:</p> + +<pre><code class="r">iris %>% + subset(Sepal.Length > mean(Sepal.Length)) %$% + cor(Sepal.Length, Sepal.Width) + +data.frame(z = rnorm(100)) %$% + ts.plot(z) +</code></pre> + +<p>Finally, the compound assignment pipe operator <code>%<>%</code> can be used as the first pipe +in a chain. The effect will be that the result of the pipeline is assigned to the +left-hand side object, rather than returning the result as usual. It is essentially +shorthand notation for expressions like <code>foo <- foo %>% bar %>% baz</code>, which +boils down to <code>foo %<>% bar %>% baz</code>. Another example is</p> + +<pre><code class="r">iris$Sepal.Length %<>% sqrt +</code></pre> + +<p>The <code>%<>%</code> can be used whenever <code>expr <- ...</code> makes sense, e.g. </p> + +<ul> +<li><code>x %<>% foo %>% bar</code></li> +<li><code>x[1:10] %<>% foo %>% bar</code></li> +<li><code>x$baz %<>% foo %>% bar</code></li> +</ul> + +<h1>Aliases</h1> + +<p>In addition to the <code>%>%</code>-operator, <em>magrittr</em> provides some aliases for other +operators which make operations such as addition or multiplication fit well +into the <em>magrittr</em>-syntax. As an example, consider:</p> + +<pre><code class="r">rnorm(1000) %>% +multiply_by(5) %>% +add(5) %>% +{ + cat("Mean:", mean(.), + "Variance:", var(.), "\n") + head(.) +} +</code></pre> + +<pre><code>Mean: 5.04 Variance: 23.25 +</code></pre> + +<pre><code>[1] 10.019 -10.231 -3.341 -4.997 -1.728 8.690 +</code></pre> + +<p>which could be written in more compact form as</p> + +<pre><code class="r">rnorm(100) %>% `*`(5) %>% `+`(5) %>% +{ + cat("Mean:", mean(.), "Variance:", var(.), "\n") + head(.) +} +</code></pre> + +<p>To see a list of the aliases, execute e.g. <code>?multiply_by</code>. </p> + +<h1>Development</h1> + +<p>The <em>magrittr</em> package is also available in a development version at the +GitHub development page: +<a href="http://github.com/smbache/magrittr">github.com/smbache/magrittr</a>.</p> + +</body> + +</html> diff --git a/man/aliases.Rd b/man/aliases.Rd new file mode 100644 index 0000000..940693d --- /dev/null +++ b/man/aliases.Rd @@ -0,0 +1,83 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{extract} +\alias{add} +\alias{and} +\alias{divide_by} +\alias{divide_by_int} +\alias{equals} +\alias{extract} +\alias{extract2} +\alias{inset} +\alias{inset2} +\alias{is_greater_than} +\alias{is_in} +\alias{is_less_than} +\alias{is_weakly_greater_than} +\alias{is_weakly_less_than} +\alias{mod} +\alias{multiply_by} +\alias{multiply_by_matrix} +\alias{n'est pas} +\alias{not} +\alias{or} +\alias{raise_to_power} +\alias{set_colnames} +\alias{set_names} +\alias{set_rownames} +\alias{subtract} +\alias{use_series} +\title{Aliases} +\description{ +magrittr provides a series of aliases which can be more pleasant to use +when composing chains using the \code{\%>\%} operator. +} +\details{ +Currently implemented aliases are +\tabular{ll}{ +\code{extract} \tab \code{`[`} \cr +\code{extract2} \tab \code{`[[`} \cr +\code{inset} \tab \code{`[<-`} \cr +\code{inset2} \tab \code{`[[<-`} \cr +\code{use_series} \tab \code{`$`} \cr +\code{add} \tab \code{`+`} \cr +\code{subtract} \tab \code{`-`} \cr +\code{multiply_by} \tab \code{`*`} \cr +\code{raise_to_power} \tab \code{`^`} \cr +\code{multiply_by_matrix} \tab \code{`\%*\%`} \cr +\code{divide_by} \tab \code{`/`} \cr +\code{divide_by_int} \tab \code{`\%/\%`} \cr +\code{mod} \tab \code{`\%\%`} \cr +\code{is_in} \tab \code{`\%in\%`} \cr +\code{and} \tab \code{`&`} \cr +\code{or} \tab \code{`|`} \cr +\code{equals} \tab \code{`==`} \cr +\code{is_greater_than} \tab \code{`>`} \cr +\code{is_weakly_greater_than} \tab \code{`>=`} \cr +\code{is_less_than} \tab \code{`<`} \cr +\code{is_weakly_less_than} \tab \code{`<=`} \cr +\code{not} (\code{`n'est pas`}) \tab \code{`!`} \cr +\code{set_colnames} \tab \code{`colnames<-`} \cr +\code{set_rownames} \tab \code{`rownames<-`} \cr +\code{set_names} \tab \code{`names<-`} \cr +} +} +\examples{ +iris \%>\% + extract(, 1:4) \%>\% + head + +good.times <- + Sys.Date() \%>\% + as.POSIXct \%>\% + seq(by = "15 mins", length.out = 100) \%>\% + data.frame(timestamp = .) + +good.times$quarter <- + good.times \%>\% + use_series(timestamp) \%>\% + format("\%M") \%>\% + as.numeric \%>\% + divide_by_int(15) \%>\% + add(1) +} + diff --git a/man/compound.Rd b/man/compound.Rd new file mode 100644 index 0000000..4928983 --- /dev/null +++ b/man/compound.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{\%<>\%} +\alias{\%<>\%} +\title{magrittr compound assignment pipe-operator} +\usage{ +lhs \%<>\% rhs +} +\arguments{ +\item{lhs}{An object which serves both as the initial value and as target.} + +\item{rhs}{a function call using the magrittr semantics.} +} +\description{ +Pipe an object forward into a function or call expression and update the +\code{lhs} object with the resulting value. +} +\details{ +The compound assignment pipe-operator, \code{\%<>\%}, is used to +update a value by first piping it into one or more \code{rhs} expressions, and +then assigning the result. For example, \code{some_object \%<>\% +foo \%>\% bar} is equivalent to \code{some_object <- some_object \%>\% foo +\%>\% bar}. It must be the first pipe-operator in a chain, but otherwise it +works like \code{\link{\%>\%}}. +} +\examples{ +iris$Sepal.Length \%<>\% sqrt + +x <- rnorm(100) + +x \%<>\% abs \%>\% sort + +is_weekend <- function(day) +{ + # day could be e.g. character a valid representation + day \%<>\% as.Date + + result <- day \%>\% format("\%u") \%>\% as.numeric \%>\% is_greater_than(5) + + if (result) + message(day \%>\% paste("is a weekend!")) + else + message(day \%>\% paste("is not a weekend!")) + + invisible(result) +} +} +\seealso{ +\code{\link{\%>\%}}, \code{\link{\%T>\%}}, \code{\link{\%$\%}} +} + diff --git a/man/debug_fseq.Rd b/man/debug_fseq.Rd new file mode 100644 index 0000000..a94ce80 --- /dev/null +++ b/man/debug_fseq.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{debug_fseq} +\alias{debug_fseq} +\alias{undebug_fseq} +\title{Debugging function for functional sequences.} +\usage{ +debug_fseq(fseq, ...) + +undebug_fseq(fseq) +} +\arguments{ +\item{fseq}{a functional sequence.} + +\item{...}{indices of functions to debug.} +} +\value{ +\code{invisible(NULL)}. +} +\description{ +This is a utility function for marking functions in a functional +sequence for debbuging. +} + diff --git a/man/debug_pipe.Rd b/man/debug_pipe.Rd new file mode 100644 index 0000000..497ad51 --- /dev/null +++ b/man/debug_pipe.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{debug_pipe} +\alias{debug_pipe} +\title{Debugging function for magrittr pipelines.} +\usage{ +debug_pipe(x) +} +\arguments{ +\item{x}{a value} +} +\value{ +x +} +\description{ +This function is a wrapper around \code{browser}, which makes it +easier to debug at certain places in a magrittr pipe chain. +} + diff --git a/man/exposition.Rd b/man/exposition.Rd new file mode 100644 index 0000000..9043d6d --- /dev/null +++ b/man/exposition.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{\%$\%} +\alias{\%$\%} +\title{magrittr exposition pipe-operator} +\usage{ +lhs \%$\% rhs +} +\arguments{ +\item{lhs}{A list, environment, or a data.frame.} + +\item{rhs}{An expression where the names in lhs is available.} +} +\description{ +Expose the names in \code{lhs} to the \code{rhs} expression. This is useful when functions +do not have a built-in data argument. +} +\details{ +Some functions, e.g. \code{lm} and \code{aggregate}, have a +data argument, which allows the direct use of names inside the data as part +of the call. This operator exposes the contents of the left-hand side object +to the expression on the right to give a similar benefit, see the examples. +} +\examples{ +iris \%>\% + subset(Sepal.Length > mean(Sepal.Length)) \%$\% + cor(Sepal.Length, Sepal.Width) + +data.frame(z = rnorm(100)) \%$\% + ts.plot(z) +} +\seealso{ +\code{\link{\%>\%}}, \code{\link{\%<>\%}}, \code{\link{\%$\%}} +} + diff --git a/man/freduce.Rd b/man/freduce.Rd new file mode 100644 index 0000000..7754b40 --- /dev/null +++ b/man/freduce.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{freduce} +\alias{freduce} +\title{Apply a list of functions sequentially} +\usage{ +freduce(value, function_list) +} +\arguments{ +\item{value}{initial value.} + +\item{function_list}{a list of functions.} +} +\value{ +The result after applying each function in turn. +} +\description{ +This function applies the first function to \code{value}, then the +next function to the result of the previous function call, etc. +} + diff --git a/man/fseq.Rd b/man/fseq.Rd new file mode 100644 index 0000000..7a7362a --- /dev/null +++ b/man/fseq.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{[[.fseq} +\alias{[.fseq} +\alias{[[.fseq} +\title{Extract function(s) from a functional sequence.} +\usage{ +\method{[[}{fseq}(x, ...) + +\method{[}{fseq}(x, ...) +} +\arguments{ +\item{x}{A functional sequence} + +\item{...}{index/indices. For double brackets, the index must be of length 1.} +} +\value{ +A function or functional sequence. +} +\description{ +Functional sequences can be subset using single or double brackets. +A single-bracket subset results in a new functional sequence, and +a double-bracket subset results in a single function. +} + diff --git a/man/functions.Rd b/man/functions.Rd new file mode 100644 index 0000000..52f5761 --- /dev/null +++ b/man/functions.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{functions} +\alias{functions} +\title{Extract the function list from a functional sequence.} +\usage{ +functions(fseq) +} +\arguments{ +\item{fseq}{A functional sequence ala magrittr.} +} +\value{ +a list of functions +} +\description{ +This can be used to extract the list of functions inside a functional +sequence created with a chain like \code{. \%>\% foo \%>\% bar}. +} + diff --git a/man/magrittr.Rd b/man/magrittr.Rd new file mode 100644 index 0000000..facffc2 --- /dev/null +++ b/man/magrittr.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\docType{package} +\name{magrittr} +\alias{magrittr} +\alias{magrittr-package} +\title{magrittr - Ceci n'est pas un pipe} +\description{ +The magrittr package offers a set of operators which promote semantics +that will improve your code by +\itemize{ + \item structuring sequences of data operations left-to-right + (as opposed to from the inside and out), + \item avoiding nested function calls, + \item minimizing the need for local variables and function definitions, and + \item making it easy to add steps anywhere in the sequence of operations. +} +The operators pipe their left-hand side values forward into expressions that +appear on the right-hand side, i.e. one can replace \code{f(x)} with +\code{x \%>\% f}, where \code{\%>\%} is the (main) pipe-operator. +\cr\cr +Consider the example below. Four operations are performed to +arrive at the desired data set, and they are written in a natural order: +the same as the order of execution. Also, no temporary variables are needed. +If yet another operation is required, it is straight-forward to add to the +sequence of operations whereever it may be needed. +\cr\cr +For a more detailed introduction see the vignette +(\code{vignette("magrittr")}) or the documentation pages for the +available operators:\cr +\tabular{ll}{ + \code{\link{\%>\%}} \tab forward-pipe operator.\cr + \code{\link{\%T>\%}} \tab tee operator.\cr + \code{\link{\%<>\%}} \tab compound assignment pipe-operator.\cr + \code{\link{\%$\%}} \tab exposition pipe-operator.\cr +} +} +\examples{ +\dontrun{ + +the_data <- + read.csv('/path/to/data/file.csv') \%>\% + subset(variable_a > x) \%>\% + transform(variable_c = variable_a/veraiable_b) \%>\% + head(100) +} +} + diff --git a/man/pipe.Rd b/man/pipe.Rd new file mode 100644 index 0000000..2fe129d --- /dev/null +++ b/man/pipe.Rd @@ -0,0 +1,125 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{\%>\%} +\alias{\%>\%} +\title{magrittr forward-pipe operator} +\usage{ +lhs \%>\% rhs +} +\arguments{ +\item{lhs}{A value or the magrittr placeholder.} + +\item{rhs}{A function call using the magrittr semantics.} +} +\description{ +Pipe an object forward into a function or call expression. +} +\details{ +\bold{Using \code{\%>\%} with unary function calls}\cr +When functions require only one argument, \code{x \%>\% f} is equivalent +to \code{f(x)} (not exactly equivalent; see technical note below.) +\cr\cr +\bold{Placing \code{lhs} as the first argument in \code{rhs} call}\cr +The default behavior of \code{\%>\%} when multiple arguments are required +in the \code{rhs} call, is to place \code{lhs} as the first argument, i.e. +\code{x \%>\% f(y)} is equivalent to \code{f(x, y)}. +\cr\cr +\bold{Placing \code{lhs} elsewhere in \code{rhs} call}\cr +Often you will want \code{lhs} to the \code{rhs} call at another position than the first. +For this purpose you can use the dot (\code{.}) as placeholder. For example, +\code{y \%>\% f(x, .)} is equivalent to \code{f(x, y)} and +\code{z \%>\% f(x, y, arg = .)} is equivalent to \code{f(x, y, arg = z)}. +\cr\cr +\bold{Using the dot for secondary purposes}\cr +Often, some attribute or property of \code{lhs} is desired in the \code{rhs} call in +addition to the value of \code{lhs} itself, e.g. the number of rows or columns. +It is perfectly valid to use the dot placeholder several times in the \code{rhs} +call, but by design the behavior is slightly different when using it inside +nested function calls. In particular, if the placeholder is only used +in a nested function call, \code{lhs} will also be placed as the first argument! +The reason for this is that in most use-cases this produces the most readable +code. For example, \code{iris \%>\% subset(1:nrow(.) \%\% 2 == 0)} is +equivalent to \code{iris \%>\% subset(., 1:nrow(.) \%\% 2 == 0)} but +slightly more compact. It is possible to overrule this behavior by enclosing +the \code{rhs} in braces. For example, \code{1:10 \%>\% {c(min(.), max(.))}} is +equivalent to \code{c(min(1:10), max(1:10))}. +\cr\cr +\bold{Using \%>\% with call- or function-producing \code{rhs}}\cr +It is possible to force evaluation of \code{rhs} before the piping of \code{lhs} takes +place. This is useful when \code{rhs} produces the relevant call or function. +To evaluate \code{rhs} first, enclose it in parentheses, i.e. +\code{a \%>\% (function(x) x^2)}, and \code{1:10 \%>\% (call("sum"))}. +Another example where this is relevant is for reference class methods +which are accessed using the \code{$} operator, where one would do +\code{x \%>\% (rc$f)}, and not \code{x \%>\% rc$f}. +\cr\cr +\bold{Using lambda expressions with \code{\%>\%}}\cr +Each \code{rhs} is essentially a one-expression body of a unary function. +Therefore defining lambdas in magrittr is very natural, and as +the definitions of regular functions: if more than a single expression +is needed one encloses the body in a pair of braces, \code{\{ rhs \}}. +However, note that within braces there are no "first-argument rule": +it will be exactly like writing a unary function where the argument name is +"\code{.}" (the dot). +\cr\cr +\bold{Using the dot-place holder as \code{lhs}}\cr +When the dot is used as \code{lhs}, the result will be a functional sequence, +i.e. a function which applies the entire chain of right-hand sides in turn +to its input. See the examples. +} +\section{Technical notes}{ + +The magrittr pipe operators use non-standard evaluation. They capture +their inputs and examines them to figure out how to proceed. First a function +is produced from all of the individual right-hand side expressions, and +then the result is obtained by applying this function to the left-hand side. +For most purposes, one can disregard the subtle aspects of magrittr's +evaluation, but some functions may capture their calling environment, +and thus using the operators will not be exactly equivalent to the +"standard call" without pipe-operators. +\cr\cr +Another note is that special attention is advised when using non-magrittr +operators in a pipe-chain (\code{+, -, $,} etc.), as operator precedence will impact how the +chain is evaluated. In general it is advised to use the aliases provided +by magrittr. +} +\examples{ +# Basic use: +iris \%>\% head + +# Use with lhs as first argument +iris \%>\% head(10) + +# Using the dot place-holder +"Ceci n'est pas une pipe" \%>\% gsub("une", "un", .) + +# When dot is nested, lhs is still placed first: +sample(1:10) \%>\% paste0(LETTERS[.]) + +# This can be avoided: +rnorm(100) \%>\% {c(min(.), mean(.), max(.))} \%>\% floor + +# Lambda expressions: +iris \%>\% +{ + size <- sample(1:10, size = 1) + rbind(head(., size), tail(., size)) +} + +# renaming in lambdas: +iris \%>\% +{ + my_data <- . + size <- sample(1:10, size = 1) + rbind(head(my_data, size), tail(my_data, size)) +} + +# Building unary functions with \%>\% +trig_fest <- . \%>\% tan \%>\% cos \%>\% sin + +1:10 \%>\% trig_fest +trig_fest(1:10) +} +\seealso{ +\code{\link{\%<>\%}}, \code{\link{\%T>\%}}, \code{\link{\%$\%}} +} + diff --git a/man/print.fseq.Rd b/man/print.fseq.Rd new file mode 100644 index 0000000..ec7eb08 --- /dev/null +++ b/man/print.fseq.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{print.fseq} +\alias{print.fseq} +\title{Print method for functional sequence.} +\usage{ +\method{print}{fseq}(x, ...) +} +\arguments{ +\item{x}{A functional sequence object} + +\item{...}{not used.} +} +\value{ +x +} +\description{ +Print method for functional sequence. +} + diff --git a/man/tee.Rd b/man/tee.Rd new file mode 100644 index 0000000..703b23e --- /dev/null +++ b/man/tee.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2 (4.0.2): do not edit by hand +\name{\%T>\%} +\alias{\%T>\%} +\title{magrittr tee operator} +\usage{ +lhs \%T>\% rhs +} +\arguments{ +\item{lhs}{A value or the magrittr placeholder.} + +\item{rhs}{A function call using the magrittr semantics.} +} +\description{ +Pipe a value forward into a function- or call expression and return the +original value instead of the result. This is useful when an expression +is used for its side-effect, say plotting or printing. +} +\details{ +The tee operator works like \code{\link{\%>\%}}, except the +return value is \code{lhs} itself, and not the result of \code{rhs} function/expression. +} +\examples{ +rnorm(200) \%>\% +matrix(ncol = 2) \%T>\% +plot \%>\% # plot usually does not return anything. +colSums +} +\seealso{ +\code{\link{\%>\%}}, \code{\link{\%<>\%}}, \code{\link{\%$\%}} +} + diff --git a/tests/test-all.R b/tests/test-all.R new file mode 100644 index 0000000..6e896c2 --- /dev/null +++ b/tests/test-all.R @@ -0,0 +1,2 @@ +library(testthat) +test_check("magrittr") \ No newline at end of file diff --git a/tests/testthat/test-aliases.R b/tests/testthat/test-aliases.R new file mode 100644 index 0000000..fb6f10d --- /dev/null +++ b/tests/testthat/test-aliases.R @@ -0,0 +1,41 @@ +context("magrittr aliases") + +test_that("the provided aliases work as intended.", { + + expect_that(iris %>% extract(, 1:2), is_identical_to(iris[, 1:2])) + expect_that(iris %>% extract2(1), is_identical_to(iris[[1]])) + expect_that(iris %>% use_series(Species), is_identical_to(iris$Species)) + expect_that(1:10 %>% add(10:1), is_identical_to(1:10 + 10:1)) + expect_that(1:10 %>% subtract(10:1), is_identical_to(1:10 - 10:1)) + expect_that(1:10 %>% multiply_by(10:1), is_identical_to(1:10 * 10:1)) + + A <- matrix(1:16, 4, 4) + expect_that(A %>% multiply_by_matrix(A), is_identical_to(A %*% A)) + expect_that(1:10 %>% raise_to_power(10:1), is_identical_to((1:10)^(10:1))) + expect_that(1:10 %>% divide_by(10:1), is_identical_to(1:10 / 10:1)) + expect_that(1:10 %>% divide_by_int(10:1), is_identical_to(1:10 %/% 10:1)) + expect_that(1:10 %>% mod(3), is_identical_to((1:10) %% 3)) + expect_that(((1:10) > 5) %>% and((1:10) > 7), + is_identical_to(((1:10) > 5) & (1:10) > 7)) + expect_that(((1:10) > 5) %>% or((1:10) > 7), + is_identical_to(((1:10) > 5) | (1:10) > 7)) + + expect_that(1:10 %>% (magrittr::equals)(5) %>% sum, is_identical_to(1L)) + expect_that(1:10 %>% is_greater_than(5) %>% sum, is_identical_to(5L)) + expect_that(1:10 %>% is_weakly_greater_than(5) %>% sum, is_identical_to(6L)) + expect_that(1:10 %>% (magrittr::is_less_than)(5) %>% sum, is_identical_to(4L)) + expect_that(1:10 %>% is_weakly_less_than(5) %>% sum, is_identical_to(5L)) + + expect_that(iris %>% set_colnames(LETTERS[1:ncol(iris)]), + is_identical_to(`colnames<-`(iris, LETTERS[1:ncol(iris)]))) + + expect_that(1:10 %>% set_names(LETTERS[1:10]), + is_identical_to(`names<-`(1:10, LETTERS[1:10]))) + + expect_that(diag(3) %>% set_rownames(c("x", "y", "z")), + is_identical_to(`rownames<-`(diag(3), c("x", "y", "z")))) + + expect_that(1:10 %>% is_greater_than(5) %>% not, + is_identical_to(1:10 %>% is_weakly_less_than(5))) + +}) diff --git a/tests/testthat/test-anonymous-functions.r b/tests/testthat/test-anonymous-functions.r new file mode 100644 index 0000000..5c815cf --- /dev/null +++ b/tests/testthat/test-anonymous-functions.r @@ -0,0 +1,82 @@ +context("%>%: anonymous functions on right-hand side") + +test_that("%>% handles anonymous functions in GlobalEnv", { + + # Simple vectorized function + a <- (function(x) 1 + x^2/2 + x^3/9 + x^4/16)(1:100) + + b <- + 1:100 %>% + (function(x) 1 + x^2/2 + x^3/9 + x^4/16) + + # in principle, the dot should also work: + c <- + 1:100 %>% + (function(x) 1 + x^2/2 + x^3/9 + x^4/16)(.) + + expect_that(a, is_identical_to(b)) + expect_that(a, is_identical_to(c)) + + # Same using preferred magrittr syntax + a <- (function(x) 1 + x^2/2 + x^3/9 + x^4/16)(1:100) + + b <- + 1:100 %>% + {1 + .^2/2 + .^3/9 + .^4/16} + + expect_that(a, is_identical_to(b)) + + + # Simple data.frame functions + ht1 <- + iris %>% + (function(x) rbind(head(x), tail(x))) + + ht2 <- rbind(head(iris), tail(iris)) + + expect_that(ht1, is_identical_to(ht2)) + + + df1 <- iris[iris$Species == "setosa", 1:4] + + df2 <- + iris %>% + (function(x) x[x$Species == "setosa", 1:4]) + + expect_that(df1, is_identical_to(df2)) + + +}) + +test_that("%>% handles anonymous functions in other situations.", { + + # Anonymous functions when %>% used in arguments. + df1 <- + transform(iris, test = (function(x) x^2)(Sepal.Length)) + + df2 <- + iris %>% + transform(test = Sepal.Length %>% (function(x) x^2)) + + expect_that(df1, is_identical_to(df2)) + + + a <- sin(abs(1:10)) + b <- sin(1:10 %>% (function(x) abs(x))) + + expect_that(a, is_identical_to(b)) + + # Nested anonymous functions. + a <- iris %>% (function(x) x[, 1] %>% (function(y) max(y))) + b <- max(iris[, 1]) + + expect_that(a, is_identical_to(b)) +}) + + +test_that("%>% throws error with anonymous functions when not parenthesized.", { + + expect_that(iris %>% function(x) { head(x) }, throws_error()) + +}) + \ No newline at end of file diff --git a/tests/testthat/test-compound.R b/tests/testthat/test-compound.R new file mode 100644 index 0000000..f383f8f --- /dev/null +++ b/tests/testthat/test-compound.R @@ -0,0 +1,22 @@ +context("%<>%: compound assignment") + +test_that("Compound assignment operator works", { + + x <- y <- 1:10 + x[1:5] <- sin(cos(x[1:5])) + y[1:5] %<>% cos %>% sin + + expect_that(x, is_identical_to(y)) + + + somedata <- iris + somedata$Sepal.Length %<>% add(10) + iris$Sepal.Length <- iris$Sepal.Length + 10 + + expect_that(somedata, is_identical_to(iris)) + + z <- 1:10 + z %<>% add(2) %T>% plot + expect_that(z, is_identical_to(as.numeric(3:12))) + +}) diff --git a/tests/testthat/test-fseq.r b/tests/testthat/test-fseq.r new file mode 100644 index 0000000..a0c1efc --- /dev/null +++ b/tests/testthat/test-fseq.r @@ -0,0 +1,10 @@ +context("functional sequences") + + +test_that("fseq functions work", { + a <- . %>% cos %>% sin %>% tan + + b <- function(x) tan(sin(cos(x))) + + expect_that(a(1:10), is_identical_to(b(1:10))) +}) diff --git a/tests/testthat/test-multiple-arguments.r b/tests/testthat/test-multiple-arguments.r new file mode 100644 index 0000000..fdbb6db --- /dev/null +++ b/tests/testthat/test-multiple-arguments.r @@ -0,0 +1,49 @@ +context("%>%: multi-argument functions on right-hand side") + +test_that("placement of lhs is correct in different situations", { + + # When not to be placed in first position and in the presence of + # non-placeholder dots, e.g. in formulas. + case0a <- + lm(Sepal.Length ~ ., data = iris) %>% coef + + case1a <- + iris %>% lm(Sepal.Length ~ ., .) %>% coef + + case2a <- + iris %>% lm(Sepal.Length ~ ., data = .) %>% coef + + expect_that(case1a, is_equivalent_to(case0a)) + expect_that(case2a, is_equivalent_to(case0a)) + + # In first position and used in arguments + case0b <- + transform(iris, Species = substring(Species, 1, 1)) + + case1b <- + iris %>% transform(Species = Species %>% substr(1, 1)) + + case2b <- + iris %>% transform(., Species = Species %>% substr(., 1, 1)) + + expect_that(case1b, is_equivalent_to(case0b)) + expect_that(case2b, is_equivalent_to(case0b)) + + # LHS function values + case0c <- + aggregate(. ~ Species, iris, function(x) mean(x >= 5)) + + case1c <- + (function(x) mean(x >= 5)) %>% + aggregate(. ~ Species, iris, .) + + expect_that(case1c, is_equivalent_to(case0c)) + + # several placeholder dots + expect_that(iris %>% identical(., .), is_true()) + + + # "indirect" function expressions + expect_that(1:100 %>% iris[., ], is_identical_to(iris[1:100, ])) + +}) \ No newline at end of file diff --git a/tests/testthat/test-single-argument.r b/tests/testthat/test-single-argument.r new file mode 100644 index 0000000..cada4d2 --- /dev/null +++ b/tests/testthat/test-single-argument.r @@ -0,0 +1,18 @@ +context("%>%: one-argument function alternatives.") + +test_that("%>% works as expected with and without parentheses and placeholder", { + + expect_that(1:100 %>% sin %>% abs, is_identical_to(abs(sin(1:100)))) + expect_that(1:100 %>% sin() %>% abs(), is_identical_to(abs(sin(1:100)))) + expect_that(1:100 %>% sin(.) %>% abs(.), is_identical_to(abs(sin(1:100)))) + + expect_that(iris %>% head, is_identical_to(head(iris))) + + dnormsd <- function(sd) function(x) dnorm(x, sd = sd) + some_x <- rnorm(20) + expect_that(some_x %>% dnormsd(5)(.), is_identical_to(dnormsd(5)(some_x))) + expect_that(some_x %>% (dnormsd(5)), is_identical_to(dnormsd(5)(some_x))) + + expect_that(some_x %>% dnormsd(5), throws_error()) + expect_that(some_x %>% function(x) {x} %>% sin, throws_error()) +}) diff --git a/tests/testthat/test-tee.r b/tests/testthat/test-tee.r new file mode 100644 index 0000000..a08cd5c --- /dev/null +++ b/tests/testthat/test-tee.r @@ -0,0 +1,11 @@ +context("magrittr tee") + +test_that("tee related functionality works.", { + + dim_message <- function(data.) + message(sprintf("Data has dimension %d x %d", NROW(data.), NCOL(data.))) + + expect_that(iris %T>% dim_message, shows_message(dim_message(iris))) + expect_that(iris %T>% dim_message, is_identical_to(iris)) + +}) diff --git a/vignettes/magrittr.Rmd b/vignettes/magrittr.Rmd new file mode 100644 index 0000000..919ec22 --- /dev/null +++ b/vignettes/magrittr.Rmd @@ -0,0 +1,212 @@ +<!-- +%\VignetteEngine{knitr} +%\VignetteIndexEntry{Introducing magrittr} +--> + +```{r, echo = FALSE, message = FALSE} +library(magrittr) +options(scipen = 3) +knitr::opts_chunk$set( + comment = NA, + error = FALSE, + tidy = FALSE) +``` + + + +*This version: November, 2014. Stefan Milton Bache* + +# Abstract + +*The magrittr* (to be pronounced with a sophisticated french accent) is +a package with two aims: to decrease development time and to improve +readability and maintainability of code. Or even shortr: to make your code smokin' (puff puff)! + +To archive its humble aims, *magrittr* (remember the accent) provides a new +"pipe"-like operator, `%>%`, with which you may pipe a value forward into an +expression or function call; something along the lines of ` x %>% f `, rather +than ` f(x)`. This is not an unknown feature +elsewhere; a prime example is the `|>` operator used extensively in `F#` +(to say the least) and indeed this -- along with Unix pipes -- served as a +motivation for developing the magrittr package. + +This vignette describes the main features of *magrittr* and demonstrates +some features which has been added since the initial release. + +# Introduction and basics + +At first encounter, you may wonder whether an operator such as `%>%` can really +be all that beneficial; but as you may notice, it semantically changes your +code in a way that makes it more intuitive to both read and write. + +Consider the following example, in which the `mtcars` dataset shipped with +R is munged a little. +```{r} +library(magrittr) + +car_data <- + mtcars %>% + subset(hp > 100) %>% + aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>% + transform(kpl = mpg %>% multiply_by(0.4251)) %>% + print +``` +We start with a value, here `mtcars` (a `data.frame`). Based on this, we +first extract a subset, then we aggregate the information based on the number +of cylinders, and then we transform the dataset by adding a variable +for kilometers per liter as supplement to miles per gallon. Finally we print +the result before assigning it. +Note how the code is arranged in the logical +order of how you think about the task: data->transform->aggregate, which +is also the same order as the code will execute. It's like a recipe -- easy to +read, easy to follow! + +A horrific alternative would be to write +```{r} +car_data <- + transform(aggregate(. ~ cyl, + data = subset(mtcars, hp > 100), + FUN = function(x) round(mean(x, 2))), + kpl = mpg*0.4251) +``` +There is a lot more clutter with parentheses, and the mental task of deciphering +the code is more challenging---in particular if you did not write it yourself. + +Note also how "building" a function on the fly for use in `aggregate` is very +simple in *magrittr*: rather than an actual value as left-hand side in +pipeline, just use the placeholder. This is also very useful in R's +`*apply` family of functions. + +Granted: you may make the second example better, perhaps throw in a few temporary +variables (which is often avoided to some degree when using *magrittr*), +but one often sees cluttered lines like the ones presented. + +And here is another selling point. Suppose I want to quickly want to add +another step somewhere in the process. This is very easy in the +to do in the pipeline version, but a little more challenging in the +"standard" example. + +The combined example shows a few neat features of the pipe (which it is not): + +1. By default the left-hand side (LHS) will be *piped in* as the first argument of +the function appearing on the right-hand side (RHS). This is the case in the +`subset` and `transform` expressions. +2. `%>%` may be used in a nested fashion, e.g. it may appear in expressions within +arguments. This is used in the `mpg` to `kpl` conversion. +3. When the LHS is needed at a position other than the first, one can use +the dot,`'.'`, as placeholder. This is used in the `aggregate` expression. +4. The dot in e.g. a formula is *not* confused with a placeholder, which is +utilized in the `aggregate` expression. +5. Whenever only *one* argument is needed, the LHS, then one can omit the +empty parentheses. This is used in the call to `print` (which also returns its +argument). Here, `LHS %>% print()`, or even `LHS %>% print(.)` would also work. +6. A pipeline with a dot (`.`) as LHS will create a unary function. This is +used to define the aggregator function. + +One feature, which was not utilized above is piping into *anonymous functions*, +or *lambdas*. This is possible using standard function definitions, e.g. +```{r, eval = FALSE} +car_data %>% +(function(x) { + if (nrow(x) > 2) + rbind(head(x, 1), tail(x, 1)) + else x +}) +``` +However, *magrittr* also allows a short-hand notation: +```{r} +car_data %>% +{ + if (nrow(.) > 0) + rbind(head(., 1), tail(., 1)) + else . +} +``` +Since all right-hand sides are really "body expressions" of unary functions, this +is only the natural extension the simple right-hand side expressions. Of course +longer and more complex functions can be made using this approach. + +In the first example the anonymous function is enclosed in parentheses. +Whenever you want to use a function- or call-generating statement as right-hand side, +parentheses are used to evaluate the right-hand side before piping takes place. + +Another, less useful example is: +```{r} +1:10 %>% (substitute(f(), list(f = sum))) +``` + +# Additional pipe operators +*magrittr* also provides three related pipe operators. These are not as +common as `%>%` but they become useful in special cases. + +The "tee" operator, `%T>%` works like `%>%`, except it returns the left-hand +side value, and not the result of the right-hand side operation. +This is useful when a step in a pipeline is used for its side-effect (printing, +plotting, logging, etc.). As an example (where the actual plot is omitted here): +```{r, fig.keep='none'} +rnorm(200) %>% +matrix(ncol = 2) %T>% +plot %>% # plot usually does not return anything. +colSums +``` + +The "exposition" pipe operator, `%$%` exposes the names within the left-hand side +object to the right-hand side expression. Essentially, it is a short-hand for +using the `with` functions (and the same left-hand side objects are accepted). +This operator is handy when functions do not themselves have a data argument, as for +example `lm` and `aggregate` do. Here are a few examples as illustration: + +```{r, eval = FALSE} +iris %>% + subset(Sepal.Length > mean(Sepal.Length)) %$% + cor(Sepal.Length, Sepal.Width) + +data.frame(z = rnorm(100)) %$% + ts.plot(z) +``` + +Finally, the compound assignment pipe operator `%<>%` can be used as the first pipe +in a chain. The effect will be that the result of the pipeline is assigned to the +left-hand side object, rather than returning the result as usual. It is essentially +shorthand notation for expressions like `foo <- foo %>% bar %>% baz`, which +boils down to `foo %<>% bar %>% baz`. Another example is + +```{r, eval = FALSE} +iris$Sepal.Length %<>% sqrt +``` + +The `%<>%` can be used whenever `expr <- ...` makes sense, e.g. + +* `x %<>% foo %>% bar` +* `x[1:10] %<>% foo %>% bar` +* `x$baz %<>% foo %>% bar` + +# Aliases + +In addition to the `%>%`-operator, *magrittr* provides some aliases for other +operators which make operations such as addition or multiplication fit well +into the *magrittr*-syntax. As an example, consider: +```{r} +rnorm(1000) %>% +multiply_by(5) %>% +add(5) %>% +{ + cat("Mean:", mean(.), + "Variance:", var(.), "\n") + head(.) +} +``` +which could be written in more compact form as +```{r, results = 'hide'} +rnorm(100) %>% `*`(5) %>% `+`(5) %>% +{ + cat("Mean:", mean(.), "Variance:", var(.), "\n") + head(.) +} +``` +To see a list of the aliases, execute e.g. `?multiply_by`. + +# Development +The *magrittr* package is also available in a development version at the +GitHub development page: +[github.com/smbache/magrittr](http://github.com/smbache/magrittr). diff --git a/vignettes/magrittr.jpg b/vignettes/magrittr.jpg new file mode 100644 index 0000000..0fd2ac9 Binary files /dev/null and b/vignettes/magrittr.jpg differ -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-magrittr.git _______________________________________________ debian-med-commit mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/debian-med-commit
