R — применение условия к нескольким столбцам, игнорируя NA

r #if-statement #conditional-statements

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

Вопрос:

Предположим, у меня есть следующий фрейм данных:

 x <- c(1, 1, 2, 3, 4, 5)
y <- c(1, 1, 1, 3, 4, 5)
z <- c(NA, 1, 1, 3, 4, NA)
 

чтобы получить:

 x  y  z
1  1  NA
1  1  1
2  1  1
3  3  3
4  4  4
5  4  NA
 

и я хотел получить условный оператор таким образом, что если все значения x, y и z, отличные от NA, равны 1, то они будут помечены как 1, как мне приступить к написанию этого сценария?

Например, я хочу следующее:

 x  y  z  flag1
1  1  NA 1
1  1  1  1
2  1  1  0
3  3  3  0
4  4  4  0
5  4  NA 0
 

Кроме того, я также хотел бы указать, содержит ли какая-либо из переменных 4, игнорируя NA, чтобы я мог получить:

 x  y  z  flag1 flag2
1  1  NA 1     0
1  1  1  1     0
2  1  1  0     0
3  3  3  0     0
4  4  4  0     1
5  4  NA 0     1
 

Ответ №1:

Проще всего с rowSums

 df$flag <-   (!rowSums(df != 1, na.rm = TRUE) amp; !!rowSums(!is.na(df)))
df$flag2 <-  (rowSums(df == 4, na.rm = TRUE) > 0 amp; !!rowSums(!is.na(df)))
 

-вывод

 > df
  x y  z flag flag2
1 1 1 NA    1     0
2 1 1  1    1     0
3 2 1  1    0     0
4 3 3  3    0     0
5 4 4  4    0     1
6 5 4 NA    0     1
 

In tidyverse , мы можем использовать if_all with if_any для создания этих столбцов

 library(dplyr)
df %>%
    mutate(flag1 =  (if_all(everything(),  ~is.na(.)| . %in% 1)), 
            flag2 =  (if_any(x:z, ~ . %in% 4)))
  x y  z flag1 flag2
1 1 1 NA     1     0
2 1 1  1     1     0
3 2 1  1     0     0
4 3 3  3     0     0
5 4 4  4     0     1
6 5 4 NA     0     1
 

данные

 df <-structure(list(x = c(1, 1, 2, 3, 4, 5), y = c(1, 1, 1, 3, 4, 
4), z = c(NA, 1, 1, 3, 4, NA)), class = "data.frame", row.names = c(NA, 
-6L))
 

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

1. Когда я запускаю этот аналогичный код в своем фактическом наборе данных, в котором есть наблюдения со всеми NA, он помечается как «1». Есть ли способ игнорировать наблюдения, где все это NA?

2. @ssjjaca я думал об этом. Раньше я думал, что это не так. Вы можете либо добавить условие с is.na помощью (как в обновлении), либо пропустить эти строки в вычислении с помощью is.na

Ответ №2:

Вот версия, которая более подробная, чем ответ @Akrun (и медленнее для больших наборов данных), но более настраиваемая:

 flag1 <- ifelse( (x == 1 | is.na(x) ) amp;
                 (y == 1 | is.na(y) ) amp;
                 (z == 1 | is.na(z) ), 1, 0)

flag2 <- ifelse( x == 4 | y == 4 | z == 4, 1, 0)
 

Если бы у вас была куча этих векторов, вы могли бы сохранить их в matrix или data.frame, поэтому вам не нужно перечислять каждый столбец для выполнения вычисления:

 mat <- cbind(x,y,z)

flag1 <- apply(mat, 1, function(r) sum(r==1 | is.na(r)) == length(r))
flag2 <- apply(mat, 1, function(r) any(r==4, na.rm=T))
 

Ответ №3:

Использование функции apply:

 apply(df, 1, function(x)  all(x == 1,na.rm = 1))
[1] 1 1 0 0 0 0
apply(df, 1, function(x)  any(x == 4,na.rm = 1))
[1] 0 0 0 0 1 0
 

Используемые данные:

 df
  x y  z
1 1 1 NA
2 1 1  1
3 2 1  1
4 3 3  3
5 4 4  4
6 5 5 NA
 

Ответ №4:

Вот дополнительный альтернативный способ поворота с использованием all и any :

 library(tidyr)
library(dplyr)

df %>% 
  pivot_longer(
    cols=everything()
  ) %>% 
  mutate(id = as.integer(gl(n(), 3, n()))) %>% 
  group_by(id) %>% 
  mutate(flag1 = ifelse(all(value == 1, na.rm=TRUE), 1,0),
         flag2 = ifelse(any(value == 4, na.rm=TRUE), 1,0)) %>% 
  pivot_wider(
    names_from = name, 
    values_from = value
  ) %>% 
  ungroup() %>% 
  select(x,y,z,flag1, flag2)
 

вывод:

       x     y     z flag1 flag2
  <dbl> <dbl> <dbl> <dbl> <dbl>
1     1     1    NA     1     0
2     1     1     1     1     0
3     2     1     1     0     0
4     3     3     3     0     0
5     4     4     4     0     1
6     5     4    NA     0     1
 

Ответ №5:

 library(tidyverse)

df = tibble(
  x = c(1, 1, 2, 3, 4, 5),
  y = c(1, 1, 1, 3, 4, 5),
  z = c(NA, 1, 1, 3, 4, NA)
)


df %>% mutate(
  flag1 = ifelse((x==1 | is.na(x)) amp; (y==1 | is.na(y)) amp; (z==1 | is.na(z)), 1, 0),
  flaf2 = ifelse((x==4 | is.na(x)) | (y==4 | is.na(y)) | (z==4 | is.na(z)), 1, 0)
)

 

вывод

 # A tibble: 6 x 5
      x     y     z flag1 flaf2
  <dbl> <dbl> <dbl> <dbl> <dbl>
1     1     1    NA     1     1
2     1     1     1     1     0
3     2     1     1     0     0
4     3     3     3     0     0
5     4     4     4     0     1
6     5     5    NA     0     1
 

Обновление 1

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

 library(tidyverse)

df = tibble(
  x = c(1, 1, 2, 3, 4, 5, NA),
  y = c(1, 1, 1, 3, 4, 5, NA),
  z = c(NA, 1, 1, 3, 4, NA, NA)
)


df %>% mutate(
  flag1 = ifelse(is.na(x) amp; is.na(y) amp; is.na(z), NA, 
                 ifelse((x==1 | is.na(x)) amp; (y==1 | is.na(y)) amp; (z==1 | is.na(z)), 1, 0)),
  flag2 = ifelse(is.na(x) amp; is.na(y) amp; is.na(z), NA,
                 ifelse((x==4 | is.na(x)) | (y==4 | is.na(y)) | (z==4 | is.na(z)), 1, 0))
)

 

вывод

 # A tibble: 7 x 5
      x     y     z flag1 flag2
  <dbl> <dbl> <dbl> <dbl> <dbl>
1     1     1    NA     1     1
2     1     1     1     1     0
3     2     1     1     0     0
4     3     3     3     0     0
5     4     4     4     0     1
6     5     5    NA     0     1
7    NA    NA    NA    NA    NA
 

Ответ №6:

Вот вариант, использующий rowwise и c_across :

 library(dplyr)

df %>% 
  rowwise() %>% 
  mutate(flag1 = as.numeric(all(c_across() == 1, na.rm = T)),
         flag2 = as.numeric(any(c_across() == 4, na.rm = T))) %>% 
  ungroup()
 

c_across объединит каждую строку в атомарный вектор для сравнения с вашим условием.

Примечание: по умолчанию c_across работает во всех столбцах. Вы можете изменить это с помощью любого синтаксиса tidyselect. Например, x:z .

Вывод

       x     y     z flag1 flag2
  <dbl> <dbl> <dbl> <dbl> <dbl>
1     1     1    NA     1     0
2     1     1     1     1     0
3     2     1     1     0     0
4     3     3     3     0     0
5     4     4     4     0     1
6     5     4    NA     0     1