R: адаптировать мутирующий вызов от обработки трех двоичных переменных к n двоичным переменным

#r #tidyverse #dplyr #tidyselect

#r #tidyverse #dplyr #tidyselect

Вопрос:

У меня есть фрейм данных с 3 двоичными переменными, которые относятся к периоду времени 1, и тремя соответствующими переменными, которые относятся ко времени 2.

 df <- data.frame("user" = c("a","b","c","d","e"), "item_1_time_1" = c(1,0,0,0,NA), "item_2_time_1" = c(1,1,1,0,NA), "item_3_time_1" = c(0,0,1,0,0), "item_1_time_2" = c(1,0,0,0,NA), "item_2_time_2" = c(1,0,0,0,NA), "item_3_time_2" = c(0,0,1,0,1))

df

   user item_1_time_1 item_2_time_1 item_3_time_1 item_1_time_2 item_2_time_2 item_3_time_2
1    a             1             1             0             1             1             0
2    b             0             1             0             0             0             0
3    c             0             1             1             0             0             1
4    d             0             0             1             0             0             0
5    e            NA            NA             0            NA            NA             1
  

Я хотел бы знать, имеет ли наблюдение a 1 для данного item периода в течение периода 1, но не в течение периода 2. Более того, я хотел бы знать, есть ли у наблюдения какой-либо экземпляр, в котором элемент находится 1 в течение периода 1, а не периода 2.

Таким образом, идеальный результат будет выглядеть так

 df2 <- data.frame("user" = c("a","b","c","d","e"), "item_1_time_1" = c(1,0,0,0,NA), "item_2_time_1" = c(1,1,1,0,NA), "item_3_time_1" = c(0,0,1,1,0), "item_1_time_2" = c(1,0,0,0,NA), "item_2_time_2" = c(1,0,0,0,NA), "item_3_time_2" = c(0,0,1,0,1), "item_1_check" = c(1,1,1,1,1), "item_2_check" = c(1,0,0,1,1), "item_3_check" = c(1,1,1,0,1), item_check = c(1,0,0,0,1))

df2 

user item_1_time_1 item_2_time_1 item_3_time_1 item_1_time_2 item_2_time_2 item_3_time_2 item_1_check item_2_check item_3_check item_check
1    a             1             1             0             1             1             0            1            1            1          1
2    b             0             1             0             0             0             0            1            0            1          0
3    c             0             1             1             0             0             1            1            0            1          0
4    d             0             0             1             0             0             0            1            1            0          0
5    e            NA            NA             0            NA            NA             1            1            1            1          1
  

До сих пор я пытался

 library(tidyverse)
df2 <- df %>%
   mutate(across(ends_with('time_2'), replace_na, 0)) %>% 
   mutate(across(ends_with('time_1'), replace_na, 0)) %>% 
   mutate(item_1_check = if_else(item_1_time_1 == 1 amp; item_1_time_2 == 0, 0, 1),
          item_2_check = if_else(item_2_time_1 == 1 amp; item_2_time_2 == 0, 0, 1),
          item_3_check = if_else(item_3_time_1 == 1 amp; item_3_time_2 == 0, 0, 1)) %>% 
   mutate(item_check = pmin(item_1_check, item_2_check, item_3_check))
  

Я хотел бы обобщить приведенные выше вызовы mutate, чтобы они могли обрабатывать n многих элементов, а не только 3.Есть ли способ, который я могу использовать ends_with('check') для окончательного изменения? Имена переменных не меняются, кроме номера элемента и периода времени.

Ответ №1:

Одним из вариантов было бы изменить формат на «длинный» и сделать это один раз

 library(dplyr)
library(tidyr)
df %>% 
  pivot_longer(cols = -user, names_to = c('group', '.value'), 
         names_sep="_(?=time)") %>% 
  mutate(across(starts_with('time'), replace_na, 0)) %>% 
  group_by(group) %>% 
  transmute(user, check = !(time_1 amp; !time_2)) %>% 
  ungroup %>% 
  group_by(user) %>%
  summarise(check = min(check), .groups = 'drop') %>% 
  right_join(df, .) %>%
  select(names(df), check)
# user item_1_time_1 item_2_time_1 item_3_time_1 item_1_time_2 item_2_time_2 item_3_time_2 check
#1    a             1             1             0             1             1             0     1
#2    b             0             1             0             0             0             0     0
#3    c             0             1             1             0             0             1     0
#4    d             0             0             0             0             0             0     1
#5    e            NA            NA             0            NA            NA             1     1
  

Или с помощью base R

 df$check <-   ( Reduce(`amp;`, lapply(split.default(replace(df[-1], 
 is.na(df[-1]), 0), sub("time_\d ", "", names(df)[-1])), 
    function(x)  !(x[[1]] amp; !x[[2]]))))