1. Pendahuluan

Bagian ini menyajikan gambaran umum serta sistematika pembahasan guna memudahkan pembaca dalam memahami seluruh rangkaian analisis data ini.

1.1 Latar Belakang

Kemajuan internet dan platform kerja daring membuat semakin banyak orang memilih menjadi pekerja lepas (freelancer). Fleksibilitas waktu dan tempat kerja menjadi daya tarik utama bagi para pencari kerja saat ini. Oleh karena itu, freelancer kini tidak lagi dipandang sebagai pekerjaan sampingan, melainkan sebuah pilihan utama yang menjanjikan di era digital.

Pertumbuhan jumlah pekerja lepas ini terjadi secara global dan mencakup berbagai industri. Tren ini memicu persaingan yang ketat di pasar kerja internasional. Dengan masifnya pertumbuhan tersebut, analisis data mengenai profil, keahlian, dan pendapatan para freelancer menjadi sangat penting untuk dipelajari guna memahami dinamika pasar kerja modern saat ini.

Namun, data mentah yang dikumpulkan dari berbagai platform sering kali masih berantakan, tidak konsisten, atau memiliki nilai yang hilang (missing values). Oleh karena itu, tahapan data preprocessing dan eksplorasi data awal menjadi langkah krusial untuk membersihkan dan menyiapkan data, sehingga siap digunakan untuk analisis lebih lanjut.

1.2 Rumusan Masalah

Rumusan masalah adalah pertanyaan-pertanyaan yang ingin dijawab oleh analisis ini.

  1. Bagaimana kondisi dan kualitas data pada dataset Global Freelancers?

  2. Permasalahan data apa saja yang terdapat pada dataset tersebut, seperti missing value, inkonsistensi penulisan, kesalahan format, baris kembar, dan nilai yang janggal?

  3. Bagaimana proses pembersihan dan penanganan data (penyeragaman kategori, konversi tipe data, dan imputasi) agar data menjadi rapi dan siap digunakan?

  4. Bagaimana karakteristik data setelah pembersihan, yaitu sebaran pengalaman kerja dan tarif per jam, kenormalan data, dan kesamaan varians tarif antara perempuan dan laki-laki?

  5. Bagaimana hubungan antara pengalaman kerja (years of experience) dan tarif per jam (hourly rate) freelancer?

  6. Bagaimana skala dan bentuk sebaran pengalaman kerja dan tarif per jam berubah setelah dilakukan normalisasi, standardisasi, dan transformasi invers kuadrat?

1.3 Tujuan

Tujuan adalah “jawaban yang ingin dicapai” dari setiap rumusan masalah di atas, dengan urutan yang sama.

  1. Mengidentifikasi kondisi dan kualitas data pada dataset freelancer.

  2. Menemukan dan mengevaluasi permasalahan data, seperti missing value, inkonsistensi, dan kesalahan format.

  3. Melakukan pembersihan dan transformasi data, agar data menjadi lebih rapi dan siap digunakan.

  4. Menganalisis karakteristik data setelah pembersihan melalui grafik dan uji asumsi (normalitas dan homogenitas varians), sebagai dasar untuk analisis selanjutnya.

  5. Menganalisis hubungan antara pengalaman kerja (years of experience) dan tarif per jam (hourly rate) freelancer.

  6. Menyelaraskan skala dan distribusi variabel numerik melalui transformasi, lalu menyimpan dataset final dalam format CSV.

2. Gambaran Dataset

Sebelum dibersihkan, data dilihat terlebih dahulu apa adanya. Dengan begitu, kondisi awal dapat dibandingkan dengan hasil akhir setelah seluruh proses selesai. Ibarat dokter, kita “memeriksa pasien” dulu sebelum memberi obat.

2.1 Profil Dataset (5W+1H)

Sebelum masuk ke isi data, perlu diketahui dulu asal-usulnya. Ringkasan berikut memakai kerangka 5W+1H (siapa, apa, kapan, di mana, mengapa, dan bagaimana).

Pertanyaan Penjelasan
Who (siapa pembuatnya) Urvish Ahir, kontributor di platform Kaggle.
What (apa isinya) Data mentah (raw dataset) profil pekerja lepas global, dengan atribut seperti usia, jenis kelamin, negara, bahasa, keahlian utama, lama pengalaman, tarif per jam, rating, status keaktifan, dan kepuasan klien.
When (kapan) Diunggah ke Kaggle pada tahun 2024.
Where (cakupan lokasi) Global, mencakup berbagai negara, dari platform pasar kerja lepas digital.
Why (mengapa ada) Menyediakan data mentah sebagai bahan latihan pembersihan data (data cleaning), analisis data eksploratif (EDA), dan penanganan data kotor (dirty data).
How (cara pengumpulan) Dibuat dari data sintetis yang dihasilkan dengan Python menggunakan pustaka (library).
Format Berkas CSV di Kaggle (pada laporan ini dibaca dari berkas Excel), dengan satu baris mewakili satu freelancer.

Interpretasi. Karena datanya sintetis (dibuat dengan program), hasil analisis pada laporan ini tidak boleh dipakai untuk menyimpulkan kondisi nyata pasar freelancer. Tujuan utamanya adalah berlatih merapikan dan menjelajahi data mentah. Sumber data: https://www.kaggle.com/datasets/urvishahir/global-freelancers-raw-dataset.

2.2 Variabel pada Data

Setiap baris data mewakili satu freelancer (total 1.000 baris dan 12 variabel). Berikut fungsi setiap kolom beserta jenis dan skala datanya.

Variabel Fungsi Jenis Data Skala
freelancer_ID ID unik freelancer Kualitatif Nominal
name Nama freelancer Kualitatif Nominal
gender Jenis kelamin Kualitatif Nominal
age Usia freelancer Kuantitatif kontinu Rasio
country Negara asal Kualitatif Nominal
language Bahasa utama Kualitatif Nominal
primary_skill Keahlian utama Kualitatif Nominal
years_of_experience Lama pengalaman kerja (tahun) Kuantitatif diskrit Rasio
hourly_rate (USD) Tarif per jam (USD) Kuantitatif kontinu Rasio
rating Rating freelancer (0 sampai 5) Kuantitatif kontinu Interval
is_active Status keaktifan Kualitatif Nominal
client_satisfaction Tingkat kepuasan klien Kuantitatif kontinu Rasio

Data kualitatif berupa kategori atau label (misalnya negara), sedangkan data kuantitatif berupa angka yang bisa dihitung (misalnya usia). Diskrit berarti nilainya bulat hasil menghitung (tahun pengalaman), sedangkan kontinu bisa bernilai pecahan.

2.3 Import Dataset

Langkah pertama adalah memasukkan file Excel ke R. File Excel disimpan di folder yang sama dengan file .Rmd, jadi R cukup membacanya lewat nama file tanpa alamat lengkap. Kode di bawah juga menyertakan pengaman: jika file tidak ditemukan, proses berhenti dan muncul pesan peringatan, sehingga kesalahan mudah dikenali.

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)

Interpretasi. Kode ini tidak menampilkan angka apa pun, tetapi tidak muncul pesan galat. Artinya file berhasil dibaca dan seluruh isinya kini tersimpan di objek bernama ADE. Objek inilah yang akan dibersihkan langkah demi langkah pada bab-bab berikutnya.

2.4 Struktur Awal Data

Setelah data masuk, kita lihat “wajah awalnya”: berapa baris dan kolomnya, apa saja nama kolomnya, tipe datanya (angka atau teks), beberapa baris pertama dan terakhir, serta ringkasan statistiknya.

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       
##  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. Dataset berisi 1.000 baris (1.000 freelancer) dan 12 kolom: freelancer_ID, name, gender, age, country, language, primary_skill, years_of_experience, hourly_rate (USD), rating, is_active, dan client_satisfaction. Hal penting yang terlihat dari str() adalah kolom hourly_rate (USD) dan is_active masih terbaca sebagai teks, padahal seharusnya tarif berupa angka. Penyebabnya, isinya campuran angka dan simbol seperti $ dan USD. Selama masih berupa teks, tarif tidak bisa dihitung rata-ratanya. Ini menjadi tanda pertama bahwa data perlu dibersihkan.

3. Permasalahan Data

Bab ini adalah tahap “mendiagnosis dan mengobati”. Setiap subbab menangani satu jenis masalah: sel kosong, penulisan yang tidak seragam, simbol yang menempel pada angka, baris ganda, dan angka yang tidak masuk akal. Di setiap subbab, ada penjelasan masalahnya, kodenya, lalu interpretasi hasilnya.

3.1 Missing Value

Missing value adalah sel yang kosong (tidak ada isinya). Sel kosong perlu dihitung dulu karena sebagian besar fungsi statistik tidak bisa bekerja dengan benar jika ada sel kosong. Kita hitung jumlah dan persentasenya di tiap kolom, supaya tahu kolom mana yang paling bermasalah.

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

Interpretasi. Terdapat 541 sel kosong yang tersebar di enam kolom. Kolom dengan sel kosong terbanyak adalah client_satisfaction (176 data, 17,6%), disusul rating (101 data, 10,1%), hourly_rate (USD) (94 data, 9,4%), is_active (89 data, 8,9%), years_of_experience (51 data, 5,1%), dan age (30 data, 3,0%). Kolom identitas, gender, country, language, dan primary_skill lengkap. Persentase kosong tertinggi masih di bawah 20%, jadi datanya masih tergolong aman untuk diperbaiki. Karena itu, baris yang kosong tidak perlu dibuang. Cukup diisi dengan nilai pengganti (imputasi), sehingga ke-1.000 freelancer tetap bisa dipakai.

3.2 Inkonsistensi Gender

Kolom gender ditulis dengan banyak gaya untuk arti yang sama, misalnya F, f, female, atau FEMALE. Bagi komputer, huruf besar dan kecil dianggap berbeda, sehingga F dan f terhitung dua kategori. Karena itu, semua variasi disatukan menjadi dua kategori saja: Female dan Male. Caranya: ubah semua huruf menjadi kecil, buang spasi di pinggir, lalu petakan ke dua kategori.

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

Interpretasi. Sebelum diseragamkan, gender memiliki 10 bentuk penulisan (misalnya F, f, female, FEMALE, M, m, male, MALE). Setelah proses, hanya tersisa dua kategori: Female 490 dan Male 510. Artinya, jumlah freelancer perempuan dan laki-laki hampir sama (49% berbanding 51%), jadi data tidak timpang ke satu jenis kelamin. Tidak ada data gender yang kosong, sehingga kolom ini sudah siap dipakai, termasuk untuk uji homogenitas pada bab 7.

3.3 Membersihkan Hourly Rate

Tarif per jam ditulis dalam beberapa gaya, seperti $20, USD 30, atau angka saja, sehingga R membacanya sebagai teks. Agar bisa dihitung, simbol $ dan kata USD dibuang dulu, spasi dirapikan, lalu kolom 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 ...

Interpretasi. Kolom hourly_rate (USD) kini bertipe numerik dengan 906 nilai valid dan 94 nilai kosong (sel kosong ini akan diisi pada subbab 4.2). Menariknya, tarif yang muncul hanya beberapa tingkatan tetap, yaitu 20, 30, 40, 50, 75, dan 100 dolar per jam. Artinya tarif tidak bersifat kontinu seperti tinggi badan yang bisa bernilai 160,3 atau 160,4. Pola “anak tangga” ini akan terlihat jelas di grafik pada bab 6 dan memengaruhi hasil uji statistik pada bab 7.

3.4 Membersihkan Client Satisfaction

Tanda persen dihapus lalu kolom diubah menjadi angka. Pada data ini skalanya tidak seragam: sebagian nilai ditulis dalam skala 0 sampai 1 (misalnya 0,84) dan sebagian dalam skala 0 sampai 100 atau persen (misalnya 84%). Padahal artinya sama. Karena itu nilai yang lebih besar dari 1 dibagi 100 agar semuanya berada pada skala 0 sampai 1. Sel yang masih kosong lalu diisi dengan median, sebab median lebih tahan terhadap nilai yang sangat besar atau sangat kecil dibandingkan rata-rata.

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 ...
# Menyamakan skala: nilai 0-100 diubah ke skala 0-1
jumlah_skala_besar <- sum(ADE$client_satisfaction > 1, na.rm = TRUE)
cat("Nilai di atas 1 (skala 0-100):", jumlah_skala_besar, "\n")
## Nilai di atas 1 (skala 0-100): 114
skala_besar <- !is.na(ADE$client_satisfaction) &
  ADE$client_satisfaction > 1

ADE$client_satisfaction[skala_besar] <-
  ADE$client_satisfaction[skala_besar] / 100

summary(ADE$client_satisfaction)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##  0.6000  0.6975  0.7900  0.7927  0.8900  1.0000     176
# Isi NA dengan median
jumlah_na_satisfaction <- sum(is.na(ADE$client_satisfaction))
cat("Jumlah NA client satisfaction:", jumlah_na_satisfaction, "\n")
## Jumlah NA client satisfaction: 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.7100  0.7900  0.7922  0.8700  1.0000
sum(is.na(ADE$client_satisfaction))
## [1] 0

Interpretasi. Awalnya kolom ini memuat dua skala penulisan sekaligus. Sebanyak 114 nilai (11,4% data) ditulis pada skala 0 sampai 100 (atau persen), lalu dibagi 100 sehingga seluruh data kini berada pada skala 0 sampai 1. Setelah itu, 176 nilai kosong diisi dengan median sebesar 0,79, dan jumlah sel kosong menjadi 0. Penyamaan skala ini penting: tanpanya, rata-rata kepuasan klien akan jauh lebih besar dari yang sebenarnya karena angka 84 dan 0,84 dianggap berbeda.

3.5 Membersihkan Age, Years of Experience, dan Rating

Kolom usia, pengalaman kerja, dan rating tersimpan sebagai teks (misalnya "52.0"), sehingga belum bisa dihitung. Kode di bawah juga memeriksa akhiran .00.00 (misalnya 52.00.00) yang sering muncul pada data mentah dan tidak dikenali sebagai angka, lalu membuangnya jika ada. Setelah itu, ketiga kolom dikonversi menjadi 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"
cat("Nilai age berakhiran .00.00:",
    sum(grepl("\\.00\\.00$", as.character(ADE$age)), na.rm = TRUE), "\n")
## Nilai age berakhiran .00.00: 0
ADE$age <- gsub("\\.00\\.00$", "", as.character(ADE$age))
ADE$age <- gsub("%", "", 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"
cat("Nilai years_of_experience berakhiran .00.00:",
    sum(grepl("\\.00\\.00$", as.character(ADE$years_of_experience)), na.rm = TRUE), "\n")
## Nilai years_of_experience berakhiran .00.00: 0
ADE$years_of_experience <- gsub("\\.00\\.00$", "", as.character(ADE$years_of_experience))
ADE$years_of_experience <- gsub("%", "", 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 ...

Interpretasi. Ketiga kolom sekarang bertipe numerik, jadi sudah bisa dihitung rata-rata, median, dan sebarannya. Pemeriksaan akhiran .00.00 menghasilkan 0 pada kedua kolom, artinya akhiran itu tidak ada di data ini. Kolom-kolom tersebut hanya perlu dikonversi dari teks ke angka. Sel kosongnya masih ada, yaitu 30 pada age, 51 pada years_of_experience, dan 101 pada rating. Sel-sel ini akan ditangani pada tahap imputasi (subbab 4.2).

3.6 Membersihkan Is_Active

Status keaktifan dicatat dengan banyak kode (1, y, yes, true, 0, n, dan lainnya). Semua kode yang berarti aktif dipetakan menjadi Active, semua kode yang berarti tidak aktif menjadi Inactive, sedangkan data kosong atau yang tidak dikenali diberi label Unknown. Memberi label Unknown lebih jujur daripada menebak status seseorang.

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

Interpretasi. Ditemukan 8 variasi penulisan untuk status yang sebenarnya hanya dua: empat untuk aktif (1, Y, yes, TRUE) dan empat untuk tidak aktif (0, N, no, FALSE). Hasil akhirnya adalah Inactive 465, Active 446, dan Unknown 89. Jumlah freelancer aktif dan tidak aktif hampir berimbang, sedangkan yang statusnya tidak diketahui hanya sekitar 9% dari seluruh data. Angka 89 ini sama dengan jumlah sel kosong pada kolom is_active di subbab 3.1, artinya semua data kosong sudah tertangani dan tidak ada informasi yang dikarang.

3.7 Duplikasi Data

Baris yang persis sama lebih dari satu kali bisa membuat hasil analisis berlebih, misalnya satu freelancer terhitung dua kali. Karena itu, jumlah baris ganda dihitung dulu, lalu dihapus jika ada.

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

Interpretasi. Jumlah baris duplikat adalah 0, jadi tidak ada baris yang dihapus dan data tetap berjumlah 1.000 baris. Ini kabar baik: setiap baris benar-benar mewakili satu freelancer yang berbeda.

3.8 Data Noisy

Noisy data adalah angka yang tidak masuk akal, misalnya usia negatif, pengalaman kerja negatif, atau rating yang melebihi 5. Angka seperti ini biasanya akibat salah ketik dan bisa merusak hasil analisis. Pemeriksaan dilakukan dengan menghitung berapa banyak nilai yang berada di luar batas wajar. Jika hasilnya 0, berarti aman. Rating bernilai 0 juga dihitung karena maknanya ambigu: bisa berarti penilaian terendah atau belum adanya penilaian.

summary(ADE$age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   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.    NA's 
##    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.    NA's 
##   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
jumlah_rating_nol <- sum(ADE$rating == 0, na.rm = TRUE)
jumlah_rating_nol
## [1] 145
summary(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
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.7100  0.7900  0.7922  0.8700  1.0000
sum(ADE$client_satisfaction < 0, na.rm = TRUE)
## [1] 0
sum(ADE$client_satisfaction > 1, na.rm = TRUE)
## [1] 0

Interpretasi. Semua nilai berada pada rentang yang wajar: usia 20 sampai 60 tahun, pengalaman kerja 0 sampai 41 tahun, rating 0 sampai 5, tarif 20 sampai 100 dolar, dan kepuasan klien 0 sampai 1 (setelah skalanya disamakan pada subbab 3.4). Seluruh penghitungan nilai tidak wajar bernilai 0, jadi tidak ada data yang perlu dibuang. Terdapat 145 freelancer dengan rating 0. Nilai ini tetap dipertahankan karena masih berada dalam rentang 0 sampai 5, tetapi perlu diingat maknanya ambigu (penilaian terendah atau belum dinilai).

4. Data Clean

Setelah semua masalah penulisan dan tipe data beres, bab ini menyiapkan salinan data yang bersih (data_clean) dan mengisi sel kosong yang masih tersisa.

4.1 Membuat Data Clean

Data yang sudah diseragamkan disalin ke objek baru bernama data_clean. Dengan cara ini, hasil penyeragaman tetap terpisah dari proses imputasi yang dilakukan berikutnya, sehingga jika ada yang keliru kita tidak perlu mengulang dari awal.

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.79 0.84 0.71 0.9 0.83 0.79 0.94 0.94 0.76 0.79 ...
dim(data_clean)
## [1] 1000   12
summary(data_clean)
##  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.6000     
##  1st Qu.: 30.00    1st Qu.:1.400   Class :character   1st Qu.:0.7100     
##  Median : 40.00    Median :2.600   Mode  :character   Median :0.7900     
##  Mean   : 52.46    Mean   :2.513                      Mean   :0.7922     
##  3rd Qu.: 75.00    3rd Qu.:3.800                      3rd Qu.:0.8700     
##  Max.   :100.00    Max.   :5.000                      Max.   :1.0000     
##  NA's   :94        NA's   :101

Interpretasi. Sekarang data_clean berisi 1.000 baris dan kolom-kolom numerik (age, years_of_experience, hourly_rate (USD), rating, client_satisfaction) sudah bertipe angka, sedangkan gender dan is_active sudah berupa kategori yang rapi. Pada summary(), sel kosong masih tampak sebagai NA's di beberapa kolom. Sel inilah yang akan diisi pada subbab berikutnya.

4.2 Mengisi Missing Value dengan Median

Sel kosong pada pengalaman kerja, tarif per jam, dan rating diisi dengan nilai median masing-masing kolom. Median adalah nilai tengah ketika data diurutkan dari kecil ke besar, sehingga tidak mudah “tertarik” oleh nilai ekstrem seperti yang bisa terjadi pada rata-rata. Hasil imputasi pengalaman dan tarif disimpan di kolom sementara (_clean) supaya data asli tetap utuh sebagai pembanding.

# 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

Interpretasi. Median yang dipakai untuk mengisi sel kosong adalah 9 tahun untuk pengalaman kerja, 40 dolar untuk tarif per jam, dan 2,6 untuk rating. Tiga angka 0 pada bagian “Cek hasil imputasi” berarti tidak ada lagi sel kosong pada ketiga kolom tersebut. Perlu diingat bahwa imputasi median membuat banyak data bernilai sama, jadi keragaman data sedikit menyempit. Kolom age sengaja tidak diimputasi dan dibiarkan apa adanya (30 sel kosong), karena analisis utama laporan ini berfokus pada pengalaman kerja dan tarif per jam.

5. Validasi Setelah Pembersihan

Setelah “operasi” selesai, perlu ada “pemeriksaan ulang” untuk memastikan semuanya benar-benar sudah beres. Pemeriksaan pada bab 3 diulang terhadap data_clean: apakah masih ada sel kosong, baris ganda, atau angka di luar batas wajar.

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.79 0.84 0.71 0.9 0.83 0.79 0.94 0.94 0.76 0.79 ...
##  $ 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  
##  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.6000     
##  1st Qu.: 30.00    1st Qu.:1.500   Class :character   1st Qu.:0.7100     
##  Median : 40.00    Median :2.600   Mode  :character   Median :0.7900     
##  Mean   : 52.46    Mean   :2.521                      Mean   :0.7922     
##  3rd Qu.: 75.00    3rd Qu.:3.700                      3rd Qu.:0.8700     
##  Max.   :100.00    Max.   :5.000                      Max.   :1.0000     
##  NA's   :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-1:",
    sum(data_clean$client_satisfaction < 0 |
          data_clean$client_satisfaction > 1, na.rm = TRUE), "\n")
## Client satisfaction di luar 0-1: 0

Interpretasi. Setelah pembersihan, data_clean berisi 1.000 baris tanpa duplikat. Pada colSums(is.na(data_clean)), kolom rating, client_satisfaction, dan is_active sudah tidak memiliki sel kosong. Kolom age masih memiliki 30 sel kosong karena memang tidak diimputasi. Kolom years_of_experience (51) dan hourly_rate (USD) (94) masih tercatat kosong karena hasil imputasinya disimpan di kolom terpisah, yaitu years_of_experience_clean dan hourly_rate_clean, yang keduanya sudah lengkap (0 sel kosong). Semua pemeriksaan rentang menunjukkan angka 0, yang berarti tidak ada nilai di luar batas yang wajar. Setelah imputasi, rata-rata pengalaman kerja menjadi sekitar 11,2 tahun dan rata-rata tarif sekitar 51,3 dolar per jam. Kesimpulannya, data sudah cukup bersih dan siap dieksplorasi.

6. Encoding dan Visualisasi

Pada bab ini data mulai “dipelajari”. Pengalaman kerja dikelompokkan menjadi tingkatan (encoding), dihitung statistik deskriptifnya, lalu bentuk sebaran data dilihat lewat grafik. Grafik membantu kita memahami data dengan cepat sebelum masuk ke uji statistik di bab 7.

6.1 Encoding Pengalaman Kerja

Encoding berarti mengubah data menjadi bentuk lain yang lebih mudah dianalisis. Di sini, lama pengalaman (angka tahun) dibagi menjadi empat tingkat: pemula (sampai 10 tahun), menengah (11 sampai 20 tahun), jago (21 sampai 30 tahun), dan master (lebih dari 30 tahun). Tiap tingkat kemudian diberi kode angka 1 sampai 4 sesuai urutannya, sehingga komputer tahu bahwa master lebih tinggi daripada pemula. Nilai yang tidak tersedia diberi kategori unknown dan tidak diberi kode angka.

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),
  "unknown",
  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 ...

Interpretasi. Pengalaman kerja maksimum adalah 41 tahun, sehingga keempat kategori terisi. Sebagian besar freelancer berada di tingkat awal: pemula 571 orang (57,1%), menengah 252 (25,2%), jago 122 (12,2%), dan master 55 (5,5%). Semakin tinggi tingkat pengalaman, semakin sedikit jumlah freelancernya. Tabel silang di atas memastikan pemetaan kode sudah benar: pemula berkode 1, menengah 2, jago 3, dan master 4. Kategori unknown tidak muncul karena pengalaman kerja sudah lengkap setelah imputasi.

6.2 Statistik Deskriptif

Statistik deskriptif adalah ringkasan angka yang menggambarkan data: berapa banyak datanya, di mana pusatnya (mean dan median), seberapa menyebar (standar deviasi), serta nilai terkecil dan terbesarnya. Perhitungan memakai data pengalaman kerja dan tarif per jam setelah imputasi, sehingga jumlah datanya lengkap. Jika mean berbeda cukup jauh dari median, sebaran data cenderung miring.

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

ringkas <- function(x) {
  c(length(x), round(mean(x), 2), median(x),
    round(sd(x), 2), min(x), max(x))
}

tabel_deskriptif <- data.frame(
  Statistik = c("Jumlah Data", "Mean", "Median",
                "Standar Deviasi", "Minimum", "Maksimum"),
  Years_of_Experience = ringkas(experience_test),
  Hourly_Rate_USD = ringkas(hourly_rate_test)
)

knitr::kable(tabel_deskriptif, align = "lrr")
Statistik Years_of_Experience Hourly_Rate_USD
Jumlah Data 1000.00 1000.00
Mean 11.22 51.29
Median 9.00 40.00
Standar Deviasi 9.44 26.26
Minimum 0.00 20.00
Maksimum 41.00 100.00
# Nilai-nilai untuk teks interpretasi
mean_exp_desk  <- mean(experience_test)
med_exp_desk   <- median(experience_test)
sd_exp_desk    <- sd(experience_test)
mean_rate_desk <- mean(hourly_rate_test)
med_rate_desk  <- median(hourly_rate_test)
sd_rate_desk   <- sd(hourly_rate_test)

Interpretasi. Kedua variabel memiliki 1000 data tanpa sel kosong. Pada pengalaman kerja, rata-rata sebesar 11,22 tahun lebih besar dibanding median 9 tahun, dengan simpangan baku 9,44 dan rentang 0 sampai 41 tahun. Mean yang lebih besar dari median menandakan sebaran miring ke kanan: ada sebagian freelancer dengan pengalaman sangat lama yang menarik rata-rata ke atas. Pada tarif per jam, rata-rata 51,29 dolar dibanding median 40 dolar, dengan simpangan baku 26,26 dan rentang 20 sampai 100 dolar. Dugaan awal tentang bentuk sebaran ini akan dilihat lewat grafik pada subbab berikutnya.

6.3 Histogram

Histogram menunjukkan seberapa sering suatu nilai muncul. Sumbu mendatar adalah nilai (di sini lama pengalaman), dan tinggi batang adalah jumlah freelancer pada rentang itu. Dari bentuk batangnya, kita bisa tahu apakah data simetris (seperti lonceng) atau miring.

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

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

Interpretasi. Histogram pengalaman kerja miring ke kanan (right-skewed): batang tertinggi berada di pengalaman rendah dan semakin ke kanan semakin pendek. Artinya mayoritas freelancer baru berpengalaman beberapa tahun, sedangkan yang berpengalaman puluhan tahun jumlahnya sedikit. Histogram kategori memperlihatkan pola yang sama, dengan batang tertinggi pada kode 1 (pemula) dan terendah pada kode 4 (master). Data yang miring seperti ini tidak berbentuk lonceng, sehingga kita sudah bisa menduga bahwa pengalaman kerja tidak berdistribusi normal. Dugaan ini akan diperiksa pada Q-Q plot (subbab 6.4) dan uji Shapiro-Wilk (subbab 7.1).

6.4 Q-Q Plot

Q-Q plot adalah grafik untuk memeriksa kenormalan data secara visual. Titik-titik data dibandingkan dengan garis lurus yang mewakili distribusi normal. Jika titik-titik menempel pada garis, data mendekati normal. Semakin menyimpang dari garis, semakin jauh data dari normal.

if (length(hourly_rate_test) >= 2) {
  qqnorm(hourly_rate_test, main = "QQ Plot Hourly Rate", col = "#D08AA5", pch = 19)
  qqline(hourly_rate_test, col = "#4A2545", lwd = 2)
}

if (length(experience_test) >= 2) {
  qqnorm(experience_test, main = "QQ Plot Years of Experience", col = "#D08AA5", pch = 19)
  qqline(experience_test, col = "#4A2545", lwd = 2)
}

Interpretasi. Pada kedua Q-Q plot, titik-titik tidak mengikuti garis lurus. Ujung kanan melengkung menjauh dari garis, yang menandakan ada sejumlah nilai besar yang jauh lebih tinggi dari yang diharapkan pada data normal. Pada tarif per jam, titik-titik tampak berbentuk anak tangga karena tarifnya hanya muncul pada beberapa nilai tetap (20, 30, 40, 50, 75, dan 100 dolar). Dengan demikian, secara visual pengalaman kerja dan tarif per jam tidak berdistribusi normal. Kesimpulan ini sejalan dengan bentuk histogram yang miring.

6.5 Scatter Plot

Scatter plot (diagram pencar) menampilkan setiap freelancer sebagai satu titik, dengan lama pengalaman di sumbu mendatar dan tarif per jam di sumbu tegak. Grafik ini dipakai untuk melihat apakah ada hubungan antara dua variabel. Jika titik-titik membentuk pola naik, berarti makin lama pengalaman, makin tinggi tarif. Jika titik menyebar acak tanpa pola, hubungannya lemah.

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 = "#9D4E73"
)

Interpretasi. Titik-titik pada scatter plot menyebar di seluruh area dan membentuk garis-garis mendatar, sesuai dengan tarif yang hanya bernilai 20, 30, 40, 50, 75, atau 100 dolar. Tidak tampak kecenderungan naik atau turun yang jelas. Artinya, pengalaman kerja yang lebih lama tidak otomatis diikuti tarif yang jauh lebih tinggi pada data ini. Freelancer dengan pengalaman sedikit pun ada yang memasang tarif tinggi, dan sebaliknya. Ini baru kesan dari grafik. Seberapa kuat hubungannya akan diuji dengan angka pada subbab 7.3.

7. Uji Asumsi

Banyak uji statistik mensyaratkan hal tertentu pada data, misalnya data harus berdistribusi normal atau variansnya sama antarkelompok. Bab ini memeriksa syarat-syarat tersebut secara resmi dengan uji statistik, sebagai pelengkap pengamatan visual pada bab 6.

7.1 Uji Normalitas (Shapiro-Wilk)

Uji Shapiro-Wilk menguji apakah data berdistribusi normal.

  • H0 (hipotesis nol): data berdistribusi normal.
  • Aturan keputusan: jika p-value lebih kecil dari 0,05, H0 ditolak, artinya data tidak normal. Jika p-value 0,05 atau lebih, H0 tidak ditolak, artinya data bisa dianggap normal.

Nilai W mendekati 1 berarti data semakin mirip distribusi normal. Hasil uji ini 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

Interpretasi. Kedua uji menghasilkan p-value yang sangat kecil dan jauh di bawah 0,05 (statistik W sekitar 0,85 untuk tarif per jam dan 0,9101 untuk pengalaman kerja, dengan p-value pengalaman kerja < 2,2e-16). Dengan demikian H0 ditolak: baik tarif per jam maupun pengalaman kerja tidak berdistribusi normal. Hasil ini konsisten dengan bentuk histogram yang miring dan pola Q-Q plot yang menyimpang dari garis lurus. Konsekuensinya, hubungan pengalaman kerja dan tarif per jam pada subbab 7.3 dianalisis dengan korelasi Spearman yang tidak mensyaratkan normalitas (non-parametrik), bukan korelasi Pearson.

7.2 Uji Homogenitas Varians (Bartlett)

Uji Bartlett membandingkan keragaman (varians) tarif per jam antara kelompok perempuan dan laki-laki. Tujuannya mengetahui apakah tarif satu kelompok jauh lebih beragam daripada kelompok lainnya.

  • H0: varians kedua kelompok sama.
  • Aturan keputusan: jika p-value lebih kecil dari 0,05, H0 ditolak, artinya varians kedua kelompok 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

Untuk melihat angka di balik uji ini, berikut ringkasan tarif per jam menurut gender (jumlah, rata-rata, dan simpangan baku).

ringkasan_gender <- data_bartlett %>%
  group_by(gender) %>%
  summarise(
    Jumlah    = n(),
    Rata_rata = round(mean(hourly_rate_clean), 2),
    SD        = round(sd(hourly_rate_clean), 2),
    .groups   = "drop"
  ) %>%
  rename(Gender = gender)

knitr::kable(ringkasan_gender, align = "lrrr",
             caption = "Ringkasan tarif per jam menurut gender")
Ringkasan tarif per jam menurut gender
Gender Jumlah Rata_rata SD
Female 490 51.91 26.99
Male 510 50.70 25.55
# Nilai-nilai untuk teks interpretasi
rata_f <- ringkasan_gender$Rata_rata[ringkasan_gender$Gender == "Female"]
rata_m <- ringkasan_gender$Rata_rata[ringkasan_gender$Gender == "Male"]
sd_f   <- ringkasan_gender$SD[ringkasan_gender$Gender == "Female"]
sd_m   <- ringkasan_gender$SD[ringkasan_gender$Gender == "Male"]

Interpretasi. Pengujian melibatkan 490 freelancer perempuan dan 510 laki-laki. Statistik uji Bartlett (K-squared) sebesar 1,4921 dengan df = 1, dan p-value sekitar 0,22 (lebih besar dari 0,05). Tabel ringkasan mendukung hasil ini: rata-rata tarif perempuan sebesar 51,91 dolar (simpangan baku 26,99) dan laki-laki 50,70 dolar (simpangan baku 25,55). Selisih rata-ratanya hanya sekitar 1,21 dolar dan simpangan bakunya berdekatan, artinya tarif kedua kelompok hampir sama baik dari segi besar maupun keragamannya. Karena itu H0 tidak ditolak: tidak ada bukti bahwa keragaman tarif per jam berbeda antara perempuan dan laki-laki, sehingga asumsi homogenitas varians dapat dianggap terpenuhi. Dengan kata lain, sebaran tarif perempuan dan laki-laki kurang lebih sama lebar. Namun, uji Bartlett peka terhadap data yang tidak normal, dan tarif per jam terbukti tidak normal pada subbab 7.1. Karena itu hasil ini sebaiknya dipakai dengan hati-hati dan dianggap sebagai gambaran awal.

7.3 Korelasi Spearman

Pertanyaan utama analisis ini: apakah freelancer yang lebih berpengalaman memasang tarif per jam yang lebih tinggi? Karena data tidak berdistribusi normal (subbab 7.1), dipakai korelasi Spearman. Metode ini membandingkan peringkat data, bukan nilai aslinya, sehingga tidak mensyaratkan normalitas.

  • H0: tidak ada hubungan monotonik antara pengalaman kerja dan tarif per jam.
  • Aturan keputusan: jika p-value lebih kecil dari 0,05, H0 ditolak, artinya ada hubungan yang signifikan secara statistik.
  • Nilai rho berkisar dari -1 sampai 1. Tanda positif berarti keduanya cenderung naik bersama, tanda negatif berarti berlawanan arah. Semakin dekat ke 0, semakin lemah hubungannya. Kekuatan dikategorikan: sangat lemah (sampai 0,2), lemah (0,2 sampai 0,4), sedang (0,4 sampai 0,6), kuat (0,6 sampai 0,8), dan sangat kuat (di atas 0,8).
data_korelasi <- data.frame(
  pengalaman = data_clean$years_of_experience_clean,
  tarif      = data_clean$hourly_rate_clean
)

data_korelasi <- data_korelasi[complete.cases(data_korelasi), ]

if (nrow(data_korelasi) >= 3 &&
    length(unique(data_korelasi$pengalaman)) > 1 &&
    length(unique(data_korelasi$tarif)) > 1) {

  hasil_spearman <- cor.test(
    data_korelasi$pengalaman,
    data_korelasi$tarif,
    method = "spearman",
    exact = FALSE
  )

  print(hasil_spearman)

  rho       <- unname(hasil_spearman$estimate)
  p_spearman <- hasil_spearman$p.value

  kuat_txt <- as.character(cut(
    abs(rho),
    breaks = c(-Inf, 0.2, 0.4, 0.6, 0.8, Inf),
    labels = c("sangat lemah", "lemah", "sedang", "kuat", "sangat kuat")
  ))
  arah_txt <- ifelse(rho > 0, "positif", "negatif")
  sig_txt  <- ifelse(p_spearman < 0.05,
                     "signifikan secara statistik",
                     "tidak signifikan secara statistik")

  cat("\nKoefisien Spearman (rho):", round(rho, 4), "\n")
  cat("P-value:", signif(p_spearman, 4), "\n")
  cat("Arah hubungan:", arah_txt, "\n")
  cat("Kekuatan hubungan:", kuat_txt, "\n")
} else {
  message("Data tidak cukup bervariasi untuk menghitung korelasi.")
}
## 
##  Spearman's rank correlation rho
## 
## data:  data_korelasi$pengalaman and data_korelasi$tarif
## S = 153640365, p-value = 0.01343
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
##        rho 
## 0.07815689 
## 
## 
## Koefisien Spearman (rho): 0.0782 
## P-value: 0.01343 
## Arah hubungan: positif 
## Kekuatan hubungan: sangat lemah

Interpretasi. Koefisien Spearman (rho) bernilai 0,0782 dengan p-value 0,0134 (dihitung dari 1000 freelancer). Karena p-value lebih kecil dari 0,05, H0 ditolak: terdapat hubungan yang signifikan secara statistik antara pengalaman kerja dan tarif per jam. Arah hubungannya positif, yaitu freelancer yang lebih berpengalaman cenderung memiliki tarif sedikit lebih tinggi, dan kekuatannya tergolong sangat lemah. Hubungan yang signifikan tetapi lemah berarti pola itu memang ada, namun pengaruhnya kecil. Hal ini selaras dengan scatter plot pada subbab 6.5, yang titiknya tersebar luas tanpa pola naik yang tegas. Dengan data sebanyak 1.000 baris, hubungan yang kecil pun bisa terdeteksi signifikan, jadi signifikan tidak sama dengan kuat. Perlu diingat juga bahwa korelasi hanya menunjukkan keterkaitan, bukan sebab-akibat, dan tarif yang hanya bernilai pada enam tingkat tetap membuat banyak data kembar sehingga p-value dihitung dengan pendekatan.

8. Transformasi Numerik

Tahapan transformasi numerik bertujuan untuk menyelaraskan skala dan distribusi variabel years_of_experience_clean dan hourly_rate_clean (hasil imputasi pada bagian 4.2) agar siap digunakan dalam analisis eksploratif dan pemodelan statistik lanjutan. Ada satu masalah yang ingin diatasi: pengalaman kerja berkisar 0 sampai 41, sedangkan tarif berkisar 20 sampai 100, sehingga satuan dan skalanya tidak sebanding. Tiga transformasi diterapkan: normalisasi min-max, standardisasi z-score, dan transformasi invers kuadrat. Tahap ini dilakukan sebelum dataset final disusun, sehingga kolom hasil transformasi ikut terbawa ke dataset final pada bagian 9. Perlu diingat bahwa tidak ada satu transformasi yang selalu terbaik untuk semua data, jadi pilihlah transformasi sesuai kebutuhan analisis.

8.1 Normalisasi Min-Max

Normalisasi min-max mengubah setiap nilai menjadi \((x - x_{min}) / (x_{max} - x_{min})\), sehingga seluruh data berada pada rentang 0 sampai 1. Nilai terkecil menjadi 0, nilai terbesar menjadi 1, dan nilai lain berada di antaranya. Kode di bawah juga membuat tema grafik (tema_plum) agar warna grafik ggplot sesuai dengan palet laporan.

# Load library pendukung
library(dplyr)
library(ggplot2)

# Tema grafik disamakan dengan palet laporan
tema_plum <- theme_minimal(base_size = 12) +
  theme(
    plot.background  = element_rect(fill = "#FCEFF3", color = NA),
    panel.background = element_rect(fill = "#FCEFF3", color = NA),
    panel.grid.major = element_line(color = "#EBCFDA"),
    panel.grid.minor = element_blank(),
    plot.title       = element_text(color = "#4A2545", face = "bold"),
    axis.title       = element_text(color = "#4A2545"),
    axis.text        = element_text(color = "#3B2A35")
  )

# 1. Pemeriksaan Rentang Data Awal
rentang_awal <- data_clean %>%
  summarise(
    across(
      c(years_of_experience_clean, hourly_rate_clean),
      list(
        min = ~ min(.x, na.rm = TRUE),
        max = ~ max(.x, na.rm = TRUE)
      )
    )
  )
print(rentang_awal)
## # A tibble: 1 × 4
##   years_of_experience_clean_min years_of_experience_clea…¹ hourly_rate_clean_min
##                           <dbl>                      <dbl>                 <dbl>
## 1                             0                         41                    20
## # ℹ abbreviated name: ¹​years_of_experience_clean_max
## # ℹ 1 more variable: hourly_rate_clean_max <dbl>
# 2. Definisi Fungsi dan Penerapan Min-Max
min_max <- function(x) {
  (x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
}

data_clean <- data_clean %>%
  mutate(
    exp_minmax  = min_max(years_of_experience_clean),
    rate_minmax = min_max(hourly_rate_clean)
  )

# Output Ringkas Min-Max
cat("HASIL NORMALISASI MIN-MAX\n")
## HASIL NORMALISASI MIN-MAX
cat("Rentang Exp Min-Max  :", min(data_clean$exp_minmax), "-", max(data_clean$exp_minmax), "\n")
## Rentang Exp Min-Max  : 0 - 1
cat("Rentang Rate Min-Max :", min(data_clean$rate_minmax), "-", max(data_clean$rate_minmax), "\n")
## Rentang Rate Min-Max : 0 - 1

Interpretasi. Sebelum normalisasi, pengalaman kerja berkisar 0 sampai 41 tahun dan tarif per jam 20 sampai 100 dolar, sehingga skalanya sangat berbeda. Setelah normalisasi, keduanya berada pada rentang 0 sampai 1 dan dapat dibandingkan langsung. Min-max hanya menggeser dan mengubah skala, sehingga bentuk sebaran (termasuk kemiringannya) tidak berubah. Kekurangannya, metode ini peka terhadap nilai ekstrem karena bergantung pada nilai minimum dan maksimum. Satu data yang sangat besar saja bisa menekan nilai-nilai lainnya ke area yang sempit.

8.2 Standardisasi Z-Score

Standardisasi z-score mengubah nilai menjadi \((x - \bar{x}) / s\), sehingga data berpusat di 0 dengan simpangan baku 1. Nilai z menunjukkan berapa simpangan baku suatu nilai berada di atas (positif) atau di bawah (negatif) rata-rata. Misalnya z = 2 berarti nilai itu dua simpangan baku di atas rata-rata, sedangkan z = 0 berarti tepat di rata-rata.

data_clean <- data_clean %>%
  mutate(
    exp_z  = as.numeric(scale(years_of_experience_clean)),
    rate_z = as.numeric(scale(hourly_rate_clean))
  )

# Verifikasi Hasil Z-Score
cat("HASIL VERIFIKASI Z-SCORE\n")
## HASIL VERIFIKASI Z-SCORE
cat("Rata-rata Z-Score Exp  :", round(mean(data_clean$exp_z, na.rm = TRUE), 4), "\n")
## Rata-rata Z-Score Exp  : 0
cat("Standar Deviasi Z Exp  :", round(sd(data_clean$exp_z, na.rm = TRUE), 4), "\n")
## Standar Deviasi Z Exp  : 1
cat("Rata-rata Z-Score Rate :", round(mean(data_clean$rate_z, na.rm = TRUE), 4), "\n")
## Rata-rata Z-Score Rate : 0
cat("Standar Deviasi Z Rate :", round(sd(data_clean$rate_z, na.rm = TRUE), 4), "\n")
## Standar Deviasi Z Rate : 1

Interpretasi. Rata-rata z-score pengalaman kerja dan tarif per jam masing-masing 0,0000 dan 0,0000, dengan simpangan baku 1,0000 dan 1,0000. Rata-rata yang hampir 0 dan simpangan baku yang tepat 1 menunjukkan standardisasi berhasil. Kedua variabel berpusat di 0 dengan keragaman 1, sehingga sebanding walaupun satuan aslinya berbeda (tahun dan dolar). Seperti min-max, z-score tidak mengubah bentuk sebaran, jadi data yang miring ke kanan tetap miring dan tidak otomatis menjadi normal.

8.3 Transformasi Invers & Konsolidasi Data

Transformasi invers kuadrat, \(1 / (x + 1)^2\), dicoba untuk melihat apakah kemiringan sebaran dapat dikurangi. Angka 1 ditambahkan pada penyebut untuk menghindari pembagian dengan nol, sehingga perlu dicek lebih dulu apakah ada nilai 0. Tingkat kemiringan diukur dengan skewness: nilai mendekati 0 berarti simetris, nilai positif berarti miring ke kanan, dan nilai negatif berarti miring ke kiri.

# 1. Pengecekan Keamanan Nilai Nol
cek_nol <- data_clean %>%
  summarise(
    min_val    = min(years_of_experience_clean, na.rm = TRUE),
    jumlah_nol = sum(years_of_experience_clean == 0, na.rm = TRUE)
  )
print(cek_nol)
## # A tibble: 1 × 2
##   min_val jumlah_nol
##     <dbl>      <int>
## 1       0         72
# 2. Transformasi Inverse Square (+1 Proteksi Pembagian 1/0)
data_clean <- data_clean %>%
  mutate(
    exp_inv_sq  = 1 / ((years_of_experience_clean + 1)^2),
    rate_inv_sq = 1 / ((hourly_rate_clean + 1)^2)
  )

# 3. Kemiringan (skewness) sebelum dan sesudah transformasi
skewness_manual <- function(x) {
  x <- x[!is.na(x)]
  (sum((x - mean(x))^3) / length(x)) / (sum((x - mean(x))^2) / length(x))^(3 / 2)
}

sk_exp_sebelum  <- skewness_manual(data_clean$years_of_experience_clean)
sk_exp_sesudah  <- skewness_manual(data_clean$exp_inv_sq)
sk_rate_sebelum <- skewness_manual(data_clean$hourly_rate_clean)
sk_rate_sesudah <- skewness_manual(data_clean$rate_inv_sq)

tabel_skew <- data.frame(
  Variabel = c("Pengalaman kerja", "Tarif per jam"),
  Skewness_Sebelum = round(c(sk_exp_sebelum, sk_rate_sebelum), 3),
  Skewness_Sesudah = round(c(sk_exp_sesudah, sk_rate_sesudah), 3)
)
knitr::kable(tabel_skew, align = "lrr")
Variabel Skewness_Sebelum Skewness_Sesudah
Pengalaman kerja 0.945 3.008
Tarif per jam 0.761 1.355
# Visualisasi Distribusi Sebelum Transformasi Invers
ggplot(data_clean, aes(x = years_of_experience_clean)) +
  geom_histogram(bins = 20, fill = "#D08AA5", color = "white") +
  labs(
    title = "Distribusi Pengalaman Kerja Sebelum Transformasi",
    x = "Pengalaman Kerja (Tahun)",
    y = "Frekuensi"
  ) +
  tema_plum

# Visualisasi Distribusi Sesudah Transformasi Invers
ggplot(data_clean, aes(x = exp_inv_sq)) +
  geom_histogram(bins = 20, fill = "#9D4E73", color = "white") +
  labs(
    title = "Distribusi Pengalaman Kerja Sesudah Transformasi Invers Kuadrat",
    x = "1 / (Pengalaman + 1)^2",
    y = "Frekuensi"
  ) +
  tema_plum

Interpretasi. Pengalaman kerja memiliki 72 nilai nol (minimum 0 tahun), sehingga penambahan 1 pada penyebut memang diperlukan agar tidak terjadi pembagian dengan nol. Skewness pengalaman kerja berubah dari 0,95 menjadi 3,01, sedangkan tarif per jam dari 0,76 menjadi 1,36. Pada pengalaman kerja, nilai mutlak skewness tidak menurun, sehingga transformasi ini belum membantu menyimetriskan sebaran. Hal ini dapat dibandingkan dengan kedua histogram di atas. Dua hal perlu diperhatikan. Pertama, transformasi invers membalik urutan data: nilai asli yang kecil menjadi besar, sehingga freelancer dengan pengalaman paling sedikit memperoleh nilai transformasi tertinggi dan arah hubungan dengan variabel lain ikut terbalik. Kedua, tarif per jam hanya bernilai pada enam tingkat tetap, sehingga transformasi apa pun tidak akan menghilangkan pola anak tangganya.

Seluruh variabel hasil transformasi kemudian dikonsolidasikan dalam data_transformasi, dan ringkasan statistiknya dievaluasi. Tujuannya memastikan semua transformasi menghasilkan angka yang sesuai harapan dan tidak ada sel kosong.

# 4. Konsolidasi & Evaluasi Ringkasan Statistik
# (kolom transformasi sudah dihitung pada langkah sebelumnya)
data_transformasi <- data_clean

# Ringkasan Evaluasi Terpadu (Dicetak Langsung)
evaluasi_stat <- data_transformasi %>%
  summarise(
    Mean_Exp_Asli   = round(mean(years_of_experience_clean, na.rm = TRUE), 2),
    SD_Exp_Asli     = round(sd(years_of_experience_clean, na.rm = TRUE), 2),
    Min_Exp_MinMax  = min(exp_minmax, na.rm = TRUE),
    Max_Exp_MinMax  = max(exp_minmax, na.rm = TRUE),
    Mean_Exp_Z      = round(mean(exp_z, na.rm = TRUE), 4),
    SD_Exp_Z        = round(sd(exp_z, na.rm = TRUE), 4),
    Mean_Rate_Asli  = round(mean(hourly_rate_clean, na.rm = TRUE), 2),
    SD_Rate_Asli    = round(sd(hourly_rate_clean, na.rm = TRUE), 2)
  )

print(evaluasi_stat)
## # A tibble: 1 × 8
##   Mean_Exp_Asli SD_Exp_Asli Min_Exp_MinMax Max_Exp_MinMax Mean_Exp_Z SD_Exp_Z
##           <dbl>       <dbl>          <dbl>          <dbl>      <dbl>    <dbl>
## 1          11.2        9.44              0              1          0        1
## # ℹ 2 more variables: Mean_Rate_Asli <dbl>, SD_Rate_Asli <dbl>
# Tabel evaluasi transformasi pengalaman kerja
tabel_evaluasi <- data.frame(
  Statistik = c("Mean asli", "SD asli", "Minimum Min-Max",
                "Maksimum Min-Max", "Mean Z-Score", "SD Z-Score"),
  Nilai = c(evaluasi_stat$Mean_Exp_Asli, evaluasi_stat$SD_Exp_Asli,
            evaluasi_stat$Min_Exp_MinMax, evaluasi_stat$Max_Exp_MinMax,
            evaluasi_stat$Mean_Exp_Z, evaluasi_stat$SD_Exp_Z)
)
knitr::kable(tabel_evaluasi, digits = 4, align = "lr",
             caption = "Evaluasi transformasi pengalaman kerja")
Evaluasi transformasi pengalaman kerja
Statistik Nilai
Mean asli 11.22
SD asli 9.44
Minimum Min-Max 0.00
Maksimum Min-Max 1.00
Mean Z-Score 0.00
SD Z-Score 1.00
# Pemeriksaan sel kosong pada keenam kolom hasil transformasi
kolom_baru <- c("exp_minmax", "exp_z", "exp_inv_sq",
                "rate_minmax", "rate_z", "rate_inv_sq")
colSums(is.na(data_transformasi[, kolom_baru]))
##  exp_minmax       exp_z  exp_inv_sq rate_minmax      rate_z rate_inv_sq 
##           0           0           0           0           0           0

Interpretasi. Berdasarkan tabel evaluasi di atas, rata-rata pengalaman kerja asli adalah 11,22 tahun dengan simpangan baku 9,44, sedangkan tarif per jam rata-rata 51,29 dolar dengan simpangan baku 26,26. Hasil min-max berada tepat pada rentang 0 sampai 1, dan hasil z-score berpusat di 0,0000 dengan simpangan baku 1,0000, sesuai harapan. Keenam kolom hasil transformasi tidak memiliki sel kosong, sehingga siap dibawa ke dataset final pada bagian 9. Sebagai panduan memilih metode: normalisasi dan standardisasi cocok untuk menyamakan skala, misalnya sebelum analisis berbasis jarak, sedangkan transformasi invers kuadrat dipakai bila tujuannya mengurangi kemiringan sebaran, dengan catatan urutan nilai menjadi terbalik dan hasilnya tidak selalu membaik. Pada data ini, skewness pengalaman kerja justru naik dari 0,95 menjadi 3,01, jadi transformasi invers kuadrat kurang cocok untuk menyimetriskan sebaran pengalaman kerja.

9. Dataset Final

Bab ini merapikan hasil kerja sebelumnya menjadi satu dataset akhir yang siap dipakai: nama kolom dikembalikan seperti semula, kolom sementara dibuang, lalu hanya kolom yang diperlukan yang dipilih.

9.1 Membuat Data Final

Hasil imputasi dikembalikan ke kolom aslinya, kemudian kolom sementara _clean dihapus agar dataset tetap ringkas. Dengan begitu, nama kolom pada data final sama dengan data awal. Kolom hasil transformasi numerik dari bagian 8 (exp_minmax, exp_z, exp_inv_sq, rate_minmax, rate_z, rate_inv_sq) tetap dipertahankan.

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 × 20] (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.79 0.84 0.71 0.9 0.83 0.79 0.94 0.94 0.76 0.79 ...
##  $ kategori_pengalaman: chr [1:1000] "menengah" "master" "master" "pemula" ...
##  $ kategori_encode    : num [1:1000] 2 4 4 1 3 2 1 2 1 3 ...
##  $ exp_minmax         : num [1:1000] 0.2683 0.8293 0.7561 0.0976 0.6585 ...
##  $ rate_minmax        : num [1:1000] 1 1 0.375 0.25 0.125 ...
##  $ exp_z              : num [1:1000] -0.0234 2.4119 2.0943 -0.7646 1.6707 ...
##  $ rate_z             : num [1:1000] 1.8549 1.8549 -0.0491 -0.4299 -0.8107 ...
##  $ exp_inv_sq         : num [1:1000] 0.006944 0.000816 0.000977 0.04 0.001276 ...
##  $ rate_inv_sq        : num [1:1000] 0.000098 0.000098 0.000384 0.000595 0.001041 ...
summary(data_final)
##  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.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   Class :character   1st Qu.:0.7100     
##  Median : 40.00    Median :2.600   Mode  :character   Median :0.7900     
##  Mean   : 51.29    Mean   :2.521                      Mean   :0.7922     
##  3rd Qu.: 75.00    3rd Qu.:3.700                      3rd Qu.:0.8700     
##  Max.   :100.00    Max.   :5.000                      Max.   :1.0000     
##                                                                          
##  kategori_pengalaman kategori_encode   exp_minmax       rate_minmax    
##  Length:1000         Min.   :1.000   Min.   :0.00000   Min.   :0.0000  
##  Class :character    1st Qu.:1.000   1st Qu.:0.07317   1st Qu.:0.1250  
##  Mode  :character    Median :1.000   Median :0.21951   Median :0.2500  
##                      Mean   :1.661   Mean   :0.27368   Mean   :0.3911  
##                      3rd Qu.:2.000   3rd Qu.:0.41463   3rd Qu.:0.6875  
##                      Max.   :4.000   Max.   :1.00000   Max.   :1.0000  
##                                                                        
##      exp_z             rate_z          exp_inv_sq         rate_inv_sq       
##  Min.   :-1.1881   Min.   :-1.1916   Min.   :0.0005669   Min.   :9.803e-05  
##  1st Qu.:-0.8705   1st Qu.:-0.8107   1st Qu.:0.0030864   1st Qu.:1.731e-04  
##  Median :-0.2352   Median :-0.4299   Median :0.0100000   Median :5.949e-04  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   :0.1059001   Mean   :7.328e-04  
##  3rd Qu.: 0.6119   3rd Qu.: 0.9029   3rd Qu.:0.0625000   3rd Qu.:1.041e-03  
##  Max.   : 3.1531   Max.   : 1.8549   Max.   :1.0000000   Max.   :2.268e-03  
## 
dim(data_final)
## [1] 1000   20
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          exp_minmax         rate_minmax 
##                   0                   0                   0                   0 
##               exp_z              rate_z          exp_inv_sq         rate_inv_sq 
##                   0                   0                   0                   0
sum(duplicated(data_final))
## [1] 0

Interpretasi. data_final terdiri atas 1.000 baris tanpa duplikat dan memuat kolom hasil transformasi numerik. Sel kosong yang tersisa hanya pada kolom age (30 data), sedangkan kolom lain sudah lengkap. Kolom years_of_experience dan hourly_rate (USD) kini sudah berisi data hasil imputasi, jadi tidak lagi memiliki sel kosong seperti pada bab 5.

9.2 Integrasi Data

Integrasi data berarti menyusun variabel-variabel yang relevan ke dalam satu dataset yang rapi agar mudah dipakai untuk analisis dan visualisasi. Dari data final dipilih enam belas kolom, yaitu sepuluh kolom inti dan enam kolom hasil transformasi numerik. Fungsi intersect() dipakai agar hanya kolom yang benar-benar tersedia yang diambil, sehingga kode tidak error bila ada nama kolom yang tidak cocok.

data_integrasi <- data_final

str(data_integrasi)
## tibble [1,000 × 20] (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.79 0.84 0.71 0.9 0.83 0.79 0.94 0.94 0.76 0.79 ...
##  $ kategori_pengalaman: chr [1:1000] "menengah" "master" "master" "pemula" ...
##  $ kategori_encode    : num [1:1000] 2 4 4 1 3 2 1 2 1 3 ...
##  $ exp_minmax         : num [1:1000] 0.2683 0.8293 0.7561 0.0976 0.6585 ...
##  $ rate_minmax        : num [1:1000] 1 1 0.375 0.25 0.125 ...
##  $ exp_z              : num [1:1000] -0.0234 2.4119 2.0943 -0.7646 1.6707 ...
##  $ rate_z             : num [1:1000] 1.8549 1.8549 -0.0491 -0.4299 -0.8107 ...
##  $ exp_inv_sq         : num [1:1000] 0.006944 0.000816 0.000977 0.04 0.001276 ...
##  $ rate_inv_sq        : num [1:1000] 0.000098 0.000098 0.000384 0.000595 0.001041 ...
dim(data_integrasi)
## [1] 1000   20
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"     "exp_minmax"         
## [16] "rate_minmax"         "exp_z"               "rate_z"             
## [19] "exp_inv_sq"          "rate_inv_sq"
kolom_analisis <- c(
  "age",
  "gender",
  "country",
  "years_of_experience",
  "kategori_pengalaman",
  "kategori_encode",
  "hourly_rate (USD)",
  "client_satisfaction",
  "rating",
  "is_active",
  "exp_minmax",
  "exp_z",
  "exp_inv_sq",
  "rate_minmax",
  "rate_z",
  "rate_inv_sq"
)

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

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

head(data_analisis)
## # A tibble: 6 × 16
##     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
## # ℹ 10 more variables: `hourly_rate (USD)` <dbl>, client_satisfaction <dbl>,
## #   rating <dbl>, is_active <chr>, exp_minmax <dbl>, exp_z <dbl>,
## #   exp_inv_sq <dbl>, rate_minmax <dbl>, rate_z <dbl>, rate_inv_sq <dbl>
str(data_analisis)
## tibble [1,000 × 16] (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.79 0.84 0.71 0.9 0.83 0.79 0.94 0.94 0.76 0.79 ...
##  $ 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" ...
##  $ exp_minmax         : num [1:1000] 0.2683 0.8293 0.7561 0.0976 0.6585 ...
##  $ exp_z              : num [1:1000] -0.0234 2.4119 2.0943 -0.7646 1.6707 ...
##  $ exp_inv_sq         : num [1:1000] 0.006944 0.000816 0.000977 0.04 0.001276 ...
##  $ rate_minmax        : num [1:1000] 1 1 0.375 0.25 0.125 ...
##  $ rate_z             : num [1:1000] 1.8549 1.8549 -0.0491 -0.4299 -0.8107 ...
##  $ rate_inv_sq        : num [1:1000] 0.000098 0.000098 0.000384 0.000595 0.001041 ...
summary(data_analisis)
##       age           gender            country          years_of_experience
##  Min.   :20.00   Length:1000        Length:1000        Min.   : 0.00      
##  1st Qu.:31.00   Class :character   Class :character   1st Qu.: 3.00      
##  Median :41.00   Mode  :character   Mode  :character   Median : 9.00      
##  Mean   :40.51                                         Mean   :11.22      
##  3rd Qu.:51.00                                         3rd Qu.:17.00      
##  Max.   :60.00                                         Max.   :41.00      
##  NA's   :30                                                               
##  kategori_pengalaman kategori_encode hourly_rate (USD) client_satisfaction
##  Length:1000         Min.   :1.000   Min.   : 20.00    Min.   :0.6000     
##  Class :character    1st Qu.:1.000   1st Qu.: 30.00    1st Qu.:0.7100     
##  Mode  :character    Median :1.000   Median : 40.00    Median :0.7900     
##                      Mean   :1.661   Mean   : 51.29    Mean   :0.7922     
##                      3rd Qu.:2.000   3rd Qu.: 75.00    3rd Qu.:0.8700     
##                      Max.   :4.000   Max.   :100.00    Max.   :1.0000     
##                                                                           
##      rating       is_active           exp_minmax          exp_z        
##  Min.   :0.000   Length:1000        Min.   :0.00000   Min.   :-1.1881  
##  1st Qu.:1.500   Class :character   1st Qu.:0.07317   1st Qu.:-0.8705  
##  Median :2.600   Mode  :character   Median :0.21951   Median :-0.2352  
##  Mean   :2.521                      Mean   :0.27368   Mean   : 0.0000  
##  3rd Qu.:3.700                      3rd Qu.:0.41463   3rd Qu.: 0.6119  
##  Max.   :5.000                      Max.   :1.00000   Max.   : 3.1531  
##                                                                        
##    exp_inv_sq         rate_minmax         rate_z         rate_inv_sq       
##  Min.   :0.0005669   Min.   :0.0000   Min.   :-1.1916   Min.   :9.803e-05  
##  1st Qu.:0.0030864   1st Qu.:0.1250   1st Qu.:-0.8107   1st Qu.:1.731e-04  
##  Median :0.0100000   Median :0.2500   Median :-0.4299   Median :5.949e-04  
##  Mean   :0.1059001   Mean   :0.3911   Mean   : 0.0000   Mean   :7.328e-04  
##  3rd Qu.:0.0625000   3rd Qu.:0.6875   3rd Qu.: 0.9029   3rd Qu.:1.041e-03  
##  Max.   :1.0000000   Max.   :1.0000   Max.   : 1.8549   Max.   :2.268e-03  
## 

Interpretasi. Dataset analisis memuat 16 kolom. Sepuluh kolom inti adalah age, gender, country, years_of_experience, kategori_pengalaman, kategori_encode, hourly_rate (USD), client_satisfaction, rating, dan is_active, ditambah enam kolom hasil transformasi numerik: exp_minmax, exp_z, exp_inv_sq, rate_minmax, rate_z, dan rate_inv_sq. Kolom identitas seperti freelancer_ID dan name, serta language dan primary_skill, tidak diikutkan karena tidak dipakai pada analisis di laporan ini.

9.3 Validasi Hasil Integrasi

Sebagai pemeriksaan terakhir, dataset hasil integrasi dicek ulang: ukurannya, sel kosong, baris ganda, serta sebaran rating, status keaktifan, dan kategori pengalaman. Tujuannya memastikan tidak ada yang berubah atau hilang saat kolom dipilih.

dim(data_analisis)
## [1] 1000   16
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          exp_minmax               exp_z 
##                   0                   0                   0                   0 
##          exp_inv_sq         rate_minmax              rate_z         rate_inv_sq 
##                   0                   0                   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

Interpretasi. Dimensi dataset analisis adalah 1.000 baris x 16 kolom tanpa duplikat. Satu-satunya kolom yang masih memiliki sel kosong adalah age (30 data), sedangkan keenam kolom transformasi lengkap. Rating tidak lagi memiliki sel kosong, status keaktifan terdiri atas Inactive, Active, dan Unknown, dan keempat kategori pengalaman terisi dengan jumlah yang sama seperti hasil encoding sebelumnya. Artinya, dataset ini konsisten dengan hasil bab-bab sebelumnya dan siap disimpan.

10. Menyimpan Hasil

Tahap terakhir pengolahan adalah menyimpan dataset ke file CSV agar bisa dibuka kembali tanpa mengulang seluruh proses. Dataset yang disimpan adalah data_analisis hasil integrasi pada bagian 9.2, yaitu 16 kolom yang terdiri atas 10 kolom inti dan 6 kolom hasil transformasi numerik (bagian 8).

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

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

Interpretasi. Pesan “Proses selesai” menandakan file berhasil dibuat. File data_analisis_final.csv tersimpan di folder yang sama dengan file Rmd dan bisa langsung dipakai untuk analisis lanjutan, misalnya korelasi, regresi, atau visualisasi lain.

11. Kesimpulan

Berikut jawaban atas enam rumusan masalah pada bab 1, dengan urutan yang sama.

  1. Kondisi dan kualitas data. Data mentah berisi 1.000 freelancer dan 12 variabel, tetapi belum rapi: banyak sel kosong, penulisan tidak seragam, dan beberapa kolom angka terbaca sebagai teks.
  2. Permasalahan data. Ditemukan 541 sel kosong di enam kolom, 10 variasi penulisan gender, 8 variasi kode keaktifan, tarif yang tercampur simbol ($, USD), skala kepuasan klien yang tercampur (0 sampai 1 dan 0 sampai 100), serta kolom usia, pengalaman kerja, dan rating yang tersimpan sebagai teks. Tidak ditemukan baris ganda maupun angka di luar rentang yang wajar.
  3. Pembersihan. Gender disatukan menjadi Female (490) dan Male (510), kolom teks dikonversi menjadi numerik, skala client_satisfaction disamakan ke 0 sampai 1, dan is_active menjadi Active, Inactive, atau Unknown. Sel kosong diisi median (pengalaman 9 tahun, tarif 40 dolar, rating 2,6, dan kepuasan klien 0,79), sedangkan age dibiarkan apa adanya (30 data kosong).
  4. Karakteristik data. Pengalaman kerja miring ke kanan: 57,1% freelancer berada pada tingkat pemula. Tarif per jam hanya muncul pada beberapa nilai tetap. Uji Shapiro-Wilk menunjukkan pengalaman kerja dan tarif per jam tidak berdistribusi normal, sedangkan uji Bartlett menunjukkan varians tarif antara perempuan dan laki-laki dapat dianggap homogen.
  5. Hubungan pengalaman kerja dan tarif. Karena data tidak normal, dipakai korelasi Spearman. Hasilnya rho = 0,0782 dengan p-value 0,0134: hubungan positif, signifikan secara statistik, dengan kekuatan sangat lemah.
  6. Transformasi numerik (pengembangan tambahan). Normalisasi min-max menyamakan skala pengalaman kerja dan tarif ke rentang 0 sampai 1, standardisasi z-score memusatkan keduanya di 0 dengan simpangan baku 1, dan transformasi invers kuadrat mengubah skewness pengalaman kerja dari 0,95 menjadi 3,01, sehingga pada data ini transformasi tersebut tidak membantu menyimetriskan sebaran. Ketiganya disimpan sebagai enam kolom baru.

Interpretasi Akhir. Data mentah yang awalnya berantakan (sel kosong, penulisan tidak seragam, dan simbol yang bercampur dengan angka) kini sudah menjadi dataset bersih berukuran 1000 baris x 16 kolom dan disimpan sebagai data_analisis_final.csv. Pada dataset tersebut, pengalaman kerja dan tarif per jam memiliki hubungan positif yang signifikan secara statistik, dengan kekuatan sangat lemah. Artinya, freelancer yang lebih berpengalaman tidak otomatis memasang tarif yang jauh lebih tinggi. Pengalaman kerja hanyalah salah satu dari banyak faktor yang kemungkinan memengaruhi tarif.

Keterbatasan dan Saran

Beberapa hal perlu diingat saat membaca hasil laporan ini.

  • Data sintetis. Dataset dibuat dengan program, sehingga hasilnya tidak mewakili kondisi pasar freelancer yang sebenarnya.
  • Tarif berupa tingkatan tetap. Tarif per jam hanya bernilai 20, 30, 40, 50, 75, atau 100 dolar, sehingga banyak data kembar dan p-value korelasi dihitung dengan pendekatan.
  • Imputasi median. Pengisian sel kosong dengan median membuat keragaman data sedikit menyempit, dan kolom age masih memiliki 30 sel kosong.

Untuk analisis berikutnya, beberapa langkah yang bisa dicoba:

  • Menambahkan variabel lain seperti keahlian utama (primary_skill), negara (country), dan rating untuk mencari faktor yang lebih berperan terhadap tarif.
  • Membandingkan tarif antartingkat pengalaman (pemula, menengah, jago, master) dengan uji non-parametrik seperti Kruskal-Wallis.
  • Memakai data_analisis_final.csv sebagai bahan pemodelan lanjutan, misalnya regresi ordinal karena tarif per jam berupa tingkatan tetap.

Penutup.

Data yang bersih adalah fondasi analisis yang bisa dipercaya. Dengan tahapan preprocessing yang rapi, data freelancer ini sudah siap dipakai untuk menjawab pertanyaan yang lebih dalam tentang apa saja yang menentukan tarif seorang freelancer.