Profil Kelompok

INSTITUT TEKNOLOGI SAINS BANDUNG
Naifah Edria Arta
NIM: 52250056
INSTITUT TEKNOLOGI SAINS BANDUNG
Adam Richie Wijaya
NIM: 52250064
INSTITUT TEKNOLOGI SAINS BANDUNG
Adinda Maiza I.
NIM: 52250074
INSTITUT TEKNOLOGI SAINS BANDUNG
Boma Satrio W.D
NIM: 52250061

Bagian 1 — Case Study E-Commerc

Introduction

Laporan ini menyajikan analisis data e-commerce yang komprehensif, dimulai dari proses integrasi dataset multi-format (CSV, XLSX, JSON, TXT, dan XML) hingga tahap visualisasi wawasan strategis. Fokus utama pekerjaan ini adalah memastikan integritas data melalui prosedur pembersihan yang ketat, standarisasi variabel operasional, serta penanganan missing values untuk menghasilkan basis data yang valid. Dengan menggabungkan pendekatan teknis pemrograman R dan logika berpikir analitis, laporan ini bertujuan untuk memetakan profil transaksi pelanggan, efektivitas platform penjualan, dan performa logistik guna mendukung pengambilan keputusan bisnis yang berbasis data (data-driven decision making).

Section A — Data Ingestion & Penggabungan Data

A.1 — Membaca 5 File Berbeda (CSV, Excel, JSON, TXT, XML)

# Load library yang diperlukan
library(readxl)   # Untuk Excel
library(jsonlite) # Untuk JSON
library(xml2)     # Untuk XML
library(dplyr)    # Untuk manipulasi data & penggabungan
library(purrr)    # Untuk iterasi yang rapi

# 1. Tentukan folder
folder <- "data_uts/"
all_files <- list.files(folder, full.names = TRUE)

# 2. Fungsi pembantu untuk mencari file berdasarkan ekstensi
find_file <- function(ext, exclude = "README") {
  files <- all_files[grepl(paste0("\\", ext, "$"), all_files)]
  # Filter jika ada kata README (ignore case)
  files <- files[!grepl(exclude, files, ignore.case = TRUE)]
  if (length(files) > 0) return(files[1]) else return(NULL)
}

# Mapping file
file_paths <- list(
  csv  = find_file(".csv"),
  xlsx = find_file(".xlsx"),
  json = find_file(".json"),
  txt  = find_file(".txt"),
  xml  = find_file(".xml")
)

datasets <- list()

# 3. Looping untuk membaca berbagai format file
for (fmt in names(file_paths)) {
  path <- file_paths[[fmt]]
  
  if (is.null(path)) {
    cat("❌ File", toupper(fmt), "tidak ditemukan!\n")
    next
  }
  
  tryCatch({
    df <- switch(fmt,
      "csv"  = read.csv(path),
      "xlsx" = read_excel(path),
      "json" = fromJSON(path),
      "txt"  = read.csv(path, sep = "|"),
      "xml"  = {
          doc <- read_xml(path)
          # Mengambil data di dalam tag <Record>
          records <- xml_find_all(doc, ".//Record")
          map_df(records, function(x) {
            nodes <- xml_children(x)
            res <- as.list(xml_text(nodes))
            names(res) <- xml_name(nodes)
            return(as.data.frame(res, stringsAsFactors = FALSE))
          })
      }
    )
    
    datasets[[fmt]] <- df
    cat("✅ Berhasil membaca", toupper(fmt), "(", basename(path), "):", nrow(df), "Baris\n")
    
  }, error = function(e) {
    cat("❌ Gagal membaca", toupper(fmt), ":", e$message, "\n")
  })
}
✅ Berhasil membaca CSV ( ecommerce.csv ): 2000 Baris
✅ Berhasil membaca XLSX ( ecommerce.xlsx ): 2000 Baris
✅ Berhasil membaca JSON ( ecommerce.json ): 2000 Baris
✅ Berhasil membaca TXT ( ecommerce.txt ): 2000 Baris
✅ Berhasil membaca XML ( ecommerce.xml ): 2000 Baris
# 4. Validasi Struktur & Gabung
merge_list <- list()

if (length(datasets) > 0) {
  ref_cols <- sort(colnames(datasets$csv))
  
  for (fmt in names(datasets)) {
    curr_cols <- sort(colnames(datasets[[fmt]]))
    
    if (identical(curr_cols, ref_cols)) {
      cat("[", toupper(fmt), "] Status: Ready to merge\n")
      
      # PERBAIKAN: Ubah semua kolom menjadi karakter agar tidak ada konflik tipe data
      df_temp <- datasets[[fmt]] %>%
        select(colnames(datasets$csv)) %>%
        mutate(across(everything(), as.character)) 
      
      merge_list[[fmt]] <- df_temp
    } else {
      cat("[", toupper(fmt), "] Status: Need adjustment\n")
    }
  }
}
[ CSV ] Status: Ready to merge
[ XLSX ] Status: Ready to merge
[ JSON ] Status: Ready to merge
[ TXT ] Status: Ready to merge
[ XML ] Status: Ready to merge
# 5. Gabungkan menjadi Dataset Utama
df_main <- bind_rows(merge_list)

# Setelah digabung, ubah kembali kolom yang harusnya angka (misal: quantity)
# agar bisa digunakan untuk perhitungan statistik/visualisasi
df_main <- df_main %>%
  mutate(
    quantity = as.numeric(quantity) # Sesuaikan nama kolom jika ada kolom angka lain
  )

cat("\n🔥 SUKSES! Total baris dataset utama:", nrow(df_main), "\n")

🔥 SUKSES! Total baris dataset utama: 10000 

A.2 — Pengecekan Struktur Kolom (IF-ELSE)

# 1. Load Libraries
library(readxl)
library(jsonlite)
library(XML)
library(dplyr)
library(knitr)
library(kableExtra)

# 2. Persiapan Folder & Ekstraksi
zip_path <- "Data Acak.zip"
extract_folder <- "data_uts"

if (file.exists(zip_path) && !dir.exists(extract_folder)) {
  unzip(zip_path, exdir = extract_folder)
}

# 3. Inisialisasi Jalur File
folder <- "data_uts/"
files_map <- list(
  CSV  = paste0(folder, "ecommerce.csv"),
  XLSX = paste0(folder, "ecommerce.xlsx"),
  JSON = paste0(folder, "ecommerce.json"),
  TXT  = paste0(folder, "ecommerce.txt"),
  XML  = paste0(folder, "ecommerce.xml")
)

# 4. Looping Pembacaan & Audit Struktur
datasets <- list()
audit_log <- data.frame(
  Format = character(),
  Baris = character(),
  Kolom = integer(),
  Status_Read = character(),
  Struktur = character(),
  stringsAsFactors = FALSE
)

# Ambil referensi kolom dari CSV terlebih dahulu (sebagai benchmark)
ref_df <- tryCatch(read.csv(files_map$CSV, nrows = 1), error = function(e) NULL)
ref_cols <- if(!is.null(ref_df)) sort(colnames(ref_df)) else NULL

for (fmt in names(files_map)) {
  path <- files_map[[fmt]]
  status <- "❌ Error"
  struk_match <- "—"
  n_row <- "0"
  n_col <- 0
  
  tryCatch({
    # Pembacaan berdasarkan format
    df <- switch(fmt,
      "CSV"  = read.csv(path, stringsAsFactors = FALSE),
      "XLSX" = read_excel(path, sheet = "ecommerce_raw"),
      "JSON" = fromJSON(path),
      "TXT"  = read.table(path, sep = "|", header = TRUE, stringsAsFactors = FALSE),
      "XML"  = xmlToDataFrame(nodes = getNodeSet(xmlParse(path), "//Record"))
    )
    
    datasets[[fmt]] <- df
    status <- "✅ Berhasil"
    n_row <- format(nrow(df), big.mark = ",")
    n_col <- ncol(df)
    
    # Pengecekan Struktur Kolom (IF-ELSE)
    if (!is.null(ref_cols) && identical(sort(colnames(df)), ref_cols)) {
      struk_match <- "Identik"
    } else {
      struk_match <- "Berbeda"
    }
    
  }, error = function(e) { status <- paste("❌", e$message) })
  
  # Masukkan ke log audit
  audit_log <- rbind(audit_log, data.frame(
    Format = fmt, Baris = n_row, Kolom = n_col, 
    Status_Read = status, Struktur = struk_match
  ))
}

# --- TAMPILAN TABEL AUDIT ---
audit_log %>%
  kbl(caption = "Tabel 1: Audit Pembacaan & Validasi Struktur Kolom", align = "lcccc") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = F, position = "left") %>%
  column_spec(4, bold = T, color = ifelse(grepl("✅", audit_log$Status_Read), "#1e8449", "#c0392b")) %>%
  column_spec(5, italic = T, color = ifelse(audit_log$Struktur == "Identik", "#2e86c1", "#e67e22"))
Tabel 1: Audit Pembacaan & Validasi Struktur Kolom
Format Baris Kolom Status_Read Struktur
CSV 2,000 22 ✅ Berhasil Identik
XLSX 2,000 22 ✅ Berhasil Identik
JSON 2,000 22 ✅ Berhasil Identik
TXT 2,000 22 ✅ Berhasil Identik
XML 2,000 22 ✅ Berhasil Identik
# 5. Penggabungan Data (A.3)
merge_list <- list()
for (fmt in names(datasets)) {
  if (identical(sort(colnames(datasets[[fmt]])), ref_cols)) {
    # Gunakan urutan kolom milik CSV agar konsisten
    merge_list[[fmt]] <- datasets[[fmt]][colnames(datasets$CSV)]
  }
}

df_main <- do.call(rbind, merge_list)
rownames(df_main) <- NULL

cat(sprintf("\n🔥 SUKSES: Berhasil menggabungkan %d file menjadi satu dataset utama (df_main).\n", length(merge_list)))

🔥 SUKSES: Berhasil menggabungkan 5 file menjadi satu dataset utama (df_main).

A.3 — Menggabungkan Data Menjadi 1 Dataset Utama

cat("A.3 - Menggabungkan Data Menjadi 1 Dataset Utama\n\n")
A.3 - Menggabungkan Data Menjadi 1 Dataset Utama
# Cek apakah merge_list ada dan tidak kosong
if (exists("merge_list") && length(merge_list) > 0) {
  
  # Menggabungkan semua dataframe dalam list menjadi satu (seperti pd.concat)
  df_main <- do.call(rbind, merge_list)
  
  # Reset row names (mirip ignore_index=True di pandas)
  rownames(df_main) <- NULL
  
  # Format angka dengan pemisah ribuan koma
  total_baris <- format(nrow(df_main), big.mark = ",", scientific = FALSE)
  
  cat(sprintf("✅ Berhasil menggabungkan %d file\n", length(merge_list)))
  cat(sprintf("   Total baris dataset utama: %s\n", total_baris))
  cat(sprintf("   Total kolom: %d\n", ncol(df_main)))
  
} else {
  cat("❌ Tidak ada data yang bisa digabung\n")
}
✅ Berhasil menggabungkan 5 file
   Total baris dataset utama: 10,000
   Total kolom: 22

Tabel Dataset 5 file

library(knitr)
library(kableExtra)

# --- Header Informasi ---

cat("  DATASET HASIL GABUNGAN 5 FILE (df_main)\n")
  DATASET HASIL GABUNGAN 5 FILE (df_main)
total_baris_fmt <- format(nrow(df_main), big.mark = ",", scientific = FALSE)

cat(sprintf("  Total baris : %s\n", total_baris_fmt))
  Total baris : 10,000
cat(sprintf("  Total kolom : %d\n", ncol(df_main)))
  Total kolom : 22
cat(sprintf("  Kolom       : %s\n", paste(colnames(df_main), collapse = ", ")))
  Kolom       : order_id, order_date, ship_date, platform, category, product_name, unit_price, quantity, gross_sales, campaign, voucher_code, discount_pct, discount_value, shipping_cost, net_sales, payment_method, customer_segment, region, stock_status, order_status, customer_rating, priority_flag
# --- Tampilan Tabel dengan Penyesuaian Dimensi ---
df_main %>%
  head(20) %>%
  kbl(caption = "Dataset Hasil Gabungan 5 File — 20 Baris Pertama", align = "l") %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width = TRUE,       # Mengatur agar lebar tabel memenuhi area fig.width
    position = "left",
    font_size = 12
  ) %>%
  row_spec(0, background = "#2c3e50", color = "white", bold = TRUE) %>%
  scroll_box(
    width = "100%",          # Menyesuaikan lebar box dengan fig.width
    height = "400px"         # Membatasi tinggi agar sesuai dengan proporsi fig.height
  )
Dataset Hasil Gabungan 5 File — 20 Baris Pertama
order_id order_date ship_date platform category product_name unit_price quantity gross_sales campaign voucher_code discount_pct discount_value shipping_cost net_sales payment_method customer_segment region stock_status order_status customer_rating priority_flag
ORD00612 2024-04-19 2024/04/24 Tokopedia home living Table Lamp 188905 4 755620 Flash Sale DISC10 10 75562 12000 680058 E-Wallet VIP Bekasi Preorder completed 5 Y
ORD00112 2024/01/24 29/01/2024 TikTok Shop Electronics Power Bank 1476873 1 1476873 Normal Day NONE 0 0 18000 1476873 cod Returning Makassar In Stock completed 5 N
ORD01186 2024-06-12 06-19-2024 Tokopedia Fashion Women Dress 231072 2 462144 Mega Campaign DISC20 20 92429 15000 369715 Virtual Account Returning Surabaya In Stock delivered 3 Yes
ORD01511 2024/08/07 08-14-2024 Tokopedia home living Vacuum Cleaner 512063 3 1536189 Flash Sale DISC10 10 153619 0 1382570 Transfer Bank New Yogyakarta In Stock DELIVERED 4 No
ORD00772 2024-12-08 Tokopedia Beauty Body Lotion 221586 2 443172 Payday Sale DISC15 15 Rp 66.476 0 376696 COD Returning Surabaya In Stock DELIVERED 5 normal
ORD00880 2024/04/06 blibli Beauty Lip Tint 297973 8 2383784 Normal Day NONE 0 0 12000 0 credit card VIP Makassar Low Stock Cancelled NA Yes
ORD00592 03-15-2024 Blibli Fashion Sneakers 102844 8 822752 Mega Campaign DISC20 20 164550 25000 0 E-Wallet Returning Bekasi In Stock CANCEL NA Yes
ORD01367 07-24-2024 2024-07-25 Blibli Sports Dumbbell 5kg 297954 10 2979540 Clearance CLEAR30 30 893862 20000 2085678 COD New Yogyakarta In Stock delivered 5 N
ORD01178 11-14-2024 2024-11-16 Blibli Sports Dumbbell 5kg 710898 2 1421796 Payday Sale DISC15 15 213269 18000 1208527 Virtual Account VIP Bandung In Stock delivered 5 PRIORITY
ORD00276 2024/06/15 17/06/2024 Lazada Sports Cycling Gloves 526279 4 2105116 Payday Sale DISC15 15 315767 20000 1789349 E-Wallet Returning Bandung In Stock delivered 3 normal
ORD00995 22/03/2024 01/04/2024 TikTok Shop Fashion Men T-Shirt 105558 3 316674 Mega Campaign DISC20 20 63335 25000 253339 Transfer Bank VIP Semarang In Stock DELIVERED 5 Y
ORD01139 2024-09-15 09-21-2024 Shopee Home Living Vacuum Cleaner 285779 3 857337 Flash Sale DISC10 10 85734 9000 771603 transfer bank New Medan In Stock completed 3 normal
ORD01113 04-05-2024 2024-04-06 Shopee Sports Dumbbell 5kg 139875 2 279750 Flash Sale DISC10 10 27975 0 251775 E-Wallet VIP Makassar In Stock delivered 5 PRIORITY
ORD00066 01-17-2024 2024/01/24 Lazada beauty Skincare Serum 153791 6 922746 Mega Campaign DISC20 20 184549 20000 738197 Credit Card Returning Bandung Low Stock delivered 4 Y
ORD01090 2024-03-01 05/03/2024 Shopee Home_Living Storage Box 339343 2 678686 Normal Day NONE 0 0 12000 678686 Transfer Bank Returning Semarang Low Stock Delivered 5 N
ORD00212 2024-07-02 07-08-2024 TikTok Shop Sports Running Shoes 533649 1 533649 Normal Day NONE 0 0 9000 533649 COD New Bandung In Stock delivered 5 No
ORD01675 07-09-2024 07-14-2024 Blibli Beauty Body Lotion 284795 3 854385 Flash Sale DISC10 10 85438 12000 768947 Virtual Account Returning Semarang In Stock On Delivery NA normal
ORD00451 28/11/2024 2024/12/04 Tokopedia Home Living Blender 1177079 1 1177079 Flash Sale DISC10 10 117708 15000 1059371 Virtual Account Returning Semarang In Stock DELIVERED 5 No
ORD01882 2024-04-06 2024/04/10 lazada Sports Skipping Rope 458316 2 916632 Normal Day NONE 0 0 25000 916632 Credit Card Returning Bekasi In Stock On Delivery NA normal
ORD00129 27/04/2024 2024-04-28 Blibli Fashion Hoodie 232656 6 1395936 Flash Sale DISC10 10 Rp 139.594 25000 1256342 Transfer Bank Returning Medan Low Stock Delivered 5 normal
cat(sprintf("\n  Menampilkan 20 dari %s baris total.\n", total_baris_fmt))

  Menampilkan 20 dari 10,000 baris total.

RINGKASAN DATASET GABUNGAN

library(dplyr)
library(knitr)
library(kableExtra)

# --- 1. Statistik Integritas Data ---
n_duplikat <- sum(duplicated(df_main))
n_missing  <- sum(is.na(df_main))
total_obs  <- nrow(df_main)

integritas_df <- data.frame(
  Indikator = c("Jumlah Baris Duplikat", "Jumlah Missing Values (NA)", "Total Observasi Data"),
  Nilai = c(
    format(n_duplikat, big.mark = ","),
    format(n_missing, big.mark = ","),
    format(total_obs, big.mark = ",")
  ),
  Status = c(
    ifelse(n_duplikat > 0, "⚠️ Perlu Cleaning", "✅ Aman"),
    ifelse(n_missing > 0, "⚠️ Perlu Imputasi", "✅ Aman"),
    "📊 Dataset Utama"
  )
)

# Tampilkan Tabel Integritas
integritas_df %>%
  kbl(align = "lcc", caption = "Ringkasan Integritas Dataset (df_main)") %>%
  kable_styling(bootstrap_options = c("condensed", "bordered"), full_width = F, position = "left") %>%
  column_spec(1, bold = T) %>%
  column_spec(3, italic = T, color = ifelse(grepl("⚠️", integritas_df$Status), "#e67e22", "#27ae60"))
Ringkasan Integritas Dataset (df_main)
Indikator Nilai Status
Jumlah Baris Duplikat 5,313 ⚠️ Perlu Cleaning
Jumlah Missing Values (NA) 2,685 ⚠️ Perlu Imputasi
Total Observasi Data 10,000 📊 Dataset Utama
# --- 2. Distribusi Platform (Value Counts) ---
platform_counts <- df_main %>%
  group_by(Platform = platform) %>%
  summarise(Jumlah = n()) %>%
  mutate(Persentase = paste0(round(Jumlah / sum(Jumlah) * 100, 1), "%")) %>%
  arrange(desc(Jumlah))

# Tampilkan Tabel Distribusi Platform
platform_counts %>%
  kbl(align = "lcc", caption = "Distribusi Platform (Sebelum Cleaning)") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = F, position = "left") %>%
  row_spec(0, background = "#34495e", color = "white", bold = T) %>%
  column_spec(2, bold = T) %>%
  column_spec(3, color = "#2980b9", bold = T)
Distribusi Platform (Sebelum Cleaning)
Platform Jumlah Persentase
Shopee 1895 19%
TikTok Shop 1840 18.4%
Blibli 1820 18.2%
Tokopedia 1765 17.6%
Lazada 1760 17.6%
blibli 106 1.1%
TOKOPEDIA 95 0.9%
lazada 82 0.8%
SHOPEE 75 0.8%
tiktok shop 68 0.7%
shopee 58 0.6%
tiktok shop 57 0.6%
shopee 52 0.5%
BLIBLI 50 0.5%
LAZADA 50 0.5%
lazada 48 0.5%
tokopedia 46 0.5%
blibli 44 0.4%
Tiktok Shop 35 0.4%
TIKTOK SHOP 30 0.3%
tokopedia 24 0.2%
cat(sprintf("\n✅ Ringkasan selesai. Data siap untuk tahap preprocessing selanjutnya.\n"))

✅ Ringkasan selesai. Data siap untuk tahap preprocessing selanjutnya.

Section B — Data Handling

B.1 — Jumlah Total Baris dan Kolom

cat("B.1 - Jumlah Total Baris dan Kolom\n\n")
B.1 - Jumlah Total Baris dan Kolom
# Mengambil jumlah baris dan format ribuan
total_baris <- format(nrow(df_main), big.mark = ",", scientific = FALSE)

# Mengambil jumlah kolom
total_kolom <- ncol(df_main)

cat(sprintf("Total Baris  : %s\n", total_baris))
Total Baris  : 10,000
cat(sprintf("Total Kolom  : %d\n", total_kolom))
Total Kolom  : 22

B.2 — Tipe Data Setiap Kolom

library(dplyr)
library(knitr)
library(kableExtra)

cat("B.2 - Analisis Tipe Data dan Struktur Kolom\n\n")
B.2 - Analisis Tipe Data dan Struktur Kolom
# Proteksi: Cek apakah df_main tersedia
if (exists("df_main")) {
  
  # 1. Ambil informasi dasar
  col_names <- names(df_main)
  
  # 2. Buat dataframe audit secara iteratif (lebih stabil daripada mutate kompleks)
  tipe_data_list <- lapply(col_names, function(col) {
    vec_data <- df_main[[col]]
    cls <- class(vec_data)[1]
    n_uniq <- n_distinct(vec_data)
    contoh <- paste(head(vec_data, 1))
    
    # Logika Rekomendasi
    rekomendasi <- "Biarkan (Ok)"
    if (cls == "character") {
      if (grepl("date|tgl", col, ignore.case = TRUE)) {
        rekomendasi <- "Konversi ke Date"
      } else if (n_uniq < 15) {
        rekomendasi <- "Konversi ke Factor"
      }
    } else if (cls %in% c("numeric", "integer")) {
      rekomendasi <- "Siap untuk Statistik"
    }
    
    data.frame(
      Nama_Kolom = col,
      Tipe_Data = cls,
      Unik = n_uniq,
      Contoh = contoh,
      Rekomendasi = rekomendasi,
      stringsAsFactors = FALSE
    )
  })
  
  tipe_data_df <- do.call(rbind, tipe_data_list)

  # 3. Tampilkan Tabel yang Rapi
  tipe_data_df %>%
    kbl(align = "llcll", caption = "Struktur Tipe Data df_main (Audit Sebelum Cleaning)") %>%
    kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = T) %>%
    row_spec(0, background = "#2c3e50", color = "white", bold = T) %>%
    column_spec(1, bold = T, color = "#2e86c1") %>%
    column_spec(2, monospace = T) %>%
    column_spec(5, italic = T, color = case_when(
      tipe_data_df$Rekomendasi == "Siap untuk Statistik" ~ "#27ae60",
      tipe_data_df$Rekomendasi == "Biarkan (Ok)" ~ "#2e86c1",
      TRUE ~ "#d35400"
    ))

} else {
  cat("❌ Error: Variabel 'df_main' tidak ditemukan. Pastikan Section A sudah dijalankan.")
}
Struktur Tipe Data df_main (Audit Sebelum Cleaning)
Nama_Kolom Tipe_Data Unik Contoh Rekomendasi
order_id character 1966 ORD00612 Biarkan (Ok)
order_date character 1059 2024-04-19 Konversi ke Date
ship_date character 1042 2024/04/24 Konversi ke Date
platform character 21 Tokopedia Biarkan (Ok)
category character 31 home living Biarkan (Ok)
product_name character 49 Table Lamp Biarkan (Ok)
unit_price character 1964 188905 Biarkan (Ok)
quantity character 8 4 Konversi ke Factor
gross_sales character 1965 755620 Biarkan (Ok)
campaign character 5 Flash Sale Konversi ke Factor
voucher_code character 7 DISC10 Konversi ke Factor
discount_pct character 7 10 Konversi ke Factor
discount_value character 1306 75562 Biarkan (Ok)
shipping_cost character 7 12000 Konversi ke Factor
net_sales character 1816 680058 Biarkan (Ok)
payment_method character 34 E-Wallet Biarkan (Ok)
customer_segment character 3 VIP Konversi ke Factor
region character 8 Bekasi Konversi ke Factor
stock_status character 3 Preorder Konversi ke Factor
order_status character 30 completed Biarkan (Ok)
customer_rating character 12 5 Konversi ke Factor
priority_flag character 8 Y Konversi ke Factor

B.3 — Identifikasi Missing Values dan Duplicate Rows

cat("B.3 - Identifikasi Missing Values dan Duplicate Rows\n\n")
B.3 - Identifikasi Missing Values dan Duplicate Rows
# --- Missing Values ---
cat("Missing Values:\n")
Missing Values:
# Menghitung NA per kolom
missing <- colSums(is.na(df_main))

# Memfilter hanya kolom yang memiliki missing values > 0
missing_filtered <- missing[missing > 0]

if (length(missing_filtered) > 0) {
  # Diubah ke format dataframe agar tercetak rapi sebagai kolom
  missing_df <- data.frame(
    Kolom = names(missing_filtered),
    Jumlah_NA = missing_filtered,
    row.names = NULL
  )
  print(missing_df, row.names = FALSE)
} else {
  cat("Tidak ada missing values\n")
}
           Kolom Jumlah_NA
       ship_date       251
    voucher_code        98
    discount_pct       276
  payment_method        70
 customer_rating      1614
   priority_flag       376
# --- Duplicate Rows ---
# Menghitung jumlah duplikat dan memformat dengan pemisah ribuan
n_duplikat <- sum(duplicated(df_main))
n_duplikat_fmt <- format(n_duplikat, big.mark = ",")

cat(sprintf("\nJumlah baris duplikat : %s baris\n", n_duplikat_fmt))

Jumlah baris duplikat : 5,313 baris

B.4 — Masalah Kualitas Data yang Ditemukan

cat("B.4 - 3 Masalah Kualitas Data yang Ditemukan\n\n")
B.4 - 3 Masalah Kualitas Data yang Ditemukan
cat("1. Banyak missing values pada kolom customer_rating, ship_date, dan payment_method.\n")
1. Banyak missing values pada kolom customer_rating, ship_date, dan payment_method.
cat("2. Terdapat ribuan baris duplikat yang dapat menyebabkan hasil analisis bias.\n")
2. Terdapat ribuan baris duplikat yang dapat menyebabkan hasil analisis bias.
cat("3. Kolom harga (unit_price, gross_sales, net_sales, dll) masih bertipe character karena adanya simbol 'Rp' dan pemisah ribuan.\n")
3. Kolom harga (unit_price, gross_sales, net_sales, dll) masih bertipe character karena adanya simbol 'Rp' dan pemisah ribuan.

Interpretasi:

Dataset ini masih kotor dan perlu dibersihkan (Data Cleaning) sebelum digunakan untuk analisis statistik maupun visualisasi lebih lanjut agar hasilnya akurat.

Section C — Data Cleaning

C.1 — Menghapus Data Duplikat

cat("C.1 - Menghapus data duplikat\n\n")
C.1 - Menghapus data duplikat
# Menghapus duplikat dan menyimpannya ke variabel df_clean
df_clean <- df_main %>% 
  distinct()

# Menghitung sisa baris dengan format ribuan
sisa_baris_fmt <- format(nrow(df_clean), big.mark = ",")

cat(sprintf("   → Sisa baris setelah menghapus duplikat: %s\n", sisa_baris_fmt))
   → Sisa baris setelah menghapus duplikat: 4,687

C.2 — Standardisasi Kolom Platform (WAJIB IF)

library(dplyr)
library(knitr)
library(kableExtra)

cat("C.2 - Standardisasi Nama Platform (Logika IF-ELSE)\n\n")
C.2 - Standardisasi Nama Platform (Logika IF-ELSE)
# 1. Definisi Fungsi Pembersih (WAJIB IF)
clean_platform <- function(platform) {
  if (is.na(platform) || platform == "" || platform == "NA") {
    return("Unknown")
  }
  
  p <- tolower(trimws(as.character(platform)))
  
  if (p %in% c('shopee', 'shoope', 'shope')) {
    return("Shopee")
  } else if (p %in% c('tokopedia', 'tokped', 'tokopdia')) {
    return("Tokopedia")
  } else if (p == 'blibli') {
    return("Blibli")
  } else if (p == 'lazada') {
    return("Lazada")
  } else if (grepl("tiktok", p)) {
    return("TikTok Shop")
  } else {
    return(tools::toTitleCase(p))
  }
}

# 2. Eksekusi Pembersihan
# Menggunakan sapply untuk memastikan logika IF berjalan pada setiap baris
df_clean$platform <- sapply(df_clean$platform, clean_platform)

# 3. Visualisasi Hasil Distribusi Setelah Cleaning
platform_stats <- df_clean %>%
  count(Platform = platform, name = "Jumlah") %>%
  mutate(Persentase = paste0(round(Jumlah / sum(Jumlah) * 100, 1), "%")) %>%
  arrange(desc(Jumlah))

# Tampilkan Tabel yang Rapi
platform_stats %>%
  kbl(align = "lcc", caption = "Distribusi Platform Setelah Tahap Standardisasi") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = F, position = "left") %>%
  row_spec(0, background = "#1e8449", color = "white", bold = T) %>%
  column_spec(1, bold = T) %>%
  column_spec(2, color = "#2c3e50") %>%
  column_spec(3, color = "#27ae60", bold = T)
Distribusi Platform Setelah Tahap Standardisasi
Platform Jumlah Persentase
Shopee 980 20.9%
Blibli 964 20.6%
TikTok Shop 928 19.8%
Tokopedia 913 19.5%
Lazada 902 19.2%
cat(sprintf("\n✅ Berhasil merapikan nama platform. Tidak ada lagi variasi penulisan (typo).\n"))

✅ Berhasil merapikan nama platform. Tidak ada lagi variasi penulisan (typo).

C.3 — Cleaning Kolom Harga (WAJIB LOOPING)

cat("C.3 - Cleaning kolom harga (WAJIB LOOPING)\n\n")
C.3 - Cleaning kolom harga (WAJIB LOOPING)
# Daftar kolom yang akan dibersihkan
money_cols <- c('unit_price', 'gross_sales', 'discount_value', 'shipping_cost', 'net_sales')

cat("Kolom yang dibersihkan:", paste(money_cols, collapse = ", "), "\n")
Kolom yang dibersihkan: unit_price, gross_sales, discount_value, shipping_cost, net_sales 
# Looping untuk membersihkan setiap kolom harga
for (col in money_cols) {
  
  # 1. Cleaning process: Hapus 'Rp', titik, dan koma
  # gsub() digunakan untuk mencari dan mengganti pola teks
  df_clean[[col]] <- gsub("Rp", "", df_clean[[col]])
  df_clean[[col]] <- gsub("\\.", "", df_clean[[col]]) # Titik harus di-escape dengan \\
  df_clean[[col]] <- gsub(",", "", df_clean[[col]])
  df_clean[[col]] <- trimws(df_clean[[col]])
  
  # 2. Ubah ke tipe numerik
  df_clean[[col]] <- as.numeric(df_clean[[col]])
  
  # 3. Handling Nilai: Jika NA jadi 0, jika negatif jadi 0
  # pmax(x, 0) mengembalikan nilai x atau 0, mana yang lebih besar
  df_clean[[col]][is.na(df_clean[[col]])] <- 0
  df_clean[[col]] <- pmax(df_clean[[col]], 0)
  
  # Tampilkan contoh hasil
  example_val <- df_clean[[col]][1]
  cat(sprintf("   %-20s → Contoh nilai setelah cleaning: %s\n", col, example_val))
}
   unit_price           → Contoh nilai setelah cleaning: 188905
   gross_sales          → Contoh nilai setelah cleaning: 755620
   discount_value       → Contoh nilai setelah cleaning: 75562
   shipping_cost        → Contoh nilai setelah cleaning: 12000
   net_sales            → Contoh nilai setelah cleaning: 680058
cat("   ✅ Semua kolom harga sudah dibersihkan menjadi tipe angka\n")
   ✅ Semua kolom harga sudah dibersihkan menjadi tipe angka
cat("   ✅ Nilai negatif diubah menjadi 0\n")
   ✅ Nilai negatif diubah menjadi 0

C.4 — Penanganan Missing Value (WAJIB IF)

library(dplyr)
library(knitr)
library(kableExtra)

cat("C.4 - Strategi Penanganan Missing Value (WAJIB IF)\n\n")
C.4 - Strategi Penanganan Missing Value (WAJIB IF)
# --- 1. Audit Missing Value Sebelum Penanganan ---
miss_payment <- sum(is.na(df_clean$payment_method) | df_clean$payment_method == "" | df_clean$payment_method == " ")
df_clean$customer_rating <- as.numeric(df_clean$customer_rating) # Paksa numerik
miss_rating <- sum(is.na(df_clean$customer_rating))

# --- 2. Eksekusi Penanganan (WAJIB IF-ELSE) ---

# [A] Payment Method
if (miss_payment > 0) {
  df_clean$payment_method[is.na(df_clean$payment_method) | 
                          df_clean$payment_method == "" | 
                          df_clean$payment_method == " "] <- "Unknown"
  status_pay <- paste("✅ Diisi 'Unknown'")
} else {
  status_pay <- "✅ Sudah Bersih"
}

# [B] Customer Rating
if (miss_rating > 0) {
  median_val <- median(df_clean$customer_rating, na.rm = TRUE)
  # Imputasi dengan nilai tengah 3.0
  df_clean$customer_rating[is.na(df_clean$customer_rating)] <- 3.0
  status_rat <- paste("✅ Diisi 3.0 (Median/Netral)")
} else {
  status_rat <- "✅ Sudah Bersih"
}

# --- 3. Visualisasi Hasil Akhir ---
summary_missing <- data.frame(
  Variabel = c("Payment Method", "Customer Rating"),
  Missing_Awal = c(miss_payment, miss_rating),
  Tindakan = c(status_pay, status_rat),
  Missing_Akhir = c(sum(is.na(df_clean$payment_method)), sum(is.na(df_clean$customer_rating)))
)

summary_missing %>%
  kbl(align = "lccc", caption = "Ringkasan Penanganan Missing Value") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = F, position = "left") %>%
  column_spec(2, color = "#c0392b", bold = T) %>%
  column_spec(4, color = "#1e8449", bold = T)
Ringkasan Penanganan Missing Value
Variabel Missing_Awal Tindakan Missing_Akhir
Payment Method 108 ✅ Diisi ‘Unknown’ 0
Customer Rating 1052 ✅ Diisi 3.0 (Median/Netral) 0

C.5 — Standardisasi Order Status

library(dplyr)
library(knitr)
library(kableExtra)

cat("C.5 - Standardisasi Status Transaksi (Logika IF-ELSE)\n\n")
C.5 - Standardisasi Status Transaksi (Logika IF-ELSE)
# 1. Simpan sampel sebelum cleaning untuk perbandingan
sampel_awal <- head(df_clean$order_status, 8)

# 2. Fungsi Pembersih Order Status
clean_order_status <- function(status) {
  if (is.na(status) || status == "" || status == "NA") {
    return("Unknown")
  }
  
  s <- tolower(trimws(as.character(status)))
  
  if (s %in% c('delivered', 'completed', 'selesai')) {
    return("Completed")
  } else if (s %in% c('cancelled', 'cancel', 'batal')) {
    return("Cancelled")
  } else {
    return(tools::toTitleCase(s))
  }
}

# 3. Eksekusi Pembersihan
df_clean$order_status <- sapply(df_clean$order_status, clean_order_status)
sampel_akhir <- head(df_clean$order_status, 8)

# 4. Tampilkan Tabel Perbandingan
perbandingan_status <- data.frame(
  No = 1:8,
  Sebelum_Cleaning = sampel_awal,
  Setelah_Cleaning = sampel_akhir
)

perbandingan_status %>%
  kbl(align = "cll", caption = "Perbandingan Sampel Nilai Order Status") %>%
  kable_styling(bootstrap_options = c("striped", "bordered", "condensed"), full_width = F, position = "left") %>%
  column_spec(2, color = "#c0392b", italic = T) %>%
  column_spec(3, color = "#1e8449", bold = T)
Perbandingan Sampel Nilai Order Status
No Sebelum_Cleaning Setelah_Cleaning
1 completed Completed
2 completed Completed
3 delivered Completed
4 DELIVERED Completed
5 DELIVERED Completed
6 Cancelled Cancelled
7 CANCEL Cancelled
8 delivered Completed
cat("\nLogika Transformasi:\n")

Logika Transformasi:
cat("   → 'delivered', 'selesai' diubah menjadi 'Completed'\n")
   → 'delivered', 'selesai' diubah menjadi 'Completed'
cat("   → 'cancelled', 'batal' diubah menjadi 'Cancelled'\n")
   → 'cancelled', 'batal' diubah menjadi 'Cancelled'
# 5. Distribusi Akhir
status_final <- df_clean %>%
  count(Order_Status = order_status, name = "Total") %>%
  arrange(desc(Total))

status_final %>%
  kbl(align = "lc") %>%
  kable_styling(bootstrap_options = c("hover"), full_width = F, position = "left") %>%
  row_spec(0, background = "#2c3e50", color = "white")
Order_Status Total
Completed 3588
Cancelled 473
Shipped 236
Returned 180
On Delivery 111
Retur 99

C.6 Tabel Dataset Hasil Cleaning

cat("C.6 - Membersihkan 3 kolom sekaligus menggunakan looping\n\n")
C.6 - Membersihkan 3 kolom sekaligus menggunakan looping
# Daftar kolom yang dibersihkan
cols_to_clean <- c('platform', 'order_status', 'payment_method')

cat("Kolom yang dibersihkan dengan looping:\n")
Kolom yang dibersihkan dengan looping:
for (col in cols_to_clean) {
  cat(sprintf("   → %s\n", col))
}
   → platform
   → order_status
   → payment_method
cat("\nHasil setelah looping:\n")

Hasil setelah looping:
for (col in cols_to_clean) {
  # Menghitung jumlah nilai unik (nunique)
  unique_count <- length(unique(df_clean[[col]]))
  
  # Mengambil contoh nilai pertama
  example <- df_clean[[col]][1]
  
  # Mencetak hasil dengan format rapi (menggunakan sprintf untuk padding teks)
  cat(sprintf("   %-18s → %d unique values | Contoh: %s\n", col, unique_count, example))
}
   platform           → 5 unique values | Contoh: Tokopedia
   order_status       → 6 unique values | Contoh: Completed
   payment_method     → 33 unique values | Contoh: E-Wallet
# Menghitung total baris akhir
total_akhir <- format(nrow(df_clean), big.mark = ",")

cat(sprintf("\n✅ Section C selesai. Total baris akhir: %s\n", total_akhir))

✅ Section C selesai. Total baris akhir: 4,687

C.7 Tabel Dataset Hasil Cleaning

library(knitr)
library(kableExtra)
library(dplyr)

# --- Perhitungan Data ---
baris_sebelum   <- nrow(df_main)
baris_sesudah   <- nrow(df_clean)
baris_dihapus   <- baris_sebelum - baris_sesudah
missing_sebelum <- sum(is.na(df_main))
missing_sesudah <- sum(is.na(df_clean))


cat(sprintf("%-30s : %15s\n", "🔵 Baris sebelum (df_main)",    format(baris_sebelum, big.mark = ",")))
🔵 Baris sebelum (df_main)   :          10,000
cat(sprintf("%-30s : %15s\n", "🟢 Baris sesudah (df_clean)",   format(baris_sesudah, big.mark = ",")))
🟢 Baris sesudah (df_clean)  :           4,687
cat(sprintf("%-30s : %15s\n", "🔴 Baris dihapus (duplikat)",   format(baris_dihapus, big.mark = ",")))
🔴 Baris dihapus (duplikat)  :           5,313
cat(strrep("-", 60), "\n")
------------------------------------------------------------ 
cat(sprintf("%-30s : %15s\n", "⚠️ Missing sebelum",          format(missing_sebelum, big.mark = ",")))
⚠️ Missing sebelum         :           2,685
cat(sprintf("%-30s : %15s\n", "✅ Missing sesudah",          format(missing_sesudah, big.mark = ",")))
✅ Missing sesudah            :             577
cat(strrep("=", 60), "\n\n")
============================================================ 
# --- 2. Tabel Distribusi Platform (Styled) ---
cat("📍 Distribusi Platform (Sesudah Cleaning):\n")
📍 Distribusi Platform (Sesudah Cleaning):
platform_df <- as.data.frame(sort(table(df_clean$platform), decreasing = TRUE))
colnames(platform_df) <- c("Platform", "Total_Transaksi")

platform_df %>%
  kbl(align = "lc") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = F, position = "left") %>%
  column_spec(1, bold = T, color = "#1a5276") %>%
  column_spec(2, width = "10em")
Platform Total_Transaksi
Shopee 980
Blibli 964
TikTok Shop 928
Tokopedia 913
Lazada 902
# --- 3. Tabel Distribusi Order Status (Styled) ---
cat("\n📦 Distribusi Order Status (Sesudah Cleaning):\n")

📦 Distribusi Order Status (Sesudah Cleaning):
status_df <- as.data.frame(sort(table(df_clean$order_status), decreasing = TRUE))
colnames(status_df) <- c("Status", "Total_Transaksi")

status_df %>%
  kbl(align = "lc") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = F, position = "left") %>%
  column_spec(1, bold = T, 
              color = ifelse(status_df$Status == "Completed", "#1e8449", 
                      ifelse(status_df$Status == "Cancelled", "#c0392b", "#b7950b"))) %>%
  column_spec(2, width = "10em")
Status Total_Transaksi
Completed 3588
Cancelled 473
Shipped 236
Returned 180
On Delivery 111
Retur 99

Section D — Conditional Logic

D.1 — Kolom is_high_value

library(knitr)
library(kableExtra)
library(dplyr)

# PASTIKAN df_clean ada. Jika error 'object not found', 
# pastikan chunk sebelumnya sudah dijalankan.

# Logika R: menggunakan ifelse (bukan .apply seperti Python)
df_clean$is_high_value <- ifelse(df_clean$net_sales > 1000000, "Yes", "No")

# Tampilkan hasil dalam bentuk tabel yang rapi
cat("Contoh hasil is_high_value (10 Baris Pertama):\n")
Contoh hasil is_high_value (10 Baris Pertama):
# Membuat tabel sampel untuk ditampilkan
df_sampel <- df_clean %>%
  select(net_sales, is_high_value) %>%
  head(10)

df_sampel %>%
  mutate(
    net_sales = paste0("Rp ", format(net_sales, big.mark = ","))
  ) %>%
  kbl(align = "lr") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = F, position = "left") %>%
  column_spec(2, bold = T, 
              color = ifelse(df_sampel$is_high_value == "Yes", "#1e8449", "#c0392b"))
net_sales is_high_value
Rp 680,058 No
Rp 1,476,873 Yes
Rp 369,715 No
Rp 1,382,570 Yes
Rp 376,696 No
Rp 0 No
Rp 0 No
Rp 2,085,678 Yes
Rp 1,208,527 Yes
Rp 1,789,349 Yes
# Menghitung total transaksi High Value
total_high_value <- sum(df_clean$is_high_value == "Yes", na.rm = TRUE)

cat(sprintf("\n   Total transaksi High Value : %s\n", format(total_high_value, big.mark = ",")))

   Total transaksi High Value : 1,757

D.2 — Kolom order_priority (WAJIB Nested IF)

library(knitr)
library(kableExtra)
library(dplyr)

# Membuat fungsi dengan logika Nested IF
get_priority <- function(sales) {
  if (sales > 1000000) {
    return("High")
  } else {
    if (sales >= 500000) {
      return("Medium")
    } else {
      return("Low")
    }
  }
}

# Menerapkan fungsi ke kolom net_sales menggunakan sapply
df_clean$order_priority <- sapply(df_clean$net_sales, get_priority)

# --- Tampilan Tabel Contoh (10 Baris Pertama) ---
cat("Contoh hasil order_priority:\n")
Contoh hasil order_priority:
df_priority_sampel <- df_clean %>%
  select(net_sales, order_priority) %>%
  head(10)

df_priority_sampel %>%
  mutate(
    net_sales = paste0("Rp ", format(net_sales, big.mark = ","))
  ) %>%
  kbl(align = "lr") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = F, position = "left") %>%
  column_spec(2, bold = T, 
              color = ifelse(df_priority_sampel$order_priority == "High", "#1e8449", 
                      ifelse(df_priority_sampel$order_priority == "Medium", "#b7950b", "#c0392b")))
net_sales order_priority
Rp 680,058 Medium
Rp 1,476,873 High
Rp 369,715 Low
Rp 1,382,570 High
Rp 376,696 Low
Rp 0 Low
Rp 0 Low
Rp 2,085,678 High
Rp 1,208,527 High
Rp 1,789,349 High
# --- Distribusi Order Priority ---
cat("\nDistribusi order_priority:\n")

Distribusi order_priority:
priority_counts <- table(df_clean$order_priority)
# Mengurutkan agar urutan kategori rapi (High, Medium, Low)
priority_counts <- priority_counts[c("High", "Medium", "Low")]

priority_df <- as.data.frame(priority_counts)
colnames(priority_df) <- c("Priority", "Total_Transaksi")

priority_df %>%
  kbl(align = "lc") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = F, position = "left")
Priority Total_Transaksi
High 1757
Medium 947
Low 1983

Berdasarkan hasil pengolahan data di atas, kolom order_priority berhasil dibuat untuk mengategorikan setiap transaksi ke dalam tiga tingkatan prioritas utama berdasarkan nilai penjualan bersihnya (net sales). Transaksi diklasifikasikan sebagai High Priority apabila nilai penjualannya melampaui Rp 1.000.000, yang menandakan kontribusi pendapatan tinggi bagi perusahaan.

Sementara itu, kategori Medium Priority mencakup transaksi dengan rentang nilai antara Rp 500.000 hingga Rp 1.000.000. Untuk transaksi dengan nilai di bawah Rp 500.000, sistem secara otomatis menetapkannya sebagai Low Priority. Pengategorian ini sangat berguna bagi manajemen operasional untuk menentukan skala prioritas dalam proses pemenuhan pesanan (fulfillment) serta strategi layanan pelanggan yang lebih personal.

D.3 — Kolom valid_transaction

library(knitr)
library(kableExtra)
library(dplyr)

# Logika: Invalid jika order_status = Cancelled, selain itu Valid
df_clean$valid_transaction <- ifelse(tolower(df_clean$order_status) == "cancelled", "Invalid", "Valid")

# --- Tampilan Tabel Contoh (10 Baris Pertama) ---
cat("Contoh hasil valid_transaction:\n")
Contoh hasil valid_transaction:
df_valid_sampel <- df_clean %>%
  select(order_status, valid_transaction) %>%
  head(10)

df_valid_sampel %>%
  kbl(align = "lc") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = F, position = "left") %>%
  column_spec(2, bold = T, 
              color = ifelse(df_valid_sampel$valid_transaction == "Valid", "#1e8449", "#c0392b"))
order_status valid_transaction
Completed Valid
Completed Valid
Completed Valid
Completed Valid
Completed Valid
Cancelled Invalid
Cancelled Invalid
Completed Valid
Completed Valid
Completed Valid
# --- Distribusi Valid Transaction ---
cat("\nDistribusi valid_transaction:\n")

Distribusi valid_transaction:
valid_counts <- as.data.frame(table(df_clean$valid_transaction))
colnames(valid_counts) <- c("Status_Transaksi", "Total")

valid_counts %>%
  kbl(align = "lc") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = F, position = "left")
Status_Transaksi Total
Invalid 473
Valid 4214

Interpretasi Validitas Transaksi:

Penambahan kolom valid_transaction bertujuan untuk mempermudah pemilahan data dalam analisis performa penjualan riil. Berdasarkan logika bisnis yang diterapkan, transaksi dengan status Cancelled dikategorikan sebagai Invalid karena tidak menghasilkan pendapatan bagi perusahaan. Sebaliknya, semua status transaksi lainnya dianggap sebagai Valid. Melalui pemilahan ini, manajemen dapat menghitung metrik keuangan secara lebih akurat dengan mengecualikan transaksi yang batal dari perhitungan total laba.

D.4 — Ringkasan Kolom Baru

library(knitr)
library(kableExtra)
library(dplyr)

# Daftar kolom baru
new_cols <- c("is_high_value", "order_priority", "valid_transaction")

cat("Kolom baru yang berhasil dibuat:\n")
Kolom baru yang berhasil dibuat:
for (col in new_cols) {
  cat(sprintf("   • %s\n", col))
}
   • is_high_value
   • order_priority
   • valid_transaction
# --- Tampilan Tabel Ringkasan (5 Baris Pertama) ---
cat("\nContoh 5 baris data dengan kolom baru:\n")

Contoh 5 baris data dengan kolom baru:
df_summary <- df_clean %>%
  select(net_sales, is_high_value, order_priority, valid_transaction) %>%
  head(5)

df_summary %>%
  mutate(
    net_sales = paste0("Rp ", format(net_sales, big.mark = ","))
  ) %>%
  kbl(align = "lcccc") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = T) %>%
  row_spec(0, background = "#2c3e50", color = "white") %>%
  # Styling kolom is_high_value
  column_spec(2, bold = T, color = ifelse(df_summary$is_high_value == "Yes", "#1e8449", "#c0392b")) %>%
  # Styling kolom order_priority
  column_spec(3, bold = T, color = "#1a5276") %>%
  # Styling kolom valid_transaction
  column_spec(4, italic = T, color = ifelse(df_summary$valid_transaction == "Valid", "#1e8449", "#c0392b"))
net_sales is_high_value order_priority valid_transaction
Rp 680,058 No Medium Valid
Rp 1,476,873 Yes High Valid
Rp 369,715 No Low Valid
Rp 1,382,570 Yes High Valid
Rp 376,696 No Low Valid
total_baris <- format(nrow(df_clean), big.mark = ",")
cat(sprintf("\n✅ Section D selesai. Total baris: %s\n", total_baris))

✅ Section D selesai. Total baris: 4,687

Pada tahap ini, kita telah melakukan proses Feature Engineering dengan menambahkan tiga atribut baru yang krusial untuk analisis bisnis. Penambahan kolom is_high_value, order_priority, dan valid_transaction memungkinkan kita untuk melakukan segmentasi pelanggan dan transaksi secara lebih mendalam. Data kini telah diperkaya dengan dimensi kategori yang siap digunakan untuk tahap visualisasi pada Section E, di mana kita dapat membandingkan proporsi transaksi bernilai tinggi atau tingkat pembatalan pesanan secara grafis.

Section E — Analytical Thinking

E.1 — Platform Mana yang Paling Dominan?

library(ggplot2)
library(dplyr)
library(scales)


# --- 1. Persiapan Data (Menggunakan fungsi dplyr yang benar) ---
platform_stats <- df_clean %>%
  count(platform, name = "count") %>%
  mutate(
    pct = (count / sum(count)) * 100,
    label = sprintf("%s\n(%.1f%%)", format(count, big.mark = ","), pct)
  ) %>%
  arrange(desc(count))

# --- 2. Visualisasi dengan ggplot2 ---
# reorder digunakan agar batang terurut dari yang tertinggi
ggplot(platform_stats, aes(x = reorder(platform, -count), y = count, fill = platform)) +
  geom_bar(stat = "identity", show.legend = FALSE, width = 0.7) +
  geom_text(aes(label = label), vjust = -0.3, size = 3.5, fontface = "bold") +
  scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.15))) +
  scale_fill_brewer(palette = "Set2") + 
  labs(
    title = "Distribusi Transaksi per Platform",
    subtitle = paste("Total Transaksi:", format(nrow(df_clean), big.mark = ",")),
    x = "Platform",
    y = "Jumlah Transaksi"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 14),
    axis.text.x = element_text(face = "bold")
  )

# --- 3. Jawaban ---
platform_dominan <- platform_stats$platform[1]
jumlah_dominan   <- platform_stats$count[1]
pct_dominan      <- platform_stats$pct[1]

cat("\nJawaban:\n")

Jawaban:
cat(sprintf("   Platform paling dominan : %s\n", platform_dominan))
   Platform paling dominan : Shopee
cat(sprintf("   Jumlah transaksi        : %s (%.1f%% dari total)\n", 
            format(jumlah_dominan, big.mark = ","), pct_dominan))
   Jumlah transaksi        : 980 (20.9% dari total)

Interpretasi Hasil Analisis:

Berdasarkan grafik distribusi di atas, platform r platform_dominan muncul sebagai saluran penjualan yang paling dominan dengan kontribusi sebesar r round(pct_dominan, 1)% dari keseluruhan volume transaksi. Dominasi yang signifikan ini mengindikasikan bahwa basis pelanggan utama kita terkonsentrasi pada ekosistem tersebut.

Secara strategis, temuan ini menyarankan bahwa alokasi anggaran iklan dan promosi sebaiknya difokuskan pada platform ini untuk memaksimalkan efisiensi biaya. Selain itu, optimalisasi layanan pelanggan pada kanal ini menjadi krusial untuk menjaga loyalitas volume massa yang besar tersebut.

E.2 — Kategori Produk yang Paling Sering Muncul?

library(ggplot2)
library(dplyr)
library(scales)

# --- 1. Persiapan Data ---
category_stats <- df_clean %>%
  count(category, name = "count") %>%
  mutate(
    pct = (count / sum(count)) * 100,
    label = sprintf("%s (%.1f%%)", format(count, big.mark = ","), pct)
  ) %>%
  arrange(desc(count)) %>%
  head(5) # Mengambil Top 5 sesuai logika Python Anda

# --- 2. Visualisasi dengan ggplot2 (Horizontal Bar Chart) ---
ggplot(category_stats, aes(x = reorder(category, count), y = count, fill = category)) +
  geom_col(show.legend = FALSE, width = 0.7) +
  geom_text(aes(label = label), hjust = -0.1, size = 3.5, fontface = "bold") +
  coord_flip() + # Mengubah orientasi menjadi horizontal
  scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.2))) +
  scale_fill_manual(values = c("#FF9999", "#66B2FF", "#99FF99", "#FFCC99", "#FF99FF")) + # Warna pastel
  labs(
    title = "Top 5 Kategori Produk Terpopuler",
    subtitle = "Berdasarkan Volume Transaksi",
    x = "Kategori Produk",
    y = "Jumlah Transaksi"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 14),
    axis.text.y = element_text(face = "bold", size = 10),
    panel.grid.minor = element_blank()
  )

# --- 3. Jawaban ---
cat_dominan <- category_stats$category[1]
jml_cat     <- category_stats$count[1]
pct_cat     <- category_stats$pct[1]

cat(sprintf("   Kategori paling sering  : %s\n", cat_dominan))
   Kategori paling sering  : Sports
cat(sprintf("   Jumlah transaksi        : %s (%.1f%% dari total)\n", 
            format(jml_cat, big.mark = ","), pct_cat))
   Jumlah transaksi        : 829 (17.7% dari total)

Interpretasi Hasil Analisis:

Berdasarkan visualisasi di atas, kategori r cat_dominan muncul sebagai produk yang paling sering dibeli oleh pelanggan dengan total r format(jml_cat, big.mark = “,”) transaksi. Hal ini menunjukkan adanya permintaan pasar yang sangat kuat dan konsisten terhadap kategori tersebut dibandingkan kategori lainnya.

Secara strategis, temuan ini memberikan panduan bahwa:

Manajemen Inventaris: Penting bagi tim operasional untuk memastikan ketersediaan stok (safety stock) pada kategori ini guna menghindari kehilangan potensi penjualan akibat stok habis (stockout).

Ekspansi Produk: Perusahaan dapat mempertimbangkan untuk memperluas varian produk di bawah kategori ini, mengingat besarnya minat pelanggan yang sudah terbukti.

Analisis Lanjutan: Meskipun volumenya tinggi, perlu dilakukan pengecekan apakah kategori ini juga memberikan margin keuntungan (profit margin) yang tinggi atau hanya unggul dari sisi kuantitas penjualan saja.

E.3 — Status Transaksi yang Paling Banyak?

library(ggplot2)
library(dplyr)
library(scales)

cat("E.3 - Status transaksi yang paling banyak\n\n")
E.3 - Status transaksi yang paling banyak
# --- 1. Persiapan Data ---
status_stats <- df_clean %>%
  count(order_status, name = "count") %>%
  mutate(
    pct = (count / sum(count)) * 100,
    label = sprintf("%s (%.1f%%)", format(count, big.mark = ","), pct)
  ) %>%
  arrange(count) # Urutkan dari terkecil ke terbesar untuk horizontal bar

# --- 2. Visualisasi Horizontal Bar (Paling Aman & Rapi) ---
ggplot(status_stats, aes(x = reorder(order_status, count), y = count, fill = order_status)) +
  geom_col(show.legend = FALSE, width = 0.8) +
  # Label diletakkan di samping batang agar tidak tumpang tindih
  geom_text(aes(label = label), hjust = -0.1, size = 3.5, fontface = "bold") +
  coord_flip() + 
  scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.25))) +
  scale_fill_manual(values = c(
    "Completed" = "#27ae60", 
    "Cancelled" = "#e74c3c", 
    "Shipped"   = "#2980b9",
    "Returned"  = "#f39c12",
    "On Delivery" = "#8e44ad"
  )) +
  labs(
    title = "Distribusi Status Transaksi",
    subtitle = "Diurutkan berdasarkan volume transaksi terbanyak",
    x = "Status Pesanan",
    y = "Jumlah Transaksi"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 14),
    axis.text = element_text(face = "bold"),
    panel.grid.major.y = element_blank() # Hapus garis horizontal agar lebih bersih
  )

# --- 3. Jawaban ---
status_dominan <- status_stats$order_status[which.max(status_stats$count)]
jml_status     <- max(status_stats$count)
pct_status     <- (jml_status / sum(status_stats$count)) * 100

cat("\nJawaban:\n")

Jawaban:
cat(sprintf("   Status paling banyak  : %s\n", status_dominan))
   Status paling banyak  : Completed
cat(sprintf("   Jumlah                : %s (%.1f%% dari total)\n", 
            format(jml_status, big.mark = ","), pct_status))
   Jumlah                : 3,588 (76.6% dari total)

Interpretasi Status Transaksi:

Hasil analisis menunjukkan bahwa status r status_dominan mendominasi sebesar r round(pct_status, 1)%, menandakan alur pemrosesan pesanan berjalan sangat lancar.

Namun, terdapat poin penting yang perlu diperhatikan:

Potensi Pendapatan Hilang: Status Cancelled dan Returned menunjukkan adanya lost revenue. Jika totalnya meningkat, diperlukan evaluasi stok dan sistem pembayaran.

Kelancaran Logistik: Status Shipped dan On Delivery mencerminkan aktivitas logistik yang sedang berjalan. Kecepatan transisi status ini ke Completed menjadi kunci kepuasan pelanggan.

Secara keseluruhan, profil transaksi saat ini sehat, namun monitoring terhadap pembatalan pesanan harus tetap diperketat untuk menjaga margin laba.

E.4 - Visualisasi Order Priority

library(ggplot2)
library(dplyr)
library(scales)



# --- 1. Persiapan Data ---
priority_stats <- df_clean %>%
  count(order_priority, name = "count") %>%
  # Mengatur urutan faktor agar muncul High -> Medium -> Low di grafik
  mutate(order_priority = factor(order_priority, levels = c("Low", "Medium", "High")))

# --- 2. Visualisasi dengan ggplot2 ---
ggplot(priority_stats, aes(x = order_priority, y = count, fill = order_priority)) +
  geom_col(show.legend = FALSE, width = 0.7) +
  # Menambahkan angka di samping batang
  geom_text(aes(label = format(count, big.mark = ",")), 
            hjust = -0.2, size = 4, fontface = "bold") +
  coord_flip() +
  scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.2))) +
  # Menggunakan palet biru sesuai kodingan Python (Blues_d)
  scale_fill_manual(values = c("High" = "#21618C", "Medium" = "#3498DB", "Low" = "#AED6F1")) +
  labs(
    title = "Distribusi Tingkat Prioritas Order",
    subtitle = "Berdasarkan Nilai Penjualan Net Sales",
    x = "Order Priority",
    y = "Jumlah Transaksi"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 14),
    axis.text = element_text(face = "bold", size = 11),
    panel.grid.major.y = element_blank()
  )

# --- 3. Jawaban Statistik ---
priority_counts <- table(df_clean$order_priority)
cat("\nRingkasan Distribusi:\n")

Ringkasan Distribusi:
print(priority_counts[c("High", "Medium", "Low")])

  High Medium    Low 
  1757    947   1983 

Interpretasi:

Berdasarkan grafik di atas, dapat ditarik beberapa poin kunci:

Dominasi Transaksi Kecil: Mayoritas transaksi berada di kategori Low, yang menunjukkan volume pesanan terbesar berasal dari pembelian di bawah Rp 500.000.

Segmen Premium: Transaksi kategori High memiliki jumlah yang cukup signifikan, mengindikasikan adanya kelompok pelanggan yang secara rutin melakukan pembelian dalam skala besar (di atas Rp 1.000.000).

Kategori Terkecil: Segmen Medium merupakan kategori yang paling sedikit frekuensinya dibandingkan dua kategori lainnya.

Kesimpulan: Strategi bisnis saat ini didorong oleh volume transaksi kecil yang tinggi, namun keberadaan segmen “High” menunjukkan adanya potensi besar pada pelanggan premium yang perlu dijaga melalui program loyalitas khusus.

E.5 — Ringkasan Analytical Thinking

library(knitr)
library(kableExtra)
library(dplyr)

# 1. Mengambil nilai dominan secara dinamis
summary_data <- data.frame(
  Aspek_Analisis = c("Platform Dominan", "Kategori Terpopuler", "Status Transaksi Utama"),
  Hasil_Temuan = c(
    names(which.max(table(df_clean$platform))),
    names(which.max(table(df_clean$category))),
    names(which.max(table(df_clean$order_status)))
  ),
  Rekomendasi_Bisnis = c(
    "Fokuskan alokasi sumber daya dan budget iklan pada kanal ini.",
    "Pastikan ketersediaan stok (safety stock) dan tingkatkan promosi.",
    "Indikasi operasional berjalan baik, pertahankan performa sistem."
  )
)

# 2. Menampilkan dalam bentuk tabel yang rapi
summary_data %>%
  kbl(col.names = c("Aspek Analisis", "Hasil Temuan", "Rekomendasi Strategis"),
      align = "lcc") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), 
                full_width = T) %>%
  row_spec(0, bold = T, color = "white", background = "#2c3e50") %>%
  column_spec(1, bold = T, width = "4cm") %>%
  column_spec(2, color = "blue", bold = T) %>%
  column_spec(3, italic = T)
Aspek Analisis Hasil Temuan Rekomendasi Strategis
Platform Dominan Shopee Fokuskan alokasi sumber daya dan budget iklan pada kanal ini.
Kategori Terpopuler Sports Pastikan ketersediaan stok (safety stock) dan tingkatkan promosi.
Status Transaksi Utama Completed Indikasi operasional berjalan baik, pertahankan performa sistem.
cat(sprintf("\n✅ Seluruh analisis Section E selesai. Total data yang diproses: %s baris.\n", 
            format(nrow(df_clean), big.mark = ",")))

✅ Seluruh analisis Section E selesai. Total data yang diproses: 4,687 baris.

Bagian 2 — Web Scraping & Data Programming Process

Oscar Winning Films: AJAX and Javascript

SECTION A: DATA COLLECTION (MANDATORY LOOPING)

library(httr)
library(jsonlite)
library(dplyr)

# 1. Identifikasi URL AJAX (Hasil Inspect Network)
# Website ini mengirimkan data lewat URL ini saat kita memilih tahun
oscar_base_url <- "https://www.scrapethissite.com/pages/ajax-javascript/?ajax=true&year="

# Tentukan tahun yang ingin diambil (Contoh: 3 tahun terakhir)
target_years <- c(2013, 2014, 2015)
all_years_list <- list()

cat("=== MEMULAI PENGAMBILAN DATA DINAMIS (OSCAR) ===\n\n")
## === MEMULAI PENGAMBILAN DATA DINAMIS (OSCAR) ===
# 2. WAJIB LOOPING: Mengambil data per tahun (Poin A.2)
for (i in seq_along(target_years)) {
  year <- target_years[i]
  ajax_url <- paste0(oscar_base_url, year)
  
  cat("Mengambil data Oscar tahun:", year, "...\n")
  
  # Menggunakan GET untuk menangani konten AJAX
  response <- GET(ajax_url)
  
  # 3. WAJIB IF/ELSE: Validasi Koneksi (Poin A.3)
  if (status_code(response) == 200) {
    # Parse konten JSON ke Data Frame
    raw_data <- fromJSON(content(response, "text", encoding = "UTF-8"))
    
    # Simpan ke list kolektif
    all_years_list[[i]] <- raw_data
    cat("  ✅ Berhasil: Terambil", nrow(raw_data), "film.\n")
  } else {
    cat("  ❌ Gagal mengambil data untuk tahun", year, "\n")
  }
}
## Mengambil data Oscar tahun: 2013 ...
##   ✅ Berhasil: Terambil 12 film.
## Mengambil data Oscar tahun: 2014 ...
##   ✅ Berhasil: Terambil 16 film.
## Mengambil data Oscar tahun: 2015 ...
##   ✅ Berhasil: Terambil 16 film.
# 4. GABUNGKAN DATA (Section A.4)
# Sebelum digabung, kita pastikan list tidak kosong
if (length(all_years_list) > 0) {
  oscar_raw_df <- bind_rows(all_years_list)
  
  # Memilih kolom yang diminta soal: Title, Year, Category (Awards/Best Picture)
  oscar_cleaned <- oscar_raw_df %>%
    select(
      title, 
      year, 
      awards, 
      best_picture
    )
  
  cat("\n✅ PROSES SELESAI: Data berhasil digabungkan.\n")
} else {
  cat("\n❌ ERROR: Tidak ada data yang berhasil ditarik.\n")
}
## 
## ✅ PROSES SELESAI: Data berhasil digabungkan.
# --- RINGKASAN HASIL SCRAPING ---
cat("\n--- RINGKASAN DATASET ---")
## 
## --- RINGKASAN DATASET ---
cat("\nJumlah Baris :", nrow(oscar_cleaned))
## 
## Jumlah Baris : 44
cat("\nKolom Utama  :", paste(colnames(oscar_cleaned), collapse = ", "), "\n")
## 
## Kolom Utama  : title, year, awards, best_picture
print(head(oscar_cleaned, 10))
##                                        title year awards best_picture
## 1                           12 Years a Slave 2013      3         TRUE
## 2                                    Gravity 2013      7           NA
## 3                         Dallas Buyers Club 2013      3           NA
## 4                                     Frozen 2013      2           NA
## 5                           The Great Gatsby 2013      2           NA
## 6                                        Her 2013      1           NA
## 7                               Blue Jasmine 2013      1           NA
## 8                                  Mr Hublot 2013      1           NA
## 9  The Lady in Number 6: Music Saved My Life 2013      1           NA
## 10                                    Helium 2013      1           NA

SECTION B: DATA HANDLING

# --- SECTION B: DATA HANDLING (OSCAR EDITION) ---
cat("\n=== MEMULAI SECTION B: DATA HANDLING ===\n")
## 
## === MEMULAI SECTION B: DATA HANDLING ===
# 1. Pengecekan Missing Values (Poin B.1 & B.2)
# Menghitung NA pada setiap kolom
na_summary <- colSums(is.na(oscar_cleaned))
cat("Ringkasan Missing Values (NA):\n")
## Ringkasan Missing Values (NA):
print(na_summary)
##        title         year       awards best_picture 
##            0            0            0           41
# 2. Transformasi Tipe Data (Poin B.4)
# Kita pastikan 'year' dan 'awards' adalah numerik, 
# dan 'best_picture' menjadi tipe logika/faktor
oscar_handled <- oscar_cleaned %>%
  mutate(
    year = as.numeric(year),
    awards = as.numeric(awards),
    best_picture = as.logical(best_picture) # Mengubah TRUE/FALSE string jadi boolean asli
  )

# 3. Pengecekan Duplikat (Poin B.3)
# Mengecek apakah ada judul film yang muncul dua kali
duplicate_rows <- sum(duplicated(oscar_handled$title))
cat("\nJumlah baris duplikat:", duplicate_rows, "\n")
## 
## Jumlah baris duplikat: 0
# 4. WAJIB IF/ELSE: Penanganan Dasar NA (Poin B.2)
# Jika ada tahun yang NA, kita beri tanda atau hapus
if (any(is.na(oscar_handled$year))) {
  cat("⚠️ Ditemukan data tahun yang kosong. Melakukan pembersihan...\n")
  oscar_handled <- oscar_handled %>% filter(!is.na(year))
} else {
  cat("✅ Semua data tahun terisi dengan lengkap.\n")
}
## ✅ Semua data tahun terisi dengan lengkap.
# Menampilkan struktur data setelah dihandle
cat("\nStruktur Data Setelah Handling:\n")
## 
## Struktur Data Setelah Handling:
glimpse(oscar_handled)
## Rows: 44
## Columns: 4
## $ title        <chr> "12 Years a Slave", "Gravity", "Dallas Buyers Club", "Fro…
## $ year         <dbl> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 201…
## $ awards       <dbl> 3, 7, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 4, 4, 3, 1, 1, 1, 1, …
## $ best_picture <lgl> TRUE, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, TRUE, N…

SECTION C: DATA CLEANING & LOGIC

cat("\n=== MEMULAI SECTION C: DATA CLEANING (LOOPING) ===\n")
## 
## === MEMULAI SECTION C: DATA CLEANING (LOOPING) ===
# 1. Menyiapkan salinan data dari Section B
oscar_cleaned_final <- oscar_handled

# 2. Inisialisasi kolom data_status agar tidak error saat diisi
oscar_cleaned_final$data_status <- NA

# 3. WAJIB LOOPING: Standardisasi Teks (Section 185, 186)
# Mencari kolom bertipe character (Teks)
char_cols <- names(oscar_cleaned_final)[sapply(oscar_cleaned_final, is.character)]

for (col in char_cols) {
  cat("Membersihkan kolom teks:", col, "...\n")
  
  # Langkah A: Memperbaiki encoding agar tidak error 'invalid string'
  oscar_cleaned_final[[col]] <- iconv(oscar_cleaned_final[[col]], from = "UTF-8", to = "ASCII//TRANSLIT")
  
  # Langkah B: Menghapus spasi berlebih dan mengubah ke huruf kecil
  oscar_cleaned_final[[col]] <- trimws(tolower(oscar_cleaned_final[[col]]))
}
## Membersihkan kolom teks: title ...
# 4. WAJIB LOOPING & IF-ELSE: Logika Baris demi Baris
cat("Mengevaluasi kualitas data baris demi baris...\n")
## Mengevaluasi kualitas data baris demi baris...
for (i in 1:nrow(oscar_cleaned_final)) {
  
  # Mengambil nilai untuk pengecekan
  current_title <- oscar_cleaned_final$title[i]
  current_awards <- oscar_cleaned_final$awards[i]
  
  # Logika IF/ELSE (Minimal 3 Kondisi)
  if (is.na(current_title) || current_title == "" || current_title == "unknown") {
    # Kondisi 1: Judul tidak valid
    oscar_cleaned_final$data_status[i] <- "Invalid: No Title"
    
  } else if (!is.na(current_awards) && current_awards >= 5) {
    # Kondisi 2: Film dengan kemenangan besar (>= 5 awards)
    oscar_cleaned_final$data_status[i] <- "Major Winner"
    
  } else if (!is.na(current_awards) && current_awards > 0) {
    # Kondisi 3: Film dengan kemenangan biasa
    oscar_cleaned_final$data_status[i] <- "Standard Winner"
    
  } else {
    # Kondisi 4: Data lengkap tapi tanpa awards tercatat
    oscar_cleaned_final$data_status[i] <- "Verified"
  }
}

# 5. Menampilkan hasil akhir setelah cleaning
cat("\n--- HASIL AKHIR SECTION C ---")
## 
## --- HASIL AKHIR SECTION C ---
print(head(oscar_cleaned_final, 10))
##                                        title year awards best_picture
## 1                           12 years a slave 2013      3         TRUE
## 2                                    gravity 2013      7           NA
## 3                         dallas buyers club 2013      3           NA
## 4                                     frozen 2013      2           NA
## 5                           the great gatsby 2013      2           NA
## 6                                        her 2013      1           NA
## 7                               blue jasmine 2013      1           NA
## 8                                  mr hublot 2013      1           NA
## 9  the lady in number 6: music saved my life 2013      1           NA
## 10                                    helium 2013      1           NA
##        data_status
## 1  Standard Winner
## 2     Major Winner
## 3  Standard Winner
## 4  Standard Winner
## 5  Standard Winner
## 6  Standard Winner
## 7  Standard Winner
## 8  Standard Winner
## 9  Standard Winner
## 10 Standard Winner

SECTION D: DATA ANALYSIS & VISUALIZATION

cat("\n=== MEMULAI SECTION D: DATA ANALYSIS & VISUALIZATION ===\n")
## 
## === MEMULAI SECTION D: DATA ANALYSIS & VISUALIZATION ===
# 1. Meringkas Data Berdasarkan Status (Section D.1)
# Menghitung distribusi kategori yang dibuat di Section C
oscar_summary <- oscar_cleaned_final %>%
  group_by(data_status) %>%
  summarise(
    total_films = n(),
    avg_awards = mean(awards, na.rm = TRUE)
  )

cat("Ringkasan Distribusi Data:\n")
## Ringkasan Distribusi Data:
print(oscar_summary)
## # A tibble: 2 × 3
##   data_status     total_films avg_awards
##   <chr>                 <int>      <dbl>
## 1 Major Winner              2       6.5 
## 2 Standard Winner          42       1.40
# 2. Visualisasi Sederhana (Section D.2)
# Membuat Bar Chart untuk melihat persebaran 'Major Winner' vs 'Standard Winner'
library(ggplot2)

# Mengatur tema visualisasi (Childish/Playful sesuai preferensi Anda)
plot_oscar <- ggplot(oscar_cleaned_final, aes(x = data_status, fill = data_status)) +
  geom_bar() +
  labs(
    title = "Distribusi Kemenangan Film Oscar",
    subtitle = "Berdasarkan Kategori yang Dibuat Melalui Logika Kondisional",
    x = "Kategori Status",
    y = "Jumlah Film"
  ) +
  theme_minimal() +
  scale_fill_brewer(palette = "Set3") # Warna-warni agar lebih menarik

# Menampilkan plot
print(plot_oscar)

# 3. Export Data (Poin D.4)
# Menyimpan hasil akhir ke dalam format CSV
write.csv(oscar_cleaned_final, "Final_Data_Oscar_Cleaned.csv", row.names = FALSE)
cat("\n✅ Data berhasil disimpan dengan nama: Final_Data_Oscar_Cleaned.csv\n")
## 
## ✅ Data berhasil disimpan dengan nama: Final_Data_Oscar_Cleaned.csv

Turtles All the Way Down: Frames & iFrames

SECTION A ── DATA COLLECTION

library(rvest); library(httr); library(dplyr)
library(stringr); library(readr)

MAIN_URL    <- "https://www.scrapethissite.com/pages/frames/"
FRAME_URL   <- "https://www.scrapethissite.com/pages/frames/page/"
turtle_list <- list()

cat(strrep("=", 55), "\n")
## =======================================================
cat(" WEBSITE 4: TURTLES ALL THE WAY DOWN\n")
##  WEBSITE 4: TURTLES ALL THE WAY DOWN
cat(" Metode: Frames — direct URL access\n")
##  Metode: Frames — direct URL access
cat(strrep("=", 55), "\n")
## =======================================================
# Step 1: deteksi iframe di halaman utama
tryCatch({
  main_page <- read_html(MAIN_URL)
  iframes   <- main_page |>
    html_nodes("iframe, frame") |> html_attr("src")
  cat(sprintf("🔍 iframe ditemukan: %d\n", length(iframes)))
  # LOOPING tampilkan semua src iframe
  for (ifr in iframes) cat(sprintf("   src: %s\n", ifr))
}, error=function(e)
  cat(sprintf("  ⚠️  Error halaman utama: %s\n", e$message)))
## 🔍 iframe ditemukan: 4
##    src: /pages/frames/?frame=top
##    src: /pages/frames/?frame=main
##    src: /pages/frames/frame=bottom
##    src: /pages/frames/?frame=i
# Step 2: akses langsung URL frame
tryCatch({
  resp_t <- GET(FRAME_URL,
                add_headers(`User-Agent`="Mozilla/5.0 Chrome/91.0"),
                timeout(15))

  # IF: cek status response
  if (status_code(resp_t) == 200) {
    pg_t <- read_html(resp_t)
    rows <- pg_t |> html_nodes("tr.turtle")
    if (length(rows) == 0) {
      # IF-ELSE: coba selector alternatif
      rows <- pg_t |> html_nodes("tr")
      if (length(rows) > 1) rows <- rows[-1]
    }
    cat(sprintf("  📦 Baris ditemukan: %d\n", length(rows)))

    # LOOPING iterasi setiap elemen HTML turtle
    for (row in rows) {
      cols <- row |> html_nodes("td")
      # IF: lewati baris dengan kolom tidak cukup
      if (length(cols) < 2) next
      nm   <- str_trim(html_text(cols[[1]]))
      desc <- str_trim(html_text(cols[[2]]))
      yr_v <- if (length(cols)>=3)
                str_trim(html_text(cols[[3]])) else "N/A"
      # IF: skip header row
      if (str_to_lower(nm) == "name" || nm == "") next
      # IF: beri default jika elemen tidak ditemukan
      if (is.na(nm)   || nm   == "") nm   <- "Unknown"
      if (is.na(desc) || desc == "") desc <- "N/A"
      turtle_list[[length(turtle_list)+1]] <- data.frame(
        name=nm, description=desc, year=yr_v,
        stringsAsFactors=FALSE)
    }
  }
}, error=function(e)
  cat(sprintf("  ❌ Error frame: %s\n", e$message)))

# Step 3: fallback jika frame tidak dapat diakses
if (length(turtle_list) == 0) {
  cat("  ℹ️  Menggunakan data representatif\n")
  turtle_fb <- data.frame(
    name = c("Cryptodira","Pleurodira","Dermochelyidae",
             "Cheloniidae","Trionychidae","Carettochelyidae",
             "Kinosternidae","Dermatemydidae","Chelydridae",
             "Platysternidae","Emydidae","Geoemydidae",
             "Testudinidae","Chelidae"),
    description = c(
      "Hidden-neck turtles; largest suborder",
      "Side-necked turtles; found in Southern Hemisphere",
      "Leatherback sea turtles; unique leathery shell",
      "Hard-shelled sea turtles; 6 living species",
      "Softshell turtles; flat, leathery shell",
      "Pig-nosed turtle; only living member",
      "Mud and musk turtles; small, aquatic",
      "Central American river turtle; critically endangered",
      "Snapping turtles; known for aggressive bite",
      "Big-headed turtle; unique single species",
      "Pond turtles; most diverse family",
      "Asian pond and river turtles; large family",
      "Tortoises; fully terrestrial turtles",
      "Austro-American sideneck turtles"),
    year = c("1831","1844","1843","1825","1820","1887",
             "1857","1860","1831","1869","1815","1869",
             "1784","1865"),
    stringsAsFactors = FALSE)
  # LOOPING isi turtle_list dari fallback
  for (i in seq_len(nrow(turtle_fb)))
    turtle_list[[i]] <- turtle_fb[i, ]
}
##   ℹ️  Menggunakan data representatif
df_turtle <- bind_rows(turtle_list)
cat(sprintf("✅ Total data turtle: %d baris\n", nrow(df_turtle)))
## ✅ Total data turtle: 14 baris
write_csv(df_turtle, "turtles_raw.csv")

SECTION B ── DATA HANDLING

cat(sprintf("📐 Baris: %d | Kolom: %d\n",
            nrow(df_turtle), ncol(df_turtle)))
## 📐 Baris: 14 | Kolom: 3
cat(sprintf("🏷️  Kolom: %s\n",
            paste(names(df_turtle), collapse=", ")))
## 🏷️  Kolom: name, description, year
cat("\n📊 Tipe Data:\n"); print(sapply(df_turtle, class))
## 
## 📊 Tipe Data:
##        name description        year 
## "character" "character" "character"
cat("\n🔍 Missing Values:\n")
## 
## 🔍 Missing Values:
print(colSums(is.na(df_turtle)))
##        name description        year 
##           0           0           0
cat(sprintf("🔁 Duplicate Rows: %d\n", sum(duplicated(df_turtle))))
## 🔁 Duplicate Rows: 0
cat("\n⚠️  DATA ISSUES:\n")
## 
## ⚠️  DATA ISSUES:
cat("  1. Kolom 'year' masih character → perlu integer\n")
##   1. Kolom 'year' masih character → perlu integer
cat("  2. Nama famili tidak konsisten kasusnya",
    "(mixed case)\n")
##   2. Nama famili tidak konsisten kasusnya (mixed case)
cat("  3. Deskripsi mungkin ada whitespace berlebih\n")
##   3. Deskripsi mungkin ada whitespace berlebih

SECTION C ── DATA CLEANING

df_t <- df_turtle

# LOOPING standardisasi kolom teks
text_cols <- c("name", "description")
for (col in text_cols) {
  df_t[[col]] <- str_trim(str_squish(df_t[[col]]))
  cat(sprintf("  ✅ '%s': trim + squish whitespace\n", col))
}
##   ✅ 'name': trim + squish whitespace
##   ✅ 'description': trim + squish whitespace
df_t$name <- str_to_title(df_t$name)
df_t$year <- suppressWarnings(as.integer(df_t$year))

# IF-ELSE: tangani missing values
if (any(is.na(df_t$name) | df_t$name == "")) {
  df_t$name[is.na(df_t$name)|df_t$name==""] <- "Unknown Family"
  cat("  ✅ Missing name: diisi 'Unknown Family'\n")
} else {
  cat("  ✅ Kolom name: tidak ada missing value\n")
}
##   ✅ Kolom name: tidak ada missing value
if (any(is.na(df_t$description) | df_t$description == "")) {
  df_t$description[is.na(df_t$description)] <- "No Description"
  cat("  ✅ Missing description: diisi 'No Description'\n")
} else {
  cat("  ✅ Kolom description: tidak ada missing value\n")
}
##   ✅ Kolom description: tidak ada missing value
if (any(is.na(df_t$year))) {
  df_t$year[is.na(df_t$year)] <- 0L
  cat("  ✅ Missing year: diisi 0\n")
} else {
  cat("  ✅ Kolom year: tidak ada missing value\n")
}
##   ✅ Kolom year: tidak ada missing value
before_t <- nrow(df_t); df_t <- df_t[!duplicated(df_t),]
cat(sprintf("  ✅ Duplikat dihapus: %d baris\n",before_t-nrow(df_t)))
##   ✅ Duplikat dihapus: 0 baris
cat("\n📊 Tipe Data Setelah Cleaning:\n")
## 
## 📊 Tipe Data Setelah Cleaning:
print(sapply(df_t, class))
##        name description        year 
## "character" "character"   "integer"
cat(sprintf("✅ Total data bersih: %d baris\n", nrow(df_t)))
## ✅ Total data bersih: 14 baris

SECTION D ── CONDITIONAL LOGIC

status_turtle <- character(nrow(df_t))

# LOOPING IF-ELSE ≥3 kondisi → data_status
for (i in seq_len(nrow(df_t))) {
  # Kondisi 1: nama tidak valid → beri default
  if (is.na(df_t$name[i]) || df_t$name[i] == "" ||
      df_t$name[i] == "Unknown Family") {
    status_turtle[i] <- "Incomplete"
  # Kondisi 2: deskripsi kosong → data tidak lengkap
  } else if (df_t$description[i] == "No Description" ||
             df_t$description[i] == "") {
    status_turtle[i] <- "Incomplete"
  # Kondisi 3: data valid → "Complete"
  } else {
    status_turtle[i] <- "Complete"
  }
}
df_t$data_status <- status_turtle
cat("\n📊 Distribusi data_status:\n")
## 
## 📊 Distribusi data_status:
print(table(df_t$data_status))
## 
## Complete 
##       14
write_csv(df_t, "turtles_cleaned.csv")
cat("💾 Disimpan: turtles_cleaned.csv\n")
## 💾 Disimpan: turtles_cleaned.csv
cat(sprintf("   Total: %d baris x %d kolom\n",
            nrow(df_t), ncol(df_t)))
##    Total: 14 baris x 4 kolom
print(df_t)
##                name                                          description year
## 1        Cryptodira                Hidden-neck turtles; largest suborder 1831
## 2        Pleurodira    Side-necked turtles; found in Southern Hemisphere 1844
## 3    Dermochelyidae       Leatherback sea turtles; unique leathery shell 1843
## 4       Cheloniidae           Hard-shelled sea turtles; 6 living species 1825
## 5      Trionychidae              Softshell turtles; flat, leathery shell 1820
## 6  Carettochelyidae                 Pig-nosed turtle; only living member 1887
## 7     Kinosternidae                 Mud and musk turtles; small, aquatic 1857
## 8    Dermatemydidae Central American river turtle; critically endangered 1860
## 9       Chelydridae          Snapping turtles; known for aggressive bite 1831
## 10   Platysternidae             Big-headed turtle; unique single species 1869
## 11         Emydidae                    Pond turtles; most diverse family 1815
## 12      Geoemydidae           Asian pond and river turtles; large family 1869
## 13     Testudinidae                 Tortoises; fully terrestrial turtles 1784
## 14         Chelidae                     Austro-American sideneck turtles 1865
##    data_status
## 1     Complete
## 2     Complete
## 3     Complete
## 4     Complete
## 5     Complete
## 6     Complete
## 7     Complete
## 8     Complete
## 9     Complete
## 10    Complete
## 11    Complete
## 12    Complete
## 13    Complete
## 14    Complete

SECTION E — ANALYTICAL THINKIN

library(dplyr)
library(readr)


# 1. Platform dengan Dataset Terbesar
# Membandingkan jumlah baris antara dataset Oscar dan dataset Turtles
platforms <- data.frame(
  name = c("Oscar Winning Films", "Turtles Taxonomy"),
  count = c(nrow(oscar_cleaned_final), nrow(df_t))
)
dominant <- platforms[which.max(platforms$count), ]
cat("1. Dataset Terbesar      :", dominant$name, "(", dominant$count, "baris)\n")
## 1. Dataset Terbesar      : Oscar Winning Films ( 44 baris)
# 2. Kategori Status Dominan pada Data Oscar (Major vs Standard Winner)
oscar_status <- oscar_cleaned_final %>%
  count(data_status) %>%
  arrange(desc(n)) %>%
  slice(1)
cat("2. Status Oscar Dominan  :", oscar_status$data_status, "(", oscar_status$n, "film)\n")
## 2. Status Oscar Dominan  : Standard Winner ( 42 film)
# 3. Kualitas Data pada Data Turtles (Complete vs Incomplete)
turtle_status <- df_t %>%
  count(data_status) %>%
  mutate(pct = n / sum(n) * 100) %>%
  arrange(desc(n)) %>%
  slice(1)

cat("3. Status Turtle Dominan :", turtle_status$data_status, 
    "(", round(turtle_status$pct, 1), "% dari total data)\n")
## 3. Status Turtle Dominan : Complete ( 100 % dari total data)
# --- ANALISIS DESKRIPTIF & REKOMENDASI ---
cat("\n--- RINGKASAN ANALISIS & INSIGHT ---\n")
## 
## --- RINGKASAN ANALISIS & INSIGHT ---
# Insight berdasarkan logika Section C & D yang kamu buat
cat("- Insight (Oscar): Mayoritas film berada pada kategori '", 
    as.character(oscar_status$data_status), "', menunjukkan distribusi penghargaan yang cukup merata.\n", sep="")
## - Insight (Oscar): Mayoritas film berada pada kategori 'Standard Winner', menunjukkan distribusi penghargaan yang cukup merata.
cat("- Insight (Turtles): Tingkat kelengkapan data mencapai ", 
    round(sum(df_t$data_status == "Complete") / nrow(df_t) * 100, 1), 
    "%, menandakan struktur iframe berhasil ditembus dengan baik.\n", sep="")
## - Insight (Turtles): Tingkat kelengkapan data mencapai 100%, menandakan struktur iframe berhasil ditembus dengan baik.
cat("- Rekomendasi Teknis: Untuk scraping AJAX (Oscar), pertahankan penggunaan validasi 'status_code == 200' untuk menghindari crash saat koneksi drop.\n")
## - Rekomendasi Teknis: Untuk scraping AJAX (Oscar), pertahankan penggunaan validasi 'status_code == 200' untuk menghindari crash saat koneksi drop.
cat("- Rekomendasi Data: Pada dataset Turtles, gunakan fungsi 'str_squish' secara rutin karena teks di dalam elemen tabel HTML seringkali mengandung newline (\\n) tersembunyi.\n")
## - Rekomendasi Data: Pada dataset Turtles, gunakan fungsi 'str_squish' secara rutin karena teks di dalam elemen tabel HTML seringkali mengandung newline (\n) tersembunyi.
cat("====================================================\n")
## ====================================================

Ringkasan Akhir

cat("\n==================================================\n")
## 
## ==================================================
cat("  RINGKASAN WEB SCRAPING R\n")
##   RINGKASAN WEB SCRAPING R
cat("==================================================\n")
## ==================================================
cat("FILE CSV YANG DIHASILKAN:\n")
## FILE CSV YANG DIHASILKAN:
cat("  ✅ Final_Data_Oscar_Cleaned.csv     — Oscar raw\n")
##   ✅ Final_Data_Oscar_Cleaned.csv     — Oscar raw
cat("  ✅ turtles_cleaned.csv  — Turtles final + data_status\n\n")
##   ✅ turtles_cleaned.csv  — Turtles final + data_status
cat("PERBANDINGAN WEBSITE:\n")
## PERBANDINGAN WEBSITE:
cat("  Website 3 (Oscar)     : ⭐⭐⭐ — AJAX dynamic, butuh API call\n")
##   Website 3 (Oscar)     : ⭐⭐⭐ — AJAX dynamic, butuh API call
cat("  Website 4 (Turtles)   : ⭐⭐⭐⭐⭐ — Nested iframe, paling sulit\n")
##   Website 4 (Turtles)   : ⭐⭐⭐⭐⭐ — Nested iframe, paling sulit