Randomly insert NAs into dataframe proportionaly
Asked Answered
C

7

14

I have a complete dataframe. I want to 20% of the values in the dataframe to be replaced by NAs to simulate random missing data.

A <- c(1:10)
B <- c(11:20)
C <- c(21:30)
df<- data.frame(A,B,C)

Can anyone suggest a quick way of doing that?

Cricoid answered 13/12, 2014 at 0:36 Comment(3)
You mean 15% of each variable? or overall observations?Microsporangium
20% is fine (i.e, 6 of the value should be NA)Cricoid
You might want to check out this answer that gives you exact proportions of NA: https://mcmap.net/q/827694/-add-exact-proportion-of-random-missing-values-to-data-frame/3871924Tanh
C
17
df <- data.frame(A = 1:10, B = 11:20, c = 21:30)
head(df)
##   A  B  c
## 1 1 11 21
## 2 2 12 22
## 3 3 13 23
## 4 4 14 24
## 5 5 15 25
## 6 6 16 26

as.data.frame(lapply(df, function(cc) cc[ sample(c(TRUE, NA), prob = c(0.85, 0.15), size = length(cc), replace = TRUE) ]))
##     A  B  c
## 1   1 11 21
## 2   2 12 22
## 3   3 13 23
## 4   4 14 24
## 5   5 NA 25
## 6   6 16 26
## 7  NA 17 27
## 8   8 18 28
## 9   9 19 29
## 10 10 20 30

It's a random process, so it might not give 15% every time.

Christianchristiana answered 13/12, 2014 at 0:50 Comment(0)
C
10

You can unlist the data.frame and then take a random sample, then put back in a data.frame.

df <- unlist(df)
n <- length(df) * 0.15
df[sample(df, n)] <- NA
as.data.frame(matrix(df, ncol=3))

It can be done a bunch of different ways using sample().

Canarese answered 13/12, 2014 at 0:55 Comment(0)
S
5

If you are in the mood to use purrr instead of lapply, you can also do it like this:

> library(purrr)
> df <- data.frame(A = 1:10, B = 11:20, C = 21:30)
> df
    A  B  C
1   1 11 21
2   2 12 22
3   3 13 23
4   4 14 24
5   5 15 25
6   6 16 26
7   7 17 27
8   8 18 28
9   9 19 29
10 10 20 30
> map_df(df, function(x) {x[sample(c(TRUE, NA), prob = c(0.8, 0.2), size = length(x), replace = TRUE)]})
# A tibble: 10 x 3
       A     B     C
   <int> <int> <int>
1      1    11    21
2      2    12    22
3     NA    13    NA
4      4    14    NA
5      5    15    25
6      6    16    26
7      7    17    27
8      8    NA    28
9      9    19    29
10    10    20    30
Stirpiculture answered 1/8, 2016 at 18:43 Comment(0)
T
2

May i suggest a first function (ggNAadd) designed to do this, and improve it with a second function providing graphical distribution of the NAs created (ggNA)

What is neat is the possibility to input either a proportion of a fixed number of NAs.

ggNAadd = function(data, amount, plot=F){
  temp <- data
  amount2 <- ifelse(amount<1, round(prod(dim(data))*amount), amount)
  if (amount2 >= prod(dim(data))) stop("exceeded data size")
  for (i in 1:amount2) temp[sample.int(nrow(temp), 1), sample.int(ncol(temp), 1)] <- NA
  if (plot) print(ggNA(temp))
  return(temp)
}

And the plotting function:

ggNA = function(data, alpha=0.5){
  require(ggplot2)
  DF <- data
  if (!is.matrix(data)) DF <- as.matrix(DF)
  to.plot <- cbind.data.frame('y'=rep(1:nrow(DF), each=ncol(DF)), 
                              'x'=as.logical(t(is.na(DF)))*rep(1:ncol(DF), nrow(DF)))
  size <- 20 / log( prod(dim(DF)) )  # size of point depend on size of table
  g <- ggplot(data=to.plot) + aes(x,y) +
    geom_point(size=size, color="red", alpha=alpha) +
    scale_y_reverse() + xlim(1,ncol(DF)) +
    ggtitle("location of NAs in the data frame") +
    xlab("columns") + ylab("lines")
  pc <- round(sum(is.na(DF))/prod(dim(DF))*100, 2) # % NA
  print(paste("percentage of NA data: ", pc))
  return(g)
}

Which gives (using ggplot2 as graphical output):

ggNAadd(df, amount=0.20, plot=TRUE)
## [1] "percentage of NA data:  20"
##     A  B  c
## 1   1 11 21
## 2   2 12 22
## 3   3 13 23
## 4   4 NA 24
## ..

enter image description here

Of course, as mentioned earlier, if you ask too many NAs the actual percentage will drop because of repetitions.

Tanh answered 6/2, 2015 at 14:38 Comment(0)
M
1

Same result, using binomial distribution:

dd=dim(df)
nna=20/100 #overall
df1<-df
df1[matrix(rbinom(prod(dd), size=1,prob=nna)==1,nrow=dd[1])]<-NA
df1
Microsporangium answered 13/12, 2014 at 1:31 Comment(0)
E
1

A mutate_all approach:

df %>% 
  dplyr::mutate_all(~ifelse(sample(c(TRUE, FALSE), size = length(.), replace = TRUE, prob = c(0.8, 0.2)),
         as.character(.), NA))
Earn answered 25/10, 2020 at 18:39 Comment(0)
V
0

There is also collapse::na_insert which is vectorized over columns, and very fast:

collapse::na_insert(df, prop = .2)

Benchmark:

microbenchmark::microbenchmark(
  lapply = as.data.frame(lapply(df, function(cc) cc[ sample(c(TRUE, NA), prob = c(0.8, 0.2), size = length(cc), replace = TRUE) ])),
  collapse = collapse::na_insert(df, prop = .2), 
  matrix = {df <- unlist(df)
  n <- length(df) * 0.15
  df[sample(df, n)] <- NA
  as.data.frame(matrix(df, ncol=3))}
)

#Unit: microseconds
#     expr      min        lq       mean    median        uq       max neval
#   lapply 1662.001 1993.2515 3882.82004 3276.5515 4339.7010 16854.700   100
# collapse    7.900   17.5505   33.16901   27.1005   44.8015    94.801   100
#   matrix   39.402   66.1515  103.34798   93.1010  130.5505   327.501   100
Vizza answered 5/5, 2023 at 13:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.