DOI CRAN_Status_Badge CRAN_Release_Badge Codecov test coverage Build Status R-CMD-check-devel test-coverage Project Status lifecycle license Downloads TotalDownloads GitHub last commit GitHub issues GitHub issues-closed PRs Welcome Maintenance

The goal of mde is to ease exploration of missingness.

Loading the package


library(mde)

Recoding as NA

As the name might imply, this converts any value or vector of values to NA i.e. we take a value such as “missing” or “NA” (not a real NA according to R) and convert it to R’s known handler for missing values (NA).

To use the function out of the box (with default arguments), one simply does something like:


dummy_test <- data.frame(ID = c("A","B","B","A"), 
                         values = c("n/a",NA,"Yes","No"))
# Convert n/a and no to NA
head(recode_as_na(dummy_test, value = c("n/a","No")))
#>   ID values
#> 1  A   <NA>
#> 2  B   <NA>
#> 3  B    Yes
#> 4  A   <NA>

Great, but I want to do so for specific columns not the entire dataset. You can do this by providing column names to subset_cols.



another_dummy <- data.frame(ID = 1:5, Subject = 7:11, 
Change = c("missing","n/a",2:4 ))
# Only change values at the column Change
head(recode_as_na(another_dummy, subset_cols = "Change", value = c("n/a","missing")))
#>   ID Subject Change
#> 1  1       7   <NA>
#> 2  2       8   <NA>
#> 3  3       9      2
#> 4  4      10      3
#> 5  5      11      4

To recode columns using RegEx,one can provide pattern_type and a target pattern. Currently supported pattern_types are starts_with, ends_with, contains and regex. See docs for more details.:

# only change at columns that start with Solar
head(recode_as_na(airquality,value=190,pattern_type="starts_with",pattern="Solar"))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41      NA  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5    NA      NA 14.3   56     5   5
#> 6    28      NA 14.9   66     5   6
# recode at columns that start with O or S(case sensitive)
head(recode_as_na(airquality,value=c(67,118),pattern_type="starts_with",pattern="S|O"))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36      NA  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5    NA      NA 14.3   56     5   5
#> 6    28      NA 14.9   66     5   6
# use my own RegEx
head(recode_as_na(airquality,value=c(67,118),pattern_type="regex",pattern="(?i)^(s|o)"))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36      NA  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5    NA      NA 14.3   56     5   5
#> 6    28      NA 14.9   66     5   6

This function allows one to deliberately introduce missing values if a column meets a certain threshold of missing values. This is similar to amputation but is much more basic. It is only provided here because it is hoped it may be useful to someone for whatever reason.


head(recode_as_na_if(airquality,sign="gt", percent_na=20))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    NA     190  7.4   67     5   1
#> 2    NA     118  8.0   72     5   2
#> 3    NA     149 12.6   74     5   3
#> 4    NA     313 11.5   62     5   4
#> 5    NA      NA 14.3   56     5   5
#> 6    NA      NA 14.9   66     5   6

This allows recoding as NA based on a string match.


partial_match <- data.frame(A=c("Hi","match_me","nope"), B=c(NA, "not_me","nah"))

recode_as_na_str(partial_match,"ends_with","ME", case_sensitive=FALSE)
#>      A    B
#> 1   Hi <NA>
#> 2 <NA> <NA>
#> 3 nope  nah

For all values greater/less/less or equal/greater or equal than some value, can I convert them to NA?!

Yes You Can! All we have to do is use recode_as_na_for:


head(recode_as_na_for(airquality,criteria="gt",value=25))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    NA      NA  7.4   NA     5   1
#> 2    NA      NA  8.0   NA     5   2
#> 3    12      NA 12.6   NA     5   3
#> 4    18      NA 11.5   NA     5   4
#> 5    NA      NA 14.3   NA     5   5
#> 6    NA      NA 14.9   NA     5   6

To do so at specific columns, pass an optional subset_cols character vector:


head(recode_as_na_for(airquality, value=40,subset_cols=c("Solar.R","Ozone"), criteria="gt"))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    NA      NA  7.4   67     5   1
#> 2    36      NA  8.0   72     5   2
#> 3    12      NA 12.6   74     5   3
#> 4    18      NA 11.5   62     5   4
#> 5    NA      NA 14.3   56     5   5
#> 6    28      NA 14.9   66     5   6

Recoding NA as

Sometimes, for whatever reason, one would like to replace NAs with whatever value they would like. recode_na_as provides a very simple way to do just that.


head(recode_na_as(airquality))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5     0       0 14.3   56     5   5
#> 6    28       0 14.9   66     5   6

# use NaN

head(recode_na_as(airquality, value=NaN))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5   NaN     NaN 14.3   56     5   5
#> 6    28     NaN 14.9   66     5   6

As a “bonus”, you can manipulate the data only at specific columns as shown here:


head(recode_na_as(airquality, value=0, subset_cols="Ozone"))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5     0      NA 14.3   56     5   5
#> 6    28      NA 14.9   66     5   6

The above also supports custom recoding similar to recode_na_as:


head(mde::recode_na_as(airquality, value=0, pattern_type="starts_with",pattern="Solar"))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5    NA       0 14.3   56     5   5
#> 6    28       0 14.9   66     5   6

Ever needed to change values in a given column based on the proportions of NAs in other columns(row-wise)?!. The goal of column_based_recode is to achieve just that. Let’s see how we could do this with a simple example:



head(column_based_recode(airquality, values_from = "Wind", values_to="Wind", pattern_type = "regex", pattern = "Solar|Ozone"))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5    NA      NA  0.0   56     5   5
#> 6    28      NA 14.9   66     5   6

This allows recoding NA values with common stats functions such as mean,max,min,sd.

To use default values:


head(custom_na_recode(airquality))
#>      Ozone  Solar.R Wind Temp Month Day
#> 1 41.00000 190.0000  7.4   67     5   1
#> 2 36.00000 118.0000  8.0   72     5   2
#> 3 12.00000 149.0000 12.6   74     5   3
#> 4 18.00000 313.0000 11.5   62     5   4
#> 5 42.12931 185.9315 14.3   56     5   5
#> 6 28.00000 185.9315 14.9   66     5   6

To use select columns:




head(custom_na_recode(airquality,func="mean",across_columns=c("Solar.R","Ozone")))
#>      Ozone  Solar.R Wind Temp Month Day
#> 1 41.00000 190.0000  7.4   67     5   1
#> 2 36.00000 118.0000  8.0   72     5   2
#> 3 12.00000 149.0000 12.6   74     5   3
#> 4 18.00000 313.0000 11.5   62     5   4
#> 5 42.12931 185.9315 14.3   56     5   5
#> 6 28.00000 185.9315 14.9   66     5   6

To use a function from another package to perform replacements:

To perform a forward fill with dplyr’s lead:


# use lag for a backfill
head(custom_na_recode(airquality,func=dplyr::lead ))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5    23      99 14.3   56     5   5
#> 6    28      19 14.9   66     5   6

To perform replacement by group:


some_data <- data.frame(ID=c("A1","A1","A1","A2","A2", "A2"),A=c(5,NA,0,8,3,4),B=c(10,0,0,NA,5,6),C=c(1,NA,NA,25,7,8))

head(custom_na_recode(some_data,func = "mean", grouping_cols = "ID"))
#> # A tibble: 6 x 4
#>   ID        A     B     C
#>   <chr> <dbl> <dbl> <dbl>
#> 1 A1      5    10       1
#> 2 A1      2.5   0       1
#> 3 A1      0     0       1
#> 4 A2      8     5.5    25
#> 5 A2      3     5       7
#> 6 A2      4     6       8

Across specific columns:


head(custom_na_recode(some_data,func = "mean", grouping_cols = "ID", across_columns = c("C", "A")))
#> # A tibble: 6 x 4
#>   ID        A     B     C
#>   <chr> <dbl> <dbl> <dbl>
#> 1 A1      5      10     1
#> 2 A1      2.5     0     1
#> 3 A1      0       0     1
#> 4 A2      8      NA    25
#> 5 A2      3       5     7
#> 6 A2      4       6     8

Given a data.frame object, one can recode NAs as another value based on a grouping variable. In the example below, we replace all NAs in all columns with 0s if the ID is A2 or A3


some_data <- data.frame(ID=c("A1","A2","A3", "A4"), 
                        A=c(5,NA,0,8), B=c(10,0,0,1),
                        C=c(1,NA,NA,25))
                        
head(recode_na_if(some_data,grouping_col="ID", target_groups=c("A2","A3"),
           replacement= 0))   
#> # A tibble: 4 x 4
#>   ID        A     B     C
#>   <chr> <dbl> <dbl> <dbl>
#> 1 A1        5    10     1
#> 2 A2        0     0     0
#> 3 A3        0     0     0
#> 4 A4        8     1    25

Dropping Missing Values

Suppose you wanted to drop any column that has a percentage of NAs greater than or equal to a certain value? drop_na_if does just that.

We can drop any columns that have greater than or equal(gteq) to 24% of the values missing from airquality:


head(drop_na_if(airquality, sign="gteq",percent_na = 24))
#>   Solar.R Wind Temp Month Day
#> 1     190  7.4   67     5   1
#> 2     118  8.0   72     5   2
#> 3     149 12.6   74     5   3
#> 4     313 11.5   62     5   4
#> 5      NA 14.3   56     5   5
#> 6      NA 14.9   66     5   6

The above also supports less than or equal to(lteq), equal to(eq), greater than(gt) and less than(lt).

To keep certain columns despite fitting the target percent_na criteria, one can provide an optional keep_columns character vector.



head(drop_na_if(airquality, percent_na = 24, keep_columns = "Ozone"))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5    NA      NA 14.3   56     5   5
#> 6    28      NA 14.9   66     5   6

Compare the above result to the following:


head(drop_na_if(airquality, percent_na = 24))
#>   Solar.R Wind Temp Month Day
#> 1     190  7.4   67     5   1
#> 2     118  8.0   72     5   2
#> 3     149 12.6   74     5   3
#> 4     313 11.5   62     5   4
#> 5      NA 14.3   56     5   5
#> 6      NA 14.9   66     5   6

To drop groups that meet a set missingness criterion, we proceed as follows.

grouped_drop <- structure(list(ID = c("A", "A", "B", "A", "B"), 
          Vals = c(4, NA,  NA, NA, NA), Values = c(5, 6, 7, 8, NA)), 
          row.names = c(NA, -5L), class = "data.frame")
# Drop all columns for groups that meet a percent missingness of greater than or
# equal to 67
drop_na_if(grouped_drop,percent_na = 67,sign="gteq",
                                    grouping_cols = "ID")
#> # A tibble: 3 x 3
#>   ID     Vals Values
#>   <chr> <dbl>  <dbl>
#> 1 A         4      5
#> 2 A        NA      6
#> 3 A        NA      8

This is similar to drop_na_if but does operations rowwise not columnwise. Compare to the example above:

# Drop rows with at least two NAs
head(drop_row_if(airquality, sign="gteq", type="count" , value = 2))
#> Dropped 2 rows.
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 6    28      NA 14.9   66     5   6
#> 7    23     299  8.6   65     5   7

To drop based on percentages:

# Drops 42 rows
head(drop_row_if(airquality, type="percent", value=16, sign="gteq",
                 as_percent=TRUE))
#> Dropped 42 rows.
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 7    23     299  8.6   65     5   7
#> 8    19      99 13.8   59     5   8

For more details, please see the documentation of drop_row_if.

This provides a simple way to drop missing values only at specific columns. It currently only returns those columns with their missing values removed. See usage below. Further details are given in the documentation. It is currently case sensitive.


head(drop_na_at(airquality,pattern_type = "starts_with","O"))
#>   Ozone
#> 1    41
#> 2    36
#> 3    12
#> 4    18
#> 5    28
#> 6    23

This drops columns where all values are missing.


test2 <- data.frame(ID= c("A","A","B","A","B"), Vals = c(4,rep(NA, 4))) 

drop_all_na(test2, grouping_cols="ID")
#> # A tibble: 3 x 2
#>   ID     Vals
#>   <chr> <dbl>
#> 1 A         4
#> 2 A        NA
#> 3 A        NA

Alternatively, we can drop groups where all variables are all NA.


test2 <- data.frame(ID= c("A","A","B","A","B"), Vals = rep(NA, 5)) 

head(drop_all_na(test2, grouping_cols = "ID"))
#> # A tibble: 0 x 2
#> # ... with 2 variables: ID <chr>, Vals <lgl>

If one would like to recode column values using a “dictionary”, dict_recode provides a simple way to do that. For example, if one would like to convert NA values in Solar.R to 520 and those in Ozone to 42, one simply calls the following:

head(dict_recode(airquality, use_func="recode_na_as",
                 patterns = c("solar", "ozone"),
                 pattern_type="starts_with", values = c(520,42)))
#>   Ozone Solar.R Wind Temp Month Day
#> 1    41     190  7.4   67     5   1
#> 2    36     118  8.0   72     5   2
#> 3    12     149 12.6   74     5   3
#> 4    18     313 11.5   62     5   4
#> 5    42     520 14.3   56     5   5
#> 6    28     520 14.9   66     5   6

Please note that the mde project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

For further exploration, please browseVignettes("mde").

To raise an issue, please do so here

Thank you, feedback is always welcome :)