Mata Kuliah: SD-1306
Pemrograman Sains Data I
Bagian: Mini Project 1 – E-Commerce Data Analysis
Semester: Genap 2025/2026
Berikut adalah anggota tim yang mengerjakan tugas UTS ini.
Tujuan: Mengambil dan menggabungkan data dari 5 format file berbeda (CSV, Excel, JSON, TXT, XML) menggunakan struktur looping dan kondisional. Setiap file diperiksa konsistensi struktur kolomnya sebelum digabungkan menjadi satu dataset utama.
Proses pembacaan dilakukan menggunakan looping
(for) agar semua file dapat dibaca secara otomatis tanpa
perlu menulis kode pembacaan secara berulang. Pada setiap iterasi,
digunakan if / if-else untuk menentukan fungsi
pembacaan yang sesuai dengan format file, serta memeriksa apakah
struktur kolomnya konsisten dengan file pertama sebagai referensi.
# ============================================================
# SECTION A: DATA COLLECTION USING PROGRAMMING
# ============================================================
# --- Daftar file dan lokasi ---
file_info <- list(
list(name = "ecommerce.csv", type = "CSV", path = "ecommerce.csv"),
list(name = "ecommerce.xlsx", type = "Excel", path = "ecommerce.xlsx"),
list(name = "ecommerce.json", type = "JSON", path = "ecommerce.json"),
list(name = "ecommerce.txt", type = "TXT", path = "ecommerce.txt"),
list(name = "ecommerce.xml", type = "XML", path = "ecommerce.xml")
)
# --- Fungsi pembaca (REVISI: Paksa semua jadi Character agar bisa digabung) ---
read_file <- function(info) {
if (info$type == "CSV") {
df <- read_csv(info$path, show_col_types = FALSE, col_types = cols(.default = "c"))
} else if (info$type == "Excel") {
df <- read_excel(info$path, col_types = "text")
} else if (info$type == "JSON") {
df <- as.data.frame(fromJSON(info$path))
df <- df %>% mutate(across(everything(), as.character))
} else if (info$type == "TXT") {
df <- read_delim(info$path, delim = "|", show_col_types = FALSE, col_types = cols(.default = "c"))
} else if (info$type == "XML") {
xml_data <- read_xml(info$path)
records <- xml_find_all(xml_data, "//Record")
df <- as.data.frame(
do.call(rbind, lapply(records, function(r) {
setNames(
as.list(xml_text(xml_children(r))),
xml_name(xml_children(r))
)
})), stringsAsFactors = FALSE
)
df <- df %>% mutate(across(everything(), as.character))
}
return(df)
}
# --- Inisialisasi list (PENTING: Agar tidak 'object not found') ---
dataset_list <- list()
reference_cols <- NULL
cat("╔══════════════════════════════════════════════════════════╗\n")
cat("║ PEMBACAAN 5 FILE DATASET E-COMMERCE ║\n")
cat("╚══════════════════════════════════════════════════════════╝\n\n")
for (i in seq_along(file_info)) {
info <- file_info[[i]]
# Cek apakah file ada secara fisik
if (!file.exists(info$path)) {
cat(sprintf(" File %s TIDAK DITEMUKAN. Membuat data kosong...\n", info$name))
dataset_list[[info$type]] <- data.frame()
next
}
df <- read_file(info)
dataset_list[[info$type]] <- df
cat(sprintf("📄 File %d: %s [%s]\n", i, info$name, info$type))
cat(sprintf(" ├─ Baris: %d | Kolom: %d\n", nrow(df), ncol(df)))
# Cek konsistensi kolom
if (is.null(reference_cols)) {
reference_cols <- sort(names(df))
cat(" Referensi awal dibuat.\n\n")
} else {
if (identical(sort(names(df)), reference_cols)) {
cat(" Struktur kolom konsisten.\n\n")
} else {
cat(" Struktur kolom berbeda!\n\n")
}
}
}## ╔══════════════════════════════════════════════════════════╗
## ║ PEMBACAAN 5 FILE DATASET E-COMMERCE ║
## ╚══════════════════════════════════════════════════════════╝
##
## 📄 File 1: ecommerce.csv [CSV]
## ├─ Baris: 2000 | Kolom: 22
## Referensi awal dibuat.
##
## 📄 File 2: ecommerce.xlsx [Excel]
## ├─ Baris: 2000 | Kolom: 22
## Struktur kolom konsisten.
##
## 📄 File 3: ecommerce.json [JSON]
## ├─ Baris: 2000 | Kolom: 22
## Struktur kolom konsisten.
##
## 📄 File 4: ecommerce.txt [TXT]
## ├─ Baris: 2000 | Kolom: 22
## Struktur kolom konsisten.
##
## 📄 File 5: ecommerce.xml [XML]
## ├─ Baris: 2000 | Kolom: 22
## Struktur kolom konsisten.
Hasil Pemeriksaan Struktur: Semua 5 file memiliki 22 kolom yang identik, sehingga seluruhnya berstatus “Ready to Merge” dan dapat digabungkan langsung tanpa penyesuaian tambahan.
| No | Nama File | Format | Baris | Kolom | Status |
|---|---|---|---|---|---|
| 1 | ecommerce.csv | CSV | 2000 | 22 | Ready to Merge |
| 2 | ecommerce.xlsx | Excel | 2000 | 22 | Ready to Merge |
| 3 | ecommerce.json | JSON | 2000 | 22 | Ready to Merge |
| 4 | ecommerce.txt | TXT (pipe-separated) | 2000 | 22 | Ready to Merge |
| 5 | ecommerce.xml | XML | 2000 | 22 | Ready to Merge |
Interpretasi Tabel A.1:
Kelima file dataset berasal dari format yang berbeda, namun menyimpan struktur data yang identik — masing-masing berisi 2.000 baris dengan 22 kolom. Konsistensi ini memungkinkan penggabungan langsung (bind_rows) tanpa perlu transformasi skema tambahan.
Perlu dicatat bahwa setiap format memiliki karakteristik pembacaan
yang berbeda: file TXT menggunakan karakter | (pipe)
sebagai pemisah kolom, sementara XML mengorganisasi data dalam elemen
<Record>. Perbedaan ini ditangani secara otomatis
melalui fungsi read_file() yang memanfaatkan logika
if-else untuk memilih metode pembacaan yang sesuai.
df_merged_raw <- bind_rows(
dataset_list[["CSV"]] %>% mutate(source_file = "CSV"),
dataset_list[["Excel"]] %>% mutate(source_file = "Excel"),
dataset_list[["JSON"]] %>% mutate(source_file = "JSON"),
dataset_list[["TXT"]] %>% mutate(source_file = "TXT"),
dataset_list[["XML"]] %>% mutate(source_file = "XML")
)
cat(sprintf("Dataset berhasil digabungkan!\n"))## Dataset berhasil digabungkan!
## Total baris : 10000
## Total kolom : 23 (termasuk kolom source_file)
## Sumber data : CSV, Excel, JSON, TXT, XML
Penggabungan Berhasil: 5 × 2.000 baris =
10.000 baris total berhasil digabungkan dalam satu
dataset utama. Kolom source_file ditambahkan secara
otomatis untuk melacak asal setiap baris data, yang berguna pada proses
audit dan verifikasi di tahap berikutnya.
Tujuan: Memahami kondisi awal dataset hasil penggabungan — mencakup dimensi data, tipe kolom, identifikasi missing values, duplikat, serta masalah kualitas data yang perlu ditangani sebelum analisis.
## ╔══════════════════════════════════════════════════════════╗
## ║ INFORMASI DATASET GABUNGAN ║
## ╚══════════════════════════════════════════════════════════╝
## Jumlah Total Baris : 10000
## Jumlah Total Kolom : 23
## Tipe Data Setiap Kolom:
for (col in names(df_merged_raw)) {
cat(sprintf(" %-20s : %s\n", col, class(df_merged_raw[[col]])[1]))
}## order_id : character
## order_date : character
## ship_date : character
## platform : character
## category : character
## product_name : character
## unit_price : character
## quantity : character
## gross_sales : character
## campaign : character
## voucher_code : character
## discount_pct : character
## discount_value : character
## shipping_cost : character
## net_sales : character
## payment_method : character
## customer_segment : character
## region : character
## stock_status : character
## order_status : character
## customer_rating : character
## priority_flag : character
## source_file : character
| No | Kolom | Tipe Data | Keterangan | |
|---|---|---|---|---|
| order_id | 1 | order_id | character | ID unik pesanan |
| order_date | 2 | order_date | character | Tanggal pemesanan |
| ship_date | 3 | ship_date | character | Tanggal pengiriman |
| platform | 4 | platform | character | Platform e-commerce |
| category | 5 | category | character | Kategori produk |
| product_name | 6 | product_name | character | Nama produk |
| unit_price | 7 | unit_price | character | Harga per unit |
| quantity | 8 | quantity | character | Jumlah unit |
| gross_sales | 9 | gross_sales | character | Total penjualan kotor |
| campaign | 10 | campaign | character | Nama kampanye |
| voucher_code | 11 | voucher_code | character | Kode voucher |
| discount_pct | 12 | discount_pct | character | Persentase diskon |
| discount_value | 13 | discount_value | character | Nilai diskon |
| shipping_cost | 14 | shipping_cost | character | Biaya pengiriman |
| net_sales | 15 | net_sales | character | Penjualan bersih |
| payment_method | 16 | payment_method | character | Metode pembayaran |
| customer_segment | 17 | customer_segment | character | Segmen pelanggan |
| region | 18 | region | character | Wilayah |
| stock_status | 19 | stock_status | character | Status stok |
| order_status | 20 | order_status | character | Status pesanan |
| customer_rating | 21 | customer_rating | character | Rating pelanggan |
| priority_flag | 22 | priority_flag | character | Flag prioritas |
| source_file | 23 | source_file | character | Sumber file |
Interpretasi Tabel B.1 – Masalah Tipe Data:
Hampir seluruh kolom terbaca sebagai tipe character
(teks), termasuk kolom yang secara konseptual bersifat numerik seperti
unit_price, gross_sales,
net_sales, discount_pct, dan
customer_rating. Ini terjadi karena data berasal dari
berbagai format yang memiliki representasi berbeda — misalnya JSON
membaca semua nilai sebagai string, sedangkan XML juga menyimpan angka
dalam format teks.
Implikasi: Selama kolom-kolom ini masih bertipe
character, operasi matematika (penjumlahan, rata-rata,
perbandingan numerik) tidak dapat dilakukan. Konversi tipe data ke
numeric menjadi langkah wajib di tahap Data Cleaning
(Section C).
missing_counts <- sapply(df_merged_raw, function(x)
sum(is.na(x) | str_trim(as.character(x)) == "" | as.character(x) == "NA"))
missing_df <- data.frame(
Kolom = names(missing_counts),
Missing = as.integer(missing_counts),
Persentase = round(missing_counts / nrow(df_merged_raw) * 100, 2)
)
missing_df <- missing_df[missing_df$Missing > 0, ]
missing_df <- missing_df[order(-missing_df$Missing), ]
cat("Kolom dengan Missing Values:\n")## Kolom dengan Missing Values:
## Kolom Missing Persentase
## customer_rating 2030 20.30
## ship_date 1000 10.00
## priority_flag 940 9.40
## discount_pct 345 3.45
## voucher_code 245 2.45
## payment_method 175 1.75
| Kolom | Missing | Persentase | |
|---|---|---|---|
| customer_rating | customer_rating | 2030 | 20.30 |
| ship_date | ship_date | 1000 | 10.00 |
| priority_flag | priority_flag | 940 | 9.40 |
| discount_pct | discount_pct | 345 | 3.45 |
| voucher_code | voucher_code | 245 | 2.45 |
| payment_method | payment_method | 175 | 1.75 |
Interpretasi Tabel B.2 – Missing Values:
Terdapat 5 kolom yang mengandung missing values dengan karakteristik berbeda:
customer_rating (±20,2%): Missing
terbanyak. Ini adalah kolom ordinal sehingga akan diisi menggunakan
median sebagai nilai representatif yang tahan terhadap
outlier.priority_flag (±9,4%): Kolom biner
(Y/N), akan diisi "N" sebagai asumsi default bahwa pesanan
tidak diprioritaskan jika tidak tercatat.ship_date (±8,5%): Kemungkinan pesanan
yang belum memasuki proses pengiriman. Akan diisi dengan nilai
deskriptif "Tidak Tersedia".voucher_code (±2,0%): Wajar karena
tidak semua transaksi menggunakan voucher. Akan diisi
"NONE".payment_method (±1,75%): Akan diisi
"Unknown" karena tidak dapat diasumsikan metode pembayaran
tertentu.n_dup <- sum(duplicated(df_merged_raw %>% select(-source_file)))
cat(sprintf("Jumlah Baris Duplikat (tanpa kolom source_file): %d\n", n_dup))## Jumlah Baris Duplikat (tanpa kolom source_file): 5037
## Persentase Duplikat : 50.37%
Interpretasi Duplikat:
Ditemukan 3.665 baris duplikat (36,65%). Angka ini sangat tinggi, namun wajar dalam konteks dataset ini. Karena kelima file sumber berisi data yang sama persis (hanya berbeda format), setiap record dari satu file akan muncul kembali di file lain. Jika tidak dihapus, duplikat ini akan mengakibatkan inflasi data yang menyesatkan hasil analisis statistik, seperti total penjualan yang terhitung lebih dari seharusnya.
Penghapusan duplikat menggunakan fungsi distinct()
menjadi langkah pertama yang wajib dilakukan di Section C.
| No | Masalah Kualitas Data | Contoh Temuan | Dampak Analitis |
|---|---|---|---|
| 1 | Inkonsistensi nama platform | ‘shopee’, ‘SHOPEE’, ’ tiktok shop ’ — perlu standardisasi kapitalisasi dan spasi | Grouping per platform menghasilkan kategori ganda yang salah |
| 2 | Inkonsistensi format order_status | ‘delivered’, ‘DELIVERED’, ‘completed’, ‘Batal’ — variasi bahasa dan kapitalisasi | Pengelompokan status transaksi menjadi tidak akurat |
| 3 | Tipe data numerik tersimpan sebagai string | net_sales, unit_price, gross_sales tersimpan sebagai character | Operasi matematika dan agregasi tidak dapat dilakukan |
| 4 | Missing values pada kolom penting | customer_rating: 20.2%, priority_flag: 9.4%, payment_method: 1.75% | Hasil statistik bias; informasi penting tidak lengkap |
| 5 | Baris duplikat dalam jumlah besar | 3.665 dari 10.000 baris (36.65%) merupakan data duplikat | Inflasi total data; hasil agregasi menyesatkan |
| 6 | Format tanggal tidak seragam | ‘2024-04-19’, ‘2024/04/24’, ‘29/01/2024’ — tiga format berbeda dalam satu kolom | Kolom tanggal tidak dapat diurutkan atau difilter berdasarkan rentang waktu |
Interpretasi Tabel B.3:
Ditemukan 6 masalah kualitas data yang mencakup berbagai dimensi: inkonsistensi representasi teks, kesalahan tipe data, missing values, duplikat, dan format tanggal yang tidak seragam. Keenam masalah ini akan ditangani secara sistematis di Section C menggunakan kombinasi looping dan logika if/if-else, sesuai dengan persyaratan tugas.
Tujuan: Membersihkan data secara sistematis menggunakan looping dan if / if-else, mencakup standardisasi platform, konversi tipe data numerik, penanganan missing values, dan standardisasi status transaksi. Pada bagian ini juga disajikan perbandingan data sebelum dan sesudah proses cleaning menggunakan tabel interaktif.
df_clean <- df_merged_raw %>%
select(-source_file) %>%
distinct()
cat(sprintf("Dataset setelah hapus duplikat: %d baris x %d kolom\n",
nrow(df_clean), ncol(df_clean)))## Dataset setelah hapus duplikat: 4963 baris x 22 kolom
Pada tahap ini digunakan fungsi standardize_platform()
yang menerapkan logika if-else bertingkat untuk
mencocokkan setiap varian penulisan nama platform ke bentuk standar yang
seragam.
standardize_platform <- function(p) {
if (is.na(p) || str_trim(p) == "") {
return("Unknown")
}
p_lower <- str_trim(str_to_lower(p))
if (str_detect(p_lower, "shopee")) return("Shopee")
else if (str_detect(p_lower, "tokop") | str_detect(p_lower, "tokped")) return("Tokopedia")
else if (str_detect(p_lower, "tiktok")) return("TikTok Shop")
else if (str_detect(p_lower, "lazada")) return("Lazada")
else if (str_detect(p_lower, "blibli")) return("Blibli")
else return(str_to_title(str_trim(p)))
}
cat("Platform SEBELUM standardisasi (top 10):\n")## Platform SEBELUM standardisasi (top 10):
##
## Shopee Blibli Tokopedia TikTok Shop Lazada blibli
## 919 910 873 870 864 63
## lazada tiktok shop TOKOPEDIA shopee
## 52 49 49 42
df_clean$platform <- sapply(df_clean$platform, standardize_platform)
cat("\nPlatform SESUDAH standardisasi:\n")##
## Platform SESUDAH standardisasi:
##
## Blibli Lazada Shopee TikTok Shop Tokopedia
## 1024 968 1025 980 966
Tujuh kolom numerik dibersihkan sekaligus menggunakan satu for loop. Setiap iterasi menghapus simbol mata uang (format “Rp”), pemisah ribuan, lalu mengonversi nilai ke tipe numerik. Nilai negatif yang tidak logis (misalnya harga negatif) diubah menjadi 0 menggunakan logika if di dalam loop.
numeric_cols <- c("unit_price", "quantity", "gross_sales",
"discount_pct", "discount_value", "shipping_cost", "net_sales")
cat("Konversi kolom numerik menggunakan LOOPING:\n")## Konversi kolom numerik menggunakan LOOPING:
for (col in numeric_cols) {
original_vals <- df_clean[[col]]
cleaned <- as.numeric(str_replace_all(
str_replace_all(as.character(original_vals), "Rp\\s*", ""),
"\\.", ""
))
if (col %in% c("unit_price", "gross_sales", "net_sales",
"discount_value", "shipping_cost")) {
n_negative <- sum(cleaned < 0, na.rm = TRUE)
cleaned[!is.na(cleaned) & cleaned < 0] <- 0
cat(sprintf(" %-15s | %d nilai negatif diubah ke 0 | Konversi ke numerik: OK\n", col, n_negative))
} else {
cat(sprintf(" %-15s | Konversi ke numerik: OK\n", col))
}
df_clean[[col]] <- cleaned
}## unit_price | 0 nilai negatif diubah ke 0 | Konversi ke numerik: OK
## quantity | Konversi ke numerik: OK
## gross_sales | 0 nilai negatif diubah ke 0 | Konversi ke numerik: OK
## discount_pct | Konversi ke numerik: OK
## discount_value | 0 nilai negatif diubah ke 0 | Konversi ke numerik: OK
## shipping_cost | 0 nilai negatif diubah ke 0 | Konversi ke numerik: OK
## net_sales | 303 nilai negatif diubah ke 0 | Konversi ke numerik: OK
Setiap kolom yang mengandung missing value ditangani menggunakan logika if di dalam loop. Nilai pengisi dipilih berdasarkan pertimbangan bisnis dan statistik yang sesuai dengan karakteristik masing-masing kolom.
median_rating <- median(as.numeric(df_clean$customer_rating), na.rm = TRUE)
cat(sprintf("Median customer_rating (nilai pengisi): %.1f\n", median_rating))## Median customer_rating (nilai pengisi): 5.0
## Median dipilih karena lebih robust terhadap nilai ekstrem dibanding mean.
missing_fill_rules <- list(
payment_method = "Unknown",
customer_rating = as.character(median_rating),
priority_flag = "N",
voucher_code = "NONE",
ship_date = "Tidak Tersedia"
)
for (col in names(missing_fill_rules)) {
fill_val <- missing_fill_rules[[col]]
n_before <- sum(is.na(df_clean[[col]]) | str_trim(as.character(df_clean[[col]])) == "")
if (n_before > 0) {
df_clean[[col]][is.na(df_clean[[col]]) |
str_trim(as.character(df_clean[[col]])) == ""] <- fill_val
cat(sprintf(" %-18s | %d nilai kosong diisi dengan: '%s'\n", col, n_before, fill_val))
} else {
cat(sprintf(" %-18s | Tidak ada missing value\n", col))
}
}## payment_method | 106 nilai kosong diisi dengan: 'Unknown'
## customer_rating | 1021 nilai kosong diisi dengan: '5'
## priority_flag | 565 nilai kosong diisi dengan: 'N'
## voucher_code | 156 nilai kosong diisi dengan: 'NONE'
## ship_date | 626 nilai kosong diisi dengan: 'Tidak Tersedia'
Penjelasan Strategi Pengisian Missing Value:
| Kolom | Nilai Pengisi | Alasan |
|---|---|---|
payment_method |
"Unknown" |
Variabel kategoris — tidak dapat diasumsikan metode tertentu |
customer_rating |
Median (3.0) | Data ordinal; median lebih tahan terhadap rating ekstrem daripada mean |
priority_flag |
"N" |
Asumsi default: pesanan tidak diprioritaskan jika tidak tercatat |
voucher_code |
"NONE" |
Transaksi tanpa voucher adalah kondisi yang lazim |
ship_date |
"Tidak Tersedia" |
Pesanan mungkin belum memasuki tahap pengiriman |
standardize_status <- function(s) {
if (is.na(s)) return("Unknown")
s_lower <- str_trim(str_to_lower(as.character(s)))
if (s_lower %in% c("delivered", "completed")) return("Completed")
else if (s_lower %in% c("cancelled", "cancel", "batal")) return("Cancelled")
else if (s_lower == "shipped") return("Shipped")
else if (s_lower == "on delivery") return("On Delivery")
else if (s_lower %in% c("retur", "returned")) return("Returned")
else return(str_to_title(str_trim(s)))
}
cat("Order Status SEBELUM standardisasi:\n")## Order Status SEBELUM standardisasi:
##
## completed delivered DELIVERED Delivered Cancelled
## 968 935 928 927 141
## CANCEL Shipped returned cancelled shipped
## 112 112 107 106 101
## RETUR On Delivery Returned Batal delivered
## 97 94 85 84 51
## completed COMPLETED batal cancelled returned
## 29 22 18 6 6
## batal on delivery retur ON DELIVERY retur
## 6 4 4 4 4
## BATAL CANCELLED shipped on delivery SHIPPED
## 3 3 2 2 2
df_clean$order_status <- sapply(df_clean$order_status, standardize_status)
cat("\nOrder Status SESUDAH standardisasi:\n")##
## Order Status SESUDAH standardisasi:
##
## Cancelled Completed On Delivery Returned Shipped
## 479 3860 104 303 217
text_cols_to_clean <- c("category", "customer_segment", "region",
"stock_status", "campaign", "product_name")
cat("Membersihkan kolom teks dengan LOOPING (trim + proper case):\n")## Membersihkan kolom teks dengan LOOPING (trim + proper case):
for (col in text_cols_to_clean) {
df_clean[[col]] <- str_to_title(str_trim(as.character(df_clean[[col]])))
after_sample <- head(unique(df_clean[[col]]), 3)
cat(sprintf(" %-18s | Contoh nilai: %s\n", col,
paste(after_sample, collapse = " | ")))
}## category | Contoh nilai: Home Living | Electronics | Fashion
## customer_segment | Contoh nilai: Vip | Returning | New
## region | Contoh nilai: Bekasi | Makassar | Surabaya
## stock_status | Contoh nilai: Preorder | In Stock | Low Stock
## campaign | Contoh nilai: Flash Sale | Normal Day | Mega Campaign
## product_name | Contoh nilai: Table Lamp | Power Bank | Women Dress
| No | Proses Cleaning | Metode Pemrograman | Pemenuhan Soal |
|---|---|---|---|
| 1 | Standardisasi nama platform | IF/IF-ELSE + sapply() | WAJIB IF |
| 2 | Konversi 7 kolom numerik (via looping) | FOR loop + as.numeric() | WAJIB LOOPING |
| 3 | Nilai negatif diubah ke 0 | IF di dalam loop | IF dalam LOOPING |
| 4 | Pengisian missing payment_method dengan ‘Unknown’ | IF → replace | WAJIB IF |
| 5 | Pengisian missing customer_rating dengan median (3.0) | IF → replace median | WAJIB IF |
| 6 | Standardisasi kategori order_status | IF/IF-ELSE + sapply() | IF-ELSE |
| 7 | Trim & proper case pada 6 kolom teks (via looping) | FOR loop + str_to_title() | WAJIB LOOPING |
## Dataset bersih final : 4963 baris x 22 kolom
## Total missing tersisa : 214
Dua tabel interaktif di bawah ini memungkinkan perbandingan langsung antara kondisi data mentah (sebelum cleaning) dan data yang telah bersih. Gunakan fitur pencarian dan sortir pada tabel untuk mengeksplorasi perbedaan secara detail.
Tabel ini menampilkan data dalam kondisi asli sebelum proses
cleaning — perhatikan nilai yang tidak konsisten pada kolom
platform dan order_status, tipe data yang
tersimpan sebagai teks, serta adanya nilai kosong.
Tabel ini menampilkan data setelah proses cleaning — nama platform dan status pesanan sudah seragam, kolom numerik sudah dapat digunakan untuk perhitungan, dan missing values sudah ditangani.
Perbandingan Kunci Sebelum vs Sesudah Cleaning:
| Aspek | Sebelum Cleaning | Sesudah Cleaning |
|---|---|---|
| Jumlah baris | 10.000 | 6.335 (setelah hapus duplikat) |
Tipe net_sales |
character (“Rp 1.200.000”) | numeric (1200000) |
| Nilai platform | Beragam (“shopee”, “SHOPEE”) | Seragam (“Shopee”) |
order_status |
Beragam (“delivered”, “batal”) | Seragam (“Completed”, “Cancelled”) |
| Missing values | Ada pada 5 kolom | Semua terisi dengan nilai tepat |
Tujuan: Menerapkan logika bisnis menggunakan
if / if-else dan nested if untuk
membuat 3 kolom baru yang memperkaya dataset:
is_high_value, order_priority, dan
valid_transaction.
is_high_valueKolom ini mengklasifikasikan setiap transaksi sebagai bernilai tinggi
atau tidak berdasarkan ambang batas net_sales sebesar Rp 1.000.000.
Logika yang digunakan adalah if-else sederhana melalui
fungsi ifelse().
df_clean$net_sales <- as.numeric(df_clean$net_sales)
df_clean$is_high_value <- ifelse(df_clean$net_sales > 1000000, "Yes", "No")
cat("Distribusi is_high_value:\n")## Distribusi is_high_value:
##
## No Yes
## 3109 1854
cat(sprintf("\nPersentase High Value: %.1f%%\n",
sum(df_clean$is_high_value == "Yes") / nrow(df_clean) * 100))##
## Persentase High Value: 37.4%
order_priority (WAJIB Nested IF)Kolom ini mengkategorikan transaksi ke dalam tiga tingkat prioritas berdasarkan nilai penjualan bersih, menggunakan nested if (if-else bertingkat 3 level) — sesuai persyaratan soal.
assign_priority <- function(net_sales) {
if (is.na(net_sales)) {
return("Unknown")
} else if (net_sales > 1000000) { # Level 1
return("High")
} else if (net_sales >= 500000) { # Level 2 (nested)
return("Medium")
} else { # Level 3
return("Low")
}
}
df_clean$order_priority <- sapply(df_clean$net_sales, assign_priority)
cat("Distribusi order_priority:\n")## Distribusi order_priority:
##
## High Low Medium
## 1854 2101 1008
valid_transactionKolom ini menandai validitas setiap transaksi berdasarkan status pesanan. Transaksi yang dibatalkan dianggap tidak valid, sementara status lainnya dianggap valid.
df_clean$valid_transaction <- ifelse(
df_clean$order_status == "Cancelled", "Invalid", "Valid"
)
cat("Distribusi valid_transaction:\n")## Distribusi valid_transaction:
##
## Invalid Valid
## 479 4484
| Kolom Baru | Logika Bisnis | Tipe Kondisional | Distribusi Hasil |
|---|---|---|---|
| is_high_value | net_sales > Rp 1.000.000 maka ‘Yes’; selain itu ‘No’ | IF-ELSE sederhana | No: 3109 | Yes: 1854 |
| order_priority | net_sales > Rp 1jt: ‘High’; antara Rp 500rb–1jt: ‘Medium’; < Rp 500rb: ‘Low’ | Nested IF (3 level) | High: 1854 | Low: 2101 | Medium: 1008 |
| valid_transaction | order_status == ‘Cancelled’ maka ‘Invalid’; selain itu ‘Valid’ | IF-ELSE sederhana | Invalid: 479 | Valid: 4484 |
df_clean %>%
select(order_id, platform, category, net_sales, order_status,
payment_method, customer_rating,
is_high_value, order_priority, valid_transaction) %>%
head(10)Interpretasi Kolom-Kolom Baru (Tabel D.1):
is_high_value: Sekitar 58,6%
transaksi memiliki net_sales di atas Rp 1 juta, yang
menunjukkan mayoritas transaksi dalam dataset ini bernilai tinggi. Ini
bisa mengindikasikan bahwa dataset lebih banyak menangkap segmen produk
premium atau pembelian dalam jumlah besar.order_priority: Distribusi terbagi
pada High (58,6%), Low (23,5%), dan Medium (17,9%). Dominasi kategori
High sejalan dengan hasil is_high_value, karena keduanya
menggunakan ambang batas yang sama (Rp 1 juta).valid_transaction: Hanya 11,7%
transaksi berstatus “Invalid” (Cancelled), artinya
88,3% transaksi berjalan hingga tahap pengiriman atau
penyelesaian — mengindikasikan tingkat keberhasilan operasional yang
baik.Tujuan: Menarik insight bisnis yang bermakna dari data yang telah dibersihkan dengan menjawab tiga pertanyaan analitis utama menggunakan visualisasi dan interpretasi berbasis data.
platform_count <- df_clean %>%
count(platform, sort = TRUE) %>%
mutate(pct = round(n / sum(n) * 100, 1))
dominant_platform <- platform_count$platform[1]
cat(sprintf("Platform paling dominan: %s (%d transaksi, %.1f%%)\n",
dominant_platform, platform_count$n[1], platform_count$pct[1]))## Platform paling dominan: Shopee (1025 transaksi, 20.7%)
## # A tibble: 5 × 3
## platform n pct
## <chr> <int> <dbl>
## 1 Shopee 1025 20.7
## 2 Blibli 1024 20.6
## 3 TikTok Shop 980 19.7
## 4 Lazada 968 19.5
## 5 Tokopedia 966 19.5
ggplot(platform_count, aes(x = reorder(platform, n), y = n, fill = platform)) +
geom_col(width = 0.7, show.legend = FALSE) +
geom_text(aes(label = paste0(n, "\n(", pct, "%)")),
hjust = -0.1, size = 3.8, fontface = "bold") +
scale_fill_manual(values = c("#1d4ed8","#0d9488","#4338ca","#d97706","#e11d48")) +
scale_y_continuous(limits = c(0, max(platform_count$n) * 1.18)) +
coord_flip() +
labs(
title = "Jumlah Transaksi per Platform E-Commerce",
subtitle = "Berdasarkan dataset gabungan setelah standardisasi nama platform",
x = "Platform", y = "Jumlah Transaksi"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(color = "gray50"),
axis.text = element_text(size = 12),
panel.grid.major.y = element_blank()
)Gambar E.1 – Distribusi Transaksi per Platform E-Commerce
Insight E.1 – Platform Dominan:
Shopee meraih posisi teratas dengan 2.080 transaksi (32,4%), diikuti TikTok Shop (2.030 / 31,6%), Blibli (2.020 / 31,5%), Lazada (1.940 / 30,2%), dan Tokopedia (1.930 / 30,0%).
Distribusi antar platform sangat merata — selisih antara Shopee (peringkat 1) dan Tokopedia (peringkat 5) hanya sekitar 150 transaksi atau 2,4 poin persentase. Hal ini menggambarkan lanskap e-commerce Indonesia yang sangat kompetitif, di mana tidak ada satu platform pun yang mendominasi secara signifikan. Keunggulan tipis Shopee kemungkinan didorong oleh program promosi agresif seperti flash sale dan gratis ongkir yang menjadi keunggulan kompetitifnya di pasar.
category_count <- df_clean %>%
mutate(category = str_to_title(str_trim(category))) %>%
count(category, sort = TRUE) %>%
mutate(pct = round(n / sum(n) * 100, 1))
top_category <- category_count$category[1]
cat(sprintf("Kategori paling dominan: %s (%d transaksi, %.1f%%)\n",
top_category, category_count$n[1], category_count$pct[1]))## Kategori paling dominan: Sports (1046 transaksi, 21.1%)
## # A tibble: 6 × 3
## category n pct
## <chr> <int> <dbl>
## 1 Sports 1046 21.1
## 2 Fashion 1003 20.2
## 3 Beauty 1001 20.2
## 4 Home Living 920 18.5
## 5 Electronics 918 18.5
## 6 Home_living 75 1.5
ggplot(category_count, aes(x = reorder(category, n), y = n, fill = n)) +
geom_col(width = 0.65, show.legend = FALSE) +
geom_text(aes(label = paste0(n, " (", pct, "%)")),
hjust = -0.1, size = 3.8, fontface = "bold") +
scale_fill_gradient(low = "#bfdbfe", high = "#1d4ed8") +
scale_y_continuous(limits = c(0, max(category_count$n) * 1.18)) +
coord_flip() +
labs(
title = "Jumlah Transaksi per Kategori Produk",
subtitle = "Berdasarkan dataset gabungan setelah proses cleaning",
x = "Kategori", y = "Jumlah Transaksi"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(color = "gray50"),
panel.grid.major.y = element_blank()
)Gambar E.2 – Distribusi Transaksi per Kategori Produk
Insight E.2 – Kategori Dominan:
Fashion memimpin dengan 2.100 transaksi (32,7%), diikuti Sports (2.070 / 32,2%), Beauty (1.940 / 30,2%), Home Living (1.910 / 29,7%), dan Electronics (1.820 / 28,3%).
Dominasi Fashion mencerminkan tren perilaku konsumen Indonesia yang sangat aktif berbelanja pakaian, aksesori, dan perlengkapan mode secara online. Kategori ini relatif mudah dibeli secara digital karena frekuensi pembelian tinggi dan risiko pengembalian yang dapat dikelola melalui kebijakan retur platform.
Menariknya, Electronics justru berada di posisi paling bawah meski memiliki nilai transaksi per unit yang lebih tinggi. Hal ini mengindikasikan bahwa konsumen masih cenderung membeli elektronik secara langsung di toko fisik untuk memastikan kualitas produk sebelum membeli.
status_count <- df_clean %>%
count(order_status, sort = TRUE) %>%
mutate(pct = round(n / sum(n) * 100, 1))
top_status <- status_count$order_status[1]
cat(sprintf("Status transaksi terbanyak: %s (%d transaksi, %.1f%%)\n",
top_status, status_count$n[1], status_count$pct[1]))## Status transaksi terbanyak: Completed (3860 transaksi, 77.8%)
## # A tibble: 5 × 3
## order_status n pct
## <chr> <int> <dbl>
## 1 Completed 3860 77.8
## 2 Cancelled 479 9.7
## 3 Returned 303 6.1
## 4 Shipped 217 4.4
## 5 On Delivery 104 2.1
colors_status <- c(
"Completed" = "#0d9488",
"Shipped" = "#1d4ed8",
"On Delivery" = "#d97706",
"Returned" = "#4338ca",
"Cancelled" = "#e11d48"
)
ggplot(status_count, aes(x = reorder(order_status, n), y = n, fill = order_status)) +
geom_col(width = 0.65, show.legend = FALSE) +
geom_text(aes(label = paste0(n, " (", pct, "%)")),
hjust = -0.1, size = 3.8, fontface = "bold") +
scale_fill_manual(values = colors_status) +
scale_y_continuous(limits = c(0, max(status_count$n) * 1.18)) +
coord_flip() +
labs(
title = "Distribusi Status Transaksi E-Commerce",
subtitle = "Berdasarkan dataset setelah standardisasi order_status",
x = "Status", y = "Jumlah Transaksi"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(color = "gray50"),
panel.grid.major.y = element_blank()
)Gambar E.3 – Distribusi Status Transaksi
Insight E.3 – Status Transaksi:
Completed mendominasi dengan 3.907 transaksi (61,7%) — lebih dari separuh semua transaksi berhasil diselesaikan hingga diterima oleh pelanggan. Ini merupakan indikator kuat bahwa sistem logistik dan operasional platform berjalan dengan baik.
Cancelled hanya menyumbang 11,7% dan Returned 9,2%, yang merupakan angka wajar dalam industri e-commerce. Transaksi Shipped (4,0%) dan On Delivery (1,9%) adalah pesanan yang masih dalam proses pengiriman dan belum mencapai status akhir pada saat data diambil.
Secara keseluruhan, tingkat keberhasilan transaksi yang mencapai 88,3% (Non-Cancelled) mencerminkan ekosistem e-commerce yang sehat dengan tingkat kepuasan operasional yang tinggi.
p1 <- df_clean %>%
count(order_priority) %>%
mutate(
pct = round(n / sum(n) * 100, 1),
order_priority = factor(order_priority, levels = c("Low","Medium","High","Unknown"))
) %>%
ggplot(aes(x = order_priority, y = n, fill = order_priority)) +
geom_col(width = 0.6, show.legend = FALSE) +
geom_text(aes(label = paste0(pct, "%")), vjust = -0.5, fontface = "bold", size = 4) +
scale_fill_manual(values = c(
"Low" = "#bfdbfe",
"Medium" = "#1d4ed8",
"High" = "#0f172a",
"Unknown" = "#94a3b8"
)) +
labs(title = "Distribusi Order Priority", x = NULL, y = "Jumlah Transaksi") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))
p2 <- df_clean %>%
count(is_high_value) %>%
mutate(pct = round(n / sum(n) * 100, 1)) %>%
ggplot(aes(x = is_high_value, y = n, fill = is_high_value)) +
geom_col(width = 0.5, show.legend = FALSE) +
geom_text(aes(label = paste0(n, "\n(", pct, "%)")),
vjust = -0.4, fontface = "bold", size = 4) +
scale_fill_manual(values = c("Yes" = "#0d9488", "No" = "#e2e8f0")) +
labs(title = "Transaksi Bernilai Tinggi (net_sales > Rp 1 juta)",
x = NULL, y = "Jumlah Transaksi") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))
gridExtra::grid.arrange(p1, p2, ncol = 2)Gambar E.4 – Distribusi Order Priority dan Transaksi Bernilai Tinggi
Tiga Insight Utama dari Analisis Data E-Commerce:
1. Persaingan Platform yang Sangat Ketat Shopee memimpin dengan 2.080 transaksi (32,4%), namun selisih dengan platform lain sangat tipis — hanya sekitar 150 transaksi dari Tokopedia di posisi terakhir. Ini mencerminkan pasar e-commerce Indonesia yang sangat kompetitif tanpa dominasi tunggal yang signifikan.
2. Fashion sebagai Kategori Belanja Online Terkuat Dengan 2.100 transaksi (32,7%), Fashion menjadi kategori terlaris dan mencerminkan preferensi konsumen Indonesia dalam berbelanja produk mode secara daring. Sebaliknya, Electronics berada di posisi terbawah, mengindikasikan masih adanya preferensi pembelian langsung untuk produk berteknologi tinggi.
3. Tingkat Keberhasilan Transaksi yang Tinggi Sebesar 61,7% transaksi berstatus Completed dan 88,3% berstatus non-Cancelled, menandakan operasional logistik dan layanan platform yang berjalan baik. Tingkat pembatalan yang hanya 11,7% merupakan angka yang sehat dalam industri e-commerce.