1 Introduction

In the previous lesson, we learned a range of functions for diagnosing data issues. Now, let’s focus on some common techniques and functions for fixing those issues. Let’s get started!

2 Learning Objectives

By the end of this lesson, you will be able to:

  • Understand how to clean column names, both automatically and manually.
  • Effectively eliminate duplicate entries.
  • Correct and fix string values in your data.
  • Convert data types as required.

3 Packages

Load the following packages for this lesson:

3.1 Dataset

‣ Working with a modified version of the dataset from the first Data Cleaning lesson.

More errors have been added for cleaning purposes.

non_adherence <- read_csv(here("data/non_adherence_messy.csv"))
## Rows: 1420 Columns: 15
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (5): Sex, Age_35, EDUCATION_OF_PATIENT, OCCUPATION_OF_PATIENT, Civil...s...
## dbl (9): patient_id, District, Health unit, Age at ART initiation, WHO statu...
## lgl (1): NA
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
non_adherence
## # A tibble: 1,420 × 15
##    patient_id District `Health unit` Sex   Age_35   `Age at ART initiation`
##         <dbl>    <dbl>         <dbl> <chr> <chr>                      <dbl>
##  1      10037        1             1 Male  over 35                     36  
##  2      10537        1             1 F     over 35                     40  
##  3       5489        2             3 F     Under 35                    34.1
##  4       5523        2             3 Male  Under 35                    28.1
##  5       4942        2             3 F     over 35                     46.9
##  6       4742        2             3 Male  over 35                     37.5
##  7      10879        1             1 Male  over 35                     49.2
##  8       2885        2             3 Male  over 35                     43.2
##  9       4861        2             3 F     over 35                     50.9
## 10       5180        2             3 Male  over 35                     36.1
## # ℹ 1,410 more rows
## # ℹ 9 more variables: EDUCATION_OF_PATIENT <chr>, OCCUPATION_OF_PATIENT <chr>,
## #   Civil...status <chr>, `WHO status at ART initiaiton` <dbl>,
## #   BMI_Initiation_Art <dbl>, CD4_Initiation_Art <dbl>, regimen.1 <dbl>,
## #   Nr_of_pills_day <dbl>, `NA` <lgl>

3.2 Cleaning column names

‣ Column names should be clean and standardized for ease of use and readability.

‣ Ideal column names should be short, have no spaces or periods, no unusual characters, and similar style.

‣ Use the names() function from base R to check column names of our non_adherence dataset.

 # check column names
names(non_adherence)
##  [1] "patient_id"                   "District"                    
##  [3] "Health unit"                  "Sex"                         
##  [5] "Age_35"                       "Age at ART initiation"       
##  [7] "EDUCATION_OF_PATIENT"         "OCCUPATION_OF_PATIENT"       
##  [9] "Civil...status"               "WHO status at ART initiaiton"
## [11] "BMI_Initiation_Art"           "CD4_Initiation_Art"          
## [13] "regimen.1"                    "Nr_of_pills_day"             
## [15] "NA"

‣ Some names have spaces, special characters, or are not uniformly cased.

3.3 Automatic column name cleaning with janitor::clean_names()

‣ Use janitor::clean_names() to standardize column names.

non_adherence %>%
  clean_names() %>%
  names()
##  [1] "patient_id"                   "district"                    
##  [3] "health_unit"                  "sex"                         
##  [5] "age_35"                       "age_at_art_initiation"       
##  [7] "education_of_patient"         "occupation_of_patient"       
##  [9] "civil_status"                 "who_status_at_art_initiaiton"
## [11] "bmi_initiation_art"           "cd4_initiation_art"          
## [13] "regimen_1"                    "nr_of_pills_day"             
## [15] "na"

‣ Observe changes like upper case to lower case, spaces to underscores, and periods replaced.

‣ Let’s save this cleaned dataset as non_adherence_clean.

non_adherence_clean <- 
  non_adherence %>%
  clean_names()

Q: Automatic cleaning

(NOTE: Answers are at the bottom of the page. Try to answer the questions yourself before checking.)

The following dataset has been adapted from a study that used retrospective data to characterize the tmporal and spatial dynamics of typhoid fever epidemics in Kasene, Uganda.

typhoid <- read_csv(here("data/typhoid_uganda.csv"))

names(typhoid)

Use the clean_names() function from janitor to clean the variables names in the typhoid dataset.

typhoid <- read_csv(here("data/typhoid_uganda.csv"))
## Rows: 215 Columns: 31
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (18): Householdmembers, Positioninthehousehold, Watersourcedwithinhouseh...
## dbl (11): UniqueKey, CaseorControl, Age, Sex, Levelofeducation, Below10years...
## lgl  (2): NA, NAN
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
typhoid %>%
  clean_names() %>% 
  names()
##  [1] "unique_key"                  "caseor_control"             
##  [3] "age"                         "sex"                        
##  [5] "levelofeducation"            "householdmembers"           
##  [7] "below10years"                "n1119years"                 
##  [9] "n2035years"                  "n3644years"                 
## [11] "n4565years"                  "above65years"               
## [13] "positioninthehousehold"      "watersourcedwithinhousehold"
## [15] "borehole"                    "river"                      
## [17] "tap"                         "rainwatertank"              
## [19] "unprotectedspring"           "protectedspring"            
## [21] "pond"                        "shallowwell"                
## [23] "stream"                      "jerrycan"                   
## [25] "bucket"                      "county"                     
## [27] "subcounty"                   "parish"                     
## [29] "village"                     "na"                         
## [31] "nan"

3.4 {stringr} and dplyr::rename_with() for Renaming Columns

rename_with() from dplyr allows applying functions to all column names. Sometimes easier to use than rename().

‣ Example: Convert all column names to upper case with rename_with(colname, toupper).

non_adherence %>%
  rename_with(.cols = everything(), .fn = toupper)
## # A tibble: 1,420 × 15
##    PATIENT_ID DISTRICT `HEALTH UNIT` SEX   AGE_35   `AGE AT ART INITIATION`
##         <dbl>    <dbl>         <dbl> <chr> <chr>                      <dbl>
##  1      10037        1             1 Male  over 35                     36  
##  2      10537        1             1 F     over 35                     40  
##  3       5489        2             3 F     Under 35                    34.1
##  4       5523        2             3 Male  Under 35                    28.1
##  5       4942        2             3 F     over 35                     46.9
##  6       4742        2             3 Male  over 35                     37.5
##  7      10879        1             1 Male  over 35                     49.2
##  8       2885        2             3 Male  over 35                     43.2
##  9       4861        2             3 F     over 35                     50.9
## 10       5180        2             3 Male  over 35                     36.1
## # ℹ 1,410 more rows
## # ℹ 9 more variables: EDUCATION_OF_PATIENT <chr>, OCCUPATION_OF_PATIENT <chr>,
## #   CIVIL...STATUS <chr>, `WHO STATUS AT ART INITIAITON` <dbl>,
## #   BMI_INITIATION_ART <dbl>, CD4_INITIATION_ART <dbl>, REGIMEN.1 <dbl>,
## #   NR_OF_PILLS_DAY <dbl>, `NA` <lgl>

‣ Another task: In the non_adherence dataset, remove _of_patient from column names for simplicity.

‣ Use stringr::str_replace_all() within rename_with() for this task.

str_replace_all() syntax: str_replace_all(string, pattern, replacement).

test_string <- "this is a test test string" # replace test with new
str_replace_all(string = test_string, pattern = "test", replacement = "new")
## [1] "this is a new new string"

‣ Apply str_replace_all() to remove _of_patient in column names of non_adherence_clean.

non_adherence_clean_2 <- non_adherence_clean %>% 
  rename_with(.cols = c(occupation_of_patient, education_of_patient), .fn = ~ str_replace_all(.x, "_of_patient", ""))
   # non_adherence_clean then rename_with()

Remember, creating many intermediate objects like non_adherence_clean and non_adherence_clean_2 is for tutorial clarity. In practice, combine multiple cleaning steps in a single pipe chain:

non_adherence_clean <- 
  non_adherence %>%
  # cleaning step 1 %>%
  # cleaning step 2 %>%
  # cleaning step 3 %>%
  # etc.

Q: Complete cleaning of column names

Standardize the column names in the typhoid dataset with clean_names() then;

  • replace or_ with _

  • replace of with _

typhoid %>% 
  clean_names() %>% 
  rename_with(.cols = c(caseor_control, levelofeducation), .fn = ~ str_replace_all(.x, c("or_", "of"), "_"))
## # A tibble: 215 × 31
##    unique_key case_control   age   sex level_education householdmembers
##         <dbl>        <dbl> <dbl> <dbl>           <dbl> <chr>           
##  1          1            0    29     0               2 01-May          
##  2          2            0    31     1               1 9               
##  3          3            1    21     0               1 12              
##  4          4            0    47     1               0 7               
##  5          5            0    39     1               1 7               
##  6          6            1    46     1               0 9               
##  7          7            0    58     0               1 01-May          
##  8          8            0    48     0               1 7               
##  9          9            1    21     1               3 10              
## 10         10            0    38     1               0 7               
## # ℹ 205 more rows
## # ℹ 25 more variables: below10years <dbl>, n1119years <dbl>, n2035years <dbl>,
## #   n3644years <dbl>, n4565years <dbl>, above65years <dbl>,
## #   positioninthehousehold <chr>, watersourcedwithinhousehold <chr>,
## #   borehole <chr>, river <chr>, tap <chr>, rainwatertank <chr>,
## #   unprotectedspring <chr>, protectedspring <chr>, pond <chr>,
## #   shallowwell <chr>, stream <chr>, jerrycan <chr>, bucket <chr>, …

3.5 Removing Duplicate Rows

‣ Duplicated rows in datasets can be due to multiple data sources or survey responses.

‣ It’s essential to identify and remove these duplicates for accurate analysis.

‣ Use janitor::get_dupes() to identify duplicate rows. This allows for visual inspection before removal.

 # Use get_dupes() to identify duplicates
get_dupes(non_adherence_clean_2)
## No variable names specified - using all columns.
## # A tibble: 11 × 16
##    patient_id district health_unit sex   age_35  age_at_art_initiation education
##         <dbl>    <dbl>       <dbl> <chr> <chr>                   <dbl> <chr>    
##  1         NA       NA          NA <NA>  <NA>                     NA   <NA>     
##  2         NA       NA          NA <NA>  <NA>                     NA   <NA>     
##  3         NA       NA          NA <NA>  <NA>                     NA   <NA>     
##  4       2412        1           1 F     Under …                  27.1 <NA>     
##  5       2412        1           1 F     Under …                  27.1 <NA>     
##  6       3576        2           3 Male  Under …                  28.4 <NA>     
##  7       3576        2           3 Male  Under …                  28.4 <NA>     
##  8       4208        1           1 F     Under …                  31.7 Primary  
##  9       4208        1           1 F     Under …                  31.7 Primary  
## 10       4692        2           3 F     over 35                  54.2 <NA>     
## 11       4692        2           3 F     over 35                  54.2 <NA>     
## # ℹ 9 more variables: occupation <chr>, civil_status <chr>,
## #   who_status_at_art_initiaiton <dbl>, bmi_initiation_art <dbl>,
## #   cd4_initiation_art <dbl>, regimen_1 <dbl>, nr_of_pills_day <dbl>, na <lgl>,
## #   dupe_count <int>

‣ After identifying, use dplyr::distinct() to remove duplicates, keeping only the unique rows.

# Before removal
nrow(non_adherence_clean_2)
## [1] 1420
# Removing duplicates
non_adherence_distinct <- 
  non_adherence_clean_2 %>% 
  distinct()

# After removal
nrow(non_adherence_distinct)
## [1] 1414

‣ Re-check for duplicates with get_dupes() to ensure all have been removed.

non_adherence_distinct %>% 
  get_dupes()
## No variable names specified - using all columns.
## No duplicate combinations found of: patient_id, district, health_unit, sex, age_35, age_at_art_initiation, education, occupation, civil_status, ... and 6 other variables
## # A tibble: 0 × 16
## # ℹ 16 variables: patient_id <dbl>, district <dbl>, health_unit <dbl>,
## #   sex <chr>, age_35 <chr>, age_at_art_initiation <dbl>, education <chr>,
## #   occupation <chr>, civil_status <chr>, who_status_at_art_initiaiton <dbl>,
## #   bmi_initiation_art <dbl>, cd4_initiation_art <dbl>, regimen_1 <dbl>,
## #   nr_of_pills_day <dbl>, na <lgl>, dupe_count <int>

Q: Removing duplicates

Identify the duplicates in the typhoid dataset using get_dupes(), then remove them using distinct().

# Number of rows before duplicates removal
nrow(typhoid)
## [1] 215
# Get duplicated rows
typhoid %>% 
  get_dupes()
## No variable names specified - using all columns.
## # A tibble: 18 × 32
##    UniqueKey CaseorControl   Age   Sex Levelofeducation Householdmembers
##        <dbl>         <dbl> <dbl> <dbl>            <dbl> <chr>           
##  1        23             0    23     1                1 01-May          
##  2        23             0    23     1                1 01-May          
##  3        23             0    23     1                1 01-May          
##  4        23             0    23     1                1 01-May          
##  5        56             0    24     1                0 01-May          
##  6        56             0    24     1                0 01-May          
##  7        56             0    24     1                0 01-May          
##  8        56             0    24     1                0 01-May          
##  9        78             1    36     1                1 7               
## 10        78             1    36     1                1 7               
## 11        78             1    36     1                1 7               
## 12        78             1    36     1                1 7               
## 13       100             0    50     0                1 7               
## 14       100             0    50     0                1 7               
## 15       100             0    50     0                1 7               
## 16       100             0    50     0                1 7               
## 17        NA            NA    NA    NA               NA <NA>            
## 18        NA            NA    NA    NA               NA <NA>            
## # ℹ 26 more variables: Below10years <dbl>, N1119years <dbl>, N2035years <dbl>,
## #   N3644years <dbl>, N4565years <dbl>, Above65years <dbl>,
## #   Positioninthehousehold <chr>, Watersourcedwithinhousehold <chr>,
## #   Borehole <chr>, River <chr>, Tap <chr>, Rainwatertank <chr>,
## #   Unprotectedspring <chr>, Protectedspring <chr>, Pond <chr>,
## #   Shallowwell <chr>, Stream <chr>, Jerrycan <chr>, Bucket <chr>,
## #   County <chr>, Subcounty <chr>, Parish <chr>, Village <chr>, `NA` <lgl>, …
# Remove duplicated rows
typhoid_distinct <- typhoid %>% 
  distinct() 
  
# Number of rows aqfter removing duplicates
nrow(typhoid_distinct)
## [1] 202

3.6 Homogenize strings

‣ We observed inconsistent capitalization in string characters, like Professor and professor, in the occupation variable.

‣ To address this, we can transform character columns to a specific case. Here, we’ll use title case. Preferable for graphics and reports.

non_adherence_case_corrected <- 
  non_adherence_distinct %>% 
  mutate(across(.cols = c(sex, age_35, education, occupation, civil_status), .fns = str_to_title)) # then the across function

  # check the values of age_35 and occupation
non_adherence_distinct %>% 
  count(age_35)
## # A tibble: 3 × 2
##   age_35       n
##   <chr>    <int>
## 1 Under 35   976
## 2 over 35    437
## 3 <NA>         1
non_adherence_distinct %>% 
  count(occupation) %>% 
  arrange(-(str_detect(occupation, "rofessor")))
## # A tibble: 51 × 2
##    occupation                 n
##    <chr>                  <int>
##  1 Professor                 35
##  2 professor                 11
##  3 Accountant                 1
##  4 Administrator              1
##  5 Agriculture technician     3
##  6 Artist                     1
##  7 Basic service agent        2
##  8 Boat captain               1
##  9 Business                   3
## 10 Commercial                18
## # ℹ 41 more rows
 # check the updated values of age_35 and occupation
non_adherence_case_corrected %>% 
  count(age_35)
## # A tibble: 3 × 2
##   age_35       n
##   <chr>    <int>
## 1 Over 35    437
## 2 Under 35   976
## 3 <NA>         1
non_adherence_case_corrected %>% 
  count(occupation) %>% 
  arrange(-(str_detect(occupation, "rofessor")))
## # A tibble: 49 × 2
##    occupation                 n
##    <chr>                  <int>
##  1 Professor                 46
##  2 Accountant                 1
##  3 Administrator              1
##  4 Agriculture Technician     3
##  5 Artist                     1
##  6 Bartender                  1
##  7 Basic Service Agent        2
##  8 Boat Captain               1
##  9 Business                   3
## 10 Commercial                18
## # ℹ 39 more rows

Q: Transforming to lowercase

Transform all the strings in the typhoid dataset to lowercase.

typhoid_distinct %>% 
  mutate(across(Positioninthehousehold:Village, .fns = str_to_lower))
## # A tibble: 202 × 31
##    UniqueKey CaseorControl   Age   Sex Levelofeducation Householdmembers
##        <dbl>         <dbl> <dbl> <dbl>            <dbl> <chr>           
##  1         1             0    29     0                2 01-May          
##  2         2             0    31     1                1 9               
##  3         3             1    21     0                1 12              
##  4         4             0    47     1                0 7               
##  5         5             0    39     1                1 7               
##  6         6             1    46     1                0 9               
##  7         7             0    58     0                1 01-May          
##  8         8             0    48     0                1 7               
##  9         9             1    21     1                3 10              
## 10        10             0    38     1                0 7               
## # ℹ 192 more rows
## # ℹ 25 more variables: Below10years <dbl>, N1119years <dbl>, N2035years <dbl>,
## #   N3644years <dbl>, N4565years <dbl>, Above65years <dbl>,
## #   Positioninthehousehold <chr>, Watersourcedwithinhousehold <chr>,
## #   Borehole <chr>, River <chr>, Tap <chr>, Rainwatertank <chr>,
## #   Unprotectedspring <chr>, Protectedspring <chr>, Pond <chr>,
## #   Shallowwell <chr>, Stream <chr>, Jerrycan <chr>, Bucket <chr>, …

3.7 dplyr::case_match() for String Cleaning

‣ We will explore the case_match() function from the {dplyr} package for string cleaning.

case_match() allows for specifying conditions and values to be applied to a vector.

‣ Here is an example using case_match():

test_vector <- c("+", "-", "NA", "missing")
case_match(test_vector,
           "+" ~ "positive",
           "-" ~ "negative",
           .default = "unknown") # + to positive, - to negative, default as unknown

‣ The function takes a vector and series of conditions. .default is optional for unmatched conditions.

‣ Let’s apply case_match() to the sex column in the non_adherence_distinct dataset.

‣ First, observe the levels in this variable:

non_adherence_distinct %>% 
  count(sex)
## # A tibble: 3 × 2
##   sex       n
##   <chr> <int>
## 1 F      1084
## 2 Male    329
## 3 <NA>      1

‣ Inconsistencies in the sex column coding can be fixed using case_match(). Let’s change F to Female:

# case match F to Female, with default as is
non_adherence_distinct %>% 
  mutate(sex = case_match(sex, "F" ~ "Female", .default = sex))
## # A tibble: 1,414 × 15
##    patient_id district health_unit sex    age_35 age_at_art_initiation education
##         <dbl>    <dbl>       <dbl> <chr>  <chr>                  <dbl> <chr>    
##  1      10037        1           1 Male   over …                  36   <NA>     
##  2      10537        1           1 Female over …                  40   Secondary
##  3       5489        2           3 Female Under…                  34.1 <NA>     
##  4       5523        2           3 Male   Under…                  28.1 <NA>     
##  5       4942        2           3 Female over …                  46.9 Universi…
##  6       4742        2           3 Male   over …                  37.5 Technical
##  7      10879        1           1 Male   over …                  49.2 Technical
##  8       2885        2           3 Male   over …                  43.2 Technical
##  9       4861        2           3 Female over …                  50.9 Technical
## 10       5180        2           3 Male   over …                  36.1 Technical
## # ℹ 1,404 more rows
## # ℹ 8 more variables: occupation <chr>, civil_status <chr>,
## #   who_status_at_art_initiaiton <dbl>, bmi_initiation_art <dbl>,
## #   cd4_initiation_art <dbl>, regimen_1 <dbl>, nr_of_pills_day <dbl>, na <lgl>

‣ This function is useful for multiple value changes, like in the occupation column.

‣ Modifications to be made: - “Worker” to “Laborer” - “Housewife” to “Homemaker” - “Truck Driver” and “Taxi Driver” to “Driver”

non_adherence_recoded <- 
  non_adherence_case_corrected %>%
  mutate(sex = case_match(sex, "F" ~ "Female", .default = sex)) %>%
  mutate(occupation = case_match(occupation, "Worker" ~ "Laborer", "Housewife" ~ "Homemaker", "Truck Driver" ~ "Driver", "Taxi Driver" ~ "Driver",
                                 .default = occupation))
  # case match Worker to Laborer, Housewife to Homemaker, Truck Driver and Taxi Driver to Driver
non_adherence_recoded
## # A tibble: 1,414 × 15
##    patient_id district health_unit sex    age_35 age_at_art_initiation education
##         <dbl>    <dbl>       <dbl> <chr>  <chr>                  <dbl> <chr>    
##  1      10037        1           1 Male   Over …                  36   <NA>     
##  2      10537        1           1 Female Over …                  40   Secondary
##  3       5489        2           3 Female Under…                  34.1 <NA>     
##  4       5523        2           3 Male   Under…                  28.1 <NA>     
##  5       4942        2           3 Female Over …                  46.9 Universi…
##  6       4742        2           3 Male   Over …                  37.5 Technical
##  7      10879        1           1 Male   Over …                  49.2 Technical
##  8       2885        2           3 Male   Over …                  43.2 Technical
##  9       4861        2           3 Female Over …                  50.9 Technical
## 10       5180        2           3 Male   Over …                  36.1 Technical
## # ℹ 1,404 more rows
## # ℹ 8 more variables: occupation <chr>, civil_status <chr>,
## #   who_status_at_art_initiaiton <dbl>, bmi_initiation_art <dbl>,
## #   cd4_initiation_art <dbl>, regimen_1 <dbl>, nr_of_pills_day <dbl>, na <lgl>

Remember to use .default=column_name in case_match(). Without it, unmatched values become NA.

Q: Fixing strings

The variable householdmembers from the typhoid dataset should represent the number of individuals in a household. There is a value 01-May in this variable. Recode this value to 1-5.

typhoid %>% 
  mutate(Householdmembers = case_match(Householdmembers,
                                      "01-May" ~ "1-5", .default = Householdmembers))
## # A tibble: 215 × 31
##    UniqueKey CaseorControl   Age   Sex Levelofeducation Householdmembers
##        <dbl>         <dbl> <dbl> <dbl>            <dbl> <chr>           
##  1         1             0    29     0                2 1-5             
##  2         2             0    31     1                1 9               
##  3         3             1    21     0                1 12              
##  4         4             0    47     1                0 7               
##  5         5             0    39     1                1 7               
##  6         6             1    46     1                0 9               
##  7         7             0    58     0                1 1-5             
##  8         8             0    48     0                1 7               
##  9         9             1    21     1                3 10              
## 10        10             0    38     1                0 7               
## # ℹ 205 more rows
## # ℹ 25 more variables: Below10years <dbl>, N1119years <dbl>, N2035years <dbl>,
## #   N3644years <dbl>, N4565years <dbl>, Above65years <dbl>,
## #   Positioninthehousehold <chr>, Watersourcedwithinhousehold <chr>,
## #   Borehole <chr>, River <chr>, Tap <chr>, Rainwatertank <chr>,
## #   Unprotectedspring <chr>, Protectedspring <chr>, Pond <chr>,
## #   Shallowwell <chr>, Stream <chr>, Jerrycan <chr>, Bucket <chr>, …

3.8 Converting Data Types

‣ Understanding and correctly classifying 2data types is crucial for data to behave as expected.

R’s 6 basic data types/classes:

  • character: strings or characters, always quoted.
  • numeric: real numbers, including decimals.
  • integer: whole numbers.
  • logical: TRUE or FALSE values.
  • factor: categorical variables.
  • Date/POSIXct: dates and times.

‣ Recall our dataset: 5 character variables and 9 numeric variables.

str(non_adherence_recoded)
## tibble [1,414 × 15] (S3: tbl_df/tbl/data.frame)
##  $ patient_id                  : num [1:1414] 10037 10537 5489 5523 4942 ...
##  $ district                    : num [1:1414] 1 1 2 2 2 2 1 2 2 2 ...
##  $ health_unit                 : num [1:1414] 1 1 3 3 3 3 1 3 3 3 ...
##  $ sex                         : chr [1:1414] "Male" "Female" "Female" "Male" ...
##  $ age_35                      : chr [1:1414] "Over 35" "Over 35" "Under 35" "Under 35" ...
##  $ age_at_art_initiation       : num [1:1414] 36 40 34.1 28.1 46.9 37.5 49.2 43.2 50.9 36.1 ...
##  $ education                   : chr [1:1414] NA "Secondary" NA NA ...
##  $ occupation                  : chr [1:1414] "Driver" "Laborer" "Laborer" "Laborer" ...
##  $ civil_status                : chr [1:1414] "Stable Union" "Stable Union" "Widowed" "Stable Union" ...
##  $ who_status_at_art_initiaiton: num [1:1414] 1 1 3 1 3 2 2 2 1 1 ...
##  $ bmi_initiation_art          : num [1:1414] 19.4 24.7 NA NA NA ...
##  $ cd4_initiation_art          : num [1:1414] NA 107 NA NA NA NA 139 NA NA NA ...
##  $ regimen_1                   : num [1:1414] 3 6 6 6 6 3 6 3 3 6 ...
##  $ nr_of_pills_day             : num [1:1414] 2 1 1 1 1 2 1 2 2 1 ...
##  $ na                          : logi [1:1414] NA NA NA NA NA NA ...

‣ Looking at our data, the only true numerical variables are age_at_art_initation, bmi_initiation_art, cd4_initiation_art, and nr_of_pills_day. Let’s change all the others to factor variables using the as.factor() function!

‣ Change all others to factor variables using as.factor within across.

non_adherence_recoded %>%
  mutate(across(
    .cols = !c(age_at_art_initiation, bmi_initiation_art, cd4_initiation_art, nr_of_pills_day),
    .fns = as.factor
  ))
## # A tibble: 1,414 × 15
##    patient_id district health_unit sex    age_35 age_at_art_initiation education
##    <fct>      <fct>    <fct>       <fct>  <fct>                  <dbl> <fct>    
##  1 10037      1        1           Male   Over …                  36   <NA>     
##  2 10537      1        1           Female Over …                  40   Secondary
##  3 5489       2        3           Female Under…                  34.1 <NA>     
##  4 5523       2        3           Male   Under…                  28.1 <NA>     
##  5 4942       2        3           Female Over …                  46.9 Universi…
##  6 4742       2        3           Male   Over …                  37.5 Technical
##  7 10879      1        1           Male   Over …                  49.2 Technical
##  8 2885       2        3           Male   Over …                  43.2 Technical
##  9 4861       2        3           Female Over …                  50.9 Technical
## 10 5180       2        3           Male   Over …                  36.1 Technical
## # ℹ 1,404 more rows
## # ℹ 8 more variables: occupation <fct>, civil_status <fct>,
## #   who_status_at_art_initiaiton <fct>, bmi_initiation_art <dbl>,
## #   cd4_initiation_art <dbl>, regimen_1 <fct>, nr_of_pills_day <dbl>, na <fct>

‣ This should result in correct classification as expected.

Q: Changing data types

Convert the variables in positions 13 to 29 in the typhoid dataset to factor.

typhoid %>% 
  mutate(across(Positioninthehousehold:Village, .fns = as.factor))
## # A tibble: 215 × 31
##    UniqueKey CaseorControl   Age   Sex Levelofeducation Householdmembers
##        <dbl>         <dbl> <dbl> <dbl>            <dbl> <chr>           
##  1         1             0    29     0                2 01-May          
##  2         2             0    31     1                1 9               
##  3         3             1    21     0                1 12              
##  4         4             0    47     1                0 7               
##  5         5             0    39     1                1 7               
##  6         6             1    46     1                0 9               
##  7         7             0    58     0                1 01-May          
##  8         8             0    48     0                1 7               
##  9         9             1    21     1                3 10              
## 10        10             0    38     1                0 7               
## # ℹ 205 more rows
## # ℹ 25 more variables: Below10years <dbl>, N1119years <dbl>, N2035years <dbl>,
## #   N3644years <dbl>, N4565years <dbl>, Above65years <dbl>,
## #   Positioninthehousehold <fct>, Watersourcedwithinhousehold <fct>,
## #   Borehole <fct>, River <fct>, Tap <fct>, Rainwatertank <fct>,
## #   Unprotectedspring <fct>, Protectedspring <fct>, Pond <fct>,
## #   Shallowwell <fct>, Stream <fct>, Jerrycan <fct>, Bucket <fct>, …

4 Learning Objectives

By the end of this lesson, you will be able to:

‣ Understand how to clean column names, both automatically and manually.

‣ Eliminate duplicate entries.

‣ Correct and fix string values in your data.

‣ Convert data types as required.

5 Wrap Up!

Congratulations on completing the two-part lesson on the data cleaning pipeline! You are now better equipped to tackle the cleaning of real-world datasets.

Keep practicing!

Answer Key

Q: Automatic cleaning

clean_names(typhoid)

Q: Complete cleaning of column names

typhoid %>%
  clean_names() %>%
  rename_with(.fn = ~ str_replace_all(.x, pattern = "or_|of", replacement = "_")) %>%
  names()
##  [1] "unique_key"                  "case_control"               
##  [3] "age"                         "sex"                        
##  [5] "level_education"             "householdmembers"           
##  [7] "below10years"                "n1119years"                 
##  [9] "n2035years"                  "n3644years"                 
## [11] "n4565years"                  "above65years"               
## [13] "positioninthehousehold"      "watersourcedwithinhousehold"
## [15] "borehole"                    "river"                      
## [17] "tap"                         "rainwatertank"              
## [19] "unprotectedspring"           "protectedspring"            
## [21] "pond"                        "shallowwell"                
## [23] "stream"                      "jerrycan"                   
## [25] "bucket"                      "county"                     
## [27] "subcounty"                   "parish"                     
## [29] "village"                     "na"                         
## [31] "nan"

Q: Removing duplicates

# Identify duplicates
get_dupes(typhoid)
## No variable names specified - using all columns.
# Remove duplicates
typhoid_distinct <- typhoid %>% 
  distinct()

# Ensure all distinct rows left 
get_dupes(typhoid_distinct)
## No variable names specified - using all columns.
## No duplicate combinations found of: UniqueKey, CaseorControl, Age, Sex, Levelofeducation, Householdmembers, Below10years, N1119years, N2035years, ... and 22 other variables

Q: Transforming to lowercase

typhoid %>% 
  mutate(across(where(is.character),
                ~ tolower(.x)))

Q: Fixing strings

typhoid %>%
  mutate(Householdmembers = case_match(Householdmembers, "01-May" ~ "1-5", .default=Householdmembers)) %>% 
  count(Householdmembers)

Q: Changing data types

typhoid %>%
  mutate(across(13:29, ~as.factor(.)))

Contributors

The following team members contributed to this lesson:

LS0tDQp0aXRsZTogJ0RhdGEgQ2xlYW5pbmcgUGlwZWxpbmUgMjogIEZpeGluZyBJbmNvbnNpc3RlbmNpZXMnDQphdXRob3I6DQogIC0gbmFtZTogIktlbmUgRGF2aWQgTndvc3UiDQogIC0gbmFtZTogIkFtYW5kYSBNY0tpbmxleSINCiAgLSBuYW1lOiAiTGF1cmUgVmFuY2F1d2VuYmVyZ2hlIg0KZGF0ZTogIjIwMjQtMTEtMjEiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9mb2xkaW5nOiAic2hvdyINCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY3NzOiAhZXhwciBoZXJlOjpoZXJlKCJnbG9iYWwvc3R5bGUvc3R5bGUuY3NzIikNCiAgICBoaWdobGlnaHQ6IGthdGUNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQpgYGB7ciwgZWNobyA9IEYsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmlmKCFyZXF1aXJlKHBhY21hbikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQpwYWNtYW46OnBfbG9hZCh0aWR5dmVyc2UsIGtuaXRyLCBoZXJlKQ0KDQojIyBmdW5jdGlvbnMNCnNvdXJjZShoZXJlOjpoZXJlKCJnbG9iYWwvZnVuY3Rpb25zL21pc2NfZnVuY3Rpb25zLlIiKSkNCg0KIyMgZGVmYXVsdCByZW5kZXINCnJlZ2lzdGVyUzNtZXRob2QoInJlYWN0YWJsZV81X3Jvd3MiLCAiZGF0YS5mcmFtZSIsIHJlYWN0YWJsZV81X3Jvd3MpDQprbml0cjo6b3B0c19jaHVuayRzZXQoY2xhc3Muc291cmNlID0gInRnYy1jb2RlLWJsb2NrIikNCmBgYA0KDQojIEludHJvZHVjdGlvbg0KDQpJbiB0aGUgcHJldmlvdXMgbGVzc29uLCB3ZSBsZWFybmVkIGEgcmFuZ2Ugb2YgZnVuY3Rpb25zIGZvciBkaWFnbm9zaW5nIGRhdGEgaXNzdWVzLiBOb3csIGxldCdzIGZvY3VzIG9uIHNvbWUgY29tbW9uIHRlY2huaXF1ZXMgYW5kIGZ1bmN0aW9ucyBmb3IgZml4aW5nIHRob3NlIGlzc3Vlcy4gTGV0J3MgZ2V0IHN0YXJ0ZWQhDQoNCiMgTGVhcm5pbmcgT2JqZWN0aXZlcw0KDQpCeSB0aGUgZW5kIG9mIHRoaXMgbGVzc29uLCB5b3Ugd2lsbCBiZSBhYmxlIHRvOg0KDQotICAgVW5kZXJzdGFuZCBob3cgdG8gY2xlYW4gY29sdW1uIG5hbWVzLCBib3RoIGF1dG9tYXRpY2FsbHkgYW5kIG1hbnVhbGx5Lg0KLSAgIEVmZmVjdGl2ZWx5IGVsaW1pbmF0ZSBkdXBsaWNhdGUgZW50cmllcy4NCi0gICBDb3JyZWN0IGFuZCBmaXggc3RyaW5nIHZhbHVlcyBpbiB5b3VyIGRhdGEuDQotICAgQ29udmVydCBkYXRhIHR5cGVzIGFzIHJlcXVpcmVkLg0KDQojIFBhY2thZ2VzDQoNCkxvYWQgdGhlIGZvbGxvd2luZyBwYWNrYWdlcyBmb3IgdGhpcyBsZXNzb246DQoNCmBgYHtyIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRiwgZWNobyA9IEZ9DQojIExvYWQgcGFja2FnZXMgDQppZighcmVxdWlyZShwYWNtYW4pKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQ0KcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlLA0KICAgICAgICAgICAgICAgamFuaXRvciwNCiAgICAgICAgICAgICAgIGluc3BlY3RkZikNCmBgYA0KDQojIyBEYXRhc2V0DQoNCuKAoyBXb3JraW5nIHdpdGggYSAqKm1vZGlmaWVkIHZlcnNpb24qKiBvZiB0aGUgZGF0YXNldCBmcm9tIHRoZSBmaXJzdCBgRGF0YSBDbGVhbmluZ2AgbGVzc29uLg0KDQrigKMgKipNb3JlIGVycm9ycyoqIGhhdmUgYmVlbiBhZGRlZCBmb3IgY2xlYW5pbmcgcHVycG9zZXMuDQoNCmBgYHtyfQ0Kbm9uX2FkaGVyZW5jZSA8LSByZWFkX2NzdihoZXJlKCJkYXRhL25vbl9hZGhlcmVuY2VfbWVzc3kuY3N2IikpDQpub25fYWRoZXJlbmNlDQpgYGANCg0KIyMgQ2xlYW5pbmcgY29sdW1uIG5hbWVzDQoNCuKAoyBDb2x1bW4gbmFtZXMgc2hvdWxkIGJlICoqY2xlYW4qKiBhbmQgKipzdGFuZGFyZGl6ZWQqKiBmb3IgZWFzZSBvZiB1c2UgYW5kIHJlYWRhYmlsaXR5Lg0KDQrigKMgSWRlYWwgY29sdW1uIG5hbWVzIHNob3VsZCBiZSAqKnNob3J0KiosIGhhdmUgKipubyBzcGFjZXMgb3IgcGVyaW9kcyoqLCAqKm5vIHVudXN1YWwgY2hhcmFjdGVycyoqLCBhbmQgKipzaW1pbGFyIHN0eWxlKiouDQoNCuKAoyBVc2UgdGhlIGBuYW1lcygpYCBmdW5jdGlvbiBmcm9tIGJhc2UgUiB0byBjaGVjayBjb2x1bW4gbmFtZXMgb2Ygb3VyIGBub25fYWRoZXJlbmNlYCBkYXRhc2V0Lg0KDQpgYGB7cn0NCiAjIGNoZWNrIGNvbHVtbiBuYW1lcw0KbmFtZXMobm9uX2FkaGVyZW5jZSkNCmBgYA0KDQrigKMgU29tZSBuYW1lcyBoYXZlICoqc3BhY2VzKiosICoqc3BlY2lhbCBjaGFyYWN0ZXJzKiosIG9yIGFyZSAqKm5vdCB1bmlmb3JtbHkgY2FzZWQqKi4NCg0KIyMgQXV0b21hdGljIGNvbHVtbiBuYW1lIGNsZWFuaW5nIHdpdGggYGphbml0b3I6OmNsZWFuX25hbWVzKClgDQoNCuKAoyBVc2UgYGphbml0b3I6OmNsZWFuX25hbWVzKClgIHRvICoqc3RhbmRhcmRpemUgY29sdW1uIG5hbWVzKiouDQoNCmBgYHtyfQ0Kbm9uX2FkaGVyZW5jZSAlPiUNCiAgY2xlYW5fbmFtZXMoKSAlPiUNCiAgbmFtZXMoKQ0KYGBgDQoNCuKAoyBPYnNlcnZlIGNoYW5nZXMgbGlrZSAqKnVwcGVyIGNhc2UgdG8gbG93ZXIgY2FzZSoqLCAqKnNwYWNlcyB0byB1bmRlcnNjb3JlcyoqLCBhbmQgKipwZXJpb2RzIHJlcGxhY2VkKiouDQoNCuKAoyBMZXQncyBzYXZlIHRoaXMgY2xlYW5lZCBkYXRhc2V0IGFzIGBub25fYWRoZXJlbmNlX2NsZWFuYC4NCg0KYGBge3J9DQpub25fYWRoZXJlbmNlX2NsZWFuIDwtIA0KICBub25fYWRoZXJlbmNlICU+JQ0KICBjbGVhbl9uYW1lcygpDQpgYGANCg0KOjo6IHItcHJhY3RpY2UNCiMjIyBROiBBdXRvbWF0aWMgY2xlYW5pbmcgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KKihOT1RFOiBBbnN3ZXJzIGFyZSBhdCB0aGUgYm90dG9tIG9mIHRoZSBwYWdlLiBUcnkgdG8gYW5zd2VyIHRoZSBxdWVzdGlvbnMgeW91cnNlbGYgYmVmb3JlIGNoZWNraW5nLikqDQoNClRoZSBmb2xsb3dpbmcgZGF0YXNldCBoYXMgYmVlbiBhZGFwdGVkIGZyb20gYSBzdHVkeSB0aGF0IHVzZWQgcmV0cm9zcGVjdGl2ZSBkYXRhIHRvIGNoYXJhY3Rlcml6ZSB0aGUgdG1wb3JhbCBhbmQgc3BhdGlhbCBkeW5hbWljcyBvZiB0eXBob2lkIGZldmVyIGVwaWRlbWljcyBpbiBLYXNlbmUsIFVnYW5kYS4NCg0KYGBge3IgZXZhbCA9IEZ9DQp0eXBob2lkIDwtIHJlYWRfY3N2KGhlcmUoImRhdGEvdHlwaG9pZF91Z2FuZGEuY3N2IikpDQoNCm5hbWVzKHR5cGhvaWQpDQpgYGANCg0KVXNlIHRoZSBgY2xlYW5fbmFtZXMoKWAgZnVuY3Rpb24gZnJvbSBgamFuaXRvcmAgdG8gY2xlYW4gdGhlIHZhcmlhYmxlcyBuYW1lcyBpbiB0aGUgYHR5cGhvaWRgIGRhdGFzZXQuDQoNCmBgYHtyfQ0KdHlwaG9pZCA8LSByZWFkX2NzdihoZXJlKCJkYXRhL3R5cGhvaWRfdWdhbmRhLmNzdiIpKQ0KDQp0eXBob2lkICU+JQ0KICBjbGVhbl9uYW1lcygpICU+JSANCiAgbmFtZXMoKQ0KICANCmBgYA0KDQo6OjoNCg0KIyMge3N0cmluZ3J9IGFuZCBgZHBseXI6OnJlbmFtZV93aXRoKClgIGZvciBSZW5hbWluZyBDb2x1bW5zDQoNCuKAoyBgcmVuYW1lX3dpdGgoKWAgZnJvbSBgZHBseXJgIGFsbG93cyBhcHBseWluZyBmdW5jdGlvbnMgdG8gYWxsIGNvbHVtbiBuYW1lcy4gU29tZXRpbWVzIGVhc2llciB0byB1c2UgdGhhbiBgcmVuYW1lKClgLg0KDQrigKMgRXhhbXBsZTogQ29udmVydCBhbGwgY29sdW1uIG5hbWVzIHRvIHVwcGVyIGNhc2Ugd2l0aCBgcmVuYW1lX3dpdGgoY29sbmFtZSwgdG91cHBlcilgLg0KDQpgYGB7cn0NCm5vbl9hZGhlcmVuY2UgJT4lDQogIHJlbmFtZV93aXRoKC5jb2xzID0gZXZlcnl0aGluZygpLCAuZm4gPSB0b3VwcGVyKQ0KYGBgDQoNCuKAoyBBbm90aGVyIHRhc2s6IEluIHRoZSBgbm9uX2FkaGVyZW5jZWAgZGF0YXNldCwgcmVtb3ZlIGBfb2ZfcGF0aWVudGAgZnJvbSBjb2x1bW4gbmFtZXMgZm9yIHNpbXBsaWNpdHkuDQoNCuKAoyBVc2UgYHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbCgpYCB3aXRoaW4gYHJlbmFtZV93aXRoKClgIGZvciB0aGlzIHRhc2suDQoNCuKAoyBgc3RyX3JlcGxhY2VfYWxsKClgIHN5bnRheDogYHN0cl9yZXBsYWNlX2FsbChzdHJpbmcsIHBhdHRlcm4sIHJlcGxhY2VtZW50KWAuDQoNCmBgYHtyfQ0KdGVzdF9zdHJpbmcgPC0gInRoaXMgaXMgYSB0ZXN0IHRlc3Qgc3RyaW5nIiAjIHJlcGxhY2UgdGVzdCB3aXRoIG5ldw0Kc3RyX3JlcGxhY2VfYWxsKHN0cmluZyA9IHRlc3Rfc3RyaW5nLCBwYXR0ZXJuID0gInRlc3QiLCByZXBsYWNlbWVudCA9ICJuZXciKQ0KYGBgDQoNCuKAoyBBcHBseSBgc3RyX3JlcGxhY2VfYWxsKClgIHRvIHJlbW92ZSBgX29mX3BhdGllbnRgIGluIGNvbHVtbiBuYW1lcyBvZiBgbm9uX2FkaGVyZW5jZV9jbGVhbmAuDQoNCmBgYHtyfQ0Kbm9uX2FkaGVyZW5jZV9jbGVhbl8yIDwtIG5vbl9hZGhlcmVuY2VfY2xlYW4gJT4lIA0KICByZW5hbWVfd2l0aCguY29scyA9IGMob2NjdXBhdGlvbl9vZl9wYXRpZW50LCBlZHVjYXRpb25fb2ZfcGF0aWVudCksIC5mbiA9IH4gc3RyX3JlcGxhY2VfYWxsKC54LCAiX29mX3BhdGllbnQiLCAiIikpDQogICAjIG5vbl9hZGhlcmVuY2VfY2xlYW4gdGhlbiByZW5hbWVfd2l0aCgpDQpgYGANCg0KOjo6IHNpZGUtbm90ZQ0KUmVtZW1iZXIsIGNyZWF0aW5nIG1hbnkgaW50ZXJtZWRpYXRlIG9iamVjdHMgbGlrZSBgbm9uX2FkaGVyZW5jZV9jbGVhbmAgYW5kIGBub25fYWRoZXJlbmNlX2NsZWFuXzJgIGlzIGZvciB0dXRvcmlhbCBjbGFyaXR5LiBJbiBwcmFjdGljZSwgY29tYmluZSBtdWx0aXBsZSBjbGVhbmluZyBzdGVwcyBpbiBhIHNpbmdsZSBwaXBlIGNoYWluOg0KDQpgYGB7ciBldmFsID0gRn0NCm5vbl9hZGhlcmVuY2VfY2xlYW4gPC0gDQogIG5vbl9hZGhlcmVuY2UgJT4lDQogICMgY2xlYW5pbmcgc3RlcCAxICU+JQ0KICAjIGNsZWFuaW5nIHN0ZXAgMiAlPiUNCiAgIyBjbGVhbmluZyBzdGVwIDMgJT4lDQogICMgZXRjLg0KYGBgDQo6OjoNCg0KOjo6IHItcHJhY3RpY2UNCiMjIyBROiBDb21wbGV0ZSBjbGVhbmluZyBvZiBjb2x1bW4gbmFtZXMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KU3RhbmRhcmRpemUgdGhlIGNvbHVtbiBuYW1lcyBpbiB0aGUgYHR5cGhvaWRgIGRhdGFzZXQgd2l0aCBgY2xlYW5fbmFtZXMoKWAgdGhlbjsNCg0KLSAgIHJlcGxhY2UgYG9yX2Agd2l0aCBgX2ANCg0KLSAgIHJlcGxhY2UgYG9mYCB3aXRoIGBfYA0KDQpgYGB7cn0NCnR5cGhvaWQgJT4lIA0KICBjbGVhbl9uYW1lcygpICU+JSANCiAgcmVuYW1lX3dpdGgoLmNvbHMgPSBjKGNhc2Vvcl9jb250cm9sLCBsZXZlbG9mZWR1Y2F0aW9uKSwgLmZuID0gfiBzdHJfcmVwbGFjZV9hbGwoLngsIGMoIm9yXyIsICJvZiIpLCAiXyIpKQ0KICANCmBgYA0KDQo6OjoNCg0KIyMgUmVtb3ZpbmcgRHVwbGljYXRlIFJvd3MNCg0K4oCjIER1cGxpY2F0ZWQgcm93cyBpbiBkYXRhc2V0cyBjYW4gYmUgZHVlIHRvICoqbXVsdGlwbGUgZGF0YSBzb3VyY2VzKiogb3IgKipzdXJ2ZXkgcmVzcG9uc2VzKiouDQoNCuKAoyBJdCdzICoqZXNzZW50aWFsKiogdG8gKippZGVudGlmeSBhbmQgcmVtb3ZlIHRoZXNlIGR1cGxpY2F0ZXMqKiBmb3IgYWNjdXJhdGUgYW5hbHlzaXMuDQoNCuKAoyBVc2UgYGphbml0b3I6OmdldF9kdXBlcygpYCB0byAqKmlkZW50aWZ5IGR1cGxpY2F0ZSByb3dzKiouIFRoaXMgYWxsb3dzIGZvciAqKnZpc3VhbCBpbnNwZWN0aW9uKiogYmVmb3JlIHJlbW92YWwuDQoNCmBgYHtyfQ0KICMgVXNlIGdldF9kdXBlcygpIHRvIGlkZW50aWZ5IGR1cGxpY2F0ZXMNCmdldF9kdXBlcyhub25fYWRoZXJlbmNlX2NsZWFuXzIpDQpgYGANCg0K4oCjIEFmdGVyIGlkZW50aWZ5aW5nLCB1c2UgYGRwbHlyOjpkaXN0aW5jdCgpYCB0byAqKnJlbW92ZSBkdXBsaWNhdGVzKiosIGtlZXBpbmcgb25seSB0aGUgKip1bmlxdWUgcm93cyoqLg0KDQpgYGB7cn0NCiMgQmVmb3JlIHJlbW92YWwNCm5yb3cobm9uX2FkaGVyZW5jZV9jbGVhbl8yKQ0KDQojIFJlbW92aW5nIGR1cGxpY2F0ZXMNCm5vbl9hZGhlcmVuY2VfZGlzdGluY3QgPC0gDQogIG5vbl9hZGhlcmVuY2VfY2xlYW5fMiAlPiUgDQogIGRpc3RpbmN0KCkNCg0KIyBBZnRlciByZW1vdmFsDQpucm93KG5vbl9hZGhlcmVuY2VfZGlzdGluY3QpDQpgYGANCg0K4oCjIFJlLWNoZWNrIGZvciBkdXBsaWNhdGVzIHdpdGggYGdldF9kdXBlcygpYCB0byBlbnN1cmUgYWxsIGhhdmUgYmVlbiByZW1vdmVkLg0KDQpgYGB7cn0NCm5vbl9hZGhlcmVuY2VfZGlzdGluY3QgJT4lIA0KICBnZXRfZHVwZXMoKQ0KYGBgDQoNCjo6OiByLXByYWN0aWNlDQojIyMgUTogUmVtb3ZpbmcgZHVwbGljYXRlcyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpJZGVudGlmeSB0aGUgZHVwbGljYXRlcyBpbiB0aGUgYHR5cGhvaWRgIGRhdGFzZXQgdXNpbmcgYGdldF9kdXBlcygpYCwgdGhlbiByZW1vdmUgdGhlbSB1c2luZyBgZGlzdGluY3QoKWAuDQoNCmBgYHtyfQ0KIyBOdW1iZXIgb2Ygcm93cyBiZWZvcmUgZHVwbGljYXRlcyByZW1vdmFsDQpucm93KHR5cGhvaWQpDQoNCiMgR2V0IGR1cGxpY2F0ZWQgcm93cw0KdHlwaG9pZCAlPiUgDQogIGdldF9kdXBlcygpDQoNCiMgUmVtb3ZlIGR1cGxpY2F0ZWQgcm93cw0KdHlwaG9pZF9kaXN0aW5jdCA8LSB0eXBob2lkICU+JSANCiAgZGlzdGluY3QoKSANCiAgDQojIE51bWJlciBvZiByb3dzIGFxZnRlciByZW1vdmluZyBkdXBsaWNhdGVzDQpucm93KHR5cGhvaWRfZGlzdGluY3QpDQpgYGANCg0KOjo6DQoNCiMjIEhvbW9nZW5pemUgc3RyaW5ncw0KDQrigKMgV2Ugb2JzZXJ2ZWQgKippbmNvbnNpc3RlbnQgY2FwaXRhbGl6YXRpb24qKiBpbiBzdHJpbmcgY2hhcmFjdGVycywgbGlrZSBgUHJvZmVzc29yYCBhbmQgYHByb2Zlc3NvcmAsIGluIHRoZSBgb2NjdXBhdGlvbmAgdmFyaWFibGUuDQoNCuKAoyBUbyBhZGRyZXNzIHRoaXMsIHdlIGNhbiAqKnRyYW5zZm9ybSBjaGFyYWN0ZXIgY29sdW1ucyB0byBhIHNwZWNpZmljIGNhc2UqKi4gSGVyZSwgd2UnbGwgdXNlICoqdGl0bGUgY2FzZSoqLiBQcmVmZXJhYmxlIGZvciBncmFwaGljcyBhbmQgcmVwb3J0cy4NCg0KYGBge3J9DQpub25fYWRoZXJlbmNlX2Nhc2VfY29ycmVjdGVkIDwtIA0KICBub25fYWRoZXJlbmNlX2Rpc3RpbmN0ICU+JSANCiAgbXV0YXRlKGFjcm9zcyguY29scyA9IGMoc2V4LCBhZ2VfMzUsIGVkdWNhdGlvbiwgb2NjdXBhdGlvbiwgY2l2aWxfc3RhdHVzKSwgLmZucyA9IHN0cl90b190aXRsZSkpICMgdGhlbiB0aGUgYWNyb3NzIGZ1bmN0aW9uDQoNCiAgIyBjaGVjayB0aGUgdmFsdWVzIG9mIGFnZV8zNSBhbmQgb2NjdXBhdGlvbg0Kbm9uX2FkaGVyZW5jZV9kaXN0aW5jdCAlPiUgDQogIGNvdW50KGFnZV8zNSkNCg0Kbm9uX2FkaGVyZW5jZV9kaXN0aW5jdCAlPiUgDQogIGNvdW50KG9jY3VwYXRpb24pICU+JSANCiAgYXJyYW5nZSgtKHN0cl9kZXRlY3Qob2NjdXBhdGlvbiwgInJvZmVzc29yIikpKQ0KDQogIyBjaGVjayB0aGUgdXBkYXRlZCB2YWx1ZXMgb2YgYWdlXzM1IGFuZCBvY2N1cGF0aW9uDQpub25fYWRoZXJlbmNlX2Nhc2VfY29ycmVjdGVkICU+JSANCiAgY291bnQoYWdlXzM1KQ0KDQpub25fYWRoZXJlbmNlX2Nhc2VfY29ycmVjdGVkICU+JSANCiAgY291bnQob2NjdXBhdGlvbikgJT4lIA0KICBhcnJhbmdlKC0oc3RyX2RldGVjdChvY2N1cGF0aW9uLCAicm9mZXNzb3IiKSkpDQpgYGANCg0KOjo6IHItcHJhY3RpY2UNCiMjIyBROiBUcmFuc2Zvcm1pbmcgdG8gbG93ZXJjYXNlIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRyYW5zZm9ybSBhbGwgdGhlIHN0cmluZ3MgaW4gdGhlIGB0eXBob2lkYCBkYXRhc2V0IHRvIGxvd2VyY2FzZS4NCg0KYGBge3J9DQp0eXBob2lkX2Rpc3RpbmN0ICU+JSANCiAgbXV0YXRlKGFjcm9zcyhQb3NpdGlvbmludGhlaG91c2Vob2xkOlZpbGxhZ2UsIC5mbnMgPSBzdHJfdG9fbG93ZXIpKQ0KYGBgDQoNCjo6Og0KDQojIyBgZHBseXI6OmNhc2VfbWF0Y2goKWAgZm9yIFN0cmluZyBDbGVhbmluZw0KDQrigKMgV2Ugd2lsbCBleHBsb3JlIHRoZSBgY2FzZV9tYXRjaCgpYCBmdW5jdGlvbiBmcm9tIHRoZSB7ZHBseXJ9IHBhY2thZ2UgZm9yIHN0cmluZyBjbGVhbmluZy4NCg0K4oCjIGBjYXNlX21hdGNoKClgIGFsbG93cyBmb3Igc3BlY2lmeWluZyBjb25kaXRpb25zIGFuZCB2YWx1ZXMgdG8gYmUgYXBwbGllZCB0byBhIHZlY3Rvci4NCg0K4oCjIEhlcmUgaXMgYW4gZXhhbXBsZSB1c2luZyBgY2FzZV9tYXRjaCgpYDoNCg0KYGBge3IgZXZhbCA9IEZ9DQp0ZXN0X3ZlY3RvciA8LSBjKCIrIiwgIi0iLCAiTkEiLCAibWlzc2luZyIpDQpjYXNlX21hdGNoKHRlc3RfdmVjdG9yLA0KICAgICAgICAgICAiKyIgfiAicG9zaXRpdmUiLA0KICAgICAgICAgICAiLSIgfiAibmVnYXRpdmUiLA0KICAgICAgICAgICAuZGVmYXVsdCA9ICJ1bmtub3duIikgIyArIHRvIHBvc2l0aXZlLCAtIHRvIG5lZ2F0aXZlLCBkZWZhdWx0IGFzIHVua25vd24NCmBgYA0KDQrigKMgVGhlIGZ1bmN0aW9uIHRha2VzIGEgdmVjdG9yIGFuZCBzZXJpZXMgb2YgY29uZGl0aW9ucy4gYC5kZWZhdWx0YCBpcyBvcHRpb25hbCBmb3IgdW5tYXRjaGVkIGNvbmRpdGlvbnMuDQoNCuKAoyBMZXQncyBhcHBseSBgY2FzZV9tYXRjaCgpYCB0byB0aGUgYHNleGAgY29sdW1uIGluIHRoZSBgbm9uX2FkaGVyZW5jZV9kaXN0aW5jdGAgZGF0YXNldC4NCg0K4oCjIEZpcnN0LCBvYnNlcnZlIHRoZSBsZXZlbHMgaW4gdGhpcyB2YXJpYWJsZToNCg0KYGBge3J9DQpub25fYWRoZXJlbmNlX2Rpc3RpbmN0ICU+JSANCiAgY291bnQoc2V4KQ0KYGBgDQoNCuKAoyBJbmNvbnNpc3RlbmNpZXMgaW4gdGhlIGBzZXhgIGNvbHVtbiBjb2RpbmcgY2FuIGJlIGZpeGVkIHVzaW5nIGBjYXNlX21hdGNoKClgLiBMZXQncyBjaGFuZ2UgYEZgIHRvIGBGZW1hbGVgOg0KDQpgYGB7cn0NCiMgY2FzZSBtYXRjaCBGIHRvIEZlbWFsZSwgd2l0aCBkZWZhdWx0IGFzIGlzDQpub25fYWRoZXJlbmNlX2Rpc3RpbmN0ICU+JSANCiAgbXV0YXRlKHNleCA9IGNhc2VfbWF0Y2goc2V4LCAiRiIgfiAiRmVtYWxlIiwgLmRlZmF1bHQgPSBzZXgpKQ0KYGBgDQoNCuKAoyBUaGlzIGZ1bmN0aW9uIGlzIHVzZWZ1bCBmb3IgbXVsdGlwbGUgdmFsdWUgY2hhbmdlcywgbGlrZSBpbiB0aGUgYG9jY3VwYXRpb25gIGNvbHVtbi4NCg0K4oCjIE1vZGlmaWNhdGlvbnMgdG8gYmUgbWFkZTogLSAiV29ya2VyIiB0byAiTGFib3JlciIgLSAiSG91c2V3aWZlIiB0byAiSG9tZW1ha2VyIiAtICJUcnVjayBEcml2ZXIiIGFuZCAiVGF4aSBEcml2ZXIiIHRvICJEcml2ZXIiDQoNCmBgYHtyfQ0Kbm9uX2FkaGVyZW5jZV9yZWNvZGVkIDwtIA0KICBub25fYWRoZXJlbmNlX2Nhc2VfY29ycmVjdGVkICU+JQ0KICBtdXRhdGUoc2V4ID0gY2FzZV9tYXRjaChzZXgsICJGIiB+ICJGZW1hbGUiLCAuZGVmYXVsdCA9IHNleCkpICU+JQ0KICBtdXRhdGUob2NjdXBhdGlvbiA9IGNhc2VfbWF0Y2gob2NjdXBhdGlvbiwgIldvcmtlciIgfiAiTGFib3JlciIsICJIb3VzZXdpZmUiIH4gIkhvbWVtYWtlciIsICJUcnVjayBEcml2ZXIiIH4gIkRyaXZlciIsICJUYXhpIERyaXZlciIgfiAiRHJpdmVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5kZWZhdWx0ID0gb2NjdXBhdGlvbikpDQogICMgY2FzZSBtYXRjaCBXb3JrZXIgdG8gTGFib3JlciwgSG91c2V3aWZlIHRvIEhvbWVtYWtlciwgVHJ1Y2sgRHJpdmVyIGFuZCBUYXhpIERyaXZlciB0byBEcml2ZXINCm5vbl9hZGhlcmVuY2VfcmVjb2RlZA0KYGBgDQoNCjo6OiB3YXJuaW5nDQpSZW1lbWJlciB0byB1c2UgYC5kZWZhdWx0PWNvbHVtbl9uYW1lYCBpbiBgY2FzZV9tYXRjaCgpYC4gV2l0aG91dCBpdCwgdW5tYXRjaGVkIHZhbHVlcyBiZWNvbWUgYE5BYC4NCjo6Og0KDQo6Ojogci1wcmFjdGljZQ0KIyMjIFE6IEZpeGluZyBzdHJpbmdzIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSB2YXJpYWJsZSBgaG91c2Vob2xkbWVtYmVyc2AgZnJvbSB0aGUgYHR5cGhvaWRgIGRhdGFzZXQgc2hvdWxkIHJlcHJlc2VudCB0aGUgbnVtYmVyIG9mIGluZGl2aWR1YWxzIGluIGEgaG91c2Vob2xkLiBUaGVyZSBpcyBhIHZhbHVlIGAwMS1NYXlgIGluIHRoaXMgdmFyaWFibGUuIFJlY29kZSB0aGlzIHZhbHVlIHRvIGAxLTVgLg0KDQpgYGB7cn0NCnR5cGhvaWQgJT4lIA0KICBtdXRhdGUoSG91c2Vob2xkbWVtYmVycyA9IGNhc2VfbWF0Y2goSG91c2Vob2xkbWVtYmVycywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjAxLU1heSIgfiAiMS01IiwgLmRlZmF1bHQgPSBIb3VzZWhvbGRtZW1iZXJzKSkNCmBgYA0KDQo6OjoNCg0KIyMgQ29udmVydGluZyBEYXRhIFR5cGVzDQoNCuKAoyBVbmRlcnN0YW5kaW5nIGFuZCBjb3JyZWN0bHkgY2xhc3NpZnlpbmcgMmRhdGEgdHlwZXMgaXMgY3J1Y2lhbCBmb3IgZGF0YSB0byBiZWhhdmUgYXMgZXhwZWN0ZWQuDQoNCjo6OiByZW1pbmRlcg0KUidzIDYgYmFzaWMgZGF0YSB0eXBlcy9jbGFzc2VzOg0KDQotICAgYGNoYXJhY3RlcmA6IHN0cmluZ3Mgb3IgY2hhcmFjdGVycywgYWx3YXlzIHF1b3RlZC4NCi0gICBgbnVtZXJpY2A6IHJlYWwgbnVtYmVycywgaW5jbHVkaW5nIGRlY2ltYWxzLg0KLSAgIGBpbnRlZ2VyYDogd2hvbGUgbnVtYmVycy4NCi0gICBgbG9naWNhbGA6IGBUUlVFYCBvciBgRkFMU0VgIHZhbHVlcy4NCi0gICBgZmFjdG9yYDogY2F0ZWdvcmljYWwgdmFyaWFibGVzLg0KLSAgIGBEYXRlL1BPU0lYY3RgOiBkYXRlcyBhbmQgdGltZXMuDQo6OjoNCg0K4oCjIFJlY2FsbCBvdXIgZGF0YXNldDogNSBjaGFyYWN0ZXIgdmFyaWFibGVzIGFuZCA5IG51bWVyaWMgdmFyaWFibGVzLg0KDQpgYGB7cn0NCnN0cihub25fYWRoZXJlbmNlX3JlY29kZWQpDQpgYGANCg0K4oCjIExvb2tpbmcgYXQgb3VyIGRhdGEsIHRoZSBvbmx5IHRydWUgbnVtZXJpY2FsIHZhcmlhYmxlcyBhcmUgYGFnZV9hdF9hcnRfaW5pdGF0aW9uYCwgYGJtaV9pbml0aWF0aW9uX2FydGAsIGBjZDRfaW5pdGlhdGlvbl9hcnRgLCBhbmQgYG5yX29mX3BpbGxzX2RheWAuIExldCdzIGNoYW5nZSBhbGwgdGhlIG90aGVycyB0byBmYWN0b3IgdmFyaWFibGVzIHVzaW5nIHRoZSBgYXMuZmFjdG9yKClgIGZ1bmN0aW9uIQ0KDQrigKMgQ2hhbmdlIGFsbCBvdGhlcnMgdG8gZmFjdG9yIHZhcmlhYmxlcyB1c2luZyBhcy5mYWN0b3Igd2l0aGluIGFjcm9zcy4NCg0KYGBge3J9DQpub25fYWRoZXJlbmNlX3JlY29kZWQgJT4lDQogIG11dGF0ZShhY3Jvc3MoDQogICAgLmNvbHMgPSAhYyhhZ2VfYXRfYXJ0X2luaXRpYXRpb24sIGJtaV9pbml0aWF0aW9uX2FydCwgY2Q0X2luaXRpYXRpb25fYXJ0LCBucl9vZl9waWxsc19kYXkpLA0KICAgIC5mbnMgPSBhcy5mYWN0b3INCiAgKSkNCmBgYA0KDQrigKMgVGhpcyBzaG91bGQgcmVzdWx0IGluIGNvcnJlY3QgY2xhc3NpZmljYXRpb24gYXMgZXhwZWN0ZWQuDQoNCjo6OiByLXByYWN0aWNlDQojIyMgUTogQ2hhbmdpbmcgZGF0YSB0eXBlcyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpDb252ZXJ0IHRoZSB2YXJpYWJsZXMgaW4gcG9zaXRpb25zIDEzIHRvIDI5IGluIHRoZSBgdHlwaG9pZGAgZGF0YXNldCB0byBmYWN0b3IuDQoNCmBgYHtyfQ0KdHlwaG9pZCAlPiUgDQogIG11dGF0ZShhY3Jvc3MoUG9zaXRpb25pbnRoZWhvdXNlaG9sZDpWaWxsYWdlLCAuZm5zID0gYXMuZmFjdG9yKSkNCmBgYA0KDQoNCjo6Og0KDQoNCiMgTGVhcm5pbmcgT2JqZWN0aXZlcw0KDQpCeSB0aGUgZW5kIG9mIHRoaXMgbGVzc29uLCB5b3Ugd2lsbCBiZSBhYmxlIHRvOg0KDQrigKMgVW5kZXJzdGFuZCBob3cgdG8gY2xlYW4gY29sdW1uIG5hbWVzLCBib3RoIGF1dG9tYXRpY2FsbHkgYW5kIG1hbnVhbGx5Lg0KDQrigKMgRWxpbWluYXRlIGR1cGxpY2F0ZSBlbnRyaWVzLg0KDQrigKMgQ29ycmVjdCBhbmQgZml4IHN0cmluZyB2YWx1ZXMgaW4geW91ciBkYXRhLg0KDQrigKMgQ29udmVydCBkYXRhIHR5cGVzIGFzIHJlcXVpcmVkLg0KDQojIFdyYXAgVXAhDQoNCkNvbmdyYXR1bGF0aW9ucyBvbiBjb21wbGV0aW5nIHRoZSB0d28tcGFydCBsZXNzb24gb24gdGhlIGRhdGEgY2xlYW5pbmcgcGlwZWxpbmUhIFlvdSBhcmUgbm93IGJldHRlciBlcXVpcHBlZCB0byB0YWNrbGUgdGhlIGNsZWFuaW5nIG9mIHJlYWwtd29ybGQgZGF0YXNldHMuDQoNCktlZXAgcHJhY3RpY2luZyENCg0KIyBBbnN3ZXIgS2V5IHsudW5udW1iZXJlZH0NCg0KIyMjIFE6IEF1dG9tYXRpYyBjbGVhbmluZyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpgYGB7ciByZW5kZXI9cmVhY3RhYmxlXzVfcm93c30NCmNsZWFuX25hbWVzKHR5cGhvaWQpDQpgYGANCg0KIyMjIFE6IENvbXBsZXRlIGNsZWFuaW5nIG9mIGNvbHVtbiBuYW1lcyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpgYGB7cn0NCnR5cGhvaWQgJT4lDQogIGNsZWFuX25hbWVzKCkgJT4lDQogIHJlbmFtZV93aXRoKC5mbiA9IH4gc3RyX3JlcGxhY2VfYWxsKC54LCBwYXR0ZXJuID0gIm9yX3xvZiIsIHJlcGxhY2VtZW50ID0gIl8iKSkgJT4lDQogIG5hbWVzKCkNCmBgYA0KDQojIyMgUTogUmVtb3ZpbmcgZHVwbGljYXRlcyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpgYGB7ciByZW5kZXI9cmVhY3RhYmxlXzVfcm93c30NCiMgSWRlbnRpZnkgZHVwbGljYXRlcw0KZ2V0X2R1cGVzKHR5cGhvaWQpDQoNCiMgUmVtb3ZlIGR1cGxpY2F0ZXMNCnR5cGhvaWRfZGlzdGluY3QgPC0gdHlwaG9pZCAlPiUgDQogIGRpc3RpbmN0KCkNCg0KIyBFbnN1cmUgYWxsIGRpc3RpbmN0IHJvd3MgbGVmdCANCmdldF9kdXBlcyh0eXBob2lkX2Rpc3RpbmN0KQ0KYGBgDQoNCiMjIyBROiBUcmFuc2Zvcm1pbmcgdG8gbG93ZXJjYXNlIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCmBgYHtyIHJlbmRlcj1yZWFjdGFibGVfNV9yb3dzfQ0KdHlwaG9pZCAlPiUgDQogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuY2hhcmFjdGVyKSwNCiAgICAgICAgICAgICAgICB+IHRvbG93ZXIoLngpKSkNCmBgYA0KDQojIyMgUTogRml4aW5nIHN0cmluZ3Mgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KYGBge3IgcmVuZGVyPXJlYWN0YWJsZV81X3Jvd3N9DQp0eXBob2lkICU+JQ0KICBtdXRhdGUoSG91c2Vob2xkbWVtYmVycyA9IGNhc2VfbWF0Y2goSG91c2Vob2xkbWVtYmVycywgIjAxLU1heSIgfiAiMS01IiwgLmRlZmF1bHQ9SG91c2Vob2xkbWVtYmVycykpICU+JSANCiAgY291bnQoSG91c2Vob2xkbWVtYmVycykNCmBgYA0KDQojIyMgUTogQ2hhbmdpbmcgZGF0YSB0eXBlcyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpgYGB7ciByZW5kZXI9cmVhY3RhYmxlXzVfcm93c30NCnR5cGhvaWQgJT4lDQogIG11dGF0ZShhY3Jvc3MoMTM6MjksIH5hcy5mYWN0b3IoLikpKQ0KYGBgDQoNCiMgQ29udHJpYnV0b3JzIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNClRoZSBmb2xsb3dpbmcgdGVhbSBtZW1iZXJzIGNvbnRyaWJ1dGVkIHRvIHRoaXMgbGVzc29uOg0KDQpgciB0Z2NfY29udHJpYnV0b3JzX2xpc3QoaWRzID0gYygiYW1ja2lubGV5IiwgImtlbmRhdmlkbiIsICJsb2xvdmFuY28iLCAiZWxtYW51a28iKSlgDQo=