Dataset yang digunakan adalah Airline Passenger Satisfaction Survey, berisi hasil survei kepuasan penumpang maskapai penerbangan dengan total 103.904 observasi pada data training.

Keterangan Variabel:

Load Library

library(MASS)
## Warning: package 'MASS' was built under R version 4.5.3
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.5.3
## 
## Attaching package: 'dplyr'
## The following object is masked from 'package:MASS':
## 
##     select
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(ggplot2)
library(car)
## 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.3
## 
## Attaching package: 'car'
## The following object is masked from 'package:dplyr':
## 
##     recode
library(caret)
## Loading required package: lattice
library(brant)
## Warning: package 'brant' was built under R version 4.5.3
library(tidyr)
library(kableExtra)
## Warning: package 'kableExtra' was built under R version 4.5.3
## 
## Attaching package: 'kableExtra'
## The following object is masked from 'package:dplyr':
## 
##     group_rows

Load Data

train <- read.csv("train.csv") %>% na.omit()
test  <- read.csv("test.csv") %>% na.omit()

prepare_data <- function(df) {
  df$Class <- factor(df$Class, levels = c("Eco", "Eco Plus", "Business"), ordered = TRUE)
  df$loyal <- ifelse(df$Customer.Type == "Loyal Customer", 1, 0)
  df$business_travel <- ifelse(df$Type.of.Travel == "Business travel", 1, 0)
  df$satisfaction_bin <- ifelse(df$satisfaction == "satisfied", 1, 0)
  return(df)
}

train <- prepare_data(train)
test  <- prepare_data(test)

Kode tersebut membaca data training dan testing, lalu menghapus nilai kosong agar data bersih. Selanjutnya, melalui fungsi prepare_data, variabel Class diubah menjadi ordinal (Eco < Eco Plus < Business) dan dibuat variabel biner (loyal, business_travel, satisfaction_bin). Fungsi ini diterapkan pada kedua dataset agar siap digunakan dalam pemodelan.

Exploratory Data Analysis (EDA)

Struktur Data

glimpse(train)
## Rows: 103,594
## Columns: 28
## $ X                                 <int> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11…
## $ id                                <int> 70172, 5047, 110028, 24026, 119299, …
## $ Gender                            <chr> "Male", "Male", "Female", "Female", …
## $ Customer.Type                     <chr> "Loyal Customer", "disloyal Customer…
## $ Age                               <int> 13, 25, 26, 25, 61, 26, 47, 52, 41, …
## $ Type.of.Travel                    <chr> "Personal Travel", "Business travel"…
## $ Class                             <ord> Eco Plus, Business, Business, Busine…
## $ Flight.Distance                   <int> 460, 235, 1142, 562, 214, 1180, 1276…
## $ Inflight.wifi.service             <int> 3, 3, 2, 2, 3, 3, 2, 4, 1, 3, 4, 2, …
## $ Departure.Arrival.time.convenient <int> 4, 2, 2, 5, 3, 4, 4, 3, 2, 3, 5, 4, …
## $ Ease.of.Online.booking            <int> 3, 3, 2, 5, 3, 2, 2, 4, 2, 3, 5, 2, …
## $ Gate.location                     <int> 1, 3, 2, 5, 3, 1, 3, 4, 2, 4, 4, 2, …
## $ Food.and.drink                    <int> 5, 1, 5, 2, 4, 1, 2, 5, 4, 2, 2, 1, …
## $ Online.boarding                   <int> 3, 3, 5, 2, 5, 2, 2, 5, 3, 3, 5, 2, …
## $ Seat.comfort                      <int> 5, 1, 5, 2, 5, 1, 2, 5, 3, 3, 2, 1, …
## $ Inflight.entertainment            <int> 5, 1, 5, 2, 3, 1, 2, 5, 1, 2, 2, 1, …
## $ On.board.service                  <int> 4, 1, 4, 2, 3, 3, 3, 5, 1, 2, 3, 1, …
## $ Leg.room.service                  <int> 3, 5, 3, 5, 4, 4, 3, 5, 2, 3, 3, 2, …
## $ Baggage.handling                  <int> 4, 3, 4, 3, 4, 4, 4, 5, 1, 4, 5, 5, …
## $ Checkin.service                   <int> 4, 1, 4, 1, 3, 4, 3, 4, 4, 4, 3, 5, …
## $ Inflight.service                  <int> 5, 4, 4, 4, 3, 4, 5, 5, 1, 3, 5, 5, …
## $ Cleanliness                       <int> 5, 1, 5, 2, 3, 1, 2, 4, 2, 2, 2, 1, …
## $ Departure.Delay.in.Minutes        <int> 25, 1, 0, 11, 0, 0, 9, 4, 0, 0, 0, 0…
## $ Arrival.Delay.in.Minutes          <dbl> 18, 6, 0, 9, 0, 0, 23, 0, 0, 0, 0, 0…
## $ satisfaction                      <chr> "neutral or dissatisfied", "neutral …
## $ loyal                             <dbl> 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, …
## $ business_travel                   <dbl> 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, …
## $ satisfaction_bin                  <dbl> 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, …

Missing Values

colSums(is.na(train))
##                                 X                                id 
##                                 0                                 0 
##                            Gender                     Customer.Type 
##                                 0                                 0 
##                               Age                    Type.of.Travel 
##                                 0                                 0 
##                             Class                   Flight.Distance 
##                                 0                                 0 
##             Inflight.wifi.service Departure.Arrival.time.convenient 
##                                 0                                 0 
##            Ease.of.Online.booking                     Gate.location 
##                                 0                                 0 
##                    Food.and.drink                   Online.boarding 
##                                 0                                 0 
##                      Seat.comfort            Inflight.entertainment 
##                                 0                                 0 
##                  On.board.service                  Leg.room.service 
##                                 0                                 0 
##                  Baggage.handling                   Checkin.service 
##                                 0                                 0 
##                  Inflight.service                       Cleanliness 
##                                 0                                 0 
##        Departure.Delay.in.Minutes          Arrival.Delay.in.Minutes 
##                                 0                                 0 
##                      satisfaction                             loyal 
##                                 0                                 0 
##                   business_travel                  satisfaction_bin 
##                                 0                                 0

Dataset berisi 103.904 baris dan 25 kolom, terdiri dari variabel kategorik dan numerik. Hampir seluruh data bersih, kecuali 310 missing values pada Arrival.Delay.in.Minutes.

Distribusi Target

table(train$Class)
## 
##      Eco Eco Plus Business 
##    46593     7468    49533
prop.table(table(train$Class))
## 
##        Eco   Eco Plus   Business 
## 0.44976543 0.07208912 0.47814545
ggplot(train, aes(Class, fill = Class)) +
  geom_bar() +
  theme_minimal()

Distribusi kelas penerbangan menunjukkan bahwa data didominasi oleh kelas Business (47,8%) dan Eco (45,0%), sementara Eco Plus hanya mencakup 7,2% sehingga menjadi kelompok minoritas. Meskipun Business dan Eco relatif seimbang, proporsi Eco Plus yang kecil perlu diperhatikan karena berpotensi menyulitkan model dalam melakukan prediksi pada kategori tersebut.

Kepuasan Penumpang per Kelas

ggplot(train, aes(x = Class, fill = satisfaction)) +
  geom_bar(position = "fill", color = "white") +
  scale_fill_manual(values = c("satisfied"               = "#00b894",
                               "neutral or dissatisfied" = "#d63031"),
                    labels = c("Satisfied", "Neutral/Dissatisfied")) +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Proporsi Kepuasan Penumpang per Kelas",
        x = "Kelas", y = "Proporsi", fill = "Kepuasan") +
  theme_minimal()

Visualisasi menunjukkan perbedaan kepuasan antar kelas penerbangan. Kelas Business didominasi penumpang yang puas (hampir 70%), sedangkan kelas Eco dan Eco Plus mayoritas berada pada kategori netral atau tidak puas. Terlihat pula tren meningkat, di mana semakin tinggi kelas penerbangan, semakin tinggi tingkat kepuasan penumpang.

Distribusi Usia per Kelas

ggplot(train, aes(x = Class, y = Age, fill = Class)) +
  geom_boxplot(color = "white", alpha = 0.85) +
  scale_fill_manual(values = c("Eco" = "#74b9ff",
                               "Eco Plus" = "#0984e3",
                               "Business" = "#2d3436")) +
  labs(title = "Distribusi Usia Penumpang per Kelas",
        x = "Kelas", y = "Usia (Tahun)") +
  theme_minimal() +
  theme(legend.position = "none")

Boxplot menunjukkan bahwa median usia penumpang berada pada kisaran 30–50 tahun di semua kelas, dengan kelas Business cenderung sedikit lebih tinggi. Sebaran usia pada kelas Business juga lebih didominasi usia dewasa matang, sedangkan kelas Eco mencakup lebih banyak penumpang usia muda. Hal ini menunjukkan bahwa penumpang kelas Business umumnya lebih dewasa dibandingkan kelas lainnya.

Jarak Penerbangan per Kelas

ggplot(train, aes(x = Class, y = Flight.Distance, fill = Class)) +
  geom_boxplot(color = "white", alpha = 0.85) +
  scale_fill_manual(values = c("Eco" = "#74b9ff",
                               "Eco Plus" = "#0984e3",
                               "Business" = "#2d3436")) +
  labs(title = "Distribusi Jarak Penerbangan per Kelas",
        x = "Kelas", y = "Jarak (mil)") +
  theme_minimal() +
  theme(legend.position = "none")

Boxplot menunjukkan perbedaan jelas pada jarak penerbangan antar kelas. Kelas Business memiliki median sekitar 1.500–2.000 mil, sedangkan kelas Eco dan Eco Plus berada di bawah 1.000 mil dengan pola yang relatif mirip. Hal ini menunjukkan bahwa Flight Distance berpotensi menjadi prediktor kuat, di mana semakin jauh jarak perjalanan, semakin besar kemungkinan penumpang memilih kelas Business.


Preprocessing

# Hapus NA
train <- train %>% filter(!is.na(Arrival.Delay.in.Minutes))
test  <- test  %>% filter(!is.na(Arrival.Delay.in.Minutes))

# Encoding target
train$Class <- factor(train$Class,
                      levels = c("Eco", "Eco Plus", "Business"),
                      ordered = TRUE)

test$Class <- factor(test$Class,
                     levels = c("Eco", "Eco Plus", "Business"),
                     ordered = TRUE)

# Encoding variabel lain
train$satisfaction_bin <- ifelse(train$satisfaction == "satisfied", 1, 0)
test$satisfaction_bin  <- ifelse(test$satisfaction == "satisfied", 1, 0)

train$loyal <- ifelse(train$Customer.Type == "Loyal Customer", 1, 0)
test$loyal  <- ifelse(test$Customer.Type == "Loyal Customer", 1, 0)

train$business_travel <- ifelse(train$Type.of.Travel == "Business travel", 1, 0)
test$business_travel  <- ifelse(test$Type.of.Travel == "Business travel", 1, 0)

Tahap preprocessing dilakukan untuk menyiapkan data sebelum pemodelan. Baris dengan nilai kosong pada Arrival.Delay.in.Minutes dihapus karena model tidak dapat memproses data yang tidak lengkap. Selanjutnya, variabel Class diubah menjadi ordered factor (Eco < Eco Plus < Business) agar sesuai dengan regresi ordinal. Selain itu, dilakukan rekayasa fitur dengan mengubah variabel kategorik menjadi biner, yaitu satisfaction_bin, loyal, dan business_travel, sehingga lebih mudah diolah dan diinterpretasikan dalam model.


Pemilihan Variabel

predictors <- c(
  "Age",
  "Flight.Distance",
  "business_travel",
  "loyal",
  "Inflight.wifi.service",
  "Seat.comfort",
  "Inflight.entertainment",
  "On.board.service",
  "Food.and.drink",
  "Cleanliness",
  "Departure.Delay.in.Minutes",
  "satisfaction_bin"
)

Pemilihan variabel dilakukan dengan mengelompokkan prediktor berdasarkan beberapa dimensi, yaitu demografi dan perjalanan (Age, Flight.Distance), karakteristik penumpang (business_travel, loyal), kualitas layanan penerbangan (seperti Inflight.wifi.service, Seat.comfort, Inflight.entertainment, Food.and.drink, On.board.service, dan Cleanliness), serta faktor operasional dan persepsi (Departure.Delay.in.Minutes dan satisfaction_bin). Variabel-variabel ini dipilih untuk menangkap pengaruh profil penumpang, pengalaman layanan, dan kondisi perjalanan terhadap pemilihan kelas penerbangan.


Uji Asumsi

Uji Multikolinearitas (VIF)

formula_vif <- as.formula(paste("as.numeric(Class) ~",
                                paste(predictors, collapse = " + ")))

model_vif <- lm(formula_vif, data = train)
vif(model_vif)
##                        Age            Flight.Distance 
##                   1.131370                   1.237154 
##            business_travel                      loyal 
##                   1.767073                   1.554721 
##      Inflight.wifi.service               Seat.comfort 
##                   1.115294                   2.182682 
##     Inflight.entertainment           On.board.service 
##                   3.037469                   1.451024 
##             Food.and.drink                Cleanliness 
##                   2.100972                   2.739152 
## Departure.Delay.in.Minutes           satisfaction_bin 
##                   1.005301                   1.910842

Uji multikolinearitas menggunakan VIF menunjukkan bahwa seluruh variabel memiliki nilai di bawah 5, sehingga tidak terdapat korelasi tinggi antar prediktor. Nilai tertinggi terdapat pada Inflight.entertainment (3,03) dan Cleanliness (2,73), namun masih dalam batas aman, sedangkan Departure.Delay.in.Minutes mendekati 1 yang menunjukkan hampir tidak ada korelasi. Dengan demikian, asumsi non-multikolinearitas terpenuhi dan seluruh variabel layak digunakan dalam model.

Uji Outlier

par(mfrow = c(1, 3))
boxplot(train$Age, main = "Age", col = "#74b9ff")
boxplot(train$Flight.Distance, main = "Flight Distance", col = "#fd79a8")
boxplot(train$Departure.Delay.in.Minutes, main = "Departure Delay", col = "#55efc4")

par(mfrow = c(1, 1))

Berdasarkan boxplot, variabel Age tidak menunjukkan adanya outlier dan tersebar relatif simetris di sekitar median. Pada Flight Distance, terdapat beberapa outlier pada kisaran 4000–5000 mil yang menandakan adanya penerbangan jarak sangat jauh. Sementara itu, Departure Delay memiliki outlier yang sangat ekstrem, dengan sebagian besar data dekat nol namun terdapat nilai hingga lebih dari 1500 menit, sehingga menunjukkan variasi keterlambatan yang sangat tinggi.


Pemodelan OLR

formula_olr <- as.formula(paste("Class ~",
                                paste(predictors, collapse = " + ")))

set.seed(42)
model_olr <- polr(formula_olr,
                  data   = train,
                  Hess   = TRUE,
                  method = "logistic")

summary(model_olr)
## Call:
## polr(formula = formula_olr, data = train, Hess = TRUE, method = "logistic")
## 
## Coefficients:
##                                 Value Std. Error  t value
## Age                         5.761e-03  5.517e-04  10.4421
## Flight.Distance             9.309e-04  9.488e-06  98.1068
## business_travel             2.452e+00  2.131e-02 115.0529
## loyal                       7.771e-01  2.173e-02  35.7672
## Inflight.wifi.service      -2.195e-01  6.551e-03 -33.5070
## Seat.comfort                2.309e-01  8.904e-03  25.9269
## Inflight.entertainment     -1.632e-01  1.062e-02 -15.3679
## On.board.service            3.099e-01  7.092e-03  43.7001
## Food.and.drink             -1.958e-02  9.075e-03  -2.1579
## Cleanliness                 9.709e-03  1.044e-02   0.9299
## Departure.Delay.in.Minutes -7.974e-05  2.051e-04  -0.3887
## satisfaction_bin            1.083e+00  2.031e-02  53.3286
## 
## Intercepts:
##                   Value    Std. Error t value 
## Eco|Eco Plus        4.2749   0.0454    94.0837
## Eco Plus|Business   4.8078   0.0461   104.2861
## 
## Residual Deviance: 125311.50 
## AIC: 125339.50

Berdasarkan hasil summary(model_olr), sebagian besar variabel seperti business_travel, Flight.Distance, satisfaction_bin, dan Inflight.wifi.service terbukti signifikan karena memiliki nilai t-value di atas 1,96, sedangkan Cleanliness dan Departure.Delay.in.Minutes tidak signifikan. Koefisien positif pada business_travel dan loyal menunjukkan bahwa penumpang dengan karakteristik tersebut cenderung berada pada kelas yang lebih tinggi, sementara koefisien negatif pada Inflight.wifi.service menunjukkan hubungan terbalik yang perlu dianalisis lebih lanjut. Model juga memiliki dua threshold (Eco|Eco Plus dan Eco Plus|Business) sebagai batas antar kategori. Dari sisi performa, nilai Residual Deviance dan AIC digunakan untuk menilai kecocokan model, di mana nilai AIC yang lebih kecil menunjukkan model yang lebih baik.


Uji Signifikansi Paramter

Uji Parsial (Wald Test)

coef_table <- coef(summary(model_olr))
p_values <- pnorm(abs(coef_table[, "t value"]), lower.tail = FALSE) * 2

cbind(coef_table, "p value" = p_values)
##                                    Value   Std. Error     t value       p value
## Age                         5.761047e-03 5.517139e-04  10.4420924  1.592611e-25
## Flight.Distance             9.308542e-04 9.488177e-06  98.1067540  0.000000e+00
## business_travel             2.452213e+00 2.131378e-02 115.0529349  0.000000e+00
## loyal                       7.770760e-01 2.172591e-02  35.7672458 3.569002e-280
## Inflight.wifi.service      -2.195177e-01 6.551391e-03 -33.5070319 3.807240e-246
## Seat.comfort                2.308573e-01 8.904148e-03  25.9269346 3.310397e-148
## Inflight.entertainment     -1.632444e-01 1.062244e-02 -15.3678821  2.688136e-53
## On.board.service            3.099054e-01 7.091647e-03  43.7000575  0.000000e+00
## Food.and.drink             -1.958241e-02 9.074801e-03  -2.1578891  3.093646e-02
## Cleanliness                 9.709493e-03 1.044153e-02   0.9298921  3.524270e-01
## Departure.Delay.in.Minutes -7.974476e-05 2.051421e-04  -0.3887295  6.974763e-01
## satisfaction_bin            1.082958e+00 2.030728e-02  53.3285551  0.000000e+00
## Eco|Eco Plus                4.274861e+00 4.543678e-02  94.0837214  0.000000e+00
## Eco Plus|Business           4.807775e+00 4.610178e-02 104.2861078  0.000000e+00

Hasil uji parsial (Wald Test) menunjukkan bahwa hampir seluruh variabel seperti Age, Flight.Distance, business_travel, loyal, serta berbagai indikator layanan memiliki p-value < 0,05, sehingga berpengaruh signifikan terhadap kelas penerbangan. Sebaliknya, Cleanliness (p = 0,352) dan Departure.Delay.in.Minutes (p = 0,697) tidak signifikan. Variabel seperti business_travel, Flight.Distance, dan On.board.service menjadi faktor paling dominan karena p-value mendekati nol. Selain itu, kedua nilai intercept juga signifikan, menunjukkan bahwa pemisahan antar kategori kelas dalam model sudah jelas dan model ordinal layak digunakan.

Uji Serentak

model_null <- polr(Class ~ 1, data = train, Hess = TRUE)
lrt        <- anova(model_null, model_olr)
print(lrt)
## Likelihood ratio tests of ordinal regression models
## 
## Response: Class
##                                                                                                                                                                                                               Model
## 1                                                                                                                                                                                                                 1
## 2 Age + Flight.Distance + business_travel + loyal + Inflight.wifi.service + Seat.comfort + Inflight.entertainment + On.board.service + Food.and.drink + Cleanliness + Departure.Delay.in.Minutes + satisfaction_bin
##   Resid. df Resid. Dev   Test    Df LR stat. Pr(Chi)
## 1    103592   186832.7                              
## 2    103580   125311.5 1 vs 2    12 61521.18       0

Hasil Likelihood Ratio Test menunjukkan nilai statistik sebesar 61521,18 dengan p-value ≈ 0 (< 0,05), sehingga H0H_0H0​ ditolak. Ini berarti seluruh variabel prediktor secara simultan berpengaruh signifikan terhadap Class, dan model dengan prediktor jauh lebih baik dibandingkan model tanpa prediktor. Dengan demikian, model layak digunakan dan analisis dapat dilanjutkan ke tahap Odds Ratio atau uji Brant.


Odds Ratio

exp(coef(model_olr))
##                        Age            Flight.Distance 
##                  1.0057777                  1.0009313 
##            business_travel                      loyal 
##                 11.6140149                  2.1751029 
##      Inflight.wifi.service               Seat.comfort 
##                  0.8029060                  1.2596794 
##     Inflight.entertainment           On.board.service 
##                  0.8493836                  1.3632961 
##             Food.and.drink                Cleanliness 
##                  0.9806081                  1.0097568 
## Departure.Delay.in.Minutes           satisfaction_bin 
##                  0.9999203                  2.9534026

Nilai Odds Ratio (OR) menunjukkan besarnya pengaruh setiap variabel terhadap peluang penumpang berada di kelas yang lebih tinggi. Variabel dengan pengaruh positif (OR > 1) seperti business_travel (11,61) menjadi prediktor terkuat, diikuti satisfaction_bin (2,95), loyal (2,17), serta On.board.service (1,36) dan Seat.comfort (1,25) yang meningkatkan peluang naik kelas. Sebaliknya, variabel dengan OR < 1 seperti Inflight.wifi.service (0,80) dan Inflight.entertainment (0,84) menunjukkan hubungan negatif terhadap peluang naik kelas. Sementara itu, variabel seperti Age, Flight.Distance, dan Departure.Delay.in.Minutes memiliki OR mendekati 1, sehingga secara praktis pengaruhnya sangat kecil meskipun sebagian signifikan secara statistik.


Uji Proportional Odds (Brant Test)

brant(model_olr)
## ------------------------------------------------------------ 
## Test for         X2  df  probability 
## ------------------------------------------------------------ 
## Omnibus              5465.91 12  0
## Age              98.26   1   0
## Flight.Distance      1856.28 1   0
## business_travel      1053.65 1   0
## loyal                1192.44 1   0
## Inflight.wifi.service        261.52  1   0
## Seat.comfort         173.63  1   0
## Inflight.entertainment   57.69   1   0
## On.board.service     389.82  1   0
## Food.and.drink           0.61    1   0.43
## Cleanliness          3.05    1   0.08
## Departure.Delay.in.Minutes   0   1   1
## satisfaction_bin     374.21  1   0
## ------------------------------------------------------------ 
## 
## H0: Parallel Regression Assumption holds

Hasil Brant Test menunjukkan nilai omnibus p = 0 (< 0,05), sehingga asumsi proportional odds tidak terpenuhi secara keseluruhan. Sebagian besar variabel seperti Age, Flight.Distance, business_travel, loyal, serta beberapa indikator layanan memiliki p-value < 0,05, yang berarti pengaruhnya tidak konsisten pada setiap level kelas. Sebaliknya, variabel seperti Food.and.drink (p = 0,43), Cleanliness (p = 0,08), dan Departure.Delay.in.Minutes (p = 1) memenuhi asumsi karena pengaruhnya relatif konsisten. Meskipun demikian, model OLR tetap digunakan sebagai pendekatan awal dengan interpretasi yang hati-hati.


Prediksi dan Evaluasi Model

Prediksi Probabilitas

pred_prob  <- predict(model_olr, newdata = test, type = "probs")
pred_class <- predict(model_olr, newdata = test, type = "class")

prob_preview <- data.frame(
  Umur     = test$Age[1:8],
  Jarak    = test$Flight.Distance[1:8],
  Puas     = test$satisfaction[1:8],
  round(pred_prob[1:8, ], 4), 
  Prediksi = pred_class[1:8],
  Aktual   = test$Class[1:8]
)

prob_preview
##   Umur Jarak                    Puas    Eco Eco.Plus Business Prediksi   Aktual
## 1   52   160               satisfied 0.3100   0.1236   0.5664 Business      Eco
## 2   36  2863               satisfied 0.0123   0.0085   0.9791 Business Business
## 3   20   192 neutral or dissatisfied 0.6485   0.1102   0.2413      Eco      Eco
## 4   44  3377               satisfied 0.0112   0.0077   0.9811 Business Business
## 5   49  1182               satisfied 0.1549   0.0831   0.7620 Business      Eco
## 6   16   311               satisfied 0.3043   0.1227   0.5730 Business      Eco
## 7   77  3987               satisfied 0.0070   0.0049   0.9881 Business Business
## 8   43  2556               satisfied 0.0196   0.0134   0.9670 Business Business

Tabel prob_preview hanya menampilkan 8 contoh data untuk menunjukkan cara model memprediksi berdasarkan probabilitas tertinggi. Dari 8 data tersebut, 5 prediksi benar dan 3 salah. Namun, karena sampelnya kecil, evaluasi utama dilakukan menggunakan confusion matrix pada seluruh data uji.


Confusion Matrix

cm <- confusionMatrix(pred_class, test$Class)

cm_df <- as.data.frame(cm$table)
names(cm_df) <- c("Prediksi", "Aktual", "Frekuensi")

ggplot(cm_df, aes(x = Aktual, y = Prediksi, fill = Frekuensi)) +
  geom_tile(color = "white") +
  geom_text(aes(label = Frekuensi), size = 5, fontface = "bold") +
  scale_fill_gradient(low = "#dfe6e9", high = "#0984e3") +
  labs(title    = "Confusion Matrix — Prediksi vs Aktual",
       subtitle = "Data Testing",
       x = "Kelas Aktual", y = "Kelas Prediksi") +
  theme_minimal()

Confusion matrix menunjukkan bahwa model mampu memprediksi kelas Eco dan Business dengan cukup baik, masing-masing sebanyak 9.229 dan 10.422 prediksi benar. Namun, model tidak menghasilkan prediksi untuk kelas Eco Plus, sehingga seluruh observasi pada kategori ini diklasifikasikan ke Eco (1.303) atau Business (609). Hal ini mengindikasikan bahwa model kesulitan membedakan kategori tengah, kemungkinan akibat proporsi data Eco Plus yang kecil serta karakteristiknya yang mirip dengan dua kelas lainnya. Akibatnya, model lebih efektif dalam membedakan Eco dan Business dibandingkan memodelkan ketiga kelas secara seimbang.

Akurasi Model

akurasi <- mean(as.character(pred_class) == as.character(test$Class))

cat("Akurasi Model:", round(akurasi * 100, 2), "%\n")
## Akurasi Model: 75.89 %

Model menghasilkan akurasi sebesar 75,89%, yang berarti sekitar 3 dari 4 data testing berhasil diprediksi dengan benar. Meskipun model mengalami keterbatasan dalam mendeteksi kelas Eco Plus dan tidak sepenuhnya memenuhi asumsi proportional odds, performa ini tetap menunjukkan kemampuan yang cukup baik, terutama dalam membedakan kelas Eco dan Business. Oleh karena itu, model masih dapat dianggap layak digunakan sebagai pendekatan prediksi.


Visualisasi Hasil Model

new_data_fd <- data.frame(
  Age                        = mean(train$Age),
  Flight.Distance            = seq(min(train$Flight.Distance),
                                   max(train$Flight.Distance), length.out = 200),
  business_travel            = 1,
  loyal                      = 1,
  Inflight.wifi.service      = 3,
  Seat.comfort               = 3,
  Inflight.entertainment     = 3,
  On.board.service           = 3,
  Food.and.drink             = 3,
  Cleanliness                = 3,
  Departure.Delay.in.Minutes = 0,
  satisfaction_bin           = 1
)

prob_fd    <- predict(model_olr, newdata = new_data_fd, type = "probs")
prob_fd_df <- as.data.frame(prob_fd)
prob_fd_df$Flight.Distance <- new_data_fd$Flight.Distance

prob_long <- pivot_longer(prob_fd_df,
                          cols      = c("Eco", "Eco Plus", "Business"),
                          names_to  = "Class",
                          values_to = "Probability")
prob_long$Class <- factor(prob_long$Class,
                          levels = c("Eco", "Eco Plus", "Business"))

ggplot(prob_long, aes(x = Flight.Distance, y = Probability, color = Class)) +
  geom_line(size = 1.3) +
  scale_color_manual(values = c("Eco" = "#74b9ff",
                                "Eco Plus" = "#0984e3",
                                "Business" = "#2d3436")) +
  labs(title    = "Prediksi Probabilitas Kelas vs Jarak Penerbangan",
       subtitle = "Kondisi: Business traveler, loyal, semua rating layanan = 3, satisfied",
       x = "Jarak Penerbangan (mil)", y = "Probabilitas", color = "Kelas") +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Grafik menunjukkan bahwa semakin jauh jarak penerbangan, semakin tinggi probabilitas penumpang berada di kelas Business, bahkan mendekati 1 pada jarak di atas 3.000 mil. Sebaliknya, probabilitas kelas Eco dan Eco Plus terus menurun hingga hampir nol pada jarak jauh. Selain itu, garis Eco Plus selalu berada di bawah kelas lain, sehingga jarang menjadi probabilitas tertinggi. Hal ini menjelaskan mengapa model hampir tidak pernah memprediksi kelas Eco Plus pada hasil sebelumnya.


Kesimpulan

1. Kualitas Data Data relatif bersih dengan sedikit missing value, serta terdapat outlier pada Flight Distance dan Departure Delay yang tetap dipertahankan.

2. Signifikansi Variabel Secara simultan, seluruh variabel berpengaruh signifikan terhadap pemilihan kelas (p < 0,05). Secara parsial, variabel paling dominan adalah business_travel, Flight.Distance, dan satisfaction_bin, sedangkan Cleanliness dan Departure.Delay tidak signifikan.

3. Pengaruh Variabel (Odds Ratio) Penumpang dengan tujuan bisnis memiliki peluang 11,61 kali lebih besar untuk berada di kelas lebih tinggi, sementara pelanggan loyal memiliki peluang 2,17 kali lebih besar. Faktor layanan seperti kenyamanan dan pelayanan juga meningkatkan peluang naik kelas.

4. Validitas Model Hasil Brant Test menunjukkan asumsi proportional odds tidak terpenuhi, sehingga pengaruh variabel tidak konsisten di setiap kategori. Namun, model tetap digunakan sebagai pendekatan awal dengan interpretasi yang hati-hati.

5. Performa Model Model mencapai akurasi 75,89%, menunjukkan performa yang cukup baik. Model efektif membedakan kelas Eco dan Business, namun gagal memprediksi Eco Plus akibat ketidakseimbangan data dan kemiripan karakteristik.

6. Pola Prediksi dan Insight Semakin jauh jarak penerbangan, semakin tinggi probabilitas memilih kelas Business hingga mendekati 100% pada jarak sangat jauh. Sebaliknya, peluang Eco dan Eco Plus menurun signifikan.

Kesimpulan Keseluruhan:

Model OLR cukup efektif untuk menjelaskan pola pemilihan kelas penerbangan dengan akurasi yang baik. Faktor utama yang memengaruhi adalah tujuan perjalanan bisnis, jarak penerbangan, dan tingkat kepuasan penumpang. Meskipun terdapat keterbatasan pada asumsi dan prediksi kelas menengah, model tetap layak digunakan untuk analisis dan memberikan insight yang relevan.