hrs_00 <- hrs_data |>
select(ed, race_original, srh, sex, act_vig, height, degree)Missing data: transformations
From finding missing data to handling it
Before we start
- Install and load the
naniarpackage (a play on Narnia) if you haven’t already!
- We’ll be working with only a few variables in the HRS dataset in this lesson!
- Mostly to help us view the tables and plots within the slides
What do we do with missing data?
- Filtering out missing values with
drop_na() - A common gotcha: how
filter()silently dropsNAs
- We will also use
naniarfunctions to work withNAs in a more flexible way- Filling in missing values with
replace_na()(andfct_na_value_to_level()for factors) - Replacing a values (numbers or levels) with
NAusingreplace_with_na()
- Filling in missing values with
- Not covered in this course (but very important in practice):
- Imputation methods for filling in missing values
- Weighting methods for adjusting for missingness
Filtering out missing values using drop_na()
Check missingness rates first before dropping NAs
We may want to remove rows that have missing data, coded as
NAFirst, summarize and visualize missingness using
naniarWe’ll take a quick look at the counts
Create missingness sumamry table with miss_var_summary()
hrs_00 |>
miss_var_summary() |>
gt() |>
tab_options(table.font.size = px(40))| variable | n_miss | pct_miss |
|---|---|---|
| ed | 507 | 18.6 |
| race_original | 486 | 17.8 |
| degree | 470 | 17.2 |
| height | 55 | 2.02 |
| act_vig | 9 | 0.330 |
| srh | 4 | 0.147 |
| sex | 0 | 0 |
drop_na(): removing missingness from all variables in the dataset
drop_na()removes rows with missing data- You can target specific columns, or drop any row with any missingness
hrs_01_na_rm <- hrs_00 |>
drop_na()
tibble(hrs_01_na_rm)# A tibble: 2,151 × 7
ed race_original srh sex act_vig height degree
<dbl> <fct> <fct> <fct> <fct> <dbl> <fct>
1 12 White/Caucasian Good Female Never 1.60 High school…
2 16 White/Caucasian Excellent Male >1 per week 1.88 Master's de…
3 16 Black/African American Good Male 1 per week 1.75 Master's de…
4 17 White/Caucasian Poor Female Never 1.55 Professiona…
5 11 Black/African American Excellent Male Never 1.78 None
6 13 Black/African American Fair Male Never 1.78 Associate's…
7 16 White/Caucasian Very Good Male 1 per week 1.73 Master's de…
8 12 White/Caucasian Good Male Never 1.88 High school…
9 12 Black/African American Fair Male Never 1.73 None
10 12 Black/African American Very Good Male Never 1.73 None
# ℹ 2,141 more rows
Pay attention to the number of rows for each of these results
drop_na(): removing missingness from all variables in the dataset
- Remove missingness in one or more columns:
hrs_02_race_rm <- hrs_00 |>
drop_na(race_original, ed)
hrs_02_race_rm |> miss_var_summary() |>
gt() |>
tab_options(table.font.size = px(40))| variable | n_miss | pct_miss |
|---|---|---|
| height | 47 | 2.13 |
| act_vig | 6 | 0.272 |
| srh | 4 | 0.181 |
| ed | 0 | 0 |
| race_original | 0 | 0 |
| sex | 0 | 0 |
| degree | 0 | 0 |
A warning about filter(): check for NA
Warning on filter(): it can silently drop your NAs
- Suppose we want to remove the
"Poor"category fromsrh(self-reported health)
hrs_00 |> tabyl(srh) srh n percent valid_percent
Excellent 277 0.101539589 0.10168869
Very Good 647 0.237170088 0.23751836
Good 870 0.318914956 0.31938326
Fair 743 0.272360704 0.27276065
Poor 187 0.068548387 0.06864905
<NA> 4 0.001466276 NA
srh has 4 missing values!
When we try to filter using !=, we lose our NAs!
hrs_03_rm_poor <- hrs_00 |>
filter(srh != "Poor")When R checks NA != "Poor", it evaluates to NA, not TRUE, so that row gets dropped
hrs_03_rm_poor |> tabyl(srh) srh n percent
Excellent 277 0.1091841
Very Good 647 0.2550256
Good 870 0.3429247
Fair 743 0.2928656
Poor 0 0.0000000
Use filter_out() to keep NAs
- We can use
filter_out()from thedplyrpackage to keep theNAs when we want to perform an exclusionary (!=) filter
hrs_04_rm_poor <- hrs_00 |>
filter_out(srh == "Poor")
hrs_04_rm_poor |> tabyl(srh) srh n percent valid_percent
Excellent 277 0.109012200 0.1091841
Very Good 647 0.254624164 0.2550256
Good 870 0.342384888 0.3429247
Fair 743 0.292404565 0.2928656
Poor 0 0.000000000 0.0000000
<NA> 4 0.001574183 NA
Build this habit
Whenever you filter on a column that has missing values, stop and check: how many observations were removed and does that correspond to the number of values I expect (excluding NAs?)
Replace missing values with replace_na()
replace_na(): replace missing values with a specific value
Sometimes we want to fill in missing values with a specific value instead of removing the row
We can use the
replace_na()function insidemutate()to setNAs to a specific valuereplace_na()works on numeric and character variables, not factorsHere’s the general version of the function:
- 1
-
replace_na()swaps out anyNAinvariablefor the value we provide. It’s good practice to assign this to a new variable, likevariable_filled - 2
-
variableis the column we want to fill in - 3
-
value_to_fill_inis the value we want to use to replace the missing
Example 1: filling a numeric variable
- Suppose if
heightis missing, we want to replace it with the averageheightacross the dataset- This is not an advised solution, I just want to show you how the function works!
- Here’s how we can use
replace_na()to fill in the missing values:
- 1
-
We need to wrap
replace_na()inmutate()to create a new variable,height_filled - 2
-
replace_na()swaps out anyNAinheightfor the value we provide - 3
-
heightis the column we want to fill in.
- 4
-
We will replace the missing values with the mean of
height
Check that the function worked
hrs_05_height_filled |>
select(contains("height")) |>
arrange(height) |> tail() height height_filled
2723 NA 1.688729
2724 NA 1.688729
2725 NA 1.688729
2726 NA 1.688729
2727 NA 1.688729
2728 NA 1.688729
fct_na_value_to_level(): filling a factor variable
- This is the
forcatsversion ofreplace_na()for factor variables
We can use the
fct_na_value_to_level()function insidemutate()to setNAs to a specific valuefct_na_value_to_level()works on factors!!General version of the function is similar to
replace_na():
- 1
-
We need to wrap
fct_na_value_to_level()inmutate()to create a new variable,variable_filled - 2
-
fct_na_value_to_level()swaps out anyNAinvariablefor the level we provide - 3
-
variableis the column we want to fill in - 4
-
"new level to replace NAs"is added as a new level, and used to replace the missing values
Example 2: filling a factor variable
- Suppose if
race_originalis missing, we want to replace it withUnknown- This is not an advised solution, I just want to show you how the function works!
- Here’s how we can use
replace_na()to fill in the missing values:
- 1
-
fct_na_value_to_level()swaps out anyNAinrace_originalfor the level we provide - 2
-
race_originalis the column we want to fill in - 3
-
"Unknown"is added as a new level, and used to replace the missing values
Check that the function worked
hrs_06_race_filled |> tabyl(race_original, race_filled) race_original White/Caucasian Black/African American Other Unknown
White/Caucasian 1000 0 0 0
Black/African American 0 884 0 0
Other 0 0 358 0
<NA> 0 0 0 486
Replacing a factor level with NA
replace_with_na(): replace a factor level with NA
- Sometimes there are data values that are not missing, but we want to treat them as missing
- Example: you might see a numeric value as
-99or"999"to indicate missingness, but because it’s a valid number, R doesn’t treat it asNA
- Example: you might see a numeric value as
- Note: there are no good examples of this in the HRS dataset, so I’m going to show you how to use the function with levels and values that should not be considered missing
- General version of the function:
- 1
-
replace_with_na()takes a named list of columns and values to replace withNA - 2
-
numeric_var1is the name of a numeric column, andnumeric_val1is the value to replace withNA - 3
-
numeric_var2is the name of a numeric column, andnumeric_val2a,numeric_val2b, andnumeric_val2care the values to replace withNA - 4
-
factor_var1is the name of a factor column, and"factor_level1"and"factor_level2"are the levels to replace withNA
Example 3: replace certain levels or values with NA (1/2)
- We can replace a few values in the
hrs_06_race_filleddataset withNA
- 1
- I often make a new variable to hold the original values, so I can compare before and after
- 2
-
replace_with_na()takes a named list of columns and values to replace withNA - 3
-
In the
sexcolumn, we will replace"Male"withNA - 4
-
In the
heightcolumn, we will replace70withNA - 5
-
In the
race_filledcolumn, we will replace"Other"and"Unknown"withNA
Example 3: replace certain levels or values with NA (2/2)
- We need to check our data to make sure we replaced the values we wanted to replace with
NA
Check sex_na
hrs_07_other_na |>
tabyl(sex, sex_na) |>
gt() |>
tab_options(table.font.size = px(35))| sex | Female | Male | NA_ |
|---|---|---|---|
| Female | 1568 | 0 | 0 |
| Male | 0 | 0 | 1160 |
Check race_filled_na
hrs_07_other_na |>
tabyl(race_filled, race_filled_na) |>
gt() |>
tab_options(table.font.size = px(35))| race_filled | White/Caucasian | Black/African American | Other | Unknown | NA_ |
|---|---|---|---|---|---|
| White/Caucasian | 1000 | 0 | 0 | 0 | 0 |
| Black/African American | 0 | 884 | 0 | 0 | 0 |
| Other | 0 | 0 | 0 | 0 | 358 |
| Unknown | 0 | 0 | 0 | 0 | 486 |
Check height_na
hrs_07_other_na |>
filter(is.na(height_na)) |>
select(height, height_na) |>
head() height height_na
1 1.6002 NA
2 1.6002 NA
3 1.6002 NA
4 1.6002 NA
5 NA NA
6 1.6002 NA
Wrap-up
Wrap-up
We learned a few ways to handle missing data, once we know it’s there
drop_na()removes rows with missing data — target specific columns, or drop any row with any missingnessfilter()silently dropsNArows unless you explicitly keep them with| is.na(...);filter_out()handles this for you automaticallyreplace_na()fills in missing values for numeric and character columnsfct_na_value_to_level()fills in missing values for factor columns, by adding a new explicit levelreplace_with_na()goes the other direction: turns a specific value (or factor level) intoNA- Always check your work afterward — compare before/after values with
tabyl(), or comparemiss_var_summary()before and after
- Always check your work afterward — compare before/after values with
There is no single “correct” way to handle missing data — the right choice depends on why the data is missing and what you’re trying to learn from it
