# ============================================================
# ANALISIS MULTIPLE LINEAR REGRESSION (MLR)
# DATASET : STUDENT LIFESTYLE DATASET (KAGGLE)
# JUMLAH OBSERVASI : 2000
# VARIABEL DEPENDEN : GPA
# ============================================================
# ============================================================
# 0. PERSIAPAN: LOAD LIBRARY DAN DATA
# ============================================================
library(car) # 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) # Breusch-Pagan, Durbin-Watson, Breusch-Godfrey, 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) # Anderson-Darling
## Warning: package 'nortest' was built under R version 4.5.2
library(corrplot) # Matriks korelasi
## Warning: package 'corrplot' was built under R version 4.5.3
## corrplot 0.95 loaded
data <- read.csv("C:/Users/user/Downloads/student_lifestyle_dataset.csv")
# Lihat struktur data
str(data)
## 'data.frame': 2000 obs. of 8 variables:
## $ Student_ID : int 1 2 3 4 5 6 7 8 9 10 ...
## $ Study_Hours_Per_Day : num 6.9 5.3 5.1 6.5 8.1 6 8 8.4 5.2 7.7 ...
## $ Extracurricular_Hours_Per_Day : num 3.8 3.5 3.9 2.1 0.6 2.1 0.7 1.8 3.6 0.7 ...
## $ Sleep_Hours_Per_Day : num 8.7 8 9.2 7.2 6.5 8 5.3 5.6 6.3 9.8 ...
## $ Social_Hours_Per_Day : num 2.8 4.2 1.2 1.7 2.2 0.3 5.7 3 4 4.5 ...
## $ Physical_Activity_Hours_Per_Day: num 1.8 3 4.6 6.5 6.6 7.6 4.3 5.2 4.9 1.3 ...
## $ GPA : num 2.99 2.75 2.67 2.88 3.51 2.85 3.08 3.2 2.82 2.76 ...
## $ Stress_Level : chr "Moderate" "Low" "Low" "Moderate" ...
# 6 baris pertama
head(data)
## Student_ID Study_Hours_Per_Day Extracurricular_Hours_Per_Day
## 1 1 6.9 3.8
## 2 2 5.3 3.5
## 3 3 5.1 3.9
## 4 4 6.5 2.1
## 5 5 8.1 0.6
## 6 6 6.0 2.1
## Sleep_Hours_Per_Day Social_Hours_Per_Day Physical_Activity_Hours_Per_Day GPA
## 1 8.7 2.8 1.8 2.99
## 2 8.0 4.2 3.0 2.75
## 3 9.2 1.2 4.6 2.67
## 4 7.2 1.7 6.5 2.88
## 5 6.5 2.2 6.6 3.51
## 6 8.0 0.3 7.6 2.85
## Stress_Level
## 1 Moderate
## 2 Low
## 3 Low
## 4 Moderate
## 5 High
## 6 Moderate
# Jumlah observasi dan variabel
cat("\nJumlah observasi:", nrow(data), "\n")
##
## Jumlah observasi: 2000
cat("Jumlah variabel:", ncol(data), "\n")
## Jumlah variabel: 8
# Keterangan variabel:
# - Student_ID : identitas observasi, tidak digunakan sebagai prediktor
# - Stress_Level: variabel kategorik, tidak digunakan dalam model MLR
# - GPA : variabel dependen (Y)
# ============================================================
# 1. PEMERIKSAAN DATA
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("1. PEMERIKSAAN DATA\n")
## 1. PEMERIKSAAN DATA
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Cek missing value
cat("\nJumlah missing value setiap variabel:\n")
##
## Jumlah missing value setiap variabel:
print(colSums(is.na(data)))
## Student_ID Study_Hours_Per_Day
## 0 0
## Extracurricular_Hours_Per_Day Sleep_Hours_Per_Day
## 0 0
## Social_Hours_Per_Day Physical_Activity_Hours_Per_Day
## 0 0
## GPA Stress_Level
## 0 0
# Statistik deskriptif
cat("\nStatistik deskriptif:\n")
##
## Statistik deskriptif:
print(summary(data))
## Student_ID Study_Hours_Per_Day Extracurricular_Hours_Per_Day
## Min. : 1.0 Min. : 5.000 Min. :0.00
## 1st Qu.: 500.8 1st Qu.: 6.300 1st Qu.:1.00
## Median :1000.5 Median : 7.400 Median :2.00
## Mean :1000.5 Mean : 7.476 Mean :1.99
## 3rd Qu.:1500.2 3rd Qu.: 8.700 3rd Qu.:3.00
## Max. :2000.0 Max. :10.000 Max. :4.00
## Sleep_Hours_Per_Day Social_Hours_Per_Day Physical_Activity_Hours_Per_Day
## Min. : 5.000 Min. :0.000 Min. : 0.000
## 1st Qu.: 6.200 1st Qu.:1.200 1st Qu.: 2.400
## Median : 7.500 Median :2.600 Median : 4.100
## Mean : 7.501 Mean :2.705 Mean : 4.328
## 3rd Qu.: 8.800 3rd Qu.:4.100 3rd Qu.: 6.100
## Max. :10.000 Max. :6.000 Max. :13.000
## GPA Stress_Level
## Min. :2.240 Length:2000
## 1st Qu.:2.900 Class :character
## Median :3.110 Mode :character
## Mean :3.116
## 3rd Qu.:3.330
## Max. :4.000
# ============================================================
# 2. PEMERIKSAAN TOTAL WAKTU HARIAN
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("2. PEMERIKSAAN TOTAL WAKTU HARIAN\n")
## 2. PEMERIKSAAN TOTAL WAKTU HARIAN
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Lima aktivitas harian
data$Total_Hours <- rowSums(data[, c("Study_Hours_Per_Day", "Extracurricular_Hours_Per_Day", "Sleep_Hours_Per_Day", "Social_Hours_Per_Day", "Physical_Activity_Hours_Per_Day")])
cat("\nRingkasan total jam harian:\n")
##
## Ringkasan total jam harian:
print(summary(data$Total_Hours))
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 24 24 24 24 24 24
cat("\nApakah seluruh observasi berjumlah 24 jam?\n")
##
## Apakah seluruh observasi berjumlah 24 jam?
print(all(abs(data$Total_Hours - 24) < 1e-10))
## [1] TRUE
# Karena kelima aktivitas berjumlah tepat 24 jam:
#
# Study + Extracurricular + Sleep + Social + Physical Activity = 24
#
# maka kelima variabel tidak dapat dimasukkan sekaligus ke dalam
# model dengan intercept (perfect multicollinearity).
#
# Physical Activity dijadikan variabel referensi, sehingga model
# menggunakan empat variabel:
# - Study Hours
# - Extracurricular Hours
# - Sleep Hours
# - Social Hours
# Pembuktian perfect multicollinearity
model_check <- lm(GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day + Social_Hours_Per_Day + Physical_Activity_Hours_Per_Day, data = data)
cat("\nKoefisien jika kelima variabel dimasukkan sekaligus:\n")
##
## Koefisien jika kelima variabel dimasukkan sekaligus:
print(coef(model_check))
## (Intercept) Study_Hours_Per_Day
## 2.007303084 0.154384498
## Extracurricular_Hours_Per_Day Sleep_Hours_Per_Day
## -0.007496213 -0.004548773
## Social_Hours_Per_Day Physical_Activity_Hours_Per_Day
## 0.001312227 NA
cat("(Koefisien NA menunjukkan adanya perfect multicollinearity)\n")
## (Koefisien NA menunjukkan adanya perfect multicollinearity)
# ============================================================
# 3. STATISTIK DESKRIPTIF DAN EKSPLORASI
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("3. STATISTIK DESKRIPTIF DAN EKSPLORASI\n")
## 3. STATISTIK DESKRIPTIF DAN EKSPLORASI
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Variabel yang digunakan dalam analisis
analysis_data <- data[, c("Study_Hours_Per_Day", "Extracurricular_Hours_Per_Day", "Sleep_Hours_Per_Day", "Social_Hours_Per_Day", "GPA")]
# Statistik deskriptif
print(summary(analysis_data))
## Study_Hours_Per_Day Extracurricular_Hours_Per_Day Sleep_Hours_Per_Day
## Min. : 5.000 Min. :0.00 Min. : 5.000
## 1st Qu.: 6.300 1st Qu.:1.00 1st Qu.: 6.200
## Median : 7.400 Median :2.00 Median : 7.500
## Mean : 7.476 Mean :1.99 Mean : 7.501
## 3rd Qu.: 8.700 3rd Qu.:3.00 3rd Qu.: 8.800
## Max. :10.000 Max. :4.00 Max. :10.000
## Social_Hours_Per_Day GPA
## Min. :0.000 Min. :2.240
## 1st Qu.:1.200 1st Qu.:2.900
## Median :2.600 Median :3.110
## Mean :2.705 Mean :3.116
## 3rd Qu.:4.100 3rd Qu.:3.330
## Max. :6.000 Max. :4.000
# Matriks korelasi
cat("\nMatriks Korelasi:\n")
##
## Matriks Korelasi:
cor_matrix <- cor(analysis_data)
print(round(cor_matrix, 3))
## Study_Hours_Per_Day Extracurricular_Hours_Per_Day
## Study_Hours_Per_Day 1.000 -0.003
## Extracurricular_Hours_Per_Day -0.003 1.000
## Sleep_Hours_Per_Day 0.027 0.009
## Social_Hours_Per_Day -0.138 -0.139
## GPA 0.734 -0.032
## Sleep_Hours_Per_Day Social_Hours_Per_Day GPA
## Study_Hours_Per_Day 0.027 -0.138 0.734
## Extracurricular_Hours_Per_Day 0.009 -0.139 -0.032
## Sleep_Hours_Per_Day 1.000 -0.194 -0.004
## Social_Hours_Per_Day -0.194 1.000 -0.086
## GPA -0.004 -0.086 1.000
# Visualisasi korelasi
corrplot(cor_matrix, method = "color", type = "upper", addCoef.col = "black", tl.col = "black", tl.srt = 45, title = "Matriks Korelasi Antar Variabel", mar = c(0, 0, 2, 0))

# Scatter plot matrix
pairs(analysis_data, main = "Scatter Plot Matrix", pch = 19)

# ============================================================
# 4. ESTIMASI MODEL AWAL (FULL MODEL)
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("4. ESTIMASI MODEL AWAL (FULL MODEL)\n")
## 4. ESTIMASI MODEL AWAL (FULL MODEL)
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Physical Activity tidak dimasukkan (variabel referensi).
model <- lm(GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day + Social_Hours_Per_Day, data = data)
# Ringkasan model
cat("\nRingkasan Model Awal:\n")
##
## Ringkasan Model Awal:
print(summary(model))
##
## Call:
## lm(formula = GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day +
## Sleep_Hours_Per_Day + Social_Hours_Per_Day, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.59447 -0.13516 -0.00293 0.13439 0.78728
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.007303 0.037815 53.082 <2e-16 ***
## Study_Hours_Per_Day 0.154384 0.003213 48.046 <2e-16 ***
## Extracurricular_Hours_Per_Day -0.007496 0.003960 -1.893 0.0585 .
## Sleep_Hours_Per_Day -0.004549 0.003161 -1.439 0.1503
## Social_Hours_Per_Day 0.001312 0.002788 0.471 0.6380
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2026 on 1995 degrees of freedom
## Multiple R-squared: 0.541, Adjusted R-squared: 0.54
## F-statistic: 587.8 on 4 and 1995 DF, p-value: < 2.2e-16
# Koefisien regresi
coef_model <- summary(model)$coefficients
coef_df <- data.frame(
Variabel = rownames(coef_model),
Koefisien = round(coef_model[, 1], 4),
Std_Error = round(coef_model[, 2], 4),
t_value = round(coef_model[, 3], 4),
p_value = round(coef_model[, 4], 4)
)
cat("\nKoefisien Regresi Model Awal:\n")
##
## Koefisien Regresi Model Awal:
print(coef_df)
## Variabel Koefisien Std_Error
## (Intercept) (Intercept) 2.0073 0.0378
## Study_Hours_Per_Day Study_Hours_Per_Day 0.1544 0.0032
## Extracurricular_Hours_Per_Day Extracurricular_Hours_Per_Day -0.0075 0.0040
## Sleep_Hours_Per_Day Sleep_Hours_Per_Day -0.0045 0.0032
## Social_Hours_Per_Day Social_Hours_Per_Day 0.0013 0.0028
## t_value p_value
## (Intercept) 53.0818 0.0000
## Study_Hours_Per_Day 48.0464 0.0000
## Extracurricular_Hours_Per_Day -1.8931 0.0585
## Sleep_Hours_Per_Day -1.4389 0.1503
## Social_Hours_Per_Day 0.4706 0.6380
# Interval kepercayaan 95%
cat("\nInterval Kepercayaan 95%:\n")
##
## Interval Kepercayaan 95%:
print(round(confint(model, level = 0.95), 4))
## 2.5 % 97.5 %
## (Intercept) 1.9331 2.0815
## Study_Hours_Per_Day 0.1481 0.1607
## Extracurricular_Hours_Per_Day -0.0153 0.0003
## Sleep_Hours_Per_Day -0.0107 0.0017
## Social_Hours_Per_Day -0.0042 0.0068
# ============================================================
# 5. PEMILIHAN MODEL (FORWARD, BACKWARD, STEPWISE)
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("5. PEMILIHAN MODEL\n")
## 5. PEMILIHAN MODEL
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# ------------------------------------------------------------
# Forward
# ------------------------------------------------------------
model_null <- lm(GPA ~ 1, data = data)
model_forward <- step(model_null, scope = formula(model), direction = "forward", trace = 1)
## Start: AIC=-4832.62
## GPA ~ 1
##
## Df Sum of Sq RSS AIC
## + Study_Hours_Per_Day 1 96.195 82.128 -6381.3
## + Social_Hours_Per_Day 1 1.309 177.014 -4845.4
## + Extracurricular_Hours_Per_Day 1 0.185 178.138 -4832.7
## <none> 178.323 -4832.6
## + Sleep_Hours_Per_Day 1 0.003 178.319 -4830.7
##
## Step: AIC=-6381.26
## GPA ~ Study_Hours_Per_Day
##
## Df Sum of Sq RSS AIC
## + Extracurricular_Hours_Per_Day 1 0.16310 81.965 -6383.2
## + Sleep_Hours_Per_Day 1 0.10194 82.026 -6381.7
## <none> 82.128 -6381.3
## + Social_Hours_Per_Day 1 0.04394 82.084 -6380.3
##
## Step: AIC=-6383.23
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day
##
## Df Sum of Sq RSS AIC
## + Sleep_Hours_Per_Day 1 0.099666 81.865 -6383.7
## <none> 81.965 -6383.2
## + Social_Hours_Per_Day 1 0.023808 81.941 -6381.8
##
## Step: AIC=-6383.66
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day
##
## Df Sum of Sq RSS AIC
## <none> 81.865 -6383.7
## + Social_Hours_Per_Day 1 0.0090872 81.856 -6381.9
cat("\n--- MODEL FORWARD ---\n")
##
## --- MODEL FORWARD ---
print(formula(model_forward))
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day
# ------------------------------------------------------------
# Backward
# ------------------------------------------------------------
model_backward <- step(model, direction = "backward", trace = 1)
## Start: AIC=-6381.89
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day +
## Social_Hours_Per_Day
##
## Df Sum of Sq RSS AIC
## - Social_Hours_Per_Day 1 0.009 81.865 -6383.7
## <none> 81.856 -6381.9
## - Sleep_Hours_Per_Day 1 0.085 81.941 -6381.8
## - Extracurricular_Hours_Per_Day 1 0.147 82.003 -6380.3
## - Study_Hours_Per_Day 1 94.717 176.573 -4846.3
##
## Step: AIC=-6383.66
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day
##
## Df Sum of Sq RSS AIC
## <none> 81.865 -6383.7
## - Sleep_Hours_Per_Day 1 0.100 81.965 -6383.2
## - Extracurricular_Hours_Per_Day 1 0.161 82.026 -6381.7
## - Study_Hours_Per_Day 1 96.270 178.135 -4830.7
cat("\n--- MODEL BACKWARD ---\n")
##
## --- MODEL BACKWARD ---
print(formula(model_backward))
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day
# ------------------------------------------------------------
# Stepwise / Both
# ------------------------------------------------------------
model_stepwise <- step(model, direction = "both", trace = 1)
## Start: AIC=-6381.89
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day +
## Social_Hours_Per_Day
##
## Df Sum of Sq RSS AIC
## - Social_Hours_Per_Day 1 0.009 81.865 -6383.7
## <none> 81.856 -6381.9
## - Sleep_Hours_Per_Day 1 0.085 81.941 -6381.8
## - Extracurricular_Hours_Per_Day 1 0.147 82.003 -6380.3
## - Study_Hours_Per_Day 1 94.717 176.573 -4846.3
##
## Step: AIC=-6383.66
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day
##
## Df Sum of Sq RSS AIC
## <none> 81.865 -6383.7
## - Sleep_Hours_Per_Day 1 0.100 81.965 -6383.2
## + Social_Hours_Per_Day 1 0.009 81.856 -6381.9
## - Extracurricular_Hours_Per_Day 1 0.161 82.026 -6381.7
## - Study_Hours_Per_Day 1 96.270 178.135 -4830.7
cat("\n--- MODEL STEPWISE / BOTH ---\n")
##
## --- MODEL STEPWISE / BOTH ---
print(formula(model_stepwise))
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day
# ============================================================
# 6. PERBANDINGAN MODEL DAN MODEL AKHIR
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("6. PERBANDINGAN MODEL DAN MODEL AKHIR\n")
## 6. PERBANDINGAN MODEL DAN MODEL AKHIR
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Fungsi AICc (k = jumlah koefisien + 1 parameter varians)
AICc <- function(model) {
n <- nobs(model)
k <- length(coef(model)) + 1
AIC(model) + (2 * k * (k + 1)) / (n - k - 1)
}
model_compare <- data.frame(
Model = c("Model Awal", "Forward", "Backward", "Stepwise"),
Jumlah_X = c(length(coef(model)) - 1, length(coef(model_forward)) - 1, length(coef(model_backward)) - 1, length(coef(model_stepwise)) - 1),
AIC = c(AIC(model), AIC(model_forward), AIC(model_backward), AIC(model_stepwise)),
BIC = c(BIC(model), BIC(model_forward), BIC(model_backward), BIC(model_stepwise)),
AICc = c(AICc(model), AICc(model_forward), AICc(model_backward), AICc(model_stepwise)),
Adjusted_R2 = c(summary(model)$adj.r.squared, summary(model_forward)$adj.r.squared, summary(model_backward)$adj.r.squared, summary(model_stepwise)$adj.r.squared)
)
model_compare[, 3:6] <- round(model_compare[, 3:6], 4)
print(model_compare)
## Model Jumlah_X AIC BIC AICc Adjusted_R2
## 1 Model Awal 4 -704.1322 -670.5267 -704.09 0.5400
## 2 Forward 3 -705.9101 -677.9056 -705.88 0.5402
## 3 Backward 3 -705.9101 -677.9056 -705.88 0.5402
## 4 Stepwise 3 -705.9101 -677.9056 -705.88 0.5402
# ------------------------------------------------------------
# Pemilihan kriteria berdasarkan ukuran data
# ------------------------------------------------------------
# AICc digunakan jika n/k kecil (aturan umum: n/k < 40), karena pada
# sampel kecil AIC cenderung memilih model yang terlalu kompleks.
# Jika n/k besar, AICc hampir sama dengan AIC sehingga AIC digunakan.
n_data <- nrow(data)
k_full <- length(coef(model)) + 1
rasio_n_k <- n_data / k_full
kriteria <- ifelse(rasio_n_k < 40, "AICc", "AIC")
cat("\nn =", n_data, "| k =", k_full, "| n/k =", round(rasio_n_k, 2), "\n")
##
## n = 2000 | k = 6 | n/k = 333.33
cat("Kriteria yang digunakan untuk memilih model akhir:", kriteria, "\n")
## Kriteria yang digunakan untuk memilih model akhir: AIC
if (kriteria == "AICc") {
idx_final <- which.min(model_compare$AICc)
} else {
idx_final <- which.min(model_compare$AIC)
}
# ------------------------------------------------------------
# Model akhir
# ------------------------------------------------------------
daftar_model <- list(model, model_forward, model_backward, model_stepwise)
model_final <- daftar_model[[idx_final]]
cat("\nModel akhir terpilih:", model_compare$Model[idx_final], "\n")
##
## Model akhir terpilih: Forward
cat("\nFormula Model Akhir:\n")
##
## Formula Model Akhir:
print(formula(model_final))
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day
cat("\nRingkasan Model Akhir:\n")
##
## Ringkasan Model Akhir:
print(summary(model_final))
##
## Call:
## lm(formula = GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day +
## Sleep_Hours_Per_Day, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.59676 -0.13630 -0.00346 0.13466 0.78628
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.015074 0.034013 59.244 <2e-16 ***
## Study_Hours_Per_Day 0.154177 0.003182 48.448 <2e-16 ***
## Extracurricular_Hours_Per_Day -0.007760 0.003919 -1.980 0.0478 *
## Sleep_Hours_Per_Day -0.004835 0.003102 -1.559 0.1192
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2025 on 1996 degrees of freedom
## Multiple R-squared: 0.5409, Adjusted R-squared: 0.5402
## F-statistic: 783.9 on 3 and 1996 DF, p-value: < 2.2e-16
cat("\nInterval Kepercayaan 95% Model Akhir:\n")
##
## Interval Kepercayaan 95% Model Akhir:
print(round(confint(model_final, level = 0.95), 4))
## 2.5 % 97.5 %
## (Intercept) 1.9484 2.0818
## Study_Hours_Per_Day 0.1479 0.1604
## Extracurricular_Hours_Per_Day -0.0154 -0.0001
## Sleep_Hours_Per_Day -0.0109 0.0012
# ============================================================
# 7. UJI HIPOTESIS MODEL AKHIR
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("7. UJI HIPOTESIS MODEL AKHIR\n")
## 7. UJI HIPOTESIS MODEL AKHIR
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
final_summary <- summary(model_final)
# ------------------------------------------------------------
# 7.1 Uji F (simultan)
# ------------------------------------------------------------
f_stat <- final_summary$fstatistic
f_value <- f_stat[1]
df1 <- f_stat[2]
df2 <- f_stat[3]
f_pvalue <- pf(f_value, df1, df2, lower.tail = FALSE)
cat("\n--- UJI F (SIMULTAN) ---\n")
##
## --- UJI F (SIMULTAN) ---
cat("H0: seluruh koefisien slope = 0\n")
## H0: seluruh koefisien slope = 0
cat("H1: minimal terdapat satu koefisien slope ≠ 0\n")
## H1: minimal terdapat satu koefisien slope ≠ 0
cat("\nF-statistic:", round(f_value, 4), "\n")
##
## F-statistic: 783.9318
cat("df1:", df1, "\n")
## df1: 3
cat("df2:", df2, "\n")
## df2: 1996
cat("p-value:", format(f_pvalue, scientific = TRUE), "\n")
## p-value: 0e+00
if (f_pvalue < 0.05) {
cat("Kesimpulan: Tolak H0 pada α = 5%. Model signifikan secara simultan.\n")
} else {
cat("Kesimpulan: Gagal menolak H0 pada α = 5%.\n")
}
## Kesimpulan: Tolak H0 pada α = 5%. Model signifikan secara simultan.
# ------------------------------------------------------------
# 7.2 Uji t (parsial)
# ------------------------------------------------------------
tabel_t <- final_summary$coefficients
cat("\n--- UJI t (PARSIAL) ---\n")
##
## --- UJI t (PARSIAL) ---
cat("H0: βj = 0\n")
## H0: βj = 0
cat("H1: βj ≠ 0\n")
## H1: βj ≠ 0
cat("\nTabel koefisien model akhir:\n")
##
## Tabel koefisien model akhir:
print(round(tabel_t, 4))
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.0151 0.0340 59.2445 0.0000
## Study_Hours_Per_Day 0.1542 0.0032 48.4482 0.0000
## Extracurricular_Hours_Per_Day -0.0078 0.0039 -1.9802 0.0478
## Sleep_Hours_Per_Day -0.0048 0.0031 -1.5588 0.1192
cat("\nKeputusan (α = 5%):\n")
##
## Keputusan (α = 5%):
for (i in 2:nrow(tabel_t)) {
var_name <- rownames(tabel_t)[i]
t_val <- tabel_t[i, 3]
p_val <- tabel_t[i, 4]
keputusan <- ifelse(p_val < 0.05, "signifikan", "tidak signifikan")
cat(sprintf("%-35s : t = %8.4f, p-value = %8.4f -> %s\n", var_name, t_val, p_val, keputusan))
}
## Study_Hours_Per_Day : t = 48.4482, p-value = 0.0000 -> signifikan
## Extracurricular_Hours_Per_Day : t = -1.9802, p-value = 0.0478 -> signifikan
## Sleep_Hours_Per_Day : t = -1.5588, p-value = 0.1192 -> tidak signifikan
# ------------------------------------------------------------
# 7.3 Koefisien determinasi
# ------------------------------------------------------------
r_squared <- final_summary$r.squared
adj_r_squared <- final_summary$adj.r.squared
resid_se <- final_summary$sigma
cat("\n--- KOEFISIEN DETERMINASI ---\n")
##
## --- KOEFISIEN DETERMINASI ---
cat("R-squared:", round(r_squared, 4), "\n")
## R-squared: 0.5409
cat("Adjusted R-squared:", round(adj_r_squared, 4), "\n")
## Adjusted R-squared: 0.5402
cat("Residual Standard Error:", round(resid_se, 4), "\n")
## Residual Standard Error: 0.2025
cat("Model menjelaskan sekitar", round(r_squared * 100, 2), "% variasi GPA.\n")
## Model menjelaskan sekitar 54.09 % variasi GPA.
# ------------------------------------------------------------
# 7.4 Persamaan regresi
# ------------------------------------------------------------
final_coef <- coef(model_final)
cat("\n--- PERSAMAAN REGRESI MODEL AKHIR ---\n")
##
## --- PERSAMAAN REGRESI MODEL AKHIR ---
cat("GPA =", round(final_coef[1], 4))
## GPA = 2.0151
if (length(final_coef) > 1) {
for (i in 2:length(final_coef)) {
if (final_coef[i] >= 0) {
cat(" + ", round(final_coef[i], 4), "*", names(final_coef)[i], sep = "")
} else {
cat(" - ", round(abs(final_coef[i]), 4), "*", names(final_coef)[i], sep = "")
}
}
}
## + 0.1542*Study_Hours_Per_Day - 0.0078*Extracurricular_Hours_Per_Day - 0.0048*Sleep_Hours_Per_Day
cat("\n")
# Catatan interpretasi:
# Karena Physical Activity menjadi variabel referensi dalam alokasi
# waktu 24 jam, koefisien setiap variabel waktu dibaca sebagai
# perubahan GPA jika 1 jam dialihkan dari Physical Activity ke
# variabel tersebut, dengan variabel lain dalam model dianggap tetap.
# ============================================================
# 8. UJI ASUMSI MODEL AKHIR
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("8. UJI ASUMSI MODEL AKHIR\n")
## 8. UJI ASUMSI MODEL AKHIR
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Residual
residuals_model <- residuals(model_final)
# Fitted value
fitted_values <- fitted(model_final)
# Standardized residual
standardized_resid <- rstandard(model_final)
# ------------------------------------------------------------
# 8.1 UJI NORMALITAS RESIDUAL
# ------------------------------------------------------------
cat("\n--- 8.1 UJI NORMALITAS RESIDUAL ---\n")
##
## --- 8.1 UJI NORMALITAS RESIDUAL ---
# Anderson-Darling dipilih karena:
# - jumlah data besar (n = 2000)
# - sensitif terhadap penyimpangan di bagian ekor distribusi
# - tersedia pada package nortest
#
# Shapiro-Wilk lebih sesuai untuk sampel kecil, dan Kolmogorov-Smirnov
# kurang sensitif untuk parameter yang diestimasi dari data.
ad_test <- ad.test(residuals_model)
cat("\nAnderson-Darling Test:\n")
##
## Anderson-Darling Test:
cat("H0: residual berdistribusi normal\n")
## H0: residual berdistribusi normal
cat("H1: residual tidak berdistribusi normal\n")
## H1: residual tidak berdistribusi normal
cat("A =", round(ad_test$statistic, 4), "\n")
## A = 0.4531
cat("p-value =", format(ad_test$p.value, scientific = TRUE), "\n")
## p-value = 2.711342e-01
if (ad_test$p.value > 0.05) {
cat("Kesimpulan: Tidak terdapat bukti yang cukup untuk menyatakan residual tidak normal.\n")
} else {
cat("Kesimpulan: Terdapat bukti bahwa residual tidak berdistribusi normal.\n")
}
## Kesimpulan: Tidak terdapat bukti yang cukup untuk menyatakan residual tidak normal.
# ------------------------------------------------------------
# 8.2 UJI HETEROSKEDASTISITAS
# ------------------------------------------------------------
cat("\n--- 8.2 UJI HETEROSKEDASTISITAS ---\n")
##
## --- 8.2 UJI HETEROSKEDASTISITAS ---
bp_test <- bptest(model_final)
cat("\nBreusch-Pagan Test:\n")
##
## Breusch-Pagan Test:
cat("H0: varians residual homogen\n")
## H0: varians residual homogen
cat("H1: varians residual tidak homogen\n")
## H1: varians residual tidak homogen
cat("BP =", round(bp_test$statistic, 4), "\n")
## BP = 1.9558
cat("p-value =", round(bp_test$p.value, 4), "\n")
## p-value = 0.5816
if (bp_test$p.value > 0.05) {
cat("Kesimpulan: Tidak terdapat bukti heteroskedastisitas.\n")
} else {
cat("Kesimpulan: Terdapat bukti heteroskedastisitas.\n")
}
## Kesimpulan: Tidak terdapat bukti heteroskedastisitas.
# ------------------------------------------------------------
# 8.3 UJI AUTOKORELASI (PELENGKAP)
# ------------------------------------------------------------
cat("\n--- 8.3 UJI AUTOKORELASI (PELENGKAP) ---\n")
##
## --- 8.3 UJI AUTOKORELASI (PELENGKAP) ---
# Data merupakan data cross-sectional mahasiswa, sehingga tidak ada
# urutan waktu yang bermakna dan Student_ID hanya identitas observasi.
# Independensi observasi terutama dinilai dari desain pengumpulan data.
# Durbin-Watson dan Breusch-Godfrey ditampilkan sebagai pelengkap saja.
dw_test <- dwtest(model_final)
cat("\nDurbin-Watson Test:\n")
##
## Durbin-Watson Test:
cat("DW =", round(dw_test$statistic, 4), "\n")
## DW = 2.0058
cat("p-value =", round(dw_test$p.value, 4), "\n")
## p-value = 0.5514
bg_test <- bgtest(model_final, order = 1)
cat("\nBreusch-Godfrey Test (Lag 1):\n")
##
## Breusch-Godfrey Test (Lag 1):
cat("LM =", round(bg_test$statistic, 4), "\n")
## LM = 0.0205
cat("p-value =", round(bg_test$p.value, 4), "\n")
## p-value = 0.8862
if (bg_test$p.value > 0.05) {
cat("Kesimpulan: Tidak terdapat bukti autokorelasi berdasarkan urutan data.\n")
} else {
cat("Kesimpulan: Terdapat indikasi autokorelasi berdasarkan urutan data.\n")
}
## Kesimpulan: Tidak terdapat bukti autokorelasi berdasarkan urutan data.
# ------------------------------------------------------------
# 8.4 UJI MULTIKOLINEARITAS
# ------------------------------------------------------------
cat("\n--- 8.4 UJI MULTIKOLINEARITAS ---\n")
##
## --- 8.4 UJI MULTIKOLINEARITAS ---
if (length(coef(model_final)) > 2) {
vif_values <- vif(model_final)
cat("\nVIF Values:\n")
print(round(vif_values, 4))
cat("\nInterpretasi VIF:\n")
for (i in seq_along(vif_values)) {
vif_val <- vif_values[i]
var_name <- names(vif_values)[i]
status <- ifelse(vif_val < 5, "Tidak menunjukkan multikolinearitas", ifelse(vif_val < 10, "Multikolinearitas moderat", "Multikolinearitas serius"))
cat(sprintf("%-35s : VIF = %7.4f -> %s\n", var_name, vif_val, status))
}
# Tolerance
tolerance <- 1 / vif_values
cat("\nTolerance Values:\n")
print(round(tolerance, 4))
vif_maks <- round(max(vif_values), 4)
} else {
cat("\nModel akhir hanya memiliki satu prediktor, sehingga VIF tidak dihitung.\n")
vif_maks <- NA
}
##
## VIF Values:
## Study_Hours_Per_Day Extracurricular_Hours_Per_Day
## 1.0007 1.0001
## Sleep_Hours_Per_Day
## 1.0008
##
## Interpretasi VIF:
## Study_Hours_Per_Day : VIF = 1.0007 -> Tidak menunjukkan multikolinearitas
## Extracurricular_Hours_Per_Day : VIF = 1.0001 -> Tidak menunjukkan multikolinearitas
## Sleep_Hours_Per_Day : VIF = 1.0008 -> Tidak menunjukkan multikolinearitas
##
## Tolerance Values:
## Study_Hours_Per_Day Extracurricular_Hours_Per_Day
## 0.9993 0.9999
## Sleep_Hours_Per_Day
## 0.9992
# ------------------------------------------------------------
# 8.5 UJI LINEARITAS / SPESIFIKASI MODEL
# ------------------------------------------------------------
cat("\n--- 8.5 UJI LINEARITAS / SPESIFIKASI MODEL ---\n")
##
## --- 8.5 UJI LINEARITAS / SPESIFIKASI MODEL ---
reset_test <- resettest(model_final, power = 2:3, type = "fitted")
cat("\nRamsey RESET Test:\n")
##
## Ramsey RESET Test:
cat("H0: spesifikasi model linear sudah tepat\n")
## H0: spesifikasi model linear sudah tepat
cat("H1: terdapat kesalahan spesifikasi model\n")
## H1: terdapat kesalahan spesifikasi model
cat("F =", round(reset_test$statistic, 4), "\n")
## F = 0.4191
cat("p-value =", round(reset_test$p.value, 4), "\n")
## p-value = 0.6577
if (reset_test$p.value > 0.05) {
cat("Kesimpulan: Tidak terdapat bukti adanya kesalahan spesifikasi non-linear.\n")
} else {
cat("Kesimpulan: Terdapat indikasi masalah spesifikasi model.\n")
}
## Kesimpulan: Tidak terdapat bukti adanya kesalahan spesifikasi non-linear.
# Catatan:
# Jika ada uji asumsi yang tidak terpenuhi, data tidak diperbaiki
# (tanpa transformasi).
# ============================================================
# 9. DIAGNOSTIK OUTLIER DAN INFLUENTIAL POINTS
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("9. DIAGNOSTIK OUTLIER DAN INFLUENTIAL POINTS\n")
## 9. DIAGNOSTIK OUTLIER DAN INFLUENTIAL POINTS
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
n_obs <- nrow(data)
k_coef <- length(coef(model_final))
# ------------------------------------------------------------
# 9.1 Cook's Distance
# ------------------------------------------------------------
cooks_d <- cooks.distance(model_final)
cat("\nCook's Distance:\n")
##
## Cook's Distance:
cat("Nilai maksimum:", round(max(cooks_d), 4), "\n")
## Nilai maksimum: 0.0115
cat("Jumlah observasi dengan Cook's D > 1:", sum(cooks_d > 1), "\n")
## Jumlah observasi dengan Cook's D > 1: 0
# ------------------------------------------------------------
# 9.2 Leverage
# ------------------------------------------------------------
leverage <- hatvalues(model_final)
leverage_threshold <- 2 * k_coef / n_obs
cat("\nLeverage (Hat Values):\n")
##
## Leverage (Hat Values):
cat("Nilai maksimum:", round(max(leverage), 4), "\n")
## Nilai maksimum: 0.0047
cat("Threshold 2(k+1)/n:", round(leverage_threshold, 4), "\n")
## Threshold 2(k+1)/n: 0.004
cat("Jumlah observasi dengan leverage > threshold:", sum(leverage > leverage_threshold), "\n")
## Jumlah observasi dengan leverage > threshold: 19
# ------------------------------------------------------------
# 9.3 Studentized Residual
# ------------------------------------------------------------
std_resid <- rstudent(model_final)
cat("\nStudentized Residuals:\n")
##
## Studentized Residuals:
cat("Nilai maksimum absolut:", round(max(abs(std_resid)), 4), "\n")
## Nilai maksimum absolut: 3.9022
cat("Jumlah observasi dengan |studentized residual| > 3:", sum(abs(std_resid) > 3), "\n")
## Jumlah observasi dengan |studentized residual| > 3: 3
# ------------------------------------------------------------
# 9.4 DFBETAS
# ------------------------------------------------------------
dfbetas_val <- dfbetas(model_final)
# - |DFBETAS| > 2/sqrt(n): batas yang disesuaikan dengan ukuran sampel
dfbetas_threshold <- 2 / sqrt(n_obs)
cat("\nDFBETAS:\n")
##
## DFBETAS:
cat("Jumlah observasi dengan minimal satu |DFBETAS| > 1:", sum(apply(abs(dfbetas_val) > 1, 1, any)), "\n")
## Jumlah observasi dengan minimal satu |DFBETAS| > 1: 0
cat("Threshold 2/sqrt(n):", round(dfbetas_threshold, 4), "\n")
## Threshold 2/sqrt(n): 0.0447
cat("Jumlah observasi dengan minimal satu |DFBETAS| > 2/sqrt(n):", sum(apply(abs(dfbetas_val) > dfbetas_threshold, 1, any)), "\n")
## Jumlah observasi dengan minimal satu |DFBETAS| > 2/sqrt(n): 304
cat("\nCatatan: observasi yang terdeteksi tidak otomatis dihapus.\n")
##
## Catatan: observasi yang terdeteksi tidak otomatis dihapus.
# ============================================================
# 10. VISUALISASI DIAGNOSTIK
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("10. VISUALISASI DIAGNOSTIK\n")
## 10. VISUALISASI DIAGNOSTIK
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
par(mfrow = c(2, 2))
# Plot 1: Residual vs Fitted
plot(fitted_values, residuals_model, xlab = "Fitted Values", ylab = "Residuals", main = "Residual vs Fitted", pch = 19)
abline(h = 0, lty = 2)
lines(lowess(fitted_values, residuals_model), lwd = 2)
# Plot 2: Normal Q-Q
qqnorm(residuals_model, pch = 19, main = "Normal Q-Q Plot")
qqline(residuals_model, lwd = 2)
# Plot 3: Scale-Location
plot(fitted_values, sqrt(abs(standardized_resid)), xlab = "Fitted Values", ylab = "√|Standardized Residuals|", main = "Scale-Location", pch = 19)
lines(lowess(fitted_values, sqrt(abs(standardized_resid))), lwd = 2)
# Plot 4: Residual vs Leverage
plot(leverage, standardized_resid, xlab = "Leverage", ylab = "Standardized Residuals", main = "Residual vs Leverage", pch = 19)
abline(h = 0, lty = 2)
abline(v = leverage_threshold, lty = 2)

par(mfrow = c(1, 1))
# ============================================================
# 11. RINGKASAN HASIL ANALISIS
# ============================================================
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("11. RINGKASAN HASIL ANALISIS\n")
## 11. RINGKASAN HASIL ANALISIS
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("\nJumlah observasi:", nrow(data), "\n")
##
## Jumlah observasi: 2000
cat("\nModel akhir:", model_compare$Model[idx_final], "\n")
##
## Model akhir: Forward
print(formula(model_final))
## GPA ~ Study_Hours_Per_Day + Extracurricular_Hours_Per_Day + Sleep_Hours_Per_Day
cat("\nKriteria pemilihan model:", kriteria, "\n")
##
## Kriteria pemilihan model: AIC
cat("AIC =", round(AIC(model_final), 4), "\n")
## AIC = -705.9101
cat("BIC =", round(BIC(model_final), 4), "\n")
## BIC = -677.9056
cat("AICc =", round(AICc(model_final), 4), "\n")
## AICc = -705.88
cat("\nUji F: F =", round(f_value, 4), "| p-value =", format(f_pvalue, scientific = TRUE), "\n")
##
## Uji F: F = 783.9318 | p-value = 0e+00
cat("\nR-squared:", round(r_squared, 4), "\n")
##
## R-squared: 0.5409
cat("Adjusted R-squared:", round(adj_r_squared, 4), "\n")
## Adjusted R-squared: 0.5402
cat("\nUji asumsi:\n")
##
## Uji asumsi:
cat("Normalitas (Anderson-Darling) p-value:", format(ad_test$p.value, scientific = TRUE), "\n")
## Normalitas (Anderson-Darling) p-value: 2.711342e-01
cat("Heteroskedastisitas (Breusch-Pagan) p-value:", round(bp_test$p.value, 4), "\n")
## Heteroskedastisitas (Breusch-Pagan) p-value: 0.5816
cat("Autokorelasi (Durbin-Watson) DW:", round(dw_test$statistic, 4), "\n")
## Autokorelasi (Durbin-Watson) DW: 2.0058
cat("VIF maksimum:", vif_maks, "\n")
## VIF maksimum: 1.0008
cat("Linearitas (Ramsey RESET) p-value:", round(reset_test$p.value, 4), "\n")
## Linearitas (Ramsey RESET) p-value: 0.6577
cat("\nDiagnostik:\n")
##
## Diagnostik:
cat("Cook's D maksimum:", round(max(cooks_d), 4), "\n")
## Cook's D maksimum: 0.0115
cat("Leverage maksimum:", round(max(leverage), 4), "\n")
## Leverage maksimum: 0.0047
cat("Jumlah |Studentized Residual| > 3:", sum(abs(std_resid) > 3), "\n")
## Jumlah |Studentized Residual| > 3: 3
cat("\n")
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
cat("ANALISIS SELESAI\n")
## ANALISIS SELESAI
cat(rep("=", 60), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =