1 Pendahuluan

1.1 Latar Belakang

Kemajuan teknologi digital telah mengubah cara orang bekerja. Platform daring memungkinkan seseorang menawarkan keahliannya sebagai freelancer dari mana saja tanpa harus terikat pada satu kantor atau satu pemberi kerja. Pasar kerja lepas yang terbentuk sangat beragam: para freelancer berbeda dalam usia, negara asal, bidang keahlian, lamanya pengalaman, dan tarif yang mereka pasang. Keragaman ini membuat pasar freelance menarik untuk dipelajari secara statistik, karena banyak faktor yang saling berkaitan dan memengaruhi bagaimana seorang freelancer dinilai oleh klien. Salah satu faktor yang paling sering dibicarakan adalah lama pengalaman (years of experience), sebab pengalaman yang lebih panjang umumnya dianggap menjadi sinyal kemampuan dan kepercayaan sehingga berpengaruh pada besarnya tarif per jam (hourly rate).

Data yang digunakan dalam laporan ini adalah Global Freelancers Dataset dari Kaggle, yang berisi 1.000 observasi freelancer dengan 12 variabel. Variabel-variabel tersebut mencakup ciri individu seperti usia dan jenis kelamin, pengalaman kerja, tarif per jam, rating, tingkat kepuasan klien, serta status aktif. Data mentah seperti ini jarang langsung siap dianalisis. Pada praktiknya ditemukan nilai yang kosong (missing value), penulisan kategori yang tidak seragam, simbol mata uang atau tanda persen yang menempel pada angka, baris ganda, hingga nilai yang tampak janggal. Jika masalah tersebut dibiarkan, ukuran ringkasan seperti rata-rata, simpangan baku, atau korelasi dapat menjadi bias dan menyesatkan, sehingga kualitas data perlu diperiksa dan dibenahi sebelum analisis lanjutan dilakukan.

Pendekatan yang dipakai mengikuti semangat analisis data eksploratif (exploratory data analysis, EDA), yaitu memahami data lewat ringkasan numerik dan visualisasi sebelum menarik kesimpulan atau membangun model formal (Tukey, 1993; Velleman & Hoaglin, 2004). Tahapannya mencakup pembersihan dan penyeragaman format, penanganan nilai kosong dengan imputasi median, pengelompokan pengalaman kerja menjadi kategori ordinal, visualisasi sebaran data, transformasi numerik, serta pemeriksaan asumsi normalitas dan homogenitas varians. Analisis kemudian difokuskan pada gambaran pengalaman kerja freelancer dan keterkaitannya dengan tarif per jam, dengan tetap berhati-hati bahwa hubungan yang teramati bersifat asosiasi dan belum tentu menunjukkan hubungan sebab-akibat.

1.2 Rumusan Masalah

  1. Masalah kualitas data apa saja yang ditemukan pada Global Freelancers Dataset?
  2. Bagaimana tahapan preprocessing untuk menangani nilai kosong, format yang tidak seragam, dan tipe data yang tidak sesuai?
  3. Seperti apa gambaran pengalaman kerja freelancer setelah data dibersihkan, dilihat dari statistik deskriptif dan uji normalitas?
  4. Bagaimana keterkaitan antara pengalaman kerja dan tarif per jam?

1.3 Tujuan Analisis

  1. Menemukan masalah kualitas data pada Global Freelancers Dataset.
  2. Melakukan preprocessing untuk mengatasi nilai kosong, menyamakan format, dan membetulkan tipe data.
  3. Menggambarkan pengalaman kerja freelancer lewat statistik deskriptif dan pemeriksaan normalitas pada data yang sudah bersih.
  4. Menelaah hubungan antara pengalaman kerja dan tarif per jam.

2 Import dan Gambaran Dataset

File Excel dibaca otomatis dari folder yang sama dengan dokumen ini, jadi tidak perlu memilih file secara manual setiap kali dokumen dijalankan.

# Isi nama file jika sudah pasti, misalnya "global_freelancers.xlsx".
# Jika dikosongkan (""), R mencari file Excel di folder yang sama dengan file .Rmd ini.
nama_file <- "data ade global_freelancers_raw.xlsx"

if (nama_file != "" && file.exists(nama_file)) {
  file_excel <- nama_file
} else {
  kandidat <- list.files(pattern = "\\.(xlsx|xls)$", ignore.case = TRUE)
  kandidat <- kandidat[!grepl("^~\\$", kandidat)]
  if (length(kandidat) == 1) {
    file_excel <- kandidat
  } else if (interactive()) {
    file_excel <- file.choose()
  } else {
    stop("File Excel tidak ditemukan atau lebih dari satu di folder: ", getwd(),
         "\nFile yang terdeteksi: ",
         ifelse(length(kandidat) == 0, "(tidak ada)", paste(kandidat, collapse = ", ")),
         "\nIsi variabel nama_file dengan nama file yang benar.")
  }
}
cat("File yang dibaca:", file_excel, "\n")
## File yang dibaca: data ade global_freelancers_raw.xlsx
ADE <- readxl::read_excel(file_excel)
ADE <- as.data.frame(ADE)

knitr::kable(
  data.frame(
    Keterangan = c("Jumlah observasi", "Jumlah variabel"),
    Nilai = c(nrow(ADE), ncol(ADE))
  ),
  caption = "Ringkasan ukuran dataset"
)
Ringkasan ukuran dataset
Keterangan Nilai
Jumlah observasi 1000
Jumlah variabel 12
knitr::kable(
  data.frame(Nama_Variabel = names(ADE)),
  caption = "Daftar variabel dalam dataset"
)
Daftar variabel dalam dataset
Nama_Variabel
freelancer_ID
name
gender
age
country
language
primary_skill
years_of_experience
hourly_rate (USD)
rating
is_active
client_satisfaction
DT::datatable(
  head(ADE, 10),
  options = list(pageLength = 10, scrollX = TRUE),
  rownames = FALSE,
  caption = "Sepuluh observasi pertama"
)

✨ Interpretasi: Dataset berhasil dibaca dengan 1000 observasi dan 12 variabel. Sepuluh baris pertama memberi gambaran awal tentang isi tiap kolom, termasuk kemungkinan adanya format yang belum seragam, misalnya simbol mata uang pada tarif atau tanda persen pada tingkat kepuasan. Pemeriksaan ini menjadi dasar untuk tahap pembersihan berikutnya.

str(ADE)
## 'data.frame':    1000 obs. of  12 variables:
##  $ freelancer_ID      : chr  "FL250001" "FL250002" "FL250003" "FL250004" ...
##  $ name               : chr  "Ms. Nicole Kidd" "Vanessa Garcia" "Juan Nelson" "Amanda Spencer" ...
##  $ gender             : chr  "f" "FEMALE" "male" "F" ...
##  $ age                : chr  "52.0" "52.0" "53.0" "38.0" ...
##  $ country            : chr  "Italy" "Australia" "Germany" "Australia" ...
##  $ language           : chr  "Italian" "English" "German" "English" ...
##  $ primary_skill      : chr  "Blockchain Development" "Mobile Apps" "Graphic Design" "Web Development" ...
##  $ years_of_experience: chr  "11.0" "34.0" "31.0" "4.0" ...
##  $ hourly_rate (USD)  : chr  "100" "USD 100" "50" "$40" ...
##  $ rating             : chr  NA "3.3" "0.0" "1.5" ...
##  $ is_active          : chr  "0" "1" "N" "N" ...
##  $ client_satisfaction: num  NA 0.84 0.71 0.9 0.83 NA 0.94 0.94 0.76 NA ...
summary(ADE)
##  freelancer_ID          name              gender              age           
##  Length:1000        Length:1000        Length:1000        Length:1000       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##    country            language         primary_skill      years_of_experience
##  Length:1000        Length:1000        Length:1000        Length:1000        
##  Class :character   Class :character   Class :character   Class :character   
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character   
##                                                                              
##                                                                              
##                                                                              
##                                                                              
##  hourly_rate (USD)     rating           is_active         client_satisfaction
##  Length:1000        Length:1000        Length:1000        Min.   :  0.60     
##  Class :character   Class :character   Class :character   1st Qu.:  0.71     
##  Mode  :character   Mode  :character   Mode  :character   Median :  0.82     
##                                                           Mean   : 11.73     
##                                                           3rd Qu.:  0.94     
##                                                           Max.   :100.00     
##                                                           NA's   :176

✨ Interpretasi: Dari 12 variabel, terdapat 11 kolom yang terbaca sebagai teks (character) dan 1 kolom yang terbaca sebagai angka. Kolom yang seharusnya numerik tetapi terbaca sebagai teks, seperti tarif per jam atau tingkat kepuasan klien, menandakan adanya karakter non-angka yang perlu dibersihkan sebelum dapat dihitung statistiknya. Ringkasan summary() pada kolom numerik juga memberi petunjuk awal tentang nilai minimum, maksimum, dan jumlah NA.

3 Pemeriksaan Permasalahan Data

3.1 Missing Value

Missing value berarti ada observasi yang tidak memiliki nilai pada suatu variabel. Persentasenya dihitung dari total baris dalam dataset.

missing_awal <- data.frame(
  Variabel = names(ADE),
  Jumlah_NA = colSums(is.na(ADE)),
  Persentase_NA = round(colMeans(is.na(ADE)) * 100, 2)
)

knitr::kable(
  missing_awal,
  row.names = FALSE,
  caption = "Jumlah dan persentase missing value sebelum cleaning",
  digits = 2
)
Jumlah dan persentase missing value sebelum cleaning
Variabel Jumlah_NA Persentase_NA
freelancer_ID 0 0.0
name 0 0.0
gender 0 0.0
age 30 3.0
country 0 0.0
language 0 0.0
primary_skill 0 0.0
years_of_experience 51 5.1
hourly_rate (USD) 94 9.4
rating 101 10.1
is_active 89 8.9
client_satisfaction 176 17.6
cat("Total missing value:", sum(is.na(ADE)), "\n")
## Total missing value: 541

✨ Interpretasi: Secara keseluruhan terdapat 541 sel kosong pada dataset. Variabel dengan nilai kosong terbanyak adalah client_satisfaction yaitu 176 sel (17.60% dari seluruh baris). Nilai kosong ini perlu ditangani agar tidak mengurangi ketepatan ringkasan statistik.

3.2 Duplikasi Awal

jumlah_duplikat_awal <- sum(duplicated(ADE))
cat("Jumlah baris duplikat sebelum dihapus:", jumlah_duplikat_awal, "\n")
## Jumlah baris duplikat sebelum dihapus: 0
if (jumlah_duplikat_awal > 0) {
  knitr::kable(
    head(ADE[duplicated(ADE), , drop = FALSE], 10),
    caption = "Contoh baris duplikat"
  )
}

✨ Interpretasi: Tidak ditemukan baris yang sama persis pada data mentah. Perlu dicatat bahwa pemeriksaan ini dilakukan sebelum format dirapikan, sehingga duplikat yang tersembunyi karena perbedaan penulisan baru akan terdeteksi setelah tahap cleaning.

4 Data Cleaning

4.1 Membersihkan Gender

Penulisan kategori disamakan supaya variasi seperti f, female, m, dan male tidak dihitung sebagai kategori yang terpisah.

if ("gender" %in% names(ADE)) {
  gender_raw <- tolower(trimws(as.character(ADE$gender)))
  ADE$gender <- ifelse(
    gender_raw %in% c("f", "female"), "Female",
    ifelse(
      gender_raw %in% c("m", "male"), "Male",
      ifelse(is.na(ADE$gender) | gender_raw == "", NA_character_,
             trimws(as.character(ADE$gender)))
    )
  )
  print(table(ADE$gender, useNA = "ifany"))
}
## 
## Female   Male 
##    490    510

✨ Interpretasi: Setelah penulisan diseragamkan, kategori gender menjadi: Female (490), Male (510), dengan 0 observasi yang gendernya kosong. Dengan penyeragaman ini, variasi penulisan seperti huruf besar-kecil atau singkatan tidak lagi dihitung sebagai kategori berbeda, sehingga perbandingan antar kelompok gender menjadi valid.

4.2 Membersihkan Hourly Rate

Tanda dolar, tulisan USD, koma pemisah ribuan, dan spasi dibuang dulu, baru kolom dikonversi ke angka.

if ("hourly_rate (USD)" %in% names(ADE)) {
  ADE[["hourly_rate (USD)"]] <- bersihkan_numerik(
    ADE[["hourly_rate (USD)"]],
    mata_uang = TRUE
  )
  print(summary(ADE[["hourly_rate (USD)"]]))
  str(ADE[["hourly_rate (USD)"]])
}
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   20.00   30.00   40.00   52.46   75.00  100.00      94 
##  num [1:1000] 100 100 50 40 30 30 75 40 NA 50 ...

✨ Interpretasi: Kolom tarif per jam kini bertipe numerik. Median tarif sebesar 40.00 USD dengan rentang 20.00 sampai 100.00 USD, dan tersisa 94 nilai kosong. Jika nilai kosong bertambah dibandingkan sebelum konversi, berarti ada isian yang tidak dapat dibaca sebagai angka dan perlu ditinjau.

4.3 Membersihkan Client Satisfaction

if ("client_satisfaction" %in% names(ADE)) {
  ADE$client_satisfaction <- bersihkan_numerik(
    ADE$client_satisfaction,
    persen = TRUE
  )
  summary(ADE$client_satisfaction)
}
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##    0.60    0.71    0.82   11.73    0.94  100.00     176

✨ Interpretasi: Setelah tanda persen dibuang, tingkat kepuasan klien dapat dihitung sebagai angka. Nilai tengah (median) kepuasan adalah 0.82 dengan rentang 0.60 sampai 100.00. Karena skala kepuasan diasumsikan 0 sampai 100, nilai di luar rentang tersebut akan diperiksa pada tahap pemeriksaan data janggal.

4.4 Membersihkan Age, Years of Experience, dan Rating

if ("age" %in% names(ADE)) {
  ADE$age <- bersihkan_numerik(ADE$age, persen = TRUE)
}
if ("years_of_experience" %in% names(ADE)) {
  ADE$years_of_experience <- bersihkan_numerik(
    ADE$years_of_experience,
    persen = TRUE
  )
}
if ("rating" %in% names(ADE)) {
  ADE$rating <- bersihkan_numerik(ADE$rating, persen = TRUE)
}

str(ADE)
## 'data.frame':    1000 obs. of  12 variables:
##  $ freelancer_ID      : chr  "FL250001" "FL250002" "FL250003" "FL250004" ...
##  $ name               : chr  "Ms. Nicole Kidd" "Vanessa Garcia" "Juan Nelson" "Amanda Spencer" ...
##  $ gender             : chr  "Female" "Female" "Male" "Female" ...
##  $ age                : num  52 52 53 38 53 59 52 43 26 52 ...
##  $ country            : chr  "Italy" "Australia" "Germany" "Australia" ...
##  $ language           : chr  "Italian" "English" "German" "English" ...
##  $ primary_skill      : chr  "Blockchain Development" "Mobile Apps" "Graphic Design" "Web Development" ...
##  $ years_of_experience: num  11 34 31 4 27 14 10 14 4 22 ...
##  $ hourly_rate (USD)  : num  100 100 50 40 30 30 75 40 NA 50 ...
##  $ rating             : num  NA 3.3 0 1.5 4.8 2.4 3.1 4.6 4 NA ...
##  $ is_active          : chr  "0" "1" "N" "N" ...
##  $ client_satisfaction: num  NA 0.84 0.71 0.9 0.83 NA 0.94 0.94 0.76 NA ...
summary(ADE)
##  freelancer_ID          name              gender               age       
##  Length:1000        Length:1000        Length:1000        Min.   :20.00  
##  Class :character   Class :character   Class :character   1st Qu.:31.00  
##  Mode  :character   Mode  :character   Mode  :character   Median :41.00  
##                                                           Mean   :40.51  
##                                                           3rd Qu.:51.00  
##                                                           Max.   :60.00  
##                                                           NA's   :30     
##    country            language         primary_skill      years_of_experience
##  Length:1000        Length:1000        Length:1000        Min.   : 0.00      
##  Class :character   Class :character   Class :character   1st Qu.: 3.00      
##  Mode  :character   Mode  :character   Mode  :character   Median : 9.00      
##                                                           Mean   :11.34      
##                                                           3rd Qu.:17.00      
##                                                           Max.   :41.00      
##                                                           NA's   :51         
##  hourly_rate (USD)     rating       is_active         client_satisfaction
##  Min.   : 20.00    Min.   :0.000   Length:1000        Min.   :  0.60     
##  1st Qu.: 30.00    1st Qu.:1.400   Class :character   1st Qu.:  0.71     
##  Median : 40.00    Median :2.600   Mode  :character   Median :  0.82     
##  Mean   : 52.46    Mean   :2.513                      Mean   : 11.73     
##  3rd Qu.: 75.00    3rd Qu.:3.800                      3rd Qu.:  0.94     
##  Max.   :100.00    Max.   :5.000                      Max.   :100.00     
##  NA's   :94        NA's   :101                        NA's   :176

✨ Interpretasi: Usia, lama pengalaman, dan rating kini seluruhnya bertipe numerik. Rata-rata usia freelancer adalah 40.5 tahun, rata-rata pengalaman 11.3 tahun, dan rata-rata rating 2.51. Dengan tipe data yang benar, seluruh operasi statistik pada tahap berikutnya dapat dilakukan tanpa galat konversi.

4.5 Membersihkan Status Is Active

Status yang bisa dikenali dipetakan menjadi Active atau Inactive. Isian yang kosong atau tidak dikenali ditandai Unknown agar tidak dianggap sebagai status yang pasti.

if ("is_active" %in% names(ADE)) {
  active_raw <- tolower(trimws(as.character(ADE$is_active)))
  ADE$is_active <- ifelse(
    active_raw %in% c("1", "y", "yes", "true", "active"), "Active",
    ifelse(
      active_raw %in% c("0", "n", "no", "false", "inactive"), "Inactive",
      "Unknown"
    )
  )
  print(table(ADE$is_active, useNA = "ifany"))
}
## 
##   Active Inactive  Unknown 
##      446      465       89

✨ Interpretasi: Status aktif freelancer kini hanya memiliki kategori yang seragam, yaitu Active (446), Inactive (465), Unknown (89). Kategori Unknown sengaja dipertahankan sebagai penanda bahwa status tersebut tidak dapat dipastikan, sehingga tidak ada asumsi yang dipaksakan pada data.

4.6 Menghapus Duplikasi

Baris ganda dihapus setelah format dirapikan, sebab dua baris yang sebenarnya sama bisa terlihat berbeda hanya karena cara penulisannya.

duplikat_sebelum <- sum(duplicated(ADE))
ADE <- ADE[!duplicated(ADE), , drop = FALSE]
duplikat_sesudah <- sum(duplicated(ADE))

knitr::kable(
  data.frame(
    Tahap = c("Sebelum penghapusan", "Sesudah penghapusan"),
    Jumlah_Duplikat = c(duplikat_sebelum, duplikat_sesudah),
    Jumlah_Baris = c(NA_integer_, nrow(ADE))
  ),
  caption = "Pemeriksaan duplikasi"
)
Pemeriksaan duplikasi
Tahap Jumlah_Duplikat Jumlah_Baris
Sebelum penghapusan 0 NA
Sesudah penghapusan 0 1000

✨ Interpretasi: Setelah format dirapikan, terdapat 0 baris duplikat yang kemudian dihapus, sehingga duplikat yang tersisa menjadi 0. Dataset kini berisi 1000 baris unik. Jumlah ini sama dengan pemeriksaan awal, sehingga penyeragaman format tidak memunculkan duplikat baru.

4.7 Pemeriksaan Nilai Tidak Masuk Akal (Data Noisy)

Tahap ini hanya menandai nilai yang mencurigakan, belum menghapusnya. Keputusan menghapus sebaiknya menunggu pengecekan ke sumber data atau definisi variabelnya.

cek_rentang <- function(data, variabel, batas_bawah = -Inf, batas_atas = Inf) {
  if (!variabel %in% names(data)) return(NULL)
  x <- data[[variabel]]
  if (!is.numeric(x)) return(NULL)

  data.frame(
    Variabel = variabel,
    Jumlah_NA = sum(is.na(x)),
    Di_Bawah_Batas = sum(x < batas_bawah, na.rm = TRUE),
    Di_Atas_Batas = sum(x > batas_atas, na.rm = TRUE),
    Minimum = if (all(is.na(x))) NA_real_ else min(x, na.rm = TRUE),
    Maksimum = if (all(is.na(x))) NA_real_ else max(x, na.rm = TRUE)
  )
}

hasil_rentang <- do.call(rbind, Filter(Negate(is.null), list(
  cek_rentang(ADE, "age", 0, 100),
  cek_rentang(ADE, "years_of_experience", 0, Inf),
  cek_rentang(ADE, "rating", 0, 5),
  cek_rentang(ADE, "hourly_rate (USD)", 0, Inf),
  cek_rentang(ADE, "client_satisfaction", 0, 100)
)))

if (!is.null(hasil_rentang) && nrow(hasil_rentang) > 0) {
  knitr::kable(hasil_rentang, digits = 2, row.names = FALSE,
               caption = "Ringkasan pemeriksaan nilai di luar rentang")
} else {
  cat("Tidak ada variabel numerik yang sesuai untuk pemeriksaan rentang.\n")
}
Ringkasan pemeriksaan nilai di luar rentang
Variabel Jumlah_NA Di_Bawah_Batas Di_Atas_Batas Minimum Maksimum
age 30 0 0 20.0 60
years_of_experience 51 0 0 0.0 41
rating 101 0 0 0.0 5
hourly_rate (USD) 94 0 0 20.0 100
client_satisfaction 176 0 0 0.6 100

✨ Interpretasi: Dari lima variabel yang diperiksa, total terdapat 0 nilai yang berada di luar batas kewajaran. Seluruh nilai berada dalam rentang yang masuk akal, sehingga tidak ada data yang perlu dikoreksi.

if (all(c("years_of_experience", "age") %in% names(ADE))) {
  cat("Pengalaman kerja negatif:",
      sum(ADE$years_of_experience < 0, na.rm = TRUE), "\n")
  cat("Pengalaman lebih besar dari usia:",
      sum(ADE$years_of_experience > ADE$age, na.rm = TRUE), "\n")
}
## Pengalaman kerja negatif: 0 
## Pengalaman lebih besar dari usia: 0

✨ Interpretasi: Terdapat 0 freelancer dengan pengalaman negatif dan 0 freelancer yang pengalamannya lebih besar daripada usianya. Tidak ada inkonsistensi logis antara usia dan pengalaman kerja.

5 Membuat Data Clean dan Mengatasi Missing Value

Nilai kosong pada variabel numerik terpilih diisi dengan median. Kolom aslinya tidak diubah, hasil imputasi disimpan di kolom baru berakhiran _clean sehingga sebelum dan sesudahnya bisa dibandingkan.

data_clean <- ADE

if ("years_of_experience" %in% names(data_clean)) {
  data_clean$years_of_experience_clean <-
    imputasi_median(data_clean$years_of_experience)
}
if ("hourly_rate (USD)" %in% names(data_clean)) {
  data_clean$hourly_rate_clean <-
    imputasi_median(data_clean[["hourly_rate (USD)"]])
}
if ("rating" %in% names(data_clean)) {
  data_clean$rating_clean <- imputasi_median(data_clean$rating)
}
if ("client_satisfaction" %in% names(data_clean)) {
  data_clean$client_satisfaction_clean <-
    imputasi_median(data_clean$client_satisfaction)
}
if ("age" %in% names(data_clean)) {
  data_clean$age_clean <- imputasi_median(data_clean$age)
}

missing_sesudah <- data.frame(
  Variabel = names(data_clean),
  Jumlah_NA = colSums(is.na(data_clean)),
  Persentase_NA = round(colMeans(is.na(data_clean)) * 100, 2)
)

knitr::kable(
  missing_sesudah,
  row.names = FALSE,
  caption = "Missing value setelah membuat kolom imputasi",
  digits = 2
)
Missing value setelah membuat kolom imputasi
Variabel Jumlah_NA Persentase_NA
freelancer_ID 0 0.0
name 0 0.0
gender 0 0.0
age 30 3.0
country 0 0.0
language 0 0.0
primary_skill 0 0.0
years_of_experience 51 5.1
hourly_rate (USD) 94 9.4
rating 101 10.1
is_active 0 0.0
client_satisfaction 176 17.6
years_of_experience_clean 0 0.0
hourly_rate_clean 0 0.0
rating_clean 0 0.0
client_satisfaction_clean 0 0.0
age_clean 0 0.0

Catatan interpretasi: apabila ada kolom yang semua isinya kosong, median tidak bisa dihitung sehingga kolom itu tetap berisi NA dan harus ditinjau secara khusus.

✨ Interpretasi: Imputasi median mengisi 51 nilai kosong pada pengalaman kerja, 94 pada tarif per jam, 101 pada rating, 176 pada kepuasan klien, dan 30 pada usia. Setelah imputasi, kolom berakhiran _clean memiliki 0 nilai kosong. Median dipilih karena tidak terpengaruh nilai ekstrem, tetapi perlu diingat bahwa imputasi cenderung mengecilkan variasi data karena banyak nilai diisi dengan angka yang sama.

6 Validasi Setelah Cleaning

validasi <- data.frame(
  Pemeriksaan = c(
    "Duplikasi tersisa",
    "Age di bawah 0",
    "Age di atas 100",
    "Pengalaman kerja negatif",
    "Rating di bawah 0",
    "Rating di atas 5",
    "Hourly rate negatif",
    "Satisfaction di bawah 0",
    "Satisfaction di atas 100"
  ),
  Jumlah = c(
    sum(duplicated(data_clean)),
    if ("age" %in% names(data_clean)) sum(data_clean$age < 0, na.rm = TRUE) else NA,
    if ("age" %in% names(data_clean)) sum(data_clean$age > 100, na.rm = TRUE) else NA,
    if ("years_of_experience_clean" %in% names(data_clean)) sum(data_clean$years_of_experience_clean < 0, na.rm = TRUE) else NA,
    if ("rating_clean" %in% names(data_clean)) sum(data_clean$rating_clean < 0, na.rm = TRUE) else NA,
    if ("rating_clean" %in% names(data_clean)) sum(data_clean$rating_clean > 5, na.rm = TRUE) else NA,
    if ("hourly_rate_clean" %in% names(data_clean)) sum(data_clean$hourly_rate_clean < 0, na.rm = TRUE) else NA,
    if ("client_satisfaction_clean" %in% names(data_clean)) sum(data_clean$client_satisfaction_clean < 0, na.rm = TRUE) else NA,
    if ("client_satisfaction_clean" %in% names(data_clean)) sum(data_clean$client_satisfaction_clean > 100, na.rm = TRUE) else NA
  )
)

knitr::kable(validasi, caption = "Hasil validasi data")
Hasil validasi data
Pemeriksaan Jumlah
Duplikasi tersisa 0
Age di bawah 0 0
Age di atas 100 0
Pengalaman kerja negatif 0
Rating di bawah 0 0
Rating di atas 5 0
Hourly rate negatif 0
Satisfaction di bawah 0 0
Satisfaction di atas 100 0

✨ Interpretasi: Dari sembilan pemeriksaan validasi, total temuan masalah adalah 0. Seluruh nilai bernilai nol, artinya data sudah bebas dari duplikasi dan nilai di luar rentang yang diperiksa sehingga layak dilanjutkan ke tahap analisis.

7 Encoding Pengalaman Kerja

Pengalaman kerja dikelompokkan menjadi empat kategori. Kodenya bersifat ordinal: angka kode hanya menunjukkan urutan tingkat pengalaman, bukan selisih yang besarnya sama antar tingkat.

Rentang pengalaman Kategori Kode
0 sampai 10 tahun pemula 1
Lebih dari 10 sampai 20 tahun menengah 2
Lebih dari 20 sampai 30 tahun jago 3
Lebih dari 30 tahun master 4
if ("years_of_experience_clean" %in% names(data_clean)) {
  exp <- data_clean$years_of_experience_clean

  data_clean$kategori_pengalaman <- ifelse(
    is.na(exp), NA_character_,
    ifelse(exp <= 10, "pemula",
           ifelse(exp <= 20, "menengah",
                  ifelse(exp <= 30, "jago", "master")))
  )

  data_clean$kategori_encode <- match(
    data_clean$kategori_pengalaman,
    c("pemula", "menengah", "jago", "master")
  )

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

  tabel_encoding <- as.data.frame(
    table(
      Kategori = data_clean$kategori_pengalaman,
      Kode = data_clean$kategori_encode,
      useNA = "no"
    )
  )
  tabel_encoding <- tabel_encoding[tabel_encoding$Freq > 0, ]

  knitr::kable(
    tabel_encoding,
    caption = "Frekuensi kategori dan kode pengalaman kerja"
  )
  print(table(data_clean$kategori_pengalaman, useNA = "ifany"))
}
## 
##   pemula menengah     jago   master 
##      571      252      122       55

✨ Interpretasi: Kategori dengan anggota terbanyak adalah pemula, yaitu 571 freelancer atau 57.1% dari seluruh data. Sebaran per kategori: pemula (571), menengah (252), jago (122), master (55). Karena kodenya ordinal, angka 1 sampai 4 hanya menunjukkan urutan tingkat pengalaman dan tidak boleh ditafsirkan sebagai jarak yang sama antar kategori.

8 Visualisasi Data

8.1 Histogram Pengalaman Kerja

if ("years_of_experience_clean" %in% names(data_clean)) {
  ggplot(data_clean, aes(x = years_of_experience_clean)) +
    geom_histogram(
      bins = 20,
      fill = warna_biru,
      color = "white",
      linewidth = 0.5,
      alpha = 0.95
    ) +
    labs(
      title = "Distribusi Pengalaman Kerja Freelancer",
      subtitle = "Frekuensi jumlah tahun pengalaman",
      x = "Pengalaman kerja (tahun)",
      y = "Frekuensi"
    ) +
    tema_laporan
}

✨ Interpretasi: Pengalaman kerja freelancer memiliki rata-rata 11.2 tahun dan median 9.0 tahun, dengan simpangan baku 9.4 tahun. Nilai kemencengan (skewness) sebesar 0.95 menunjukkan bentuk sebaran yang menjulur ke kanan (miring positif). Histogram memperlihatkan di rentang tahun mana freelancer paling banyak terkonsentrasi.

8.2 Jumlah Freelancer per Kategori Pengalaman

if ("kategori_pengalaman" %in% names(data_clean)) {
  ggplot(data_clean, aes(x = kategori_pengalaman, fill = kategori_pengalaman)) +
    geom_bar(color = "white", linewidth = 0.6, show.legend = FALSE) +
    scale_fill_manual(values = c(
      "pemula" = warna_hijaumuda,
      "menengah" = warna_hijau,
      "jago" = warna_biru,
      "master" = warna_lilac
    ), na.value = "#D9D9D9") +
    labs(
      title = "Komposisi Freelancer Menurut Pengalaman",
      x = "Kategori pengalaman",
      y = "Jumlah freelancer"
    ) +
    tema_laporan
}

✨ Interpretasi: Grafik batang menunjukkan bahwa kategori pemula adalah yang paling banyak, sedangkan kategori master adalah yang paling sedikit. Selisih antara kategori terbesar dan terkecil adalah 516 freelancer. Komposisi ini menggambarkan apakah pasar freelance dalam data didominasi oleh pekerja berpengalaman rendah, menengah, atau tinggi.

8.3 Distribusi Hourly Rate

if ("hourly_rate_clean" %in% names(data_clean)) {
  ggplot(data_clean, aes(x = hourly_rate_clean)) +
    geom_histogram(
      bins = 25,
      fill = warna_hijau,
      color = "white",
      linewidth = 0.4,
      alpha = 0.95
    ) +
    labs(
      title = "Distribusi Tarif per Jam",
      subtitle = "Hourly rate dalam USD",
      x = "Hourly rate (USD)",
      y = "Frekuensi"
    ) +
    tema_laporan
}

✨ Interpretasi: Tarif per jam memiliki rata-rata 51.29 USD dan median 40.00 USD, dengan rentang 20.00 sampai 100.00 USD. Kemencengan sebesar 0.76 menunjukkan sebaran yang menjulur ke kanan (miring positif). Rata-rata yang lebih besar daripada median menandakan adanya sebagian freelancer dengan tarif tinggi yang menarik rata-rata ke atas.

8.4 QQ Plot

QQ plot membandingkan kuantil data dengan kuantil distribusi normal teoretis. Jika titik-titiknya berada dekat garis, data cenderung mendekati normal. Meski begitu, kesimpulan sebaiknya tidak hanya bersandar pada grafik ini.

if ("hourly_rate_clean" %in% names(data_clean)) {
  x <- data_clean$hourly_rate_clean
  x <- x[is.finite(x)]

  if (length(x) >= 2) {
    qqnorm(
      x,
      main = "QQ Plot Hourly Rate",
      pch = 19,
      col = warna_biru,
      cex = 0.8,
      family = "sans"
    )
    qqline(x, col = warna_hijau, lwd = 2.5)
  } else {
    cat("QQ plot hourly rate membutuhkan setidaknya dua observasi valid.")
  }
}

✨ Interpretasi: Pada QQ plot tarif per jam, kemencengan data sebesar 0.76 (menjulur ke kanan (miring positif)). Titik-titik yang menyimpang dari garis, terutama di kedua ujung, menandakan distribusi tarif tidak sepenuhnya normal. Kesimpulan akhir tetap perlu dikonfirmasi dengan uji Shapiro-Wilk.

if ("years_of_experience_clean" %in% names(data_clean)) {
  x <- data_clean$years_of_experience_clean
  x <- x[is.finite(x)]

  if (length(x) >= 2) {
    qqnorm(
      x,
      main = "QQ Plot Pengalaman Kerja",
      pch = 19,
      col = warna_lilac,
      cex = 0.8,
      family = "sans"
    )
    qqline(x, col = warna_hijau, lwd = 2.5)
  } else {
    cat("QQ plot pengalaman kerja membutuhkan setidaknya dua observasi valid.")
  }
}

✨ Interpretasi: Pada QQ plot pengalaman kerja, kemencengan data sebesar 0.95 (menjulur ke kanan (miring positif)). Penyimpangan titik dari garis menandakan distribusi pengalaman kerja tidak sepenuhnya normal. Perlu diingat bahwa pengalaman kerja berupa angka tahun sehingga bentuk titik dapat tampak bertingkat.

8.5 Hubungan Pengalaman Kerja dan Hourly Rate

if (all(c("years_of_experience_clean", "hourly_rate_clean") %in% names(data_clean))) {
  ggplot(
    data_clean,
    aes(x = years_of_experience_clean, y = hourly_rate_clean)
  ) +
    geom_point(shape = 21, fill = warna_biru, color = "white", stroke = 0.8, alpha = 0.9, size = 3) +
    geom_smooth(
      method = "lm",
      formula = y ~ x,
      se = TRUE,
      color = warna_hijau,
      fill = warna_hijaumuda,
      linewidth = 1.1
    ) +
    labs(
      title = "Pengalaman Kerja dan Tarif per Jam",
      subtitle = "Garis menunjukkan kecenderungan linear, bukan bukti sebab-akibat",
      x = "Pengalaman kerja (tahun)",
      y = "Hourly rate (USD)"
    ) +
    tema_laporan
}

Untuk mengukur kekuatan hubungan secara numerik, dihitung koefisien korelasi Pearson dan koefisien kemiringan garis regresi sederhana.

uji_kor <- cor.test(
  data_clean$years_of_experience_clean,
  data_clean$hourly_rate_clean
)
print(uji_kor)
## 
##  Pearson's product-moment correlation
## 
## data:  data_clean$years_of_experience_clean and data_clean$hourly_rate_clean
## t = 2.4916, df = 998, p-value = 0.01288
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  0.01671316 0.13993579
## sample estimates:
##        cor 
## 0.07862478
model_sederhana <- lm(hourly_rate_clean ~ years_of_experience_clean,
                      data = data_clean)
print(round(coef(model_sederhana), 4))
##               (Intercept) years_of_experience_clean 
##                   48.8369                    0.2186

✨ Interpretasi: Koefisien korelasi Pearson antara pengalaman kerja dan tarif per jam adalah 0.079 dengan nilai p 0.0129. Hubungan ini tergolong sangat lemah dan berarah positif, serta signifikan secara statistik pada taraf 5%. Garis regresi menunjukkan perubahan tarif sekitar 0.219 USD per jam untuk setiap tambahan satu tahun pengalaman, dengan R-kuadrat 0.006 (artinya 0.6% variasi tarif terjelaskan oleh pengalaman). Hasil ini menunjukkan asosiasi, bukan hubungan sebab-akibat.

9 Transformasi Numerik

Transformasi berguna untuk menyamakan skala antarvariabel sehingga lebih mudah dibandingkan. Jenis transformasi dipilih sesuai kebutuhan analisis, karena tidak ada satu transformasi yang selalu terbaik untuk semua data.

9.1 Rentang Awal

variabel_transformasi <- intersect(
  c("years_of_experience_clean", "hourly_rate_clean"),
  names(data_clean)
)

ringkasan_rentang <- lapply(variabel_transformasi, function(nama) {
  x <- data_clean[[nama]]
  data.frame(
    Variabel = nama,
    Minimum = if (all(is.na(x))) NA_real_ else min(x, na.rm = TRUE),
    Maksimum = if (all(is.na(x))) NA_real_ else max(x, na.rm = TRUE),
    Mean = if (all(is.na(x))) NA_real_ else mean(x, na.rm = TRUE),
    SD = if (sum(!is.na(x)) < 2) NA_real_ else sd(x, na.rm = TRUE)
  )
})

if (length(ringkasan_rentang) > 0) {
  knitr::kable(do.call(rbind, ringkasan_rentang), digits = 3,
               caption = "Rentang dan ringkasan variabel sebelum transformasi")
}
Rentang dan ringkasan variabel sebelum transformasi
Variabel Minimum Maksimum Mean SD
years_of_experience_clean 0 41 11.221 9.444
hourly_rate_clean 20 100 51.290 26.260

✨ Interpretasi: Pengalaman kerja berada pada skala 0.0 sampai 41.0 tahun, sedangkan tarif per jam berada pada skala 20.0 sampai 100.0 USD. Perbedaan skala yang besar ini membuat kedua variabel sulit dibandingkan langsung, sehingga transformasi seperti Min-Max dan Z-Score diperlukan.

9.2 Normalisasi Min-Max dan Standardisasi Z-Score

Min-Max memetakan nilai ke skala 0 sampai 1 (selama nilainya bervariasi). Z-Score menggeser data sehingga rata-ratanya 0 dan simpangan bakunya 1 (selama simpangan baku tidak nol).

data_final <- data_clean

if ("years_of_experience_clean" %in% names(data_final)) {
  data_final$exp_minmax <- min_max(data_final$years_of_experience_clean)
  data_final$exp_z <- z_score(data_final$years_of_experience_clean)
}
if ("hourly_rate_clean" %in% names(data_final)) {
  data_final$rate_minmax <- min_max(data_final$hourly_rate_clean)
  data_final$rate_z <- z_score(data_final$hourly_rate_clean)
}

ringkasan_z <- data.frame(
  Variabel = character(),
  Mean_ZScore = numeric(),
  SD_ZScore = numeric()
)

if ("exp_z" %in% names(data_final)) {
  ringkasan_z <- rbind(
    ringkasan_z,
    data.frame(
      Variabel = "Pengalaman kerja",
      Mean_ZScore = mean(data_final$exp_z, na.rm = TRUE),
      SD_ZScore = sd(data_final$exp_z, na.rm = TRUE)
    )
  )
}
if ("rate_z" %in% names(data_final)) {
  ringkasan_z <- rbind(
    ringkasan_z,
    data.frame(
      Variabel = "Hourly rate",
      Mean_ZScore = mean(data_final$rate_z, na.rm = TRUE),
      SD_ZScore = sd(data_final$rate_z, na.rm = TRUE)
    )
  )
}

knitr::kable(ringkasan_z, digits = 4,
             caption = "Verifikasi hasil standardisasi Z-Score")
Verifikasi hasil standardisasi Z-Score
Variabel Mean_ZScore SD_ZScore
Pengalaman kerja 0 1
Hourly rate 0 1

✨ Interpretasi: Hasil verifikasi menunjukkan rata-rata Z-Score pengalaman kerja sebesar -0.0000 dengan simpangan baku 1.0000, dan untuk tarif per jam rata-ratanya 0.0000 dengan simpangan baku 1.0000. Nilai tersebut sesuai harapan (rata-rata 0 dan simpangan baku 1), sehingga standardisasi berjalan benar. Nilai Min-Max pengalaman kerja berada pada rentang 0.00 sampai 1.00.

9.3 Transformasi Inverse Square

Rumus yang dipakai adalah \(1/(x+1)^2\). Angka 1 ditambahkan agar tidak terjadi pembagian dengan nol ketika pengalaman bernilai nol. Karena transformasi ini mengubah arti nilai (nilai besar menjadi kecil dan sebaliknya), kolom aslinya tetap disimpan.

if ("years_of_experience_clean" %in% names(data_final)) {
  data_final$exp_inv_sq <-
    1 / ((data_final$years_of_experience_clean + 1)^2)

  p1 <- ggplot(data_final, aes(x = years_of_experience_clean)) +
    geom_histogram(bins = 20, fill = warna_lilac,
                   color = "white", linewidth = 0.4) +
    labs(
      title = "Pengalaman Kerja Sebelum Transformasi",
      x = "Pengalaman kerja (tahun)",
      y = "Frekuensi"
    ) +
    tema_laporan

  p2 <- ggplot(data_final, aes(x = exp_inv_sq)) +
    geom_histogram(bins = 20, fill = warna_pink,
                   color = "white", linewidth = 0.4) +
    labs(
      title = "Distribusi Setelah Transformasi Inverse Square",
      x = "Nilai inverse square",
      y = "Frekuensi"
    ) +
    tema_laporan

  print(p1)
  print(p2)
}

✨ Interpretasi: Setelah transformasi inverse square, nilai berada pada rentang 0.0006 sampai 1.0000. Kemencengan data berubah dari 0.95 (sebelum) menjadi 3.01 (sesudah), sehingga bentuk sebaran sesudah transformasi menjulur ke kanan (miring positif). Karena urutan nilai terbalik (pengalaman besar menghasilkan nilai kecil), hasil transformasi ini harus ditafsirkan hati-hati dan tidak dipakai menggantikan variabel asli.

9.4 Evaluasi Perbandingan Transformasi

if ("years_of_experience_clean" %in% names(data_final)) {
  evaluasi_transformasi <- data.frame(
    Statistik = c("Mean asli", "SD asli", "Minimum Min-Max",
                  "Maksimum Min-Max", "Mean Z-Score", "SD Z-Score"),
    Nilai = c(
      mean(data_final$years_of_experience_clean, na.rm = TRUE),
      sd(data_final$years_of_experience_clean, na.rm = TRUE),
      if (all(is.na(data_final$exp_minmax))) NA_real_
      else min(data_final$exp_minmax, na.rm = TRUE),
      if (all(is.na(data_final$exp_minmax))) NA_real_
      else max(data_final$exp_minmax, na.rm = TRUE),
      mean(data_final$exp_z, na.rm = TRUE),
      sd(data_final$exp_z, na.rm = TRUE)
    )
  )
  knitr::kable(evaluasi_transformasi, digits = 4,
               caption = "Evaluasi transformasi pengalaman kerja")
}
Evaluasi transformasi pengalaman kerja
Statistik Nilai
Mean asli 11.2210
SD asli 9.4443
Minimum Min-Max 0.0000
Maksimum Min-Max 1.0000
Mean Z-Score 0.0000
SD Z-Score 1.0000

✨ Interpretasi: Data asli berskala rata-rata 11.22 dan simpangan baku 9.44. Min-Max menyempitkan skala menjadi 0 sampai 1 tanpa mengubah bentuk sebaran, sedangkan Z-Score memusatkan data di sekitar 0 dengan simpangan baku 1. Keduanya hanya mengubah skala, bukan bentuk distribusi, sehingga cocok untuk membandingkan variabel dengan satuan berbeda. Sementara itu, inverse square benar-benar mengubah bentuk distribusi dan dipakai bila tujuannya mengurangi kemencengan.

10 Uji Asumsi Normalitas

Uji Shapiro-Wilk menguji hipotesis nol bahwa data berasal dari populasi berdistribusi normal. R mensyaratkan jumlah observasi antara 3 sampai 5000 dan data tidak boleh seluruhnya bernilai sama.

uji_shapiro_aman <- function(x, nama) {
  x <- x[is.finite(x)]
  if (length(x) >= 3 && length(x) <= 5000 && length(unique(x)) > 1) {
    hasil <- shapiro.test(x)
    cat("\nVariabel:", nama, "\n")
    print(hasil)
    invisible(hasil)
  } else {
    cat("\nUji Shapiro-Wilk dilewati untuk", nama,
        ": dibutuhkan 3-5000 observasi valid dengan variasi nilai.\n")
    invisible(NULL)
  }
}

sw_rate <- NULL
sw_exp <- NULL

if ("hourly_rate_clean" %in% names(data_final)) {
  sw_rate <- uji_shapiro_aman(data_final$hourly_rate_clean, "Hourly rate")
}
## 
## Variabel: Hourly rate 
## 
##  Shapiro-Wilk normality test
## 
## data:  x
## W = 0.85064, p-value < 2.2e-16
if ("years_of_experience_clean" %in% names(data_final)) {
  sw_exp <- uji_shapiro_aman(data_final$years_of_experience_clean, "Years of experience")
}
## 
## Variabel: Years of experience 
## 
##  Shapiro-Wilk normality test
## 
## data:  x
## W = 0.9101, p-value < 2.2e-16

✨ Interpretasi: Uji Shapiro-Wilk pada tarif per jam menghasilkan statistik W = 0.8506 dengan nilai p < 0.001, sehingga hipotesis nol ditolak dan tarif per jam dinyatakan tidak berdistribusi normal pada taraf 5%. Untuk pengalaman kerja, W = 0.9101 dengan nilai p < 0.001, sehingga pengalaman kerja dinyatakan tidak berdistribusi normal. Perlu diingat bahwa pada sampel besar, uji ini sangat sensitif terhadap penyimpangan kecil, sehingga hasilnya sebaiknya dibaca bersama QQ plot dan histogram.

11 Uji Homogenitas Varians (Bartlett)

Uji Bartlett dipakai untuk melihat apakah varians antar kelompok sama. Di sini hourly rate dibandingkan antara kelompok gender. Uji ini mudah terpengaruh bila data tidak normal, sehingga hasilnya perlu dibaca bersama pemeriksaan distribusi.

bt <- NULL

if (all(c("hourly_rate_clean", "gender") %in% names(data_final))) {
  data_bartlett <- data_final[
    !is.na(data_final$hourly_rate_clean) &
      !is.na(data_final$gender) &
      data_final$gender != "",
    , drop = FALSE
  ]

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

  if (nlevels(data_bartlett$gender) >= 2 &&
      all(table(data_bartlett$gender) >= 2)) {
    bt <- bartlett.test(hourly_rate_clean ~ gender, data = data_bartlett)
    print(bt)
  } else {
    cat("Uji Bartlett dilewati: diperlukan setidaknya dua kelompok, masing-masing minimal dua observasi.\n")
  }
}
## 
##  Bartlett test of homogeneity of variances
## 
## data:  hourly_rate_clean by gender
## Bartlett's K-squared = 1.4921, df = 1, p-value = 0.2219

✨ Interpretasi: Uji Bartlett menghasilkan statistik K-kuadrat = 1.4921 dengan nilai p 0.2219. Hipotesis nol tidak ditolak, artinya varians tarif per jam relatif sama antar kelompok gender (homogen). Karena uji Bartlett peka terhadap ketidaknormalan data, kesimpulan ini sebaiknya dipertimbangkan bersama hasil uji normalitas sebelumnya.

12 Dataset Final dan Integrasi Variabel

Pada bagian ini dibentuk satu dataset analisis dari data yang sudah bersih. Sumber datanya hanya satu file, sehingga prosesnya sebatas menyatukan dan memilih kolom, bukan menggabungkan dua dataset yang berbeda.

# Isi kolom analisis utama dari hasil imputasi jika tersedia
if ("years_of_experience_clean" %in% names(data_final)) {
  data_final$years_of_experience <- data_final$years_of_experience_clean
}
if ("hourly_rate_clean" %in% names(data_final)) {
  data_final[["hourly_rate (USD)"]] <- data_final$hourly_rate_clean
}
if ("rating_clean" %in% names(data_final)) {
  data_final$rating <- data_final$rating_clean
}
if ("client_satisfaction_clean" %in% names(data_final)) {
  data_final$client_satisfaction <- data_final$client_satisfaction_clean
}
if ("age_clean" %in% names(data_final)) {
  data_final$age <- data_final$age_clean
}

kolom_analisis <- c(
  "age", "gender", "country", "years_of_experience",
  "kategori_pengalaman", "kategori_encode",
  "hourly_rate (USD)", "client_satisfaction", "rating", "is_active",
  "exp_minmax", "rate_minmax", "exp_z", "rate_z", "exp_inv_sq"
)

kolom_tersedia <- intersect(kolom_analisis, names(data_final))
data_analisis <- data_final[, kolom_tersedia, drop = FALSE]

knitr::kable(
  data.frame(
    Keterangan = c("Jumlah baris", "Jumlah kolom", "Jumlah duplikat"),
    Nilai = c(nrow(data_analisis), ncol(data_analisis),
              sum(duplicated(data_analisis)))
  ),
  caption = "Ringkasan dataset analisis akhir"
)
Ringkasan dataset analisis akhir
Keterangan Nilai
Jumlah baris 1000
Jumlah kolom 15
Jumlah duplikat 0
DT::datatable(
  head(data_analisis, 100),
  options = list(pageLength = 10, scrollX = TRUE),
  rownames = FALSE,
  caption = "Pratinjau maksimal 100 baris pertama"
)

✨ Interpretasi: Dataset analisis akhir terdiri dari 1000 baris dan 15 kolom, dengan 0 baris duplikat. Kolom analisis utama kini berisi nilai hasil imputasi, ditambah kolom kategori pengalaman, kode ordinal, serta hasil transformasi Min-Max, Z-Score, dan inverse square. Dataset ini siap dipakai untuk analisis lanjutan atau pemodelan.

13 Validasi Akhir

validasi_akhir <- data.frame(
  Variabel = names(data_analisis),
  Jumlah_NA = colSums(is.na(data_analisis)),
  Persentase_NA = round(colMeans(is.na(data_analisis)) * 100, 2)
)

knitr::kable(
  validasi_akhir,
  row.names = FALSE,
  digits = 2,
  caption = "Missing value pada dataset analisis akhir"
)
Missing value pada dataset analisis akhir
Variabel Jumlah_NA Persentase_NA
age 0 0
gender 0 0
country 0 0
years_of_experience 0 0
kategori_pengalaman 0 0
kategori_encode 0 0
hourly_rate (USD) 0 0
client_satisfaction 0 0
rating 0 0
is_active 0 0
exp_minmax 0 0
rate_minmax 0 0
exp_z 0 0
rate_z 0 0
exp_inv_sq 0 0
cat("Jumlah baris:", nrow(data_analisis), "\n")
## Jumlah baris: 1000
cat("Jumlah kolom:", ncol(data_analisis), "\n")
## Jumlah kolom: 15
cat("Jumlah duplikat:", sum(duplicated(data_analisis)), "\n")
## Jumlah duplikat: 0
if ("is_active" %in% names(data_analisis)) {
  print(table(data_analisis$is_active, useNA = "ifany"))
}
## 
##   Active Inactive  Unknown 
##      446      465       89
if ("kategori_pengalaman" %in% names(data_analisis)) {
  print(table(data_analisis$kategori_pengalaman, useNA = "ifany"))
}
## 
##   pemula menengah     jago   master 
##      571      252      122       55

✨ Interpretasi: Pada dataset akhir tersisa 0 sel kosong. Tidak ada nilai kosong yang tersisa, sehingga dataset lengkap untuk seluruh variabel analisis. Jumlah duplikat 0 menegaskan bahwa data final telah bersih dari baris ganda.

14 Ekspor Dataset

File CSV akan tersimpan di working directory R. Jalankan getwd() untuk mengetahui folder tempat file disimpan.

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

cat("Dataset analisis berhasil disimpan sebagai data_analisis_final.csv\n")
## Dataset analisis berhasil disimpan sebagai data_analisis_final.csv
cat("Lokasi penyimpanan:", getwd(), "\n")
## Lokasi penyimpanan: C:/Users/ACER/Downloads

✨ Interpretasi: Dataset akhir dengan 1000 baris dan 15 kolom telah disimpan sebagai data_analisis_final.csv. Nilai kosong ditulis sebagai sel kosong sehingga file dapat dibuka kembali di R, Excel, atau perangkat lunak statistik lain tanpa kehilangan struktur.

15 Kesimpulan

Pemeriksaan kualitas data pada Global Freelancers Dataset menemukan sejumlah masalah, yaitu 541 sel kosong, 0 baris duplikat, serta format yang tidak seragam pada gender, tarif per jam, tingkat kepuasan, dan status aktif. Masalah tersebut ditangani melalui penyeragaman kategori, pembuangan simbol mata uang dan tanda persen, konversi ke tipe numerik, penghapusan baris ganda sehingga tersisa 1000 baris unik dari 1000 baris awal, serta imputasi median pada 452 nilai kosong di variabel numerik. Validasi akhir menunjukkan dataset analisis final terdiri dari 1000 baris dan 15 kolom dengan 0 baris duplikat, sehingga data layak dipakai untuk analisis lanjutan.

Dari sisi karakteristik, rata-rata pengalaman kerja freelancer adalah 11.2 tahun (median 9.0 tahun) dengan kategori terbanyak pemula (57.1%), sedangkan rata-rata tarif per jam adalah 51.29 USD. Hasil uji Shapiro-Wilk menunjukkan bahwa tarif per jam tidak berdistribusi normal (p < 0.001) dan pengalaman kerja tidak berdistribusi normal (p < 0.001), sementara uji Bartlett menunjukkan bahwa varians tarif relatif homogen antar gender (p 0.2219). Korelasi Pearson antara pengalaman kerja dan tarif per jam sebesar 0.079 (hubungan sangat lemah dan signifikan pada taraf 5%), dengan pengalaman menjelaskan sekitar 0.6% variasi tarif. Temuan ini hanya menunjukkan asosiasi dan bukan hubungan sebab-akibat, karena tarif juga dapat dipengaruhi faktor lain seperti bidang keahlian, negara, dan rating yang belum dimodelkan dalam analisis ini.

Daftar Pustaka

Ahir, U. (2024). Global Freelancers Raw Dataset [Data set]. Kaggle. https://www.kaggle.com/datasets/urvishahir/global-freelancers-raw-dataset

Tukey, J. W. (1993). Exploratory Data Analysis: Past, Present and Future. Princeton University.

Velleman, P. F., & Hoaglin, D. C. (2004). Applications, Basics, and Computing of Exploratory Data Analysis. Duxbury Press.