如何仅在没有 NA 的情况下检查一组变量中是否存在某个值?

How to check for the existence of a certain value in a set of variables only when there is no NA?

我有一个包含数百个变量的数据框,按不同的因素(“Happy_”、“Sad_”等)分组,我想创建一组新变量来指示参与者是否将 4 的评级放在任何一个因素中的变量。但是,如果该因子中的任何变量为 NA,则新变量也将输出 NA。

我试过以下方法,但没有用:

library(tidyverse)
df <- data.frame(Subj = c("A", "B", "C", "D"),
                 Happy_1_Num = c(4,2,2,NA),
                 Happy_2_Num = c(4,2,2,1),
                 Happy_3_Num = c(1,NA,2,4),
                 Sad_1_Num = c(2,1,4,3),
                 Sad_2_Num = c(NA,1,2,4),
                 Sad_3_Num = c(4,2,2,1))

# Don't work
df <- df %>% mutate(Happy_Any4 = ifelse(if_any(matches("^Happy_") & matches("_Num$"), ~ is.na(.)), NA,
                                                                 ifelse(if_any(matches("^Happy_") & matches("_Num$"), ~ . == 4),1,0)),
                    Sad_Any4 = ifelse(if_any(matches("^Sad_") & matches("_Num$"), ~ is.na(.)), NA,
                                      ifelse(if_any(matches("^Sad_") & matches("_Num$"), ~ . == 4),1,0)))

我尝试了一种解决方法,首先生成一组变量来指示该因素是否有任何 NA,然后检查参与者是否给出了“4”的任何评级。有用;但由于我有很多因素,我想知道是否有更优雅的方法。

# workaround
df <- df %>% mutate(
  NA_Happy = ifelse(if_any(matches("^Happy_") & matches("_Num$"), ~ is.na(.)), 1,0),
  NA_Sad = ifelse(if_any(matches("^Sad_") & matches("_Num$"), ~ is.na(.)), 1,0))

df <- df %>% mutate(
  Happy_Any4 = ifelse(NA_Happy == 1, NA,
                        ifelse(if_any(matches("^Happy_") & matches("_Num$"), ~ . == 4),1,0)),
  Sad_Any4 = ifelse(NA_Sad == 1, NA,
                        ifelse(if_any(matches("^Sad_") & matches("_Num$"), ~ . == 4),1,0)))

这是另一种变通方法,它通过转置 data.frame 并在冒号上应用。我不确定它是否更优雅,但它在这里 ^^

tmp <- cbind(sub("^((Happy)|(Sad))(_.*_Num)$", "\1", colnames(df)), t(df))
Happy_Any4 <- apply(tmp[tmp[,1]== "Happy", -1], 2, 
                    function(x) ifelse(any(is.na(x)), NA, length(grep("4", x))) )
Sad_Any4 <- apply(tmp[tmp[,1]== "Sad", -1], 2, 
                    function(x) ifelse(any(is.na(x)), NA, length(grep("4", x))) )

df <- cbind(df, Happy_Any4 = Happy_Any4, Sad_Any4 = Sad_Any4)

编辑:上面是一个奇怪的测试,但现在这个工作更漂亮了!

这是因为任何有 NA 的东西的总和将 return NA。

df <- df %>% mutate(Happy_Any4 = apply(df[,grep("^Happy_.*_Num$", colnames(df))], 
                                       1, function(x) 1*(sum(x == 4) > 0)),
                    Sad_Any4 = apply(df[, grep("^Sad_.*_Num$", colnames(df))], 
                                     1, function(x) 1*(sum(x == 4) > 0)))

apply 将查找每一行,仅在我们在 colnames 中找到正确部分的列上查找(使用 grep。然后它会找到每一次出现的 4,它们形成一个逻辑向量,并且sum 是出现的次数。NA 的存在将使总和达到 NA。然后我只检查总和是否大于 0,1* 将将数字转换为逻辑。

这是使用 split.default -

的基础 R 选项
tmp <- df[-1]
cbind(df, sapply(split.default(tmp, sub('_.*', '', names(tmp))), 
                 function(x) as.integer(rowSums(x== 4) > 0)))

#  Subj Happy_1_Num Happy_2_Num Happy_3_Num Sad_1_Num Sad_2_Num Sad_3_Num Happy Sad
#1    A           4           4           1         2        NA         4     1  NA
#2    B           2           2          NA         1         1         2    NA   0
#3    C           2           2           2         4         2         2     0   1
#4    D          NA           1           4         3         4         1    NA   1

sub 将仅保留 "Happy""Sad" 部分名称,split.default 以此为基础拆分数据并使用 sapply 进行计算如果任何值 4 出现在一行中。


如果你有能力手动编写每个因素,你可以做到 -

library(dplyr)

df %>%
  mutate(Happy = as.integer(rowSums(select(., starts_with('Happy')) == 4) > 0), 
         Sad = as.integer(rowSums(select(., starts_with('Sad')) == 4) > 0))