У меня есть опрос, в котором некоторые участники не ответили на некоторые вопросы. Вот упрощенная версия моих данных
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
Я хотел бы узнать, какие идентификационные номера не отвечали на какие вопросы. Следующий код очень близок к результату, который я хочу, но идентифицирует тему по номеру строки - я бы хотел, чтобы субъект идентифицировался по идентификационному номеру
table(data.frame(which(is.na(df), arr.ind=TRUE)))
прямо сейчас выходные данные показывают, что строки 1,3,5 не ответили хотя бы на один вопрос, и он идентифицирует столбец с отсутствующим значением. Я бы хотел, чтобы он показал мне то же самое, но с номерами 12,14,16. Было бы неплохо, если бы вы могли иметь имена столбцов (например, Q1, Q2, Q3) в выводе вместо номера столбца.





Мы можем получить столбец names, который является строкой NA, используя apply, превратить его в строку, разделенную запятыми, и прикрепить ее к новому фрейму данных вместе с его 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
выход: Икс:
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>
Это в основном то, что я хотел, но меня смущает столбец 5.1 и почему он там
Правильно, принятый ответ проще, поэтому я предлагаю его использовать. но причина дублирования строк в том, что which(..., arr.ind=TRUE) возвращает индексы строк и столбцов для каждого пропущенного значения. В строке 5 два NA, поэтому он продублирован. Вы можете обойти это с помощью df[unique(which(is.na(df), arr.ind=TRUE=[,1]),], но принятый ответ предлагает более простое решение.
Мне тоже очень нравится этот ответ. Я не буду использовать его для моего текущего набора данных, потому что у меня слишком много испытуемых, которые действительно ответили на все вопросы (а они меня сейчас не интересуют), но это определенно пригодится в будущем.