Kanker payudara (Carcinoma Mammae) merupakan tumor ganas berupa benjolan abnormal yang tumbuh di dalam jaringan payudara. Berdasarkan data World Health Organization (WHO) tahun 2022, diperkirakan terdapat 2,3 juta kasus baru kanker payudara di seluruh dunia.
Sekitar 80% kasus kanker payudara berkembang pada wanita tanpa faktor risiko spesifik yang dapat dimodifikasi — hanya berkaitan dengan faktor penuaan dan jenis kelamin. Hal ini menunjukkan bahwa penentuan status tumor tidak dapat diidentifikasi secara akurat hanya melalui hasil riwayat penyakit pasien.
Oleh karena itu, digunakan Model Regresi Logistik Biner berbasis karakteristik mammographic mass sebagai instrumen pendukung deteksi dini.
Penelitian ini menganalisis karakteristik mammographic mass yang meliputi bentuk (shape), tepi (margin), dan kepadatan (density) bersama usia pasien (age) untuk memprediksi probabilitas keganasan lesi payudara.
Penelitian ini bertujuan untuk:
Dataset yang digunakan adalah dataset publik Mammographic Mass dari UCI Machine Learning Repository, dikumpulkan oleh Institute of Radiology.
# Load library yang dibutuhkan
library(tidyverse)
library(knitr)
library(kableExtra)
library(car)
library(caret)
library(ResourceSelection)
# ── Muat dataset ──────────────────────────────────────────────────────────────
# Dataset dapat diunduh dari: https://archive.ics.uci.edu/dataset/161/mammographic+mass
# Dataset tanpa missing value: https://drive.google.com/file/d/1KwCCnu0Hj7-qvuxpeV8CE8FSOjj1Wmdv/view?usp=drive_link
mam_raw <- read.csv("mammographic_masses.csv", header = TRUE, sep = ";")
names(mam_raw)#> [1] "BI.Rads" "Age" "Shape" "Margin" "Density" "Severity"
# ── Bersihkan data ────────────────────────────────────────────────────────────
mam <- mam_raw %>%
select(-BI.Rads) %>%
drop_na() %>%
mutate(
Severity = factor(Severity, levels = c(0, 1), labels = c("Benign", "Malignant")),
Shape = factor(Shape,
levels = 1:4,
labels = c("Round", "Oval", "Lobular", "Irregular")),
Margin = factor(Margin,
levels = 1:5,
labels = c("Circumscribed", "Microlobulated",
"Obscured", "Ill-defined", "Spiculated")),
Density_cat = factor(Density,
levels = 1:4,
labels = c("High", "Iso", "Low", "Fat-containing"))
)
cat("Jumlah observasi setelah pembersihan:", nrow(mam), "\n")#> Jumlah observasi setelah pembersihan: 828
#> Distribusi Severity:
#>
#> Benign Malignant
#> 426 402
Informasi Variabel
| Variabel | Jenis | Keterangan |
|---|---|---|
Severity |
Biner (Respons) | 0 = Benign, 1 = Malignant |
Age |
Numerik kontinu | Usia pasien (tahun) |
Shape |
Nominal (4 kategori) | Round, Oval, Lobular, Irregular |
Margin |
Nominal (5 kategori) | Circumscribed, Microlobulated, Obscured, Ill-defined, Spiculated |
Density |
Ordinal (4 level) | High, Iso, Low, Fat-containing |
Sebelum melanjutkan analisis ke tahap pemodelan, data dianalisis secara deskriptif untuk merangkum karakteristik dasar dan memahami sifat masing-masing variabel.
Data Kategorik
Variabel Severity, Shape,
Margin, dan Density dianalisis menggunakan distribusi
frekuensi dan proporsi untuk merangkum sebaran masing-masing
kategori.
Data Numerik
Variabel Age dianalisis dengan
menghitung rata-rata, median, nilai minimum, dan
nilai maksimum untuk memahami distribusi dan variasi data
numerik.
Variabel respons bersifat biner sehingga digunakan regresi logistik biner. Fungsi penghubung logit mengubah probabilitas non-linear menjadi bentuk linear:
\[ \text{logit}\big(\pi(x)\big) = \log\left(\frac{\pi(\text{Malignant})}{1 - \pi(\text{Malignant})}\right) = \alpha + \beta_1(\text{Age}) + \beta_2(\text{Shape}) + \beta_3(\text{Margin}) + \beta_4(\text{Density}) \]
Penaksiran parameter dilakukan dengan Maximum Likelihood Estimation (MLE) melalui algoritma Fisher’s Scoring secara iteratif. Standard error diperoleh dari diagonal utama invers matriks informasi Fisher.
Uji simultan digunakan untuk mengetahui apakah semua variabel independen (X) yang ada di dalam model secara bersama-sama (serentak) memiliki pengaruh yang signifikan terhadap variabel dependen (Y) \[ G^2 = -2(L_0 - L_1) \sim \chi^2_{(k)} \]
Uji parsial digunakan untuk mengetahui pengaruh masing-masing variabel independen (X) secara sendirian (individual) terhadap variabel dependen (Y), dengan asumsi variabel independen lainnya dianggap konstan/tetap.
\[ W = \frac{\hat\beta_i}{SE(\hat\beta_i)} \sim N(0,1) \text{ (jika } H_0 \text{ benar)} \]
# ── Tabel frekuensi variabel kategorik ───────────────────────────────────────
freq_tbl <- function(var, var_name) {
tbl <- as.data.frame(table(mam[[var]]))
tbl$Persen <- round(tbl$Freq / nrow(mam) * 100, 2)
tbl$Variabel <- var_name
names(tbl)[1] <- "Kategori"
tbl[, c("Variabel", "Kategori", "Freq", "Persen")]
}
tbl_kat <- rbind(
freq_tbl("Severity", "Severity"),
freq_tbl("Shape", "Shape"),
freq_tbl("Margin", "Margin"),
freq_tbl("Density_cat", "Density")
)
names(tbl_kat) <- c("Variabel", "Kategori", "Frekuensi", "Persentase (%)")
nice_kable(tbl_kat, caption = "Tabel 1. Karakteristik Data Mammographic Masses")| Variabel | Kategori | Frekuensi | Persentase (%) |
|---|---|---|---|
| Severity | Benign | 426 | 51.45 |
| Severity | Malignant | 402 | 48.55 |
| Shape | Round | 190 | 22.95 |
| Shape | Oval | 179 | 21.62 |
| Shape | Lobular | 81 | 9.78 |
| Shape | Irregular | 378 | 45.65 |
| Margin | Circumscribed | 319 | 38.53 |
| Margin | Microlobulated | 23 | 2.78 |
| Margin | Obscured | 106 | 12.80 |
| Margin | Ill-defined | 253 | 30.56 |
| Margin | Spiculated | 127 | 15.34 |
| Density | High | 10 | 1.21 |
| Density | Iso | 56 | 6.76 |
| Density | Low | 754 | 91.06 |
| Density | Fat-containing | 8 | 0.97 |
# ── Ringkasan variabel numerik Age ───────────────────────────────────────────
age_summary <- data.frame(
Minimum = min(mam$Age),
`Kuartil 1` = quantile(mam$Age, 0.25),
Median = median(mam$Age),
Mean = round(mean(mam$Age), 2),
`Kuartil 3` = quantile(mam$Age, 0.75),
Maksimum = max(mam$Age),
check.names = FALSE
)
knitr::kable(age_summary, caption = "Tabel 2. Ringkasan Variabel Numerik (Age)", align = "c")| Minimum | Kuartil 1 | Median | Mean | Kuartil 3 | Maksimum | |
|---|---|---|---|---|---|---|
| 25% | 18 | 45.75 | 57 | 55.79 | 66 | 96 |
Interpretasi Deskriptif. Distribusi Severity relatif seimbang: Benign 51,45% dan Malignant 48,55%, sehingga data tidak mengalami ketimpangan kelas. Karakteristik massa didominasi oleh bentuk Irregular (45,65%), margin Circumscribed (38,53%), dan densitas Low (91,06%) yang memperlihatkan adanya pola asimetri. Rata-rata usia pasien sebesar 55,79 tahun. Indikator rerata usia di angka kepala lima ini menunjukkan bahwa mayoritas sampel berada pada kelompok paruh baya hingga lansia, yang merupakan fase siklus hidup dengan tingkat kerentanan tinggi terhadap kanker payudara.
Dua spesifikasi model dibandingkan untuk mengkaji sensitivitas penanganan variabel ordinal Density:
Model 1 — Density sebagai Skala Kontinu (Numerik) \[ \begin{aligned} \ln \left( \frac{p}{1-p} \right) &= \beta_0 + \beta_1 (\text{Age}) + \beta_2 (\text{Shape}_{\text{Oval}}) + \beta_3 (\text{Shape}_{\text{Lobular}}) + \beta_4 (\text{Shape}_{\text{Irregular}}) \\ &\quad + \beta_5 (\text{Margin}_{\text{Micro}}) + \beta_6 (\text{Margin}_{\text{Obscured}}) + \beta_7 (\text{Margin}_{\text{Ill-defined}}) + \beta_8 (\text{Margin}_{\text{Spiculated}}) + \beta_9 (\text{Density}) \end{aligned} \]
Model memperlakukan skor 1 hingga 4 sebagai satu kesatuan skala numerik. Model berasumsi bahwa dampak risiko dari skor 1 ke 2, 2 ke 3, dan 3 ke 4 memiliki bobot perubahan yang konstan. Pendekatan ini tidak memerlukan kategori acuan karena yang dihitung adalah gradien kemiringan trennya. Kelebihan model adalah menghasilkan model yang sederhana dan hemat parameter (parsimonious).
Model 2 — Density sebagai Variabel Nominal (Kategorik)
\[ \ln\left(\frac{p}{1-p}\right) = \beta_0 + \beta_1(\text{Age}) + \cdots + \beta_8(\text{Margin}_{\text{Spiculated}}) + \beta_9(\text{Density}_{\text{Iso}}) + \beta_{10}(\text{Density}_{\text{Low}}) + \beta_{11}(\text{Density}_{\text{Fat-containing}}) \] ::: {.interpret-box} Model memperlakukan skor 1 sampai 4 murni sebagai empat kelompok kategori terpisah yang berdiri sendiri. Pendekatan ini mewajibkan adanya satu kelompok acuan (yaitu Skor 1 / High) sebagai pembanding. Keunggulannya adalah model menjadi sangat fleksibel karena membebaskan tiap tingkatan density untuk memiliki lompatan nilai risiko yang berbeda. Namun kelemahannya, model menjadi boros parameter karena harus memecah satu variabel menjadi tiga koefisien dummy baru.
Kategori acuan: High Density (Tingkat 1).
# ── Referensi level ───────────────────────────────────────────────────────────
mam$Shape <- relevel(mam$Shape, ref = "Round")
mam$Margin <- relevel(mam$Margin, ref = "Circumscribed")
# ── Model 1: Density kontinu ──────────────────────────────────────────────────
m1 <- glm(Severity ~ Age + Shape + Margin + Density,
data = mam, family = binomial(link = "logit"))
# ── Model 2: Density nominal ──────────────────────────────────────────────────
mam$Density_cat <- relevel(mam$Density_cat, ref = "High")
m2 <- glm(Severity ~ Age + Shape + Margin + Density_cat,
data = mam, family = binomial(link = "logit"))# ── Hosmer-Lemeshow ───────────────────────
hl_test <- function(model, g = 10) {
tryCatch({
ResourceSelection::hoslem.test(
as.numeric(model$y), fitted(model), g = g
)
}, error = function(e) list(statistic = NA, p.value = NA))
}
hl1 <- hl_test(m1)
hl2 <- hl_test(m2)
# ── Akurasi ────────────────────────────────────────────────────────────────────
acc_fn <- function(model) {
pred <- ifelse(fitted(model) > 0.5, "Malignant", "Benign")
mean(pred == as.character(mam$Severity)) * 100
}
# ── Fungsi tambahan untuk evaluasi model ──────────────────────────────
acc_fn <- function(model){
pred <- ifelse(fitted(model) > 0.5, 1, 0)
mean(pred == model$y) * 100
}
sens_fn <- function(model){
pred <- ifelse(fitted(model) > 0.5, 1, 0)
sum(pred == 1 & model$y == 1) / sum(model$y == 1) * 100
}
spec_fn <- function(model){
pred <- ifelse(fitted(model) > 0.5, 1, 0)
sum(pred == 0 & model$y == 0) / sum(model$y == 0) * 100
}
ci_fn <- function(model){
n <- length(model$y)
p <- mean(ifelse(fitted(model) > 0.5, 1, 0) == model$y)
error <- 1.96 * sqrt(p*(1-p)/n) * 100
c((p*100)-error, (p*100)+error)
}
# ── Hosmer-Lemeshow test ──────────────────────────────
library(ResourceSelection)
hl1 <- hoslem.test(m1$y, fitted(m1), g=10)
hl2 <- hoslem.test(m2$y, fitted(m2), g=10)
# ── Tabel perbandingan GOF ──────────────────────────────
gof <- data.frame(
`Metrik Evaluasi` = c(
"Null Deviance (df)",
"Residual Deviance (df)",
"Log-Likelihood Maximum (ln L)",
"Akaike Information Criterion (AIC)",
"Akurasi Klasifikasi (%)",
"Rentang 95% Confidence Interval (CI)",
"Sensitivitas",
"Spesifisitas",
"Hosmer-Lemeshow χ² (p-value)"
),
`Model 1 (Density Kontinu)` = c(
paste0(round(m1$null.deviance, 4), " (df = ", m1$df.null, ")"),
paste0(round(deviance(m1), 4), " (df = ", m1$df.residual, ")"),
round(logLik(m1)[1], 4),
round(AIC(m1), 4),
paste0(round(acc_fn(m1), 2), "%"),
paste0(round(ci_fn(m1)[1], 2), " – ", round(ci_fn(m1)[2], 2), "%"),
paste0(round(sens_fn(m1), 2), "%"),
paste0(round(spec_fn(m1), 2), "%"),
paste0(round(hl1$statistic, 5), " (p = ", round(hl1$p.value, 4), ")")
),
`Model 2 (Density Nominal)` = c(
paste0(round(m2$null.deviance, 4), " (df = ", m2$df.null, ")"),
paste0(round(deviance(m2), 4), " (df = ", m2$df.residual, ")"),
round(logLik(m2)[1], 4),
round(AIC(m2), 4),
paste0(round(acc_fn(m2), 2), "%"),
paste0(round(ci_fn(m2)[1], 2), " – ", round(ci_fn(m2)[2], 2), "%"),
paste0(round(sens_fn(m2), 2), "%"),
paste0(round(spec_fn(m2), 2), "%"),
paste0(round(hl2$statistic, 5), " (p = ", round(hl2$p.value, 4), ")")
),
check.names = FALSE
)
# ── Menampilkan tabel menggunakan kable untuk Rpubs ──────────────
library(knitr)
library(kableExtra)
kable(gof, caption = "Tabel 3. Statistik Kebaikan Suai (Goodness of Fit) Model", align="c") %>%
kable_styling(full_width = FALSE, position = "center", bootstrap_options = c("striped", "hover", "condensed"))| Metrik Evaluasi | Model 1 (Density Kontinu) | Model 2 (Density Nominal) |
|---|---|---|
| Null Deviance (df) | 1147.156 (df = 827) | 1147.156 (df = 827) |
| Residual Deviance (df) | 726.8935 (df = 818) | 723.6525 (df = 816) |
| Log-Likelihood Maximum (ln L) | -363.4468 | -361.8263 |
| Akaike Information Criterion (AIC) | 746.8935 | 747.6525 |
| Akurasi Klasifikasi (%) | 81.64% | 81.52% |
| Rentang 95% Confidence Interval (CI) | 79.01 – 84.28% | 78.88 – 84.17% |
| Sensitivitas | 85.32% | 85.07% |
| Spesifisitas | 78.17% | 78.17% |
| Hosmer-Lemeshow χ² (p-value) | 2.00894 (p = 0.9807) | 1.06932 (p = 0.9978) |
# ── Likelihood Ratio Test antara Model 1 dan Model 2 ─────────────────────────
lrt <- anova(m1, m2, test = "Chisq")
print(lrt)#> Analysis of Deviance Table
#>
#> Model 1: Severity ~ Age + Shape + Margin + Density
#> Model 2: Severity ~ Age + Shape + Margin + Density_cat
#> Resid. Df Resid. Dev Df Deviance Pr(>Chi)
#> 1 818 727
#> 2 816 724 2 3.24 0.2
Pemilihan Model Terbaik. Model 2 menghasilkan Log-Likelihood yang sedikit lebih besar, namun pengujian LRT menghasilkan \(\chi^2 \approx 3{,}24\) (\(df = 2\), \(p = 0{,}1978 > 0{,}05\)), tidak signifikan secara statistik. Artinya fleksibilitas yang ditawarkan oleh Model 2 (Nominal) sama sekali tidak memberikan kontribusi peningkatan akurasi yang berarti pada data riil pasien di lapangan.
Berdasarkan prinsip parsimoni, Model 1 dipilih sebagai model final karena: - AIC lebih rendah (746,89 vs 747,65) - Efisiensi parameter lebih tinggi (df lebih besar) - Performa akurasi setara (81,64%) serta sensitivitas yang lebih tinggi (85,32%) yang lebih tinggi - Kedua model fit berdasarkan uji Hosmer-Lemeshow (\(p > 0{,}05\))
# ── GVIF (Generalized VIF) ────────────────────────────────────────────────────
gvif_res <- as.data.frame(car::vif(m1))
gvif_res$Variabel <- rownames(gvif_res)
names(gvif_res)[1:3] <- c("GVIF", "Df", "GVIF^(1/(2*Df))")
gvif_res$Keterangan <- ifelse(
gvif_res$`GVIF^(1/(2*Df))` < 2,
"Bebas Multikolinearitas", "Terindikasi Multikolinearitas"
)
gvif_out <- gvif_res[, c("Variabel", "GVIF", "Df", "GVIF^(1/(2*Df))", "Keterangan")]
nice_kable(gvif_out,
caption = "Tabel 4. Hasil Uji Multikolinearitas (GVIF)",
digits = 6)| Variabel | GVIF | Df | GVIF^(1/(2*Df)) | Keterangan | |
|---|---|---|---|---|---|
| Age | Age | 1.014 | 1 | 1.007 | Bebas Multikolinearitas |
| Shape | Shape | 1.891 | 3 | 1.112 | Bebas Multikolinearitas |
| Margin | Margin | 1.935 | 4 | 1.086 | Bebas Multikolinearitas |
| Density | Density | 1.026 | 1 | 1.013 | Bebas Multikolinearitas |
Interpretasi GVIF. Seluruh variabel prediktor memiliki nilai \(\text{GVIF}^{1/(2\cdot df)}\) pada rentang yang sangat rendah (1,006855 hingga 1,112007). Hasil ini membuktikan bahwa Model 1 terbebas dari bias multikolinearitas, sehingga estimasi koefisien bersifat stabil dan bebas bias.
Berikut adalah tabel analisis deviance untuk menguji pengaruh seluruh variabel prediktor secara serentak:
# Model null untuk Model 1 (tanpa prediktor)
model_null1 <- glm(Severity ~ 1, data = mam, family = binomial(link = "logit"))
# Uji Chi-Square serentak untuk semua prediktor Model 1
anova(model_null1, m1, test = "Chisq")# ── Tabel koefisien regresi ────────────────────────────────────────────────────
coef_tbl <- as.data.frame(summary(m1)$coefficients)
coef_tbl$Variabel <- rownames(coef_tbl)
names(coef_tbl) <- c("β (Koefisien)", "SE", "z-value", "p-value", "Variabel")
coef_tbl$`Signifikansi` <- ifelse(
coef_tbl$`p-value` < 0.001, "***",
ifelse(coef_tbl$`p-value` < 0.01, "**",
ifelse(coef_tbl$`p-value` < 0.05, "*",
ifelse(coef_tbl$`p-value` < 0.1, ".", "ns")))
)
nice_kable(coef_tbl,
caption = "Tabel 5. Koefisien Regresi Logistik Model 1",
digits = 4)| β (Koefisien) | SE | z-value | p-value | Variabel | Signifikansi | |
|---|---|---|---|---|---|---|
| (Intercept) | -4.4481 | 0.8645 | -5.1452 | 0.0000 | (Intercept) | *** |
| Age | 0.0549 | 0.0078 | 7.0747 | 0.0000 | Age | *** |
| ShapeOval | -0.2744 | 0.3179 | -0.8632 | 0.3880 | ShapeOval | ns |
| ShapeLobular | 0.6089 | 0.3731 | 1.6321 | 0.1027 | ShapeLobular | ns |
| ShapeIrregular | 1.3546 | 0.3325 | 4.0742 | 0.0000 | ShapeIrregular | *** |
| MarginMicrolobulated | 1.6610 | 0.5602 | 2.9653 | 0.0030 | MarginMicrolobulated | ** |
| MarginObscured | 1.1852 | 0.3508 | 3.3788 | 0.0007 | MarginObscured | *** |
| MarginIll-defined | 1.4733 | 0.3018 | 4.8822 | 0.0000 | MarginIll-defined | *** |
| MarginSpiculated | 2.0068 | 0.3731 | 5.3784 | 0.0000 | MarginSpiculated | *** |
| Density | -0.1327 | 0.2546 | -0.5210 | 0.6024 | Density | ns |
Persamaan Model Final (Model 1)
\[ \begin{aligned} \ln\left(\frac{p}{1-p}\right) = \; &{-4{,}4481} + {0{,}0549}(\text{Age}) - {0{,}2744}(\text{Shape}_{\text{Oval}}) + {0{,}6090}(\text{Shape}_{\text{Lobular}}) + {1{,}3546}(\text{Shape}_{\text{Irregular}}) \\ & + {1{,}6610}(\text{Margin}_{\text{Micro}}) + {1{,}1852}(\text{Margin}_{\text{Obscured}}) + {1{,}4733}(\text{Margin}_{\text{Ill-defined}}) + {2{,}0069}(\text{Margin}_{\text{Spiculated}}) - {0{,}1327}(\text{Density}) \end{aligned} \]
# ── Odds Ratio dan CI ──────────────────────────────────────────────────────────
or_tbl <- as.data.frame(exp(cbind(OR = coef(m1), confint(m1, level = 0.95))))
or_tbl$Variabel <- rownames(or_tbl)
or_tbl$`CI 95%` <- paste0(
round(or_tbl$`2.5 %`, 4), " – ", round(or_tbl$`97.5 %`, 4)
)
# Ambil p-value dari model
pval <- summary(m1)$coefficients[, 4]
or_tbl$Signifikan <- ifelse(pval < 0.05, "Signifikan", "Tidak Signifikan")
or_out <- or_tbl[, c("Variabel", "OR", "CI 95%", "Signifikan")]
or_out <- or_out[-1, ] # hapus intercept
nice_kable(or_out,
caption = "Tabel 6. Estimasi Odds Ratio Model 1",
digits = 4)| Variabel | OR | CI 95% | Signifikan | |
|---|---|---|---|---|
| Age | Age | 1.0564 | 1.0408 – 1.073 | Signifikan |
| ShapeOval | ShapeOval | 0.7600 | 0.4044 – 1.4123 | Tidak Signifikan |
| ShapeLobular | ShapeLobular | 1.8385 | 0.8804 – 3.8142 | Tidak Signifikan |
| ShapeIrregular | ShapeIrregular | 3.8753 | 2.0158 – 7.4491 | Signifikan |
| MarginMicrolobulated | MarginMicrolobulated | 5.2647 | 1.7822 – 16.2625 | Signifikan |
| MarginObscured | MarginObscured | 3.2714 | 1.6484 – 6.5377 | Signifikan |
| MarginIll-defined | MarginIll-defined | 4.3637 | 2.4274 – 7.9461 | Signifikan |
| MarginSpiculated | MarginSpiculated | 7.4398 | 3.6185 – 15.6858 | Signifikan |
| Density | Density | 0.8758 | 0.5331 – 1.4536 | Tidak Signifikan |
Interpretasi Estimasi Parameter dan Odds Ratio Utama
| Variabel | β | OR | p-value | Interpretasi |
|---|---|---|---|---|
| Intercept | -4,4481 | - | <0.001 | Nilai logit saat semua prediktor = 0; probabilitas 0,47%. |
| Age | 0.0549 | 1.0564 | <0.001 | Setiap kenaikan usia 1 tahun, odds keganasan meningkat 5,64% (CI: 4,02%–7,31%). |
| Shape Oval | -0,2744 | 0.76 | 0.3881 | Lesi oval memiliki odds 19,27% lebih rendah dibanding Round; tidak signifikan. |
| Shape Lobular | 0,6090 | 1,8385 | 0.1027 | Lesi berbentuk lobular memiliki odds 67,75% lebih tinggi dibanding Round; tidak signifikan. |
| Shape Irregular | 1.3546 | 3.8753 | <0.001 | Lesi irregular memiliki odds 3,87× lebih tinggi dibanding Round; signifikan. |
| Margin Microlobulated | 1,661 | 5,2647 | 0.0074 | Odds 5,26× lebih tinggi dibanding Circumscribed; signifikan. |
| Margin Obscured | 1,1852 | 3,2714 | <0.001 | Odds 3,27× lebih tinggi dibanding Circumscribed; signifikan. |
| Margin Ill-defined | 1,4733 | 4,3637 | <0.001 | Odds 4,36× lebih tinggi dibanding Circumscribed; signifikan. |
| Margin Spiculated | 2.0069 | 7.4398 | <0.001 | Odds 7,44× lebih tinggi dibanding Circumscribed; signifikan; penanda utama stadium lanjut. |
| Density | -0.1327 | 0.8758 | 0.6000 | Setiap kenaikan 1 satuan skor menunjukkan penurunan odds keganasan 12,42%; tidak signifikan. |
# ── Confusion Matrix (threshold = 0.5) ────────────────────────────────────────
pred_class <- factor(
ifelse(fitted(m1) > 0.5, "Malignant", "Benign"),
levels = c("Benign", "Malignant")
)
cm <- confusionMatrix(pred_class, mam$Severity, positive = "Malignant")
print(cm)#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction Benign Malignant
#> Benign 333 59
#> Malignant 93 343
#>
#> Accuracy : 0.816
#> 95% CI : (0.788, 0.842)
#> No Information Rate : 0.514
#> P-Value [Acc > NIR] : < 0.0000000000000002
#>
#> Kappa : 0.633
#>
#> Mcnemar's Test P-Value : 0.00744
#>
#> Sensitivity : 0.853
#> Specificity : 0.782
#> Pos Pred Value : 0.787
#> Neg Pred Value : 0.849
#> Prevalence : 0.486
#> Detection Rate : 0.414
#> Detection Prevalence : 0.527
#> Balanced Accuracy : 0.817
#>
#> 'Positive' Class : Malignant
#>
# ── Sajikan confusion matrix sebagai tabel ─────────────────────────────────────
cm_df <- data.frame(
` ` = c("Prediksi Benign (Jinak)", "Prediksi Malignant (Ganas)", "Total Aktual"),
`Aktual: Benign` = c(
paste0(cm$table[1,1], " (True Negative)"),
paste0(cm$table[2,1], " (False Positive)"),
cm$table[1,1] + cm$table[2,1]
),
`Aktual: Malignant` = c(
paste0(cm$table[1,2], " (False Negative)"),
paste0(cm$table[2,2], " (True Positive)"),
cm$table[1,2] + cm$table[2,2]
),
`Total Prediksi` = c(
cm$table[1,1] + cm$table[1,2],
cm$table[2,1] + cm$table[2,2],
nrow(mam)
),
check.names = FALSE
)
knitr::kable(cm_df,
caption = "Tabel 7. Confusion Matrix Klasifikasi Keganasan Model 1",
align = "c")| Aktual: Benign | Aktual: Malignant | Total Prediksi | |
|---|---|---|---|
| Prediksi Benign (Jinak) | 333 (True Negative) | 59 (False Negative) | 392 |
| Prediksi Malignant (Ganas) | 93 (False Positive) | 343 (True Positive) | 436 |
| Total Aktual | 426 | 402 | 828 |
Ringkasan Performa Klasifikasi Model 1
| Metrik | Nilai |
|---|---|
| Akurasi Keseluruhan | 81,64% (95% CI: 78,83% – 84,22%) |
| Sensitivitas (True Positive Rate) | 85,32% |
| Spesifisitas (True Negative Rate) | 78,17% |
| Kappa | 0,6333 (Substantial Agreement) |
| Hosmer-Lemeshow \(p\)-value | 0,9807 (Model Fit) |
Performa Klasifikasi Model (Confusion Matrix):
Meskipun tidak signifikan secara statistik, variabel Density tetap dipertahankan dalam model final dengan landasan ilmiah:
Density tetap dipertahankan (diretensi) di dalam model final meskipun tidak signifikan adalah karena kepatuhan terhadap Standar Internasional BI-RADS (Breast Imaging Reporting and Data System). Berdasarkan panduan resmi American College of Radiology (ACR), penilaian tingkat kepadatan (density) jaringan payudara merupakan komponen wajib dan mutlak yang harus dilaporkan dalam setiap pemeriksaan mamografi bersama dengan karakteristik Shape (bentuk) dan Margin (tepi). Mengeluarkan variabel ini hanya karena pertimbangan angka statistik (p-value) akan menyebabkan model menyalahi protokol standar klinis yang berlaku di dunia medis. Oleh karena itu, variabel Density tetap dimasukkan sebagai variabel kontrol wajib untuk menjaga validitas, objektivitas, dan pemenuhan standar regulasi medis yang utuh.
Hasil pengujian parsial Wald pada Model 1 menunjukkan bahwa variabel Age, karakteristik bentuk Shape Irregular, serta seluruh level pada karakteristik Margin memiliki kontribusi yang signifikan (p < 0,05) dalam membentuk probabilitas keganasan lesi payudara. Sebaliknya, karakteristik bentuk Shape Oval (p = 0,388) dan Shape Lobular (p = 0,1027) terbukti tidak memiliki perbedaan risiko yang signifikan jika dibandingkan dengan bentuk acuan bulat (Round).
Variabel Usia (Age) memperlihatkan hubungan positif linier yang kuat terhadap kecenderungan keganasan (p < 0,001; OR = 1,0564). Setiap peningkatan usia pasien sebesar satu tahun akan meningkatkan risiko keganasan lesi sebesar 1,056 kali lipat. Hal ini sejalan dengan data epidemiologi WHO [2] yang menyatakan bahwa pertambahan usia berkaitan erat dengan paparan kumulatif hormonal estrogen internal serta penurunan efektivitas mekanisme perbaikan kerusakan DNA seluler akibat penuaan jaringan stroma payudara [2, 1].
Bentuk massa tidak beraturan (Shape Irregular) memiliki nilai OR = 3,8753 terhadap bentuk bulat (Round). Hilangnya batas sferis simetris mengindikasikan karakteristik sel kanker yang invasif, di mana proliferasi sel terjadi dengan kecepatan tidak seragam dan mendesak jaringan parenkim payudara yang sehat di sekitarnya [1].
Indikator risiko tinggi ditunjukkan oleh tepi berduri (Margin Spiculated) dengan OR = 7,4398 terhadap tepi halus (Circumscribed). Secara patologi anatomi, visualisasi tepi berduri (spiculated margin) pada citra mammografi merupakan manifestasi langsung dari proses desmoplasia akibat infiltrasi untaian sel ganas yang menginvasi stroma payudara, menjadikannya penanda utama tumor ganas stadium lanjut secara radiologis [1].
Model terpilih adalah Model 1 (Density) sebagai skala kontinu terbukti sebagai spesifikasi terbaik berdasarkan prinsip parsimoni statistik. Model ini menghasilkan efisiensi parameter optimal dengan nilai AIC terendah (746,8935) dan akurasi model paling tinggi (81,64%).
Performa diagnostik andal: Model matematika yang dikembangkan memiliki tingkat sensitivitas tinggi mencapai 85,32%, yang berarti sangat handal dan aman dalam meminimalkan risiko lolosnya pasien kanker dari penegakan diagnosis awal (False Negative).
Prediktor keganasan utama:
Density tidak signifikan secara statistik namun dipertahankan sebagai kontrol klinis radiologis (masking effect, standar BI-RADS).
Model direkomendasikan sebagai instrumen pendukung keputusan klinis (DSS) untuk membantu radiolog meningkatkan efektivitas deteksi dini kanker payudara.
Suparna, K.; Sari, L.M.K.K.S. Kanker payudara: diagnostik, faktor risiko, dan stadium. Ganesha Medicina 2022, 2, 42–48. https://doi.org/10.23887/gm.v2i1.47032
World Health Organization. Breast cancer. Available online: https://www.who.int/news-room/fact-sheets/detail/breast-cancer (accessed on 14 August 2025).
Bodewes, F.T.H.; van Asselt, A.A.; Dorrius, M.D.; Greuter, M.J.W.; de Bock, G.H. Mammographic breast density and the risk of breast cancer: A systematic review and meta-analysis. The Breast 2022, 66, 279–289. https://doi.org/10.1016/j.breast.2022.09.007
Hosmer, D.W.; Lemeshow, S.; Sturdivant, R.X. Applied Logistic Regression, 3rd ed.; Wiley: New York, 2013.
UCI Machine Learning Repository — Mammographic Mass Dataset. https://archive.ics.uci.edu/dataset/161/mammographic+mass