Perkembangan metode pembayaran modern telah mengubah perilaku transaksi konsumen muda dan berpotensi meningkatkan risiko terjadinya overspending. Penelitian ini menganalisis faktor-faktor yang memengaruhi perilaku overspending pada konsumen muda menggunakan regresi logistik biner, dengan data sekunder dari Survey Data on Payment Methods and Overspending Among Young Consumers (Mendeley Data). Setelah pembersihan data, diperoleh 674 observasi yang dianalisis. Seleksi variabel dilakukan melalui uji bivariat (Chi-Square dan Wilcoxon), kemudian model dibentuk menggunakan backward stepwise selection berbasis AIC. Model terbaik terdiri atas variabel prepaid card usage, daily cash usage, daily credit card usage, daily prepaid usage, dan employed. Variabel daily credit card usage memiliki pengaruh terbesar terhadap peluang overspending dengan odds ratio = 13,42. Model memiliki kecocokan baik (Hosmer–Lemeshow, p = 0,2752), signifikan secara keseluruhan (Likelihood Ratio Test, p < 0,001), dan kemampuan diskriminasi cukup baik (AUC = 0,7114). Secara umum, overspending lebih banyak dipengaruhi oleh pola penggunaan metode pembayaran dibandingkan karakteristik demografis maupun literasi keuangan.
Kata kunci: regresi logistik biner; overspending; metode pembayaran; literasi keuangan; konsumen muda; odds ratio.
Perkembangan teknologi keuangan mengubah pola transaksi masyarakat, terutama pada konsumen muda. Kemudahan metode pembayaran modern—kartu kredit, kartu prabayar, hingga pembayaran digital—di satu sisi memberi fleksibilitas, namun di sisi lain berpotensi mendorong perilaku konsumsi berlebihan (overspending), yaitu kondisi pengeluaran yang melampaui kemampuan atau perencanaan keuangan individu. Pada konsumen muda, risiko ini penting diperhatikan karena mereka berada pada fase pembentukan kebiasaan keuangan jangka panjang.
Literatur menunjukkan bahwa instrumen pembayaran non-tunai dapat mengurangi persepsi terhadap pengeluaran aktual sehingga mendorong konsumsi, sementara literasi keuangan yang baik cenderung meningkatkan kemampuan individu mengendalikan pengeluaran. Karakteristik demografis (gender, status kerja, pendapatan, dsb.) juga diduga berperan, meski temuan riset sebelumnya masih beragam.
Karena variabel respons bersifat dikotomi (mengalami overspending atau tidak), regresi logistik biner dipilih sebagai metode analisis. Tujuan penelitian ini adalah mengidentifikasi faktor-faktor yang berpengaruh terhadap peluang overspending sekaligus mengukur besarnya pengaruh masing-masing faktor melalui odds ratio.
Data merupakan data sekunder dari repositori publik Mendeley
Data, berjudul Survey Data on Payment Methods and
Overspending Among Young Consumers. Dataset awal memuat 718
responden dengan 17 variabel. Missing value
ditangani dengan listwise deletion
(na.omit/drop_na), menyisakan 674
observasi yang digunakan dalam analisis dengan 13
variabel independen dan 1 varibael dependen yang digunakan.
Bentuk umum model regresi logistik biner yang digunakan:
\[\ln\left(\frac{P(Y=1)}{1-P(Y=1)}\right) = \beta_0 + \sum_{k=1}^{13}\beta_k X_k\]
dengan \(P(Y=1)\) adalah probabilitas responden mengalami overspending, \(\beta_0\) adalah intersep, dan \(\beta_k\) adalah koefisien regresi untuk variabel prediktor \(X_k\). Parameter diestimasi dengan Maximum Likelihood Estimation (MLE), dan interpretasi pengaruh setiap prediktor dilakukan melalui odds ratio (eksponensial koefisien): OR > 1 menaikkan peluang overspending, OR < 1 menurunkannya.
Tahapan analisis dilakukan secara berurutan sebagai berikut:
Tujuan bagian selanjutnya. Mulai dari sini, dokumen berpindah dari kerangka teori ke praktik: data disiapkan, dieksplorasi, dimodelkan, diuji asumsinya, dievaluasi performanya, lalu didiagnosis residualnya — seluruhnya dijalankan langsung melalui kode R di bawah.
packages <- c("readxl", "dplyr", "ggplot2", "tidyr", "caret", "pROC",
"lmtest", "ResourceSelection", "car", "DescTools",
"knitr", "kableExtra", "corrplot", "gridExtra", "PRROC", "broom")
install_if_missing <- function(pkg) {
if (!requireNamespace(pkg, quietly = TRUE)) install.packages(pkg)
}
invisible(lapply(packages, install_if_missing))
invisible(lapply(packages, library, character.only = TRUE))style_table <- function(df, caption = NULL, full_width = FALSE) {
knitr::kable(df, caption = caption, align = "c", row.names = FALSE) %>%
kableExtra::kable_styling(
bootstrap_options = c("striped", "hover", "condensed"),
full_width = full_width,
position = "center"
) %>%
kableExtra::row_spec(0, bold = TRUE, color = "#FDF6E3", background = "#117864") %>%
kableExtra::column_spec(1, bold = TRUE, color = "#0B5345")
}
style_table(tabel_variabel, caption = "Tabel 1. Variabel Penelitian")| Variabel | Skala | Keterangan |
|---|---|---|
| Overspending | Nominal (0/1) | Variabel respons biner (pengeluaran berlebihan) |
| Cash Payment Usage | Nominal (0/1) | Penggunaan uang tunai sebagai metode pembayaran |
| Daily Cash Usage | Nominal (0/1) | Penggunaan uang tunai harian |
| Credit Card Usage | Nominal (0/1) | Penggunaan kartu kredit sebagai metode pembayaran |
| Daily Credit Card Usage | Nominal (0/1) | Penggunaan kartu kredit harian |
| Prepaid Card Usage | Nominal (0/1) | Penggunaan kartu prabayar sebagai metode pembayaran |
| Daily Prepaid Card Usage | Nominal (0/1) | Penggunaan kartu prabayar harian |
| Total Financial Literacy Score | Rasio (0-6) | Skor total literasi keuangan |
| Gender | Nominal (0/1) | 0=Female, 1=Male |
| Academic Year | Ordinal (1-4) | 1=Freshmen … 4=Senior |
| Department | Nominal (1-3) | 1=Other, 2=Accounting, 3=Management |
| Employed | Nominal (0/1) | 0=Not working, 1=Employed |
| Income | Ordinal (1-3) | 1=<1jt, 2=1-3jt, 3=3-5jt |
| Received Financial Education | Nominal (0/1) | Pernah/tidak menerima edukasi keuangan |
# Ganti path di bawah ini sesuai lokasi file dataset Anda.
# Jika file tidak ditemukan pada path tersebut, jendela pemilihan file akan terbuka.
data_path <- "Survey Data on Payment Methods and Overspending.xlsx"
if (file.exists(data_path)) {
df_raw <- read_excel(data_path)
} else {
df_raw <- read_excel(file.choose())
}
cat("=== DIMENSI DATA ===\n")## === DIMENSI DATA ===
## Jumlah baris : 718
## Jumlah kolom : 18
## === NAMA KOLOM ASLI ===
## [1] "Respondent's identifier" "Overspending"
## [3] "Cash payment usage" "Cash payment frequency"
## [5] "Daily cash usage" "Credit card usage"
## [7] "Credit card payment frequency" "Daily credit card usage"
## [9] "Prepaid card usage" "Prepaid card payment frequency"
## [11] "Daily prepaid card usage" "Total financial literacy scores"
## [13] "Male" "Acedemic year"
## [15] "Department" "Employed"
## [17] "Income" "Received financial education"
Dataset awal berisi 718 responden dengan 17 variabel. Tahap berikutnya melakukan penyesuaian nama kolom, ekstraksi nilai numerik, dan penghapusan missing value.
Rename kolom agar lebih mudah dipakai dalam analisis:
df <- df_raw %>%
dplyr::rename(
id = `Respondent's identifier`,
overspending = `Overspending`,
cash_usage = `Cash payment usage`,
cash_freq = `Cash payment frequency`,
daily_cash = `Daily cash usage`,
cc_usage = `Credit card usage`,
cc_freq = `Credit card payment frequency`,
daily_cc = `Daily credit card usage`,
prepaid_usage = `Prepaid card usage`,
prepaid_freq = `Prepaid card payment frequency`,
daily_prepaid = `Daily prepaid card usage`,
fin_literacy = `Total financial literacy scores`,
gender = `Male`,
academic_year = `Acedemic year`,
department = `Department`,
employed = `Employed`,
income = `Income`,
fin_education = `Received financial education`
)Ekstraksi nilai numerik dari kolom berformat teks
(misalnya "0=no", "1=yes"):
extract_num <- function(x) {
as.numeric(sub("^([0-9]+).*", "\\1", as.character(x)))
}
df <- df %>%
mutate(
# Variabel dependen: Overspending (0 = no, 1 = yes)
overspending = extract_num(overspending),
# Payment usage (0/1)
cash_usage = extract_num(cash_usage),
cc_usage = extract_num(cc_usage),
prepaid_usage = extract_num(prepaid_usage),
# Payment frequency (1=never, 2=once a month, 3=once a week, 4=daily)
cash_freq = extract_num(cash_freq),
cc_freq = extract_num(cc_freq),
prepaid_freq = extract_num(prepaid_freq),
# Daily usage (0/1)
daily_cash = extract_num(daily_cash),
daily_cc = extract_num(daily_cc),
daily_prepaid = extract_num(daily_prepaid),
# Financial literacy (numerik, sudah langsung)
fin_literacy = as.numeric(fin_literacy),
# Demografis
gender = extract_num(gender), # 0 = female, 1 = male
academic_year = extract_num(academic_year), # 1-4
department = extract_num(department), # 1=other, 2=accounting, 3=management
employed = extract_num(employed),
income = extract_num(income), # 1, 2, 3
fin_education = extract_num(fin_education)
)
# Hapus kolom ID dan variabel frekuensi (tumpang tindih dengan usage & daily)
df <- df %>% dplyr::select(-id, -cash_freq, -cc_freq, -prepaid_freq)Pemeriksaan missing value dan penghapusan baris dengan NA:
## === MISSING VALUE PER KOLOM ===
## overspending cash_usage daily_cash cc_usage daily_cc
## 3 3 3 10 10
## prepaid_usage daily_prepaid fin_literacy gender academic_year
## 13 13 0 0 0
## department employed income fin_education
## 0 2 26 2
##
## Jumlah observasi setelah hapus NA: 674
Konversi ke factor untuk seluruh variabel kategorik:
df_clean <- df_clean %>%
mutate(
overspending = factor(overspending, levels = c(0, 1), labels = c("No", "Yes")),
cash_usage = factor(cash_usage, levels = c(0, 1), labels = c("Non-user", "Cash user")),
cc_usage = factor(cc_usage, levels = c(0, 1), labels = c("Non-user", "CC user")),
prepaid_usage = factor(prepaid_usage, levels = c(0, 1), labels = c("Non-user", "Prepaid user")),
daily_cash = factor(daily_cash, levels = c(0, 1)),
daily_cc = factor(daily_cc, levels = c(0, 1)),
daily_prepaid = factor(daily_prepaid, levels = c(0, 1)),
gender = factor(gender, levels = c(0, 1), labels = c("Female", "Male")),
academic_year = factor(academic_year, levels = 1:4,
labels = c("Freshmen", "Sophomore", "Junior", "Senior")),
department = factor(department, levels = 1:3,
labels = c("Other", "Accounting", "Management")),
employed = factor(employed, levels = c(0, 1), labels = c("Not working", "Employed")),
income = factor(income, levels = 1:3,
labels = c("<1Jt", "1-3Jt", "3-5Jt")),
fin_education = factor(fin_education, levels = c(0, 1), labels = c("No", "Yes")),
)Setelah pembersihan data, jumlah observasi yang digunakan dalam analisis adalah 674 responden, dengan 51 responden (7,57%) mengalami overspending dan 623 responden (92,43%) tidak mengalami overspending — menunjukkan distribusi kelas yang tidak seimbang (imbalanced).
## --- Distribusi Variabel Dependen ---
##
## No Yes
## 623 51
##
## No Yes
## 92.433234 7.566766
p1 <- ggplot(df_clean, aes(x = overspending, fill = overspending)) +
geom_bar(color = "white", width = 0.6) +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5, size = 4) +
scale_fill_manual(values = c("No" = EMERALD, "Yes" = GOLD)) +
labs(title = "Distribusi Overspending", x = "Overspending", y = "Frekuensi") +
theme_minimal() + theme(legend.position = "none")
p1Interpretasi. Sebagian besar responden tidak mengalami overspending (623 orang / 92,43%), sementara hanya 51 orang (7,57%) yang mengalami overspending. Ketidakseimbangan ini menjadi pertimbangan penting pada tahap evaluasi model (perlu metrik selain accuracy, seperti AUC dan AUPRC).
p2 <- ggplot(df_clean, aes(x = gender, fill = overspending)) +
geom_bar(position = "fill", color = "white") +
scale_fill_manual(values = c("No" = EMERALD, "Yes" = GOLD)) +
scale_y_continuous(labels = scales::percent) +
labs(title = "Overspending by Gender", x = "Gender", y = "Proporsi", fill = "Overspending") +
theme_minimal()
p5 <- ggplot(
df_clean %>% filter(!is.na(income)),
aes(x = income, fill = overspending)
) +
geom_bar(position = "fill", color = "white") +
scale_fill_manual(values = c("No" = EMERALD,
"Yes" = GOLD)) +
scale_y_continuous(labels = scales::percent) +
labs(title = "Overspending by Income",
x = "Income Level",
y = "Proporsi",
fill = "Overspending") +
theme_minimal()
p6 <- ggplot(df_clean, aes(x = academic_year, fill = overspending)) +
geom_bar(position = "fill", color = "white") +
scale_fill_manual(values = c("No" = EMERALD, "Yes" = GOLD)) +
scale_y_continuous(labels = scales::percent) +
labs(title = "Overspending by Academic Year", x = "Academic Year", y = "Proporsi", fill = "Overspending") +
theme_minimal()
grid.arrange(p2, p5, p6, ncol = 2)Interpretasi. Proporsi overspending pada responden laki-laki sedikit lebih tinggi dibandingkan perempuan, walau sebagian besar responden pada kedua kelompok tetap berada pada kategori tidak overspending. Berdasarkan tingkat pendapatan dan tahun akademik, perilaku overspending tersebar relatif merata pada seluruh kategori — mengindikasikan karakteristik demografis ini cenderung tidak membedakan kelompok overspending secara tajam.
df_payment <- df_clean %>%
dplyr::select(overspending, cash_usage, cc_usage, prepaid_usage) %>%
pivot_longer(-overspending, names_to = "method", values_to = "usage") %>%
mutate(method = case_match(method,
"cash_usage" ~ "Cash",
"cc_usage" ~ "Credit Card",
"prepaid_usage" ~ "Prepaid"))
p3 <- ggplot(df_payment, aes(x = usage, fill = overspending)) +
geom_bar(position = "fill", color = "white") +
scale_fill_manual(values = c("No" = EMERALD, "Yes" = GOLD)) +
scale_y_continuous(labels = scales::percent) +
facet_wrap(~method) +
labs(title = "Overspending by Payment Method Usage", x = "Usage", y = "Proporsi", fill = "Overspending") +
theme_minimal()
p3Interpretasi. Pengguna kartu kredit dan kartu prabayar menunjukkan proporsi overspending yang lebih tinggi dibandingkan kelompok non-pengguna. Sebaliknya, pada penggunaan uang tunai, perbedaan proporsi overspending antara pengguna dan non-pengguna relatif lebih kecil dibandingkan dua metode pembayaran lainnya.
## --- Summary Financial Literacy (Numerik) ---
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.000 2.000 3.000 3.047 4.000 6.000
p4 <- ggplot(df_clean, aes(x = overspending, y = fin_literacy, fill = overspending)) +
geom_boxplot(alpha = 0.8, outlier.color = GOLD_ACCENT) +
scale_fill_manual(values = c("No" = EMERALD, "Yes" = GOLD)) +
labs(title = "Financial Literacy vs Overspending", x = "Overspending", y = "Skor Financial Literacy") +
theme_minimal() + theme(legend.position = "none")
p4Interpretasi. Distribusi skor literasi keuangan pada kelompok overspending dan non-overspending memiliki rentang yang relatif serupa, namun terdapat perbedaan pola distribusi yang perlu diuji lebih lanjut secara statistik (lihat Uji Wilcoxon pada bagian berikutnya).
cat_vars <- c("cash_usage", "cc_usage", "prepaid_usage",
"daily_cash", "daily_cc", "daily_prepaid",
"gender", "academic_year", "department",
"employed", "income", "fin_education")
cat("--- Uji Chi-Square (p < 0.05 = signifikan) ---\n")## --- Uji Chi-Square (p < 0.05 = signifikan) ---
for (v in cat_vars) {
tbl <- table(df_clean[[v]], df_clean$overspending)
test <- chisq.test(tbl, simulate.p.value = TRUE)
cat(sprintf("%-25s : chi2 = %6.3f, p = %.4f %s\n",
v, test$statistic, test$p.value,
ifelse(test$p.value < 0.05, "***", "")))
}## cash_usage : chi2 = 1.113, p = 0.3428
## cc_usage : chi2 = 8.028, p = 0.0060 ***
## prepaid_usage : chi2 = 6.565, p = 0.0150 ***
## daily_cash : chi2 = 3.306, p = 0.0875
## daily_cc : chi2 = 35.960, p = 0.0005 ***
## daily_prepaid : chi2 = 0.439, p = 0.6422
## gender : chi2 = 3.243, p = 0.0750
## academic_year : chi2 = 4.249, p = 0.2379
## department : chi2 = 2.924, p = 0.2404
## employed : chi2 = 3.945, p = 0.0535
## income : chi2 = 0.458, p = 0.8356
## fin_education : chi2 = 0.314, p = 0.6702
Interpretasi. Variabel credit card usage, prepaid usage, daily credit card usage, dan employed menunjukkan hubungan signifikan dengan overspending pada taraf 5%. Variabel daily cash usage dan gender signifikan pada taraf 10% (p < 0,10), sehingga masih dipertimbangkan sebagai kandidat meskipun signifikansinya lebih lemah.
##
## Wilcoxon rank sum test with continuity correction
##
## data: fin_literacy by overspending
## W = 19031, p-value = 0.01609
## alternative hypothesis: true location shift is not equal to 0
Interpretasi. Nilai p-value sebesar 0,0161 (< 0,05) menunjukkan terdapat perbedaan skor literasi keuangan yang signifikan antara kelompok overspending dan non-overspending pada taraf deskriptif/bivariat.
df_model <- df_clean %>%
mutate(overspending_bin = ifelse(overspending == "Yes", 1, 0)) %>%
drop_na()
model_full <- glm(overspending_bin ~
cash_usage + cc_usage + prepaid_usage +
daily_cash + daily_cc + daily_prepaid +
fin_literacy + gender + academic_year +
department + employed + income + fin_education,
data = df_model,
family = binomial(link = "logit"),
na.action = na.omit)
summary(model_full)##
## Call:
## glm(formula = overspending_bin ~ cash_usage + cc_usage + prepaid_usage +
## daily_cash + daily_cc + daily_prepaid + fin_literacy + gender +
## academic_year + department + employed + income + fin_education,
## family = binomial(link = "logit"), data = df_model, na.action = na.omit)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -15.40875 714.58790 -0.022 0.982796
## cash_usageCash user 12.69712 714.58809 0.018 0.985824
## cc_usageCC user -0.26962 0.56646 -0.476 0.634092
## prepaid_usagePrepaid user 1.13261 0.54713 2.070 0.038444 *
## daily_cash1 0.89254 0.42753 2.088 0.036829 *
## daily_cc1 3.01401 0.82182 3.667 0.000245 ***
## daily_prepaid1 -1.73762 1.13480 -1.531 0.125715
## fin_literacy -0.14458 0.12279 -1.178 0.238984
## genderMale 0.24529 0.33794 0.726 0.467935
## academic_yearSophomore 0.24548 0.46295 0.530 0.595944
## academic_yearJunior -0.57123 0.50394 -1.134 0.256989
## academic_yearSenior 0.34127 0.46215 0.738 0.460251
## departmentAccounting -0.30207 0.65901 -0.458 0.646691
## departmentManagement -0.01819 0.59121 -0.031 0.975450
## employedEmployed -0.96663 0.57424 -1.683 0.092314 .
## income1-3Jt -0.05752 0.36436 -0.158 0.874559
## income3-5Jt -0.62453 0.60491 -1.032 0.301869
## fin_educationYes -0.24387 0.33767 -0.722 0.470175
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 329.93 on 633 degrees of freedom
## Residual deviance: 289.63 on 616 degrees of freedom
## AIC: 325.63
##
## Number of Fisher Scoring iterations: 14
Model penuh memuat seluruh 16 prediktor dan menghasilkan AIC = 325,63. Model ini menjadi titik awal untuk proses seleksi variabel pada tahap stepwise berikutnya.
model_null <- glm(overspending_bin ~ 1, data = df_model, family = binomial)
# Backward
cat("--- Stepwise Backward ---\n")## --- Stepwise Backward ---
##
## Call:
## glm(formula = overspending_bin ~ prepaid_usage + daily_cash +
## daily_cc + daily_prepaid + employed, family = binomial(link = "logit"),
## data = df_model, na.action = na.omit)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -3.3472 0.3856 -8.681 < 2e-16 ***
## prepaid_usagePrepaid user 1.0437 0.4854 2.150 0.0316 *
## daily_cash1 0.9137 0.4135 2.209 0.0271 *
## daily_cc1 2.5971 0.6157 4.218 2.46e-05 ***
## daily_prepaid1 -1.4446 1.0473 -1.379 0.1678
## employedEmployed -0.9690 0.5465 -1.773 0.0762 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 329.93 on 633 degrees of freedom
## Residual deviance: 298.68 on 628 degrees of freedom
## AIC: 310.68
##
## Number of Fisher Scoring iterations: 6
##
## --- Stepwise Forward ---
model_step_fwd <- step(model_null, direction = "forward",
scope = formula(model_full), trace = FALSE)
summary(model_step_fwd)##
## Call:
## glm(formula = overspending_bin ~ daily_cc + daily_cash + employed +
## prepaid_usage + daily_prepaid, family = binomial, data = df_model)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -3.3472 0.3856 -8.681 < 2e-16 ***
## daily_cc1 2.5971 0.6157 4.218 2.46e-05 ***
## daily_cash1 0.9137 0.4135 2.209 0.0271 *
## employedEmployed -0.9690 0.5465 -1.773 0.0762 .
## prepaid_usagePrepaid user 1.0437 0.4854 2.150 0.0316 *
## daily_prepaid1 -1.4446 1.0473 -1.379 0.1678
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 329.93 on 633 degrees of freedom
## Residual deviance: 298.68 on 628 degrees of freedom
## AIC: 310.68
##
## Number of Fisher Scoring iterations: 6
##
## --- Stepwise Both (Forward + Backward) ---
model_step_both <- step(model_null, direction = "both",
scope = formula(model_full), trace = FALSE)
summary(model_step_both)##
## Call:
## glm(formula = overspending_bin ~ daily_cc + daily_cash + employed +
## prepaid_usage + daily_prepaid, family = binomial, data = df_model)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -3.3472 0.3856 -8.681 < 2e-16 ***
## daily_cc1 2.5971 0.6157 4.218 2.46e-05 ***
## daily_cash1 0.9137 0.4135 2.209 0.0271 *
## employedEmployed -0.9690 0.5465 -1.773 0.0762 .
## prepaid_usagePrepaid user 1.0437 0.4854 2.150 0.0316 *
## daily_prepaid1 -1.4446 1.0473 -1.379 0.1678
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 329.93 on 633 degrees of freedom
## Residual deviance: 298.68 on 628 degrees of freedom
## AIC: 310.68
##
## Number of Fisher Scoring iterations: 6
Perbandingan AIC antar metode stepwise:
aic_step <- AIC(model_full, model_step_back, model_step_fwd, model_step_both)
aic_display <- data.frame(
Model = c("Full", "Backward", "Forward", "Both"),
df = aic_step$df,
AIC = round(aic_step$AIC, 4)
)
style_table(aic_display, caption = "Tabel: Perbandingan AIC Antar Model")| Model | df | AIC |
|---|---|---|
| Full | 18 | 325.6266 |
| Backward | 6 | 310.6772 |
| Forward | 6 | 310.6772 |
| Both | 6 | 310.6772 |
aic_vals <- aic_step$AIC
model_list <- list(model_full, model_step_back, model_step_fwd, model_step_both)
model_names <- c("Full", "Backward", "Forward", "Both")
best_idx <- which.min(aic_vals)
model_final <- model_list[[best_idx]]
cat(sprintf("-> Model terpilih: %s (AIC = %.4f)\n", model_names[best_idx], aic_vals[best_idx]))## -> Model terpilih: Backward (AIC = 310.6772)
Ketiga metode stepwise (backward, forward, both) konvergen pada model yang sama dengan AIC = 310,68, lebih kecil dibandingkan model penuh (AIC = 325,63). Model inilah yang ditetapkan sebagai model final.
## =================== SUMMARY MODEL FINAL ===================
##
## Call:
## glm(formula = overspending_bin ~ prepaid_usage + daily_cash +
## daily_cc + daily_prepaid + employed, family = binomial(link = "logit"),
## data = df_model, na.action = na.omit)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -3.3472 0.3856 -8.681 < 2e-16 ***
## prepaid_usagePrepaid user 1.0437 0.4854 2.150 0.0316 *
## daily_cash1 0.9137 0.4135 2.209 0.0271 *
## daily_cc1 2.5971 0.6157 4.218 2.46e-05 ***
## daily_prepaid1 -1.4446 1.0473 -1.379 0.1678
## employedEmployed -0.9690 0.5465 -1.773 0.0762 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 329.93 on 633 degrees of freedom
## Residual deviance: 298.68 on 628 degrees of freedom
## AIC: 310.68
##
## Number of Fisher Scoring iterations: 6
Model regresi logistik biner final yang diperoleh:
\[\ln\left(\frac{\hat{\pi}(x)}{1-\hat{\pi}(x)}\right) = -3,3472 + 1,0437\,X_1 + 0,9137\,X_2 + 2,5971\,X_3 - 1,4446\,X_4 - 0,9690\,X_5\]
dengan \(X_1\) = Prepaid Usage, \(X_2\) = Daily Cash Usage, \(X_3\) = Daily Credit Card Usage, \(X_4\) = Daily Prepaid Card Usage, dan \(X_5\) = Employed.
Interpretasi. Variabel prepaid usage, daily cash usage, dan daily credit card usage berpengaruh signifikan pada taraf 5%, sementara employed signifikan pada taraf 10%. Variabel daily prepaid usage tidak signifikan secara statistik namun tetap dipertahankan dalam model karena kontribusinya terhadap AIC minimum.
OR <- exp(coef(model_final))
CI <- exp(confint(model_final))
OR_table <- data.frame(
OR = round(OR, 4),
CI_lower = round(CI[, 1], 4),
CI_upper = round(CI[, 2], 4),
p_value = round(summary(model_final)$coefficients[, 4], 4)
)
OR_display <- data.frame(Variabel = rownames(OR_table), OR_table, row.names = NULL)
style_table(OR_display, caption = "Tabel: Odds Ratio dan 95% Confidence Interval - Model Final")| Variabel | OR | CI_lower | CI_upper | p_value |
|---|---|---|---|---|
| (Intercept) | 0.0352 | 0.0152 | 0.0699 | 0.0000 |
| prepaid_usagePrepaid user | 2.8396 | 1.0143 | 6.9755 | 0.0316 |
| daily_cash1 | 2.4935 | 1.1673 | 6.0254 | 0.0271 |
| daily_cc1 | 13.4248 | 3.9520 | 46.0066 | 0.0000 |
| daily_prepaid1 | 0.2358 | 0.0236 | 1.5648 | 0.1678 |
| employedEmployed | 0.3795 | 0.1100 | 0.9891 | 0.0762 |
Forest Plot Odds Ratio:
or_df <- OR_table[-1, ] # Hapus intercept
or_df$variable <- rownames(or_df)
or_df$signif <- ifelse(or_df$p_value < 0.05, "Signifikan", "Tidak Signifikan")
p_or <- ggplot(or_df, aes(x = reorder(variable, OR), y = OR, color = signif)) +
geom_point(size = 3) +
geom_errorbar(aes(ymin = CI_lower, ymax = CI_upper), width = 0.25) +
geom_hline(yintercept = 1, linetype = "dashed", color = "gray40") +
scale_color_manual(values = c("Signifikan" = GOLD_ACCENT, "Tidak Signifikan" = GRAY_MUTED)) +
coord_flip() +
labs(title = "Forest Plot: Odds Ratio", subtitle = "dengan 95% Confidence Interval",
x = "Variabel", y = "Odds Ratio", color = "Status") +
theme_minimal(base_size = 11)
p_orDaily credit card usage memiliki OR terbesar = 13,42 — responden yang menggunakan kartu kredit setiap hari memiliki peluang overspending 13,42 kali lebih besar dibanding kelompok referensi. Variabel prepaid usage (OR = 2,84) dan daily cash usage (OR = 2,49) juga meningkatkan peluang overspending, sementara employed (OR = 0,38) menurunkan peluang overspending — responden yang bekerja cenderung lebih kecil risikonya mengalami overspending.
## Nilai VIF > 10 mengindikasikan multikolinieritas serius
vif_values <- vif(model_final)
vif_df <- data.frame(Variabel = names(vif_values), VIF = round(as.numeric(vif_values), 3))
style_table(vif_df, caption = "Tabel: Variance Inflation Factor (VIF)")| Variabel | VIF |
|---|---|
| prepaid_usage | 1.232 |
| daily_cash | 1.017 |
| daily_cc | 1.202 |
| daily_prepaid | 1.422 |
| employed | 1.004 |
Interpretasi. Seluruh nilai VIF berada jauh di bawah ambang batas 10 (bahkan di bawah 2), sehingga tidak terdapat indikasi multikolinearitas antarprediktor dalam model final.
## H0: Model fit (tidak ada perbedaan signifikan observasi vs prediksi)
## Jika p > 0.05 -> model fit baik
##
## Hosmer and Lemeshow goodness of fit (GOF) test
##
## data: df_model$overspending_bin, fitted(model_final)
## X-squared = 2.5802, df = 2, p-value = 0.2752
Interpretasi. Nilai p-value = 0,2752 (> 0,05) menunjukkan tidak ada perbedaan signifikan antara frekuensi observasi dan prediksi model, sehingga model dinyatakan memiliki kecocokan (goodness of fit) yang baik.
model_null <- glm(overspending_bin ~ 1, data = df_model, family = binomial)
lr_test <- lrtest(model_null, model_final)
print(lr_test)## Likelihood ratio test
##
## Model 1: overspending_bin ~ 1
## Model 2: overspending_bin ~ prepaid_usage + daily_cash + daily_cc + daily_prepaid +
## employed
## #Df LogLik Df Chisq Pr(>Chisq)
## 1 1 -164.97
## 2 6 -149.34 5 31.255 8.341e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Interpretasi. Hasil LRT signifikan (p < 0,001), artinya model final secara keseluruhan signifikan dibandingkan model tanpa prediktor (null model).
L0 <- as.numeric(logLik(model_null))
Lm <- as.numeric(logLik(model_final))
n <- nrow(df_model)
cox_snell <- 1 - exp((2 / n) * (L0 - Lm))
nagelkerke <- cox_snell / (1 - exp((2 / n) * L0))
mcfadden <- 1 - (logLik(model_final) / logLik(model_null))
pseudo_r2_df <- data.frame(
Ukuran = c("Cox & Snell R²", "Nagelkerke R²", "McFadden R²"),
Nilai = round(c(as.numeric(cox_snell), nagelkerke, as.numeric(mcfadden)), 4)
)
style_table(pseudo_r2_df, caption = "Tabel: Pseudo R-Square")| Ukuran | Nilai |
|---|---|
| Cox & Snell R² | 0.0481 |
| Nagelkerke R² | 0.1186 |
| McFadden R² | 0.0947 |
Interpretasi. Nilai Nagelkerke R² = 0,1186 menunjukkan model mampu menjelaskan sekitar 11,86% variasi perilaku overspending — nilai yang wajar untuk model dengan respons biner yang sangat imbalanced, namun mengindikasikan masih ada faktor lain di luar model yang berkontribusi.
prob_pred <- predict(model_final, type = "response")
class_pred <- ifelse(prob_pred >= 0.5, "Yes", "No")
class_pred <- factor(class_pred, levels = c("No", "Yes"))
cm <- confusionMatrix(class_pred, factor(df_model$overspending, levels = c("No", "Yes")), positive = "Yes")
print(cm)## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 584 44
## Yes 4 2
##
## Accuracy : 0.9243
## 95% CI : (0.9009, 0.9437)
## No Information Rate : 0.9274
## P-Value [Acc > NIR] : 0.6559
##
## Kappa : 0.0612
##
## Mcnemar's Test P-Value : 1.811e-08
##
## Sensitivity : 0.043478
## Specificity : 0.993197
## Pos Pred Value : 0.333333
## Neg Pred Value : 0.929936
## Prevalence : 0.072555
## Detection Rate : 0.003155
## Detection Prevalence : 0.009464
## Balanced Accuracy : 0.518338
##
## 'Positive' Class : Yes
##
cm_table <- as.data.frame(cm$table)
names(cm_table) <- c("Predicted", "Actual", "Freq")
p_cm <- ggplot(cm_table, aes(x = Actual, y = Predicted, fill = Freq)) +
geom_tile(color = "white") +
geom_text(aes(label = Freq,
color = ifelse(Freq > max(Freq) * 0.5, "white", EMERALD_DARK)),
size = 6, fontface = "bold") +
scale_color_identity() +
scale_fill_gradient(low = "#FDF6E3", high = EMERALD_DARK) +
labs(title = "Confusion Matrix", x = "Actual Class", y = "Predicted Class") +
theme_minimal()
p_cmInterpretasi. Pada threshold default 0,5 diperoleh accuracy = 92,43%, namun sensitivity hanya 4,35% (specificity = 99,32%, balanced accuracy = 51,83%). Accuracy yang tinggi ini menyesatkan karena data sangat imbalanced — model hampir selalu memprediksi “tidak overspending”.
roc_obj <- roc(df_model$overspending_bin, prob_pred)
auc_val <- auc(roc_obj)
cat(sprintf("AUC = %.4f\n", auc_val))## AUC = 0.7114
plot(roc_obj,
main = paste("ROC Curve - AUC =", round(auc_val, 4)),
col = EMERALD_DARK, lwd = 2,
xlab = "1 - Specificity (False Positive Rate)",
ylab = "Sensitivity (True Positive Rate)")
abline(a = 0, b = 1, lty = 2, col = "gray60")
legend("bottomright", legend = paste("AUC =", round(auc_val, 4)), col = EMERALD_DARK, lwd = 2)Interpretasi. Nilai AUC = 0,7114 menunjukkan model memiliki kemampuan diskriminasi yang cukup baik dalam membedakan responden yang mengalami overspending dan yang tidak.
best_thresh <- coords(roc_obj, "best", ret = "threshold", best.method = "youden")
cat(sprintf("Optimal Threshold (Youden): %.4f\n", best_thresh))## Optimal Threshold (Youden): 0.0353
class_pred_opt <- ifelse(prob_pred >= as.numeric(best_thresh), "Yes", "No")
class_pred_opt <- factor(class_pred_opt, levels = c("No", "Yes"))
cm_opt <- confusionMatrix(class_pred_opt, factor(df_model$overspending, levels = c("No", "Yes")), positive = "Yes")
print(cm_opt)## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 258 5
## Yes 330 41
##
## Accuracy : 0.4716
## 95% CI : (0.4322, 0.5113)
## No Information Rate : 0.9274
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.0776
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.89130
## Specificity : 0.43878
## Pos Pred Value : 0.11051
## Neg Pred Value : 0.98099
## Prevalence : 0.07256
## Detection Rate : 0.06467
## Detection Prevalence : 0.58517
## Balanced Accuracy : 0.66504
##
## 'Positive' Class : Yes
##
Interpretasi. Pada threshold optimal (≈ 0,0353), diperoleh sensitivity = 89,13% (naik tajam dari 4,35%), dengan trade-off specificity = 43,88% dan balanced accuracy = 66,50%. Threshold yang lebih rendah lebih sesuai untuk konteks deteksi kasus minoritas (overspending) dibandingkan threshold default 0,5.
thresholds <- seq(0.1, 0.9, by = 0.05)
thresh_results <- data.frame(Threshold = thresholds)
thresh_results$Sensitivity <- sapply(thresholds, function(t) {
pred_t <- ifelse(prob_pred >= t, 1, 0)
cm_t <- table(Pred = pred_t, Obs = df_model$overspending_bin)
TP <- ifelse("1" %in% rownames(cm_t) & "1" %in% colnames(cm_t), cm_t["1", "1"], 0)
FN <- ifelse("0" %in% rownames(cm_t) & "1" %in% colnames(cm_t), cm_t["0", "1"], 0)
if ((TP + FN) == 0) return(NA)
TP / (TP + FN)
})
thresh_results$Specificity <- sapply(thresholds, function(t) {
pred_t <- ifelse(prob_pred >= t, 1, 0)
cm_t <- table(Pred = pred_t, Obs = df_model$overspending_bin)
TN <- ifelse("0" %in% rownames(cm_t) & "0" %in% colnames(cm_t), cm_t["0", "0"], 0)
FP <- ifelse("1" %in% rownames(cm_t) & "0" %in% colnames(cm_t), cm_t["1", "0"], 0)
if ((TN + FP) == 0) return(NA)
TN / (TN + FP)
})
thresh_results$Youden_J <- thresh_results$Sensitivity + thresh_results$Specificity - 1
thresh_display <- data.frame(
Threshold = thresh_results$Threshold,
Sensitivity = round(thresh_results$Sensitivity, 4),
Specificity = round(thresh_results$Specificity, 4),
Youden_J = round(thresh_results$Youden_J, 4)
)
style_table(thresh_display, caption = "Tabel: Sensitivity-Specificity pada Berbagai Threshold")| Threshold | Sensitivity | Specificity | Youden_J |
|---|---|---|---|
| 0.10 | 0.2174 | 0.9541 | 0.1715 |
| 0.15 | 0.2174 | 0.9541 | 0.1715 |
| 0.20 | 0.1522 | 0.9864 | 0.1386 |
| 0.25 | 0.1304 | 0.9864 | 0.1168 |
| 0.30 | 0.1304 | 0.9864 | 0.1168 |
| 0.35 | 0.0652 | 0.9898 | 0.0550 |
| 0.40 | 0.0652 | 0.9898 | 0.0550 |
| 0.45 | 0.0435 | 0.9932 | 0.0367 |
| 0.50 | 0.0435 | 0.9932 | 0.0367 |
| 0.55 | 0.0435 | 1.0000 | 0.0435 |
| 0.60 | 0.0435 | 1.0000 | 0.0435 |
| 0.65 | 0.0435 | 1.0000 | 0.0435 |
| 0.70 | 0.0435 | 1.0000 | 0.0435 |
| 0.75 | 0.0435 | 1.0000 | 0.0435 |
| 0.80 | 0.0000 | 1.0000 | 0.0000 |
| 0.85 | 0.0000 | 1.0000 | 0.0000 |
| 0.90 | 0.0000 | 1.0000 | 0.0000 |
best_row <- which.max(thresh_results$Youden_J)
cat(sprintf("Threshold optimal (Youden J max): %.2f\n", thresh_results$Threshold[best_row]))## Threshold optimal (Youden J max): 0.10
## Sensitivity : 0.2174
## Specificity : 0.9541
## Youden J : 0.1715
thresh_long <- thresh_results %>%
pivot_longer(cols = c(Sensitivity, Specificity), names_to = "Metrik", values_to = "Nilai")
p_thresh <- ggplot(thresh_long, aes(x = Threshold, y = Nilai, color = Metrik)) +
geom_line(size = 1.2) +
geom_point(size = 2) +
geom_vline(xintercept = thresh_results$Threshold[best_row], linetype = "dashed", color = "gray50") +
annotate("text", x = thresh_results$Threshold[best_row] + 0.03, y = 0.5,
label = paste("Optimal\n=", thresh_results$Threshold[best_row]),
size = 3.5, color = "gray30") +
scale_color_manual(values = c("Sensitivity" = GOLD, "Specificity" = EMERALD)) +
labs(title = "Trade-off Sensitivity vs Specificity", subtitle = "pada Berbagai Threshold Klasifikasi",
x = "Threshold", y = "Nilai", color = "Metrik") +
theme_minimal()
p_threshInterpretasi. Semakin tinggi threshold, sensitivity menurun sementara specificity meningkat — menggambarkan trade-off klasik antara mendeteksi kasus positif (overspending) dan menghindari false alarm. Threshold sebesar 0,10 memberikan keseimbangan: sensitivity = 0,217, specificity = 0,954, Youden Index = 0,172.
prevalensi <- mean(df_model$overspending_bin)
cat(sprintf("Prevalensi kelas positif (Overspending = Yes): %.4f (%.2f%%)\n", prevalensi, prevalensi * 100))## Prevalensi kelas positif (Overspending = Yes): 0.0726 (7.26%)
## Jika AUPRC > prevalensi -> model lebih baik dari random guess.
pr_obj <- pr.curve(
scores.class0 = prob_pred[df_model$overspending_bin == 1],
scores.class1 = prob_pred[df_model$overspending_bin == 0],
curve = TRUE
)
cat(sprintf("AUPRC (Area Under Precision-Recall Curve) = %.4f\n", pr_obj$auc.integral))## AUPRC (Area Under Precision-Recall Curve) = 0.2097
plot(pr_obj,
main = paste("Precision-Recall Curve\nAUPRC =", round(pr_obj$auc.integral, 4)),
color = GOLD_ACCENT, auc.main = FALSE)
abline(h = prevalensi, lty = 2, col = EMERALD)
legend("topright",
legend = c(paste("AUPRC =", round(pr_obj$auc.integral, 4)),
paste("Baseline =", round(prevalensi, 4))),
col = c(GOLD_ACCENT, EMERALD), lty = c(1, 2), lwd = 2)Interpretasi. Karena prevalensi overspending hanya 7,57%, ROC-AUC saja kurang informatif. AUPRC = 0,2097 jauh lebih tinggi dibanding baseline (0,0726), menunjukkan model lebih baik dari tebakan acak dalam mengidentifikasi kelas minoritas — meski nilainya masih relatif rendah, menyiratkan ruang perbaikan model pada penelitian lanjutan.
dev_resid <- residuals(model_final, type = "deviance")
pearson_resid <- residuals(model_final, type = "pearson")
fitted_vals <- fitted(model_final)
EMERALD_TRANS <- rgb(17 / 255, 120 / 255, 100 / 255, 0.45)
par(mfrow = c(2, 2))
# Plot 1: Deviance Residuals vs Fitted Values
plot(fitted_vals, dev_resid,
main = "Deviance Residuals vs Fitted",
xlab = "Fitted Values (Probabilitas)", ylab = "Deviance Residuals",
pch = 16, col = EMERALD_TRANS)
abline(h = 0, col = "#B8860B", lty = 2)
lines(lowess(fitted_vals, dev_resid), col = EMERALD_DARK, lwd = 2)
# Plot 2: Q-Q Plot Deviance Residuals
qqnorm(dev_resid, main = "Q-Q Plot Deviance Residuals", pch = 16, col = EMERALD_TRANS)
qqline(dev_resid, col = "#B8860B", lwd = 2)
# Plot 3: Cook's Distance
cooksd <- cooks.distance(model_final)
plot(cooksd, type = "h",
main = "Cook's Distance", xlab = "Observasi", ylab = "Cook's Distance",
col = ifelse(cooksd > 4 / nrow(df_model), GOLD, EMERALD))
abline(h = 4 / nrow(df_model), col = "#B8860B", lty = 2)
# Plot 4: Leverage vs Standardized Pearson Residuals
leverage <- hatvalues(model_final)
std_pearson <- rstandard(model_final, type = "pearson")
plot(leverage, std_pearson,
main = "Leverage vs Std. Pearson Residuals",
xlab = "Leverage", ylab = "Std. Pearson Residuals",
pch = 16, col = EMERALD_TRANS)
abline(h = c(-2, 2), col = "#B8860B", lty = 2)
abline(v = 2 * mean(leverage), col = "#8C6A00", lty = 2)influential <- which(cooksd > 4 / nrow(df_model))
cat("Jumlah observasi berpengaruh (Cook's D > 4/n):", length(influential), "\n")## Jumlah observasi berpengaruh (Cook's D > 4/n): 23
Interpretasi. Residual deviance tersebar di sekitar garis nol tanpa pola sistematis, dan Q-Q plot menunjukkan kesesuaian yang baik dengan distribusi normal (sedikit penyimpangan di ekor). Ditemukan 23 observasi dengan Cook’s Distance melebihi batas 4/n, namun tidak ada nilai yang ekstrem — sehingga observasi tersebut tetap dipertahankan dan tidak dianggap mendistorsi hasil model secara serius. Plot leverage juga tidak menunjukkan kombinasi leverage-residual yang ekstrem, mengonfirmasi tidak ada observasi yang mendominasi pembentukan model.
Berdasarkan analisis terhadap 674 responden, model regresi logistik biner final terdiri atas variabel prepaid card usage, daily cash usage, daily credit card usage, daily prepaid usage, dan employed. Di antara variabel tersebut, daily credit card usage memiliki pengaruh terbesar terhadap peluang overspending (OR = 13,42), diikuti oleh prepaid usage (OR = 2,84) dan daily cash usage (OR = 2,49); sementara status bekerja (employed) cenderung menurunkan risiko overspending (OR = 0,38).
Model memiliki kecocokan baik (Hosmer–Lemeshow p = 0,2752), signifikan secara keseluruhan (LRT p < 0,001), mampu menjelaskan 11,86% variasi overspending (Nagelkerke R²), dan memiliki kemampuan diskriminasi cukup baik (AUC = 0,7114; AUPRC = 0,2097 jauh di atas baseline 0,0726).
Kesimpulan utama: perilaku overspending pada konsumen muda lebih banyak dipengaruhi oleh pola penggunaan metode pembayaran — khususnya kartu kredit — dibandingkan karakteristik demografis maupun tingkat literasi keuangan semata. Temuan ini menegaskan pentingnya pengelolaan penggunaan instrumen pembayaran non-tunai sebagai upaya mitigasi risiko pengeluaran berlebih pada konsumen muda.
## R version 4.5.1 (2025-06-13 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=English_Indonesia.utf8 LC_CTYPE=English_Indonesia.utf8
## [3] LC_MONETARY=English_Indonesia.utf8 LC_NUMERIC=C
## [5] LC_TIME=English_Indonesia.utf8
##
## time zone: Asia/Jakarta
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] broom_1.0.9 PRROC_1.4 rlang_1.2.0
## [4] gridExtra_2.3 corrplot_0.95 kableExtra_1.4.0
## [7] knitr_1.50 DescTools_0.99.60 car_3.1-3
## [10] carData_3.0-5 ResourceSelection_0.3-6 lmtest_0.9-40
## [13] zoo_1.8-14 pROC_1.19.0.1 caret_7.0-1
## [16] lattice_0.22-7 tidyr_1.3.1 ggplot2_4.0.3
## [19] dplyr_1.1.4 readxl_1.4.5
##
## loaded via a namespace (and not attached):
## [1] gld_2.6.8 magrittr_2.0.3 e1071_1.7-16
## [4] compiler_4.5.1 systemfonts_1.3.1 vctrs_0.6.5
## [7] reshape2_1.4.4 stringr_1.5.2 pkgconfig_2.0.3
## [10] fastmap_1.2.0 backports_1.5.0 labeling_0.4.3
## [13] rmarkdown_2.29 prodlim_2025.04.28 tzdb_0.5.0
## [16] haven_2.5.5 purrr_1.1.0 xfun_0.53
## [19] cachem_1.1.0 jsonlite_2.0.0 recipes_1.3.1
## [22] parallel_4.5.1 R6_2.6.1 bslib_0.9.0
## [25] stringi_1.8.7 RColorBrewer_1.1-3 parallelly_1.45.1
## [28] boot_1.3-31 rpart_4.1.24 lubridate_1.9.4
## [31] jquerylib_0.1.4 cellranger_1.1.0 Rcpp_1.1.0
## [34] iterators_1.0.14 future.apply_1.20.0 readr_2.1.5
## [37] Matrix_1.7-3 splines_4.5.1 nnet_7.3-20
## [40] timechange_0.3.0 tidyselect_1.2.1 rstudioapi_0.17.1
## [43] abind_1.4-8 yaml_2.3.10 timeDate_4051.111
## [46] codetools_0.2-20 listenv_0.9.1 tibble_3.3.0
## [49] plyr_1.8.9 withr_3.0.2 S7_0.2.0
## [52] evaluate_1.0.5 future_1.67.0 survival_3.8-3
## [55] proxy_0.4-27 xml2_1.5.1 pillar_1.11.0
## [58] foreach_1.5.2 stats4_4.5.1 generics_0.1.4
## [61] hms_1.1.3 scales_1.4.0 rootSolve_1.8.2.4
## [64] globals_0.18.0 class_7.3-23 glue_1.8.0
## [67] lmom_3.2 tools_4.5.1 data.table_1.17.8
## [70] ModelMetrics_1.2.2.2 gower_1.0.2 forcats_1.0.0
## [73] Exact_3.3 fs_1.6.6 mvtnorm_1.3-3
## [76] grid_4.5.1 ipred_0.9-15 nlme_3.1-168
## [79] Formula_1.2-5 cli_3.6.5 textshaping_1.0.4
## [82] expm_1.0-0 viridisLite_0.4.2 svglite_2.2.2
## [85] lava_1.8.1 gtable_0.3.6 sass_0.4.10
## [88] digest_0.6.37 farver_2.1.2 htmltools_0.5.8.1
## [91] lifecycle_1.0.4 hardhat_1.4.2 httr_1.4.7
## [94] MASS_7.3-65