In R, sample n rows from a df in which a certain column has non-NA values (sample conditionally)

Viewed 612

Background

Here's a toy df:

df <- data.frame(ID = c("a","b","c","d","e","f"), 
                gender = c("f","f","m","f","m","m"), 
                zip = c(48601,NA,29910,54220,NA,44663),stringsAsFactors=FALSE)

As you can see, I've got a couple of NA values in the zip column.

Problem

I'm trying to randomly sample 2 entire rows from df -- but I want them to be rows for which zip is not null.

What I've tried

This code gets me a basic (i.e. non-conditional) random sample:

df2 <- df[sample(nrow(df), 2), ]

But of course, that only gets me halfway to my goal -- a bunch of the time it's going to return a row with an NA value in zip. This code attempts to add the condition:

df2 <- df[sample(nrow(df$zip != NA), 2), ]

I think I'm close, but this yields an error invalid first argument.

Any ideas?

5 Answers

We can use is.na

tmp <- df[!is.na(df$zip),]
> tmp[sample(nrow(tmp), 2),]

Here is a base R solution with complete.cases()

# define a logical vector to identify NA
x <- complete.cases(df)

# subset only not NA values
df_no_na <- df[x,]

# do the sample
df_no_na[sample(nrow(df_no_na), 2),]

Output:

  ID gender   zip
3  c      m 29910
6  f      m 44663

We can use rownames + na.omit to sample the rows

> df[sample(rownames(na.omit(df["zip"])), 2),]
  ID gender   zip
3  c      m 29910
4  d      f 54220

For the tidyverse lovers out there...

library("dplyr")
df %>% 
  tidyr::drop_na() %>% 
  dplyr::slice_sample(n = 2)

If it only NA in the zip column you care about, then:

df %>% 
  tidyr::drop_na(zip) %>% 
  dplyr::slice_sample(n = 2)

The important thing here is to avoid creating an unnecessary second data frame with the NA values dropped. You could use the solution using na.omit given in another answer, but alternatively you can use which to return a list of valid rows to sample from. For example:

nsamp <- 23
df[sample(which(!is.na(df$zip)), nsamp), ]

The advantage to doing it this way is that the condition inside the which can be anything you like, whether or not it involves missing values. For example this version will sample from all the rows with female gender in zip codes starting with 336:

df[sample(which(df$gender=='f' & grepl('^336', df$zip)), nsamp), ]
Related