Perkembangan teknologi digital memberikan kesempatan bagi masyarakat untuk bekerja secara lepas (freelance) tanpa harus terikat pada satu lokasi kerja. Dalam pasar kerja tersebut, freelancer memiliki karakteristik yang beragam, mulai dari usia, keahlian, pengalaman, hingga tarif jasa yang ditawarkan. Lama pengalaman kerja (years of experience) menjadi salah satu faktor yang menarik untuk diteliti karena diduga berkaitan dengan tarif per jam (hourly rate).
Analisis ini menggunakan Global Freelancers Dataset yang bersumber dari Kaggle. Dataset tersebut terdiri atas 1.000 observasi freelancer dan 12 variabel yang memuat informasi mengenai karakteristik individu, pengalaman kerja, tarif jasa, rating, kepuasan klien, serta status keaktifan.
Sebelum dianalisis, data perlu diperiksa untuk mengetahui kemungkinan adanya nilai yang hilang (missing value), ketidakkonsistenan kategori, kesalahan format, duplikasi, dan nilai yang berpotensi tidak wajar. Proses pembersihan dan pengolahan data dilakukan agar informasi yang digunakan lebih konsisten. Selanjutnya, analisis diarahkan untuk menggambarkan karakteristik pengalaman kerja dan mengkaji hubungannya dengan tarif per jam freelancer.
Pilih file Excel melalui jendela pemilihan file. Cara ini membuat dokumen tidak bergantung pada alamat folder khusus di satu komputer.
file_excel <- file.choose()
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
Missing value adalah nilai yang tidak tersedia pada suatu observasi. Persentase dihitung terhadap jumlah baris 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,
caption = "Jumlah dan persentase missing value sebelum cleaning",
digits = 2
)| Variabel | Jumlah_NA | Persentase_NA | |
|---|---|---|---|
| freelancer_ID | freelancer_ID | 0 | 0.0 |
| name | name | 0 | 0.0 |
| gender | gender | 0 | 0.0 |
| age | age | 30 | 3.0 |
| country | country | 0 | 0.0 |
| language | language | 0 | 0.0 |
| primary_skill | primary_skill | 0 | 0.0 |
| years_of_experience | years_of_experience | 51 | 5.1 |
| hourly_rate (USD) | hourly_rate (USD) | 94 | 9.4 |
| rating | rating | 101 | 10.1 |
| is_active | is_active | 89 | 8.9 |
| client_satisfaction | client_satisfaction | 176 | 17.6 |
## Total missing value: 541
Nilai kategori diseragamkan agar variasi penulisan seperti
f, female, m, dan
male tidak dianggap sebagai kategori berbeda.
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
Simbol dolar, teks USD, koma pemisah ribuan, dan spasi dihapus sebelum kolom diubah menjadi numerik.
if ("hourly_rate (USD)" %in% names(ADE)) {
ADE[["hourly_rate (USD)"]] <- bersihkan_numerik(
ADE[["hourly_rate (USD)"]],
mata_uang = TRUE
)
summary(ADE[["hourly_rate (USD)"]])
str(ADE[["hourly_rate (USD)"]])
}## num [1:1000] 100 100 50 40 30 30 75 40 NA 50 ...
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
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
Status yang dikenali diubah menjadi Active atau
Inactive. Nilai kosong atau tidak dikenali dicatat sebagai
Unknown, bukan 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
Duplikasi dihapus setelah pembersihan format agar baris yang sama tidak tetap terhitung berbeda hanya karena variasi format.
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 |
Pemeriksaan ini menandai nilai yang patut diperiksa. Nilai tidak otomatis dihapus karena perlu dibandingkan dengan sumber data atau definisi variabel.
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,
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 |
Imputasi median dilakukan untuk variabel numerik terpilih. Kolom asli
tetap dipertahankan, sedangkan hasil imputasi disimpan pada kolom dengan
akhiran _clean agar perubahan dapat 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
)| Variabel | Jumlah_NA | Persentase_NA | |
|---|---|---|---|
| freelancer_ID | freelancer_ID | 0 | 0.0 |
| name | name | 0 | 0.0 |
| gender | gender | 0 | 0.0 |
| age | age | 30 | 3.0 |
| country | country | 0 | 0.0 |
| language | language | 0 | 0.0 |
| primary_skill | primary_skill | 0 | 0.0 |
| years_of_experience | years_of_experience | 51 | 5.1 |
| hourly_rate (USD) | hourly_rate (USD) | 94 | 9.4 |
| rating | rating | 101 | 10.1 |
| is_active | is_active | 0 | 0.0 |
| client_satisfaction | client_satisfaction | 176 | 17.6 |
| years_of_experience_clean | years_of_experience_clean | 0 | 0.0 |
| hourly_rate_clean | hourly_rate_clean | 0 | 0.0 |
| rating_clean | rating_clean | 0 | 0.0 |
| client_satisfaction_clean | client_satisfaction_clean | 0 | 0.0 |
| age_clean | age_clean | 0 | 0.0 |
Catatan interpretasi: jika sebuah kolom seluruh
nilainya kosong, median tidak dapat dihitung. Kolom tersebut akan tetap
berisi NA dan perlu ditinjau secara khusus.
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 |
Variabel pengalaman kerja dikelompokkan ke empat kategori. Pengkodean kategori bersifat ordinal, karena 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,
caption = "Frekuensi kategori dan kode pengalaman kerja"
)
print(table(data_clean$kategori_pengalaman, useNA = "ifany"))
}##
## Pemula Menengah Jago Master
## 571 252 122 55
if ("years_of_experience_clean" %in% names(data_clean)) {
ggplot(data_clean, aes(x = years_of_experience_clean)) +
geom_histogram(
bins = 20,
fill = warna_utama,
color = "white",
linewidth = 0.5,
alpha = 0.9
) +
labs(
title = "Distribusi Pengalaman Kerja Freelancer",
subtitle = "Frekuensi jumlah tahun pengalaman",
x = "Pengalaman kerja (tahun)",
y = "Frekuensi"
) +
tema_laporan
}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" = "#58A6FF",
"Menengah" = "#00A6A6",
"Jago" = "#F28E2B",
"Master" = "#8064A2"
), na.value = "#B8C1CC") +
labs(
title = "Komposisi Freelancer Menurut Pengalaman",
x = "Kategori pengalaman",
y = "Jumlah freelancer"
) +
tema_laporan
}if ("hourly_rate_clean" %in% names(data_clean)) {
ggplot(data_clean, aes(x = hourly_rate_clean)) +
geom_histogram(
bins = 25,
fill = warna_kedua,
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
}QQ plot membandingkan kuantil data dengan kuantil teoretis distribusi normal. Titik yang mengikuti garis secara umum lebih konsisten dengan distribusi normal, tetapi keputusan sebaiknya tidak hanya didasarkan pada grafik.
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_utama,
cex = 0.8,
family = "serif"
)
qqline(x, col = warna_merah, 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 = warna_kedua,
cex = 0.8,
family = "serif"
)
qqline(x, col = warna_merah, lwd = 2)
} else {
cat("QQ plot pengalaman kerja membutuhkan setidaknya dua observasi valid.")
}
}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(color = warna_utama, alpha = 0.65, size = 2.2) +
geom_smooth(
method = "lm",
formula = y ~ x,
se = TRUE,
color = warna_aksen,
fill = "#F8D9B4",
linewidth = 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
}Transformasi dapat membantu membandingkan skala variabel. Pilih transformasi sesuai kebutuhan analisis, dan jangan menganggap satu transformasi selalu paling baik 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 |
Min-Max mengubah skala menjadi 0 sampai 1 (jika terdapat variasi nilai). 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,
caption = "Verifikasi hasil standardisasi Z-Score")| Variabel | Mean_ZScore | SD_ZScore |
|---|---|---|
| Pengalaman kerja | 0 | 1 |
| Hourly rate | 0 | 1 |
Transformasi yang digunakan adalah \(1/(x+1)^2\). Penambahan 1 mencegah pembagian dengan nol saat pengalaman bernilai nol. Transformasi ini mengubah interpretasi 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)
ggplot(data_final, aes(x = years_of_experience_clean)) +
geom_histogram(bins = 20, fill = warna_ungu,
color = "white", linewidth = 0.4) +
labs(
title = "Pengalaman Kerja Sebelum Transformasi",
x = "Pengalaman kerja (tahun)",
y = "Frekuensi"
) +
tema_laporan
ggplot(data_final, aes(x = exp_inv_sq)) +
geom_histogram(bins = 20, fill = warna_aksen,
color = "white", linewidth = 0.4) +
labs(
title = "Distribusi Setelah Transformasi Inverse Square",
x = "Nilai inverse square",
y = "Frekuensi"
) +
tema_laporan
}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 |
Uji Shapiro-Wilk digunakan untuk memeriksa hipotesis nol bahwa sampel berasal dari distribusi normal. Nilai p yang kecil memberikan bukti terhadap normalitas. Untuk uji ini, R mensyaratkan 3 sampai 5000 observasi dan data 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
Uji Bartlett memeriksa apakah varians beberapa kelompok sama. Di sini hourly rate dibandingkan berdasarkan gender. Uji ini sensitif terhadap penyimpangan normalitas, sehingga interpretasikan bersama hasil pemeriksaan distribusi.
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))
} 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
Bagian ini menyusun satu dataset analisis dari dataset yang telah dibersihkan. Karena hanya ada satu file sumber pada alur ini, prosesnya merupakan konsolidasi/pemilihan kolom, bukan penggabungan dua dataset 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 |
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,
caption = "Missing value pada dataset analisis akhir"
)| Variabel | Jumlah_NA | Persentase_NA | |
|---|---|---|---|
| age | age | 0 | 0 |
| gender | gender | 0 | 0 |
| country | country | 0 | 0 |
| years_of_experience | years_of_experience | 0 | 0 |
| kategori_pengalaman | kategori_pengalaman | 0 | 0 |
| kategori_encode | kategori_encode | 0 | 0 |
| hourly_rate (USD) | hourly_rate (USD) | 0 | 0 |
| client_satisfaction | client_satisfaction | 0 | 0 |
| rating | rating | 0 | 0 |
| is_active | is_active | 0 | 0 |
| exp_minmax | exp_minmax | 0 | 0 |
| rate_minmax | rate_minmax | 0 | 0 |
| exp_z | exp_z | 0 | 0 |
| rate_z | rate_z | 0 | 0 |
| exp_inv_sq | 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
CSV akan disimpan pada working directory R. Gunakan
getwd() untuk melihat lokasi folder penyimpanan.
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/ADVAN/Downloads
Tahapan yang dilakukan meliputi pemeriksaan struktur dan missing value, penyeragaman format, penghapusan duplikasi, pemeriksaan nilai yang tidak masuk akal, imputasi median untuk variabel numerik terpilih, encoding pengalaman kerja, visualisasi, transformasi numerik, uji asumsi, validasi, dan ekspor data.
Kesimpulan substantif mengenai karakteristik freelancer harus ditulis setelah melihat hasil aktual dari tabel, grafik, dan uji statistik. Jangan menyimpulkan normalitas, hubungan, atau perbedaan kelompok sebelum memeriksa hasil yang muncul saat dokumen dijalankan.