The goal of mde
is to
ease exploration of missingness.
Installation
CRAN release
Stable Development version
devtools::install_github("Nelson-Gon/mde")
devtools::install_github("Nelson-Gon/mde", build_vignettes=TRUE)
Unstable Development version
Loading the package
library(mde)
#> Welcome to mde. This is mde version 0.3.2.
#> Please file issues and feedback at https://www.github.com/Nelson-Gon/mde/issues
#> Turn this message off using 'suppressPackageStartupMessages(library(mde))'
#> Happy Exploration :)
To get a simple missingness report, use na_summary
:
na_summary(airquality)
#> variable missing complete percent_complete percent_missing
#> 1 Day 0 153 100.00000 0.000000
#> 2 Month 0 153 100.00000 0.000000
#> 3 Ozone 37 116 75.81699 24.183007
#> 4 Solar.R 7 146 95.42484 4.575163
#> 5 Temp 0 153 100.00000 0.000000
#> 6 Wind 0 153 100.00000 0.000000
To sort this summary by a given column :
na_summary(airquality,sort_by = "percent_complete")
#> variable missing complete percent_complete percent_missing
#> 3 Ozone 37 116 75.81699 24.183007
#> 4 Solar.R 7 146 95.42484 4.575163
#> 1 Day 0 153 100.00000 0.000000
#> 2 Month 0 153 100.00000 0.000000
#> 5 Temp 0 153 100.00000 0.000000
#> 6 Wind 0 153 100.00000 0.000000
To sort by percent_missing
instead:
na_summary(airquality, sort_by = "percent_missing")
#> variable missing complete percent_complete percent_missing
#> 1 Day 0 153 100.00000 0.000000
#> 2 Month 0 153 100.00000 0.000000
#> 5 Temp 0 153 100.00000 0.000000
#> 6 Wind 0 153 100.00000 0.000000
#> 4 Solar.R 7 146 95.42484 4.575163
#> 3 Ozone 37 116 75.81699 24.183007
To sort the above in descending order:
na_summary(airquality, sort_by="percent_missing", descending = TRUE)
#> variable missing complete percent_complete percent_missing
#> 3 Ozone 37 116 75.81699 24.183007
#> 4 Solar.R 7 146 95.42484 4.575163
#> 1 Day 0 153 100.00000 0.000000
#> 2 Month 0 153 100.00000 0.000000
#> 5 Temp 0 153 100.00000 0.000000
#> 6 Wind 0 153 100.00000 0.000000
To exclude certain columns from the analysis:
na_summary(airquality, exclude_cols = c("Day", "Wind"))
#> variable missing complete percent_complete percent_missing
#> 1 Month 0 153 100.00000 0.000000
#> 2 Ozone 37 116 75.81699 24.183007
#> 3 Solar.R 7 146 95.42484 4.575163
#> 4 Temp 0 153 100.00000 0.000000
To include or exclude via regex match:
na_summary(airquality, regex_kind = "inclusion",pattern_type = "starts_with", pattern = "O|S")
#> variable missing complete percent_complete percent_missing
#> 1 Ozone 37 116 75.81699 24.183007
#> 2 Solar.R 7 146 95.42484 4.575163
na_summary(airquality, regex_kind = "exclusion",pattern_type = "regex", pattern = "^[O|S]")
#> variable missing complete percent_complete percent_missing
#> 1 Day 0 153 100 0
#> 2 Month 0 153 100 0
#> 3 Temp 0 153 100 0
#> 4 Wind 0 153 100 0
To get this summary by group:
test2 <- data.frame(ID= c("A","A","B","A","B"), Vals = c(rep(NA,4),"No"),ID2 = c("E","E","D","E","D"))
na_summary(test2,grouping_cols = c("ID","ID2"))
#> # A tibble: 2 × 7
#> ID ID2 variable missing complete percent_complete percent_missing
#> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 B D Vals 1 1 50 50
#> 2 A E Vals 3 0 0 100
na_summary(test2, grouping_cols="ID")
#> Warning in na_summary.data.frame(test2, grouping_cols = "ID"): All non grouping
#> values used. Using select non groups is currently not supported
#> # A tibble: 4 × 6
#> ID variable missing complete percent_complete percent_missing
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 A Vals 3 0 0 100
#> 2 A ID2 0 3 100 0
#> 3 B Vals 1 1 50 50
#> 4 B ID2 0 2 100 0
get_na_counts
This provides a convenient way to show the number of missing values column-wise. It is relatively fast(tests done on about 400,000 rows, took a few microseconds.)
To get the number of missing values in each column of
airquality
, we can use the function as follows:
The above might be less useful if one would like to get the results
by group. In that case, one can provide a grouping vector of names in
grouping_cols
.
test <- structure(list(Subject = structure(c(1L, 1L, 2L, 2L), .Label = c("A",
"B"), class = "factor"), res = c(NA, 1, 2, 3), ID = structure(c(1L,
1L, 2L, 2L), .Label = c("1", "2"), class = "factor")), class = "data.frame", row.names = c(NA,
-4L))
get_na_counts(test, grouping_cols = "ID")
#> # A tibble: 2 × 3
#> ID Subject res
#> <fct> <int> <int>
#> 1 1 0 1
#> 2 2 0 0
percent_missing
This is a very simple to use but quick way to take a look at the percentage of data that is missing column-wise.
We can get the results by group by providing an optional
grouping_cols
character vector.
percent_missing(test, grouping_cols = "Subject")
#> # A tibble: 2 × 3
#> Subject res ID
#> <fct> <dbl> <dbl>
#> 1 A 50 0
#> 2 B 0 0
To exclude some columns from the above exploration, one can provide
an optional character vector in exclude_cols
.
percent_missing(airquality,exclude_cols = c("Day","Temp"))
#> Ozone Solar.R Wind Month
#> 1 24.18301 4.575163 0 0
sort_by_missingness
This provides a very simple but relatively fast way to sort variables by missingness. Unless otherwise stated, this does not currently support arranging grouped percents.
Usage:
sort_by_missingness(airquality, sort_by = "counts")
#> variable percent
#> 1 Wind 0
#> 2 Temp 0
#> 3 Month 0
#> 4 Day 0
#> 5 Solar.R 7
#> 6 Ozone 37
To sort in descending order:
sort_by_missingness(airquality, sort_by = "counts", descend = TRUE)
#> variable percent
#> 1 Ozone 37
#> 2 Solar.R 7
#> 3 Wind 0
#> 4 Temp 0
#> 5 Month 0
#> 6 Day 0
To use percentages instead:
recode_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
recode_as_na_if
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
recode_as_na_str
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
recode_as_na_for
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:
recode_na_as
Sometimes, for whatever reason, one would like to replace
NA
s 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
column_based_recode
Ever needed to change values in a given column based on the
proportions of NA
s 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
custom_na_recode
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 × 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 × 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
recode_na_if
Given a data.frame
object, one can recode
NA
s as another value based on a grouping variable. In the
example below, we replace all NA
s 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 × 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
drop_na_if
Suppose you wanted to drop any column that has a percentage of
NA
s 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")
#> Warning: Returning data frames from `filter()` expressions was deprecated in dplyr
#> 1.0.8.
#> ℹ Please use `if_any()` or `if_all()` instead.
#> ℹ The deprecated feature was likely used in the mde package.
#> Please report the issue at <https://github.com/Nelson-Gon/mde/issues>.
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
#> generated.
#> # A tibble: 3 × 3
#> ID Vals Values
#> <chr> <dbl> <dbl>
#> 1 A 4 5
#> 2 A NA 6
#> 3 A NA 8
drop_row_if
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
.
drop_na_at
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
drop_all_na
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 × 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(test, grouping_cols = "ID"))
#> # A tibble: 4 × 3
#> Subject res ID
#> <fct> <dbl> <fct>
#> 1 A NA 1
#> 2 A 1 1
#> 3 B 2 2
#> 4 B 3 2
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 :)