Замена значений столбцов на основе критериев строк

#r #if-statement #conditional-statements

#r #if-оператор #условные операторы

Вопрос:

Попытка заменить определенные значения столбцов на NA на основе определенных критериев строк во фрейме данных. Пример набора данных (тест) и код, который я пробовал, приведены ниже. Обратите внимание, что мой фактический набор данных намного больше, но мне, по сути, нужно заменить определенные столбцы в определенных строках (определенные годы) на NA. В примере я пытаюсь использовать индексацию столбцов для вставки NAs для столбцов 3: 5 за 2002 год, но в результате все значения в столбцах 3: 5 заменяются на NAs. Я хочу использовать индексацию столбцов вместо имен столбцов, потому что у меня много столбцов в моих фактических данных.

 test <- data.frame(YEAR=c(2000,2001,2002,2003,2004,2000,2001,2002,2003,2004),
    zone=c('A','A','A','A','A','B','B','B','B','B'),
    value=c(5,9,2,5,7,1,8,4,2,1),
    value2=c(5,3,6,8,9,7,2,6,1,7),
    value3=c(1,5,7,3,9,1,8,2,9,8))

ifelse(test$YEAR==2002,test[,3:5]<-NA,test[])
 

Ответ №1:

 test[,3:5] <- lapply(test[,3:5], replace, test$YEAR == 2002, NA)
test
#    YEAR zone value value2 value3
# 1  2000    A     5      5      1
# 2  2001    A     9      3      5
# 3  2002    A    NA     NA     NA
# 4  2003    A     5      8      3
# 5  2004    A     7      9      9
# 6  2000    B     1      7      1
# 7  2001    B     8      2      8
# 8  2002    B    NA     NA     NA
# 9  2003    B     2      1      9
# 10 2004    B     1      7      8
 

Пошаговый:

  • Базовый вызов replace(x, test$YEAR == 2002, NA) , который заменяет каждое значение x (подлежащее определению) на NA , если соответствующий год равен 2002; значения, которые не соответствуют 2002 году, сохраняются;
  • lapply(test[,3:5], replace, test$YEAR == 2002, NA) эквивалентно
     lapply(test[,3:5], function(x) replace(x, test$YEAR == 2002, NA))
     

    и действует на каждую из колонок 3-5. Для каждого столбца он вызывает replace функцию и возвращает результаты.

  • lapply собирается вернуть список. Поскольку мы хотим заменить только несколько столбцов всего фрейма, мы делаем test[,3:5] <- это, сохраняя остальные столбцы.

Примечание:

Это также можно было бы сделать ifelse , и это выглядело бы так:

 lapply(test[,3:5], function(x) ifelse(test$YEAR == 2002, NA, x))
 

Я склонен отдавать предпочтение replace ifelse в подобных ситуациях, которые очень хорошо определены. Почему? (1) replace меньше и быстрее. (2) ifelse имеет множество проблем с определением class столбцов, см., Например ifelse(TRUE,Sys.time(),Sys.time()) . ifelse (3) при неправильном использовании он может возвращать разные классы без предупреждения или ошибки. Хотя это может быть желательно, этого можно и не ожидать, и оно молчит. Смотрите, как ifelse(c(T,F), c(pi,pi), c("hello","hello")) всегда будет возвращать character вектор, который может быть нежелательным.

Несмотря на это, я никогда не видел ни одного случая ifelse , когда имело смысл выполнять назначения в вызове. То есть ни одно из них (для меня) никогда не имеет смысла:

 ifelse(a <- foo > bar, ..., ...)
ifelse(..., b <- 1, ...)
ifelse(..., b[2:9] <- 11, ...)
 

Комментарии:

1. Ну, это просто отлично сработало! Большое спасибо @r2evans

Ответ №2:

Просто чтобы обеспечить решение tidyverse:

 test %>%
  mutate(across(contains("value"), ~if_else(YEAR == 2002, NA_real_, .)))
 

или

 test %>%
  mutate(across(contains("value"), ~replace(., YEAR == 2002, NA)))
 

дает

 #    YEAR zone value value2 value3
# 1  2000    A     5      5      1
# 2  2001    A     9      3      5
# 3  2002    A    NA     NA     NA
# 4  2003    A     5      8      3
# 5  2004    A     7      9      9
# 6  2000    B     1      7      1
# 7  2001    B     8      2      8
# 8  2002    B    NA     NA     NA
# 9  2003    B     2      1      9
# 10 2004    B     1      7      8
 

Комментарии:

1. Да, спасибо за это. Я часто использую tidyverse.