How do I add random `NA`s into a data frame

Viewed 7081

I created a data frame with random values

n <- 50
df <- data.frame(id = seq (1:n),
age = sample(c(20:90), n, rep = TRUE), 
sex = sample(c("m", "f"), n, rep = TRUE, prob = c(0.55, 0.45))
)

and would like to introduce a few NA values to simulate real world data. I am trying to use apply but cannot get there. The line

apply(subset(df,select=-id), 2, function(x) {x[sample(c(1:n),floor(n/10))]})

will retrieve random values alright, but

apply(subset(df,select=-id), 2, function(x) {x[sample(c(1:n),floor(n/10))]<-NA}) 

will not set them to NA. Have tried with and within, too.

Brute force works:

for (i in (1:floor(n/10))) {
  df[sample(c(1:n), 1), sample(c(2:ncol(df)), 1)] <- NA
  }

But I'd prefer to use the apply family.

8 Answers

Using dplyr1 you could arrive at the desired solution using the following, compact, syntax:

set.seed(123)
library("tidyverse")
n <- 50
df <- data.frame(
  id = seq (1:n),
  age = sample(c(20:90), n, replace  = TRUE),
  sex = sample(c("m", "f"), n, replace = TRUE, prob = c(0.55, 0.45))
)
mutate(.data = as_tibble(df),
       across(
         .cols = all_of(c("age", "sex")),
         .fns = ~ ifelse(row_number(.x) %in% sample(1:n(), size = (10 * n(
         ) / 100)), NA, .x)
       ))

Results

Approximatly 10% of values is replaced with NA per column. This follows from sample(1:n(), size = (10 * n() / 100))

count(.Last.value, sex)
#   A tibble: 3 x 2
#   sex       n
#   <chr> <int>
# 1 f        21
# 2 m        24
# 3 NA        5

#  A tibble: 50 x 3
#      id   age sex  
#   <int> <int> <chr>
# 1     1    50 m    
# 2     2    70 m  

1 I'm loading tidyverse as replace_na is available via tidyr.

To introduce certain percentage of NAs in your dataframe you could use this:

while(sum(is.na(df) == TRUE) < (nrow(df) * ncol(df) * percentage/100)){
  df[sample(nrow(df),1), sample(ncol(df),1)] <- NA
}

you could also change "(nrow(df) * ncol(df) * percentage/100)" to a fixed number of NAs

You can also use prodNA from the missForest package.

library(missForest)
library(dplyr)

> bind_cols(df[1],missForest::prodNA(df[-1],noNA=0.1))

# A tibble: 50 x 3
      id   age sex  
   <int> <int> <fct>
 1     1    NA m    
 2     2    84 NA   
 3     3    82 f    
 4     4    42 f    
 5     5    35 m    
 6     6    80 m    
 7     7    90 f    
 8     8    NA NA   
 9     9    89 f    
10    10    42 m    
# … with 40 more rows
Related