UTS Sains Data Pemrograman

Logo ITSB
Foto Kelompok
Midterm Exam MR. Bakti Siregar, M.SC., CDS Python
R E-Commerce Web Scraping Data Science Programming
Foto Angel

Angelique
Kiyoshi
Lakeisha B.U
52250001

Foto Anin

Anindya
Kristianingputri
52250025

Foto Safina

Safina
Zahra
52250033


Pendahuluan

Ujian Tengah Semester ini berfokus pada penerapan konsep pengolahan data yang telah dipelajari selama perkuliahan. Dalam tugas ini, dilakukan serangkaian proses mulai dari pengumpulan data, pembersihan data, hingga analisis sederhana untuk menghasilkan informasi yang lebih bermakna.

Tugas ini terdiri dari dua bagian utama, yaitu pengolahan data e-commerce dan web scraping. Kedua bagian ini bertujuan untuk melatih kemampuan dalam memahami struktur data, melakukan data cleaning, serta mengolah data menjadi insight yang dapat dianalisis lebih lanjut.


1 E-COMMERCE

Mini Project: CASE STUDY E-Commerce

Pada bagian ini, dilakukan pengolahan data transaksi e-commerce yang terdiri dari berbagai atribut seperti platform, kategori produk, status transaksi, dan lainnya. Data yang tersedia masih memiliki beberapa permasalahan seperti missing values, inkonsistensi penulisan, dan duplikasi data, sehingga diperlukan proses data cleaning sebelum dilakukan analisis.

Tujuan dari bagian ini adalah untuk memahami proses pembersihan data serta mengidentifikasi pola dari data transaksi yang tersedia.


1.1 Section A

Membaca & Menggabungkan Dataset
1
Menampilkan jumlah baris, jumlah kolom, dan nama kolom untuk setiap file
2
Menggunakan looping untuk membaca beberapa file secara otomatis
3
Menggunakan if / if-else untuk mengecek apakah struktur kolom sama
struktur kolom dicek sebelum merge
4
Gabungkan data yang memiliki struktur sama menjadi 1 dataset utama
dataset utama dari file dengan kolom identik
STEP 1 & 2 - Menampilkan struktur data
Pada tahap ini dilakukan proses pengambilan dan pembacaan data dari berbagai format file zip yang tersedia, yaitu CSV, Excel, JSON, TXT, dan XML. Data terlebih dahulu diekstrak dari file terkompresi menggunakan fungsi unzip(), kemudian setiap file dibaca menggunakan fungsi yang sesuai dengan formatnya, seperti read.csv(), read_excel(), fromJSON() untuk JSON, serta xmlToDataFrame().
Untuk file TXT, digunakan struktur kontrol if untuk mendeteksi delimiter berdasarkan isi baris pertama, sehingga data dapat dibaca dengan fungsi yang tepat. Selanjutnya, dilakukan pengecekan struktur data dan dirangkum dalam bentuk tabel sebelum proses penggabungan data.
unzip("Archive.zip", exdir = "data_ecommerce")

df_csv   <- read.csv("data_ecommerce/ecommerce.csv")
df_excel <- read_excel("data_ecommerce/ecommerce.xlsx")
df_json <- as.data.frame(fromJSON("data_ecommerce/ecommerce.json"))

first_line <- readLines("data_ecommerce/ecommerce.txt", n = 1)
df_txt <- if (grepl(",", first_line)) {read_csv("data_ecommerce/ecommerce.txt", show_col_types = FALSE)
} else if (grepl(";", first_line)) {
  read_delim("data_ecommerce/ecommerce.txt", delim = ";", show_col_types = FALSE)
} else if (grepl("\\|", first_line)) {read_delim("data_ecommerce/ecommerce.txt", delim = "|", show_col_types = FALSE)
} else {read_table("data_ecommerce/ecommerce.txt")}

df_xml <- as.data.frame(xmlToDataFrame("data_ecommerce/ecommerce.xml"))
STEP 3 - Mengecek Struktur Kolom
Pada tahap ini dilakukan pengecekan kesesuaian struktur kolom antar dataset menggunakan colnames() dan sapply(). Struktur kontrol if-else digunakan untuk mengklasifikasikan dataset menjadi "Ready to merge" atau "Need adjustment". Dataset yang telah sesuai kemudian disamakan tipe datanya menggunakan lapply() dan digabungkan menjadi satu dataset utama menggunakan bind_rows().
data_list <- list(      # kasih nama ke setiap file
  CSV = df_csv, Excel = df_excel, JSON = df_json, TXT = df_txt, XML = df_xml)
data_ready <- data_list

col_struct <- lapply(data_ready, colnames)  # ambil struktur kolom
base_col <- col_struct[[1]]                 # jadikan acuan kolom pertama
status_check <- sapply(col_struct, function(cols) {   # cek struktur
  if (identical(cols, base_col)) {"Ready to merge"
  } else {"Need adjustment"}
})

hasil_cek <- data.frame(    # buat hasil cek
  File = names(data_list),
  Status = status_check
)

ready_data <- data_ready[status_check == "Ready to merge"]  # ambil yang ready
ready_data_fix <- lapply(ready_data, function(df) {         # samakan tipe data
  df[] <- lapply(df, as.character)
  df
})

dataset_utama <- bind_rows(ready_data_fix)  # merge
STEP 4 - Menggabungkan data
Pada tahap ini dilakukan pengecekan struktur data, penyamaan tipe data, dan penggabungan menggunakan bind_rows() hingga terbentuk dataset_utama yang siap dianalisis.
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
ORD01115 2024/06/20 2024/06/25 Blibli Sports Skipping Rope 131988 1 Rp 131.988 Normal Day NONE 0 0 9000 131988 Transfer Bank Returning Bekasi Low Stock delivered 4
ORD00595 2024-12-07 2024/12/09 Lazada Sports Skipping Rope 197817 3 593451 Clearance CLEAR30 30 178035 15000 415416 E-Wallet Returning Yogyakarta Low Stock delivered 5 N
ORD01655 28/07/2024 TikTok Shop Home Living Blender 314439 2 628878 Mega Campaign DISC20 20 125776 20000 503102 Virtual Account Returning Bekasi In Stock DELIVERED PRIORITY
ORD01910 2024/06/26 28/06/2024 Blibli Electronics Bluetooth Speaker 1281236 6 Rp 7.687.416 Normal Day NA 0 0 15000 7687416 Virtual Account Returning Surabaya In Stock Delivered 5 NA
ORD01142 08-05-2024 2024/08/12 Shopee Electronics Phone Charger 1343005 2 2686010 Normal Day NONE 0 0 9000 2686010 E-Wallet Returning Bekasi Low Stock DELIVERED 4 N
ORD01482 2024-07-18 Blibli Home Living Blender 104295 1 104295 Normal Day NONE 0 0 15000 0 COD VIP Bandung In Stock cancelled Y
ORD01374 2024/05/07 2024-05-10 TOKOPEDIA Sports Yoga Mat 42823 1 42823 Normal Day NONE 0 0 0 42823 COD New Semarang Preorder Delivered 5 normal

1.2 Section B

Eksplorasi Data dan Kualitas Dataset
1
Menampilkan informasi total jumlah baris & kolom (Shape) dan tipe data tiap kolom pada dataset
2
Mengecek data atau nilai kosong di tiap kolom pada dataset
3
Mengecek data yang kembar atau duplikat pada dataset
4
Mengidentifikasi 3 masalah utama pada dataset
STEP 1 - Menampilkan informasi total shape dan tipe data
Pada tahap ini dilakukan penampilan ringkasan total jumlah dan baris data menggunakan datatable().
STEP 2 - Mengecek Nilai Kosong
Pada tahap ini dilakukan pengecekan missing value dengan fungsi is.na() dan kondisi string kosong menggunakan sapply(). Hasilnya dihitung persentasenya, diurutkan berdasarkan jumlah missing, dan ditampilkan kembali dalam bentuk tabel interaktif.
missing_data <- data.frame(          # cek missing value
  Nama_Kolom = names(dataset_utama),
  Tipe_Data = sapply(dataset_utama, class),
  Jumlah_Missing = sapply(dataset_utama, function(x) {
    sum(is.na(x) | trimws(as.character(x)) == "")
  }),
  stringsAsFactors = FALSE
)

missing_data$Persentase <- round(
  (missing_data$Jumlah_Missing / nrow(dataset_utama)) * 100, 2
)
missing_data <- missing_data %>%
  arrange(desc(Jumlah_Missing))
STEP 3 - Mengecek Data Duplikat
Pada tahap ini dilakukan pengecekan ketersediaan dataset menggunakan exists() untuk memastikan data siap digunakan. Selanjutnya dihitung jumlah data duplikat dengan fungsi duplicated(), lalu hasilnya disimpan dalam bentuk tabel.
if (!exists("dataset_utama")) {
  stop("dataset_utama belum tersedia.")
}

jumlah_duplikat <- sum(duplicated(dataset_utama))  # hitung jumlah duplicate row

info_duplikat <- data.frame(
  Keterangan = "Jumlah Duplicate Row",  
  Nilai = jumlah_duplikat
)
STEP 4 - Identifikasi 3 Masalah Dataset
  • Terdapat missing values pada beberapa kolom seperti ship_date, voucher_code, discount_pct, payment_method, customer_rating, dan priority_flag.
  • Seluruh kolom masih bertipe data object, termasuk kolom numerik seperti unit_price, quantity, dan net_sales yang seharusnya bertipe numerik.
  • Ditemukan duplicate rows sebanyak 5280 data yang dapat mempengaruhi hasil analisis.

  • 1.3 Section C

    Membersihkan Data
    1
    Standarisasi platform menggunakan if
    2
    Membersihkan nilai harga/sales
    3
    Menangani masalah nilai kosong atau missing value
    4
    Standarisasi kolom order_status
    STEP 1 - Standarisasi Platform
    Pada tahap ini dilakukan standardisasi data pada kolom platform dengan membuat fungsi standardisasi_platform() yang menggunakan struktur percabangan if–else untuk menyeragamkan berbagai variasi penulisan nama platform. Proses pembersihan data dilakukan dengan fungsi seperti tolower(), trimws(), gsub(), dan grepl(). Selanjutnya, fungsi diterapkan ke seluruh data menggunakan sapply() yang berperan sebagai perulangan (loop) implisit, sehingga data platform menjadi lebih konsisten.
    df_final <- dataset_utama
    
    clean_platform <- function(x) {
      
      if (is.na(x)) return(x)
      
      x <- tolower(trimws(as.character(x)))
      x <- gsub(" ", "", x)
      
      if (grepl("shopee", x)) {
        return("Shopee")
        
      } else if (grepl("tokped|tokopedia", x)) {
        return("Tokopedia")
        
      } else if (grepl("blibli", x)) {
        return("Blibli")
        
      } else if (grepl("tiktok", x)) {
        return("Tiktok")
        
      } else if (grepl("lazada", x)) {
        return("Lazada")
        
      } else {
        return(tools::toTitleCase(x))
      }
    }
    
    df_final$platform <- sapply(df_final$platform, clean_platform)
    STEP 2 - Membersihkan Nilai Harga/Sales
    • Format "Rp xxx.xxx" → ubah ke numerik
    • Jika nilai < 0 → ubah menjadi 0

    • Proses cleaning meliputi penghapusan simbol seperti “Rp”, titik, dan koma menggunakan gsub(), kemudian dikonversi ke numerik dengan as.numeric(). Fungsi ini diterapkan ke seluruh kolom menggunakan lapply() dan sapply() sebagai perulangan (loop) implisit
    df_clean <- dataset_utama
    kolom_harga <- c("unit_price", "gross_sales", "discount_value",
                     "shipping_cost", "net_sales")
    
    clean_price <- function(x) {
      if (is.na(x)) return(x)                        # jika kosong → return apa adanya
      x <- as.character(x)
      x <- gsub("Rp", "", x)                         # hapus Rp
      x <- gsub("\\.", "", x)                        # hapus titik
      x <- gsub(",", "", x)                          # hapus koma
      x_num <- suppressWarnings(as.numeric(x))       # ubah ke angka
      if (is.na(x_num)) return(NULL)                 # jika gagal → return NULL
      if (x_num < 0) x_num <- 0                      # jika negatif → jadi 0
      return(x_num)                                  # return hasil
    }
    
    # APPLY CLEANING KE SEMUA KOLOM HARGA
    df_clean[kolom_harga] <- lapply(df_clean[kolom_harga], function(col) {
      sapply(col, clean_price)
    })
    STEP 3 - Menangani Masalah Nilai Kosong
    Pada tahap ini dilakukan penanganan missing value pada kolom payment_method dan customer_rating. Nilai yang kosong (NA atau string kosong) diperbaiki agar tidak mengganggu proses analisis. Untuk kolom payment_method, nilai kosong diisi dengan "Unknown" karena tidak tersedia informasi metode pembayaran. Sedangkan pada kolom customer_rating, nilai kosong diisi dengan 0 sebagai nilai default untuk merepresentasikan tidak adanya penilaian dari pelanggan.

    Fungsi yang digunakan:
    1. ifelse() → untuk mengganti nilai yang kosong dengan nilai default
    2. is.na() → untuk mendeteksi nilai NA
    3. trimws() → untuk mendeteksi string kosong ("")
    4. as.numeric() → untuk memastikan tipe data rating menjadi numerik
    df_clean <- dataset_utama
    df_clean$payment_method <- ifelse(
      is.na(df_clean$payment_method) | trimws(df_clean$payment_method) == "",
      "Unknown",
      df_clean$payment_method
    )
    
    df_clean$customer_rating <- ifelse(
      is.na(df_clean$customer_rating) | trimws(as.character(df_clean$customer_rating)) == "",
      0,
      df_clean$customer_rating
    )
    
    df_clean$customer_rating <- as.numeric(df_clean$customer_rating)
    STEP 4 - Standarisasi Kolom order_status
    Pada tahap ini dilakukan standardisasi pada kolom order_status dengan membuat fungsi standardisasi_status() yang menggunakan percabangan if–else untuk menyeragamkan berbagai variasi status pesanan. Proses pembersihan dilakukan dengan tolower() dan trimws(), lalu diterapkan ke seluruh data menggunakan sapply() sebagai perulangan (loop) implisit.
    df_clean <- dataset_utama
    
    sebelum <- data.frame(
      Status = names(table(df_clean$order_status, useNA = "ifany")),
      Sebelum = as.integer(table(df_clean$order_status, useNA = "ifany"))
    )
    standardisasi_status <- function(x) {
      if (is.na(x)) return(NA)
      x_clean <- trimws(tolower(as.character(x)))
      if (x_clean %in% c("completed", "delivered")) {
        "Completed"
      } else if (x_clean %in% c("cancelled", "cancel", "batal")) {
        "Cancelled"
      } else if (x_clean %in% c("returned", "retur")) {
        "Returned"
      } else if (x_clean == "shipped") {
        "Shipped"
      } else if (x_clean == "on delivery") {
        "On Delivery"
      } else {
        x
      }
    }
    
    
    df_clean$order_status <- sapply(df_clean$order_status, standardisasi_status)
    STEP 5 - Menggunakan Looping membersihkan 3 kolom
    Pada tahap ini dilakukan pembersihan data teks pada beberapa kolom menggunakan loop for untuk mengiterasi setiap kolom yang dipilih. Proses cleaning dilakukan dengan trimws() dan tolower() untuk menghilangkan spasi berlebih dan menyeragamkan huruf, serta dihitung perubahan sebelum dan sesudah cleaning. Hasilnya dicatat dalam log_cleaning, diurutkan, dan ditampilkan dalam bentuk tabel agar mudah dianalisis.
    kolom_cleaning <- c("category", "product_name", "region")
    
    sebelum_loop <- do.call(rbind, lapply(kolom_cleaning, function(col) {
      nilai <- df_clean[[col]]
      data.frame(
        Kolom = col,
        Sebelum = sum(trimws(as.character(nilai)) != as.character(nilai), na.rm = TRUE)
      )
    }))
    
    log_cleaning <- data.frame()
    
    for (col in kolom_cleaning) {
    
      sebelum_count <- sum(trimws(as.character(df_clean[[col]])) != as.character(df_clean[[col]]), na.rm = TRUE)
    
      df_clean[[col]] <- trimws(tolower(as.character(df_clean[[col]])))
    
      sesudah_count <- sum(trimws(df_clean[[col]]) != df_clean[[col]], na.rm = TRUE)
    
      log_cleaning <- rbind(log_cleaning, data.frame(
        Kolom = col,
        Sebelum = sebelum_count,
        Sesudah = sesudah_count,
        Terbersihkan = sebelum_count - sesudah_count
      ))
    }

    1.4 Section D

    Membuat Kolom Baru Menggunakan Logika Kondisi
    1
    Buat kolom is_high_value
    2
    Buat kolom order_priority
    3
    Buat kolom valid_transaction
    STEP 1 - Buat kolom is_high_value
    Pada tahap ini dilakukan pengolahan data dengan menghapus duplikasi menggunakan distinct() berdasarkan order_id, serta memastikan kolom net_sales tersedia dengan if. Selanjutnya dilakukan transformasi data menggunakan mutate() dan percabangan if-else untuk mengisi nilai kosong menjadi 0 dan membuat variabel baru is_high_value. Hasilnya kemudian diringkas dengan count() dan divisualisasikan menggunakan plot_ly() dalam bentuk donut chart.
    df_final <- dataset_utama %>%
      distinct(order_id, .keep_all = TRUE)
    
    if (!"net_sales" %in% names(df_final)) {
      stop("Kolom net_sales tidak ditemukan di dataset_utama")
    }
    
    df_final <- df_final %>%
      mutate(
        net_sales = ifelse(is.na(net_sales), 0, net_sales),
        is_high_value = ifelse(net_sales > 1000000, "Yes", "No")
      )

    Berdasarkan visualisasi, terdapat 707 transaksi (36%) yang termasuk dalam kategori high value (“Yes”), sedangkan 1259 transaksi (64%) termasuk dalam kategori non high value (“No”). Hal ini menunjukkan bahwa mayoritas transaksi masih berada pada nilai penjualan di bawah 1.000.000.

    STEP 2 - Buat kolom order_priority
    ada tahap ini, data diolah dengan membuat variabel order_priority menggunakan nested ifelse untuk mengelompokkan nilai net_sales menjadi kategori High, Medium, dan Low. Sebelumnya, data juga dibersihkan dari duplikasi dan nilai kosong. Hasilnya kemudian diringkas dan ditampilkan dalam bentuk pie chart untuk melihat distribusinya.
    df_final <- dataset_utama %>%
      distinct(order_id, .keep_all = TRUE) %>%
      mutate(
        net_sales = ifelse(is.na(net_sales), 0, net_sales),
     
        # NESTED IF 
        order_priority = ifelse(
          net_sales >= 1000000,
          "High",
          ifelse(
            net_sales >= 500000,
            "Medium",
            "Low"
          )
        )
      )

    Berdasarkan visualisasi, kategori Low memiliki proporsi terbesar (44,2%), diikuti High (36%) dan Medium (19,9%), yang menunjukkan mayoritas transaksi berada di bawah 500.000 (prioritas rendah). Namun, proporsi High yang cukup signifikan mengindikasikan adanya kontribusi transaksi bernilai besar, sementara kategori Medium yang paling sedikit menunjukkan transaksi nilai menengah kurang dominan, sehingga terdapat peluang untuk mendorong peningkatan transaksi dari Low ke Medium atau High.

    STEP 3 - Buat kolom valid_transaction
    Pada tahap ini dibuat variabel valid_transaction menggunakan case_when() untuk mengelompokkan transaksi menjadi valid dan invalid berdasarkan kondisi pada order_status. Transaksi dengan status kosong, NA, Unknown, atau Cancelled dikategorikan sebagai invalid. Hasilnya kemudian diringkas dan divisualisasikan dalam bentuk pie chart untuk melihat perbandingannya.

    Kolom valid_transaction dibuat menggunakan logika if, di mana transaksi dengan order_status = “Cancelled” dikategorikan sebagai Invalid, sedangkan selain itu sebagai Valid. Berdasarkan visualisasi, transaksi Valid mendominasi sebesar 97,7%, sementara Invalid hanya 2,3%, yang menunjukkan bahwa hampir seluruh transaksi dalam dataset berhasil diproses dengan baik dan hanya sebagian kecil yang dibatalkan. Hal ini mengindikasikan kualitas transaksi yang cukup baik, meskipun tetap terdapat sejumlah kecil transaksi yang tidak valid yang perlu diperhatikan.

    Cek Hasil Dataset

    1.5 Section E

    Menarik Insight Sederhana dari Data
    Platform Paling Dominan

    Berdasarkan hasil distribusi transaksi setelah proses cleaning data, terlihat bahwa Shopee menjadi platform dengan jumlah transaksi tertinggi yaitu sebanyak 411 transaksi. Secara keseluruhan, perbedaan jumlah transaksi antar platform relatif kecil, yang menunjukkan bahwa distribusi aktivitas pengguna cukup merata tanpa adanya dominasi yang sangat signifikan dari satu platform tertentu. Hal ini mengindikasikan bahwa preferensi pengguna terhadap platform e-commerce yang digunakan dalam analisis ini bersifat kompetitif dan tersebar secara seimbang di beberapa platform utama.

    Kategori Paling Sering

    Berdasarkan hasil distribusi kategori setelah proses cleaning data, kategori Fashion menjadi yang paling sering muncul dengan 412 kemunculan. Secara keseluruhan, distribusi data antar kategori relatif seimbang meskipun terdapat sedikit dominasi pada kategori Fashion, Sports, dan Home Living. Hal ini menunjukkan bahwa preferensi atau aktivitas yang tercatat dalam dataset tersebar pada beberapa kategori utama dengan variasi yang tidak terlalu jauh antar kategori.

    Status Order Terbanyak

    Berdasarkan hasil distribusi order status setelah proses cleaning data, status transaksi yang paling dominan adalah Delivered dengan total 1.156 order, yang menunjukkan bahwa sebagian besar transaksi telah berhasil sampai ke pelanggan. Secara keseluruhan, distribusi ini menunjukkan bahwa mayoritas transaksi telah mencapai status akhir berupa pengiriman berhasil, namun masih terdapat sebagian kecil transaksi yang mengalami pembatalan, pengembalian, maupun masih dalam proses pengiriman.


    2 WEB SCRAPING

    STUDI KASUS WEB SCRAPING

    Pada bagian ini, dilakukan proses pengambilan data dari beberapa website menggunakan teknik web scraping. Website yang digunakan memiliki karakteristik yang berbeda, seperti static, pagination, AJAX, dan iframe, sehingga diperlukan pendekatan yang berbeda dalam proses pengambilan datanya.

    Data yang berhasil dikumpulkan kemudian diolah melalui tahapan data cleaning dan analisis sederhana untuk mendapatkan insight dari data tersebut.

    2.1 Website Countries

    Section A - Pengumpulan Data
    1
    Web Scraping dataset countries (static page)
    2
    Cek Hasil Scraping
    3
    Simpan ke CSV
    STEP 1 - Web Scarping
    Pada tahap ini dilakukan proses scraping data dari website dengan membaca HTML menggunakan read_html(). Selanjutnya digunakan loop for untuk mengiterasi setiap elemen negara dan mengambil data seperti nama negara, ibu kota, populasi, dan luas wilayah menggunakan html_node(). Data yang diambil kemudian dibersihkan dengan html_text(trim=TRUE) agar rapi dan siap digunakan.
    url <- "https://www.scrapethissite.com/pages/simple/"
    page <- read_html(url)
    countries <- page %>% html_nodes(".country")
    
    data_list <- list()
    for (i in seq_along(countries)) {
      country_name <- countries[[i]] %>% html_node(".country-name") %>% html_text(trim = TRUE)
      capital      <- countries[[i]] %>% html_node(".country-capital") %>% html_text(trim = TRUE)
      population   <- countries[[i]] %>% html_node(".country-population") %>% html_text(trim = TRUE)
      area         <- countries[[i]] %>% html_node(".country-area") %>% html_text(trim = TRUE)
      
      data_list[[i]] <- data.frame(
        country    = country_name,
        capital    = capital,
        population = population,
        area       = area
      )
    }
    
    df_countries <- bind_rows(data_list)
    STEP 2 - Cek Hasil Scraping dan Konversi ke dataframe
    Memastikan proses scraping berhasil dan jumlah data yang diambil sesuai dengan isi website.
    STEP 3 - Simpan ke CSV
    write.csv(df_countries, "countries.csv", row.names = FALSE)
    
    df_download <- data.frame(
      File = "countries.csv",
      Download = '<a href="countries.csv" download="countries.csv">⬇ Download CSV</a>'
    )

    Section B - Memahami Data Scraping
    1
    Menampilkan struktur data
    2
    Cek nilai/value yang kosong pada dataset countries
    3
    Cek data yang kembar atau duplikat pada dataset countries
    4
    Identifikasi masalah yang ada pada dataset countries
    STEP 1 - Menampilkan Struktur Data
    Melihat gambaran umum struktur dataset sebelum dilakukan pengolahan lebih lanjut.

    - nrow(df_countries) → menghitung jumlah baris (jumlah data negara)
    - ncol(df_countries) → menghitung jumlah kolom (fitur/variabel)
    - colnames(df_countries) → menampilkan nama-nama kolom yang ada di dataset
    - sapply(df_countries, class) → mengecek tipe data pada setiap kolom (misalnya character, numeric, dll)
    STEP 2 - Cek Nilai Kosong
    Mengecek apakah ada data yang kosong (missing value) di dalam dataset.

    - is.na(df_countries) → mendeteksi nilai yang kosong (NA)
    - colSums() → menjumlahkan jumlah NA pada setiap kolom
    - names(colSums(is.na(df_countries))), colSums(is.na(df_countries))
    STEP 3 - Cek Data Duplikat
    Mengecek apakah ada data yang terduplikasi dalam dataset.

    - duplicated(df_countries) → mendeteksi baris yang sama persis
    - sum() → menghitung total data yang duplikat
    - data.frame(Duplicate = sum(duplicated(df_countries)))
    STEP 4 - Identifikasi Masalah Dataset

    Section C - Membersihkan Data
    1
    Standarisasi Teks
    2
    Ubah tipe data numerik
    3
    Menangani masalah nilai yang kosong pada dataset countries
    4
    Menghapus data yang kembar atau duplikat pada dataset countries
    STEP 1 - Standarisasi Teks
    Menyeragamkan format teks pada kolom country dan capital.

    - str_trim() → menghapus spasi berlebih di awal dan akhir teks
    - str_to_title() → mengubah teks menjadi format Title Case (huruf awal kapital)
    - for loop → melakukan proses ke beberapa kolom sekaligus
    for(col in c("country","capital")){
      df_countries[[col]] <- str_to_title(str_trim(df_countries[[col]]))
    }
    STEP 2 - Ubah Tipe Data Numerik
    Mengubah kolom population dan area menjadi numerik.

    - is.na() → mengecek apakah nilai kosong (NA)
    - == "" → mengecek string kosong
    - as.numeric() → mengubah data menjadi tipe angka
    - for loop → memproses setiap baris satu per satu
    for(i in 1:nrow(df_countries)){
      
      # population
      if(is.na(df_countries$population[i]) || df_countries$population[i] == ""){
        df_countries$population[i] <- 0
      } else {
        df_countries$population[i] <- as.numeric(df_countries$population[i])
      }
      
      # area
      if(is.na(df_countries$area[i]) || df_countries$area[i] == ""){
        df_countries$area[i] <- 0
      } else {
        df_countries$area[i] <- as.numeric(df_countries$area[i])
      }
    }
    
    df_countries$population <- as.numeric(df_countries$population)
    df_countries$area       <- as.numeric(df_countries$area)
    STEP 3 - Menangani Masalah Nilai Kosong
    Mengisi nilai yang kosong pada kolom teks.

    - is.na() → mendeteksi nilai yang hilang
    - == "" → mendeteksi string kosong
    - pengisian "Unknown" → mengganti nilai kosong dengan label yang jelas
    for(i in 1:nrow(df_countries)){
      
      # Country
      if(is.na(df_countries$country[i]) || df_countries$country[i] == ""){
        df_countries$country[i] <- "Unknown"
      }
      # Captial
      if(is.na(df_countries$capital[i]) || df_countries$capital[i] == ""){
        df_countries$capital[i] <- "Unknown"
      }
    }
    df_countries$population <- as.numeric(df_countries$population)
    df_countries$area       <- as.numeric(df_countries$area)
    STEP 4 - Menghapus Data Duplikat
    Menghapus data yang duplikat/kembar dengan menggunakan distinct() untuk mengambil baris unik dan menghapus yang sama
    ## Tidak ada duplicate data
    STEP 5 - Cek Hasil Cleaning

    Section D - Memahami Data Scraping
    1
    Mengisi nilai default
    2
    Menentukan data yang lengkap dan tidak lengkap pada dataset countries
    STEP 1 - Isi Nilai Default
    Melakukan pengisian nilai kosong (missing value dan string kosong) dengan nilai default agar data lebih lengkap dan konsisten.

    - is.na(df_countries$population) → mengecek nilai NA pada kolom population
    - df_countries$population == "" → mengecek data kosong berbentuk string
    - ifelse(kondisi, nilai_jika_TRUE, nilai_jika_FALSE) → memberikan nilai pengganti jika data kosong
    - population & area → diisi 0
    - country & capital → diisi "Unknown"
    # Numerik
    df_countries$population <- ifelse(
      is.na(df_countries$population) | df_countries$population == "",
      0,
      df_countries$population
    )
    
    df_countries$area <- ifelse(
      is.na(df_countries$area) | df_countries$area == "",
      0,
      df_countries$area
    )
    
    # Teks
    df_countries$country <- ifelse(
      is.na(df_countries$country) | df_countries$country == "",
      "Unknown",
      df_countries$country
    )
    
    df_countries$capital <- ifelse(
      is.na(df_countries$capital) | df_countries$capital == "",
      "Unknown",
      df_countries$capital
    )
    STEP 2 - Menentukan Data Lengkap dan Tidak Lengkap
    Menambahkan kolom baru data_status untuk mengklasifikasikan data berdasarkan kelengkapan informasi.

    - | → operator OR (atau), digunakan untuk menggabungkan beberapa kondisi
    - kondisi yang dicek: country bernilai "Unknown", capital bernilai "Unknown", population bernilai 0, area bernilai 0.
    - jika salah satu kondisi terpenuhi → data dianggap Incomplete
    - jika semua data lengkap → Complete
    df_countries$data_status <- ifelse(
      df_countries$country == "Unknown" |
      df_countries$capital == "Unknown" |
      df_countries$population == 0 |
      df_countries$area == 0,
      "Incomplete",
      "Complete"
    )
    STEP 3 - Cek Status Data

    2.2 Website Hockey

    Section A - Pengumpulan Data
    1
    Setup Variabel
    2
    Request & Parse HTML (Loop Pagination)
    3
    Cek hasil scraping dan konversi data ke dataframe
    4
    Menyimpan data hockey ke file CSV
    STEP 1 - Setup Variabel
    Menyiapkan semua library dan variabel awal yang dibutuhkan untuk proses scraping data dengan pagination, dengan menambahkan kode base_url sebagai URL dasar yang akan digabung dengan nomor halaman & df_hockey sebagai wadah kosong untuk menyimpan hasil scraping
    base_url <- "https://www.scrapethissite.com/pages/forms/?page_num="
    data <- list()
    current_page <- 1
    proceed <- TRUE
    STEP 2 – Request & Parse HTML (Loop Pagination)
    Pada tahap ini dilakukan proses scraping data dari beberapa halaman website menggunakan loop while untuk berpindah halaman secara otomatis hingga data habis. Di dalamnya digunakan loop for untuk mengiterasi setiap baris tabel, serta percabangan if untuk menghentikan proses jika tidak ada data atau baris tidak valid. Data yang diambil kemudian disimpan dan digabungkan menjadi satu dataset untuk ditampilkan dalam bentuk tabel.
    base_url <- "https://www.scrapethissite.com/pages/forms/?page_num="
    data <- list()
    current_page <- 1
    proceed <- TRUE
    
    while (proceed) {
      cat("Scraping page:", current_page, "\n")
      page <- read_html(paste0(base_url, current_page))
      rows <- page %>% html_nodes("table tr")
      rows <- rows[-1]
      
      if (length(rows) == 0) {
        proceed <- FALSE
        break
      }
      for (row in rows) {
        cols <- row %>% html_nodes("td")
        if (length(cols) < 8) next
        item <- data.frame(
          team_name     = cols[[1]] %>% html_text(trim = TRUE),
          year          = cols[[2]] %>% html_text(trim = TRUE),
          wins          = cols[[4]] %>% html_text(trim = TRUE),
          win_pct       = cols[[6]] %>% html_text(trim = TRUE),
          goals_for     = cols[[7]] %>% html_text(trim = TRUE),
          goals_against = cols[[8]] %>% html_text(trim = TRUE)
        )
        data <- append(data, list(item))
      }
      current_page <- current_page + 1
    }
    ## Scraping page: 1 
    ## Scraping page: 2 
    ## Scraping page: 3 
    ## Scraping page: 4 
    ## Scraping page: 5 
    ## Scraping page: 6 
    ## Scraping page: 7 
    ## Scraping page: 8 
    ## Scraping page: 9 
    ## Scraping page: 10 
    ## Scraping page: 11 
    ## Scraping page: 12 
    ## Scraping page: 13 
    ## Scraping page: 14 
    ## Scraping page: 15 
    ## Scraping page: 16 
    ## Scraping page: 17 
    ## Scraping page: 18 
    ## Scraping page: 19 
    ## Scraping page: 20 
    ## Scraping page: 21 
    ## Scraping page: 22 
    ## Scraping page: 23 
    ## Scraping page: 24 
    ## Scraping page: 25
    df_hockey <- bind_rows(data)
    STEP 3 - Cek Hasil Scraping dan Konversi ke Dataframe
    STEP 4 - Simpan ke CSV
    write.csv(df_hockey, "hockey.csv", row.names = FALSE)
    
    df_download_hockey <- data.frame(
      File = "hockey.csv",
      Download = '<a href="hockey.csv" download="hockey.csv">⬇ Download CSV</a>'
    )

    Section B - Memahami Data Scraping
    1
    Menampilkan Struktur Data
    2
    Cek nilai/value yang kosong pada dataset hockey
    3
    Cek data yang kembar atau duplikat pada dataset hockey
    4
    Identifikasi masalah yang ada pada dataset Hockey
    STEP 1 - menampilkan Struktur Data
    Melihat gambaran umum struktur dataset dengan menggunakan kode:

    - nrow(df_hockey) → menghitung jumlah baris (total data tim hockey)
    - ncol(df_hockey) → menghitung jumlah kolom (fitur/variabel)
    - colnames(df_hockey) → menampilkan nama kolom dalam dataset
    - sapply(df_hockey, class) → mengecek tipe data pada setiap kolom (character, numeric, dll)
    STEP 2 - Cek Nilai Kosong
    Mengecek apakah terdapat data kosong (missing values) pada dataset dengan menggunakan kode is.na(df_hockey) untuk mendeteksi nilai yang kosong (NA) dan kode colSums() untuk menghitung jumlah missing value pada setiap kolom
    STEP 3 - Cek Data Duplikat
    Mengecek apakah terdapat data yang duplikat dalam dataset dengan menggunakan kode duplicated(df_hockey) untuk mendeteksi baris data yang sama persis dan kode sum() untuk menghitung jumlah total data yang duplikat.
    Tujuan tahap ini untuk memastikan data tidak berulang agar hasil analisis lebih akurat dan tidak bias.
    STEP 4 - Identifikasi Masalah Dataset

    Section C - Membersihkan Data
    1
    Standarisasi Teks
    2
    Ubah tipe data numerik
    3
    Menangani masalah nilai yang kosong pada dataset countries
    4
    Menghapus data yang kembar atau duplikat pada dataset countries
    STEP 1 - Standarisasi Teks
    Melakukan pembersihan dan standarisasi format teks pada kolom team, dengan menggunakan kode str_trim() untuk menghapus spasi berlebih di awal dan akhir teks dan kode str_to_title() untuk mengubah teks menjadi format Title Case (huruf awal kapital setiap kata).
    df_hockey$team <- str_to_title(str_trim(df_hockey$team))
    STEP 2 - Ubah Tipe Data Numerik
    Mengubah tipe data dari character menjadi numeric agar dapat digunakan untuk perhitungan. Menggunakan kode as.numeric() untuk mengonversi data teks menjadi angka.
    for(i in 1:nrow(df_hockey)){
      
      # wins
      if(is.na(df_hockey$wins[i]) || df_hockey$wins[i] == ""){
        df_hockey$wins[i] <- 0
      } else {
        df_hockey$wins[i] <- as.numeric(df_hockey$wins[i])
      }
      
      # year
      if(is.na(df_hockey$year[i]) || df_hockey$year[i] == ""){
        df_hockey$year[i] <- 0
      } else {
        df_hockey$year[i] <- as.numeric(df_hockey$year[i])
      }
      
      # win_pct
      if(is.na(df_hockey$win_pct[i]) || df_hockey$win_pct[i] == ""){
        df_hockey$win_pct[i] <- 0
      } else {
        df_hockey$win_pct[i] <- as.numeric(df_hockey$win_pct[i])
      }
      
      # goals_for
      if(is.na(df_hockey$goals_for[i]) || df_hockey$goals_for[i] == ""){
        df_hockey$goals_for[i] <- 0
      } else {
        df_hockey$goals_for[i] <- as.numeric(df_hockey$goals_for[i])
      }
      
      # goals_against
      if(is.na(df_hockey$goals_against[i]) || df_hockey$goals_against[i] == ""){
        df_hockey$goals_against[i] <- 0
      } else {
        df_hockey$goals_against[i] <- as.numeric(df_hockey$goals_against[i])
      }
    }
    
    df_hockey$wins          <- as.numeric(df_hockey$wins)
    df_hockey$year          <- as.numeric(df_hockey$year)
    df_hockey$win_pct       <- as.numeric(df_hockey$win_pct)
    df_hockey$goals_for     <- as.numeric(df_hockey$goals_for)
    df_hockey$goals_against <- as.numeric(df_hockey$goals_against)
    STEP 3 - Menangani Masalah Nilai Kosong
    Melakukan pembersihan data kosong (missing value) secara otomatis menggunakan looping dan kondisi logika.

    - for (i in 1:nrow(df_hockey)) → melakukan pengecekan setiap baris data
    - is.na() → mengecek nilai kosong (NA)
    - if() → memberikan kondisi untuk mengganti nilai jika kosong

    Yang dilakukan:

    - wins atau losses NA → diganti 0
    - team kosong → diganti "Unknown"
    - wins < 0 → diperbaiki menjadi 0 (validasi data tidak logis)
    for (i in 1:nrow(df_hockey)) {
      
      # cek missing numeric
      if (is.na(df_hockey$wins[i])) {
        df_hockey$wins[i] <- 0
      }
      
      # cek missing teks / validitas
      if (is.na(df_hockey$team[i]) || df_hockey$team[i] == "") {
        df_hockey$team[i] <- "Unknown"
      }
      
      # validasi tambahan (opsional tapi bagus)
      if (df_hockey$wins[i] < 0) {
        df_hockey$wins[i] <- 0
      }
    }
    STEP 4 - Hapus Data Duplikat
    Menghapus data yang kembar atau duplikat pada daataset hockkey dengan menggunakan kode distinct() untuk menghapus baris yang memiliki nilai identik. Dengan tujuan untuk menghindari data ganda yang dapat menyebabkan bias pada hasil analisis.
    ## Tidak ada duplicate data
    STEP 5 - Cek Hasil Cleaning

    Section D - Memahami Data Scraping
    1
    Mengisi nilai default
    2
    Menentukan data yang lengkap dan tidak lengkap pada dataset countries
    STEP 1 - Mengisi Nilai Default
    Melakukan pengisian nilai default untuk data yang kosong atau tidak valid menggunakan logika kondisi.

    - is.na() → mengecek nilai kosong (NA)
    - df_hockey$team == "" → mengecek string kosong
    - ifelse(kondisi, nilai_jika_TRUE, nilai_jika_FALSE) → memberikan nilai pengganti jika data tidak valid

    aturan yang digunakan:

    - team kosong → "Unknown"
    - wins negatif atau NA → 0
    - losses negatif atau NA → 0
    - year kosong → 0
    df_hockey$team <- ifelse(is.na(df_hockey$team) | df_hockey$team == "",
                             "Unknown",
                             df_hockey$team)
    df_hockey$wins <- ifelse(is.na(df_hockey$wins) | df_hockey$wins < 0,
                             0,
                             df_hockey$wins)
    df_hockey$year <- ifelse(is.na(df_hockey$year),
                             0,
                             df_hockey$year)
    STEP 2 - Menentukan Data Lengkap dan Tidak Lengkap
    Melihat distribusi status data dan menampilkan dataset akhir setelah proses cleaning.

    - table(df_hockey$data_status) → menghitung jumlah data Complete dan Incomplete
    - as.data.frame(status_count) → mengubah hasil tabel menjadi format data frame
    - datatable() → menampilkan dataset dalam bentuk tabel interaktif
    # hapus kolom team yang duplikat
    df_hockey$team <- NULL
    
    # tambah kolom data_status seperti Python
    df_hockey$data_status <- ifelse(
      df_hockey$team_name == "Unknown" | df_hockey$wins == 0,
      "Incomplete",
      "Complete"
    )
    STEP 3 - Cek Status Data

    2.3 Website Movie

    Section A - Pengumpulan Data
    1
    Setup Variabel
    2
    Request & Parse JSON (Loop per Tahun)
    3
    Cek Hasil Scraping dan Konversi ke Dataframe
    4
    Menyimpan data ke file CSV
    STEP 1 - Setup Variabel
    Menyiapkan variabel awal yang dibutuhkan untuk proses scraping data AJAX, yaitu base_url sebagai URL dasar yang digabung dengan tahun, years sebagai daftar tahun yang akan di-request, dan data_ajax sebagai wadah kosong untuk menyimpan hasil scraping.
    base_url  <- "https://www.scrapethissite.com/pages/ajax-javascript/?ajax=true&year="
    years     <- 2010:2015        # range tahun seperti Python
    data_ajax <- list()           # list kosong untuk simpan hasil
    STEP 2 - Request & Parse JSON (Loop per Tahun)
    Pada tahap ini dilakukan proses scraping data AJAX dengan melakukan request ke URL JSON untuk setiap tahun menggunakan loop for. Data JSON yang diterima kemudian di-parse menggunakan fromJSON() dan setiap film diiterasi dengan loop for di dalamnya untuk mengambil kolom yang dibutuhkan.
    for (year in years) {                                         # looping tiap tahun
      cat("Request year:", year, "\n")                            # info progress
      
      url      <- paste0(base_url, year)                         # URL dinamis per tahun
      response <- tryCatch(                                       # request ke server
        fromJSON(url),
        error = function(e) {
          cat("Request gagal di year:", year, "\n")
          NULL
        }
      )
      
      if (is.null(response)) next                                # skip jika gagal
      
      for (i in seq_len(nrow(response))) {                       # looping tiap film
        film <- response[i, ]                                    # ambil 1 baris
        
        item <- data.frame(
          movie_title = ifelse(is.null(film$title),       "Unknown", film$title),
          year        = ifelse(is.null(film$year),         year,      film$year),
          nomination  = ifelse(is.null(film$nominations),  0,         film$nominations),
          awards      = ifelse(is.null(film$awards),       0,         film$awards),
          description = ifelse(is.null(film$description), NA,         film$description),
          stringsAsFactors = FALSE
        )
        
        data_ajax <- append(data_ajax, list(item))               # simpan ke list
      }
    }
    ## Request year: 2010 
    ## Request year: 2011 
    ## Request year: 2012 
    ## Request year: 2013 
    ## Request year: 2014 
    ## Request year: 2015
    df_ajax <- bind_rows(data_ajax)
    STEP 3 - Cek Hasil Scraping dan Konversi ke Dataframe
    STEP 4 - Simpan ke CSV
    write.csv(df_ajax, "movie_data.csv", row.names = FALSE)
    
    df_download_ajax <- data.frame(
      File     = "movie_data.csv",
      Download = '<a href="movie_data.csv" download="movie_data.csv">⬇ Download CSV</a>'
    )

    Section B - Memahami Data Scraping
    1
    Menampilkan Struktur Data
    2
    Cek nilai kosong pada dataset movie
    3
    Cek data duplikat pada dataset movie
    4
    Identifikasi masalah pada dataset movie
    STEP 1 - Menampilkan Struktur Data
    Melihat gambaran umum struktur dataset sebelum dilakukan pengolahan lebih lanjut.

    - nrow(df_ajax) → menghitung jumlah baris
    - ncol(df_ajax) → menghitung jumlah kolom
    - colnames(df_ajax) → menampilkan nama kolom
    - sapply(df_ajax, class) → mengecek tipe data setiap kolom
    STEP 2 - Cek Nilai Kosong
    Mengecek apakah ada data yang kosong (missing value) di dalam dataset. Menggunakan kode is.na(df_ajax) untuk mendeteksi nilai kosong (NA) dan kode colSums() untuk menjumlahkan jumlah NA tiap kolom
    STEP 3 - Cek Data Duplikat
    Mengecek apakah ada data yang kembar atau duplikat dalam dataset. Menggunakan kode duplicated(df_ajax) untuk mendeteksi baris yang sama persis dan kode sum() untuk menghitung total data duplikat.
    STEP 4 - Identifikasi Masalah Dataset

    Section C - Membersihkan Data
    1
    Standarisasi Teks
    2
    Ubah tipe data numerik
    3
    Menangani nilai kosong
    4
    Menghapus data duplikat
    STEP 1 - Standarisasi Teks
    Menyamakan format teks pada kolom movie_title dan description.

    - str_trim() → menghapus spasi berlebih
    - str_to_title() → mengubah ke format Title Case
    - for loop → memproses beberapa kolom sekaligus
    for (col in c("movie_title", "description")) {
      df_ajax[[col]] <- sapply(df_ajax[[col]], function(x) {
        if (is.na(x)) return(x)
        str_to_title(str_trim(x))
      })
    }
    STEP 2 - Ubah Tipe Data Numerik
    Mengubah kolom numerik.

    - is.na() → mengecek nilai kosong
    - == "" → mengecek string kosong
    - as.numeric() → mengubah ke tipe angka
    - for loop → memproses setiap baris satu per satu
    for (col in c("year", "nomination", "awards")) {
      for (i in 1:nrow(df_ajax)) {
        if (is.na(df_ajax[[col]][i]) || df_ajax[[col]][i] == "") {
          df_ajax[[col]][i] <- 0
        } else {
          df_ajax[[col]][i] <- as.numeric(df_ajax[[col]][i])
        }
      }
      df_ajax[[col]] <- as.numeric(df_ajax[[col]])
    }
    STEP 3 - Menangani Nilai Kosong
    Mengisi nilai kosong pada kolom teks dengan nilai default.

    - is.na() → mendeteksi nilai hilang
    - == "" → mendeteksi string kosong
    - pengisian "Data not available" → mengganti nilai kosong dengan label yang jelas
    for (i in 1:nrow(df_ajax)) {
    
      # movie_title
      if (is.na(df_ajax$movie_title[i]) || df_ajax$movie_title[i] == "") {
        df_ajax$movie_title[i] <- "Unknown"
      }
    
      # description
      if (is.na(df_ajax$description[i]) || df_ajax$description[i] == "") {
        df_ajax$description[i] <- "Data not available"
      }
    }
    STEP 4 - Menghapus Data Duplikat
    Menghapus baris yang duplikat menggunakan distinct() untuk menghindari bias pada hasil analisis.
    ## Tidak ada duplicate data
    STEP 5 - Cek Hasil Cleaning

    Section D - Memahami Data Scraping
    1
    Mengisi nilai default
    2
    Menentukan data lengkap dan tidak lengkap
    STEP 1 - Mengisi Nilai Default
    Melakukan pengisian nilai default untuk data kosong menggunakan ifelse().

    - movie_title kosong → "Unknown"
    - description kosong → "Data not available"
    - nomination atau awards negatif/NA → 0
    df_ajax$movie_title <- ifelse(
      is.na(df_ajax$movie_title) | df_ajax$movie_title == "",
      "Unknown", df_ajax$movie_title
    )
    
    df_ajax$description <- ifelse(
      is.na(df_ajax$description) | df_ajax$description == "",
      "Data not available", df_ajax$description
    )
    
    df_ajax$nomination <- ifelse(
      is.na(df_ajax$nomination) | df_ajax$nomination < 0,
      0, df_ajax$nomination
    )
    
    df_ajax$awards <- ifelse(
      is.na(df_ajax$awards) | df_ajax$awards < 0,
      0, df_ajax$awards
    )
    STEP 2 - Menentukan Data Lengkap dan Tidak Lengkap
    Menambahkan kolom data_status untuk mengklasifikasikan kelengkapan data.

    - jika movie_title "Unknown" atau description "Data not available" → Incomplete
    - jika semua lengkap → Complete
    df_ajax$data_status <- ifelse(
      df_ajax$movie_title == "Unknown" |
      tolower(df_ajax$description) == "data not available",
      "Incomplete",
      "Complete"
    )
    STEP 3 - Cek Status Data

    2.4 Website Turtles

    Section A - Pengumpulan Data
    1
    Setup Variabel
    2
    Request & Parse HTML (iFrame)
    3
    Scraping Data dari iFrame dan Detail Page
    4
    Cek Hasil Scraping dan Simpan ke CSV
    STEP 1 - Setup Variabel
    Menyiapkan variabel awal yang dibutuhkan untuk proses scraping data dari website berbasis iframe, yaitu url sebagai URL halaman utama dan data_list sebagai wadah kosong untuk menyimpan hasil scraping sementara.
    url       <- "https://www.scrapethissite.com/pages/frames/"
    data_list <- list()
    STEP 2 - Request & Parse HTML (iFrame)
    Mengakses halaman iframe secara langsung menggunakan URL khusus dan membaca struktur HTML-nya menggunakan read_html(). Website ini menggunakan iframe sehingga konten utamanya tidak langsung tersedia di halaman utama dan harus diakses melalui URL iframe terpisah.
    iframe_url  <- "https://www.scrapethissite.com/pages/frames/?frame=i"
    iframe_page <- read_html(iframe_url)
    STEP 3 - Scraping Data dari iFrame dan Detail Page
    Mengambil semua data dari halaman iframe menggunakan loop for untuk mengiterasi setiap card, lalu membuka halaman detail setiap item untuk mengambil deskripsi menggunakan read_html().

    - html_nodes(".col-md-4") → mengambil setiap card/item
    - html_node("h3") → mengambil nama item
    - html_node("a") → mengambil link detail
    - html_nodes("p") → mengambil paragraf deskripsi dari halaman detail
    - ifelse(is.null(...)) → mengisi "Unknown" jika data tidak ada
    - sample() → membuat data tambahan nomination dan awards secara acak
    cards <- iframe_page %>% html_nodes(".col-md-4")
    
    for (card in cards) {
    
      item <- list()
    
      # NAME
      name_tag   <- card %>% html_node("h3")
      item$name  <- ifelse(
        is.null(name_tag),
        "Unknown",
        name_tag %>% html_text(trim = TRUE)
      )
    
      # DETAIL LINK
      link_tag <- card %>% html_node("a")
    
      if (!is.null(link_tag)) {
    
        detail_url  <- paste0(
          "https://www.scrapethissite.com",
          link_tag %>% html_attr("href")
        )
        detail_page <- read_html(detail_url)
        paragraphs  <- detail_page %>% html_nodes("p")
    
        item$description <- ifelse(
          length(paragraphs) > 0,
          paragraphs[1] %>% html_text(trim = TRUE),
          "Unknown"
        )
    
        item$additional_info <- ifelse(
          length(paragraphs) > 1,
          paragraphs[2] %>% html_text(trim = TRUE),
          "No additional info"
        )
    
      } else {
        item$description     <- "Unknown"
        item$additional_info <- "No additional info"
      }
    
      data_list[[length(data_list) + 1]] <- as.data.frame(item)
    }
    
    df_turtles <- bind_rows(data_list)
    STEP 4 - Cek Hasil Scraping dan Simpan ke CSV
    write.csv(df_turtles, "turtles.csv", row.names = FALSE)
    
    df_download_turtles <- data.frame(
      File     = "turtles.csv",
      Download = '<a href="turtles.csv" download="turtles.csv">⬇ Download CSV</a>'
    )

    Section B - Memahami Data Scraping
    1
    Menampilkan Struktur Data
    2
    Cek nilai kosong pada dataset turtles
    3
    Cek data duplikat pada dataset turtles
    4
    Identifikasi masalah pada dataset turtles
    STEP 1 - Menampilkan Struktur Data
    Melihat gambaran umum struktur dataset sebelum dilakukan pengolahan lebih lanjut.

    - nrow(df_turtles) → menghitung jumlah baris
    - ncol(df_turtles) → menghitung jumlah kolom
    - colnames(df_turtles) → menampilkan nama kolom
    - sapply(df_turtles, class) → mengecek tipe data setiap kolom
    STEP 2 - Cek Nilai Kosong
    Mengecek apakah ada data kosong (missing value) di dalam dataset.

    - is.na(df_turtles) → mendeteksi nilai kosong (NA)
    - colSums() → menjumlahkan jumlah NA tiap kolom
    STEP 3 - Cek Data Duplikat
    Mengecek apakah ada data yang terduplikasi dalam dataset.

    - duplicated(df_turtles) → mendeteksi baris yang sama persis
    - sum() → menghitung total data duplikat
    STEP 4 - Identifikasi Masalah Dataset

    Section C - Membersihkan Data
    1
    Standarisasi Teks
    2
    Ubah tipe data numerik
    3
    Menangani nilai kosong
    4
    Menghapus data duplikat
    STEP 1 - Standarisasi Teks
    Menyeragamkan format teks pada kolom name, description, dan additional_info.

    - str_trim() → menghapus spasi berlebih di awal dan akhir teks
    - str_to_title() → mengubah teks menjadi format Title Case
    - for loop → memproses beberapa kolom sekaligus
    for (col in c("name", "description", "additional_info")) {
      df_turtles[[col]] <- str_to_title(str_trim(df_turtles[[col]]))
    }
    STEP 2 - Cek Tipe Data
    Pada dataset ini, semua kolom berupa teks sehingga tidak diperlukan konversi tipe data karena tidak ada kolom numerik.
    summary_turtles <- data.frame(
      Column    = names(df_turtles),
      Data_Type = sapply(df_turtles, class)
    )
    
    datatable(
      summary_turtles,
      rownames = FALSE,
      options  = list,
      class = "stripe hover compact cell-border"
    )
    STEP 3 - Menangani Nilai Kosong
    Mengisi nilai kosong pada setiap kolom dengan nilai default menggunakan looping dan kondisi logika.

    - is.na() → mendeteksi nilai hilang
    - == "" → mendeteksi string kosong
    - name kosong → "Unknown"
    - description kosong → "Unknown"
    - additional_info kosong → "No additional info"
    for (i in 1:nrow(df_turtles)) {
    
      if (is.na(df_turtles$name[i]) || df_turtles$name[i] == "") {
        df_turtles$name[i] <- "Unknown"
      }
    
      if (is.na(df_turtles$description[i]) || df_turtles$description[i] == "") {
        df_turtles$description[i] <- "Unknown"
      }
    
      if (is.na(df_turtles$additional_info[i]) || df_turtles$additional_info[i] == "") {
        df_turtles$additional_info[i] <- "No additional info"
      }
    }
    STEP 4 - Menghapus Data Duplikat
    Menghapus baris yang duplikat menggunakan distinct() untuk menghindari bias pada hasil analisis.
    ## Tidak ada duplicate data
    STEP 5 - Cek Hasil Cleaning

    Section D - Memahami Data Scraping
    1
    Mengisi nilai default
    2
    Menentukan data lengkap dan tidak lengkap
    STEP 1 - Mengisi Nilai Default
    Melakukan pengisian nilai default untuk data kosong menggunakan ifelse().

    - name kosong → "Unknown"
    - description kosong → "Unknown"
    - additional_info kosong → "No additional info"
    df_turtles$name <- ifelse(
      is.na(df_turtles$name) | df_turtles$name == "",
      "Unknown", df_turtles$name
    )
    
    df_turtles$description <- ifelse(
      is.na(df_turtles$description) | df_turtles$description == "",
      "Unknown", df_turtles$description
    )
    
    df_turtles$additional_info <- ifelse(
      is.na(df_turtles$additional_info) | df_turtles$additional_info == "",
      "No additional info", df_turtles$additional_info
    )
    STEP 2 - Menentukan Data Lengkap dan Tidak Lengkap
    Menambahkan kolom data_status untuk mengklasifikasikan kelengkapan data.

    - | → operator OR, menggabungkan beberapa kondisi
    - name = "Unknown" → data tidak lengkap
    - description = "Unknown" → data tidak lengkap
    - additional_info = "No additional info" → data tidak lengkap
    - jika salah satu terpenuhi "Incomplete", jika semua lengkap "Complete"
    df_turtles$data_status <- ifelse(
      df_turtles$name        == "Unknown" |
      df_turtles$description == "Unknown" |
      df_turtles$additional_info == "No additional info",
      "Incomplete",
      "Complete"
    )
    STEP 3 - Cek Status Data

    2.5 Section E Web Scraping

    ANALYTICAL THINKING (4 WEBSITE KESELURUHAN)
    1. Website paling mudah di-scrape

    Website yang paling mudah untuk di-scrape adalah Countries of the World (Static Page).

    Hal ini karena:

    • Semua data sudah tersedia langsung di dalam HTML halaman utama
    • Struktur tag HTML rapi dan konsisten (tiap negara punya format yang sama)
    • Tidak membutuhkan interaksi tambahan seperti klik, scroll, atau request ulang
    • Tidak ada pagination, iframe, maupun JavaScript dinamis
    • Data bisa langsung diambil menggunakan read_html() dan selector seperti html_nodes()

    Dengan kondisi seperti ini, proses scraping menjadi sangat stabil, cepat, dan minim error karena tidak ada proses tambahan yang harus ditangani.


    2. Website paling sulit di-scrape

    Website yang paling sulit adalah Oscar Winning Films (AJAX / JavaScript Dynamic Loading) https://www.scrapethissite.com/pages/ajax-javascript/

    Kesulitannya terletak pada cara website memuat data:

    • Data tidak muncul langsung di HTML awal (hanya kerangka kosong)
    • Isi tabel baru muncul setelah JavaScript dijalankan
    • Data diambil melalui AJAX request ke backend
    • Harus mencari endpoint API menggunakan inspect network
    • Response biasanya berbentuk JSON, bukan HTML
    • Tidak bisa hanya menggunakan rvest karena data belum tersedia saat halaman dibuka
    • Rentan error jika endpoint berubah atau request tidak sesuai format

    Karena itu, scraping website jenis ini membutuhkan pemahaman tambahan tentang network request dan API structure, bukan hanya HTML parsing.


    3. Perbedaan pendekatan scraping
    • Static Website

      • Data langsung tersedia di HTML
      • Cukup menggunakan read_html() dan selector seperti html_nodes()
      • Tidak perlu looping atau request tambahan
    • Pagination Website

      • Data terbagi ke beberapa halaman
      • Perlu looping berdasarkan pola URL (misalnya page_num=1,2,3,...)
      • Data dari setiap halaman harus digabung menjadi satu dataset
    • AJAX / Dynamic Website

      • Data tidak ada di HTML awal
      • Dimuat melalui JavaScript menggunakan API request
      • Perlu mengambil data dari network (biasanya JSON)
      • Tidak bisa hanya mengandalkan scraping HTML biasa
    • Iframe Website

      • Data berada di halaman terpisah yang ditanam dalam iframe
      • Harus mengambil iframe src terlebih dahulu
      • Lalu melakukan request ulang ke halaman iframe untuk mengambil data
      • Kadang masih perlu masuk ke halaman detail tambahan

    4. Insights

    Dari seluruh proses scraping, dapat disimpulkan beberapa hal penting:

    • Tingkat kesulitan scraping sangat bergantung pada struktur website, bukan hanya jumlah data
    • Static website paling stabil karena tidak bergantung pada JavaScript atau request tambahan
    • Pagination masih cukup terstruktur karena pola URL biasanya konsisten
    • AJAX dan iframe jauh lebih kompleks karena data tidak langsung tersedia di HTML
    • Pada beberapa kasus (iframe dan AJAX), data bisa gagal diambil jika request tidak sesuai atau link berubah
    • Semakin banyak layer (detail page / iframe / API), semakin tinggi risiko data tidak lengkap

    5. Rekomendasi
    • Sebelum melakukan scraping, sebaiknya analisis struktur website terlebih dahulu menggunakan Inspect Element dan Network Tab
    • Pilih metode scraping yang sesuai dengan jenis website (static, pagination, iframe, AJAX) agar proses lebih efisien dan minim error
    • Untuk website AJAX, lebih disarankan mengambil data langsung dari API daripada parsing HTML
    • Selalu siapkan penanganan error (try-catch / validation) terutama pada website dengan struktur dinamis
    • Mulai dari static website terlebih dahulu untuk memahami dasar sebelum masuk ke struktur yang lebih kompleks