0. Persiapan: Library dan Data
# ============================================================
# ANALISIS MULTIPLE LINEAR REGRESSION (MLR)
# Data: Food Delivery Times (waktu pengantaran makanan)
# Alur: EDA -> Estimasi -> Uji F & t -> R2 -> Uji Asumsi ->
# Diagnostik Outlier -> Pemilihan Model -> Ringkasan
# ============================================================
# ------------------------------------------------------------
# 0. PERSIAPAN: LOAD LIBRARY
# ------------------------------------------------------------
# Install package jika belum ada (uncomment jika perlu)
# install.packages(c("car", "lmtest", "nortest", "moments", "corrplot"))
library(car) # Untuk VIF
## Warning: package 'car' was built under R version 4.5.3
## Loading required package: carData
## Warning: package 'carData' was built under R version 4.5.2
library(lmtest) # Untuk uji Breusch-Pagan dan Ramsey RESET
## Warning: package 'lmtest' was built under R version 4.5.3
## Loading required package: zoo
## Warning: package 'zoo' was built under R version 4.5.2
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
library(nortest) # Untuk uji normalitas Anderson-Darling
## Warning: package 'nortest' was built under R version 4.5.2
library(moments) # Untuk skewness dan kurtosis residual
## Warning: package 'moments' was built under R version 4.5.2
library(corrplot) # Untuk matriks korelasi
## Warning: package 'corrplot' was built under R version 4.5.3
## corrplot 0.95 loaded
1. Data dan Preprocessing
# ------------------------------------------------------------
# 1. MEMBACA DATA
# ------------------------------------------------------------
# Nilai kosong pada file CSV berupa sel kosong, sehingga perlu
# didefinisikan sebagai NA saat membaca data
data_asli <- read.csv("Food_Delivery_Times.csv",
na.strings = c("", "NA"),
stringsAsFactors = FALSE)
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("DATA YANG DIGUNAKAN\n")
## DATA YANG DIGUNAKAN
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
str(data_asli)
## 'data.frame': 1000 obs. of 9 variables:
## $ Order_ID : int 522 738 741 661 412 679 627 514 860 137 ...
## $ Distance_km : num 7.93 16.42 9.52 7.44 19.03 ...
## $ Weather : chr "Windy" "Clear" "Foggy" "Rainy" ...
## $ Traffic_Level : chr "Low" "Medium" "Low" "Medium" ...
## $ Time_of_Day : chr "Afternoon" "Evening" "Night" "Afternoon" ...
## $ Vehicle_Type : chr "Scooter" "Bike" "Scooter" "Scooter" ...
## $ Preparation_Time_min : int 12 20 28 5 16 8 12 5 20 29 ...
## $ Courier_Experience_yrs: num 1 2 1 1 5 9 1 6 6 1 ...
## $ Delivery_Time_min : int 43 84 59 37 68 57 49 46 35 73 ...
print(head(data_asli))
## Order_ID Distance_km Weather Traffic_Level Time_of_Day Vehicle_Type
## 1 522 7.93 Windy Low Afternoon Scooter
## 2 738 16.42 Clear Medium Evening Bike
## 3 741 9.52 Foggy Low Night Scooter
## 4 661 7.44 Rainy Medium Afternoon Scooter
## 5 412 19.03 Clear Low Morning Bike
## 6 679 19.40 Clear Low Evening Scooter
## Preparation_Time_min Courier_Experience_yrs Delivery_Time_min
## 1 12 1 43
## 2 20 2 84
## 3 28 1 59
## 4 5 1 37
## 5 16 5 68
## 6 8 9 57
# ------------------------------------------------------------
# 1.1 MISSING VALUE
# ------------------------------------------------------------
cat("\nJumlah missing value per variabel:\n")
##
## Jumlah missing value per variabel:
print(colSums(is.na(data_asli)))
## Order_ID Distance_km Weather
## 0 0 30
## Traffic_Level Time_of_Day Vehicle_Type
## 30 30 0
## Preparation_Time_min Courier_Experience_yrs Delivery_Time_min
## 0 30 0
cat("\nBaris dengan minimal 1 missing value:",
sum(!complete.cases(data_asli)), "dari", nrow(data_asli), "baris\n")
##
## Baris dengan minimal 1 missing value: 117 dari 1000 baris
# ------------------------------------------------------------
# 1.2 PREPROCESSING
# ------------------------------------------------------------
# Order_ID hanya penanda pesanan (bukan variabel penjelas) -> dibuang
data <- data_asli[, setdiff(names(data_asli), "Order_ID")]
# Missing value hanya sekitar 3% per variabel, dan jumlah data
# setelah penghapusan masih besar -> baris yang memuat missing dihapus
data <- na.omit(data)
# Variabel kategorik diubah menjadi factor. Urutan level menentukan
# kategori referensi (level pertama) pada koefisien regresi
data$Weather <- factor(data$Weather,
levels = c("Clear", "Foggy", "Rainy", "Snowy", "Windy"))
data$Traffic_Level <- factor(data$Traffic_Level,
levels = c("Low", "Medium", "High"))
data$Time_of_Day <- factor(data$Time_of_Day,
levels = c("Morning", "Afternoon", "Evening", "Night"))
data$Vehicle_Type <- factor(data$Vehicle_Type,
levels = c("Bike", "Scooter", "Car"))
n <- nrow(data)
cat("\nJumlah observasi setelah preprocessing (n):", n, "\n")
##
## Jumlah observasi setelah preprocessing (n): 883
cat("Jumlah variabel prediktor:", ncol(data) - 1, "\n")
## Jumlah variabel prediktor: 7
2. Statistik Deskriptif dan Eksplorasi Data (EDA)
# ------------------------------------------------------------
# 2. STATISTIK DESKRIPTIF DAN EKSPLORASI DATA
# ------------------------------------------------------------
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("1. STATISTIK DESKRIPTIF\n")
## 1. STATISTIK DESKRIPTIF
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
# Statistik deskriptif
summary(data)
## Distance_km Weather Traffic_Level Time_of_Day Vehicle_Type
## Min. : 0.59 Clear:425 Low :347 Morning :281 Bike :451
## 1st Qu.: 5.13 Foggy: 98 Medium:355 Afternoon:257 Scooter:260
## Median :10.28 Rainy:188 High :181 Evening :269 Car :172
## Mean :10.05 Snowy: 86 Night : 76
## 3rd Qu.:15.03 Windy: 86
## Max. :19.99
## Preparation_Time_min Courier_Experience_yrs Delivery_Time_min
## Min. : 5.00 Min. :0.00 Min. : 8.00
## 1st Qu.:11.00 1st Qu.:2.00 1st Qu.: 41.00
## Median :17.00 Median :5.00 Median : 55.00
## Mean :17.02 Mean :4.64 Mean : 56.43
## 3rd Qu.:23.00 3rd Qu.:7.00 3rd Qu.: 71.00
## Max. :29.00 Max. :9.00 Max. :141.00
# Matriks korelasi (hanya untuk variabel numerik)
var_numerik <- c("Delivery_Time_min", "Distance_km",
"Preparation_Time_min", "Courier_Experience_yrs")
cat("\nMatriks Korelasi:\n")
##
## Matriks Korelasi:
cor_matrix <- cor(data[, var_numerik])
print(round(cor_matrix, 3))
## Delivery_Time_min Distance_km Preparation_Time_min
## Delivery_Time_min 1.000 0.783 0.297
## Distance_km 0.783 1.000 -0.026
## Preparation_Time_min 0.297 -0.026 1.000
## Courier_Experience_yrs -0.077 -0.003 -0.031
## Courier_Experience_yrs
## Delivery_Time_min -0.077
## Distance_km -0.003
## Preparation_Time_min -0.031
## Courier_Experience_yrs 1.000
# Visualisasi matriks korelasi
corrplot(cor_matrix, method = "color", type = "upper",
addCoef.col = "black", tl.col = "black", tl.srt = 45,
title = "Matriks Korelasi Antar Variabel Numerik", mar = c(0, 0, 2, 0))

# Scatter plot matrix
pairs(data[, var_numerik], main = "Scatter Plot Matrix", pch = 19, col = "steelblue")

# Distribusi dan outlier variabel respon
par(mfrow = c(1, 2))
hist(data$Delivery_Time_min, main = "Histogram Waktu Pengantaran",
xlab = "Waktu pengantaran (menit)", col = "steelblue", border = "white")
boxplot(data$Delivery_Time_min, main = "Boxplot Waktu Pengantaran",
ylab = "Waktu pengantaran (menit)", col = "steelblue")

par(mfrow = c(1, 1))
outlier_y <- boxplot.stats(data$Delivery_Time_min)$out
cat("\nJumlah outlier Delivery_Time_min (aturan boxplot):", length(outlier_y), "\n")
##
## Jumlah outlier Delivery_Time_min (aturan boxplot): 4
cat("(Outlier tidak dihapus karena merupakan nilai yang masuk akal, bukan salah input;\n")
## (Outlier tidak dihapus karena merupakan nilai yang masuk akal, bukan salah input;
cat(" pengaruhnya terhadap model diperiksa pada bagian diagnostik.)\n")
## pengaruhnya terhadap model diperiksa pada bagian diagnostik.)
# Waktu pengantaran menurut variabel kategorik
par(mfrow = c(2, 2))
boxplot(Delivery_Time_min ~ Weather, data = data, main = "Menurut Cuaca",
xlab = "", ylab = "Menit", col = "lightblue")
boxplot(Delivery_Time_min ~ Traffic_Level, data = data, main = "Menurut Lalu Lintas",
xlab = "", ylab = "Menit", col = "lightblue")
boxplot(Delivery_Time_min ~ Time_of_Day, data = data, main = "Menurut Waktu",
xlab = "", ylab = "Menit", col = "lightblue")
boxplot(Delivery_Time_min ~ Vehicle_Type, data = data, main = "Menurut Kendaraan",
xlab = "", ylab = "Menit", col = "lightblue")

par(mfrow = c(1, 1))
3. Estimasi Model MLR
# ------------------------------------------------------------
# 3. ESTIMASI MODEL MULTIPLE LINEAR REGRESSION
# ------------------------------------------------------------
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("2. ESTIMASI MODEL MLR\n")
## 2. ESTIMASI MODEL MLR
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
# Fit model MLR (model penuh, semua prediktor)
model <- lm(Delivery_Time_min ~ Distance_km + Preparation_Time_min +
Courier_Experience_yrs + Weather + Traffic_Level +
Time_of_Day + Vehicle_Type,
data = data)
# Ringkasan model
cat("\nRingkasan Model:\n")
##
## Ringkasan Model:
print(summary(model))
##
## Call:
## lm(formula = Delivery_Time_min ~ Distance_km + Preparation_Time_min +
## Courier_Experience_yrs + Weather + Traffic_Level + Time_of_Day +
## Vehicle_Type, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -19.488 -4.684 -1.189 3.012 61.446
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 4.80052 1.45345 3.303 0.000996 ***
## Distance_km 2.95778 0.05959 49.632 < 2e-16 ***
## Preparation_Time_min 0.95199 0.04664 20.412 < 2e-16 ***
## Courier_Experience_yrs -0.55123 0.11623 -4.743 2.46e-06 ***
## WeatherFoggy 7.78608 1.12570 6.917 8.97e-12 ***
## WeatherRainy 4.90641 0.88229 5.561 3.57e-08 ***
## WeatherSnowy 9.84553 1.19586 8.233 6.67e-16 ***
## WeatherWindy 2.13038 1.19369 1.785 0.074659 .
## Traffic_LevelMedium 6.04437 0.76392 7.912 7.68e-15 ***
## Traffic_LevelHigh 12.09852 0.92239 13.116 < 2e-16 ***
## Time_of_DayAfternoon 0.70893 0.86777 0.817 0.414174
## Time_of_DayEvening 1.35702 0.85929 1.579 0.114647
## Time_of_DayNight -0.60854 1.30464 -0.466 0.641014
## Vehicle_TypeScooter -1.20253 0.78460 -1.533 0.125723
## Vehicle_TypeCar 0.26682 0.90421 0.295 0.768001
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.01 on 868 degrees of freedom
## Multiple R-squared: 0.7879, Adjusted R-squared: 0.7845
## F-statistic: 230.3 on 14 and 868 DF, p-value: < 2.2e-16
# Ekstrak koefisien
coef_tab <- summary(model)$coefficients
cat("\nKoefisien Regresi:\n")
##
## Koefisien Regresi:
coef_df <- data.frame(
Variabel = rownames(coef_tab),
Koefisien = round(coef_tab[, 1], 4),
Std_Error = round(coef_tab[, 2], 4),
t_value = round(coef_tab[, 3], 4),
p_value = round(coef_tab[, 4], 4),
row.names = NULL
)
print(coef_df)
## Variabel Koefisien Std_Error t_value p_value
## 1 (Intercept) 4.8005 1.4534 3.3028 0.0010
## 2 Distance_km 2.9578 0.0596 49.6320 0.0000
## 3 Preparation_Time_min 0.9520 0.0466 20.4120 0.0000
## 4 Courier_Experience_yrs -0.5512 0.1162 -4.7428 0.0000
## 5 WeatherFoggy 7.7861 1.1257 6.9167 0.0000
## 6 WeatherRainy 4.9064 0.8823 5.5610 0.0000
## 7 WeatherSnowy 9.8455 1.1959 8.2330 0.0000
## 8 WeatherWindy 2.1304 1.1937 1.7847 0.0747
## 9 Traffic_LevelMedium 6.0444 0.7639 7.9123 0.0000
## 10 Traffic_LevelHigh 12.0985 0.9224 13.1165 0.0000
## 11 Time_of_DayAfternoon 0.7089 0.8678 0.8170 0.4142
## 12 Time_of_DayEvening 1.3570 0.8593 1.5792 0.1146
## 13 Time_of_DayNight -0.6085 1.3046 -0.4664 0.6410
## 14 Vehicle_TypeScooter -1.2025 0.7846 -1.5327 0.1257
## 15 Vehicle_TypeCar 0.2668 0.9042 0.2951 0.7680
# Interval kepercayaan 95%
cat("\nInterval Kepercayaan 95%:\n")
##
## Interval Kepercayaan 95%:
ci <- confint(model, level = 0.95)
print(round(ci, 4))
## 2.5 % 97.5 %
## (Intercept) 1.9478 7.6532
## Distance_km 2.8408 3.0747
## Preparation_Time_min 0.8604 1.0435
## Courier_Experience_yrs -0.7794 -0.3231
## WeatherFoggy 5.5767 9.9955
## WeatherRainy 3.1747 6.6381
## WeatherSnowy 7.4984 12.1926
## WeatherWindy -0.2125 4.4732
## Traffic_LevelMedium 4.5450 7.5437
## Traffic_LevelHigh 10.2881 13.9089
## Time_of_DayAfternoon -0.9942 2.4121
## Time_of_DayEvening -0.3295 3.0435
## Time_of_DayNight -3.1692 1.9521
## Vehicle_TypeScooter -2.7425 0.3374
## Vehicle_TypeCar -1.5079 2.0415
4. Uji Signifikansi Simultan (Uji F)
# ------------------------------------------------------------
# 4. UJI SIGNIFIKANSI SIMULTAN (UJI F)
# ------------------------------------------------------------
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("3. UJI SIGNIFIKANSI SIMULTAN (UJI F)\n")
## 3. UJI SIGNIFIKANSI SIMULTAN (UJI F)
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
# Ekstrak informasi F-test
f_stat <- summary(model)$fstatistic
f_value <- unname(f_stat[1])
df1 <- unname(f_stat[2])
df2 <- unname(f_stat[3])
f_pvalue <- pf(f_value, df1, df2, lower.tail = FALSE)
cat("\nHipotesis:\n")
##
## Hipotesis:
cat("H0: β1 = β2 = ... = βp = 0 (Model tidak signifikan)\n")
## H0: β1 = β2 = ... = βp = 0 (Model tidak signifikan)
cat("H1: Minimal ada satu βj ≠ 0 (Model signifikan)\n")
## H1: Minimal ada satu βj ≠ 0 (Model signifikan)
cat("\nHasil Uji F:\n")
##
## Hasil Uji F:
cat("F-statistic:", round(f_value, 4), "\n")
## F-statistic: 230.3132
cat("df1:", df1, "\n")
## df1: 14
cat("df2:", df2, "\n")
## df2: 868
cat("p-value:", format(f_pvalue, scientific = TRUE), "\n")
## p-value: 1.226723e-280
if (f_pvalue < 0.05) {
cat("Kesimpulan: Tolak H0 → Model signifikan pada α = 5%\n")
} else {
cat("Kesimpulan: Gagal Tolak H0 → Model tidak signifikan\n")
}
## Kesimpulan: Tolak H0 → Model signifikan pada α = 5%
5. Uji Signifikansi Parsial (Uji t)
# ------------------------------------------------------------
# 5. UJI SIGNIFIKANSI PARSIAL (UJI T)
# ------------------------------------------------------------
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("4. UJI SIGNIFIKANSI PARSIAL (UJI T)\n")
## 4. UJI SIGNIFIKANSI PARSIAL (UJI T)
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("\nHipotesis untuk setiap βj:\n")
##
## Hipotesis untuk setiap βj:
cat("H0: βj = 0 (Variabel tidak signifikan)\n")
## H0: βj = 0 (Variabel tidak signifikan)
cat("H1: βj ≠ 0 (Variabel signifikan)\n")
## H1: βj ≠ 0 (Variabel signifikan)
cat("(Untuk variabel kategorik, setiap koefisien membandingkan satu kategori\n")
## (Untuk variabel kategorik, setiap koefisien membandingkan satu kategori
cat(" dengan kategori referensinya: Clear, Low, Morning, dan Bike)\n")
## dengan kategori referensinya: Clear, Low, Morning, dan Bike)
cat("\nHasil Uji T:\n")
##
## Hasil Uji T:
for (i in 2:nrow(coef_tab)) {
var_name <- rownames(coef_tab)[i]
t_val <- coef_tab[i, 3]
p_val <- coef_tab[i, 4]
sig <- ifelse(p_val < 0.001, "***",
ifelse(p_val < 0.01, "**",
ifelse(p_val < 0.05, "*",
ifelse(p_val < 0.1, ".", "ns"))))
cat(sprintf("%-28s: t = %8.4f, p = %s %s\n",
var_name, t_val, format.pval(p_val, digits = 3, eps = 0.0001), sig))
}
## Distance_km : t = 49.6320, p = <1e-04 ***
## Preparation_Time_min : t = 20.4120, p = <1e-04 ***
## Courier_Experience_yrs : t = -4.7428, p = <1e-04 ***
## WeatherFoggy : t = 6.9167, p = <1e-04 ***
## WeatherRainy : t = 5.5610, p = <1e-04 ***
## WeatherSnowy : t = 8.2330, p = <1e-04 ***
## WeatherWindy : t = 1.7847, p = 0.0747 .
## Traffic_LevelMedium : t = 7.9123, p = <1e-04 ***
## Traffic_LevelHigh : t = 13.1165, p = <1e-04 ***
## Time_of_DayAfternoon : t = 0.8170, p = 0.414 ns
## Time_of_DayEvening : t = 1.5792, p = 0.115 ns
## Time_of_DayNight : t = -0.4664, p = 0.641 ns
## Vehicle_TypeScooter : t = -1.5327, p = 0.126 ns
## Vehicle_TypeCar : t = 0.2951, p = 0.768 ns
cat("\nKeterangan: *** p<0.001, ** p<0.01, * p<0.05, . p<0.1, ns tidak signifikan\n")
##
## Keterangan: *** p<0.001, ** p<0.01, * p<0.05, . p<0.1, ns tidak signifikan
# Uji parsial per VARIABEL (bukan per kategori). Diperlukan karena
# variabel kategorik memiliki lebih dari satu koefisien dummy
cat("\nUji F parsial per variabel (setiap variabel diuji utuh):\n")
##
## Uji F parsial per variabel (setiap variabel diuji utuh):
print(drop1(model, test = "F"))
## Single term deletions
##
## Model:
## Delivery_Time_min ~ Distance_km + Preparation_Time_min + Courier_Experience_yrs +
## Weather + Traffic_Level + Time_of_Day + Vehicle_Type
## Df Sum of Sq RSS AIC F value Pr(>F)
## <none> 87026 4083.5
## Distance_km 1 246976 334002 5269.1 2463.3352 < 2.2e-16 ***
## Preparation_Time_min 1 41774 128800 4427.7 416.6505 < 2.2e-16 ***
## Courier_Experience_yrs 1 2255 89282 4104.1 22.4942 2.463e-06 ***
## Weather 4 10655 97682 4177.5 26.5688 < 2.2e-16 ***
## Traffic_Level 2 17953 104980 4245.1 89.5330 < 2.2e-16 ***
## Time_of_Day 3 366 87392 4081.2 1.2157 0.3029
## Vehicle_Type 2 303 87329 4082.6 1.5095 0.2216
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
6. Koefisien Determinasi
# ------------------------------------------------------------
# 6. KOEFISIEN DETERMINASI (R² dan Adjusted R²)
# ------------------------------------------------------------
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("5. KOEFISIEN DETERMINASI\n")
## 5. KOEFISIEN DETERMINASI
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
r_squared <- summary(model)$r.squared
adj_r_squared <- summary(model)$adj.r.squared
resid_se <- summary(model)$sigma
cat("\nR-squared:", round(r_squared, 4), "\n")
##
## R-squared: 0.7879
cat("Adjusted R-squared:", round(adj_r_squared, 4), "\n")
## Adjusted R-squared: 0.7845
cat("Residual Standard Error:", round(resid_se, 4), "menit\n")
## Residual Standard Error: 10.013 menit
cat("Interpretasi: Model mampu menjelaskan",
round(r_squared * 100, 2), "% variasi waktu pengantaran\n")
## Interpretasi: Model mampu menjelaskan 78.79 % variasi waktu pengantaran
7. Uji Asumsi Residual
# ------------------------------------------------------------
# 7. UJI ASUMSI RESIDUAL
# ------------------------------------------------------------
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("6. UJI ASUMSI RESIDUAL\n")
## 6. UJI ASUMSI RESIDUAL
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
# Ekstrak residual
resid_model <- residuals(model)
fitted_values <- fitted(model)
standardized_resid <- rstandard(model)
# --- 7.1. UJI NORMALITAS RESIDUAL ---
cat("\n--- 6.1. UJI NORMALITAS RESIDUAL ---\n")
##
## --- 6.1. UJI NORMALITAS RESIDUAL ---
# Pemilihan uji: n = 883 (sampel besar), sehingga dipakai Anderson-Darling.
# Uji ini sensitif pada bagian ekor sebaran, dan kinerjanya membaik pada
# sampel yang lebih besar
# Tidak dipakai: Shapiro-Wilk (umumnya dipakai untuk sampel kecil-sedang),
# Kolmogorov-Smirnov (parameter mean & sd diestimasi dari data sehingga
# p-value tidak valid), dan Jarque-Bera (hasilnya sejalan, tidak diulang).
ad_test <- ad.test(resid_model)
cat("\nAnderson-Darling Test:\n")
##
## Anderson-Darling Test:
cat("H0: Residual berdistribusi normal\n")
## H0: Residual berdistribusi normal
cat("A =", round(ad_test$statistic, 4), "\n")
## A = 33.3302
cat("p-value =", format(ad_test$p.value, scientific = TRUE, digits = 4), "\n")
## p-value = 3.7e-24
cat("Kesimpulan:", ifelse(ad_test$p.value > 0.05,
"Residual berdistribusi normal",
"Residual TIDAK berdistribusi normal"), "\n")
## Kesimpulan: Residual TIDAK berdistribusi normal
# Skewness dan kurtosis sebagai bukti pendukung bentuk sebaran residual
skew_resid <- skewness(resid_model)
kurt_resid <- kurtosis(resid_model)
cat("\nSkewness residual:", round(skew_resid, 4), "(normal = 0)\n")
##
## Skewness residual: 2.5496 (normal = 0)
cat("Kurtosis residual:", round(kurt_resid, 4), "(normal = 3)\n")
## Kurtosis residual: 14.3563 (normal = 3)
# --- 7.2. UJI HETEROSKEDASTISITAS ---
cat("\n--- 6.2. UJI HETEROSKEDASTISITAS ---\n")
##
## --- 6.2. UJI HETEROSKEDASTISITAS ---
# Breusch-Pagan (versi studentized/Koenker pada bptest), cocok untuk
# n besar dan tidak mensyaratkan residual normal
bp_test <- bptest(model)
cat("\nBreusch-Pagan Test:\n")
##
## Breusch-Pagan Test:
cat("H0: Varians residual konstan (homoskedastis)\n")
## H0: Varians residual konstan (homoskedastis)
cat("BP =", round(bp_test$statistic, 4), ", df =", bp_test$parameter, "\n")
## BP = 20.4758 , df = 14
cat("p-value =", round(bp_test$p.value, 4), "\n")
## p-value = 0.1158
cat("Kesimpulan:", ifelse(bp_test$p.value > 0.05,
"Tidak ada heteroskedastisitas",
"Ada heteroskedastisitas"), "\n")
## Kesimpulan: Tidak ada heteroskedastisitas
# --- 7.3. UJI AUTOKORELASI ---
# Tidak dilakukan. Uji Durbin-Watson/Breusch-Godfrey untuk data yang
# berurutan (deret waktu). Data ini cross-section: setiap baris adalah
# pesanan berbeda tanpa urutan waktu, sehingga uji tersebut tidak bermakna.
# --- 7.4. UJI MULTIKOLINEARITAS ---
cat("\n--- 6.4. UJI MULTIKOLINEARITAS ---\n")
##
## --- 6.4. UJI MULTIKOLINEARITAS ---
# Karena ada variabel kategorik (lebih dari 1 df), vif() dari package car
# menghasilkan GVIF. Nilai GVIF^(1/Df) dikuadratkan agar sebanding dengan
# VIF biasa, sehingga batas 5 dan 10 tetap dapat dipakai.
vif_raw <- vif(model)
cat("\nGVIF (keluaran vif):\n")
##
## GVIF (keluaran vif):
print(round(vif_raw, 4))
## GVIF Df GVIF^(1/(2*Df))
## Distance_km 1.0110 1 1.0055
## Preparation_Time_min 1.0086 1 1.0043
## Courier_Experience_yrs 1.0147 1 1.0073
## Weather 1.0538 4 1.0066
## Traffic_Level 1.0274 2 1.0068
## Time_of_Day 1.0307 3 1.0051
## Vehicle_Type 1.0306 2 1.0076
if (is.matrix(vif_raw)) {
vif_values <- vif_raw[, 3]^2
} else {
vif_values <- vif_raw
}
cat("\nInterpretasi VIF:\n")
##
## Interpretasi VIF:
for (i in 1:length(vif_values)) {
vif_val <- vif_values[i]
var_name <- names(vif_values)[i]
status <- ifelse(vif_val < 5, "Tidak ada multikolinearitas",
ifelse(vif_val < 10, "Multikolinearitas moderat",
"Multikolinearitas serius"))
cat(sprintf("%-25s: VIF = %7.4f → %s\n", var_name, vif_val, status))
}
## Distance_km : VIF = 1.0110 → Tidak ada multikolinearitas
## Preparation_Time_min : VIF = 1.0086 → Tidak ada multikolinearitas
## Courier_Experience_yrs : VIF = 1.0147 → Tidak ada multikolinearitas
## Weather : VIF = 1.0132 → Tidak ada multikolinearitas
## Traffic_Level : VIF = 1.0136 → Tidak ada multikolinearitas
## Time_of_Day : VIF = 1.0101 → Tidak ada multikolinearitas
## Vehicle_Type : VIF = 1.0152 → Tidak ada multikolinearitas
# Tolerance
tolerance <- 1 / vif_values
cat("\nTolerance Values:\n")
##
## Tolerance Values:
print(round(tolerance, 4))
## Distance_km Preparation_Time_min Courier_Experience_yrs
## 0.9891 0.9915 0.9855
## Weather Traffic_Level Time_of_Day
## 0.9870 0.9866 0.9900
## Vehicle_Type
## 0.9850
# --- 7.5. UJI LINEARITAS ---
cat("\n--- 6.5. UJI LINEARITAS ---\n")
##
## --- 6.5. UJI LINEARITAS ---
# Ramsey RESET Test
reset_test <- resettest(model, power = 2:3, type = "fitted")
cat("\nRamsey RESET Test:\n")
##
## Ramsey RESET Test:
cat("H0: Model linear (spesifikasi benar)\n")
## H0: Model linear (spesifikasi benar)
cat("F =", round(reset_test$statistic, 4), "\n")
## F = 1.0352
cat("p-value =", round(reset_test$p.value, 4), "\n")
## p-value = 0.3556
cat("Kesimpulan:", ifelse(reset_test$p.value > 0.05,
"Model linear (spesifikasi benar)",
"Model TIDAK linear (spesifikasi salah)"), "\n")
## Kesimpulan: Model linear (spesifikasi benar)
8. Diagnostik Outlier dan Influential Points
# ------------------------------------------------------------
# 8. DIAGNOSTIK OUTLIER DAN INFLUENTIAL POINTS
# ------------------------------------------------------------
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("7. DIAGNOSTIK OUTLIER DAN INFLUENTIAL POINTS\n")
## 7. DIAGNOSTIK OUTLIER DAN INFLUENTIAL POINTS
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
p_param <- length(coef(model)) # jumlah parameter (k+1)
# Cook's Distance
cooks_d <- cooks.distance(model)
cat("\nCook's Distance:\n")
##
## Cook's Distance:
cat("Nilai maksimum:", round(max(cooks_d), 4), "\n")
## Nilai maksimum: 0.0501
cat("Jumlah observasi dengan Cook's D > 1:", sum(cooks_d > 1), "\n")
## Jumlah observasi dengan Cook's D > 1: 0
# Leverage (Hat Values)
leverage <- hatvalues(model)
cat("\nLeverage (Hat Values):\n")
##
## Leverage (Hat Values):
cat("Nilai maksimum:", round(max(leverage), 4), "\n")
## Nilai maksimum: 0.0338
cat("Threshold 2(k+1)/n:", round(2 * p_param / n, 4), "\n")
## Threshold 2(k+1)/n: 0.034
cat("Jumlah observasi dengan leverage > threshold:",
sum(leverage > 2 * p_param / n), "\n")
## Jumlah observasi dengan leverage > threshold: 0
# Studentized Residuals
std_resid <- rstudent(model)
cat("\nStudentized Residuals:\n")
##
## Studentized Residuals:
cat("Nilai maksimum absolut:", round(max(abs(std_resid)), 4), "\n")
## Nilai maksimum absolut: 6.3344
cat("Jumlah observasi dengan |std_resid| > 3:",
sum(abs(std_resid) > 3), "\n")
## Jumlah observasi dengan |std_resid| > 3: 21
# DFBETAS
dfbetas_val <- dfbetas(model)
cat("\nDFBETAS:\n")
##
## DFBETAS:
cat("Jumlah nilai dengan |DFBETAS| > 1:", sum(abs(dfbetas_val) > 1), "\n")
## Jumlah nilai dengan |DFBETAS| > 1: 0
9. Visualisasi Diagnostik
# ------------------------------------------------------------
# 9. VISUALISASI DIAGNOSTIK
# ------------------------------------------------------------
# Set par untuk 2x2 plot
par(mfrow = c(2, 2))
# Plot 1: Residual vs Fitted
plot(fitted_values, resid_model,
xlab = "Fitted Values", ylab = "Residuals",
main = "Residual vs Fitted",
pch = 19, col = "steelblue")
abline(h = 0, col = "red", lty = 2)
lines(lowess(fitted_values, resid_model), col = "green", lwd = 2)
# Plot 2: QQ-Plot
qqnorm(resid_model, pch = 19, col = "steelblue",
main = "Normal Q-Q Plot")
qqline(resid_model, col = "red", lwd = 2)
# Plot 3: Scale-Location
plot(fitted_values, sqrt(abs(standardized_resid)),
xlab = "Fitted Values", ylab = "√|Standardized Residuals|",
main = "Scale-Location",
pch = 19, col = "steelblue")
lines(lowess(fitted_values, sqrt(abs(standardized_resid))),
col = "green", lwd = 2)
# Plot 4: Residual vs Leverage
plot(leverage, standardized_resid,
xlab = "Leverage", ylab = "Standardized Residuals",
main = "Residual vs Leverage",
pch = 19, col = "steelblue")
abline(h = 0, col = "red", lty = 2)
abline(v = 2 * p_param / n, col = "red", lty = 2)

# Reset par
par(mfrow = c(1, 1))
10. Pemilihan Model (AIC, BIC, AICc dengan Backward dan
Forward)
# ------------------------------------------------------------
# 10. PEMILIHAN MODEL (STEPWISE)
# ------------------------------------------------------------
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("8. PEMILIHAN MODEL (STEPWISE)\n")
## 8. PEMILIHAN MODEL (STEPWISE)
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
# Kriteria: AIC -> step(..., k = 2)
# BIC -> step(..., k = log(n))
# AICc tidak tersedia di step(), sehingga dihitung manual dengan rumus
# AICc = AIC + 2K(K+1) / (n - K - 1)
# (K = jumlah parameter yang diestimasi, termasuk varians galat)
aicc <- function(m) {
K <- attr(logLik(m), "df")
AIC(m) + (2 * K * (K + 1)) / (nobs(m) - K - 1)
}
# Model awal untuk forward selection (hanya intersep) dan batas modelnya
model_null <- lm(Delivery_Time_min ~ 1, data = data)
batas <- list(lower = ~ 1, upper = formula(model))
# --- Berdasarkan AIC ---
aic_backward <- step(model, direction = "backward", k = 2, trace = 0)
aic_forward <- step(model_null, scope = batas, direction = "forward", k = 2, trace = 0)
aic_both <- step(model, scope = batas, direction = "both", k = 2, trace = 0)
# --- Berdasarkan BIC ---
bic_backward <- step(model, direction = "backward", k = log(n), trace = 0)
bic_forward <- step(model_null, scope = batas, direction = "forward", k = log(n), trace = 0)
bic_both <- step(model, scope = batas, direction = "both", k = log(n), trace = 0)
# Langkah-langkah seleksi
cat("\nLangkah Backward (AIC):\n"); print(aic_backward$anova)
##
## Langkah Backward (AIC):
## Step Df Deviance Resid. Df Resid. Dev AIC
## 1 NA NA 868 87026.41 4083.537
## 2 - Time_of_Day 3 365.6656 871 87392.07 4081.239
## 3 - Vehicle_Type 2 275.0251 873 87667.10 4080.013
cat("\nLangkah Forward (AIC):\n"); print(aic_forward$anova)
##
## Langkah Forward (AIC):
## Step Df Deviance Resid. Df Resid. Dev AIC
## 1 NA NA 882 410305.89 5424.797
## 2 + Distance_km -1 251699.413 881 158606.48 4587.526
## 3 + Preparation_Time_min -1 41132.045 880 117474.43 4324.445
## 4 + Traffic_Level -2 16640.469 878 100833.96 4193.570
## 5 + Weather -4 10938.713 874 89895.25 4100.175
## 6 + Courier_Experience_yrs -1 2228.152 873 87667.10 4080.013
cat("\nLangkah Backward (BIC):\n"); print(bic_backward$anova)
##
## Langkah Backward (BIC):
## Step Df Deviance Resid. Df Resid. Dev AIC
## 1 NA NA 868 87026.41 4155.286
## 2 - Time_of_Day 3 365.6656 871 87392.07 4138.639
## 3 - Vehicle_Type 2 275.0251 873 87667.10 4127.847
cat("\nLangkah Forward (BIC):\n"); print(bic_forward$anova)
##
## Langkah Forward (BIC):
## Step Df Deviance Resid. Df Resid. Dev AIC
## 1 NA NA 882 410305.89 5429.580
## 2 + Distance_km -1 251699.413 881 158606.48 4597.093
## 3 + Preparation_Time_min -1 41132.045 880 117474.43 4338.795
## 4 + Traffic_Level -2 16640.469 878 100833.96 4217.487
## 5 + Weather -4 10938.713 874 89895.25 4143.225
## 6 + Courier_Experience_yrs -1 2228.152 873 87667.10 4127.847
# Perbandingan semua model
daftar_model <- list(
"Model penuh" = model,
"AIC - Backward" = aic_backward,
"AIC - Forward" = aic_forward,
"AIC - Both" = aic_both,
"BIC - Backward" = bic_backward,
"BIC - Forward" = bic_forward,
"BIC - Both" = bic_both
)
tabel_model <- data.frame(
Model = names(daftar_model),
Variabel = sapply(daftar_model, function(m) paste(attr(terms(m), "term.labels"), collapse = " + ")),
K = sapply(daftar_model, function(m) attr(logLik(m), "df")),
AIC = sapply(daftar_model, AIC),
BIC = sapply(daftar_model, BIC),
AICc = sapply(daftar_model, aicc),
Adj_R2 = sapply(daftar_model, function(m) summary(m)$adj.r.squared),
row.names = NULL
)
tabel_model[, c("AIC", "BIC", "AICc", "Adj_R2")] <-
round(tabel_model[, c("AIC", "BIC", "AICc", "Adj_R2")], 4)
cat("\nPerbandingan Model:\n")
##
## Perbandingan Model:
print(tabel_model, right = FALSE)
## Model
## 1 Model penuh
## 2 AIC - Backward
## 3 AIC - Forward
## 4 AIC - Both
## 5 BIC - Backward
## 6 BIC - Forward
## 7 BIC - Both
## Variabel
## 1 Distance_km + Preparation_Time_min + Courier_Experience_yrs + Weather + Traffic_Level + Time_of_Day + Vehicle_Type
## 2 Distance_km + Preparation_Time_min + Courier_Experience_yrs + Weather + Traffic_Level
## 3 Distance_km + Preparation_Time_min + Traffic_Level + Weather + Courier_Experience_yrs
## 4 Distance_km + Preparation_Time_min + Courier_Experience_yrs + Weather + Traffic_Level
## 5 Distance_km + Preparation_Time_min + Courier_Experience_yrs + Weather + Traffic_Level
## 6 Distance_km + Preparation_Time_min + Traffic_Level + Weather + Courier_Experience_yrs
## 7 Distance_km + Preparation_Time_min + Courier_Experience_yrs + Weather + Traffic_Level
## K AIC BIC AICc Adj_R2
## 1 16 6591.382 6667.915 6592.010 0.7845
## 2 11 6587.859 6640.475 6588.162 0.7841
## 3 11 6587.859 6640.475 6588.162 0.7841
## 4 11 6587.859 6640.475 6588.162 0.7841
## 5 11 6587.859 6640.475 6588.162 0.7841
## 6 11 6587.859 6640.475 6588.162 0.7841
## 7 11 6587.859 6640.475 6588.162 0.7841
# Pemilihan AIC vs AICc: AICc disarankan bila n/K < 40 (Burnham & Anderson, 2002)
K_penuh <- attr(logLik(model), "df")
cat("\nn / K (model penuh) =", round(n / K_penuh, 1), "\n")
##
## n / K (model penuh) = 55.2
cat(ifelse(n / K_penuh > 40,
"n/K > 40 → koreksi AICc sangat kecil, AIC dan AICc memberi pilihan yang sama\n",
"n/K < 40 → gunakan AICc sebagai kriteria utama\n"))
## n/K > 40 → koreksi AICc sangat kecil, AIC dan AICc memberi pilihan yang sama
# Model terbaik menurut masing-masing kriteria
idx_aic <- which.min(tabel_model$AIC)
idx_bic <- which.min(tabel_model$BIC)
idx_aicc <- which.min(tabel_model$AICc)
cat("\nAIC terkecil :", tabel_model$Model[idx_aic], "\n")
##
## AIC terkecil : AIC - Backward
cat("BIC terkecil :", tabel_model$Model[idx_bic], "\n")
## BIC terkecil : AIC - Backward
cat("AICc terkecil:", tabel_model$Model[idx_aicc], "\n")
## AICc terkecil: AIC - Backward
# Model akhir (kriteria utama: AICc, sesuai n/K di atas; ganti idx_aic
# jika memilih AIC). Jika AIC, BIC, dan AICc memilih himpunan variabel
# yang sama, maka pilihan model sudah konsisten.
final_model <- daftar_model[[idx_aicc]]
cat("\nModel Terbaik (Stepwise):\n")
##
## Model Terbaik (Stepwise):
print(summary(final_model))
##
## Call:
## lm(formula = Delivery_Time_min ~ Distance_km + Preparation_Time_min +
## Courier_Experience_yrs + Weather + Traffic_Level, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -19.855 -4.925 -1.191 3.101 62.311
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.03563 1.32542 3.799 0.000155 ***
## Distance_km 2.95856 0.05957 49.667 < 2e-16 ***
## Preparation_Time_min 0.95311 0.04664 20.435 < 2e-16 ***
## Courier_Experience_yrs -0.54740 0.11621 -4.710 2.87e-06 ***
## WeatherFoggy 7.77951 1.12595 6.909 9.39e-12 ***
## WeatherRainy 4.99198 0.88143 5.663 2.01e-08 ***
## WeatherSnowy 10.07569 1.19060 8.463 < 2e-16 ***
## WeatherWindy 2.17772 1.19110 1.828 0.067841 .
## Traffic_LevelMedium 5.92850 0.76073 7.793 1.85e-14 ***
## Traffic_LevelHigh 12.03682 0.92188 13.057 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.02 on 873 degrees of freedom
## Multiple R-squared: 0.7863, Adjusted R-squared: 0.7841
## F-statistic: 357 on 9 and 873 DF, p-value: < 2.2e-16
11. Ringkasan Hasil
# ------------------------------------------------------------
# 11. RINGKASAN HASIL
# ------------------------------------------------------------
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("9. RINGKASAN HASIL ANALISIS\n")
## 9. RINGKASAN HASIL ANALISIS
cat(strrep("=", 60), "\n", sep = "")
## ============================================================
cat("\n--- MODEL AKHIR ---\n")
##
## --- MODEL AKHIR ---
koef_final <- coef(final_model)
persamaan <- paste0(
"Y = ", round(koef_final[1], 4),
paste0(sprintf(" %s %.4f*%s",
ifelse(koef_final[-1] >= 0, "+", "-"),
abs(koef_final[-1]), names(koef_final)[-1]),
collapse = "")
)
cat("Persamaan Regresi:\n")
## Persamaan Regresi:
cat(persamaan, "\n")
## Y = 5.0356 + 2.9586*Distance_km + 0.9531*Preparation_Time_min - 0.5474*Courier_Experience_yrs + 7.7795*WeatherFoggy + 4.9920*WeatherRainy + 10.0757*WeatherSnowy + 2.1777*WeatherWindy + 5.9285*Traffic_LevelMedium + 12.0368*Traffic_LevelHigh
cat("\n--- UJI SIGNIFIKANSI (MODEL PENUH) ---\n")
##
## --- UJI SIGNIFIKANSI (MODEL PENUH) ---
cat("Uji F (Simultan): p-value =", format(f_pvalue, scientific = TRUE), "\n")
## Uji F (Simultan): p-value = 1.226723e-280
cat("\n--- UJI ASUMSI RESIDUAL (MODEL PENUH) ---\n")
##
## --- UJI ASUMSI RESIDUAL (MODEL PENUH) ---
cat("Normalitas (Anderson-Darling): p-value =",
format(ad_test$p.value, scientific = TRUE, digits = 4), "\n")
## Normalitas (Anderson-Darling): p-value = 3.7e-24
cat("Heteroskedastisitas (Breusch-Pagan): p-value =",
round(bp_test$p.value, 4), "\n")
## Heteroskedastisitas (Breusch-Pagan): p-value = 0.1158
cat("Multikolinearitas (VIF max):", round(max(vif_values), 4), "\n")
## Multikolinearitas (VIF max): 1.0152
cat("Linearitas (Ramsey RESET): p-value =",
round(reset_test$p.value, 4), "\n")
## Linearitas (Ramsey RESET): p-value = 0.3556
cat("\n--- KESIMPULAN ---\n")
##
## --- KESIMPULAN ---
cat("Model penuh : R-squared =", round(r_squared, 4),
", Adjusted R-squared =", round(adj_r_squared, 4), "\n")
## Model penuh : R-squared = 0.7879 , Adjusted R-squared = 0.7845
cat("Model akhir : R-squared =", round(summary(final_model)$r.squared, 4),
", Adjusted R-squared =", round(summary(final_model)$adj.r.squared, 4), "\n")
## Model akhir : R-squared = 0.7863 , Adjusted R-squared = 0.7841
cat("\n", strrep("=", 60), "\n", sep = "")
##
## ============================================================
cat("ANALISIS SELESAI\n")
## ANALISIS SELESAI
cat(strrep("=", 60), "\n", sep = "")
## ============================================================