1. Pendahuluan

1.1 Latar Belakang

Perkembangan teknologi digital dan platform daring telah membuka peluang bagi banyak orang untuk bekerja secara lepas (freelance) tanpa terikat pada satu perusahaan maupun lokasi tertentu. Pekerja lepas memiliki karakteristik yang beragam, antara lain usia, negara asal, lama pengalaman, tarif per jam, dan penilaian dari klien. Keragaman tersebut menjadikan data pekerja lepas layak untuk dikaji lebih lanjut.

Data yang digunakan dalam laporan ini adalah data mentah Global Freelancers dalam format Excel. Sebagaimana data mentah pada umumnya, kondisinya belum sepenuhnya siap dianalisis. Sejumlah variabel masih memuat simbol seperti $, USD, dan % sehingga terbaca sebagai teks. Penulisan kategori juga belum seragam, misalnya f, F, dan female yang sebenarnya merujuk pada kategori yang sama. Selain itu, data berpotensi mengandung nilai hilang (missing value), baris ganda, dan nilai yang tidak wajar.

Apabila data dengan kondisi demikian langsung dianalisis, kesimpulan yang dihasilkan berisiko tidak akurat. Oleh karena itu, laporan ini difokuskan pada tahapan data preprocessing, yaitu pemeriksaan permasalahan data, pembersihan, transformasi, visualisasi, pengujian asumsi, hingga penyimpanan dataset final yang siap digunakan untuk analisis lanjutan.

1.2 Rumusan Masalah

  1. Permasalahan kualitas data apa saja yang terdapat pada data mentah Global Freelancers, meliputi nilai hilang, ketidakseragaman penulisan, data ganda, dan nilai yang tidak wajar?
  2. Bagaimana proses penyeragaman format dan perbaikan tipe data pada variabel-variabel dalam dataset?
  3. Bagaimana penanganan nilai hilang pada variabel yang digunakan dalam analisis?
  4. Bagaimana sebaran pengalaman kerja dan tarif per jam setelah data dibersihkan?
  5. Apakah variabel yang dianalisis memenuhi asumsi normalitas dan homogenitas varians?

1.3 Tujuan

  1. Mengidentifikasi dan mendokumentasikan permasalahan kualitas pada data mentah.
  2. Menyeragamkan penulisan kategori serta mengubah variabel bertipe teks menjadi numerik.
  3. Menangani nilai hilang melalui imputasi median dan pemberian label Unknown pada status yang tidak diketahui.
  4. Menggambarkan sebaran data melalui visualisasi dan pengelompokan pengalaman kerja.
  5. Menguji asumsi normalitas dan homogenitas varians, serta menyimpan dataset final.

2. Gambaran Dataset

Bagian ini memperlihatkan isi dataset sebelum diolah, supaya kondisi awalnya bisa dibandingkan dengan hasil akhir.

2.1 Import Dataset

File Excel diletakkan satu folder dengan file .Rmd ini, sehingga lokasinya cukup ditulis dengan nama file.

if (!requireNamespace("readxl", quietly = TRUE)) {
  install.packages("readxl")
}

library(readxl)

lokasi_file <- "data ade global_freelancers_raw.xlsx"

if (!file.exists(lokasi_file)) {
  stop("File Excel tidak ditemukan. Pastikan satu folder dengan file Rmd.")
}

ADE <- read_excel(lokasi_file)

2.2 Struktur Awal Data

cat("Jumlah baris:", nrow(ADE), "\n")
## Jumlah baris: 1000
cat("Jumlah kolom:", ncol(ADE), "\n\n")
## Jumlah kolom: 12
names(ADE)
##  [1] "freelancer_ID"       "name"                "gender"             
##  [4] "age"                 "country"             "language"           
##  [7] "primary_skill"       "years_of_experience" "hourly_rate (USD)"  
## [10] "rating"              "is_active"           "client_satisfaction"
str(ADE)
## tibble [1,000 × 12] (S3: tbl_df/tbl/data.frame)
##  $ freelancer_ID      : chr [1:1000] "FL250001" "FL250002" "FL250003" "FL250004" ...
##  $ name               : chr [1:1000] "Ms. Nicole Kidd" "Vanessa Garcia" "Juan Nelson" "Amanda Spencer" ...
##  $ gender             : chr [1:1000] "f" "FEMALE" "male" "F" ...
##  $ age                : chr [1:1000] "52.0" "52.0" "53.0" "38.0" ...
##  $ country            : chr [1:1000] "Italy" "Australia" "Germany" "Australia" ...
##  $ language           : chr [1:1000] "Italian" "English" "German" "English" ...
##  $ primary_skill      : chr [1:1000] "Blockchain Development" "Mobile Apps" "Graphic Design" "Web Development" ...
##  $ years_of_experience: chr [1:1000] "11.0" "34.0" "31.0" "4.0" ...
##  $ hourly_rate (USD)  : chr [1:1000] "100" "USD 100" "50" "$40" ...
##  $ rating             : chr [1:1000] NA "3.3" "0.0" "1.5" ...
##  $ is_active          : chr [1:1000] "0" "1" "N" "N" ...
##  $ client_satisfaction: num [1:1000] NA 0.84 0.71 0.9 0.83 NA 0.94 0.94 0.76 NA ...
head(ADE)
## # A tibble: 6 × 12
##   freelancer_ID name            gender age   country     language primary_skill 
##   <chr>         <chr>           <chr>  <chr> <chr>       <chr>    <chr>         
## 1 FL250001      Ms. Nicole Kidd f      52.0  Italy       Italian  Blockchain De…
## 2 FL250002      Vanessa Garcia  FEMALE 52.0  Australia   English  Mobile Apps   
## 3 FL250003      Juan Nelson     male   53.0  Germany     German   Graphic Design
## 4 FL250004      Amanda Spencer  F      38.0  Australia   English  Web Developme…
## 5 FL250005      Lynn Curtis DDS female 53.0  Germany     German   Web Developme…
## 6 FL250006      Lisa Johnson    female 59.0  Netherlands Dutch    AI            
## # ℹ 5 more variables: years_of_experience <chr>, `hourly_rate (USD)` <chr>,
## #   rating <chr>, is_active <chr>, client_satisfaction <dbl>
tail(ADE)
## # A tibble: 6 × 12
##   freelancer_ID name            gender age   country      language primary_skill
##   <chr>         <chr>           <chr>  <chr> <chr>        <chr>    <chr>        
## 1 FL250995      Jennifer Hinton Female 34.0  South Africa Afrikaa… DevOps       
## 2 FL250996      Albert Wilcox   Male   56.0  Turkey       Turkish  DevOps       
## 3 FL250997      Cheryl Norris   f      26.0  Germany      German   Blockchain D…
## 4 FL250998      Kathy Watkins   female 37.0  Japan        Japanese Data Analysis
## 5 FL250999      John Obrien     m      46.0  Russia       Russian  Machine Lear…
## 6 FL251000      Dawn Green      Female 36.0  Mexico       Spanish  UI/UX Design 
## # ℹ 5 more variables: years_of_experience <chr>, `hourly_rate (USD)` <chr>,
## #   rating <chr>, is_active <chr>, client_satisfaction <dbl>
summary(ADE)
##    freelancer_ID         name            gender            age      
##  Length   :1000   Length   :1000   Length   :1000   Length   :1000  
##  N.unique :1000   N.unique : 992   N.unique :  10   N.unique :  41  
##  N.blank  :   0   N.blank  :   0   N.blank  :   0   N.blank  :   0  
##  Min.nchar:   8   Min.nchar:   7   Min.nchar:   1   Min.nchar:   4  
##  Max.nchar:   8   Max.nchar:  25   Max.nchar:   6   Max.nchar:   4  
##                                                     NAs      :  30  
##                                                                     
##       country          language      primary_skill  years_of_experience
##  Length   :1000   Length   :1000   Length   :1000   Length   :1000     
##  N.unique :  21   N.unique :  16   N.unique :  10   N.unique :  41     
##  N.blank  :   0   N.blank  :   0   N.blank  :   0   N.blank  :   0     
##  Min.nchar:   5   Min.nchar:   5   Min.nchar:   2   Min.nchar:   3     
##  Max.nchar:  14   Max.nchar:  10   Max.nchar:  22   Max.nchar:   4     
##                                                     NAs      :  51     
##                                                                        
##  hourly_rate (USD)       rating         is_active    client_satisfaction
##  Length   :1000    Length   :1000   Length   :1000   Min.   :  0.60     
##  N.unique :  18    N.unique :  42   N.unique :   8   1st Qu.:  0.71     
##  N.blank  :   0    N.blank  :   0   N.blank  :   0   Median :  0.82     
##  Min.nchar:   2    Min.nchar:   3   Min.nchar:   1   Mean   : 11.73     
##  Max.nchar:   7    Max.nchar:   3   Max.nchar:   5   3rd Qu.:  0.94     
##  NAs      :  94    NAs      : 101   NAs      :  89   Max.   :100.00     
##                                                      NAs    :176

3. Permasalahan Data

3.1 Missing Value

colSums(is.na(ADE))
##       freelancer_ID                name              gender                 age 
##                   0                   0                   0                  30 
##             country            language       primary_skill years_of_experience 
##                   0                   0                   0                  51 
##   hourly_rate (USD)              rating           is_active client_satisfaction 
##                  94                 101                  89                 176
round(colMeans(is.na(ADE)) * 100, 2)
##       freelancer_ID                name              gender                 age 
##                 0.0                 0.0                 0.0                 3.0 
##             country            language       primary_skill years_of_experience 
##                 0.0                 0.0                 0.0                 5.1 
##   hourly_rate (USD)              rating           is_active client_satisfaction 
##                 9.4                10.1                 8.9                17.6
cat("Total missing value:", sum(is.na(ADE)), "\n")
## Total missing value: 541

3.2 Inkonsistensi Gender

Pertama dicek dulu variasi penulisan pada kolom gender, lalu diseragamkan menjadi Female dan Male.

table(ADE$gender, useNA = "ifany")
## 
##      f      F female Female FEMALE      m      M   male   Male   MALE 
##    103     90     86     96    115     99    106    100    103    102
ADE$gender <- tolower(trimws(as.character(ADE$gender)))

ADE$gender[ADE$gender %in% c("f", "female")] <- "Female"
ADE$gender[ADE$gender %in% c("m", "male")] <- "Male"

table(ADE$gender, useNA = "ifany")
## 
## Female   Male 
##    490    510

3.3 Membersihkan Hourly Rate

Kolom tarif per jam masih bisa mengandung simbol $ dan tulisan USD, sehingga terbaca sebagai teks. Simbol dihapus dulu, baru kolomnya diubah menjadi angka.

unique(ADE$`hourly_rate (USD)`)
##  [1] "100"     "USD 100" "50"      "$40"     "30"      "$30"     "USD 75" 
##  [8] "USD 40"  NA        "$50"     "40"      "75"      "USD 50"  "USD 30" 
## [15] "$20"     "20"      "$75"     "$100"    "USD 20"
ADE$`hourly_rate (USD)` <- gsub("\\$", "", as.character(ADE$`hourly_rate (USD)`))
ADE$`hourly_rate (USD)` <- gsub("USD", "", ADE$`hourly_rate (USD)`, ignore.case = TRUE)
ADE$`hourly_rate (USD)` <- trimws(ADE$`hourly_rate (USD)`)
ADE$`hourly_rate (USD)` <- as.numeric(ADE$`hourly_rate (USD)`)

str(ADE$`hourly_rate (USD)`)
##  num [1:1000] 100 100 50 40 30 30 75 40 NA 50 ...

3.4 Membersihkan Client Satisfaction

Tanda persen dibuang lalu kolom dijadikan numerik. Nilai kosong yang tersisa diisi dengan median, karena median tidak terlalu terpengaruh oleh nilai ekstrem.

unique(ADE$client_satisfaction)
##  [1]     NA   0.84   0.71   0.90   0.83   0.94   0.76   0.77   0.86   0.93
## [11]   0.70   0.69   0.60   0.87   0.75   0.68   0.65   1.00  92.00   0.89
## [21]   0.62  82.00   0.81   0.63   0.67   0.80   0.74   0.85   0.79   0.72
## [31]  64.00  88.00   0.96  96.00  81.00   0.61   0.97   0.64   0.73   0.88
## [41]  72.00   0.92   0.82  93.00  83.00  78.00   0.95  80.00  87.00   0.66
## [51]   0.78  68.00   0.91  97.00  60.00  70.00   0.99  76.00  86.00  95.00
## [61]  74.00 100.00  73.00  67.00  77.00   0.98  71.00  85.00  91.00  94.00
## [71]  84.00  90.00  62.00  65.00  75.00  63.00  61.00  66.00  99.00  79.00
## [81]  69.00  89.00
ADE$client_satisfaction <- gsub("%", "", as.character(ADE$client_satisfaction))
ADE$client_satisfaction <- trimws(ADE$client_satisfaction)
ADE$client_satisfaction <- as.numeric(ADE$client_satisfaction)

str(ADE$client_satisfaction)
##  num [1:1000] NA 0.84 0.71 0.9 0.83 NA 0.94 0.94 0.76 NA ...
# Isi NA dengan median
sum(is.na(ADE$client_satisfaction))
## [1] 176
median_satisfaction <- median(ADE$client_satisfaction, na.rm = TRUE)
ADE$client_satisfaction[is.na(ADE$client_satisfaction)] <- median_satisfaction

summary(ADE$client_satisfaction)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##   0.6000   0.7300   0.8200   9.8065   0.9125 100.0000
sum(is.na(ADE$client_satisfaction))
## [1] 0

3.5 Membersihkan Age, Years of Experience, dan Rating

Ketiga kolom ini diperlakukan dengan cara yang sama, yaitu membuang tanda yang tidak perlu dan mengubahnya ke angka.

unique(ADE$age)
##  [1] "52.0" "53.0" "38.0" "59.0" "43.0" "26.0" "40.0" "55.0" "57.0" "50.0"
## [11] "42.0" "39.0" "56.0" NA     "51.0" "54.0" "22.0" "34.0" "36.0" "47.0"
## [21] "41.0" "46.0" "29.0" "60.0" "21.0" "35.0" "25.0" "23.0" "31.0" "58.0"
## [31] "45.0" "33.0" "32.0" "37.0" "49.0" "27.0" "48.0" "30.0" "20.0" "24.0"
## [41] "44.0" "28.0"
ADE$age <- gsub("%", "", as.character(ADE$age))
ADE$age <- trimws(ADE$age)
ADE$age <- as.numeric(ADE$age)

unique(ADE$years_of_experience)
##  [1] "11.0" "34.0" "31.0" "4.0"  "27.0" "14.0" "10.0" "22.0" "17.0" "15.0"
## [11] "18.0" "20.0" "0.0"  "13.0" NA     "3.0"  "9.0"  "21.0" "8.0"  "19.0"
## [21] "32.0" "28.0" "6.0"  "23.0" "7.0"  "1.0"  "33.0" "16.0" "35.0" "12.0"
## [31] "38.0" "5.0"  "30.0" "2.0"  "25.0" "39.0" "29.0" "40.0" "26.0" "24.0"
## [41] "37.0" "41.0"
ADE$years_of_experience <- gsub("%", "", as.character(ADE$years_of_experience))
ADE$years_of_experience <- trimws(ADE$years_of_experience)
ADE$years_of_experience <- as.numeric(ADE$years_of_experience)

unique(ADE$rating)
##  [1] NA    "3.3" "0.0" "1.5" "4.8" "2.4" "3.1" "4.6" "4.0" "3.6" "2.0" "2.9"
## [13] "3.7" "1.2" "2.5" "1.1" "1.8" "4.7" "1.3" "1.0" "4.2" "1.4" "2.2" "3.2"
## [25] "2.7" "2.8" "4.9" "4.5" "1.9" "3.4" "2.3" "3.9" "3.5" "4.4" "2.6" "1.6"
## [37] "3.8" "1.7" "5.0" "3.0" "4.1" "4.3" "2.1"
ADE$rating <- gsub("%", "", as.character(ADE$rating))
ADE$rating <- trimws(ADE$rating)
ADE$rating <- as.numeric(ADE$rating)

str(ADE[, c("age", "years_of_experience", "rating")])
## tibble [1,000 × 3] (S3: tbl_df/tbl/data.frame)
##  $ age                : num [1:1000] 52 52 53 38 53 59 52 43 26 52 ...
##  $ years_of_experience: num [1:1000] 11 34 31 4 27 14 10 14 4 22 ...
##  $ rating             : num [1:1000] NA 3.3 0 1.5 4.8 2.4 3.1 4.6 4 NA ...

3.6 Membersihkan Is_Active

Kolom status aktif punya banyak penulisan (1, y, yes, true, dan seterusnya). Semuanya disatukan menjadi Active atau Inactive, dan nilai yang tidak bisa dikenali diberi label Unknown.

table(ADE$is_active, useNA = "ifany")
## 
##     0     1 FALSE     N    no  TRUE     Y   yes  <NA> 
##   182   190    97    98    88    77    94    85    89
active_raw <- tolower(trimws(as.character(ADE$is_active)))

ADE$is_active <- ifelse(
  active_raw %in% c("1", "y", "yes", "true"),
  "Active",
  ifelse(
    active_raw %in% c("0", "n", "no", "false"),
    "Inactive",
    NA_character_
  )
)

ADE$is_active[is.na(ADE$is_active)] <- "Unknown"

table(ADE$is_active, useNA = "ifany")
## 
##   Active Inactive  Unknown 
##      446      465       89
sum(is.na(ADE$is_active))
## [1] 0

3.7 Duplikasi Data

sum(duplicated(ADE))
## [1] 0
ADE[duplicated(ADE), ]
## # A tibble: 0 × 12
## # ℹ 12 variables: freelancer_ID <chr>, name <chr>, gender <chr>, age <dbl>,
## #   country <chr>, language <chr>, primary_skill <chr>,
## #   years_of_experience <dbl>, hourly_rate (USD) <dbl>, rating <dbl>,
## #   is_active <chr>, client_satisfaction <dbl>
ADE <- ADE[!duplicated(ADE), ]

sum(duplicated(ADE))
## [1] 0
dim(ADE)
## [1] 1000   12

3.8 Data Noisy

Di sini dicek apakah ada nilai yang tidak masuk akal, misalnya usia negatif atau rating di atas 5.

summary(ADE$age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs 
##   20.00   31.00   41.00   40.51   51.00   60.00      30
sum(ADE$age < 0, na.rm = TRUE)
## [1] 0
sum(ADE$age > 100, na.rm = TRUE)
## [1] 0
summary(ADE$years_of_experience)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs 
##    0.00    3.00    9.00   11.34   17.00   41.00      51
sum(ADE$years_of_experience < 0, na.rm = TRUE)
## [1] 0
summary(ADE$rating)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs 
##   0.000   1.400   2.600   2.513   3.800   5.000     101
sum(ADE$rating < 0, na.rm = TRUE)
## [1] 0
sum(ADE$rating > 5, na.rm = TRUE)
## [1] 0
summary(ADE$`hourly_rate (USD)`)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs 
##   20.00   30.00   40.00   52.46   75.00  100.00      94
sum(ADE$`hourly_rate (USD)` < 0, na.rm = TRUE)
## [1] 0
summary(ADE$client_satisfaction)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##   0.6000   0.7300   0.8200   9.8065   0.9125 100.0000
sum(ADE$client_satisfaction < 0, na.rm = TRUE)
## [1] 0
sum(ADE$client_satisfaction > 100, na.rm = TRUE)
## [1] 0

4. Data Clean

4.1 Membuat Data Clean

Data hasil pembersihan disalin ke objek baru bernama data_clean, supaya data yang sudah diseragamkan tetap tersimpan terpisah dari tahap imputasi.

data_clean <- ADE

str(data_clean)
## tibble [1,000 × 12] (S3: tbl_df/tbl/data.frame)
##  $ freelancer_ID      : chr [1:1000] "FL250001" "FL250002" "FL250003" "FL250004" ...
##  $ name               : chr [1:1000] "Ms. Nicole Kidd" "Vanessa Garcia" "Juan Nelson" "Amanda Spencer" ...
##  $ gender             : chr [1:1000] "Female" "Female" "Male" "Female" ...
##  $ age                : num [1:1000] 52 52 53 38 53 59 52 43 26 52 ...
##  $ country            : chr [1:1000] "Italy" "Australia" "Germany" "Australia" ...
##  $ language           : chr [1:1000] "Italian" "English" "German" "English" ...
##  $ primary_skill      : chr [1:1000] "Blockchain Development" "Mobile Apps" "Graphic Design" "Web Development" ...
##  $ years_of_experience: num [1:1000] 11 34 31 4 27 14 10 14 4 22 ...
##  $ hourly_rate (USD)  : num [1:1000] 100 100 50 40 30 30 75 40 NA 50 ...
##  $ rating             : num [1:1000] NA 3.3 0 1.5 4.8 2.4 3.1 4.6 4 NA ...
##  $ is_active          : chr [1:1000] "Inactive" "Active" "Inactive" "Inactive" ...
##  $ client_satisfaction: num [1:1000] 0.82 0.84 0.71 0.9 0.83 0.82 0.94 0.94 0.76 0.82 ...
dim(data_clean)
## [1] 1000   12
summary(data_clean)
##    freelancer_ID         name            gender          age       
##  Length   :1000   Length   :1000   Length   :1000   Min.   :20.00  
##  N.unique :1000   N.unique : 992   N.unique :   2   1st Qu.:31.00  
##  N.blank  :   0   N.blank  :   0   N.blank  :   0   Median :41.00  
##  Min.nchar:   8   Min.nchar:   7   Min.nchar:   4   Mean   :40.51  
##  Max.nchar:   8   Max.nchar:  25   Max.nchar:   6   3rd Qu.:51.00  
##                                                     Max.   :60.00  
##                                                     NAs    :30     
##       country          language      primary_skill  years_of_experience
##  Length   :1000   Length   :1000   Length   :1000   Min.   : 0.00      
##  N.unique :  21   N.unique :  16   N.unique :  10   1st Qu.: 3.00      
##  N.blank  :   0   N.blank  :   0   N.blank  :   0   Median : 9.00      
##  Min.nchar:   5   Min.nchar:   5   Min.nchar:   2   Mean   :11.34      
##  Max.nchar:  14   Max.nchar:  10   Max.nchar:  22   3rd Qu.:17.00      
##                                                     Max.   :41.00      
##                                                     NAs    :51         
##  hourly_rate (USD)     rating          is_active    client_satisfaction
##  Min.   : 20.00    Min.   :0.000   Length   :1000   Min.   :  0.6000   
##  1st Qu.: 30.00    1st Qu.:1.400   N.unique :   3   1st Qu.:  0.7300   
##  Median : 40.00    Median :2.600   N.blank  :   0   Median :  0.8200   
##  Mean   : 52.46    Mean   :2.513   Min.nchar:   6   Mean   :  9.8065   
##  3rd Qu.: 75.00    3rd Qu.:3.800   Max.nchar:   8   3rd Qu.:  0.9125   
##  Max.   :100.00    Max.   :5.000                    Max.   :100.0000   
##  NAs    :94        NAs    :101

4.2 Mengisi Missing Value dengan Median

Imputasi median dilakukan pada pengalaman kerja, tarif per jam, dan rating.

# Years of experience
if (any(!is.na(data_clean$years_of_experience))) {
  median_exp <- median(data_clean$years_of_experience, na.rm = TRUE)
  data_clean$years_of_experience_clean <- ifelse(
    is.na(data_clean$years_of_experience),
    median_exp,
    data_clean$years_of_experience
  )
} else {
  data_clean$years_of_experience_clean <- data_clean$years_of_experience
}

# Hourly rate
if (any(!is.na(data_clean$`hourly_rate (USD)`))) {
  median_rate <- median(data_clean$`hourly_rate (USD)`, na.rm = TRUE)
  data_clean$hourly_rate_clean <- ifelse(
    is.na(data_clean$`hourly_rate (USD)`),
    median_rate,
    data_clean$`hourly_rate (USD)`
  )
} else {
  data_clean$hourly_rate_clean <- data_clean$`hourly_rate (USD)`
}

# Rating
if (any(!is.na(data_clean$rating))) {
  median_rating <- median(data_clean$rating, na.rm = TRUE)
  data_clean$rating[is.na(data_clean$rating)] <- median_rating
}

# Cek hasil imputasi
sum(is.na(data_clean$years_of_experience_clean))
## [1] 0
sum(is.na(data_clean$hourly_rate_clean))
## [1] 0
sum(is.na(data_clean$rating))
## [1] 0

5. Validasi Setelah Pembersihan

Semua pemeriksaan di bagian 3 diulang pada data_clean untuk memastikan masalahnya sudah teratasi.

str(data_clean)
## tibble [1,000 × 14] (S3: tbl_df/tbl/data.frame)
##  $ freelancer_ID            : chr [1:1000] "FL250001" "FL250002" "FL250003" "FL250004" ...
##  $ name                     : chr [1:1000] "Ms. Nicole Kidd" "Vanessa Garcia" "Juan Nelson" "Amanda Spencer" ...
##  $ gender                   : chr [1:1000] "Female" "Female" "Male" "Female" ...
##  $ age                      : num [1:1000] 52 52 53 38 53 59 52 43 26 52 ...
##  $ country                  : chr [1:1000] "Italy" "Australia" "Germany" "Australia" ...
##  $ language                 : chr [1:1000] "Italian" "English" "German" "English" ...
##  $ primary_skill            : chr [1:1000] "Blockchain Development" "Mobile Apps" "Graphic Design" "Web Development" ...
##  $ years_of_experience      : num [1:1000] 11 34 31 4 27 14 10 14 4 22 ...
##  $ hourly_rate (USD)        : num [1:1000] 100 100 50 40 30 30 75 40 NA 50 ...
##  $ rating                   : num [1:1000] 2.6 3.3 0 1.5 4.8 2.4 3.1 4.6 4 2.6 ...
##  $ is_active                : chr [1:1000] "Inactive" "Active" "Inactive" "Inactive" ...
##  $ client_satisfaction      : num [1:1000] 0.82 0.84 0.71 0.9 0.83 0.82 0.94 0.94 0.76 0.82 ...
##  $ years_of_experience_clean: num [1:1000] 11 34 31 4 27 14 10 14 4 22 ...
##  $ hourly_rate_clean        : num [1:1000] 100 100 50 40 30 30 75 40 40 50 ...
dim(data_clean)
## [1] 1000   14
summary(data_clean)
##    freelancer_ID         name            gender          age       
##  Length   :1000   Length   :1000   Length   :1000   Min.   :20.00  
##  N.unique :1000   N.unique : 992   N.unique :   2   1st Qu.:31.00  
##  N.blank  :   0   N.blank  :   0   N.blank  :   0   Median :41.00  
##  Min.nchar:   8   Min.nchar:   7   Min.nchar:   4   Mean   :40.51  
##  Max.nchar:   8   Max.nchar:  25   Max.nchar:   6   3rd Qu.:51.00  
##                                                     Max.   :60.00  
##                                                     NAs    :30     
##       country          language      primary_skill  years_of_experience
##  Length   :1000   Length   :1000   Length   :1000   Min.   : 0.00      
##  N.unique :  21   N.unique :  16   N.unique :  10   1st Qu.: 3.00      
##  N.blank  :   0   N.blank  :   0   N.blank  :   0   Median : 9.00      
##  Min.nchar:   5   Min.nchar:   5   Min.nchar:   2   Mean   :11.34      
##  Max.nchar:  14   Max.nchar:  10   Max.nchar:  22   3rd Qu.:17.00      
##                                                     Max.   :41.00      
##                                                     NAs    :51         
##  hourly_rate (USD)     rating          is_active    client_satisfaction
##  Min.   : 20.00    Min.   :0.000   Length   :1000   Min.   :  0.6000   
##  1st Qu.: 30.00    1st Qu.:1.500   N.unique :   3   1st Qu.:  0.7300   
##  Median : 40.00    Median :2.600   N.blank  :   0   Median :  0.8200   
##  Mean   : 52.46    Mean   :2.521   Min.nchar:   6   Mean   :  9.8065   
##  3rd Qu.: 75.00    3rd Qu.:3.700   Max.nchar:   8   3rd Qu.:  0.9125   
##  Max.   :100.00    Max.   :5.000                    Max.   :100.0000   
##  NAs    :94                                                            
##  years_of_experience_clean hourly_rate_clean
##  Min.   : 0.00             Min.   : 20.00   
##  1st Qu.: 3.00             1st Qu.: 30.00   
##  Median : 9.00             Median : 40.00   
##  Mean   :11.22             Mean   : 51.29   
##  3rd Qu.:17.00             3rd Qu.: 75.00   
##  Max.   :41.00             Max.   :100.00   
## 
colSums(is.na(data_clean))
##             freelancer_ID                      name                    gender 
##                         0                         0                         0 
##                       age                   country                  language 
##                        30                         0                         0 
##             primary_skill       years_of_experience         hourly_rate (USD) 
##                         0                        51                        94 
##                    rating                 is_active       client_satisfaction 
##                         0                         0                         0 
## years_of_experience_clean         hourly_rate_clean 
##                         0                         0
round(colMeans(is.na(data_clean)) * 100, 2)
##             freelancer_ID                      name                    gender 
##                       0.0                       0.0                       0.0 
##                       age                   country                  language 
##                       3.0                       0.0                       0.0 
##             primary_skill       years_of_experience         hourly_rate (USD) 
##                       0.0                       5.1                       9.4 
##                    rating                 is_active       client_satisfaction 
##                       0.0                       0.0                       0.0 
## years_of_experience_clean         hourly_rate_clean 
##                       0.0                       0.0
cat("Baris duplikat:", sum(duplicated(data_clean)), "\n")
## Baris duplikat: 0
cat("Usia di luar 0-100:",
    sum(data_clean$age < 0 | data_clean$age > 100, na.rm = TRUE), "\n")
## Usia di luar 0-100: 0
cat("Pengalaman kerja negatif:",
    sum(data_clean$years_of_experience_clean < 0, na.rm = TRUE), "\n")
## Pengalaman kerja negatif: 0
cat("Rating di luar 0-5:",
    sum(data_clean$rating < 0 | data_clean$rating > 5, na.rm = TRUE), "\n")
## Rating di luar 0-5: 0
cat("Hourly rate negatif:",
    sum(data_clean$hourly_rate_clean < 0, na.rm = TRUE), "\n")
## Hourly rate negatif: 0
cat("Client satisfaction di luar 0-100:",
    sum(data_clean$client_satisfaction < 0 |
          data_clean$client_satisfaction > 100, na.rm = TRUE), "\n")
## Client satisfaction di luar 0-100: 0

6. Transformasi dan Visualisasi

6.1 Encoding Pengalaman Kerja

Pengalaman kerja dikelompokkan ke dalam empat kategori berurutan, lalu setiap kategori diberi kode angka 1 sampai 4.

library(dplyr)

max(data_clean$years_of_experience_clean, na.rm = TRUE)
## [1] 41
data_clean$kategori_pengalaman <- ifelse(
  is.na(data_clean$years_of_experience_clean),
  NA_character_,
  ifelse(
    data_clean$years_of_experience_clean <= 10,
    "pemula",
    ifelse(
      data_clean$years_of_experience_clean <= 20,
      "menengah",
      ifelse(
        data_clean$years_of_experience_clean <= 30,
        "jago",
        "master"
      )
    )
  )
)

data_clean$kategori_encode <- as.numeric(
  factor(
    data_clean$kategori_pengalaman,
    levels = c("pemula", "menengah", "jago", "master")
  )
)

table(
  data_clean$kategori_pengalaman,
  data_clean$kategori_encode,
  useNA = "ifany"
)
##           
##              1   2   3   4
##   jago       0   0 122   0
##   master     0   0   0  55
##   menengah   0 252   0   0
##   pemula   571   0   0   0
str(data_clean$kategori_encode)
##  num [1:1000] 2 4 4 1 3 2 1 2 1 3 ...

6.2 Histogram

hist(
  na.omit(data_clean$years_of_experience_clean),
  main = "Histogram Pengalaman Kerja",
  xlab = "Years of Experience",
  col = "#A3B18A"
)

hist(
  na.omit(data_clean$kategori_encode),
  main = "Histogram Kategori Pengalaman",
  xlab = "Kode Kategori",
  breaks = seq(0.5, 4.5, by = 1),
  col = "#588157"
)

6.3 Q-Q Plot

hourly_rate_test <- na.omit(data_clean$hourly_rate_clean)
experience_test  <- na.omit(data_clean$years_of_experience_clean)

if (length(hourly_rate_test) >= 2) {
  qqnorm(hourly_rate_test, main = "QQ Plot Hourly Rate")
  qqline(hourly_rate_test, col = "#344E41")
}

if (length(experience_test) >= 2) {
  qqnorm(experience_test, main = "QQ Plot Years of Experience")
  qqline(experience_test, col = "#344E41")
}

6.4 Scatter Plot

plot(
  data_clean$years_of_experience_clean,
  data_clean$hourly_rate_clean,
  main = "Pengalaman Kerja dan Hourly Rate",
  xlab = "Years of Experience",
  ylab = "Hourly Rate (USD)",
  pch = 19,
  col = "#3A5A40"
)

7. Uji Asumsi

7.1 Uji Normalitas (Shapiro-Wilk)

Hipotesis yang diuji: H0 menyatakan data berdistribusi normal. Jika p-value kurang dari 0,05, H0 ditolak, artinya data tidak berdistribusi normal. Hasil uji sebaiknya dibaca bersama histogram dan Q-Q plot di atas.

if (length(hourly_rate_test) >= 3 && length(hourly_rate_test) <= 5000) {
  print(shapiro.test(hourly_rate_test))
} else {
  message("Shapiro-Wilk membutuhkan 3 sampai 5000 observasi.")
}
## 
##  Shapiro-Wilk normality test
## 
## data:  hourly_rate_test
## W = 0.85064, p-value < 2.2e-16
if (length(experience_test) >= 3 && length(experience_test) <= 5000) {
  print(shapiro.test(experience_test))
} else {
  message("Shapiro-Wilk membutuhkan 3 sampai 5000 observasi.")
}
## 
##  Shapiro-Wilk normality test
## 
## data:  experience_test
## W = 0.9101, p-value < 2.2e-16

7.2 Uji Homogenitas Varians (Bartlett)

Uji ini membandingkan varians tarif per jam antar kelompok gender. H0 menyatakan varians antar kelompok sama. Jika p-value kurang dari 0,05, varians dianggap berbeda.

data_bartlett <- data_clean[
  !is.na(data_clean$hourly_rate_clean) & !is.na(data_clean$gender),
]

data_bartlett$gender <- droplevels(factor(data_bartlett$gender))

if (
  length(unique(data_bartlett$gender)) >= 2 &&
  all(table(data_bartlett$gender) >= 2)
) {
  print(bartlett.test(hourly_rate_clean ~ gender, data = data_bartlett))
} else {
  message("Data kelompok belum cukup untuk uji Bartlett.")
}
## 
##  Bartlett test of homogeneity of variances
## 
## data:  hourly_rate_clean by gender
## Bartlett's K-squared = 1.4921, df = 1, p-value = 0.2219

8. Dataset Final

8.1 Membuat Data Final

Hasil imputasi dimasukkan kembali ke kolom aslinya, lalu kolom sementara dihapus.

data_final <- data_clean

data_final$years_of_experience <- data_final$years_of_experience_clean
data_final$`hourly_rate (USD)` <- data_final$hourly_rate_clean

data_final$years_of_experience_clean <- NULL
data_final$hourly_rate_clean <- NULL

str(data_final)
## tibble [1,000 × 14] (S3: tbl_df/tbl/data.frame)
##  $ freelancer_ID      : chr [1:1000] "FL250001" "FL250002" "FL250003" "FL250004" ...
##  $ name               : chr [1:1000] "Ms. Nicole Kidd" "Vanessa Garcia" "Juan Nelson" "Amanda Spencer" ...
##  $ gender             : chr [1:1000] "Female" "Female" "Male" "Female" ...
##  $ age                : num [1:1000] 52 52 53 38 53 59 52 43 26 52 ...
##  $ country            : chr [1:1000] "Italy" "Australia" "Germany" "Australia" ...
##  $ language           : chr [1:1000] "Italian" "English" "German" "English" ...
##  $ primary_skill      : chr [1:1000] "Blockchain Development" "Mobile Apps" "Graphic Design" "Web Development" ...
##  $ years_of_experience: num [1:1000] 11 34 31 4 27 14 10 14 4 22 ...
##  $ hourly_rate (USD)  : num [1:1000] 100 100 50 40 30 30 75 40 40 50 ...
##  $ rating             : num [1:1000] 2.6 3.3 0 1.5 4.8 2.4 3.1 4.6 4 2.6 ...
##  $ is_active          : chr [1:1000] "Inactive" "Active" "Inactive" "Inactive" ...
##  $ client_satisfaction: num [1:1000] 0.82 0.84 0.71 0.9 0.83 0.82 0.94 0.94 0.76 0.82 ...
##  $ kategori_pengalaman: chr [1:1000] "menengah" "master" "master" "pemula" ...
##  $ kategori_encode    : num [1:1000] 2 4 4 1 3 2 1 2 1 3 ...
summary(data_final)
##    freelancer_ID         name            gender          age       
##  Length   :1000   Length   :1000   Length   :1000   Min.   :20.00  
##  N.unique :1000   N.unique : 992   N.unique :   2   1st Qu.:31.00  
##  N.blank  :   0   N.blank  :   0   N.blank  :   0   Median :41.00  
##  Min.nchar:   8   Min.nchar:   7   Min.nchar:   4   Mean   :40.51  
##  Max.nchar:   8   Max.nchar:  25   Max.nchar:   6   3rd Qu.:51.00  
##                                                     Max.   :60.00  
##                                                     NAs    :30     
##       country          language      primary_skill  years_of_experience
##  Length   :1000   Length   :1000   Length   :1000   Min.   : 0.00      
##  N.unique :  21   N.unique :  16   N.unique :  10   1st Qu.: 3.00      
##  N.blank  :   0   N.blank  :   0   N.blank  :   0   Median : 9.00      
##  Min.nchar:   5   Min.nchar:   5   Min.nchar:   2   Mean   :11.22      
##  Max.nchar:  14   Max.nchar:  10   Max.nchar:  22   3rd Qu.:17.00      
##                                                     Max.   :41.00      
##                                                                        
##  hourly_rate (USD)     rating          is_active    client_satisfaction
##  Min.   : 20.00    Min.   :0.000   Length   :1000   Min.   :  0.6000   
##  1st Qu.: 30.00    1st Qu.:1.500   N.unique :   3   1st Qu.:  0.7300   
##  Median : 40.00    Median :2.600   N.blank  :   0   Median :  0.8200   
##  Mean   : 51.29    Mean   :2.521   Min.nchar:   6   Mean   :  9.8065   
##  3rd Qu.: 75.00    3rd Qu.:3.700   Max.nchar:   8   3rd Qu.:  0.9125   
##  Max.   :100.00    Max.   :5.000                    Max.   :100.0000   
##                                                                        
##  kategori_pengalaman kategori_encode
##  Length   :1000      Min.   :1.000  
##  N.unique :   4      1st Qu.:1.000  
##  N.blank  :   0      Median :1.000  
##  Min.nchar:   4      Mean   :1.661  
##  Max.nchar:   8      3rd Qu.:2.000  
##                      Max.   :4.000  
## 
dim(data_final)
## [1] 1000   14
colSums(is.na(data_final))
##       freelancer_ID                name              gender                 age 
##                   0                   0                   0                  30 
##             country            language       primary_skill years_of_experience 
##                   0                   0                   0                   0 
##   hourly_rate (USD)              rating           is_active client_satisfaction 
##                   0                   0                   0                   0 
## kategori_pengalaman     kategori_encode 
##                   0                   0
sum(duplicated(data_final))
## [1] 0

8.2 Integrasi Data

Dari data final dipilih kolom-kolom yang dibutuhkan untuk analisis. intersect() dipakai supaya hanya kolom yang benar-benar ada yang diambil.

data_integrasi <- data_final

str(data_integrasi)
## tibble [1,000 × 14] (S3: tbl_df/tbl/data.frame)
##  $ freelancer_ID      : chr [1:1000] "FL250001" "FL250002" "FL250003" "FL250004" ...
##  $ name               : chr [1:1000] "Ms. Nicole Kidd" "Vanessa Garcia" "Juan Nelson" "Amanda Spencer" ...
##  $ gender             : chr [1:1000] "Female" "Female" "Male" "Female" ...
##  $ age                : num [1:1000] 52 52 53 38 53 59 52 43 26 52 ...
##  $ country            : chr [1:1000] "Italy" "Australia" "Germany" "Australia" ...
##  $ language           : chr [1:1000] "Italian" "English" "German" "English" ...
##  $ primary_skill      : chr [1:1000] "Blockchain Development" "Mobile Apps" "Graphic Design" "Web Development" ...
##  $ years_of_experience: num [1:1000] 11 34 31 4 27 14 10 14 4 22 ...
##  $ hourly_rate (USD)  : num [1:1000] 100 100 50 40 30 30 75 40 40 50 ...
##  $ rating             : num [1:1000] 2.6 3.3 0 1.5 4.8 2.4 3.1 4.6 4 2.6 ...
##  $ is_active          : chr [1:1000] "Inactive" "Active" "Inactive" "Inactive" ...
##  $ client_satisfaction: num [1:1000] 0.82 0.84 0.71 0.9 0.83 0.82 0.94 0.94 0.76 0.82 ...
##  $ kategori_pengalaman: chr [1:1000] "menengah" "master" "master" "pemula" ...
##  $ kategori_encode    : num [1:1000] 2 4 4 1 3 2 1 2 1 3 ...
dim(data_integrasi)
## [1] 1000   14
names(data_integrasi)
##  [1] "freelancer_ID"       "name"                "gender"             
##  [4] "age"                 "country"             "language"           
##  [7] "primary_skill"       "years_of_experience" "hourly_rate (USD)"  
## [10] "rating"              "is_active"           "client_satisfaction"
## [13] "kategori_pengalaman" "kategori_encode"
kolom_analisis <- c(
  "age",
  "gender",
  "country",
  "years_of_experience",
  "kategori_pengalaman",
  "kategori_encode",
  "hourly_rate (USD)",
  "client_satisfaction",
  "rating",
  "is_active"
)

kolom_tersedia <- intersect(kolom_analisis, names(data_integrasi))

data_analisis <- data_integrasi[, kolom_tersedia, drop = FALSE]

head(data_analisis)
## # A tibble: 6 × 10
##     age gender country   years_of_experience kategori_pengalaman kategori_encode
##   <dbl> <chr>  <chr>                   <dbl> <chr>                         <dbl>
## 1    52 Female Italy                      11 menengah                          2
## 2    52 Female Australia                  34 master                            4
## 3    53 Male   Germany                    31 master                            4
## 4    38 Female Australia                   4 pemula                            1
## 5    53 Female Germany                    27 jago                              3
## 6    59 Female Netherla…                  14 menengah                          2
## # ℹ 4 more variables: `hourly_rate (USD)` <dbl>, client_satisfaction <dbl>,
## #   rating <dbl>, is_active <chr>
str(data_analisis)
## tibble [1,000 × 10] (S3: tbl_df/tbl/data.frame)
##  $ age                : num [1:1000] 52 52 53 38 53 59 52 43 26 52 ...
##  $ gender             : chr [1:1000] "Female" "Female" "Male" "Female" ...
##  $ country            : chr [1:1000] "Italy" "Australia" "Germany" "Australia" ...
##  $ years_of_experience: num [1:1000] 11 34 31 4 27 14 10 14 4 22 ...
##  $ kategori_pengalaman: chr [1:1000] "menengah" "master" "master" "pemula" ...
##  $ kategori_encode    : num [1:1000] 2 4 4 1 3 2 1 2 1 3 ...
##  $ hourly_rate (USD)  : num [1:1000] 100 100 50 40 30 30 75 40 40 50 ...
##  $ client_satisfaction: num [1:1000] 0.82 0.84 0.71 0.9 0.83 0.82 0.94 0.94 0.76 0.82 ...
##  $ rating             : num [1:1000] 2.6 3.3 0 1.5 4.8 2.4 3.1 4.6 4 2.6 ...
##  $ is_active          : chr [1:1000] "Inactive" "Active" "Inactive" "Inactive" ...
summary(data_analisis)
##       age              gender          country     years_of_experience
##  Min.   :20.00   Length   :1000   Length   :1000   Min.   : 0.00      
##  1st Qu.:31.00   N.unique :   2   N.unique :  21   1st Qu.: 3.00      
##  Median :41.00   N.blank  :   0   N.blank  :   0   Median : 9.00      
##  Mean   :40.51   Min.nchar:   4   Min.nchar:   5   Mean   :11.22      
##  3rd Qu.:51.00   Max.nchar:   6   Max.nchar:  14   3rd Qu.:17.00      
##  Max.   :60.00                                     Max.   :41.00      
##  NAs    :30                                                           
##  kategori_pengalaman kategori_encode hourly_rate (USD) client_satisfaction
##  Length   :1000      Min.   :1.000   Min.   : 20.00    Min.   :  0.6000   
##  N.unique :   4      1st Qu.:1.000   1st Qu.: 30.00    1st Qu.:  0.7300   
##  N.blank  :   0      Median :1.000   Median : 40.00    Median :  0.8200   
##  Min.nchar:   4      Mean   :1.661   Mean   : 51.29    Mean   :  9.8065   
##  Max.nchar:   8      3rd Qu.:2.000   3rd Qu.: 75.00    3rd Qu.:  0.9125   
##                      Max.   :4.000   Max.   :100.00    Max.   :100.0000   
##                                                                           
##      rating          is_active   
##  Min.   :0.000   Length   :1000  
##  1st Qu.:1.500   N.unique :   3  
##  Median :2.600   N.blank  :   0  
##  Mean   :2.521   Min.nchar:   6  
##  3rd Qu.:3.700   Max.nchar:   8  
##  Max.   :5.000                   
## 

8.3 Validasi Hasil Integrasi

dim(data_analisis)
## [1] 1000   10
colSums(is.na(data_analisis))
##                 age              gender             country years_of_experience 
##                  30                   0                   0                   0 
## kategori_pengalaman     kategori_encode   hourly_rate (USD) client_satisfaction 
##                   0                   0                   0                   0 
##              rating           is_active 
##                   0                   0
sum(duplicated(data_analisis))
## [1] 0
summary(data_analisis$rating)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   0.000   1.500   2.600   2.521   3.700   5.000
sum(is.na(data_analisis$rating))
## [1] 0
table(data_analisis$is_active, useNA = "ifany")
## 
##   Active Inactive  Unknown 
##      446      465       89
table(data_analisis$kategori_pengalaman, useNA = "ifany")
## 
##     jago   master menengah   pemula 
##      122       55      252      571

9. Menyimpan Hasil

write.csv(
  data_analisis,
  "data_analisis_final.csv",
  row.names = FALSE
)

message("Proses selesai. Dataset final telah disimpan.")

10. Kesimpulan

Data mentah global freelancers berhasil dibersihkan lewat beberapa tahap: penyeragaman kategori, perubahan tipe data, penghapusan baris ganda, imputasi median, dan pemberian label Unknown pada status yang tidak jelas. Setelah itu pengalaman kerja dikelompokkan menjadi empat kategori, divisualisasikan, dan diuji asumsinya. Dataset akhir disimpan sebagai data_analisis_final.csv dan siap dipakai untuk analisis lanjutan.