UTS Mini Project: CASE STUDY E-Commerce

SD-1306 Pemrograman Sains Data I — Kelas A

Institut Teknologi Sains Bandung (ITSB) · Semester Genap 2025/2026

Bahasa: R  | 

rvest httr ggplot2 DT Looping IF / IF-ELSE Data Cleaning AJAX Handling


1 Profil Tim

Berikut adalah anggota tim yang mengerjakan tugas UTS ini.

Ahmad Rizki Mubarak
NIM: 52250036
Iky
Arya Fharezi
NIM: 5225008
Arya
M. Fitral Aidil Harahap
NIM: 52250031
Aidil

2 Section A – Data Collection Using Programming

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.

2.1 Membaca 5 File dengan Looping

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.

2.2 Ringkasan Informasi Per File

Tabel A.1 – Ringkasan Informasi 5 File Dataset E-Commerce
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.

2.3 Menggabungkan Semua File (Merge)

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!
cat(sprintf("   Total baris : %d\n", nrow(df_merged_raw)))
##    Total baris : 10000
cat(sprintf("   Total kolom : %d (termasuk kolom source_file)\n", ncol(df_merged_raw)))
##    Total kolom : 23 (termasuk kolom source_file)
cat(sprintf("   Sumber data : %s\n", paste(unique(df_merged_raw$source_file), collapse = ", ")))
##    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.


3 Section B – Data Handling

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.

3.1 Dimensi dan Tipe Data

cat("╔══════════════════════════════════════════════════════════╗\n")
## ╔══════════════════════════════════════════════════════════╗
cat("║              INFORMASI DATASET GABUNGAN                 ║\n")
## ║              INFORMASI DATASET GABUNGAN                 ║
cat("╚══════════════════════════════════════════════════════════╝\n\n")
## ╚══════════════════════════════════════════════════════════╝
cat(sprintf("Jumlah Total Baris : %d\n",   nrow(df_merged_raw)))
## Jumlah Total Baris : 10000
cat(sprintf("Jumlah Total Kolom : %d\n\n", ncol(df_merged_raw)))
## Jumlah Total Kolom : 23
cat("Tipe Data Setiap Kolom:\n")
## 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
Tabel B.1 – Tipe Data Setiap Kolom Dataset Gabungan
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).

3.2 Identifikasi Missing Values

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:
print(missing_df, row.names = FALSE)
##            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
Tabel B.2 – Missing Values per Kolom
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.

3.3 Identifikasi Duplikat

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
cat(sprintf("Persentase Duplikat : %.2f%%\n", n_dup / nrow(df_merged_raw) * 100))
## 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.

3.4 Masalah Kualitas Data yang Ditemukan

Tabel B.3 – Daftar Masalah Kualitas Data
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.


4 Section C – Data Cleaning

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.

4.1 Simpan Snapshot Data Sebelum Cleaning

# Simpan snapshot SEBELUM cleaning untuk perbandingan
df_before_clean <- df_merged_raw %>%
  select(-source_file) %>%
  distinct() %>%
  head(200)  # ambil 200 baris untuk tampilan tabel interaktif

4.2 Setup Dataset untuk Cleaning

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

4.3 C.1 – Standardisasi Platform

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):
print(head(sort(table(df_clean$platform), decreasing = TRUE), 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:
print(table(df_clean$platform))
## 
##      Blibli      Lazada      Shopee TikTok Shop   Tokopedia 
##        1024         968        1025         980         966

4.4 C.2 – Konversi Kolom Numerik (WAJIB LOOPING)

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

4.5 C.3 – Penanganan Missing Values (WAJIB IF)

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
cat("Median dipilih karena lebih robust terhadap nilai ekstrem dibanding mean.\n\n")
## 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'
df_clean$customer_rating <- as.numeric(df_clean$customer_rating)

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

4.6 C.4 – Standardisasi Order Status

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:
print(sort(table(df_clean$order_status), decreasing = TRUE))
## 
##     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:
print(table(df_clean$order_status))
## 
##   Cancelled   Completed On Delivery    Returned     Shipped 
##         479        3860         104         303         217

4.7 C.5 – Trim & Proper Case Kolom Teks (WAJIB LOOPING)

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

4.8 Ringkasan Proses Cleaning

Tabel C.1 – Ringkasan Proses Data Cleaning
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

4.9 Cek Dataset Final Setelah Cleaning

cat(sprintf("Dataset bersih final  : %d baris x %d kolom\n",
            nrow(df_clean), ncol(df_clean)))
## Dataset bersih final  : 4963 baris x 22 kolom
cat(sprintf("Total missing tersisa : %d\n",
            sum(sapply(df_clean, function(x) sum(is.na(x))))))
## Total missing tersisa : 214

4.10 Perbandingan Data: Sebelum dan Sesudah Cleaning

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.

4.10.1 Tabel Sebelum Cleaning (Data Mentah)

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.

4.10.2 Tabel Sesudah Cleaning (Data Bersih)

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

5 Section D – Conditional Logic

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.

5.1 D.1 – Kolom is_high_value

Kolom 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:
print(table(df_clean$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%

5.2 D.2 – Kolom 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:
print(table(df_clean$order_priority))
## 
##   High    Low Medium 
##   1854   2101   1008

5.3 D.3 – Kolom valid_transaction

Kolom 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:
print(table(df_clean$valid_transaction))
## 
## Invalid   Valid 
##     479    4484

5.4 Ringkasan Kolom Baru

Tabel D.1 – Ringkasan 3 Kolom Baru Hasil Conditional Logic
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 &#124; Yes: 1854
order_priority net_sales > Rp 1jt: ‘High’; antara Rp 500rb–1jt: ‘Medium’; < Rp 500rb: ‘Low’ Nested IF (3 level) High: 1854 &#124; Low: 2101 &#124; Medium: 1008
valid_transaction order_status == ‘Cancelled’ maka ‘Invalid’; selain itu ‘Valid’ IF-ELSE sederhana Invalid: 479 &#124; Valid: 4484

5.5 Preview Dataset Final

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.

6 Section E – Analytical Thinking

Tujuan: Menarik insight bisnis yang bermakna dari data yang telah dibersihkan dengan menjawab tiga pertanyaan analitis utama menggunakan visualisasi dan interpretasi berbasis data.

6.1 E.1 – Platform yang Paling Dominan

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%)
print(platform_count)
## # 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

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.

6.2 E.2 – Kategori Produk yang Paling Banyak

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%)
print(category_count)
## # 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

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.

6.3 E.3 – Status Transaksi yang Paling Banyak

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%)
print(status_count)
## # 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

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.

6.4 Visualisasi Tambahan: Order Priority dan High Value

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

Gambar E.4 – Distribusi Order Priority dan Transaksi Bernilai Tinggi


7 Kesimpulan

7.1 Ringkasan Pemenuhan Soal

Tabel Kesimpulan – Pemenuhan Semua Syarat Mini Project E-Commerce
Section Syarat / Tugas Status
A Baca minimal 5 file berbeda (CSV, Excel, JSON, TXT, XML) Selesai
A Tampilkan jumlah baris, kolom, dan nama kolom per file Selesai
A Gunakan looping untuk membaca file secara otomatis Selesai
A IF/IF-ELSE untuk cek konsistensi struktur dan penggabungan dataset Selesai
B Tampilkan total baris, kolom, dan tipe data Selesai
B Identifikasi missing values dan baris duplikat Selesai
B Sebutkan minimal 3 masalah kualitas data beserta dampaknya Selesai
C Standardisasi platform menggunakan WAJIB IF Selesai
C Cleaning nilai numerik (format Rp, nilai negatif) Selesai
C Missing payment_method dan customer_rating (WAJIB IF) Selesai
C Standardisasi order_status Selesai
C WAJIB LOOPING: bersihkan minimal 3 kolom sekaligus Selesai
D Buat kolom is_high_value (IF-ELSE) Selesai
D Buat kolom order_priority (WAJIB Nested IF) Selesai
D Buat kolom valid_transaction (IF-ELSE) Selesai
E Identifikasi platform paling dominan + interpretasi Selesai
E Identifikasi kategori produk terbanyak + interpretasi Selesai
E Identifikasi status transaksi terbanyak + interpretasi Selesai

7.2 Insight Utama

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.