Kemajuan teknologi digital membuat banyak orang dapat bekerja secara lepas (freelance) tanpa terikat pada satu tempat kerja. Para freelancer di pasar kerja ini sangat beragam, baik dari segi usia, keahlian, lama pengalaman, maupun tarif yang ditawarkan. Lama pengalaman kerja (years of experience) menarik untuk ditelaah karena diduga berkaitan dengan besarnya tarif per jam (hourly rate).
Analisis ini memakai Global Freelancers (Raw) Dataset dari Kaggle (Ahir, 2025) yang memuat 1.000 observasi freelancer dan 12 variabel. Variabel tersebut mencakup karakteristik individu, pengalaman kerja, tarif jasa, rating, kepuasan klien, dan status keaktifan.
Sebelum dianalisis, data harus diperiksa lebih dulu untuk menemukan nilai yang hilang (missing value), kategori yang tidak seragam, kesalahan format, baris ganda, serta nilai yang tampak tidak wajar. Pembersihan dilakukan agar data konsisten. Setelah itu, analisis difokuskan pada gambaran pengalaman kerja freelancer dan hubungannya dengan tarif per jam. Pendekatan yang dipakai mengikuti prinsip analisis data eksploratif (exploratory data analysis), yaitu memeriksa struktur, sebaran, dan kejanggalan data sebelum menarik kesimpulan (Tukey, 1993; Velleman & Hoaglin, 2004).
Data bersumber dari Global Freelancers (Raw) Dataset di
Kaggle (Ahir, 2025). File Excel dibaca dari folder yang sama dengan file
.Rmd ini. Jika nama file sedikit berbeda (misalnya ada
tambahan (1) dari hasil unduhan), kode akan mencari file
.xlsx yang namanya memuat kata freelancers.
file_excel <- "data_ade_global_freelancers_raw.xlsx"
if (!file.exists(file_excel)) {
kandidat <- list.files(pattern = "freelancers.*\\.xlsx$", ignore.case = TRUE)
if (length(kandidat) == 0) {
stop("File Excel tidak ditemukan di folder: ", getwd(),
". Letakkan file .xlsx satu folder dengan file .Rmd ini.")
}
file_excel <- kandidat[1]
}
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"
)| Keterangan | Nilai |
|---|---|
| Jumlah observasi | 1000 |
| Jumlah variabel | 12 |
| Nama_Variabel |
|---|
| freelancer_ID |
| name |
| gender |
| age |
| country |
| language |
| primary_skill |
| years_of_experience |
| hourly_rate (USD) |
| rating |
| is_active |
| client_satisfaction |
| freelancer_ID | name | gender | age | country | language | primary_skill | years_of_experience | hourly_rate (USD) | rating | is_active | client_satisfaction |
|---|---|---|---|---|---|---|---|---|---|---|---|
| FL250001 | Ms. Nicole Kidd | f | 52.0 | Italy | Italian | Blockchain Development | 11.0 | 100 | NA | 0 | NA |
| FL250002 | Vanessa Garcia | FEMALE | 52.0 | Australia | English | Mobile Apps | 34.0 | USD 100 | 3.3 | 1 | 0.84 |
| FL250003 | Juan Nelson | male | 53.0 | Germany | German | Graphic Design | 31.0 | 50 | 0.0 | N | 0.71 |
| FL250004 | Amanda Spencer | F | 38.0 | Australia | English | Web Development | 4.0 | $40 | 1.5 | N | 0.90 |
| FL250005 | Lynn Curtis DDS | female | 53.0 | Germany | German | Web Development | 27.0 | 30 | 4.8 | 0 | 0.83 |
| FL250006 | Lisa Johnson | female | 59.0 | Netherlands | Dutch | AI | 14.0 | $30 | 2.4 | FALSE | NA |
| FL250007 | Eric Myers | m | 52.0 | Indonesia | Indonesian | Data Analysis | 10.0 | USD 75 | 3.1 | 0 | 0.94 |
| FL250008 | Ricky Graham | male | 43.0 | Italy | Italian | Blockchain Development | 14.0 | USD 40 | 4.6 | FALSE | 0.94 |
| FL250009 | Sean Martin | male | 26.0 | United States | English | Blockchain Development | 4.0 | NA | 4.0 | TRUE | 0.76 |
| FL250010 | Matthew Lloyd | MALE | 52.0 | Turkey | Turkish | AI | 22.0 | $50 | NA | 1 | NA |
## '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 ...
## 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 terdiri dari 1.000 freelancer
dan 12 variabel: identitas (freelancer_ID,
name), demografi (gender, age,
country, language), profesi
(primary_skill, years_of_experience,
hourly_rate (USD)), dan kinerja (rating,
is_active, client_satisfaction). Kolom
hourly_rate (USD) dan is_active masih berupa
teks karena formatnya campur aduk (mis. “$40”, “USD 75”, “yes”, “N”),
sehingga belum bisa dihitung. Pada ringkasan numerik,
client_satisfaction bernilai maksimum 100 dengan rata-rata
sekitar 11,7 padahal mediannya hanya 0,82. Ini tanda awal bahwa kolom
tersebut memakai dua skala berbeda.
Missing value adalah nilai yang tidak tersedia pada suatu observasi. Persentasenya dihitung terhadap jumlah seluruh baris.
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,
caption = "Jumlah dan persentase missing value sebelum cleaning",
digits = 2,
row.names = FALSE
)| 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 |
## Total missing value: 541
Interpretasi. Terdapat 541 sel kosong. Kolom
client_satisfaction paling banyak kosong (176 baris,
17,6%), disusul rating (101; 10,1%),
hourly_rate (USD) (94; 9,4%), is_active (89;
8,9%), years_of_experience (51; 5,1%), dan age
(30; 3,0%). Kolom identitas, gender, country,
language, dan primary_skill terisi lengkap.
Karena semua persentase di bawah 20%, imputasi masih layak dilakukan,
walaupun client_satisfaction perlu diwaspadai.
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 ada baris yang terduplikasi (0),
dan setiap freelancer_ID unik. Namun pemeriksaan ini
diulang setelah pembersihan format, karena dua baris yang sama bisa
tampak berbeda hanya akibat penulisan yang tidak seragam.
Variasi penulisan seperti f, female,
m, dan male disatukan agar tidak dihitung
sebagai kategori yang berbeda.
if ("gender" %in% names(ADE)) {
print(table(ADE$gender, useNA = "ifany"))
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)))
)
)
cat("\nSesudah cleaning:\n")
print(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
##
## Sesudah cleaning:
##
## Female Male
## 490 510
Interpretasi. Sebelum dibersihkan, gender tertulis dalam 10 bentuk (F, f, female, FEMALE, Female, M, m, male, MALE, Male). Setelah diseragamkan tersisa dua kategori: Male 510 (51%) dan Female 490 (49%), tanpa NA. Komposisinya hampir seimbang, jadi perbandingan antar gender tidak terganggu oleh perbedaan jumlah.
Simbol dolar, teks USD, koma ribuan, dan spasi dibuang sebelum kolom diubah menjadi 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. Nilai berformat “$40” atau “USD 75” berhasil menjadi angka. Dari 1.000 baris, 906 terisi dan 94 tetap NA (kosong sejak awal, bukan gagal konversi). Tarif berkisar USD 20 sampai USD 100 dengan rata-rata sekitar USD 52,5 dan median USD 40. Rata-rata yang lebih besar dari median menandakan sebaran condong ke kanan.
if ("client_satisfaction" %in% names(ADE)) {
ADE$client_satisfaction <- bersihkan_numerik(
ADE$client_satisfaction,
persen = TRUE
)
print(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. Kolom ini sudah berupa angka, dan pada data ini tidak ada tanda “%” yang perlu dibuang. Masalah sebenarnya adalah skala: 710 nilai berada pada rentang 0,60 sampai 1,00 (proporsi), sedangkan 114 nilai berada pada rentang 60 sampai 100 (persen). Membuang simbol saja tidak menyatukan kedua skala ini.
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 ...
## 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. Ketiga kolom ini sudah berupa angka sejak awal, sehingga langkah ini lebih berfungsi sebagai pengaman tipe data; jumlah NA tidak berubah (usia 30, pengalaman 51, rating 101). Ada satu temuan pada rating: 145 baris bernilai tepat 0, sementara nilai terkecil berikutnya adalah 1,0. Karena tidak ada rating di antara 0 dan 1, angka 0 kemungkinan berarti “belum dinilai”, bukan penilaian terburuk.
Status yang dikenali diubah menjadi Active atau
Inactive. Nilai kosong atau tidak dikenali dicatat sebagai
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. Delapan bentuk penulisan (0, 1, y, n, yes, no, TRUE, FALSE) kini menjadi tiga kategori: Inactive 465 (46,5%), Active 446 (44,6%), dan Unknown 89 (8,9%). Freelancer aktif dan tidak aktif jumlahnya hampir sama. Kategori Unknown dipertahankan supaya 89 baris tidak terbuang.
Duplikasi dihapus setelah format dibersihkan, agar baris yang sebenarnya sama tidak lolos hanya karena penulisannya berbeda.
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"
)| Tahap | Jumlah_Duplikat | Jumlah_Baris |
|---|---|---|
| Sebelum penghapusan | 0 | NA |
| Sesudah penghapusan | 0 | 1000 |
Interpretasi. Tetap tidak ada duplikat setelah format diseragamkan, sehingga jumlah data tidak berubah: 1.000 baris.
Pemeriksaan ini hanya menandai nilai yang patut dicurigai. Nilai tidak langsung dihapus karena perlu dicocokkan dengan sumber data atau definisi variabel, sejalan dengan prinsip analisis eksploratif untuk memeriksa data secara teliti sebelum memodelkannya (Tukey, 1993).
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")
}| 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. Semua variabel lolos pemeriksaan batas: tidak ada usia di luar 0-100, pengalaman negatif, rating di luar 0-5, tarif negatif, maupun kepuasan di luar 0-100. Meski begitu, lolos batas belum tentu berarti data bersih. Ada dua catatan penting:
client_satisfaction memakai dua skala (0,60-1,00 dan
60-100). Batas 0-100 tidak menangkap masalah ini karena kedua skala
sama-sama masuk di dalamnya.rating bernilai 0 sebanyak 145 baris (14,5%),
kemungkinan penanda “belum dinilai”.Kedua hal ini dicatat sebagai keterbatasan di bagian kesimpulan.
Missing value pada variabel numerik terpilih diisi dengan median.
Kolom asli dipertahankan, dan hasil imputasi disimpan pada kolom
berakhiran _clean agar perubahannya 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,
caption = "Missing value setelah membuat kolom imputasi",
digits = 2,
row.names = FALSE
)| 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 |
Interpretasi. Median dipilih karena lebih tahan
terhadap nilai ekstrem dan sebaran yang condong. Nilai pengisinya: usia
41, pengalaman 9 tahun, tarif USD 40, rating 2,6, dan kepuasan 0,82.
Seluruh kolom _clean kini tanpa NA dengan jumlah baris
tetap 1.000. Konsekuensinya, imputasi median mempersempit variasi data
(simpangan baku tarif turun dari sekitar 27,3 menjadi sekitar 26,3) dan
menarik nilai ke tengah. Efek ini paling terasa pada
client_satisfaction (17,6% diisi) dan rating
(10,1% diisi). Median kepuasan 0,82 juga berasal dari campuran dua
skala, sehingga hasilnya perlu dibaca hati-hati.
Catatan: jika suatu kolom seluruhnya kosong, median
tidak bisa dihitung dan kolom itu tetap berisi NA. Pada
data ini hal tersebut tidak terjadi.
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")| 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. Semua hitungan bernilai 0, artinya
tidak ada duplikat dan imputasi tidak menghasilkan nilai yang melanggar
batas logis. Validasi ini hanya menguji batas nilai, bukan konsistensi
skala client_satisfaction.
Pengalaman kerja dikelompokkan menjadi empat kategori. Pengkodeannya bersifat ordinal: angka kode menunjukkan urutan tingkat pengalaman, bukan jarak numerik yang pasti sama.
| 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_pengalaman <- factor(
data_clean$kategori_pengalaman,
levels = c("Pemula", "Menengah", "Jago", "Master"),
ordered = TRUE
)
data_clean$kategori_encode <- as.integer(data_clean$kategori_pengalaman)
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,
row.names = FALSE,
caption = "Frekuensi kategori dan kode pengalaman kerja"
)
print(table(data_clean$kategori_pengalaman, useNA = "ifany"))
}##
## Pemula Menengah Jago Master
## 571 252 122 55
Interpretasi. Sebaran kategori: Pemula 571 (57,1%), Menengah 252 (25,2%), Jago 122 (12,2%), dan Master 55 (5,5%). Lebih dari separuh freelancer berpengalaman 10 tahun atau kurang, dan jumlahnya menyusut tajam pada tingkat yang lebih tinggi. Tabel frekuensi menunjukkan tiap kategori hanya berpasangan dengan satu kode (1-4), jadi pengkodean konsisten. Kategori Master hanya berisi 55 data, sehingga perbandingan yang melibatkannya kurang stabil.
if ("years_of_experience_clean" %in% names(data_clean)) {
ggplot(data_clean, aes(x = years_of_experience_clean)) +
geom_histogram(
bins = 20,
fill = biru_utama,
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. Batang tertinggi berada di sisi kiri dan ekornya memanjang ke kanan (kemencengan sekitar +0,95). Pola ini sejalan dengan dominasi freelancer kategori Pemula. Lonjakan di nilai pengalaman 9 sebagian berasal dari imputasi median.
if ("kategori_pengalaman" %in% names(data_clean)) {
ggplot(data_clean, aes(x = kategori_pengalaman, fill = kategori_pengalaman)) +
geom_bar(color = "white", linewidth = 0.8, show.legend = FALSE) +
geom_text(stat = "count", aes(label = after_stat(count)),
vjust = -0.5, color = biru_tua, fontface = "bold") +
scale_fill_manual(values = c(
"Pemula" = biru_utama,
"Menengah" = biru_muda,
"Jago" = pink_muda,
"Master" = pink_tua
), na.value = abu_biru) +
labs(
title = "Komposisi Freelancer Menurut Pengalaman",
x = "Kategori pengalaman",
y = "Jumlah freelancer"
) +
tema_laporan
}Interpretasi. Jumlah freelancer menurun dari Pemula (571) ke Master (55). Struktur ini berbentuk piramida: semakin tinggi tingkat pengalaman, semakin sedikit orangnya.
if ("hourly_rate_clean" %in% names(data_clean)) {
ggplot(data_clean, aes(x = hourly_rate_clean)) +
geom_histogram(
bins = 25,
fill = pink_tua,
color = "white",
linewidth = 0.4,
alpha = 0.9
) +
labs(
title = "Distribusi Tarif per Jam",
subtitle = "Hourly rate dalam USD",
x = "Hourly rate (USD)",
y = "Frekuensi"
) +
tema_laporan
}Interpretasi. Grafik tampak berupa batang-batang terpisah karena tarif hanya memiliki enam nilai berbeda: 20, 30, 40, 50, 75, dan 100 USD. Batang tertinggi ada di USD 40 (nilai median yang dipakai untuk mengisi data kosong), dan sebaran condong ke kanan.
QQ plot membandingkan kuantil data dengan kuantil distribusi normal. Titik yang mengikuti garis menunjukkan data lebih sesuai dengan normal, tetapi keputusan sebaiknya tidak hanya bersandar pada grafik (Velleman & Hoaglin, 2004).
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 = biru_utama,
cex = 0.8
)
qqline(x, col = pink_tua, lwd = 2)
} else {
cat("QQ plot hourly rate membutuhkan setidaknya dua observasi valid.")
}
}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 = pink_tua,
cex = 0.8
)
qqline(x, col = biru_utama, lwd = 2)
} else {
cat("QQ plot pengalaman kerja membutuhkan setidaknya dua observasi valid.")
}
}Interpretasi. Pada kedua plot, titik menyimpang dari garis, terutama di kedua ujung, sehingga data tidak tampak berdistribusi normal. Pada tarif, titik membentuk anak tangga karena nilainya diskret (hanya enam nilai berbeda).
Karena tarif hanya punya enam nilai, titik-titik digeser sedikit (jitter) supaya tidak menumpuk. Pergeseran ini hanya untuk tampilan, bukan mengubah data.
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_jitter(color = biru_utama, alpha = 0.55, size = 2.2,
width = 0.25, height = 1.2) +
geom_smooth(
method = "lm",
formula = y ~ x,
se = TRUE,
color = pink_tua,
fill = pink_muda,
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
}Karena data tidak normal (lihat uji normalitas di bawah), selain korelasi Pearson ditampilkan juga korelasi Spearman yang berbasis peringkat.
if (all(c("years_of_experience_clean", "hourly_rate_clean") %in% names(data_clean))) {
pearson <- cor.test(data_clean$years_of_experience_clean,
data_clean$hourly_rate_clean, method = "pearson")
spearman <- suppressWarnings(
cor.test(data_clean$years_of_experience_clean,
data_clean$hourly_rate_clean,
method = "spearman", exact = FALSE)
)
knitr::kable(
data.frame(
Metode = c("Pearson", "Spearman"),
Koefisien = c(unname(pearson$estimate), unname(spearman$estimate)),
Nilai_p = c(pearson$p.value, spearman$p.value)
),
digits = 4,
caption = "Korelasi pengalaman kerja dan hourly rate"
)
}| Metode | Koefisien | Nilai_p |
|---|---|---|
| Pearson | 0.0786 | 0.0129 |
| Spearman | 0.0782 | 0.0134 |
if (all(c("kategori_pengalaman", "hourly_rate_clean") %in% names(data_clean))) {
data_clean %>%
group_by(Kategori = kategori_pengalaman) %>%
summarise(
Jumlah = n(),
Rata_rata_Tarif = mean(hourly_rate_clean),
Median_Tarif = median(hourly_rate_clean),
.groups = "drop"
) %>%
knitr::kable(digits = 2, caption = "Tarif per jam menurut kategori pengalaman")
}| Kategori | Jumlah | Rata_rata_Tarif | Median_Tarif |
|---|---|---|---|
| Pemula | 571 | 49.81 | 40 |
| Menengah | 252 | 50.81 | 40 |
| Jago | 122 | 56.64 | 50 |
| Master | 55 | 57.00 | 50 |
if (all(c("kategori_pengalaman", "hourly_rate_clean") %in% names(data_clean))) {
ggplot(data_clean, aes(x = kategori_pengalaman, y = hourly_rate_clean,
fill = kategori_pengalaman)) +
geom_boxplot(color = biru_tua, alpha = 0.9, show.legend = FALSE) +
scale_fill_manual(values = c(
"Pemula" = biru_utama,
"Menengah" = biru_muda,
"Jago" = pink_muda,
"Master" = pink_tua
)) +
labs(
title = "Tarif per Jam Menurut Kategori Pengalaman",
x = "Kategori pengalaman",
y = "Hourly rate (USD)"
) +
tema_laporan
}Interpretasi. Hubungan pengalaman dan tarif sangat lemah tetapi positif: koefisien korelasi sekitar 0,08 (Pearson maupun Spearman), dengan nilai p Spearman sekitar 0,013. Artinya hubungan ini signifikan secara statistik pada taraf 5%, namun ukurannya sangat kecil, sehingga pengalaman hanya menjelaskan sebagian kecil sekali dari variasi tarif (kurang dari 1% menurut r kuadrat). Rata-rata tarif naik tipis menurut kategori: Pemula sekitar USD 49,8, Menengah 50,8, Jago 56,6, dan Master 57,0. Kenaikan ini kecil dibanding sebaran tarif yang lebar pada tiap kategori. Hubungan ini juga hanya menunjukkan keterkaitan, bukan sebab-akibat.
Transformasi membantu menyamakan skala variabel. Pilihlah transformasi sesuai kebutuhan analisis, karena tidak ada satu transformasi yang selalu terbaik untuk semua data.
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, row.names = FALSE,
caption = "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 berkisar 0-41 tahun dan tarif USD 20-100. Skala keduanya sangat berbeda, sehingga perlu disamakan bila dipakai bersama pada analisis yang peka terhadap skala (misalnya perhitungan jarak, regresi berpenalti, atau clustering).
Min-Max mengubah skala menjadi 0 sampai 1 (jika nilainya bervariasi). Z-Score memusatkan data pada rata-rata 0 dengan simpangan baku 1 (jika 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, row.names = FALSE,
caption = "Verifikasi hasil standardisasi Z-Score")| Variabel | Mean_ZScore | SD_ZScore |
|---|---|---|
| Pengalaman kerja | 0 | 1 |
| Hourly rate | 0 | 1 |
Interpretasi. Setelah Min-Max, kedua variabel berada pada rentang 0 sampai 1 (pengalaman 0 tahun menjadi 0 dan 41 tahun menjadi 1; tarif USD 20 menjadi 0 dan USD 100 menjadi 1). Bentuk sebaran tidak berubah, hanya skalanya. Pada Z-Score, rata-rata praktis 0 dan simpangan baku tepat 1, sehingga standardisasi berhasil. Sebagai gambaran, pengalaman 41 tahun berada sekitar 3,2 simpangan baku di atas rata-rata, tanda adanya ekor kanan yang panjang.
Transformasi yang dipakai adalah \(1/(x+1)^2\). Penambahan 1 mencegah pembagian dengan nol saat pengalaman bernilai nol. Transformasi ini mengubah makna nilai, sehingga kolom asli 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)
cat("Nilai minimum pengalaman:", min(data_final$years_of_experience_clean), "\n")
cat("Jumlah pengalaman bernilai 0:",
sum(data_final$years_of_experience_clean == 0), "\n")
print(
ggplot(data_final, aes(x = years_of_experience_clean)) +
geom_histogram(bins = 20, fill = biru_utama,
color = "white", linewidth = 0.4) +
labs(
title = "Pengalaman Kerja Sebelum Transformasi",
x = "Pengalaman kerja (tahun)",
y = "Frekuensi"
) +
tema_laporan
)
print(
ggplot(data_final, aes(x = exp_inv_sq)) +
geom_histogram(bins = 20, fill = pink_tua,
color = "white", linewidth = 0.4) +
labs(
title = "Distribusi Setelah Transformasi Inverse Square",
x = "Nilai inverse square",
y = "Frekuensi"
) +
tema_laporan
)
}## Nilai minimum pengalaman: 0
## Jumlah pengalaman bernilai 0: 72
Interpretasi. Terdapat 72 baris dengan pengalaman 0 tahun. Tanpa penambahan +1, rumus 1/x² akan menghasilkan tak hingga untuk baris-baris ini. Dengan +1, hasil transformasi berkisar dari sekitar 0,0006 (41 tahun) sampai 1 (0 tahun), dengan rata-rata sekitar 0,106. Transformasi ini membalik urutan: pengalaman besar menjadi nilai kecil. Karena sangat kuat, sebaran hasilnya bisa menjadi terlalu menumpuk di dekat nol, jadi bentuk sebarannya perlu dicek sebelum dipakai.
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, row.names = FALSE,
caption = "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 (setelah imputasi) memiliki rata-rata sekitar 11,22 tahun dan simpangan baku sekitar 9,44. Min-Max menghasilkan rentang 0 sampai 1, sedangkan Z-Score menghasilkan rata-rata 0 dan simpangan baku 1. Semuanya sesuai harapan, jadi fungsi transformasi dinilai benar. Pilihan metode bergantung tujuan: Min-Max untuk rentang tetap, Z-Score bila metode mengandaikan data berpusat di nol, dan inverse square bila sebaran perlu diredam.
Uji Shapiro-Wilk memeriksa hipotesis nol bahwa sampel berasal dari distribusi normal. Nilai p yang kecil menjadi bukti melawan normalitas. R mensyaratkan 3 sampai 5000 observasi dan data yang tidak semuanya 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)
} else {
cat("\nUji Shapiro-Wilk dilewati untuk", nama,
": dibutuhkan 3-5000 observasi valid dengan variasi nilai.\n")
}
}
if ("hourly_rate_clean" %in% names(data_final)) {
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)) {
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. Untuk hourly rate, statistik W sekitar 0,851 dengan nilai p sangat kecil (sekitar 1e-29). Untuk pengalaman kerja, W sekitar 0,910 dengan nilai p sekitar 8e-24. Kedua nilai p jauh di bawah 0,05, maka H0 ditolak: tidak ada bukti bahwa tarif maupun pengalaman berdistribusi normal. Hasil ini konsisten dengan QQ plot. Perlu dicatat bahwa dengan 1.000 data, Shapiro-Wilk sangat sensitif terhadap penyimpangan kecil, dan tarif hanya memiliki enam nilai berbeda. Karena itu, uji yang mensyaratkan normalitas perlu dipertimbangkan ulang; uji nonparametrik atau metode yang tahan terhadap pelanggaran asumsi bisa menjadi alternatif.
Uji Bartlett memeriksa apakah varians beberapa kelompok sama. Di sini tarif per jam dibandingkan antar gender. Uji ini peka terhadap ketidaknormalan, jadi hasilnya perlu dibaca bersama hasil uji normalitas.
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)) {
print(bartlett.test(hourly_rate_clean ~ gender, data = data_bartlett))
data_bartlett %>%
group_by(Gender = gender) %>%
summarise(Jumlah = n(),
Rata_rata = mean(hourly_rate_clean),
SD = sd(hourly_rate_clean),
.groups = "drop") %>%
knitr::kable(digits = 2, caption = "Ringkasan tarif per jam menurut gender")
} 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
| Gender | Jumlah | Rata_rata | SD |
|---|---|---|---|
| Female | 490 | 51.91 | 26.99 |
| Male | 510 | 50.70 | 25.55 |
Interpretasi. Statistik Bartlett sekitar 1,49 dengan derajat bebas 1 dan nilai p sekitar 0,222. Karena p lebih besar dari 0,05, H0 gagal ditolak: tidak ada bukti perbedaan varians tarif antara Female (SD sekitar 27,0) dan Male (SD sekitar 25,6). Namun karena Bartlett peka terhadap ketidaknormalan dan data ini tidak normal, uji Levene atau Fligner-Killeen dapat dipakai sebagai pembanding yang lebih tahan.
Bagian ini menyusun satu dataset analisis dari data yang sudah dibersihkan. Karena hanya ada satu file sumber, prosesnya berupa konsolidasi dan pemilihan kolom, bukan penggabungan dua dataset yang berbeda.
# Isi kolom analisis utama dengan 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"
)| Keterangan | Nilai |
|---|---|
| Jumlah baris | 1000 |
| Jumlah kolom | 15 |
| Jumlah duplikat | 0 |
| 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 52 | Female | Italy | 11 | Menengah | 2 | 100 | 0.82 | 2.6 | Inactive | 0.2682927 | 1.0000 | -0.0234003 | 1.8549287 | 0.0069444 |
| 52 | Female | Australia | 34 | Master | 4 | 100 | 0.84 | 3.3 | Active | 0.8292683 | 1.0000 | 2.4119235 | 1.8549287 | 0.0008163 |
| 53 | Male | Germany | 31 | Master | 4 | 50 | 0.71 | 0.0 | Inactive | 0.7560976 | 0.3750 | 2.0942726 | -0.0491246 | 0.0009766 |
| 38 | Female | Australia | 4 | Pemula | 1 | 40 | 0.90 | 1.5 | Inactive | 0.0975610 | 0.2500 | -0.7645858 | -0.4299352 | 0.0400000 |
| 53 | Female | Germany | 27 | Jago | 3 | 30 | 0.83 | 4.8 | Inactive | 0.6585366 | 0.1250 | 1.6707380 | -0.8107459 | 0.0012755 |
| 59 | Female | Netherlands | 14 | Menengah | 2 | 30 | 0.82 | 2.4 | Inactive | 0.3414634 | 0.1250 | 0.2942506 | -0.8107459 | 0.0044444 |
| 52 | Male | Indonesia | 10 | Pemula | 1 | 75 | 0.94 | 3.1 | Inactive | 0.2439024 | 0.6875 | -0.1292839 | 0.9029021 | 0.0082645 |
| 43 | Male | Italy | 14 | Menengah | 2 | 40 | 0.94 | 4.6 | Inactive | 0.3414634 | 0.2500 | 0.2942506 | -0.4299352 | 0.0044444 |
| 26 | Male | United States | 4 | Pemula | 1 | 40 | 0.76 | 4.0 | Active | 0.0975610 | 0.2500 | -0.7645858 | -0.4299352 | 0.0400000 |
| 52 | Male | Turkey | 22 | Jago | 3 | 50 | 0.82 | 2.6 | Active | 0.5365854 | 0.3750 | 1.1413198 | -0.0491246 | 0.0018904 |
Interpretasi. Dataset analisis terdiri dari 1.000
baris dan 15 kolom tanpa duplikat. Kolom freelancer_ID,
name, language, dan primary_skill
sengaja tidak dimasukkan; bila analisis lanjutan membutuhkan keahlian
utama, kolom primary_skill perlu ditambahkan.
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,
digits = 2,
row.names = FALSE,
caption = "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 |
## Jumlah baris: 1000
## Jumlah kolom: 15
## 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. Seluruh kolom memiliki 0 NA (0%) dan
tidak ada baris duplikat. Status keaktifan: Inactive 465, Active 446,
Unknown 89. Kategori pengalaman: Pemula 571, Menengah 252, Jago 122,
Master 55. Data siap dianalisis, dengan catatan dua keterbatasan dari
pemeriksaan data noisy (skala client_satisfaction dan
rating bernilai 0).
File CSV disimpan di folder kerja R. Gunakan getwd()
untuk melihat lokasinya.
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
## Lokasi penyimpanan: C:/Users/ASUS/OneDrive/TUGAS KULIAH PUTRI/SEMESTER 3/ADE
Pemeriksaan terhadap Global Freelancers Dataset menemukan
beberapa masalah kualitas data, yaitu 541 sel kosong (terbanyak pada
client_satisfaction, 17,6%), penulisan gender dan status
keaktifan yang tidak seragam, serta tarif per jam yang tersimpan sebagai
teks dengan simbol mata uang. Tidak ditemukan baris duplikat maupun
nilai di luar batas logis, tetapi client_satisfaction
ternyata memakai dua skala (0,60-1,00 pada 710 baris dan 60-100 pada 114
baris) dan 145 baris rating bernilai 0 yang kemungkinan berarti “belum
dinilai”. Melalui preprocessing, kategori diseragamkan, tarif
diubah menjadi angka, dan nilai hilang pada variabel numerik diisi
dengan median (usia 41, pengalaman 9 tahun, tarif USD 40, rating 2,6,
kepuasan 0,82), sedangkan status yang tidak diketahui dicatat sebagai
kategori Unknown. Hasilnya adalah dataset analisis berisi 1.000 baris
dan 15 kolom tanpa NA dan tanpa duplikat.
Setelah data dibersihkan, pengalaman kerja freelancer berkisar 0-41 tahun dengan rata-rata sekitar 11,2 tahun dan simpangan baku sekitar 9,4. Sebarannya condong ke kanan: 57,1% freelancer berkategori Pemula, 25,2% Menengah, 12,2% Jago, dan hanya 5,5% Master. Uji Shapiro-Wilk menolak normalitas pada pengalaman kerja (W ≈ 0,910; p < 0,001) maupun tarif per jam (W ≈ 0,851; p < 0,001), sedangkan uji Bartlett tidak menemukan perbedaan varians tarif antar gender (p ≈ 0,222). Pengalaman kerja berhubungan positif tetapi sangat lemah dengan tarif per jam (korelasi sekitar 0,08; p Spearman sekitar 0,013). Hubungan ini signifikan secara statistik namun kecil secara praktis, karena rata-rata tarif hanya naik dari sekitar USD 49,8 pada kategori Pemula menjadi sekitar USD 57,0 pada kategori Master. Dengan demikian, pengalaman kerja bukan penentu utama tarif pada data ini, dan hasil tersebut tidak menunjukkan hubungan sebab-akibat.
client_satisfaction dan
rating yang banyak kosong.client_satisfaction yang bercampur sebaiknya
disatukan (misalnya nilai 60-100 dibagi 100) sebelum imputasi dan
analisis lanjutan.Ahir, U. (2025). 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.