wrapr
is an R
package
that supplies powerful tools for writing and debugging R
code.
Primary wrapr
services include:
%.>%
(dot arrow pipe)unpack
/to
(assign to multiple values)as_named_list
(build up a named list quickly)build_frame()
/ draw_frame()
( data.frame
builders and formatters )bc()
(blank concatenate)qc()
(quoting concatenate):=
(named map builder)%?%
(coalesce)%.|%
(reduce/expand args)uniques()
(safe unique()
replacement)partition_tables()
/ execute_parallel()
DebugFnW()
(function debug wrappers)λ()
(anonymous function builder)let()
(let block)evalb()
/si()
(evaluate with bquote
/ string interpolation)sortv()
(sort a data.frame by a set of columns).stop_if_dot_args()
(check for unexpected arguments)library(wrapr)
packageVersion("wrapr")
# [1] '2.1.0'
date()
# [1] "Sat Aug 19 09:06:13 2023"
%.>%
(dot pipe or dot arrow)%.>%
dot arrow pipe is a pipe with intended
semantics:
“
a %.>% b
” is to be treated approximately as if the user had written “{ . <- a; b };
” with “%.>%
” being treated as left-associative.
Other R
pipes include magrittr
and pipeR
.
The following two expressions should be equivalent:
cos(exp(sin(4)))
# [1] 0.8919465
4 %.>% sin(.) %.>% exp(.) %.>% cos(.)
# [1] 0.8919465
The notation is quite powerful as it treats pipe stages as expression
parameterized over the variable “.
”. This means you do not
need to introduce functions to express stages. The following is a valid
dot-pipe:
1:4 %.>% .^2
# [1] 1 4 9 16
The notation is also very regular as we show below.
1:4 %.>% sin
# [1] 0.8414710 0.9092974 0.1411200 -0.7568025
1:4 %.>% sin(.)
# [1] 0.8414710 0.9092974 0.1411200 -0.7568025
1:4 %.>% base::sin
# [1] 0.8414710 0.9092974 0.1411200 -0.7568025
1:4 %.>% base::sin(.)
# [1] 0.8414710 0.9092974 0.1411200 -0.7568025
1:4 %.>% function(x) { x + 1 }
# [1] 2 3 4 5
1:4 %.>% (function(x) { x + 1 })
# [1] 2 3 4 5
1:4 %.>% { .^2 }
# [1] 1 4 9 16
1:4 %.>% ( .^2 )
# [1] 1 4 9 16
Regularity can be a big advantage in teaching and comprehension. Please see “In Praise of Syntactic Sugar” for more details. Some formal documentation can be found here.
5 %.>% 6
deliberately stops as
6
is a right-hand side that obviously does not use its
incoming value. This check is only applied to values, not functions on
the right-hand side.
sin()
is prohibited as it looks too much like the
user declaring sin()
takes no arguments. One must pipe into
either a function, function name, or an non-trivial expression (such as
sin(.)
). A useful error message is returned to the user:
wrapr::pipe does not allow direct piping into a no-argument function call expression (such as "sin()" please use sin(.))
.
5 %.>% return(.)
is prohibited as the obvious pipe
implementation would not actually escape from user functions as users
may intend.
$
, ::
,
@
, and a few more) on the right-hand side are treated
performed (example: 5 %.>% base::sin(.)
).
5 %.>% (sin(.))
).
5 %.>% function(x) {x+1}
returns 6,
just as 5 %.>% (function(x) {x+1})(.)
does).
5 %.>% { function(x) {x+1} }
returns
function(x) {x+1}
, not 6).
.()
.
The dot pipe is also user configurable through standard
S3
/S4
methods.
The dot pipe has been formally written up in the R Journal.
@article{RJ-2018-042,
author = {John Mount and Nina Zumel},
title = {{Dot-Pipe: an S3 Extensible Pipe for R}},
year = {2018},
journal = {{The R Journal}},
url = {https://journal.r-project.org/archive/2018/RJ-2018-042/index.html}
}
unpack
/to
multiple assignmentsUnpack a named list into the current environment by name (for a
positional based multiple assignment operator please see zeallot
,
for another named base multiple assigment please see vadr::bind
).
<- data.frame(
d x = 1:9,
group = c('train', 'calibrate', 'test'),
stringsAsFactors = FALSE)
unpack[= train,
train_data = calibrate,
calibrate_data = test
test_data := split(d, d$group)
]
::kable(train_data) knitr
x | group | |
---|---|---|
1 | 1 | train |
4 | 4 | train |
7 | 7 | train |
as_named_list
Build up named lists. Very
convenient for managing workspaces when used with used with
unpack
/to
.
as_named_list(train_data, calibrate_data, test_data)
# $train_data
# x group
# 1 1 train
# 4 4 train
# 7 7 train
#
# $calibrate_data
# x group
# 2 2 calibrate
# 5 5 calibrate
# 8 8 calibrate
#
# $test_data
# x group
# 3 3 test
# 6 6 test
# 9 9 test
build_frame()
/ draw_frame()
build_frame()
is a convenient way to type in a small example data.frame
in natural row order. This can be very legible and saves having to
perform a transpose in one’s head. draw_frame()
is the complimentary function that formats a given
data.frame
(and is a great way to produce neatened
examples).
<- build_frame(
x "measure" , "training", "validation" |
"minus binary cross entropy", 5 , -7 |
"accuracy" , 0.8 , 0.6 )
print(x)
# measure training validation
# 1 minus binary cross entropy 5.0 -7.0
# 2 accuracy 0.8 0.6
str(x)
# 'data.frame': 2 obs. of 3 variables:
# $ measure : chr "minus binary cross entropy" "accuracy"
# $ training : num 5 0.8
# $ validation: num -7 0.6
cat(draw_frame(x))
# x <- wrapr::build_frame(
# "measure" , "training", "validation" |
# "minus binary cross entropy", 5 , -7 |
# "accuracy" , 0.8 , 0.6 )
qc()
(quoting concatenate)qc()
is a quoting variation on R
’s
concatenate operator c()
. This code such as the
following:
qc(a = x, b = y)
# a b
# "x" "y"
qc(one, two, three)
# [1] "one" "two" "three"
qc()
also allows bquote()
driven
.()
-style argument escaping.
<- "I_am_a"
aname <- "six"
yvalue
qc(.(aname) := x, b = .(yvalue))
# I_am_a b
# "x" "six"
Notice the :=
notation is required for syntacitic
reasons.
:=
(named map builder):=
is the “named map builder”. It allows code such as
the following:
'a' := 'x'
# a
# "x"
The important property of named map builder is it accepts values on the left-hand side allowing the following:
<- 'variableNameFromElsewhere'
name := 'newBinding'
name # variableNameFromElsewhere
# "newBinding"
A nice property is :=
commutes (in the sense of algebra
or category theory) with R
’s concatenation function
c()
. That is the following two statements are
equivalent:
c('a', 'b') := c('x', 'y')
# a b
# "x" "y"
c('a' := 'x', 'b' := 'y')
# a b
# "x" "y"
The named map builder is designed
to synergize with seplyr
.
%?%
(coalesce)The coalesce operator tries to replace elements of its first argument
with elements from its second argument. In particular %?%
replaces NULL vectors and NULL/NA entries of vectors and lists.
Example:
c(1, NA) %?% list(NA, 20)
# [1] 1 20
%.|%
(reduce/expand args)x %.|% f
stands for
f(x[[1]], x[[2]], ..., x[[length(x)]])
.
v %|.% x
also stands for
f(x[[1]], x[[2]], ..., x[[length(x)]])
. The two operators
are the same, the variation just allowing the user to choose the order
they write things. The mnemonic is: “data goes on the dot-side of the
operator.”
<- list('prefix_', c(1:3), '_suffix')
args
%.|% paste0
args # [1] "prefix_1_suffix" "prefix_2_suffix" "prefix_3_suffix"
# prefix_1_suffix" "prefix_2_suffix" "prefix_3_suffix"
%|.% args
paste0 # [1] "prefix_1_suffix" "prefix_2_suffix" "prefix_3_suffix"
# prefix_1_suffix" "prefix_2_suffix" "prefix_3_suffix"
DebugFnW()
DebugFnW()
wraps a function for debugging. If the
function throws an exception the execution context (function arguments,
function name, and more) is captured and stored for the user. The
function call can then be reconstituted, inspected and even re-run with
a step-debugger. Please see our free
debugging video series and
vignette('DebugFnW', package='wrapr')
for examples.
λ()
(anonymous function builder)λ()
is a concise abstract function creator or “lambda
abstraction”. It is a placeholder that allows the use of the
-character for very concise function abstraction.
Example:
# Make sure lambda function builder is in our enironment.
::defineLambda()
wrapr
# square numbers 1 through 4
sapply(1:4, λ(x, x^2))
# [1] 1 4 9 16
let()
let()
allows execution of arbitrary code with
substituted variable names (note this is subtly different than binding
values for names as with base::substitute()
or
base::with()
).
The function is simple and powerful. It treats strings as variable names and re-writes expressions as if you had used the denoted variables. For example the following block of code is equivalent to having written “a + a”.
<- 7
a
let(
c(VAR = 'a'),
+ VAR
VAR
)# [1] 14
This is useful in re-adapting non-standard evaluation interfaces (NSE interfaces) so one can script or program over them.
We are trying to make let()
self teaching and self
documenting (to the extent that makes sense). For example try the
arguments “eval=FALSE
” prevent execution and see what
would have been executed, or debug=TRUE
to have
the replaced code printed in addition to being executed:
let(
c(VAR = 'a'),
eval = FALSE,
{+ VAR
VAR
}
)# {
# a + a
# }
let(
c(VAR = 'a'),
debugPrint = TRUE,
{+ VAR
VAR
}
)# $VAR
# [1] "a"
#
# {
# a + a
# }
# [1] 14
Please see vignette('let', package='wrapr')
for more
examples. Some formal documentation can be found here.
wrapr::let()
was inspired by
gtools::strmacro()
and base::bquote()
, please
see here
for some notes on macro methods in R
.
evalb()
/si()
(evaluate with bquote
/ string interpolation)wrapr
supplies unified notation for quasi-quotation and
string interpolation.
= 1:10
angle <- "angle"
variable
# # execute code
# evalb(
# plot(x = .(-variable), y = sin(.(-variable)))
# )
# alter string
si("plot(x = .(variable), y = .(variable))")
# [1] "plot(x = \"angle\", y = \"angle\")"
The extra .(-x)
form is a shortcut for
.(as.name(x))
.
sortv()
(sort a data.frame by a set of columns)This is the sort command that is missing from R
: sort a
data.frame
by a chosen set of columns specified in a
variable.
<- data.frame(
d x = c(2, 2, 3, 3, 1, 1),
y = 6:1,
z = 1:6)
<- c('x', 'y')
order_cols
sortv(d, order_cols)
# x y z
# 6 1 1 6
# 5 1 2 5
# 2 2 5 2
# 1 2 6 1
# 4 3 3 4
# 3 3 4 3
Install with:
install.packages("wrapr")
More details on wrapr
capabilities can be found in the
following two technical articles:
Note: wrapr
is meant only for “tame names”, that is:
variables and column names that are also valid simple (without
quotes) R
variables names.