Bootstrap adalah teknik resampling non-parametrik yang diperkenalkan oleh Efron (1979). Metode ini menjawab pertanyaan mendasar dalam statistika inferensial:
“Bagaimana kita dapat mengukur ketidakpastian sebuah estimasi statistik ketika kita hanya memiliki satu sampel dari populasi?”
Pada minggu ini, kita mempelajari bootstrap melalui dua latihan:
| Latihan | Tugas |
|---|---|
| 1 | Bangkitkan data normal (\(n=1000\), \(\mu=30\), \(\sigma=2.5\)), lalu hitung rata-rata dari 50 sampel bootstrap |
| 2 | Visualisasikan distribusi rata-rata bootstrap vs distribusi data asli dalam satu panel gabungan yang mewah |
Prinsip Bootstrap dapat diringkas sebagai berikut:
\[\hat{\theta}^* = T(X^*_1, X^*_2, \ldots, X^*_B)\]
di mana \(X^*_b\) adalah sampel bootstrap ke-\(b\) (diambil dengan pengembalian dari sampel asli \(X\)), dan \(\hat{\theta}^*\) adalah distribusi estimator yang dihasilkan.
Standard Error Bootstrap (estimasi ketidakpastian):
\[\widehat{\text{SE}}_B(\hat{\theta}) = \sqrt{\frac{1}{B-1} \sum_{b=1}^{B} \left(\hat{\theta}^*_b - \bar{\theta}^*\right)^2}\]
Perbandingan dengan SE Analitik untuk mean:
\[\text{SE}_{\text{analitik}}(\bar{X}) = \frac{\sigma}{\sqrt{n}} = \frac{2.5}{\sqrt{1000}} \approx 0.0791\]
Jika bootstrap berjalan dengan benar, \(\widehat{\text{SE}}_B\) akan mendekati nilai analitik ini.
Teorema Limit Pusat (CLT) memastikan bahwa distribusi bootstrap dari \(\bar{X}^*\) akan mendekati distribusi normal:
\[\bar{X}^* \xrightarrow{d} \mathcal{N}\!\left(\bar{X},\, \frac{s^2}{n}\right) \quad \text{ketika } B \to \infty\]
Bagian ini memuat semua paket yang diperlukan.
ggplot2untuk visualisasi berkualitas tinggi,dplyr&tidyruntuk manipulasi data,viridis&scalesuntuk palet warna yang aksesibel,patchworkuntuk menyusun multi-panel plot, danknitruntuk tabel yang rapi. Paket-paket ini adalah fondasi ekosistem tidyverse yang menjadi standar industri analisis data modern.
library(ggplot2)
library(dplyr)
library(tidyr)
library(viridis)
library(patchwork)
library(scales)
library(knitr)myData)Sebelum melakukan bootstrap, kita membutuhkan data populasi awal. Di sini dibangkitkan 1000 observasi dari distribusi normal dengan \(\mu = 30\) dan \(\sigma = 2.5\) menggunakan
rnorm(). Nilaiset.seed(150)memastikan reproduktibilitas penuh — siapapun yang menjalankan kode ini akan mendapatkan hasil yang identik.Penting untuk dipahami bahwa
rnorm()menghasilkan nilai acak di sekitar parameter yang diberikan — nilai mean dan SD sampel tidak akan persis sama dengan \(\mu\) dan \(\sigma\) populasi, melainkan mendekatinya seiring bertambahnya \(n\) (Hukum Bilangan Besar).
# ── Parameter ────────────────────────────────────────────────
set.seed(150) # Seed untuk reproduktibilitas
N_OBS <- 1000 # Jumlah observasi
MU <- 30 # Rata-rata populasi
SIGMA <- 2.5 # Standar deviasi populasi
# ── Bangkitkan data ──────────────────────────────────────────
myData <- rnorm(N_OBS, mean = MU, sd = SIGMA)Tiga pemeriksaan awal (sanity checks) dilakukan untuk memvalidasi data yang baru dibangkitkan. Kita berharap: jumlah observasi = 1000, mean ≈ 30, dan SD ≈ 2.5. Penyimpangan kecil adalah hal normal dan mencerminkan variasi sampling alami.
# ── Sanity checks ────────────────────────────────────────────
cat("Jumlah observasi :", length(myData), "\n")## Jumlah observasi : 1000
## Mean sampel : 29.92068
## Std. Dev. sampel : 2.47517
## Min : 22.2416
## Max : 36.7845
# ── Tabel ringkasan ──────────────────────────────────────────
data.frame(
Statistik = c("Jumlah Observasi", "Mean", "Std. Deviasi",
"Min", "Q1", "Median", "Q3", "Max"),
`Nilai Sampel` = round(c(
length(myData), mean(myData), sd(myData),
quantile(myData, 0)[[1]], quantile(myData, 0.25)[[1]],
median(myData), quantile(myData, 0.75)[[1]],
quantile(myData, 1)[[1]]
), 4),
`Nilai Teoritis` = c(1000, 30, 2.5, NA, NA, 30, NA, NA),
check.names = FALSE
) %>%
kable(align = "c",
caption = "Ringkasan Statistik myData vs Parameter Populasi Teoritis")| Statistik | Nilai Sampel | Nilai Teoritis |
|---|---|---|
| Jumlah Observasi | 1000.0000 | 1000.0 |
| Mean | 29.9207 | 30.0 |
| Std. Deviasi | 2.4752 | 2.5 |
| Min | 22.2416 | NA |
| Q1 | 28.2559 | NA |
| Median | 29.8430 | 30.0 |
| Q3 | 31.6312 | NA |
| Max | 36.7845 | NA |
Histogram berikut memvisualisasikan distribusi
myData. Tiga elemen kunci ditampilkan: (1) kurva densitas empiris (biru), (2) kurva distribusi normal teoritis \(\mathcal{N}(30, 2.5^2)\) (oranye putus-putus) sebagai pembanding, dan (3) garis vertikal mean sampel. Kedekatan antara kurva empiris dan teoritis mengkonfirmasi data terbangkit dengan benar.
ggplot(data.frame(x = myData), aes(x = x)) +
geom_histogram(
aes(y = ..density..),
bins = 40,
fill = "#3949ab", color = "white", alpha = 0.75
) +
geom_density(color = "#1565c0", size = 1.2, linetype = "solid") +
stat_function(
fun = dnorm, args = list(mean = MU, sd = SIGMA),
color = "#e65100", size = 1.1, linetype = "dashed"
) +
geom_vline(
xintercept = mean(myData),
color = "#c62828", size = 1.2, linetype = "solid"
) +
annotate(
"text",
x = mean(myData) + 0.25,
y = Inf, vjust = 2, hjust = 0,
label = paste0("Mean = ", round(mean(myData), 4)),
color = "#c62828", fontface = "bold", size = 4
) +
scale_x_continuous(breaks = pretty_breaks(8)) +
labs(
title = "Distribusi Data Asli — myData",
subtitle = paste0(
"N = ", N_OBS, " | μ = ", MU, " | σ = ", SIGMA,
" | Garis merah = Mean sampel | Kurva oranye = N(30, 2.5²) teoritis"
),
x = "Nilai Observasi",
y = "Densitas"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", color = "#1a237e", size = 14),
plot.subtitle = element_text(color = "#5c6bc0", size = 9.5)
)Latihan 1 meminta kita melakukan bootstrap dengan 50 ulangan (\(B = 50\)), di mana setiap ulangan mengambil sampel berukuran 1000 dari
myDatadengan pengembalian (replace = TRUE), lalu menghitung mean-nya. Hasilnya disimpan dalam vektorbootstrap.results.Kunci konseptual: pengambilan dengan pengembalian berarti satu observasi bisa terpilih berkali-kali dalam satu sampel bootstrap, yang menciptakan variasi antar sampel meski semua berasal dari data yang sama. Inilah yang memungkinkan kita mensimulasikan variabilitas sampling tanpa perlu data baru.
# ── Parameter bootstrap ──────────────────────────────────────
set.seed(150) # Seed identik dengan soal
sample.size <- N_OBS # Ukuran tiap sampel bootstrap = 1000
n.samples <- 50 # Jumlah ulangan bootstrap (B = 50)
bootstrap.results <- numeric(n.samples) # Vektor penyimpan hasil
# ── Loop bootstrap ───────────────────────────────────────────
for (i in 1:n.samples) {
# Ambil indeks acak DENGAN PENGEMBALIAN
obs <- sample(seq_len(sample.size), size = sample.size, replace = TRUE)
# Hitung mean dari sub-sampel bootstrap ke-i
bootstrap.results[i] <- mean(myData[obs])
}Pemeriksaan panjang vektor memastikan 50 nilai mean tersimpan. Standar deviasi dari
bootstrap.resultsadalah Standard Error Bootstrap — estimasi empiris dari ketidakpastian mean. Nilai ini kemudian kita bandingkan dengan SE analitik \(\sigma/\sqrt{n}\).
# ── Sanity check ─────────────────────────────────────────────
cat("Jumlah sampel bootstrap :", length(bootstrap.results), "\n")## Jumlah sampel bootstrap : 50
## Mean dari bootstrap means: 29.92612
## SE Bootstrap (SD means) : 0.07028
## SE Analitik (σ/√n) : 0.07906
# ── Tabel perbandingan ────────────────────────────────────────
data.frame(
Statistik = c("B (jumlah ulangan)", "Mean bootstrap",
"SD bootstrap (≈ SE)", "SE analitik σ/√n",
"Selisih absolut SE", "Min mean", "Max mean", "Rentang mean"),
Nilai = round(c(
length(bootstrap.results),
mean(bootstrap.results),
sd(bootstrap.results),
SIGMA / sqrt(N_OBS),
abs(sd(bootstrap.results) - SIGMA / sqrt(N_OBS)),
min(bootstrap.results),
max(bootstrap.results),
max(bootstrap.results) - min(bootstrap.results)
), 5),
check.names = FALSE
) %>%
kable(align = "c",
caption = paste0(
"Ringkasan Distribusi Bootstrap Mean (B = ", n.samples,
", n = ", sample.size, ")"
))| Statistik | Nilai |
|---|---|
| B (jumlah ulangan) | 50.00000 |
| Mean bootstrap | 29.92612 |
| SD bootstrap (≈ SE) | 0.07028 |
| SE analitik σ/√n | 0.07906 |
| Selisih absolut SE | 0.00878 |
| Min mean | 29.73110 |
| Max mean | 30.07557 |
| Rentang mean | 0.34447 |
Tabel berikut menampilkan seluruh 50 nilai mean yang diperoleh dari 50 sampel bootstrap. Fluktuasi kecil antar nilai mencerminkan variasi sampling alami — inilah inti dari apa yang diestimasi oleh bootstrap.
data.frame(
`Sampel ke-` = 1:n.samples,
`Mean Bootstrap` = round(bootstrap.results, 5),
`Deviasi dari Grand Mean` = round(bootstrap.results - mean(bootstrap.results), 5),
check.names = FALSE
) %>%
kable(align = "c",
caption = "Seluruh 50 Nilai Mean Bootstrap")| Sampel ke- | Mean Bootstrap | Deviasi dari Grand Mean |
|---|---|---|
| 1 | 29.88877 | -0.03735 |
| 2 | 29.95125 | 0.02514 |
| 3 | 29.91789 | -0.00823 |
| 4 | 29.91696 | -0.00916 |
| 5 | 29.90099 | -0.02513 |
| 6 | 29.83304 | -0.09308 |
| 7 | 29.85498 | -0.07114 |
| 8 | 29.95973 | 0.03361 |
| 9 | 29.86083 | -0.06529 |
| 10 | 30.03870 | 0.11258 |
| 11 | 29.84256 | -0.08356 |
| 12 | 29.91217 | -0.01395 |
| 13 | 29.87484 | -0.05128 |
| 14 | 29.90375 | -0.02237 |
| 15 | 29.94881 | 0.02269 |
| 16 | 29.87566 | -0.05045 |
| 17 | 29.89902 | -0.02710 |
| 18 | 29.87050 | -0.05562 |
| 19 | 29.91005 | -0.01607 |
| 20 | 29.88778 | -0.03834 |
| 21 | 30.00650 | 0.08038 |
| 22 | 29.92184 | -0.00428 |
| 23 | 29.98550 | 0.05939 |
| 24 | 29.73110 | -0.19502 |
| 25 | 29.96483 | 0.03871 |
| 26 | 29.91879 | -0.00733 |
| 27 | 29.85054 | -0.07558 |
| 28 | 30.02939 | 0.10327 |
| 29 | 29.95772 | 0.03160 |
| 30 | 30.06538 | 0.13926 |
| 31 | 29.98891 | 0.06280 |
| 32 | 29.95251 | 0.02639 |
| 33 | 29.79974 | -0.12638 |
| 34 | 29.93682 | 0.01071 |
| 35 | 29.91790 | -0.00822 |
| 36 | 29.98175 | 0.05563 |
| 37 | 29.98773 | 0.06161 |
| 38 | 29.91802 | -0.00809 |
| 39 | 29.83295 | -0.09316 |
| 40 | 29.99206 | 0.06594 |
| 41 | 29.91268 | -0.01344 |
| 42 | 30.00392 | 0.07780 |
| 43 | 29.90862 | -0.01750 |
| 44 | 30.00265 | 0.07653 |
| 45 | 30.07557 | 0.14945 |
| 46 | 29.92908 | 0.00296 |
| 47 | 29.88484 | -0.04128 |
| 48 | 30.02723 | 0.10112 |
| 49 | 29.96257 | 0.03646 |
| 50 | 29.81047 | -0.11564 |
Latihan 2 meminta dua histogram: satu untuk distribusi mean bootstrap, satu untuk data asli, digabung dalam satu panel. Di sini kita melampaui permintaan minimal dengan membangun visualisasi multi-layer yang kaya informasi menggunakan
ggplot2danpatchwork.
Histogram ini menampilkan distribusi dari 50 mean bootstrap. Sesuai CLT, distribusi ini seharusnya mendekati normal meskipun \(B\) hanya 50. Garis merah adalah grand mean bootstrap, sementara area berbayang menunjukkan rentang ±1 SE.
df_boot <- data.frame(mean_bs = bootstrap.results)
grand_mean_bs <- mean(bootstrap.results)
se_bs <- sd(bootstrap.results)
pA <- ggplot(df_boot, aes(x = mean_bs)) +
# Rentang ±1 SE
annotate(
"rect",
xmin = grand_mean_bs - se_bs,
xmax = grand_mean_bs + se_bs,
ymin = -Inf, ymax = Inf,
alpha = 0.12, fill = "#c62828"
) +
geom_histogram(
aes(y = ..density..),
bins = 15,
fill = "#c62828", color = "white", alpha = 0.80
) +
geom_density(color = "#7b1fa2", size = 1.2) +
geom_vline(
xintercept = grand_mean_bs,
color = "#880e4f", size = 1.2, linetype = "dashed"
) +
annotate(
"text",
x = grand_mean_bs + 0.008, y = Inf,
vjust = 2, hjust = 0,
label = paste0("Grand Mean = ", round(grand_mean_bs, 4),
"\nSE = ", round(se_bs, 5)),
color = "#880e4f", fontface = "bold", size = 3.8
) +
scale_x_continuous(breaks = pretty_breaks(6)) +
labs(
title = "Plot A — Distribusi Mean Bootstrap",
subtitle = paste0("B = ", n.samples, " ulangan | Area merah = ±1 SE | ",
"SE analitik σ/√n = ", round(SIGMA/sqrt(N_OBS), 5)),
x = "Nilai Mean Bootstrap",
y = "Densitas"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", color = "#880e4f", size = 13),
plot.subtitle = element_text(color = "#ad1457", size = 9)
)Untuk perbandingan, histogram data asli
myDataditampilkan dengan skala densitas yang sama. Perhatikan perbedaan rentang sumbu-X yang dramatis: data asli tersebar dari ~20 hingga ~40, sedangkan distribusi mean bootstrap sangat terkonsentrasi di sekitar 30. Inilah visualisasi langsung dari Standard Error vs Standard Deviation.
df_raw <- data.frame(nilai = myData)
pB <- ggplot(df_raw, aes(x = nilai)) +
geom_histogram(
aes(y = ..density..),
bins = 40,
fill = "#1565c0", color = "white", alpha = 0.80
) +
geom_density(color = "#0d47a1", size = 1.2) +
stat_function(
fun = dnorm, args = list(mean = MU, sd = SIGMA),
color = "#e65100", size = 1.1, linetype = "dashed"
) +
geom_vline(
xintercept = mean(myData),
color = "#1a237e", size = 1.2, linetype = "dashed"
) +
annotate(
"text",
x = mean(myData) + 0.15, y = Inf,
vjust = 2, hjust = 0,
label = paste0("Mean = ", round(mean(myData), 4),
"\nSD = ", round(sd(myData), 4)),
color = "#1a237e", fontface = "bold", size = 3.8
) +
scale_x_continuous(breaks = pretty_breaks(8)) +
labs(
title = "Plot B — Distribusi Data Asli (myData)",
subtitle = paste0("N = ", N_OBS, " | Kurva oranye = N(",
MU, ", ", SIGMA, "²) teoritis"),
x = "Nilai Observasi",
y = "Densitas"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", color = "#1565c0", size = 13),
plot.subtitle = element_text(color = "#1976d2", size = 9)
)Ini adalah panel gabungan yang diminta Latihan 2, dibangun dengan
patchwork. Susunan vertikal (dua baris) memudahkan perbandingan langsung antara distribusi mean bootstrap (atas) dan distribusi data asli (bawah). Perhatikan perbedaan skala sumbu-X yang sangat kontras — ini secara visual mendemonstrasikan bahwa SE \((\approx 0.08)\) jauh lebih kecil dari SD \((= 2.5)\).
panel_latihan2 <- pA / pB +
plot_annotation(
title = "LATIHAN 2 — Bootstrap vs Distribusi Asli",
subtitle = paste0(
"Atas: Distribusi mean dari ", n.samples,
" sampel bootstrap · Bawah: Distribusi data asli (N = ", N_OBS, ")"
),
caption = paste0(
"Muhammad Dzaky Pratama — 2404010021 | set.seed(150) | ",
"μ = ", MU, ", σ = ", SIGMA
),
theme = theme(
plot.title = element_text(size = 16, face = "bold",
color = "#0d0d2b", hjust = 0.5),
plot.subtitle = element_text(size = 10, color = "#3e3e5e", hjust = 0.5),
plot.caption = element_text(color = "grey55", size = 8.5, hjust = 0.5)
)
)
panel_latihan2Salah satu pertanyaan teoritis penting adalah: bagaimana akurasi SE Bootstrap meningkat seiring bertambahnya \(B\)? Blok ini mensimulasikan SE bootstrap untuk berbagai nilai \(B\) (dari 10 hingga 500) dan membandingkannya dengan nilai analitik. Ini adalah demonstrasi visual dari konvergensi metode bootstrap.
set.seed(42)
B_grid <- c(10, 25, 50, 100, 200, 300, 500)
se_hasil <- numeric(length(B_grid))
for (j in seq_along(B_grid)) {
B_j <- B_grid[j]
boot_j <- numeric(B_j)
for (i in 1:B_j) {
obs <- sample(seq_len(N_OBS), size = N_OBS, replace = TRUE)
boot_j[i] <- mean(myData[obs])
}
se_hasil[j] <- sd(boot_j)
}
se_analitik <- SIGMA / sqrt(N_OBS)
df_conv <- data.frame(B = B_grid, SE_bootstrap = se_hasil)
ggplot(df_conv, aes(x = B, y = SE_bootstrap)) +
geom_hline(
yintercept = se_analitik,
color = "#e65100", size = 1.1, linetype = "dashed"
) +
geom_ribbon(
aes(ymin = se_analitik * 0.85, ymax = se_analitik * 1.15),
fill = "#e65100", alpha = 0.08
) +
geom_line(color = "#1a237e", size = 1.3) +
geom_point(
fill = "#3949ab", color = "white",
size = 4, shape = 21, stroke = 2
) +
geom_text(
aes(label = round(SE_bootstrap, 4)),
vjust = -1.1, size = 3.2, color = "#1a237e", fontface = "bold"
) +
annotate(
"text",
x = max(B_grid) * 0.75, y = se_analitik + 0.0008,
label = paste0("SE Analitik σ/√n = ", round(se_analitik, 5)),
color = "#e65100", fontface = "bold", size = 4
) +
scale_x_continuous(breaks = B_grid) +
labs(
title = "Konvergensi SE Bootstrap terhadap SE Analitik",
subtitle = paste0("Semakin besar B → SE Bootstrap mendekati σ/√n = ",
round(se_analitik, 5),
" | Area oranye = toleransi ±15%"),
x = "Jumlah Ulangan Bootstrap (B)",
y = "Standard Error Bootstrap"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", color = "#1a237e", size = 13),
plot.subtitle = element_text(color = "#5c6bc0", size = 9.5)
)Interval Kepercayaan Bootstrap (metode persentil) diperoleh langsung dari distribusi empiris estimator bootstrap — tidak memerlukan asumsi distribusi apapun. Untuk CI 95%, kita ambil persentil ke-2.5 dan ke-97.5 dari
bootstrap.results. Ini merupakan keunggulan utama bootstrap dibanding metode parametrik.Secara formal: \(\text{CI}_{95\%} = [\hat{\theta}^*_{(0.025)},\; \hat{\theta}^*_{(0.975)}]\)
# ── CI Bootstrap: Metode Persentil ───────────────────────────
ci_lower <- quantile(bootstrap.results, 0.025)
ci_upper <- quantile(bootstrap.results, 0.975)
ci_width <- ci_upper - ci_lower
# ── CI Analitik (z) untuk perbandingan ───────────────────────
se_anal <- SIGMA / sqrt(N_OBS)
ci_anal_lo <- mean(myData) - qnorm(0.975) * se_anal
ci_anal_hi <- mean(myData) + qnorm(0.975) * se_anal
cat("── Interval Kepercayaan 95% ──────────────────────────────\n")## ── Interval Kepercayaan 95% ──────────────────────────────
## Bootstrap (persentil): [29.80215, 30.05937] | lebar = 0.25722
cat(sprintf("Analitik (z) : [%.5f, %.5f] | lebar = %.5f\n",
ci_anal_lo, ci_anal_hi, ci_anal_hi - ci_anal_lo))## Analitik (z) : [29.76573, 30.07563] | lebar = 0.30990
# ── Tabel perbandingan ────────────────────────────────────────
data.frame(
Metode = c("Bootstrap (Persentil)", "Analitik (z-interval)"),
`Batas Bawah` = round(c(ci_lower, ci_anal_lo), 5),
`Batas Atas` = round(c(ci_upper, ci_anal_hi), 5),
`Lebar CI` = round(c(ci_width, ci_anal_hi - ci_anal_lo), 5),
check.names = FALSE
) %>%
kable(align = "c",
caption = "Perbandingan CI 95% — Bootstrap Persentil vs Analitik (z)")| Metode | Batas Bawah | Batas Atas | Lebar CI | |
|---|---|---|---|---|
| 2.5% | Bootstrap (Persentil) | 29.80215 | 30.05937 | 0.25722 |
| Analitik (z-interval) | 29.76573 | 30.07563 | 0.30990 |
Grafik berikut menampilkan secara visual posisi dan lebar CI Bootstrap (merah) dan CI Analitik (biru) pada distribusi mean bootstrap. Kedekatan keduanya mengkonfirmasi keakuratan metode bootstrap meskipun hanya dengan \(B = 50\) ulangan.
df_boot2 <- data.frame(mean_bs = bootstrap.results)
ggplot(df_boot2, aes(x = mean_bs)) +
# CI Bootstrap
annotate("rect",
xmin = ci_lower, xmax = ci_upper,
ymin = -Inf, ymax = Inf,
fill = "#c62828", alpha = 0.15) +
# CI Analitik
annotate("rect",
xmin = ci_anal_lo, xmax = ci_anal_hi,
ymin = -Inf, ymax = Inf,
fill = "#1565c0", alpha = 0.15) +
geom_histogram(
aes(y = ..density..),
bins = 15, fill = "#ff8f00", color = "white", alpha = 0.75
) +
geom_density(color = "#e65100", size = 1.2) +
geom_vline(xintercept = ci_lower, color = "#c62828", linetype = "dashed", size = 0.9) +
geom_vline(xintercept = ci_upper, color = "#c62828", linetype = "dashed", size = 0.9) +
geom_vline(xintercept = ci_anal_lo, color = "#1565c0", linetype = "dotted", size = 0.9) +
geom_vline(xintercept = ci_anal_hi, color = "#1565c0", linetype = "dotted", size = 0.9) +
geom_vline(xintercept = mean(bootstrap.results),
color = "#880e4f", size = 1.3) +
annotate("text", x = ci_lower - 0.003, y = Inf, vjust = 2, hjust = 1,
label = paste0("Boot\n", round(ci_lower, 3)),
color = "#c62828", fontface = "bold", size = 3.2) +
annotate("text", x = ci_upper + 0.003, y = Inf, vjust = 2, hjust = 0,
label = paste0("Boot\n", round(ci_upper, 3)),
color = "#c62828", fontface = "bold", size = 3.2) +
labs(
title = "CI 95%: Bootstrap Persentil (merah) vs Analitik-z (biru)",
subtitle = "Garis merah putus = batas CI Bootstrap | Garis biru titik = batas CI Analitik",
x = "Mean Bootstrap", y = "Densitas"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", color = "#1a237e", size = 13),
plot.subtitle = element_text(color = "#5c6bc0", size = 9.5)
)Dotplot di bawah menampilkan posisi setiap mean bootstrap secara individual, diurutkan, sehingga mudah melihat pola sebaran. Titik berwarna di zona hijau berada dalam rentang ±1 SE dari grand mean, sedangkan titik di zona merah adalah outlier relatif.
df_dot <- data.frame(
idx = 1:n.samples,
mean_bs = sort(bootstrap.results),
zona = ifelse(
abs(sort(bootstrap.results) - grand_mean_bs) <= se_bs,
"Dalam ±1 SE", "Di luar ±1 SE"
)
)
ggplot(df_dot, aes(x = idx, y = mean_bs, color = zona)) +
geom_hline(yintercept = grand_mean_bs,
color = "#1a237e", linetype = "dashed", size = 0.8) +
geom_hline(yintercept = grand_mean_bs + se_bs,
color = "#c62828", linetype = "dotted", size = 0.7) +
geom_hline(yintercept = grand_mean_bs - se_bs,
color = "#c62828", linetype = "dotted", size = 0.7) +
geom_segment(aes(xend = idx, yend = grand_mean_bs),
color = "grey80", size = 0.5) +
geom_point(size = 3.5, shape = 21, fill = "white", stroke = 2.5) +
scale_color_manual(
values = c("Dalam ±1 SE" = "#2e7d32", "Di luar ±1 SE" = "#c62828"),
name = "Zona"
) +
annotate("text", x = 1, y = grand_mean_bs + 0.001, vjust = -0.5,
label = paste0("Grand Mean = ", round(grand_mean_bs, 4)),
color = "#1a237e", fontface = "bold", size = 3.5, hjust = 0) +
labs(
title = "Dotplot — 50 Mean Bootstrap (Diurutkan)",
subtitle = "Garis putus biru = grand mean | Garis titik merah = ±1 SE",
x = "Peringkat (1 = terkecil)",
y = "Nilai Mean Bootstrap"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", color = "#1a237e", size = 13),
plot.subtitle = element_text(color = "#5c6bc0", size = 9.5),
legend.position = "top"
)Panel komprehensif ini menggabungkan empat perspektif dalam satu tampilan: distribusi data asli, distribusi mean bootstrap, plot konvergensi SE, dan dotplot individual. Panel ini dirancang sebagai ringkasan satu halaman yang dapat digunakan dalam laporan atau presentasi.
# ── Rebuild konvergensi plot untuk panel ─────────────────────
p_conv <- ggplot(df_conv, aes(x = B, y = SE_bootstrap)) +
geom_hline(yintercept = se_analitik, color = "#e65100",
size = 1.1, linetype = "dashed") +
geom_ribbon(aes(ymin = se_analitik * 0.85, ymax = se_analitik * 1.15),
fill = "#e65100", alpha = 0.08) +
geom_line(color = "#1a237e", size = 1.2) +
geom_point(fill = "#3949ab", color = "white", size = 4, shape = 21, stroke = 2) +
scale_x_continuous(breaks = B_grid) +
labs(title = "Konvergensi SE Bootstrap",
subtitle = paste0("Garis oranye = SE analitik σ/√n = ",
round(se_analitik, 5)),
x = "B (jumlah ulangan)", y = "SE Bootstrap") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#1a237e"))
# ── Panel gabungan ────────────────────────────────────────────
panel_besar <- (pB | pA) /
(p_conv | ggplot(df_dot, aes(x = idx, y = mean_bs, color = zona)) +
geom_hline(yintercept = grand_mean_bs, color = "#1a237e",
linetype = "dashed", size = 0.8) +
geom_segment(aes(xend = idx, yend = grand_mean_bs),
color = "grey80") +
geom_point(size = 3, shape = 21, fill = "white", stroke = 2) +
scale_color_manual(
values = c("Dalam ±1 SE" = "#2e7d32", "Di luar ±1 SE" = "#c62828"),
name = "Zona") +
labs(title = "Dotplot 50 Mean Bootstrap",
x = "Peringkat", y = "Mean Bootstrap") +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", color = "#1a237e"),
legend.position = "top")
) +
plot_annotation(
title = "ANALISIS BOOTSTRAP LENGKAP — WEEK 6",
subtitle = paste0(
"Data: N(μ=30, σ=2.5), n=1000 · ",
"Bootstrap: B=50 sampel dengan pengembalian · ",
"set.seed(150)"
),
caption = paste0(
"Muhammad Dzaky Pratama — 2404010021 | ",
"SE Bootstrap = ", round(sd(bootstrap.results), 5),
" | SE Analitik = ", round(se_analitik, 5)
),
theme = theme(
plot.title = element_text(size = 18, face = "bold",
color = "#0d0d2b", hjust = 0.5),
plot.subtitle = element_text(size = 10, color = "#3e3e5e", hjust = 0.5),
plot.caption = element_text(color = "grey55", size = 9, hjust = 0.5)
)
)
panel_besar| Item | Nilai |
|---|---|
| Ukuran data asli (N) | 1000.00000 |
| Parameter populasi μ | 30.00000 |
| Parameter populasi σ | 2.50000 |
| Mean sampel myData | 29.92068 |
| SD sampel myData | 2.47517 |
| Jumlah sampel bootstrap (B) | 50.00000 |
| Ukuran tiap sampel bootstrap | 1000.00000 |
| Grand Mean bootstrap | 29.92612 |
| SE Bootstrap (SD of means) | 0.07028 |
| SE Analitik (σ/√n) | 0.07906 |
| Selisih SE absolut | 0.00878 |
| CI 95% batas bawah (Bootstrap) | 29.80215 |
| CI 95% batas atas (Bootstrap) | 30.05937 |
1. Mengapa menggunakan pengembalian (replacement)?
Tanpa pengembalian, setiap sampel bootstrap akan identik dengan data asli dan tidak ada variasi yang dapat diukur. Dengan pengembalian, setiap sampel ke-\(b\) merupakan realisasi berbeda dari “distribusi empiris” \(\hat{F}\), sehingga menghasilkan variasi antar \(\hat{\theta}^*_b\) yang mencerminkan ketidakpastian estimasi.
2. Mengapa \(B = 50\) cukup untuk estimasi mean?
Karena mean adalah estimator smooth dengan varians yang kecil, 50 ulangan sudah cukup stabil. Untuk estimator yang lebih tidak stabil (misal kuantil ekstrem, koefisien regresi), \(B \geq 1000\) lebih disarankan.
3. Interpretasi SE Bootstrap:
\[\widehat{\text{SE}}_{\text{bootstrap}} = 0.07028 \approx \frac{\sigma}{\sqrt{n}} = \frac{2.5}{\sqrt{1000}} = 0.07906\]
Perbedaan sebesar 0.00878 adalah akibat variasi sampling dalam bootstrap itu sendiri — yang akan semakin kecil seiring bertambahnya \(B\).
4. Perbandingan SD vs SE:
| Statistik | Nilai | Makna |
|---|---|---|
| SD data asli | 2.4752 | Variabilitas antar individu dalam populasi |
| SE bootstrap | 0.07028 | Variabilitas estimasi mean antar sampel |
| Rasio SD/SE | 35.2 | Sesuai teori: ≈ √1000 ≈ 31.6 |
Rasio ini membuktikan secara empiris bahwa \(\text{SE}(\bar{X}) = \text{SD}(X) / \sqrt{n}\).
| # | Temuan | Implikasi |
|---|---|---|
| 1 | Bootstrap mean ≈ mean populasi | Estimator mean tidak bias |
| 2 | SE Bootstrap ≈ SE Analitik | Bootstrap valid untuk estimasi ketidakpastian |
| 3 | Distribusi mean bootstrap → Normal | CLT berlaku meskipun \(B\) kecil |
| 4 | SE meningkat akurasinya seiring \(B\) | Untuk presisi tinggi, gunakan \(B \geq 500\) |
| 5 | CI Bootstrap ≈ CI Analitik | Bootstrap dapat menggantikan asumsi distribusi |
Simpulan: Bootstrap adalah metode yang sangat fleksibel karena tidak memerlukan asumsi distribusi parametrik. Dengan hanya menggunakan data yang ada dan prinsip resampling sederhana, kita dapat membangun estimasi ketidakpastian yang valid untuk hampir semua jenis estimator statistik.