Элегантный способ изменить переменную в соответствии с несколькими условиями?

Я пытаюсь изменить переменную экспозицию в соответствии с несколькими условиями.

Например: если stimulus_content является отрицательным, и если условие является отрицательным, и если установлено значение A, то содержимое переменной экспозиции должно быть изменено на длинное для строк, в которых stimulus_no равно X1, X2. , ... или Х5. Переменная экспозиция должна быть изменена на короткую для строк, в которых stimulus_no имеет значение X6, X7, ... или X10. И так далее...

Я надеюсь, что приведенный ниже код делает проблему более ясной.

Во-первых, это приблизительный набор данных:

n <- 6
dataset <- data.frame(
participant = rep(1:n, each=40),
condition = rep(c("pos","neg"), each=40),
set = rep(c("A","B"), each=40),
stimulus_content = rep(c("pos","neg"), each=2),
stimulus_no = rep(c("X1","X10","X11","X12","X13","X14","X15","X16","X17","X18","X19","X2","X20","X3","X4","X5","X6","X7","X8","X9"), each=2),
exposure = NA)

Первое, что мы попробовали, это использовать цикл. Для простоты включена только одна часть цикла. Он не возвращает ошибку, но и ничего не делает.

for (i in 1:length(longdat[,1])){
  if(longdat[i,"stimulus_content"] == "pos") { 
    if(longdat[i,"condition"] == "pos") {
      if(longdat[i,"set"] == "A") {     
        for(stimulus_no in c("X1","X2","X3","X4","X5")){longdat[i,"exposure"] == "long"}
        for(stimulus_no in c("X6","X7","X8","X9","X10")){longdat[i,"exposure"] == "short"}
        for(stimulus_no in c("X11","X12","X13","X14","X15","X16","X17","X18","X19","X20")){longdat[i,"exposure"] == "none"}
      } else { #for condition = pos and set != A            
        for(stimulus_no in c("X11","X12","X13","X14","X15")){longdat[i,"exposure"] == "long"}
        for(stimulus_no in c("X16","X17","X18","X19","X20")){longdat[i,"exposure"] == "short"}
        for(stimulus_no in c("X1","X2","X3","X4","X5","X6","X7","X8","X9","X10")){longdat[i,"exposure"] == "none"}
      }
    }
  }
}

Затем мы попытались использовать mutate и case_when. Этот код делает именно то, что должен, но он занимает почти 100 строк! Пожалуйста, найдите отрывок ниже.

longdat2 <- longdat %>%
  mutate(exposure = case_when(
    # Condition pos, set A
    stimulus_no=="X1" & stimulus_content=="pos" & condition=="pos" & set=="A" ~ "long",
    stimulus_no=="X2" & stimulus_content=="pos" & condition=="pos" & set=="A" ~ "long",
    # ...
    stimulus_no=="X9" & stimulus_content=="pos" & condition=="pos" & set=="A" ~ "short",
    stimulus_no=="X10" & stimulus_content=="pos" & condition=="pos" & set=="A" ~ "short",
    stimulus_no=="X11" & stimulus_content=="pos" & condition=="pos" & set=="A" ~ "none",
    # ... accordingly for condition pos and set B, and for condition neg and set A
    # and eventually for condition neg and set B
    stimulus_no=="X18" & stimulus_content=="neg" & condition=="neg" & set=="B" ~ "short",
    stimulus_no=="X19" & stimulus_content=="neg" & condition=="neg" & set=="B" ~ "short",
    stimulus_no=="X20" & stimulus_content=="neg" & condition=="neg" & set=="B" ~ "short",
  )
)

Если кто-то увидит ошибку в цикле или подскажет более сжатую версию второго (или первого) варианта, буду очень признателен!

Заранее большое спасибо!


person R. Isabel    schedule 30.01.2021    source источник
comment
Какой это язык? Р? Было бы очень полезно в качестве тегов. Более полезно, чем как циклы, так и for-loop или оба if-statement и условные-операторы.   -  person knittl    schedule 30.01.2021
comment
for(stimulus_no in c("X1","X2","X3","X4","X5")){longdat[i,"exposure"] == "long"} это выглядит неправильно. Разве это не должно быть longdat[i, "exposure"] = "long" или longdat[i, "exposure"] <- "long" (присваивание, а не сравнение)?   -  person knittl    schedule 30.01.2021
comment
Кроме того, в теле цикла не используется переменная цикла (stimulus_no).   -  person knittl    schedule 30.01.2021
comment
Спасибо за совет @knittl Я попробовал то, что вы предложили во втором комментарии, но результат не изменился. Не могли бы вы объяснить, что вы имели в виду в своем третьем комментарии, пожалуйста? Я не понял, что вы имели в виду. Спасибо   -  person R. Isabel    schedule 30.01.2021
comment
Ваше тело цикла не содержит переменную цикла. Все, что он делает, это выполняет тело N раз. for(stimulus_no in c("X1","X2","X3","X4","X5")){longdat[i,"exposure"] == "long"} просто выполняет longdat[i,"exposure"] == "long" 5 раз, не используя X1, X2, X3, X4, X5   -  person knittl    schedule 30.01.2021
comment
Спасибо @knittl за пояснения. Я все еще учусь правильно делать петли. Как бы я должен был написать это, если бы я хотел сделать это правильно, если вы не возражаете? Большое спасибо, я очень благодарен за ваше время!   -  person R. Isabel    schedule 30.01.2021
comment
Я не уверен, что цикл должен делать. Если он должен сверять одно значение с набором значений, вам нужна функция contains или оператор in в условии, а не цикл для многократного выполнения его тела.   -  person knittl    schedule 30.01.2021
comment
В вашем первом решении есть два уровня цикла. Как сказал @knittl, вы должны заменить второй уровень условием с оператором in. Вы также должны заменить: stimulus_no на longdat[i,"stimulus_no"]: if(longdat[i,"stimulus_no"] %in% c("X1","X2","X3","X4","X5")){longdat[i,"exposure"] <- "long"}   -  person barboulotte    schedule 30.01.2021
comment
Большое спасибо за вашу постоянную помощь @knittl и @barboulotte! Теперь я понимаю, в чем была проблема!   -  person R. Isabel    schedule 01.02.2021


Ответы (2)


Вы можете упростить свое второе решение, используя оператор %in% и обратное условие для части else:

dataset2 <- dataset %>%
  mutate(exposure = case_when(
    # Condition pos, set A
    (stimulus_content=="pos" & condition=="pos" & set=="A") & stimulus_no %in% c("X1","X2","X3","X4","X5") ~ "long",
    (stimulus_content=="pos" & condition=="pos" & set=="A") & stimulus_no %in% c("X6","X7","X8","X9","X10") ~ "short",
    (stimulus_content=="pos" & condition=="pos" & set=="A") & stimulus_no %in% c("X11","X12","X13","X14","X15","X16","X17","X18","X19","X20") ~ "none",
    # else
    !(stimulus_content=="pos" & condition=="pos" & set=="A") & stimulus_no %in% c("X11","X12","X13","X14","X15") ~ "long",
    !(stimulus_content=="pos" & condition=="pos" & set=="A") & stimulus_no %in% c("X16","X17","X18","X19","X20") ~ "short",
    !(stimulus_content=="pos" & condition=="pos" & set=="A") & stimulus_no %in% c("X1","X2","X3","X4","X5","X6","X7","X8","X9","X10") ~ "none"
  )
)

ИЗМЕНИТЬ

Для решения с циклом:

dataset3 <- dataset
for (i in 1:length(dataset3[,1])){
  if(dataset3[i,"stimulus_content"] == "pos" & dataset3[i,"condition"] == "pos" & dataset3[i,"set"] == "A") {    
    if(dataset3[i,"stimulus_no"] %in% c("X1","X2","X3","X4","X5")) {dataset3[i,"exposure"] <- "long"}
    if(dataset3[i,"stimulus_no"] %in% c("X6","X7","X8","X9","X10")) {dataset3[i,"exposure"] <- "short"}
    if(dataset3[i,"stimulus_no"] %in% c("X11","X12","X13","X14","X15","X16","X17","X18","X19","X20")){dataset3[i,"exposure"] <- "none"}
  } else {       
    if(dataset3[i,"stimulus_no"] %in% c("X11","X12","X13","X14","X15")) {dataset3[i,"exposure"] <- "long"}
    if(dataset3[i,"stimulus_no"] %in% c("X16","X17","X18","X19","X20")) {dataset3[i,"exposure"] <- "short"}
    if(dataset3[i,"stimulus_no"] %in% c("X1","X2","X3","X4","X5","X6","X7","X8","X9","X10")) {dataset3[i,"exposure"] <- "none"}
  }
}

compareDF::compare_df(dataset3, dataset2, rownames)
#> Error in stop_or_warn("The two data frames are the same!", stop_on_error): The two data frames are the same!

и чтобы избежать цикла, как @g-grothendieck, но ближе к вашему коду:

dataset4 <- within(dataset, {
  # Condition pos, set A
  exposure[(stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X1","X2","X3","X4","X5")] <- "long"
  exposure[(stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X6","X7","X8","X9","X10")] <- "short"
  exposure[(stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X11","X12","X13","X14","X15","X16","X17","X18","X19","X20")] <- "none"
  
  # else     
  exposure[!(stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X11","X12","X13","X14","X15")] <- "long"
  exposure[!(stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X16","X17","X18","X19","X20")] <- "short"
  exposure[!(stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X1","X2","X3","X4","X5","X6","X7","X8","X9","X10")] <- "none"
})

compareDF::compare_df(dataset4, dataset2, rownames)
#> Error in stop_or_warn("The two data frames are the same!", stop_on_error): The two data frames are the same!

or

dataset5 <- within(dataset, {
  # Condition pos, set A
  exposure <- ifelse((stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X1","X2","X3","X4","X5"), "long", exposure)
  exposure <- ifelse((stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X6","X7","X8","X9","X10"), "short", exposure)
  exposure <- ifelse((stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X11","X12","X13","X14","X15","X16","X17","X18","X19","X20"), "none", exposure)
  
  # else     
  exposure <- ifelse(!(stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X11","X12","X13","X14","X15"), "long", exposure)
  exposure <- ifelse(!(stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X16","X17","X18","X19","X20"), "short", exposure)
  exposure <- ifelse(!(stimulus_content == "pos" & condition == "pos" & set == "A") & stimulus_no %in% c("X1","X2","X3","X4","X5","X6","X7","X8","X9","X10"), "none", exposure)
})

compareDF::compare_df(dataset5, dataset2, rownames)
#> Error in stop_or_warn("The two data frames are the same!", stop_on_error): The two data frames are the same!

С уважением,

person barboulotte    schedule 30.01.2021
comment
Это сработало отлично, спасибо! - person R. Isabel; 30.01.2021
comment
Спасибо за правку, которую вы предоставили! Я многому научился благодаря вам! Спасибо, что нашли время! - person R. Isabel; 01.02.2021

1) grep Создайте code, состоящий из столбцов для сопоставления, вставленных вместе, а затем используйте регулярные выражения для их сопоставления, чтобы получить краткие выражения. Пакеты не используются. Обратите внимание, что [^A] будет соответствовать любому одиночному символу, который не является A. Если у вас есть только A и B, вы можете вместо этого использовать B. X1[1-5] будет соответствовать X11, ..., X15. X[6-9]|X10 будет соответствовать X6, ..., X10. $ соответствует концу строки. Опустите строку code <- NULL, если вы хотите сохранить столбец code.

dataset2 <- within(dataset, {
  code <- paste(stimulus_content, condition, set, stimulus_no)
  exposure[grep("pos pos A X[1-5]$", code)] <- "long"
  exposure[grep("pos pos A (X[6-9]|X10)$", code)] <- "short"
  exposure[grep("pos pos A (X1[1-9]|X20)$", code)] <- "none"
  exposure[grep("pos pos [^A] X1[1-5]$", code)] <- "long"
  exposure[grep("pos pos [^A] (X1[6-9]|X20)$", code)] <- "short"
  exposure[grep("pos pos [^A] (X[1-9]|X10)$", code)] <- "none"
  code <- NULL
})

2) Между Другой подход, также использующий только основание R, заключается в определении функции Between, которая проверяет, что нечисловая и числовая части ее первого аргумента отдельно предполагают, что числовая часть должна находиться в пределах указанного диапазон, а нечисловая часть равна четвертому аргументу (который по умолчанию равен "X", поэтому для краткости мы можем опустить его в вызовах). Затем используйте within, как показано:

Between <- function(x, lo, hi, alpha = "X") {
  nonno <- gsub("\\d", "", x)
  no = as.numeric(gsub("\\D", "", x))
  no >= lo & no <= hi & nonno == alpha
}

dataset3 <- within(dataset, {

  cond1 <- stimulus_content == "pos" & condition == "pos" & set == "A"
  exposure[cond1 & Between(stimulus_no, 1, 5)] <- "long"
  exposure[cond1 & Between(stimulus_no, 6, 10)] <- "short"
  exposure[cond1 & Between(stimulus_no, 11, 20)] <- "none"

  cond2 <- stimulus_content == "pos" & condition == "pos" & set != "A"
  exposure[cond2 & Between(stimulus_no, 11, 15)] <- "long"
  exposure[cond2 & Between(stimulus_no, 16, 20)] <- "short"
  exposure[cond2 & Between(stimulus_no, 1, 10)] <- "none"

  cond1 <- cond2 <- NULL
})
person G. Grothendieck    schedule 30.01.2021
comment
Это сработало так же хорошо, спасибо! - person R. Isabel; 30.01.2021
comment
Спасибо за редактирование и спасибо, что нашли время ответить на мой вопрос! Я не знал о функции between! - person R. Isabel; 01.02.2021