Kemajuan teknologi digital telah mengubah cara orang bekerja. Platform daring memungkinkan seseorang menawarkan keahliannya sebagai freelancer dari mana saja tanpa harus terikat pada satu kantor atau satu pemberi kerja. Pasar kerja lepas yang terbentuk sangat beragam: para freelancer berbeda dalam usia, negara asal, bidang keahlian, lamanya pengalaman, dan tarif yang mereka pasang. Keragaman ini membuat pasar freelance menarik untuk dipelajari secara statistik, karena banyak faktor yang saling berkaitan dan memengaruhi bagaimana seorang freelancer dinilai oleh klien. Salah satu faktor yang paling sering dibicarakan adalah lama pengalaman (years of experience), sebab pengalaman yang lebih panjang umumnya dianggap menjadi sinyal kemampuan dan kepercayaan sehingga berpengaruh pada besarnya tarif per jam (hourly rate).
Data yang digunakan dalam laporan ini adalah Global Freelancers Dataset dari Kaggle, yang berisi 1.000 observasi freelancer dengan 12 variabel. Variabel-variabel tersebut mencakup ciri individu seperti usia dan jenis kelamin, pengalaman kerja, tarif per jam, rating, tingkat kepuasan klien, serta status aktif. Data mentah seperti ini jarang langsung siap dianalisis. Pada praktiknya ditemukan nilai yang kosong (missing value), penulisan kategori yang tidak seragam, simbol mata uang atau tanda persen yang menempel pada angka, baris ganda, hingga nilai yang tampak janggal. Jika masalah tersebut dibiarkan, ukuran ringkasan seperti rata-rata, simpangan baku, atau korelasi dapat menjadi bias dan menyesatkan, sehingga kualitas data perlu diperiksa dan dibenahi sebelum analisis lanjutan dilakukan.
Pendekatan yang dipakai mengikuti semangat analisis data eksploratif (exploratory data analysis, EDA), yaitu memahami data lewat ringkasan numerik dan visualisasi sebelum menarik kesimpulan atau membangun model formal (Tukey, 1993; Velleman & Hoaglin, 2004). Tahapannya mencakup pembersihan dan penyeragaman format, penanganan nilai kosong dengan imputasi median, pengelompokan pengalaman kerja menjadi kategori ordinal, visualisasi sebaran data, transformasi numerik, serta pemeriksaan asumsi normalitas dan homogenitas varians. Analisis kemudian difokuskan pada gambaran pengalaman kerja freelancer dan keterkaitannya dengan tarif per jam, dengan tetap berhati-hati bahwa hubungan yang teramati bersifat asosiasi dan belum tentu menunjukkan hubungan sebab-akibat.
File Excel dibaca otomatis dari folder yang sama dengan dokumen ini, jadi tidak perlu memilih file secara manual setiap kali dokumen dijalankan.
# Isi nama file jika sudah pasti, misalnya "global_freelancers.xlsx".
# Jika dikosongkan (""), R mencari file Excel di folder yang sama dengan file .Rmd ini.
nama_file <- "data ade global_freelancers_raw.xlsx"
if (nama_file != "" && file.exists(nama_file)) {
file_excel <- nama_file
} else {
kandidat <- list.files(pattern = "\\.(xlsx|xls)$", ignore.case = TRUE)
kandidat <- kandidat[!grepl("^~\\$", kandidat)]
if (length(kandidat) == 1) {
file_excel <- kandidat
} else if (interactive()) {
file_excel <- file.choose()
} else {
stop("File Excel tidak ditemukan atau lebih dari satu di folder: ", getwd(),
"\nFile yang terdeteksi: ",
ifelse(length(kandidat) == 0, "(tidak ada)", paste(kandidat, collapse = ", ")),
"\nIsi variabel nama_file dengan nama file yang benar.")
}
}
cat("File yang dibaca:", file_excel, "\n")## File yang dibaca: data ade global_freelancers_raw.xlsx
ADE <- readxl::read_excel(file_excel)
ADE <- as.data.frame(ADE)
knitr::kable(
data.frame(
Keterangan = c("Jumlah observasi", "Jumlah variabel"),
Nilai = c(nrow(ADE), ncol(ADE))
),
caption = "Ringkasan ukuran dataset"
)| 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 |
DT::datatable(
head(ADE, 10),
options = list(pageLength = 10, scrollX = TRUE),
rownames = FALSE,
caption = "Sepuluh observasi pertama"
)✨ Interpretasi: Dataset berhasil dibaca dengan 1000 observasi dan 12 variabel. Sepuluh baris pertama memberi gambaran awal tentang isi tiap kolom, termasuk kemungkinan adanya format yang belum seragam, misalnya simbol mata uang pada tarif atau tanda persen pada tingkat kepuasan. Pemeriksaan ini menjadi dasar untuk tahap pembersihan berikutnya.
## '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: Dari 12 variabel, terdapat 11 kolom
yang terbaca sebagai teks (character) dan 1 kolom yang terbaca
sebagai angka. Kolom yang seharusnya numerik tetapi terbaca sebagai
teks, seperti tarif per jam atau tingkat kepuasan klien, menandakan
adanya karakter non-angka yang perlu dibersihkan sebelum dapat dihitung
statistiknya. Ringkasan summary() pada kolom numerik juga
memberi petunjuk awal tentang nilai minimum, maksimum, dan jumlah
NA.
Missing value berarti ada observasi yang tidak memiliki nilai pada suatu variabel. Persentasenya dihitung dari total baris dalam dataset.
missing_awal <- data.frame(
Variabel = names(ADE),
Jumlah_NA = colSums(is.na(ADE)),
Persentase_NA = round(colMeans(is.na(ADE)) * 100, 2)
)
knitr::kable(
missing_awal,
row.names = FALSE,
caption = "Jumlah dan persentase missing value sebelum cleaning",
digits = 2
)| 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: Secara keseluruhan terdapat 541 sel kosong pada dataset. Variabel dengan nilai kosong terbanyak adalah client_satisfaction yaitu 176 sel (17.60% dari seluruh baris). Nilai kosong ini perlu ditangani agar tidak mengurangi ketepatan ringkasan statistik.
jumlah_duplikat_awal <- sum(duplicated(ADE))
cat("Jumlah baris duplikat sebelum dihapus:", jumlah_duplikat_awal, "\n")## Jumlah baris duplikat sebelum dihapus: 0
if (jumlah_duplikat_awal > 0) {
knitr::kable(
head(ADE[duplicated(ADE), , drop = FALSE], 10),
caption = "Contoh baris duplikat"
)
}✨ Interpretasi: Tidak ditemukan baris yang sama persis pada data mentah. Perlu dicatat bahwa pemeriksaan ini dilakukan sebelum format dirapikan, sehingga duplikat yang tersembunyi karena perbedaan penulisan baru akan terdeteksi setelah tahap cleaning.
Penulisan kategori disamakan supaya variasi seperti f,
female, m, dan male tidak
dihitung sebagai kategori yang terpisah.
if ("gender" %in% names(ADE)) {
gender_raw <- tolower(trimws(as.character(ADE$gender)))
ADE$gender <- ifelse(
gender_raw %in% c("f", "female"), "Female",
ifelse(
gender_raw %in% c("m", "male"), "Male",
ifelse(is.na(ADE$gender) | gender_raw == "", NA_character_,
trimws(as.character(ADE$gender)))
)
)
print(table(ADE$gender, useNA = "ifany"))
}##
## Female Male
## 490 510
✨ Interpretasi: Setelah penulisan diseragamkan, kategori gender menjadi: Female (490), Male (510), dengan 0 observasi yang gendernya kosong. Dengan penyeragaman ini, variasi penulisan seperti huruf besar-kecil atau singkatan tidak lagi dihitung sebagai kategori berbeda, sehingga perbandingan antar kelompok gender menjadi valid.
Tanda dolar, tulisan USD, koma pemisah ribuan, dan spasi dibuang dulu, baru kolom dikonversi ke angka.
if ("hourly_rate (USD)" %in% names(ADE)) {
ADE[["hourly_rate (USD)"]] <- bersihkan_numerik(
ADE[["hourly_rate (USD)"]],
mata_uang = TRUE
)
print(summary(ADE[["hourly_rate (USD)"]]))
str(ADE[["hourly_rate (USD)"]])
}## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 20.00 30.00 40.00 52.46 75.00 100.00 94
## num [1:1000] 100 100 50 40 30 30 75 40 NA 50 ...
✨ Interpretasi: Kolom tarif per jam kini bertipe numerik. Median tarif sebesar 40.00 USD dengan rentang 20.00 sampai 100.00 USD, dan tersisa 94 nilai kosong. Jika nilai kosong bertambah dibandingkan sebelum konversi, berarti ada isian yang tidak dapat dibaca sebagai angka dan perlu ditinjau.
if ("client_satisfaction" %in% names(ADE)) {
ADE$client_satisfaction <- bersihkan_numerik(
ADE$client_satisfaction,
persen = TRUE
)
summary(ADE$client_satisfaction)
}## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.60 0.71 0.82 11.73 0.94 100.00 176
✨ Interpretasi: Setelah tanda persen dibuang, tingkat kepuasan klien dapat dihitung sebagai angka. Nilai tengah (median) kepuasan adalah 0.82 dengan rentang 0.60 sampai 100.00. Karena skala kepuasan diasumsikan 0 sampai 100, nilai di luar rentang tersebut akan diperiksa pada tahap pemeriksaan data janggal.
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: Usia, lama pengalaman, dan rating kini seluruhnya bertipe numerik. Rata-rata usia freelancer adalah 40.5 tahun, rata-rata pengalaman 11.3 tahun, dan rata-rata rating 2.51. Dengan tipe data yang benar, seluruh operasi statistik pada tahap berikutnya dapat dilakukan tanpa galat konversi.
Status yang bisa dikenali dipetakan menjadi Active atau
Inactive. Isian yang kosong atau tidak dikenali ditandai
Unknown agar tidak dianggap sebagai status yang pasti.
if ("is_active" %in% names(ADE)) {
active_raw <- tolower(trimws(as.character(ADE$is_active)))
ADE$is_active <- ifelse(
active_raw %in% c("1", "y", "yes", "true", "active"), "Active",
ifelse(
active_raw %in% c("0", "n", "no", "false", "inactive"), "Inactive",
"Unknown"
)
)
print(table(ADE$is_active, useNA = "ifany"))
}##
## Active Inactive Unknown
## 446 465 89
✨ Interpretasi: Status aktif freelancer kini hanya memiliki kategori yang seragam, yaitu Active (446), Inactive (465), Unknown (89). Kategori Unknown sengaja dipertahankan sebagai penanda bahwa status tersebut tidak dapat dipastikan, sehingga tidak ada asumsi yang dipaksakan pada data.
Baris ganda dihapus setelah format dirapikan, sebab dua baris yang sebenarnya sama bisa terlihat berbeda hanya karena cara penulisannya.
duplikat_sebelum <- sum(duplicated(ADE))
ADE <- ADE[!duplicated(ADE), , drop = FALSE]
duplikat_sesudah <- sum(duplicated(ADE))
knitr::kable(
data.frame(
Tahap = c("Sebelum penghapusan", "Sesudah penghapusan"),
Jumlah_Duplikat = c(duplikat_sebelum, duplikat_sesudah),
Jumlah_Baris = c(NA_integer_, nrow(ADE))
),
caption = "Pemeriksaan duplikasi"
)| Tahap | Jumlah_Duplikat | Jumlah_Baris |
|---|---|---|
| Sebelum penghapusan | 0 | NA |
| Sesudah penghapusan | 0 | 1000 |
✨ Interpretasi: Setelah format dirapikan, terdapat 0 baris duplikat yang kemudian dihapus, sehingga duplikat yang tersisa menjadi 0. Dataset kini berisi 1000 baris unik. Jumlah ini sama dengan pemeriksaan awal, sehingga penyeragaman format tidak memunculkan duplikat baru.
Tahap ini hanya menandai nilai yang mencurigakan, belum menghapusnya. Keputusan menghapus sebaiknya menunggu pengecekan ke sumber data atau definisi variabelnya.
cek_rentang <- function(data, variabel, batas_bawah = -Inf, batas_atas = Inf) {
if (!variabel %in% names(data)) return(NULL)
x <- data[[variabel]]
if (!is.numeric(x)) return(NULL)
data.frame(
Variabel = variabel,
Jumlah_NA = sum(is.na(x)),
Di_Bawah_Batas = sum(x < batas_bawah, na.rm = TRUE),
Di_Atas_Batas = sum(x > batas_atas, na.rm = TRUE),
Minimum = if (all(is.na(x))) NA_real_ else min(x, na.rm = TRUE),
Maksimum = if (all(is.na(x))) NA_real_ else max(x, na.rm = TRUE)
)
}
hasil_rentang <- do.call(rbind, Filter(Negate(is.null), list(
cek_rentang(ADE, "age", 0, 100),
cek_rentang(ADE, "years_of_experience", 0, Inf),
cek_rentang(ADE, "rating", 0, 5),
cek_rentang(ADE, "hourly_rate (USD)", 0, Inf),
cek_rentang(ADE, "client_satisfaction", 0, 100)
)))
if (!is.null(hasil_rentang) && nrow(hasil_rentang) > 0) {
knitr::kable(hasil_rentang, digits = 2, row.names = FALSE,
caption = "Ringkasan pemeriksaan nilai di luar rentang")
} else {
cat("Tidak ada variabel numerik yang sesuai untuk pemeriksaan rentang.\n")
}| Variabel | Jumlah_NA | Di_Bawah_Batas | Di_Atas_Batas | Minimum | Maksimum |
|---|---|---|---|---|---|
| age | 30 | 0 | 0 | 20.0 | 60 |
| years_of_experience | 51 | 0 | 0 | 0.0 | 41 |
| rating | 101 | 0 | 0 | 0.0 | 5 |
| hourly_rate (USD) | 94 | 0 | 0 | 20.0 | 100 |
| client_satisfaction | 176 | 0 | 0 | 0.6 | 100 |
✨ Interpretasi: Dari lima variabel yang diperiksa, total terdapat 0 nilai yang berada di luar batas kewajaran. Seluruh nilai berada dalam rentang yang masuk akal, sehingga tidak ada data yang perlu dikoreksi.
if (all(c("years_of_experience", "age") %in% names(ADE))) {
cat("Pengalaman kerja negatif:",
sum(ADE$years_of_experience < 0, na.rm = TRUE), "\n")
cat("Pengalaman lebih besar dari usia:",
sum(ADE$years_of_experience > ADE$age, na.rm = TRUE), "\n")
}## Pengalaman kerja negatif: 0
## Pengalaman lebih besar dari usia: 0
✨ Interpretasi: Terdapat 0 freelancer dengan pengalaman negatif dan 0 freelancer yang pengalamannya lebih besar daripada usianya. Tidak ada inkonsistensi logis antara usia dan pengalaman kerja.
Nilai kosong pada variabel numerik terpilih diisi dengan median.
Kolom aslinya tidak diubah, hasil imputasi disimpan di kolom baru
berakhiran _clean sehingga sebelum dan sesudahnya bisa
dibandingkan.
data_clean <- ADE
if ("years_of_experience" %in% names(data_clean)) {
data_clean$years_of_experience_clean <-
imputasi_median(data_clean$years_of_experience)
}
if ("hourly_rate (USD)" %in% names(data_clean)) {
data_clean$hourly_rate_clean <-
imputasi_median(data_clean[["hourly_rate (USD)"]])
}
if ("rating" %in% names(data_clean)) {
data_clean$rating_clean <- imputasi_median(data_clean$rating)
}
if ("client_satisfaction" %in% names(data_clean)) {
data_clean$client_satisfaction_clean <-
imputasi_median(data_clean$client_satisfaction)
}
if ("age" %in% names(data_clean)) {
data_clean$age_clean <- imputasi_median(data_clean$age)
}
missing_sesudah <- data.frame(
Variabel = names(data_clean),
Jumlah_NA = colSums(is.na(data_clean)),
Persentase_NA = round(colMeans(is.na(data_clean)) * 100, 2)
)
knitr::kable(
missing_sesudah,
row.names = FALSE,
caption = "Missing value setelah membuat kolom imputasi",
digits = 2
)| Variabel | Jumlah_NA | Persentase_NA |
|---|---|---|
| freelancer_ID | 0 | 0.0 |
| name | 0 | 0.0 |
| gender | 0 | 0.0 |
| age | 30 | 3.0 |
| country | 0 | 0.0 |
| language | 0 | 0.0 |
| primary_skill | 0 | 0.0 |
| years_of_experience | 51 | 5.1 |
| hourly_rate (USD) | 94 | 9.4 |
| rating | 101 | 10.1 |
| is_active | 0 | 0.0 |
| client_satisfaction | 176 | 17.6 |
| years_of_experience_clean | 0 | 0.0 |
| hourly_rate_clean | 0 | 0.0 |
| rating_clean | 0 | 0.0 |
| client_satisfaction_clean | 0 | 0.0 |
| age_clean | 0 | 0.0 |
Catatan interpretasi: apabila ada kolom yang semua
isinya kosong, median tidak bisa dihitung sehingga kolom itu tetap
berisi NA dan harus ditinjau secara khusus.
✨ Interpretasi: Imputasi median mengisi 51 nilai
kosong pada pengalaman kerja, 94 pada tarif per jam, 101 pada rating,
176 pada kepuasan klien, dan 30 pada usia. Setelah imputasi, kolom
berakhiran _clean memiliki 0 nilai kosong. Median dipilih
karena tidak terpengaruh nilai ekstrem, tetapi perlu diingat bahwa
imputasi cenderung mengecilkan variasi data karena banyak nilai diisi
dengan angka yang sama.
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: Dari sembilan pemeriksaan validasi, total temuan masalah adalah 0. Seluruh nilai bernilai nol, artinya data sudah bebas dari duplikasi dan nilai di luar rentang yang diperiksa sehingga layak dilanjutkan ke tahap analisis.
Pengalaman kerja dikelompokkan menjadi empat kategori. Kodenya bersifat ordinal: angka kode hanya menunjukkan urutan tingkat pengalaman, bukan selisih yang besarnya sama antar tingkat.
| Rentang pengalaman | Kategori | Kode |
|---|---|---|
| 0 sampai 10 tahun | pemula | 1 |
| Lebih dari 10 sampai 20 tahun | menengah | 2 |
| Lebih dari 20 sampai 30 tahun | jago | 3 |
| Lebih dari 30 tahun | master | 4 |
if ("years_of_experience_clean" %in% names(data_clean)) {
exp <- data_clean$years_of_experience_clean
data_clean$kategori_pengalaman <- ifelse(
is.na(exp), NA_character_,
ifelse(exp <= 10, "pemula",
ifelse(exp <= 20, "menengah",
ifelse(exp <= 30, "jago", "master")))
)
data_clean$kategori_encode <- match(
data_clean$kategori_pengalaman,
c("pemula", "menengah", "jago", "master")
)
data_clean$kategori_pengalaman <- factor(
data_clean$kategori_pengalaman,
levels = c("pemula", "menengah", "jago", "master")
)
tabel_encoding <- as.data.frame(
table(
Kategori = data_clean$kategori_pengalaman,
Kode = data_clean$kategori_encode,
useNA = "no"
)
)
tabel_encoding <- tabel_encoding[tabel_encoding$Freq > 0, ]
knitr::kable(
tabel_encoding,
caption = "Frekuensi kategori dan kode pengalaman kerja"
)
print(table(data_clean$kategori_pengalaman, useNA = "ifany"))
}##
## pemula menengah jago master
## 571 252 122 55
✨ Interpretasi: Kategori dengan anggota terbanyak adalah pemula, yaitu 571 freelancer atau 57.1% dari seluruh data. Sebaran per kategori: pemula (571), menengah (252), jago (122), master (55). Karena kodenya ordinal, angka 1 sampai 4 hanya menunjukkan urutan tingkat pengalaman dan tidak boleh ditafsirkan sebagai jarak yang sama antar kategori.
if ("years_of_experience_clean" %in% names(data_clean)) {
ggplot(data_clean, aes(x = years_of_experience_clean)) +
geom_histogram(
bins = 20,
fill = warna_biru,
color = "white",
linewidth = 0.5,
alpha = 0.95
) +
labs(
title = "Distribusi Pengalaman Kerja Freelancer",
subtitle = "Frekuensi jumlah tahun pengalaman",
x = "Pengalaman kerja (tahun)",
y = "Frekuensi"
) +
tema_laporan
}✨ Interpretasi: Pengalaman kerja freelancer memiliki rata-rata 11.2 tahun dan median 9.0 tahun, dengan simpangan baku 9.4 tahun. Nilai kemencengan (skewness) sebesar 0.95 menunjukkan bentuk sebaran yang menjulur ke kanan (miring positif). Histogram memperlihatkan di rentang tahun mana freelancer paling banyak terkonsentrasi.
if ("kategori_pengalaman" %in% names(data_clean)) {
ggplot(data_clean, aes(x = kategori_pengalaman, fill = kategori_pengalaman)) +
geom_bar(color = "white", linewidth = 0.6, show.legend = FALSE) +
scale_fill_manual(values = c(
"pemula" = warna_hijaumuda,
"menengah" = warna_hijau,
"jago" = warna_biru,
"master" = warna_lilac
), na.value = "#D9D9D9") +
labs(
title = "Komposisi Freelancer Menurut Pengalaman",
x = "Kategori pengalaman",
y = "Jumlah freelancer"
) +
tema_laporan
}✨ Interpretasi: Grafik batang menunjukkan bahwa kategori pemula adalah yang paling banyak, sedangkan kategori master adalah yang paling sedikit. Selisih antara kategori terbesar dan terkecil adalah 516 freelancer. Komposisi ini menggambarkan apakah pasar freelance dalam data didominasi oleh pekerja berpengalaman rendah, menengah, atau tinggi.
if ("hourly_rate_clean" %in% names(data_clean)) {
ggplot(data_clean, aes(x = hourly_rate_clean)) +
geom_histogram(
bins = 25,
fill = warna_hijau,
color = "white",
linewidth = 0.4,
alpha = 0.95
) +
labs(
title = "Distribusi Tarif per Jam",
subtitle = "Hourly rate dalam USD",
x = "Hourly rate (USD)",
y = "Frekuensi"
) +
tema_laporan
}✨ Interpretasi: Tarif per jam memiliki rata-rata 51.29 USD dan median 40.00 USD, dengan rentang 20.00 sampai 100.00 USD. Kemencengan sebesar 0.76 menunjukkan sebaran yang menjulur ke kanan (miring positif). Rata-rata yang lebih besar daripada median menandakan adanya sebagian freelancer dengan tarif tinggi yang menarik rata-rata ke atas.
QQ plot membandingkan kuantil data dengan kuantil distribusi normal teoretis. Jika titik-titiknya berada dekat garis, data cenderung mendekati normal. Meski begitu, kesimpulan sebaiknya tidak hanya bersandar pada grafik ini.
if ("hourly_rate_clean" %in% names(data_clean)) {
x <- data_clean$hourly_rate_clean
x <- x[is.finite(x)]
if (length(x) >= 2) {
qqnorm(
x,
main = "QQ Plot Hourly Rate",
pch = 19,
col = warna_biru,
cex = 0.8,
family = "sans"
)
qqline(x, col = warna_hijau, lwd = 2.5)
} else {
cat("QQ plot hourly rate membutuhkan setidaknya dua observasi valid.")
}
}✨ Interpretasi: Pada QQ plot tarif per jam, kemencengan data sebesar 0.76 (menjulur ke kanan (miring positif)). Titik-titik yang menyimpang dari garis, terutama di kedua ujung, menandakan distribusi tarif tidak sepenuhnya normal. Kesimpulan akhir tetap perlu dikonfirmasi dengan uji Shapiro-Wilk.
if ("years_of_experience_clean" %in% names(data_clean)) {
x <- data_clean$years_of_experience_clean
x <- x[is.finite(x)]
if (length(x) >= 2) {
qqnorm(
x,
main = "QQ Plot Pengalaman Kerja",
pch = 19,
col = warna_lilac,
cex = 0.8,
family = "sans"
)
qqline(x, col = warna_hijau, lwd = 2.5)
} else {
cat("QQ plot pengalaman kerja membutuhkan setidaknya dua observasi valid.")
}
}✨ Interpretasi: Pada QQ plot pengalaman kerja, kemencengan data sebesar 0.95 (menjulur ke kanan (miring positif)). Penyimpangan titik dari garis menandakan distribusi pengalaman kerja tidak sepenuhnya normal. Perlu diingat bahwa pengalaman kerja berupa angka tahun sehingga bentuk titik dapat tampak bertingkat.
if (all(c("years_of_experience_clean", "hourly_rate_clean") %in% names(data_clean))) {
ggplot(
data_clean,
aes(x = years_of_experience_clean, y = hourly_rate_clean)
) +
geom_point(shape = 21, fill = warna_biru, color = "white", stroke = 0.8, alpha = 0.9, size = 3) +
geom_smooth(
method = "lm",
formula = y ~ x,
se = TRUE,
color = warna_hijau,
fill = warna_hijaumuda,
linewidth = 1.1
) +
labs(
title = "Pengalaman Kerja dan Tarif per Jam",
subtitle = "Garis menunjukkan kecenderungan linear, bukan bukti sebab-akibat",
x = "Pengalaman kerja (tahun)",
y = "Hourly rate (USD)"
) +
tema_laporan
}Untuk mengukur kekuatan hubungan secara numerik, dihitung koefisien korelasi Pearson dan koefisien kemiringan garis regresi sederhana.
uji_kor <- cor.test(
data_clean$years_of_experience_clean,
data_clean$hourly_rate_clean
)
print(uji_kor)##
## Pearson's product-moment correlation
##
## data: data_clean$years_of_experience_clean and data_clean$hourly_rate_clean
## t = 2.4916, df = 998, p-value = 0.01288
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.01671316 0.13993579
## sample estimates:
## cor
## 0.07862478
model_sederhana <- lm(hourly_rate_clean ~ years_of_experience_clean,
data = data_clean)
print(round(coef(model_sederhana), 4))## (Intercept) years_of_experience_clean
## 48.8369 0.2186
✨ Interpretasi: Koefisien korelasi Pearson antara pengalaman kerja dan tarif per jam adalah 0.079 dengan nilai p 0.0129. Hubungan ini tergolong sangat lemah dan berarah positif, serta signifikan secara statistik pada taraf 5%. Garis regresi menunjukkan perubahan tarif sekitar 0.219 USD per jam untuk setiap tambahan satu tahun pengalaman, dengan R-kuadrat 0.006 (artinya 0.6% variasi tarif terjelaskan oleh pengalaman). Hasil ini menunjukkan asosiasi, bukan hubungan sebab-akibat.
Transformasi berguna untuk menyamakan skala antarvariabel sehingga lebih mudah dibandingkan. Jenis transformasi dipilih sesuai kebutuhan analisis, karena tidak ada satu transformasi yang selalu terbaik untuk semua data.
variabel_transformasi <- intersect(
c("years_of_experience_clean", "hourly_rate_clean"),
names(data_clean)
)
ringkasan_rentang <- lapply(variabel_transformasi, function(nama) {
x <- data_clean[[nama]]
data.frame(
Variabel = nama,
Minimum = if (all(is.na(x))) NA_real_ else min(x, na.rm = TRUE),
Maksimum = if (all(is.na(x))) NA_real_ else max(x, na.rm = TRUE),
Mean = if (all(is.na(x))) NA_real_ else mean(x, na.rm = TRUE),
SD = if (sum(!is.na(x)) < 2) NA_real_ else sd(x, na.rm = TRUE)
)
})
if (length(ringkasan_rentang) > 0) {
knitr::kable(do.call(rbind, ringkasan_rentang), digits = 3,
caption = "Rentang dan ringkasan variabel sebelum transformasi")
}| Variabel | Minimum | Maksimum | Mean | SD |
|---|---|---|---|---|
| years_of_experience_clean | 0 | 41 | 11.221 | 9.444 |
| hourly_rate_clean | 20 | 100 | 51.290 | 26.260 |
✨ Interpretasi: Pengalaman kerja berada pada skala 0.0 sampai 41.0 tahun, sedangkan tarif per jam berada pada skala 20.0 sampai 100.0 USD. Perbedaan skala yang besar ini membuat kedua variabel sulit dibandingkan langsung, sehingga transformasi seperti Min-Max dan Z-Score diperlukan.
Min-Max memetakan nilai ke skala 0 sampai 1 (selama nilainya bervariasi). Z-Score menggeser data sehingga rata-ratanya 0 dan simpangan bakunya 1 (selama simpangan baku tidak nol).
data_final <- data_clean
if ("years_of_experience_clean" %in% names(data_final)) {
data_final$exp_minmax <- min_max(data_final$years_of_experience_clean)
data_final$exp_z <- z_score(data_final$years_of_experience_clean)
}
if ("hourly_rate_clean" %in% names(data_final)) {
data_final$rate_minmax <- min_max(data_final$hourly_rate_clean)
data_final$rate_z <- z_score(data_final$hourly_rate_clean)
}
ringkasan_z <- data.frame(
Variabel = character(),
Mean_ZScore = numeric(),
SD_ZScore = numeric()
)
if ("exp_z" %in% names(data_final)) {
ringkasan_z <- rbind(
ringkasan_z,
data.frame(
Variabel = "Pengalaman kerja",
Mean_ZScore = mean(data_final$exp_z, na.rm = TRUE),
SD_ZScore = sd(data_final$exp_z, na.rm = TRUE)
)
)
}
if ("rate_z" %in% names(data_final)) {
ringkasan_z <- rbind(
ringkasan_z,
data.frame(
Variabel = "Hourly rate",
Mean_ZScore = mean(data_final$rate_z, na.rm = TRUE),
SD_ZScore = sd(data_final$rate_z, na.rm = TRUE)
)
)
}
knitr::kable(ringkasan_z, digits = 4,
caption = "Verifikasi hasil standardisasi Z-Score")| Variabel | Mean_ZScore | SD_ZScore |
|---|---|---|
| Pengalaman kerja | 0 | 1 |
| Hourly rate | 0 | 1 |
✨ Interpretasi: Hasil verifikasi menunjukkan rata-rata Z-Score pengalaman kerja sebesar -0.0000 dengan simpangan baku 1.0000, dan untuk tarif per jam rata-ratanya 0.0000 dengan simpangan baku 1.0000. Nilai tersebut sesuai harapan (rata-rata 0 dan simpangan baku 1), sehingga standardisasi berjalan benar. Nilai Min-Max pengalaman kerja berada pada rentang 0.00 sampai 1.00.
Rumus yang dipakai adalah \(1/(x+1)^2\). Angka 1 ditambahkan agar tidak terjadi pembagian dengan nol ketika pengalaman bernilai nol. Karena transformasi ini mengubah arti nilai (nilai besar menjadi kecil dan sebaliknya), kolom aslinya tetap disimpan.
if ("years_of_experience_clean" %in% names(data_final)) {
data_final$exp_inv_sq <-
1 / ((data_final$years_of_experience_clean + 1)^2)
p1 <- ggplot(data_final, aes(x = years_of_experience_clean)) +
geom_histogram(bins = 20, fill = warna_lilac,
color = "white", linewidth = 0.4) +
labs(
title = "Pengalaman Kerja Sebelum Transformasi",
x = "Pengalaman kerja (tahun)",
y = "Frekuensi"
) +
tema_laporan
p2 <- ggplot(data_final, aes(x = exp_inv_sq)) +
geom_histogram(bins = 20, fill = warna_pink,
color = "white", linewidth = 0.4) +
labs(
title = "Distribusi Setelah Transformasi Inverse Square",
x = "Nilai inverse square",
y = "Frekuensi"
) +
tema_laporan
print(p1)
print(p2)
}✨ Interpretasi: Setelah transformasi inverse square, nilai berada pada rentang 0.0006 sampai 1.0000. Kemencengan data berubah dari 0.95 (sebelum) menjadi 3.01 (sesudah), sehingga bentuk sebaran sesudah transformasi menjulur ke kanan (miring positif). Karena urutan nilai terbalik (pengalaman besar menghasilkan nilai kecil), hasil transformasi ini harus ditafsirkan hati-hati dan tidak dipakai menggantikan variabel asli.
if ("years_of_experience_clean" %in% names(data_final)) {
evaluasi_transformasi <- data.frame(
Statistik = c("Mean asli", "SD asli", "Minimum Min-Max",
"Maksimum Min-Max", "Mean Z-Score", "SD Z-Score"),
Nilai = c(
mean(data_final$years_of_experience_clean, na.rm = TRUE),
sd(data_final$years_of_experience_clean, na.rm = TRUE),
if (all(is.na(data_final$exp_minmax))) NA_real_
else min(data_final$exp_minmax, na.rm = TRUE),
if (all(is.na(data_final$exp_minmax))) NA_real_
else max(data_final$exp_minmax, na.rm = TRUE),
mean(data_final$exp_z, na.rm = TRUE),
sd(data_final$exp_z, na.rm = TRUE)
)
)
knitr::kable(evaluasi_transformasi, digits = 4,
caption = "Evaluasi transformasi pengalaman kerja")
}| Statistik | Nilai |
|---|---|
| Mean asli | 11.2210 |
| SD asli | 9.4443 |
| Minimum Min-Max | 0.0000 |
| Maksimum Min-Max | 1.0000 |
| Mean Z-Score | 0.0000 |
| SD Z-Score | 1.0000 |
✨ Interpretasi: Data asli berskala rata-rata 11.22 dan simpangan baku 9.44. Min-Max menyempitkan skala menjadi 0 sampai 1 tanpa mengubah bentuk sebaran, sedangkan Z-Score memusatkan data di sekitar 0 dengan simpangan baku 1. Keduanya hanya mengubah skala, bukan bentuk distribusi, sehingga cocok untuk membandingkan variabel dengan satuan berbeda. Sementara itu, inverse square benar-benar mengubah bentuk distribusi dan dipakai bila tujuannya mengurangi kemencengan.
Uji Shapiro-Wilk menguji hipotesis nol bahwa data berasal dari populasi berdistribusi normal. R mensyaratkan jumlah observasi antara 3 sampai 5000 dan data tidak boleh seluruhnya bernilai sama.
uji_shapiro_aman <- function(x, nama) {
x <- x[is.finite(x)]
if (length(x) >= 3 && length(x) <= 5000 && length(unique(x)) > 1) {
hasil <- shapiro.test(x)
cat("\nVariabel:", nama, "\n")
print(hasil)
invisible(hasil)
} else {
cat("\nUji Shapiro-Wilk dilewati untuk", nama,
": dibutuhkan 3-5000 observasi valid dengan variasi nilai.\n")
invisible(NULL)
}
}
sw_rate <- NULL
sw_exp <- NULL
if ("hourly_rate_clean" %in% names(data_final)) {
sw_rate <- uji_shapiro_aman(data_final$hourly_rate_clean, "Hourly rate")
}##
## Variabel: Hourly rate
##
## Shapiro-Wilk normality test
##
## data: x
## W = 0.85064, p-value < 2.2e-16
if ("years_of_experience_clean" %in% names(data_final)) {
sw_exp <- uji_shapiro_aman(data_final$years_of_experience_clean, "Years of experience")
}##
## Variabel: Years of experience
##
## Shapiro-Wilk normality test
##
## data: x
## W = 0.9101, p-value < 2.2e-16
✨ Interpretasi: Uji Shapiro-Wilk pada tarif per jam menghasilkan statistik W = 0.8506 dengan nilai p < 0.001, sehingga hipotesis nol ditolak dan tarif per jam dinyatakan tidak berdistribusi normal pada taraf 5%. Untuk pengalaman kerja, W = 0.9101 dengan nilai p < 0.001, sehingga pengalaman kerja dinyatakan tidak berdistribusi normal. Perlu diingat bahwa pada sampel besar, uji ini sangat sensitif terhadap penyimpangan kecil, sehingga hasilnya sebaiknya dibaca bersama QQ plot dan histogram.
Uji Bartlett dipakai untuk melihat apakah varians antar kelompok sama. Di sini hourly rate dibandingkan antara kelompok gender. Uji ini mudah terpengaruh bila data tidak normal, sehingga hasilnya perlu dibaca bersama pemeriksaan distribusi.
bt <- NULL
if (all(c("hourly_rate_clean", "gender") %in% names(data_final))) {
data_bartlett <- data_final[
!is.na(data_final$hourly_rate_clean) &
!is.na(data_final$gender) &
data_final$gender != "",
, drop = FALSE
]
data_bartlett$gender <- droplevels(factor(data_bartlett$gender))
if (nlevels(data_bartlett$gender) >= 2 &&
all(table(data_bartlett$gender) >= 2)) {
bt <- bartlett.test(hourly_rate_clean ~ gender, data = data_bartlett)
print(bt)
} else {
cat("Uji Bartlett dilewati: diperlukan setidaknya dua kelompok, masing-masing minimal dua observasi.\n")
}
}##
## Bartlett test of homogeneity of variances
##
## data: hourly_rate_clean by gender
## Bartlett's K-squared = 1.4921, df = 1, p-value = 0.2219
✨ Interpretasi: Uji Bartlett menghasilkan statistik K-kuadrat = 1.4921 dengan nilai p 0.2219. Hipotesis nol tidak ditolak, artinya varians tarif per jam relatif sama antar kelompok gender (homogen). Karena uji Bartlett peka terhadap ketidaknormalan data, kesimpulan ini sebaiknya dipertimbangkan bersama hasil uji normalitas sebelumnya.
Pada bagian ini dibentuk satu dataset analisis dari data yang sudah bersih. Sumber datanya hanya satu file, sehingga prosesnya sebatas menyatukan dan memilih kolom, bukan menggabungkan dua dataset yang berbeda.
# Isi kolom analisis utama dari hasil imputasi jika tersedia
if ("years_of_experience_clean" %in% names(data_final)) {
data_final$years_of_experience <- data_final$years_of_experience_clean
}
if ("hourly_rate_clean" %in% names(data_final)) {
data_final[["hourly_rate (USD)"]] <- data_final$hourly_rate_clean
}
if ("rating_clean" %in% names(data_final)) {
data_final$rating <- data_final$rating_clean
}
if ("client_satisfaction_clean" %in% names(data_final)) {
data_final$client_satisfaction <- data_final$client_satisfaction_clean
}
if ("age_clean" %in% names(data_final)) {
data_final$age <- data_final$age_clean
}
kolom_analisis <- c(
"age", "gender", "country", "years_of_experience",
"kategori_pengalaman", "kategori_encode",
"hourly_rate (USD)", "client_satisfaction", "rating", "is_active",
"exp_minmax", "rate_minmax", "exp_z", "rate_z", "exp_inv_sq"
)
kolom_tersedia <- intersect(kolom_analisis, names(data_final))
data_analisis <- data_final[, kolom_tersedia, drop = FALSE]
knitr::kable(
data.frame(
Keterangan = c("Jumlah baris", "Jumlah kolom", "Jumlah duplikat"),
Nilai = c(nrow(data_analisis), ncol(data_analisis),
sum(duplicated(data_analisis)))
),
caption = "Ringkasan dataset analisis akhir"
)| Keterangan | Nilai |
|---|---|
| Jumlah baris | 1000 |
| Jumlah kolom | 15 |
| Jumlah duplikat | 0 |
DT::datatable(
head(data_analisis, 100),
options = list(pageLength = 10, scrollX = TRUE),
rownames = FALSE,
caption = "Pratinjau maksimal 100 baris pertama"
)✨ Interpretasi: Dataset analisis akhir terdiri dari 1000 baris dan 15 kolom, dengan 0 baris duplikat. Kolom analisis utama kini berisi nilai hasil imputasi, ditambah kolom kategori pengalaman, kode ordinal, serta hasil transformasi Min-Max, Z-Score, dan inverse square. Dataset ini siap dipakai untuk analisis lanjutan atau pemodelan.
validasi_akhir <- data.frame(
Variabel = names(data_analisis),
Jumlah_NA = colSums(is.na(data_analisis)),
Persentase_NA = round(colMeans(is.na(data_analisis)) * 100, 2)
)
knitr::kable(
validasi_akhir,
row.names = FALSE,
digits = 2,
caption = "Missing value pada dataset analisis akhir"
)| 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: Pada dataset akhir tersisa 0 sel kosong. Tidak ada nilai kosong yang tersisa, sehingga dataset lengkap untuk seluruh variabel analisis. Jumlah duplikat 0 menegaskan bahwa data final telah bersih dari baris ganda.
File CSV akan tersimpan di working directory R. Jalankan
getwd() untuk mengetahui folder tempat file disimpan.
write.csv(
data_analisis,
"data_analisis_final.csv",
row.names = FALSE,
na = ""
)
cat("Dataset analisis berhasil disimpan sebagai data_analisis_final.csv\n")## Dataset analisis berhasil disimpan sebagai data_analisis_final.csv
## Lokasi penyimpanan: C:/Users/ACER/Downloads
✨ Interpretasi: Dataset akhir dengan 1000 baris dan
15 kolom telah disimpan sebagai data_analisis_final.csv.
Nilai kosong ditulis sebagai sel kosong sehingga file dapat dibuka
kembali di R, Excel, atau perangkat lunak statistik lain tanpa
kehilangan struktur.
Pemeriksaan kualitas data pada Global Freelancers Dataset menemukan sejumlah masalah, yaitu 541 sel kosong, 0 baris duplikat, serta format yang tidak seragam pada gender, tarif per jam, tingkat kepuasan, dan status aktif. Masalah tersebut ditangani melalui penyeragaman kategori, pembuangan simbol mata uang dan tanda persen, konversi ke tipe numerik, penghapusan baris ganda sehingga tersisa 1000 baris unik dari 1000 baris awal, serta imputasi median pada 452 nilai kosong di variabel numerik. Validasi akhir menunjukkan dataset analisis final terdiri dari 1000 baris dan 15 kolom dengan 0 baris duplikat, sehingga data layak dipakai untuk analisis lanjutan.
Dari sisi karakteristik, rata-rata pengalaman kerja freelancer adalah 11.2 tahun (median 9.0 tahun) dengan kategori terbanyak pemula (57.1%), sedangkan rata-rata tarif per jam adalah 51.29 USD. Hasil uji Shapiro-Wilk menunjukkan bahwa tarif per jam tidak berdistribusi normal (p < 0.001) dan pengalaman kerja tidak berdistribusi normal (p < 0.001), sementara uji Bartlett menunjukkan bahwa varians tarif relatif homogen antar gender (p 0.2219). Korelasi Pearson antara pengalaman kerja dan tarif per jam sebesar 0.079 (hubungan sangat lemah dan signifikan pada taraf 5%), dengan pengalaman menjelaskan sekitar 0.6% variasi tarif. Temuan ini hanya menunjukkan asosiasi dan bukan hubungan sebab-akibat, karena tarif juga dapat dipengaruhi faktor lain seperti bidang keahlian, negara, dan rating yang belum dimodelkan dalam analisis ini.
Ahir, U. (2024). Global Freelancers Raw Dataset [Data set]. Kaggle. https://www.kaggle.com/datasets/urvishahir/global-freelancers-raw-dataset
Tukey, J. W. (1993). Exploratory Data Analysis: Past, Present and Future. Princeton University.
Velleman, P. F., & Hoaglin, D. C. (2004). Applications, Basics, and Computing of Exploratory Data Analysis. Duxbury Press.