Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

find a sequence of rules in a dataframe, with break rules

I showed how I see the implementation of this algorithm, I divided it into two steps

step one sequence search

enter image description here

step two check break rules

enter image description here

set.seed(123)
dat <- as.data.frame(matrix(sample(10,60,replace = T),ncol = 3))
colnames(dat) <- LETTERS[1:ncol(dat)]
dat

rule <- c("A==0","A==10 & B==4","C==9","A>10","B<0","C==0","A==5","A>10",
          "B<0","C==0","A==9 & B==9","A>10","B<0","A==10","A==7 & B==5")
action <- c("break","next","next",rep("break",3),"next",rep("break",3),
            "next",rep("break",3) ,"next")

rule <- cbind(rule,action)

enter image description here

like image 517
mr.T Avatar asked Aug 20 '21 19:08

mr.T


2 Answers

I think this works -

seq_rule <- function(dat, rule, res.only = TRUE) {
  value = rule$action
  rule <- rule$rule
  m <- with(dat, lapply(rule, function(r) eval(str2expression(r))))  
  fu <- function(x, y) {
    k <- which(y)
    ifelse(all(k <= x), NA, min(k[k > x]))
  }  
  idx <- Reduce(fu , m,init = 0, accumulate = TRUE)[-1]  
  if (!res.only) {
    idx <- na.omit(idx)
    fidx <- head(idx, length(rule))
    debug.vec <- replace(rep("no", nrow(dat)), fidx, rule[seq_along(fidx)])
    return(cbind(dat, debug.vec))
  }
  if(any(value[!is.na(idx)] == 'break')) return(FALSE)
  idx <- na.omit(idx)
  length(idx) >= length(rule)
}

Here are some checks -

rule <- data.frame(rule= c("A==9","B==4","C==4","A==4", "B==10","C==4") ,
                   action= c(rep("next",3),"break","break","next"))

seq_rule(dat = dat,rule = rule)
#[1] FALSE


rule <- data.frame(rule= c("C==9","B==3","C==4"),
                   action= c(rep("next",3)))
seq_rule(dat = dat,rule = rule)
#[1] TRUE

seq_rule(dat = dat,rule = rule, res.only = FALSE)
#    A  B  C debug.vec
#1   3  5  9      C==9
#2   3  3  3      B==3
#3  10  9  4      C==4
#4   2  9  1        no
#5   6  9  7        no
#6   5  3  5        no
#7   4  8 10        no
#8   6 10  7        no
#9   9  7  9        no
#10 10 10  9        no

rule <- data.frame(rule= c("C==9","B==3","C==4", "A == 1"),
                   action= c(rep("next",3), 'break'))
seq_rule(dat = dat,rule = rule)
#[1] FALSE

rule <- data.frame(rule= c("C==9","B==3","C==4", "A == 6"),
                   action= c(rep("next",3), 'break'))
seq_rule(dat = dat,rule = rule)
#[1] FALSE
like image 200
Ronak Shah Avatar answered Oct 20 '22 07:10

Ronak Shah


Since the logic of your question is a bit complicated, I guess a straightforward way, e.g., using loops, might be more efficient and readable. Here is one version of seq_rule

seq_rule <- function(dat, rule, res.only = TRUE) {
  m <- with(dat, as.data.frame(sapply(rule$rule, function(r) eval(str2expression(r)))))
  rule_next <- with(rule, rule[action == "next"])
  m_next <- m[rule_next]
  idx <- na.omit(
    Reduce(
      function(x, y) {
        k <- which(y)
        ifelse(all(k <= x), NA, min(k[k > x]))
      }, m_next,
      init = 0, accumulate = TRUE
    )
  )[-1]
  fidx <- head(idx, length(rule_next))
  debug.vec <- replace(rep("no", nrow(dat)), fidx, rule_next[seq_along(fidx)])
  trgs <- do.call(
    rbind,
    Map(
      function(p, q) {
        u <- as.matrix(m[p, ][q[q %in% with(rule, rule[action == "break"])]])
        k <- which(u, arr.ind = TRUE)
        data.frame(breakRowID = row.names(u)[k[, "row"]], breakTrigger = colnames(u)[k[, "col"]])
      },
      split(1:nrow(dat), cut(1:nrow(dat), c(0, idx, Inf))),
      split.default(names(m), cumsum(rule$action != "break"))
    )
  )
  triggerBreaks <- replace(rep("no", nrow(dat)), debug.vec != "no", NA)
  if (!res.only) {
    cbind(dat, debug.vec, trigger.break = with(trgs, replace(triggerBreaks, as.numeric(breakRowID), breakTrigger)))
  } else {
    nrow(trgs) == 0
  }
}

and you will see

> seq_rule(dat = dat, rule = rule)
[1] FALSE

> seq_rule(dat = dat, rule = rule, res.only = FALSE)
    A  B  C    debug.vec trigger.break
1   3  9  2           no            no
2   3  3  1           no            no
3  10  4  9 A==10 & B==4          <NA>
4   2  1  9         C==9          <NA>
5   6  7  6           no            no
6   5  5  5         A==5          <NA>
7   4 10  9           no            no
8   6  7 10           no            no
9   9  9  4  A==9 & B==9          <NA>
10 10  9  6           no         A==10
11  5 10  8           no            no
12  3  7  6           no            no
13  9  5  6           no            no
14  9  7  7           no            no
15  9  5  1           no            no
16  3  6  6           no            no
17  8  9  2           no            no
18 10  2  1           no         A==10
19  7  5  2  A==7 & B==5          <NA>
20 10  8  4           no            no
like image 27
ThomasIsCoding Avatar answered Oct 20 '22 06:10

ThomasIsCoding