Применить набор функций к объекту

У меня есть dataframe с набором объектов df$data и набором правил для применения к каждому объекту df$rules.

df <- data.frame(
  data = c(1,2,3),
  rules = c("rule1", "rule1, rule2, rule3", "rule3, rule2"),
  stringsAsFactors = FALSE
)

Правила

rule1 <- function(data) {
  data * 2
}

rule2 <- function(data) {
  data + 1
}

rule3 <- function(data) {
  data ^ 3
}

Для каждой строки в ядре данных я хочу применить все правила, указанные в столбце rules. Правила должны применяться последовательно.

Что я понял:

apply_rules <- function(data, rules) {
  for (i in 1:length(data)) {
    rules_now <- unlist(strsplit(rules[i], ", "))
    for (j in 1:length(rules_now)) {
      data[i] <- apply_rule(data[i], rules_now[j])
    }
  }
  return(data)
}

apply_rule <- function(data, rule) {
  return(sapply(data, rule))
}


apply_rules(df$data, df$rules)
# [1]   2 125  28

Хотя это работает, я уверен, что должны быть более элегантные решения. На SO я мог бы найти много материала о apply -functions, а также один посте о применении многих функций к вектору и что - то о цепочках функций. Идея Compose выглядит многообещающе, но я не мог понять, как сделать вызов Compose с моими правилами как строки. (parse() не работает..)

Любые намеки?

Ответы

Ответ 1

Несколько хороших ответов уже есть, но в другой вариант - постройте цепочку труб в виде строки, затем оцените ее. Например - для строки 1 - eval(parse(text = "1 %>% rule1")) дает 2

eval_chain <- function(df) {
    eval(parse(text = paste(c(df$data, unlist(strsplit(df$rules, ", "))), collapse=" %>% ")))
}

df$value <- sapply(1:nrow(df), function(i) df[i, ] %>% eval_chain)
  # data               rules value
# 1    1               rule1     2
# 2    2 rule1, rule2, rule3   125
# 3    3        rule3, rule2    28

Ответ 2

В этом случае вы можете использовать mapply и Reduce вместе с mget.

mapply(function(d,r) Reduce(function(lhs,rhs) rhs(lhs),
                            c(d,mget(strsplit(r,", ")[[1]],envir = globalenv())))
       ,df$data
       ,df$rules)

# [1] 2 125  28

Возможно, вам придется настроить аргумент envir mget для вашего конкретного случая. Вероятно, было бы более надежно передавать среду, в которой ваши правила определены для mget.

Ответ 3

Я думаю, что вам нужно немного изменить подход (в этом случае выражения только ухудшат ситуацию):

df <- data.frame(
  data = c(1,2,3),
  rules = c("rule1", "rule1, rule2, rule3", "rule3, rule2"),
  stringsAsFactors = FALSE
)

# list of functions
fun_list <- list(
  rule1 = function(x) x*2,
  rule2 = function(x) x+1,
  rule3 = function(x) x^3
)

# function to call list of functions
call_funs <- function(x, fun_vec) {
  for (i in seq_along(fun_vec)) {
    x <- fun_list[[fun_vec[[i]]]](x)
  }
  x
}

(want <- unlist(Map(call_funs, df$data, strsplit(gsub(" ", "", df$rules), ","))))    
#   2 125  28