%?<-%
: Assign if invalidWhen coding in R, the data checking is actually a headache. For
example, to check if a variable aa
exists and not
NULL
, otherwise set a default value to be 1, the check
looks like:
if( exists('aa') && !is.null(aa) ){
<- 1
aa }
Most of time we are repeating ourselves. With %?<-%
operator, we just need:
%?<-% 1
aa print(aa)
#> [1] 1
The powerful part is the left-hand side can be any expression. For example,
%?<-% list()
l
$aa %?<-% 1
l
print(l)
#> $aa
#> [1] 1
If the value exists, then %?<-%
does nothing (not
even evaluate the expressions on the right-hand side)
# e already exists
<- list(aa = 1)
e
# %?<-% will not evaluate rhs, nor assign values
system.time({
%?<-% { Sys.sleep(10); list(aa = 2) }
e print(e)
})#> $aa
#> [1] 1
#> user system elapsed
#> 0 0 0
In modern JavaScript, function can be created via
(arg) => { ... }
. For example,
const li = ['A', 'T', 'G', 'C'];
.map((el, ii) => {
lireturn(`The index for ${el} is ${ii}`);
; })
dipsaus
provides functions iapply
, and
%=>%
, together with glue
package, we can
apply elements like this:
# gl <- glue::glue
# `%>%` <- magrittr::`%>%`
<- c('A', 'T', 'G', 'C')
li %>% iapply(c(el, ii) %=>% {
li gl('The index for {el} is {ii}')
})#> [1] "The index for A is 1" "The index for T is 2" "The index for G is 3"
#> [4] "The index for C is 4"
%=>%
collect the left-hand side elements as arguments
and right-hand side expression as body and create function:
c(a, b=a^2, ...) %=>% {
print(c(a , b,...))
}#> function (a, b = a^2, ...)
#> {
#> print(c(a, b, ...))
#> }
match_calls
The function match.call
provided by base package let us
format calls with formals matched.
match.call(textInput, call = quote(textInput('inputId', 'label', 'aaa')))
#> textInput(inputId = "inputId", label = "label", value = "aaa")
This is already powerful as we can parse the expressions using
as.list()
to get the input parameters. However, when
encounter the nested calls like shiny
UI components,
match.call
does not work well. We can’t see the matched
results inside of the nested functions.
match.call(tagList, call = quote(tagList(
div(
$ul(
tags$li(textInput('inputId', 'label', 'aaa'))
tags
)
)
)))#> tagList(div(tags$ul(tags$li(textInput("inputId", "label", "aaa")))))
match_calls
solves this problem by recursively calling
match.call
:
match_calls(call = tagList(
div(
$ul(
tags$li(textInput('inputId', 'label', 'aaa'))
tags
)
)recursive = TRUE)
), #> tagList(div(tags$ul(tags$li(textInput(inputId = "inputId", label = "label",
#> value = "aaa")))))
It can also change modify the calls. For example, we want to add
ns
to input ID in shiny modules, then the following
replave_args
changes "inputId"
to
ns("inputId")
match_calls(call = tagList(
div(
$ul(
tags$li(textInput('inputId', 'label', 'aaa'))
tags
)
)recursive = TRUE, replace_args = list(
), 'inputId' = function(v, ...){
as.call(list(quote(ns), v))
}
))#> tagList(div(tags$ul(tags$li(textInput(inputId = ns("inputId"),
#> label = "label", value = "aaa")))))
Pipe functions can simplify the workflow and make R code more
readable. The most popular pipe %>%
allows the left-hand
elements to be the first input of the right-hand side functions.
dipsaus
provides several pipe-friendly functions.
no_op
no_op
takes whatever input in, and returns the input,
with side effects. For example, we want to plot the results from the
pipe and continue the analysis, usually this is what happens:
%>%
x do_something(...) ->
x_tmp
plot(x_tmp)
%>%
x_tmp do_others(...) ->
final_results
With no_op
, the pipe becomes:
%>%
x do_something(...) %>%
no_op(plot, ylim = c(0,100)) %>%
do_others(...) ->
final_results
Here’s an example
par(mfrow = c(1,2))
1:10) %>%
(iapply(c(el, ii) %=>% {
rnorm(20, el, ii)
simplify = FALSE) %>%
}, %>%
unlist
# Begin no-ops, result will not change
no_op({
# Use expression and "." to refer the data
print(summary(.))
%>%
}) no_op(
# Use function and pass ... to function
x = seq(0,1,length.out = 200),
plot, type = 'p', ylim = c(-20,20), pch = 16,
xlab = 'Time', ylab = 'Value', las = 1
%>%
) no_op(hist, xlab = 'Values', main = 'Histogram') ->
result#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> -11.741 1.046 3.628 5.541 9.366 29.272
str(result)
#> num [1:200] 1.032 2.355 0.271 2.138 1.048 ...
do_aggregate
This is a wrapper of aggregate
function. When using
formula, aggregate
requires the formula to be the first
element. If the pipe results are data.frame
and we want to
use formula, it’s super inconvenient.
## S3 method for class 'formula'
aggregate(formula, data, FUN, ..., subset, na.action = na.omit)
do_aggregate
allows the first element to be data frames
while using formula:
%>%
ToothGrowth do_aggregate(len ~ ., mean)
#> supp dose len
#> 1 OJ 0.5 13.23
#> 2 VC 0.5 7.98
#> 3 OJ 1.0 22.70
#> 4 VC 1.0 16.77
#> 5 OJ 2.0 26.06
#> 6 VC 2.0 26.14