Rowwise mean imputation for groups of specified columns where >= 80% are non-NA values

Viewed 38

I want to create a function that:

  • Takes the names of a group of columns (e.g. c("colA", "colB", "colC", "colD", "colE")) preferably using tidyselect to make it flexible
  • Checks whether each row has 80% or more non-missing values
  • Imputes the rowwise mean where 80% or more columns are not NA
  • Leaves rows with less than 80% non-missing values as they are

For example...

tribble(
  ~colA, ~colB, ~colC, ~colD, ~colE, ~varA, ~varB, ~varC, ~varD, ~varE,
  1, 0, 0, 1, NA, 3, 5, 2, 1, NA,
  1, 1, 0, NA, NA, 3, 5, 2, NA, NA,
)

# A tibble: 2 × 10
   colA  colB  colC  colD colE   varA  varB  varC  varD varE 
  <dbl> <dbl> <dbl> <dbl> <lgl> <dbl> <dbl> <dbl> <dbl> <lgl>
1     1     1     0     1 NA        3     5     2     2 NA   
2     1     1     0    NA NA        3     5     2    NA NA 

This tibble has two sets of columns (cols vs vars). I would like to be able to mutate across the vars so that the mean is imputed for the row with 4/5 responses, but not the row with 3/5 responses.

The tibble should end up looking like this...

# A tibble: 2 × 10
   colA  colB  colC  colD colE   varA  varB  varC  varD  varE
  <dbl> <dbl> <dbl> <dbl> <lgl> <dbl> <dbl> <dbl> <dbl> <dbl>
1     1     0     0     1 NA        3     5     2     2     3
2     1     1     0    NA NA        3     5     2    NA    NA

Only varE has been imputed (mean of 3) for row 1.

It would also be good to be able to adjust the percentage parameter within the function in case a different threshold is required in the future.

I imagine the syntax looking something like this if the function was called mean_impute()...

dat %>%
   mutate(across(.cols = c(colA, colB, colC, colD, colE), ~mean_impute(., perc = .8)))

It would also be useful to be able to use multiple groups of column names to save repetition. But I am not sure how that would work? Perhaps some kind of mapping.

1 Answers

When computing rowwise functions first call mutate %>% rowwise() then use c_across() to create one vector from the columns you wish to apply your function to. C_across uses the same syntax and helper functions as select. It is also good practice to ungroup() after rowwise() calculations.

The below code first counts the number of NA values in the var columns, then creates a mean variable if the na_count is less than 2. You can always adjust that threshold.

df %>% rowwise() %>% 
mutate(na_count = sum(is.na(c_across(starts_with("var"))), na.rm = T), 
       mean = if_else(na_count < 2, mean(c_across(starts_with("var")), na.rm = T),
                      NA_real_) %>% ungroup()
Related