names(df)[!!colSums(is.na(df))] #[1] "b" "c"
Explanation
colSums(is.na(df))
Using ! we create a logical index
!colSums(is.na(df))
But we need to select those columns that have at least one NA , therefore ! cancel again
!!colSums(is.na(df))
and use this boolean index to get code names that have at least one NA
Benchmarks
set.seed(49) df1 <- as.data.frame(matrix(sample(c(NA,1:200), 1e4*5000, replace=TRUE), ncol=5000)) library(microbenchmark) f1 <- function() {contains_any_na = sapply(df1, function(x) any(is.na(x))) names(df1)[contains_any_na]} f2 <- function() {colnames(df1)[!complete.cases(t(df1))] } f3 <- function() { names(df1)[!!colSums(is.na(df1))] } microbenchmark(f1(), f2(), f3(), unit="relative")
Explanation of the effectiveness of EDIT:
Perhaps the amazing sapply based sapply is the winner here because, as noted in @flodel's comment below, 2 other solutions created matrix behind the scenes ( t(df) and is.na(df) ), creating the matrix.
akrun source share