Data Cleansing

Tomasz Kruczalak, Krzysica Stanisław, Hatice Tatli

2024-04-02

# Set seed for reproducibility
set.seed(10)

# Generate sample data
n <- 1000  # Number of observations

# Age between 18 and 90
age <- sample(18:90, n, replace = TRUE)

# Introduce errors in age column
age[sample(1:n, 50)] <- "InvalidAge"

# Workclass: Private, Self-emp-not-inc, Self-emp-inc, Federal-gov, Local-gov, State-gov, Without-pay, Never-worked
workclass <- sample(c("Private", "Self-emp-not-inc", "Self-emp-inc", "Federal-gov", "Local-gov", "State-gov", "Without-pay", "Never-worked"), n, replace = TRUE)

# Introduce missing values in workclass column
workclass[sample(1:n, 50)] <- NA

# Education: Bachelors, Some-college, 11th, HS-grad, Prof-school, Assoc-acdm, Assoc-voc, 9th, 7th-8th, 12th, Masters, 1st-4th, 10th, Doctorate, 5th-6th, Preschool
education <- sample(c("Bachelors", "Some-college", "11th", "HS-grad", "Prof-school", "Assoc-acdm", "Assoc-voc", "9th", "7th-8th", "12th", "Masters", "1st-4th", "10th", "Doctorate", "5th-6th", "Preschool"), n, replace = TRUE)

# Introduce inconsistencies in education column
education[sample(1:n, 50)] <- "InvalidEducation"

# Education Number: 1-16
education_num <- sample(1:16, n, replace = TRUE)

# Marital Status: Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent, Married-AF-spouse
marital_status <- sample(c("Married-civ-spouse", "Divorced", "Never-married", "Separated", "Widowed", "Married-spouse-absent", "Married-AF-spouse"), n, replace = TRUE)

# Introduce missing values in marital_status column
marital_status[sample(1:n, 50)] <- NA

# Occupation: Tech-support, Craft-repair, Other-service, Sales, Exec-managerial, Prof-specialty, Handlers-cleaners, Machine-op-inspct, Adm-clerical, Farming-fishing, Transport-moving, Priv-house-serv, Protective-serv, Armed-Forces
occupation <- sample(c("Tech-support", "Craft-repair", "Other-service", "Sales", "Exec-managerial", "Prof-specialty", "Handlers-cleaners", "Machine-op-inspct", "Adm-clerical", "Farming-fishing", "Transport-moving", "Priv-house-serv", "Protective-serv", "Armed-Forces"), n, replace = TRUE)

# Introduce errors in occupation column
occupation[sample(1:n, 50)] <- "InvalidOccupation"

# Relationship: Wife, Own-child, Husband, Not-in-family, Other-relative, Unmarried
relationship <- sample(c("Wife", "Own-child", "Husband", "Not-in-family", "Other-relative", "Unmarried"), n, replace = TRUE)

# Introduce missing values in relationship column
relationship[sample(1:n, 50)] <- NA

# Race: White, Asian-Pac-Islander, Amer-Indian-Eskimo, Other, Black
race <- sample(c("White", "Asian-Pac-Islander", "Amer-Indian-Eskimo", "Other", "Black"), n, replace = TRUE)

# Introduce inconsistencies in race column
race[sample(1:n, 50)] <- "InvalidRace"

# Sex: Female, Male
sex <- sample(c("Female", "Male"), n, replace = TRUE)

# Introduce missing values in sex column
sex[sample(1:n, 50)] <- NA

# Capital Gain: 0-99999
capital_gain <- sample(0:99999, n, replace = TRUE)

# Introduce errors in capital_gain column
capital_gain[sample(1:n, 50)] <- -999

# Capital Loss: 0-99999
capital_loss <- sample(0:99999, n, replace = TRUE)

# Introduce errors in capital_loss column
capital_loss[sample(1:n, 50)] <- -999

# Hours per week: 1-99
hours_per_week <- sample(1:99, n, replace = TRUE)

# Introduce missing values in hours_per_week column
hours_per_week[sample(1:n, 50)] <- NA

# Native country: United-States, Cambodia, England, ...
native_country <- sample(c("United-States", "Cambodia", "England", "Puerto-Rico", "Canada", "Germany", "Outlying-US(Guam-USVI-etc)", "India", "Japan", "Greece", "South", "China", "Cuba", "Iran", "Honduras", "Philippines", "Italy", "Poland", "Jamaica", "Vietnam", "Mexico", "Portugal", "Ireland", "France", "Dominican-Republic", "Laos", "Ecuador", "Taiwan", "Haiti", "Columbia", "Hungary", "Guatemala", "Nicaragua", "Scotland", "Thailand", "Yugoslavia", "El-Salvador", "Trinadad&Tobago", "Peru", "Hong", "Holand-Netherlands"), n, replace = TRUE)

# Introduce missing values in native_country column
native_country[sample(1:n, 50)] <- NA

# Income: <=50K, >50K
income <- sample(c("<=50K", ">50K"), n, replace = TRUE, prob = c(0.75, 0.25))

# Create dataframe
sample_dirty_dataset <- data.frame(age, workclass, education, education_num, marital_status, occupation, relationship, race, sex, capital_gain, capital_loss, hours_per_week, native_country, income)

# Print first few rows of the dataset
head(sample_dirty_dataset)
##   age        workclass  education education_num        marital_status
## 1  26      Federal-gov Assoc-acdm             2             Separated
## 2  72          Private    HS-grad            16    Married-civ-spouse
## 3  89      Federal-gov    HS-grad            14 Married-spouse-absent
## 4  71      Without-pay       12th             7                  <NA>
## 5  56      Without-pay    5th-6th             5             Separated
## 6  32 Self-emp-not-inc Assoc-acdm             4     Married-AF-spouse
##          occupation   relationship               race    sex capital_gain
## 1      Craft-repair  Not-in-family              Other Female        95729
## 2 Machine-op-inspct      Unmarried Asian-Pac-Islander Female        16548
## 3      Armed-Forces  Not-in-family              Black   Male        26865
## 4 Machine-op-inspct           Wife Asian-Pac-Islander Female        80193
## 5   Priv-house-serv Other-relative              Other   Male        97828
## 6 InvalidOccupation        Husband              Black Female        76074
##   capital_loss hours_per_week native_country income
## 1        78233             71           Laos  <=50K
## 2        91089             81          Italy  <=50K
## 3         5355             51        Ecuador   >50K
## 4        32043             64         Taiwan   >50K
## 5        17804             47          Italy  <=50K
## 6        28165             31          China  <=50K
# Count mean of hours_per_week and change NA into this value
mean_hours_per_week <- mean(sample_dirty_dataset$hours_per_week, na.rm = TRUE)
mean_hours_per_week <- round(mean_hours_per_week,digits = 0)
sample_dirty_dataset <- sample_dirty_dataset %>%
  mutate(hours_per_week = ifelse(is.na(hours_per_week), mean_hours_per_week, hours_per_week))

# Count mean of age and change NA into this value
sample_dirty_dataset <- sample_dirty_dataset %>%
  mutate(age = ifelse(as.character(age) == "InvalidAge", NA_integer_, as.integer(age))) %>%
  mutate(age = ifelse(is.na(age), mean(age, na.rm = TRUE), age))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `age = ifelse(as.character(age) == "InvalidAge", NA_integer_,
##   as.integer(age))`.
## Caused by warning in `ifelse()`:
## ! NAs introduced by coercion
mean_age <- mean(sample_dirty_dataset$age, na.rm = TRUE)
mean_age <- round(mean_age, digits = 0)

sample_dirty_dataset <- sample_dirty_dataset %>%
  mutate(age = ifelse(is.na(age), as.integer(mean_age), as.integer(age)))


# Change InvalidEducation into NA
sample_dirty_dataset <- sample_dirty_dataset %>%
  mutate(education = ifelse(as.character(education) == "InvalidEducation", NA_character_, as.character(education)))

# Change InvalidOccupation into NA
sample_dirty_dataset <- sample_dirty_dataset %>%
  mutate(occupation = ifelse(occupation == "InvalidOccupation", NA_character_, occupation))

# Change InvalidRace into NA
sample_dirty_dataset <- sample_dirty_dataset %>%
  mutate(race = ifelse(occupation == "InvalidRace", NA_character_, race))

# Deleting dataset from observations that contains NA
clean_sample_dataset <- na.omit(sample_dirty_dataset)

Data Cleaning Steps:

Imputing Missing Values:

For the ‘hours_per_week’ column, missing values were replaced with the mean value of the column. For the ‘age’ column, invalid values (e.g., “InvalidAge”) were first converted to NA, then missing values were imputed with the mean age.

Handling Invalid Entries:

‘InvalidEducation’, ‘InvalidOccupation’, and ‘InvalidRace’ entries were converted to NA in their respective columns.

Removing Rows with Missing Values. Insights Gained:

The dataset is more consistant, without errors, and missing values, making it more reliable for analysis. By examining the dataset before and after cleaning, insights about errors and missing data can be gained. Analysis on the cleaned dataset can provide more accurate and trustworthy results. Detecting and rectifying errors can save significant amounts of time. It prevents incorrect conclusions or potential reworks.

# Summary statistics before cleaning
summary(sample_dirty_dataset)
##       age         workclass          education         education_num   
##  Min.   :18.00   Length:1000        Length:1000        Min.   : 1.000  
##  1st Qu.:36.00   Class :character   Class :character   1st Qu.: 5.000  
##  Median :53.00   Mode  :character   Mode  :character   Median : 9.000  
##  Mean   :53.78                                         Mean   : 8.697  
##  3rd Qu.:71.00                                         3rd Qu.:13.000  
##  Max.   :90.00                                         Max.   :16.000  
##  marital_status      occupation        relationship           race          
##  Length:1000        Length:1000        Length:1000        Length:1000       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##      sex             capital_gain    capital_loss   hours_per_week 
##  Length:1000        Min.   : -999   Min.   : -999   Min.   : 1.00  
##  Class :character   1st Qu.:21208   1st Qu.:21453   1st Qu.:25.00  
##  Mode  :character   Median :45534   Median :46901   Median :50.00  
##                     Mean   :47103   Mean   :47848   Mean   :49.76  
##                     3rd Qu.:74322   3rd Qu.:74949   3rd Qu.:76.00  
##                     Max.   :99692   Max.   :99804   Max.   :99.00  
##  native_country        income         
##  Length:1000        Length:1000       
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
## 
# Summary statistics after cleaning
summary(clean_sample_dataset)
##       age         workclass          education         education_num   
##  Min.   :18.00   Length:701         Length:701         Min.   : 1.000  
##  1st Qu.:36.00   Class :character   Class :character   1st Qu.: 5.000  
##  Median :53.00   Mode  :character   Mode  :character   Median : 9.000  
##  Mean   :54.19                                         Mean   : 8.716  
##  3rd Qu.:73.00                                         3rd Qu.:13.000  
##  Max.   :90.00                                         Max.   :16.000  
##  marital_status      occupation        relationship           race          
##  Length:701         Length:701         Length:701         Length:701        
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##      sex             capital_gain    capital_loss   hours_per_week 
##  Length:701         Min.   : -999   Min.   : -999   Min.   : 1.00  
##  Class :character   1st Qu.:20206   1st Qu.:20268   1st Qu.:26.00  
##  Mode  :character   Median :44187   Median :45594   Median :50.00  
##                     Mean   :46160   Mean   :47093   Mean   :50.23  
##                     3rd Qu.:72953   3rd Qu.:75199   3rd Qu.:77.00  
##                     Max.   :99692   Max.   :99727   Max.   :99.00  
##  native_country        income         
##  Length:701         Length:701        
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
## 
# Plotting age distribution before and after cleaning
par(mfrow=c(1,2))
hist(sample_dirty_dataset$age, main="Age Distribution Before Cleaning", xlab="Age", col="skyblue", border="white")
hist(clean_sample_dataset$age, main="Age Distribution After Cleaning", xlab="Age", col="red", border="white")

# Plotting income distribution before and after cleaning
par(mfrow=c(1,2))
barplot(table(sample_dirty_dataset$income), main="Income Distribution Before Cleaning", xlab="Income", col="skyblue")
barplot(table(clean_sample_dataset$income), main="Income Distribution After Cleaning", xlab="Income", col="red")

# As we can see, the results do not differ too much, which suggests that the data cleansing was successful 
# (Reports had to be commented to knit the RMarkdown)
# Diagnostic Report

#diagnose_web_report(sample_dirty_dataset)

# EDA Report
# eda_web_report(sample_dirty_dataset)

# Transformation Paged Report
# transformation_paged_report(sample_dirty_dataset, target = "age")
# Last one dosen't work I don't know why.