Dear Ravi,
I wrote a small replacement for ifelse() which avoids such unnecessary
evaluations (it bothered me a few times as well - so I decided to try a
small replacement).
### Example:
x = 1:10
FUN = list();
FUN[[1]] = function(x, y) x*y;
FUN[[2]] = function(x, y) x^2;
FUN[[3]] = function(x, y) x;
# lets run multiple conditions
# eval.by.formula(conditions, FUN.list, ... (arguments for FUN) );
eval.by.formula((x > 5 & x %% 2) ~ (x <= 5) ~ ., FUN, x, x-1)
# Example 2
eval.by.formula((x > 5 & x %% 2) ~ (x <= 5) ~ ., FUN, 2, x)
### Disclaimer:
- NOT properly tested;
The code for the function is below. Maybe someone can experiment with
the code and improve it further. There are a few issues / open
questions, like:
1.) Best Name: eval.by.formula, ifelse.formula, ...?
2.) Named arguments: not yet;
3.) Fixed values inside FUN.list
4.) Format of expression for conditions:
expression(cond1, cond2, cond3) vs cond1 ~ cond2 ~ cond3 ???
5.) Code efficiency
- some tests on large data sets & optimizations are warranted;
Sincerely,
Leonard
======
The latest code is on Github:
https://github.com/discoleo/R/blob/master/Stat/Tools.Formulas.R
eval.by.formula = function(e, FUN.list, ..., default=NA) {
?? ?tok = split.formula(e);
?? ?if(length(tok) == 0) return();
?? ?FUN = FUN.list;
?? ?# Argument List
?? ?clst = substitute(as.list(...))[-1];
?? ?len? = length(clst);
?? ?clst.all = lapply(clst, eval);
?? ?eval.f = function(idCond) {
?? ???? sapply(seq(length(isEval)), function(id) {
?? ???? ??? if(isEval[[id]] == FALSE) return(default);
?? ???? ??? args.l = lapply(clst.all, function(a) if(length(a) == 1) a
else a[[id]]);
?? ???? ??? do.call(FUN[[idCond]], args.l);
?? ???? });
?? ?}
?? ?# eval 1st condition:
?? ?isEval = eval(tok[[1]]);
?? ?rez = eval.f(1);
?? ?if(length(tok) == 1) return(rez);
?? ?# eval remaining conditions
?? ?isEvalAll = isEval;
?? ?for(id in seq(2, length(tok))) {
?? ???? if(tok[[id]] == ".") {
?? ???? ??? # Remaining conditions: tok == ".";
?? ???? ??? # makes sens only on the last position
?? ???? ??? if(id < length(tok)) warning("\".\" is not
last!");
?? ???? ??? isEval = ! isEvalAll;
?? ???? ??? rez[isEval] = eval.f(id)[isEval];
?? ???? ??? next;
?? ???? }
?? ???? isEval = rep(FALSE, length(isEval));
?? ???? isEval[ ! isEvalAll] = eval(tok[[id]])[ ! isEvalAll];
?? ???? isEvalAll[isEval] = isEval[isEval];
?? ???? rez[isEval] = eval.f(id)[isEval];
?? ?}
?? ?return(rez);
}
# current code uses the formula format:
# cond1 ~ cond 2 ~ cond3
# tokenizes a formula in its parts delimited by "~"
# Note:
# - tokenization is automatic for ",";
# - but call MUST then use FUN(expression(_conditions_), other_args, ...);
split.formula = function(e) {
?? ?tok = list();
?? ?while(length(e) > 0) {
?? ???? if(e[[1]] == "~") {
?? ???? ??? if(length(e) == 2) { tok = c(NA, e[[2]], tok); break; }
?? ???? ??? tok = c(e[[3]], tok);
?? ???? ??? e = e[[2]];
?? ???? } else {
?? ???? ??? tok = c(e, tok); break;
?? ???? }
?? ?}
?? ?return(tok);
}