ANALISIS GRADIENT BOOSTING REGRESSION DENGAN PENENTUAN JUMLAH POHON OPTIMAL

library(gbm)
library(ggplot2)
train <- read.csv(
  "D:/STATISTIKA/SEM 5/Modern Prediksi dan Machine Learning/PEkan 9-18/Studi Kasus pekan 11/train.csv",
  stringsAsFactors = FALSE
)

test <- read.csv(
  "D:/STATISTIKA/SEM 5/Modern Prediksi dan Machine Learning/PEkan 9-18/Studi Kasus pekan 11/test.csv",
  stringsAsFactors = FALSE
)

cat("Jumlah data training:", nrow(train), "\n")
## Jumlah data training: 51879
cat("Jumlah data testing:", nrow(test), "\n")
## Jumlah data testing: 22232
# Memastikan variabel respons tersedia
if (!"price" %in% names(train)) {
  stop("Variabel price tidak ditemukan pada data training.")
}

# Mengubah price menjadi numerik
train$price <- as.numeric(
  gsub("[^0-9.]", "", as.character(train$price))
)

# Menghapus harga yang tidak valid
train <- train[
  is.finite(train$price) & train$price >= 0,
  ,
  drop = FALSE
]

# Menyimpan ID data testing
if ("id" %in% names(test)) {
  id_test <- test$id
} else {
  id_test <- seq_len(nrow(test))
}

MEMILIH VARIABEL PENELITIAN

# Variabel respons
variabel_Y <- "price"

# 12 variabel prediktor sesuai tabel penelitian
variabel_X <- c(
  "property_type",
  "room_type",
  "accommodates",
  "bedrooms",
  "bathrooms",
  "beds",
  "number_of_reviews",
  "review_scores_rating",
  "host_response_rate",
  "cancellation_policy",
  "instant_bookable",
  "cleaning_fee"
)

# Memeriksa variabel pada data training
variabel_tidak_ada <- setdiff(
  c(variabel_Y, variabel_X),
  names(train)
)

if (length(variabel_tidak_ada) > 0) {
  stop(
    paste(
      "Variabel tidak tersedia pada training:",
      paste(variabel_tidak_ada, collapse = ", ")
    )
  )
}

# Memeriksa variabel pada data testing
variabel_test_tidak_ada <- setdiff(
  variabel_X,
  names(test)
)

if (length(variabel_test_tidak_ada) > 0) {
  stop(
    paste(
      "Variabel tidak tersedia pada testing:",
      paste(variabel_test_tidak_ada, collapse = ", ")
    )
  )
}

# Mengambil hanya variabel penelitian
X_train_raw <- train[, variabel_X, drop = FALSE]
X_test_raw <- test[, variabel_X, drop = FALSE]

n_train <- nrow(X_train_raw)

cat("\nJUMLAH VARIABEL PREDIKTOR:", length(variabel_X), "\n")
## 
## JUMLAH VARIABEL PREDIKTOR: 12
print(variabel_X)
##  [1] "property_type"        "room_type"            "accommodates"        
##  [4] "bedrooms"             "bathrooms"            "beds"                
##  [7] "number_of_reviews"    "review_scores_rating" "host_response_rate"  
## [10] "cancellation_policy"  "instant_bookable"     "cleaning_fee"

MENGGABUNGKAN DATA TRAINING DAN TESTING

X_gabungan <- rbind(
  X_train_raw,
  X_test_raw
)

PREPROCESSING VARIABEL

# Mengubah host_response_rate menjadi numerik
X_gabungan$host_response_rate <- as.numeric(
  gsub(
    "%",
    "",
    as.character(X_gabungan$host_response_rate)
  )
)

# Mengubah cleaning_fee menjadi numerik
X_gabungan$cleaning_fee <- as.numeric(
  gsub(
    "[$,]",
    "",
    as.character(X_gabungan$cleaning_fee)
  )
)

# Variabel numerik
variabel_numerik <- c(
  "accommodates",
  "bedrooms",
  "bathrooms",
  "beds",
  "number_of_reviews",
  "review_scores_rating",
  "host_response_rate",
  "cleaning_fee"
)

# Variabel kategorik
variabel_kategorik <- c(
  "property_type",
  "room_type",
  "cancellation_policy",
  "instant_bookable"
)

# Memastikan tipe data numerik
for (v in variabel_numerik) {
  X_gabungan[[v]] <- as.numeric(
    as.character(X_gabungan[[v]])
  )
}

# Memastikan tipe data kategorik
for (v in variabel_kategorik) {
  X_gabungan[[v]] <- as.factor(
    as.character(X_gabungan[[v]])
  )
}

STATISTIK DESKRIPTIF

# Menyiapkan variabel numerik
data_deskriptif <- X_train_raw

data_deskriptif$host_response_rate <- as.numeric(
  gsub("%", "", as.character(data_deskriptif$host_response_rate))
)
data_deskriptif$cleaning_fee <- as.numeric(
  gsub("[$,]", "", as.character(data_deskriptif$cleaning_fee))
)

for (v in variabel_numerik) {
  data_deskriptif[[v]] <- as.numeric(as.character(data_deskriptif[[v]]))
}

# Statistik deskriptif numerik
data_numerik <- data.frame(
  price = train$price,
  data_deskriptif[, variabel_numerik, drop = FALSE]
)

statistik_deskriptif <- do.call(
  rbind,
  lapply(names(data_numerik), function(v) {
    x <- data_numerik[[v]]
    x <- x[is.finite(x)]
    data.frame(
      Variabel = v,
      N = length(x),
      Mean = if (length(x) > 0) mean(x) else NA,
      Median = if (length(x) > 0) median(x) else NA,
      SD = if (length(x) > 1) sd(x) else NA,
      Minimum = if (length(x) > 0) min(x) else NA,
      Maksimum = if (length(x) > 0) max(x) else NA
    )
  })
)

cat("STATISTIK DESKRIPTIF VARIABEL NUMERIK\\n")
## STATISTIK DESKRIPTIF VARIABEL NUMERIK\n
print(statistik_deskriptif)
##               Variabel     N       Mean Median          SD Minimum Maksimum
## 1                price 51879 160.369051    111 168.9281332       1     1999
## 2         accommodates 51879   3.150176      2   2.1553052       1       16
## 3             bedrooms 51813   1.261093      1   0.8497248       0       10
## 4            bathrooms 51735   1.233507      1   0.5815484       0        8
## 5                 beds 51795   1.707443      1   1.2497201       0       18
## 6    number_of_reviews 51879  20.821488      6  37.4908359       0      605
## 7 review_scores_rating 40258  94.077177     96   7.8193346      20      100
## 8   host_response_rate 39125  94.316141    100  16.4116517       0      100
## 9         cleaning_fee     0         NA     NA          NA      NA       NA
# Frekuensi dan persentase variabel kategorik
for (v in variabel_kategorik) {
  kategori <- trimws(as.character(X_train_raw[[v]]))
  kategori[is.na(kategori) | kategori == ""] <- "Tidak diketahui"
  tabel_frekuensi <- as.data.frame(table(kategori))
  names(tabel_frekuensi) <- c("Kategori", "Frekuensi")
  tabel_frekuensi$Persentase <- round(
    100 * tabel_frekuensi$Frekuensi / sum(tabel_frekuensi$Frekuensi), 2
  )
  cat("\\nFREKUENSI VARIABEL:", v, "\\n")
  print(tabel_frekuensi)
}
## \nFREKUENSI VARIABEL: property_type \n             Kategori Frekuensi Persentase
## 1           Apartment     34370      66.25
## 2     Bed & Breakfast       307       0.59
## 3                Boat        43       0.08
## 4      Boutique hotel        53       0.10
## 5            Bungalow       254       0.49
## 6               Cabin        51       0.10
## 7           Camper/RV        63       0.12
## 8     Casa particular         1       0.00
## 9              Castle         9       0.02
## 10               Cave         1       0.00
## 11             Chalet         4       0.01
## 12        Condominium      1872       3.61
## 13               Dorm        97       0.19
## 14        Earth House         3       0.01
## 15        Guest suite        93       0.18
## 16         Guesthouse       355       0.68
## 17             Hostel        47       0.09
## 18              House     11502      22.17
## 19                Hut         7       0.01
## 20             In-law        45       0.09
## 21             Island         1       0.00
## 22         Lighthouse         1       0.00
## 23               Loft       871       1.68
## 24              Other       437       0.84
## 25      Parking Space         1       0.00
## 26 Serviced apartment        16       0.03
## 27               Tent        12       0.02
## 28          Timeshare        55       0.11
## 29               Tipi         3       0.01
## 30          Townhouse      1155       2.23
## 31              Train         1       0.00
## 32          Treehouse         5       0.01
## 33      Vacation home         8       0.02
## 34              Villa       127       0.24
## 35               Yurt         9       0.02
## \nFREKUENSI VARIABEL: room_type \n         Kategori Frekuensi Persentase
## 1 Entire home/apt     28966      55.83
## 2    Private room     21423      41.29
## 3     Shared room      1490       2.87
## \nFREKUENSI VARIABEL: cancellation_policy \n         Kategori Frekuensi Persentase
## 1        flexible     15741      30.34
## 2        moderate     13333      25.70
## 3          strict     22720      43.79
## 4 super_strict_30        74       0.14
## 5 super_strict_60        11       0.02
## \nFREKUENSI VARIABEL: instant_bookable \n  Kategori Frekuensi Persentase
## 1        f     38325      73.87
## 2        t     13554      26.13

VISUALISASI EKSPLORASI DATA

# 1. Histogram price
p_hist_price <- ggplot(train, aes(x = price)) +
  geom_histogram(bins = 30, fill = "#2C7FB8", color = "white") +
  labs(
    title = "Distribusi Harga Properti",
    x = "Harga (price)", y = "Frekuensi"
  ) +
  theme_minimal()
print(p_hist_price)

# 2. Boxplot price
p_box_price <- ggplot(train, aes(x = "", y = price)) +
  geom_boxplot(
    fill = "#75AADB", color = "#263238",
    outlier.color = "#C0392B"
  ) +
  labs(title = "Boxplot Harga Properti", x = NULL, y = "Harga (price)") +
  theme_minimal()
print(p_box_price)

# 3. Identifikasi calon pencilan menggunakan IQR
Q1 <- quantile(train$price, 0.25, na.rm = TRUE)
Q3 <- quantile(train$price, 0.75, na.rm = TRUE)
IQR_price <- IQR(train$price, na.rm = TRUE)
batas_bawah <- Q1 - 1.5 * IQR_price
batas_atas <- Q3 + 1.5 * IQR_price
jumlah_pencilan <- sum(
  train$price < batas_bawah | train$price > batas_atas,
  na.rm = TRUE
)
cat("\\nIDENTIFIKASI PENCILAN PRICE\\n")
## \nIDENTIFIKASI PENCILAN PRICE\n
cat("Batas bawah:", batas_bawah, "\\n")
## Batas bawah: -90 \n
cat("Batas atas:", batas_atas, "\\n")
## Batas atas: 350 \n
cat("Jumlah calon pencilan:", jumlah_pencilan, "\\n")
## Jumlah calon pencilan: 3556 \n
cat("Pencilan valid dipertahankan dan tidak dihapus.\\n")
## Pencilan valid dipertahankan dan tidak dihapus.\n
# 4. Heatmap korelasi
mat_korelasi <- cor(data_numerik, use = "pairwise.complete.obs")
data_korelasi <- as.data.frame(as.table(mat_korelasi))
names(data_korelasi) <- c("Variabel1", "Variabel2", "Korelasi")

p_heatmap <- ggplot(
  data_korelasi,
  aes(x = Variabel1, y = Variabel2, fill = Korelasi)
) +
  geom_tile(color = "white") +
  geom_text(aes(label = round(Korelasi, 2)), size = 3) +
  scale_fill_gradient2(
    low = "#B2182B", mid = "white", high = "#2166AC",
    midpoint = 0, limits = c(-1, 1)
  ) +
  labs(
    title = "Heatmap Korelasi Variabel Numerik",
    x = NULL, y = NULL, fill = "Korelasi"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1),
    panel.grid = element_blank()
  )
print(p_heatmap)

# 5. Scatter plot price dengan seluruh prediktor numerik
for (v in variabel_numerik) {
  data_scatter <- data.frame(
    price = train$price,
    x = data_deskriptif[[v]]
  )
  data_scatter <- data_scatter[
    is.finite(data_scatter$price) & is.finite(data_scatter$x),
    , drop = FALSE
  ]

  p_scatter <- ggplot(data_scatter, aes(x = x, y = price)) +
    geom_point(color = "#21618C", alpha = 0.45, size = 1.5) +
    labs(
      title = paste("Harga Properti dan", v),
      x = v, y = "Harga (price)"
    ) +
    theme_minimal()
  print(p_scatter)
}

# 6. Diagram batang variabel kategorik
for (v in variabel_kategorik) {
  kategori <- trimws(as.character(X_train_raw[[v]]))
  kategori[is.na(kategori) | kategori == ""] <- "Tidak diketahui"
  data_bar <- as.data.frame(table(kategori))
  names(data_bar) <- c("Kategori", "Frekuensi")

  p_bar <- ggplot(
    data_bar,
    aes(x = reorder(Kategori, Frekuensi), y = Frekuensi)
  ) +
    geom_col(fill = "#2C7FB8", width = 0.7) +
    coord_flip() +
    labs(title = paste("Distribusi", v), x = NULL, y = "Frekuensi") +
    theme_minimal()
  print(p_bar)
}

# 7. Histogram price setelah transformasi log
p_hist_log <- ggplot(
  data.frame(Log_Price = log1p(train$price)),
  aes(x = Log_Price)
) +
  geom_histogram(bins = 30, fill = "#3A8D7A", color = "white") +
  labs(
    title = "Distribusi Harga Setelah Transformasi",
    x = "log(1 + price)", y = "Frekuensi"
  ) +
  theme_minimal()
print(p_hist_log)

MENGATASI MISSING VALUE

for (j in seq_along(X_gabungan)) {

  kolom <- X_gabungan[[j]]

  if (is.numeric(kolom)) {

    kolom[!is.finite(kolom)] <- NA

    # Median dihitung dari data training
    median_train <- median(
      kolom[seq_len(n_train)],
      na.rm = TRUE
    )

    if (!is.finite(median_train)) {
      median_train <- 0
    }

    kolom[is.na(kolom)] <- median_train

    X_gabungan[[j]] <- kolom

  } else {

    kolom <- as.character(kolom)

    kolom[
      is.na(kolom) |
        trimws(kolom) == ""
    ] <- "Tidak diketahui"

    X_gabungan[[j]] <- as.factor(kolom)
  }
}
# 7. MENGUBAH VARIABEL KATEGORIK MENJADI DUMMY
X_all <- model.matrix(
  ~ . - 1,
  data = X_gabungan
)

colnames(X_all) <- make.names(
  colnames(X_all),
  unique = TRUE
)

X_all[!is.finite(X_all)] <- 0

# Memisahkan data training dan testing
X_train_all <- X_all[
  seq_len(n_train),
  ,
  drop = FALSE
]

X_test_all <- X_all[
  (n_train + 1):nrow(X_all),
  ,
  drop = FALSE
]

cat("\nJUMLAH PREDIKTOR SETELAH DUMMY:",
    ncol(X_train_all), "\n")
## 
## JUMLAH PREDIKTOR SETELAH DUMMY: 50
cat("\nDAFTAR PREDIKTOR SETELAH DUMMY:\n")
## 
## DAFTAR PREDIKTOR SETELAH DUMMY:
print(colnames(X_train_all))
##  [1] "property_typeApartment"             "property_typeBed...Breakfast"      
##  [3] "property_typeBoat"                  "property_typeBoutique.hotel"       
##  [5] "property_typeBungalow"              "property_typeCabin"                
##  [7] "property_typeCamper.RV"             "property_typeCasa.particular"      
##  [9] "property_typeCastle"                "property_typeCave"                 
## [11] "property_typeChalet"                "property_typeCondominium"          
## [13] "property_typeDorm"                  "property_typeEarth.House"          
## [15] "property_typeGuest.suite"           "property_typeGuesthouse"           
## [17] "property_typeHostel"                "property_typeHouse"                
## [19] "property_typeHut"                   "property_typeIn.law"               
## [21] "property_typeIsland"                "property_typeLighthouse"           
## [23] "property_typeLoft"                  "property_typeOther"                
## [25] "property_typeParking.Space"         "property_typeServiced.apartment"   
## [27] "property_typeTent"                  "property_typeTimeshare"            
## [29] "property_typeTipi"                  "property_typeTownhouse"            
## [31] "property_typeTrain"                 "property_typeTreehouse"            
## [33] "property_typeVacation.home"         "property_typeVilla"                
## [35] "property_typeYurt"                  "room_typePrivate.room"             
## [37] "room_typeShared.room"               "accommodates"                      
## [39] "bedrooms"                           "bathrooms"                         
## [41] "beds"                               "number_of_reviews"                 
## [43] "review_scores_rating"               "host_response_rate"                
## [45] "cancellation_policymoderate"        "cancellation_policystrict"         
## [47] "cancellation_policysuper_strict_30" "cancellation_policysuper_strict_60"
## [49] "instant_bookablet"                  "cleaning_fee"

TRANSFORMASI VARIABEL RESPONS

# Transformasi logaritma
y_all <- log1p(train$price)

MEMBAGI DATA TRAINING DAN VALIDASI

set.seed(123)
n <- nrow(X_train_all)

indeks_train <- sample(
  seq_len(n),
  size = floor(0.8 * n)
)

indeks_valid <- setdiff(
  seq_len(n),
  indeks_train
)

# Data training
X_train <- X_train_all[
  indeks_train,
  ,
  drop = FALSE
]

y_train <- y_all[indeks_train]

# Data validasi
X_valid <- X_train_all[
  indeks_valid,
  ,
  drop = FALSE
]

y_valid <- y_all[indeks_valid]

# Nilai aktual skala asli
actual <- train$price[indeks_valid]

# Membentuk data frame untuk gbm
data_gb <- data.frame(
  y = as.numeric(y_train),
  X_train,
  check.names = TRUE
)

data_valid_gb <- as.data.frame(
  X_valid,
  check.names = TRUE
)

cat("\nJumlah data training:", nrow(data_gb), "\n")
## 
## Jumlah data training: 41503
cat("Jumlah data validasi:", nrow(data_valid_gb), "\n")
## Jumlah data validasi: 10376

PEMODELAN GRADIENT BOOSTING DENGAN CROSS VALIDATION 5-FOLD

set.seed(123)

model_gb <- gbm(
  formula = y ~ .,
  distribution = "gaussian",
  data = data_gb,

  # Batas maksimum pencarian
  n.trees = 1000,

  # Parameter model
  interaction.depth = 3,
  n.minobsinnode = 2,
  shrinkage = 0.05,
  bag.fraction = 0.8,

  # Cross Validation
  cv.folds = 5,

  # Menggunakan satu core
  n.cores = 1,

  verbose = FALSE
)
## CV: 1
## CV: 2
## CV: 3
## CV: 4
## CV: 5

MENENTUKAN JUMLAH POHON OPTIMAL

jumlah_pohon_optimal <- gbm.perf(
  model_gb,
  method = "cv",
  plot.it = TRUE
)

cat("\n====================================\n")
## 
## ====================================
cat("HASIL PENCARIAN POHON OPTIMAL\n")
## HASIL PENCARIAN POHON OPTIMAL
cat("====================================\n")
## ====================================
cat(
  "Jumlah pohon optimal:",
  jumlah_pohon_optimal,
  "\n"
)
## Jumlah pohon optimal: 992
# Memeriksa apakah pencarian mencapai batas
if (jumlah_pohon_optimal >= 1000) {

  warning(
    "Jumlah pohon optimal mencapai batas maksimum 1000. ",
    "Pencarian perlu diperluas."
  )
}

PREDIKSI DATA VALIDASI MENGGUNAKAN POHON OPTIMAL

pred_valid_log <- predict(
  model_gb,
  newdata = data_valid_gb,
  n.trees = jumlah_pohon_optimal,
  type = "response"
)

# Mengembalikan ke skala asli
pred_valid <- expm1(pred_valid_log)

EVALUASI MODEL

# RMSE
RMSE <- sqrt(
  mean((actual - pred_valid)^2)
)

# MAE
MAE <- mean(
  abs(actual - pred_valid)
)

# R-Squared
R2 <- 1 -
  sum((actual - pred_valid)^2) /
  sum((actual - mean(actual))^2)

# Tabel evaluasi
hasil_evaluasi <- data.frame(
  Model = "Gradient Boosting",
  Jumlah_Pohon_Optimal = jumlah_pohon_optimal,
  RMSE = RMSE,
  MAE = MAE,
  R2 = R2
)

cat("\nHASIL EVALUASI MODEL\n")
## 
## HASIL EVALUASI MODEL
print(hasil_evaluasi)
##               Model Jumlah_Pohon_Optimal     RMSE      MAE        R2
## 1 Gradient Boosting                  992 125.6057 60.14508 0.4426829

VISUALISASI AKTUAL DAN PREDIKSI

data_plot <- data.frame(
  Aktual = actual,
  Prediksi = pred_valid
)

p_aktual_prediksi <- ggplot(
  data_plot,
  aes(x = Aktual, y = Prediksi)
) +
  geom_point(alpha = 0.4, color = "#21618C") +
  geom_abline(
    intercept = 0,
    slope = 1,
    linetype = "dashed",
    color = "#C0392B"
  ) +
  labs(
    title = "Aktual vs Prediksi Gradient Boosting",
    x = "Harga Aktual",
    y = "Harga Prediksi"
  ) +
  theme_minimal()

print(p_aktual_prediksi)

VISUALISASI RESIDUAL

data_residual <- data.frame(
  Prediksi = pred_valid,
  Residual = actual - pred_valid
)

p_residual <- ggplot(
  data_residual,
  aes(x = Prediksi, y = Residual)
) +
  geom_point(color = "#21618C", alpha = 0.5) +
  geom_hline(
    yintercept = 0,
    color = "#C0392B",
    linetype = "dashed"
  ) +
  labs(
    title = "Residual Plot Gradient Boosting",
    x = "Harga Prediksi",
    y = "Residual"
  ) +
  theme_minimal()

print(p_residual)

VARIABLE RELATIVE INFLUENCE

importance_gb <- summary(
  model_gb,
  n.trees = jumlah_pohon_optimal,
  plotit = FALSE
)

importance_gb <- importance_gb[
  order(importance_gb$rel.inf, decreasing = TRUE),
]

cat("\nRELATIVE INFLUENCE\n")
## 
## RELATIVE INFLUENCE
print(importance_gb)
##                                                                   var
## room_typePrivate.room                           room_typePrivate.room
## bedrooms                                                     bedrooms
## accommodates                                             accommodates
## room_typeShared.room                             room_typeShared.room
## bathrooms                                                   bathrooms
## number_of_reviews                                   number_of_reviews
## review_scores_rating                             review_scores_rating
## beds                                                             beds
## property_typeCondominium                     property_typeCondominium
## host_response_rate                                 host_response_rate
## cancellation_policystrict                   cancellation_policystrict
## property_typeHouse                                 property_typeHouse
## instant_bookablet                                   instant_bookablet
## property_typeLoft                                   property_typeLoft
## property_typeTimeshare                         property_typeTimeshare
## property_typeOther                                 property_typeOther
## property_typeApartment                         property_typeApartment
## cancellation_policymoderate               cancellation_policymoderate
## property_typeBed...Breakfast             property_typeBed...Breakfast
## cancellation_policysuper_strict_30 cancellation_policysuper_strict_30
## property_typeDorm                                   property_typeDorm
## property_typeGuesthouse                       property_typeGuesthouse
## property_typeBoutique.hotel               property_typeBoutique.hotel
## property_typeHostel                               property_typeHostel
## property_typeTownhouse                         property_typeTownhouse
## property_typeVilla                                 property_typeVilla
## cancellation_policysuper_strict_60 cancellation_policysuper_strict_60
## property_typeBungalow                           property_typeBungalow
## property_typeCamper.RV                         property_typeCamper.RV
## property_typeHut                                     property_typeHut
## property_typeCastle                               property_typeCastle
## property_typeBoat                                   property_typeBoat
## property_typeTent                                   property_typeTent
## property_typeServiced.apartment       property_typeServiced.apartment
## property_typeYurt                                   property_typeYurt
## property_typeCabin                                 property_typeCabin
## property_typeGuest.suite                     property_typeGuest.suite
## property_typeTipi                                   property_typeTipi
## property_typeIn.law                               property_typeIn.law
## property_typeCasa.particular             property_typeCasa.particular
## property_typeCave                                   property_typeCave
## property_typeChalet                               property_typeChalet
## property_typeEarth.House                     property_typeEarth.House
## property_typeIsland                               property_typeIsland
## property_typeLighthouse                       property_typeLighthouse
## property_typeParking.Space                 property_typeParking.Space
## property_typeTrain                                 property_typeTrain
## property_typeTreehouse                         property_typeTreehouse
## property_typeVacation.home                 property_typeVacation.home
## cleaning_fee                                             cleaning_fee
##                                         rel.inf
## room_typePrivate.room              33.063046394
## bedrooms                           21.045450690
## accommodates                       19.404653453
## room_typeShared.room               11.198534580
## bathrooms                           7.109951471
## number_of_reviews                   2.862817091
## review_scores_rating                1.581605498
## beds                                0.680402790
## property_typeCondominium            0.381969550
## host_response_rate                  0.354379034
## cancellation_policystrict           0.348865589
## property_typeHouse                  0.312614097
## instant_bookablet                   0.220099325
## property_typeLoft                   0.203114477
## property_typeTimeshare              0.136472979
## property_typeOther                  0.123037649
## property_typeApartment              0.103003123
## cancellation_policymoderate         0.095300930
## property_typeBed...Breakfast        0.087415124
## cancellation_policysuper_strict_30  0.087295517
## property_typeDorm                   0.078439072
## property_typeGuesthouse             0.077642440
## property_typeBoutique.hotel         0.063887191
## property_typeHostel                 0.054648187
## property_typeTownhouse              0.048018245
## property_typeVilla                  0.041238350
## cancellation_policysuper_strict_60  0.036130248
## property_typeBungalow               0.035684433
## property_typeCamper.RV              0.029410654
## property_typeHut                    0.028215341
## property_typeCastle                 0.026969677
## property_typeBoat                   0.020613185
## property_typeTent                   0.014325391
## property_typeServiced.apartment     0.013687123
## property_typeYurt                   0.010572050
## property_typeCabin                  0.008448837
## property_typeGuest.suite            0.006181979
## property_typeTipi                   0.003720010
## property_typeIn.law                 0.002138227
## property_typeCasa.particular        0.000000000
## property_typeCave                   0.000000000
## property_typeChalet                 0.000000000
## property_typeEarth.House            0.000000000
## property_typeIsland                 0.000000000
## property_typeLighthouse             0.000000000
## property_typeParking.Space          0.000000000
## property_typeTrain                  0.000000000
## property_typeTreehouse              0.000000000
## property_typeVacation.home          0.000000000
## cleaning_fee                        0.000000000
importance_top <- head(importance_gb, 15)

p_importance <- ggplot(
  importance_top,
  aes(x = rel.inf, y = reorder(var, rel.inf), fill = rel.inf)
) +
  geom_col(width = 0.7, color = NA) +
  geom_text(
    aes(label = sprintf("%.2f", rel.inf)),
    hjust = -0.15, size = 3.2, color = "#263238"
  ) +
  scale_fill_gradient(low = "#A6CEE3", high = "#084B75") +
  scale_x_continuous(expand = expansion(mult = c(0, 0.15))) +
  labs(
    title = "Variable Relative Influence",
    subtitle = "15 Variabel dengan Pengaruh Terbesar",
    x = "Relative Influence (%)",
    y = "Variabel Prediktor"
  ) +
  theme_minimal() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank(),
    legend.position = "none"
  )

print(p_importance)

STRUKTUR POHON REGRESI

# Mengambil struktur pohon pertama
pohon_pertama <- pretty.gbm.tree(
  model_gb,
  i.tree = 1
)

cat("\nSTRUKTUR POHON ITERASI PERTAMA\n")
## 
## STRUKTUR POHON ITERASI PERTAMA
print(pohon_pertama)
##   SplitVar SplitCodePred LeftNode RightNode MissingNode ErrorReduction Weight
## 0       35  5.000000e-01        1         8           9       4716.211  33202
## 1       38  1.500000e+00        2         6           7       1910.328  19481
## 2       36  5.000000e-01        3         4           5       1011.151  12100
## 3       -1  7.823734e-03       -1        -1          -1          0.000  11153
## 4       -1 -4.599083e-02       -1        -1          -1          0.000    947
## 5       -1  3.611966e-03       -1        -1          -1          0.000  12100
## 6       -1  3.588794e-02       -1        -1          -1          0.000   7381
## 7       -1  1.584075e-02       -1        -1          -1          0.000  19481
## 8       -1 -2.242852e-02       -1        -1          -1          0.000  13721
## 9       -1  2.565708e-05       -1        -1          -1          0.000  33202
##      Prediction
## 0  2.565708e-05
## 1  1.584075e-02
## 2  3.611966e-03
## 3  7.823734e-03
## 4 -4.599083e-02
## 5  3.611966e-03
## 6  3.588794e-02
## 7  1.584075e-02
## 8 -2.242852e-02
## 9  2.565708e-05

MEMBENTUK MODEL FINAL MENGGUNAKAN SELURUH DATA TRAINING

# Model final menggunakan jumlah pohon
# yang telah ditemukan melalui Cross Validation.

data_gb_full <- data.frame(
  y = as.numeric(y_all),
  X_train_all,
  check.names = TRUE
)

set.seed(123)

model_gb_final <- gbm(
  formula = y ~ .,
  distribution = "gaussian",
  data = data_gb_full,

  n.trees = jumlah_pohon_optimal,

  interaction.depth = 3,
  n.minobsinnode = 2,
  shrinkage = 0.05,
  bag.fraction = 0.8,

  cv.folds = 0,
  n.cores = 1,

  verbose = FALSE
)

PREDIKSI AKHIR DATA TESTING

data_test_gb <- as.data.frame(
  X_test_all,
  check.names = TRUE
)

pred_test_log <- predict(
  model_gb_final,
  newdata = data_test_gb,
  n.trees = jumlah_pohon_optimal,
  type = "response"
)

# Mengembalikan ke skala asli
pred_test_final <- expm1(pred_test_log)

# Hasil prediksi akhir
hasil_prediksi_final <- data.frame(
  id = id_test,
  Prediksi_Price = pred_test_final
)

cat("\nHASIL PREDIKSI AKHIR\n")
## 
## HASIL PREDIKSI AKHIR
print(head(hasil_prediksi_final))
##         id Prediksi_Price
## 1 17423675      209.51931
## 2  6226658       87.28115
## 3  3563677      144.70815
## 4 17615783       72.61099
## 5  2479317      183.60472
## 6 14122244      241.11865

MENYIMPAN HASIL

# Evaluasi model
write.csv(
  hasil_evaluasi,
  "Evaluasi_Gradient_Boosting.csv",
  row.names = FALSE
)

# Prediksi validasi
hasil_validasi <- data.frame(
  Aktual = actual,
  Prediksi = pred_valid
)

write.csv(
  hasil_validasi,
  "Prediksi_Validasi_Gradient_Boosting.csv",
  row.names = FALSE
)

# Prediksi data testing
write.csv(
  hasil_prediksi_final,
  "Prediksi_Final_Gradient_Boosting.csv",
  row.names = FALSE
)

# Struktur pohon pertama
write.csv(
  pohon_pertama,
  "Struktur_Pohon_Gradient_Boosting.csv",
  row.names = FALSE
)

# Relative influence
write.csv(
  importance_gb,
  "Relative_Influence_Gradient_Boosting.csv",
  row.names = FALSE
)

RINGKASAN AKHIR

cat("\n")
cat("============================================")
## ============================================
cat("\nANALISIS GRADIENT BOOSTING SELESAI\n")
## 
## ANALISIS GRADIENT BOOSTING SELESAI
cat("============================================\n")
## ============================================
cat("Jumlah data training:", nrow(train), "\n")
## Jumlah data training: 51879
cat("Jumlah data testing:", nrow(test), "\n")
## Jumlah data testing: 22232
cat("Jumlah variabel X:", length(variabel_X), "\n")
## Jumlah variabel X: 12
cat("Jumlah prediktor setelah dummy:", ncol(X_train_all), "\n")
## Jumlah prediktor setelah dummy: 50
cat("Jumlah pohon optimal:", jumlah_pohon_optimal, "\n")
## Jumlah pohon optimal: 992
cat("\nHASIL EVALUASI MODEL\n")
## 
## HASIL EVALUASI MODEL
cat("RMSE:", RMSE, "\n")
## RMSE: 125.6057
cat("MAE:", MAE, "\n")
## MAE: 60.14508
cat("R-Squared:", R2, "\n")
## R-Squared: 0.4426829
cat("\nFile hasil berhasil disimpan.\n")
## 
## File hasil berhasil disimpan.
cat("============================================\n")
## ============================================