通过 ID(不是行号)和列名识别数据框中 NA 值的位置

identifying location of NA values in a data frame by ID (not row number) and column name

我进行了一项调查,其中一些问题没有得到一些参与者的回答。这是我的数据的简化版本

df <- data.frame(ID = c(12:16), Q1 = c("a","b","a","a",NA), 
      Q2 = c("a","a",NA,"b",NA), Q3 = c(NA,"a","a","a","b"))
df

我想看看哪些身份证号码没有回答哪些问题。以下代码非常接近我想要的输出,但通过行号标识主题 - 我希望通过 ID 号

标识主题
table(data.frame(which(is.na(df), arr.ind=TRUE)))

现在,输出显示第 1、3、5 行至少没有回答一个问题,它标识了具有缺失值的列。我希望它显示相同的内容,但 ID 号为 12、14、16。如果您也可以在输出中使用列名(例如 Q1、Q2、Q3)而不是列号,那就更好了。

我们可以使用 apply 获取按行 NA 的列 names 并将其变成逗号分隔的字符串并将其连同它的 ID.

new_df <- data.frame(ID =df$ID, ques = apply(df, 1, function(x) 
               paste0(names(which(is.na(x))), collapse = ",")))

new_df

#  ID  ques
#1 12    Q3
#2 13      
#3 14    Q2
#4 15      
#5 16 Q1,Q2

类似的等价物是

new_df <- data.frame(ID = df$ID, ques = apply(is.na(df), 1, function(x) 
             paste0(names(which(x)), collapse = ",")))

如果您希望避免 apply 类型的操作并从 which(..., T) 继续,您可以执行如下操作:

tmp <- data.frame(which(is.na(df[, 2:4]), T))
# change to character
tmp[, 2] <- paste0('Q', tmp[, 2])
# gather column numbers together for each row number
tmp_split <- split(tmp[, 2], tmp[, 1])

# preallocate new column in df
df$missing <- vector('list', 5)
df$missing[as.numeric(names(tmp_split))] <- tmp_split

这会产生

> df
  ID   Q1   Q2   Q3 missing
1 12    a    a <NA>      Q3
2 13    b    a    a    NULL
3 14    a <NA>    a      Q2
4 15    a    b    a    NULL
5 16 <NA> <NA>    b  Q1, Q2

tidyverse怎么样:

数据:

library(tidyverse)
df <- data.frame(ID = c(12:16), Q1 = c("a","b","a","a",NA), Q2 = c("a","a",NA,"b",NA), Q3 = c(NA,"a","a","a","b"))

代码:

x <- df %>% filter(is.na(Q1) | is.na(Q2) | is.na(Q3)) # filter out NAs

y <- cbind(x %>% select(ID),
      x %>% select(Q1, Q2, Q3) %>% sapply(., function(x) ifelse(is.na(x), 1, 0))
) # in 1/0 format

输出: x:

  ID   Q1   Q2   Q3
1 12    a    a <NA>
2 14    a <NA>    a
3 16 <NA> <NA>    b

y:

  ID Q1 Q2 Q3
1 12  0  0  1
2 14  0  1  0
3 16  1  1  0

您可以使用 tidyr::gather 将数据转换为长格式。 Answer 的过滤器不可用。最后,您可以使用 toString 将您的数据总结为:

library(tidyverse)

df %>% gather(Question, Ans, -ID) %>%
  filter(is.na(Ans)) %>%
  group_by(ID) %>%
  summarise(NotAnswered = toString(Question))
# # A tibble: 3 x 2
#      ID NotAnswered
#   <int> <chr>     
# 1    12 Q3        
# 2    14 Q2        
# 3    16 Q1, Q2

如果,OP 想要在结果中包含所有 IDs 那么,解决方案可以是:

df %>% gather(Question, Ans, -ID) %>%
  group_by(ID) %>%
  summarise(NoAnswered = toString(Question[is.na(Ans)])) %>%
  as.data.frame()

#   ID NoAnswered
# 1 12         Q3
# 2 13           
# 3 14         Q2
# 4 15           
# 5 16     Q1, Q2

我的尝试并不比任何已经提供的更好,但这是一个有趣的问题,所以这是我的。为什么不呢?:

library( magrittr )

df$ques <- df %>%
    is.na() %>%
    apply( 1, function(x) {
        x %>%
            which() %>%
            names() %>%
            paste0( collapse = "," )
    } )

df

#   ID   Q1   Q2   Q3  ques
# 1 12    a    a <NA>    Q3
# 2 13    b    a    a      
# 3 14    a <NA>    a    Q2
# 4 15    a    b    a      
# 5 16 <NA> <NA>    b Q1,Q2

基地 R:

res <- df[!complete.cases(df),]
res[-1] <- as.numeric(is.na(res[-1]))
res
#    ID Q1 Q2 Q3
# 12 12  0  0  1
# 14 14  0  1  0
# 16 16  1  1  0

大部分答案来自您的问题:

df[which(is.na(df), arr.ind=TRUE)[,1],]
#     ID   Q1   Q2   Q3
# 5   16 <NA> <NA>    b
# 3   14    a <NA>    a
# 5.1 16 <NA> <NA>    b
# 1   12    a    a <NA>