set.seed(123) # Untuk reproduktibilitas
n <- 10000
x <- runif(n, 0, 1)
ex2 <- mean(x^2)
ex2
## [1] 0.3297405
Dengan menggunakan simulasi sebanyak 10.000 angka acak dari distribusi Uniform \(X \sim U(0,1)\), diperoleh nilai pendekatan untuk \(E[X^2]\) sebesar 0.3297.
Hasil ini mendekati nilai eksak \(E[X^2] = \int_0^1 x^2 \, dx = \frac{1}{3} \approx 0.3333\), sehingga simulasi dapat dikatakan cukup akurat dalam memperkirakan nilai harapan kuadrat dari variabel acak uniform.
par(mfrow=c(1,3))
# Untuk n1 = 100
n1 <- 100
x1 <- rnorm(n1, mean=100, sd=15)
mean1 <- mean(x1)
sd1 <- sd(x1)
hist(x1, main=paste("n =", n1), xlab="Nilai", col="lightgreen")
# Untuk n2 = 1000
n2 <- 1000
x2 <- rnorm(n2, mean=100, sd=15)
mean2 <- mean(x2)
sd2 <- sd(x2)
hist(x2, main=paste("n =", n2), xlab="Nilai", col="red")
# Untuk n3 = 10000
n3 <- 10000
x3 <- rnorm(n3, mean=100, sd=15)
mean3 <- mean(x3)
sd3 <- sd(x3)
hist(x3, main=paste("n =", n3), xlab="Nilai", col="skyblue")
data.frame(
n = c(n1, n2, n3),
Mean = c(mean1, mean2, mean3),
SD = c(sd1, sd2, sd3)
)
## n Mean SD
## 1 100 101.0205 13.34109
## 2 1000 100.4924 14.92385
## 3 10000 99.9738 15.04648
Berdasarkan simulasi data acak dari distribusi normal \(N(100, 15^2)\) dengan ukuran sampel \(n = 100\), \(n = 1000\), dan \(n = 10000\), diperoleh nilai rata-rata (mean) dan simpangan baku (standar deviasi) yang semakin mendekati parameter populasi seiring bertambahnya ukuran sampel. Nilai mean untuk masing-masing sampel adalah 99.80, 99.77, dan 100.32, sementara nilai standar deviasi masing-masing adalah 15.25, 14.93, dan 15.15. Hal ini menunjukkan bahwa semakin besar ukuran sampel, hasil simulasi semakin stabil dan mendekati nilai teoritis, yaitu mean 100 dan standar deviasi 15, sesuai dengan sifat hukum bilangan besar (law of large numbers).
set.seed(123)
n_sim <- 5000
x_binom <- rbinom(n_sim, size=30, prob=0.25)
prob_estimate <- mean(x_binom >= 15)
prob_estimate
## [1] 0.0024
Dari simulasi distribusi binomial \(\text{Bin}(30, 0.25)\) sebanyak 5000 kali, diperoleh estimasi peluang \(P(X \geq 15)\) sebesar 0.0024. Nilai ini menunjukkan bahwa kejadian munculnya 15 atau lebih keberhasilan dari 30 percobaan dengan probabilitas sukses 0.25 tergolong sangat kecil atau jarang terjadi. Hasil ini sesuai dengan ekspektasi secara teoritis karena nilai 15 berada jauh di sisi kanan dari nilai harapan distribusi, yaitu \(\mu = np = 30 \times 0.25 = 7.5\).
calculate_s <- function(x) {
n <- length(x)
sqrt((sum(x^2) - (sum(x)^2)/n)/(n-1))
}
set.seed(123)
data <- rnorm(1000, mean=89, sd=10)
s_value <- calculate_s(data)
s_value
## [1] 9.91695
Dengan menggunakan fungsi rumus standar deviasi \(s = \sqrt{\frac{\sum x^2 - \frac{(\sum x)^2}{n}}{n - 1}}\) dan data acak sebanyak 1000 dari distribusi normal dengan rata-rata 89 dan standar deviasi 10, diperoleh hasil sebesar 9.91695. Nilai ini sangat mendekati nilai teoritis standar deviasi populasi, yaitu 10. Hal ini menunjukkan bahwa rumus tersebut telah diimplementasikan dengan benar dan menghasilkan estimasi yang akurat dari data yang dihasilkan secara acak.
#(a) Buatlah simulasi antrian selama 20 hari kerja
library(queuecomputer)
library(ggplot2)
library(simmer)
## Soal 5 (a): Simulasi Antrian 20 Hari Kerja di Bank
set.seed(123)
# Parameter dasar
arrival_rate <- 5 / 60 # 5 nasabah per jam = 1 nasabah per 12 menit
service_rate <- 1 / 8 # rata-rata waktu pelayanan = 8 menit
total_minutes <- 6 * 60 # 6 jam kerja per hari
n_tellers <- 2 # jumlah teller
simulation_days <- 20 # simulasi selama 20 hari
# Fungsi pemrosesan antrian (FIFO)
queue_step <- function(arrivals, service_times, servers) {
n <- length(arrivals)
end_times <- numeric(n)
server_available <- rep(0, servers)
for (i in 1:n) {
start_time <- max(arrivals[i], min(server_available))
server <- which.min(server_available)
end_times[i] <- start_time + service_times[i]
server_available[server] <- end_times[i]
}
return(end_times)
}
# Fungsi simulasi untuk 1 hari
simulate_day <- function(day) {
# 1. Generate waktu kedatangan antar nasabah
interarrivals <- rexp(ceiling(arrival_rate * total_minutes * 1.5), arrival_rate)
arrivals <- cumsum(interarrivals)
arrivals <- arrivals[arrivals <= total_minutes]
# 2. Generate waktu pelayanan
service_times <- rexp(length(arrivals), service_rate)
# 3. Proses antrian dengan queue_step()
queue_sim <- queue_step(arrivals, service_times, servers = n_tellers)
# 4. Buat data frame hasil simulasi
departures <- data.frame(
arrivals = arrivals,
service = service_times,
departures = queue_sim,
waiting = queue_sim - arrivals - service_times
)
# 5. Hitung panjang antrian setiap menit
time_points <- seq(0, total_minutes, by = 1)
queue_length <- sapply(time_points, function(t) {
sum(arrivals <= t & queue_sim > t)
})
return(list(
day = day,
n_customers = length(arrivals),
departures = departures,
queue_length = queue_length,
time_points = time_points
))
}
# Jalankan simulasi untuk 20 hari
results <- lapply(1:simulation_days, simulate_day)
# Contoh: tampilkan jumlah nasabah per hari
sapply(results, function(res) res$n_customers)
## [1] 31 33 27 23 34 30 25 29 31 29 21 26 26 31 30 34 26 22 30 28
Simulasi dilakukan selama 20 hari kerja, dengan masing-masing hari berlangsung 6 jam dan melibatkan 2 teller. Nasabah datang mengikuti distribusi eksponensial dengan rata-rata 12 menit antar kedatangan (5 nasabah per jam), dan waktu pelayanan mengikuti distribusi eksponensial dengan rata-rata 8 menit. Simulasi mencatat waktu kedatangan, pelayanan, dan keberangkatan nasabah, serta menghitung panjang antrian tiap menit. Hasil menunjukkan variasi jumlah nasabah harian dan karakteristik antrian yang bisa dianalisis lebih lanjut.
# Total waktu kerja tersedia: 2 teller × 6 jam × 60 menit × 20 hari
total_available_time <- n_tellers * total_minutes * simulation_days
# Hitung total waktu pelayanan selama 20 hari
total_service_time <- sum(sapply(results, function(x) sum(x$departures$service)))
# Hitung persentase teller sibuk (utilisasi)
teller_utilization <- (total_service_time / total_available_time) * 100
cat("Persentase waktu teller sibuk selama 20 hari:", round(teller_utilization, 2), "%\n")
## Persentase waktu teller sibuk selama 20 hari: 32.59 %
Berdasarkan hasil simulasi, diperoleh bahwa persentase waktu teller berada dalam kondisi sibuk selama 20 hari kerja adalah sebesar 32.59%. Nilai ini dihitung dengan membandingkan total waktu pelayanan seluruh nasabah terhadap total waktu kerja yang tersedia dari dua teller, yaitu 14.400 menit (2 teller × 6 jam × 60 menit × 20 hari). Angka ini menunjukkan bahwa teller masih memiliki kapasitas kerja yang cukup besar dan belum bekerja secara penuh sepanjang hari. Dengan tingkat utilisasi sekitar 30%, sistem antrian masih efisien dan dapat menampung tambahan beban nasabah jika diperlukan.
arrival_rate <- 5/60 # 5 nasabah/jam -> nasabah/menit
service_rate <- 1/8 # 8 menit/nasabah -> nasabah/menit
operational_hours <- 6 # Buka 6 jam/hari
total_minutes <- operational_hours * 60
simulation_days <- 20 # Simulasi 20 hari
set.seed(123)
simulate_with_tellers <- function(n_tellers) {
results <- lapply(1:simulation_days, function(day) {
interarrivals <- rexp(ceiling(arrival_rate * total_minutes * 1.5), arrival_rate)
arrivals <- cumsum(interarrivals)
arrivals <- arrivals[arrivals <= total_minutes]
service_times <- rexp(length(arrivals), service_rate)
departures <- queue_step(arrivals, service_times, servers = n_tellers)
return(list(
waiting = departures - arrivals - service_times,
queue_length = sapply(1:total_minutes, function(t) sum(arrivals <= t & departures > t)),
utilization = sum(service_times) / (n_tellers * total_minutes)
))
})
list(
avg_wait = mean(unlist(lapply(results, function(x) mean(x$waiting)))),
max_queue = max(unlist(lapply(results, function(x) max(x$queue_length)))),
utilization = mean(sapply(results, function(x) x$utilization)) * 100
)
}
baseline_2tellers <- simulate_with_tellers(2)
scenario_3tellers <- simulate_with_tellers(3)
comparison_df <- data.frame(
Metric = c("Rata-rata Waktu Tunggu (menit)",
"Antrian Maksimum",
"Utilisasi Teller (%)"),
`2_Tellers` = c(round(baseline_2tellers$avg_wait, 2),
baseline_2tellers$max_queue,
round(baseline_2tellers$utilization, 1)),
`3_Tellers` = c(round(scenario_3tellers$avg_wait, 2),
scenario_3tellers$max_queue,
round(scenario_3tellers$utilization, 1)),
Perubahan = c(
paste0(round((baseline_2tellers$avg_wait - scenario_3tellers$avg_wait)/baseline_2tellers$avg_wait * 100, 1), "%"),
paste0(baseline_2tellers$max_queue - scenario_3tellers$max_queue, " orang"),
paste0(round(baseline_2tellers$utilization - scenario_3tellers$utilization, 1), "%")
)
)
knitr::kable(comparison_df, caption = "Perbandingan 2 vs 3 Teller")
Metric | X2_Tellers | X3_Tellers | Perubahan |
---|---|---|---|
Rata-rata Waktu Tunggu (menit) | 0.74 | 0.04 | 94.3% |
Antrian Maksimum | 6.00 | 4.00 | 2 orang |
Utilisasi Teller (%) | 32.60 | 20.50 | 12.1% |
# Asumsi biaya
hourly_wage <- 50000 # Rp50.000/jam/teller
daily_cost_2 <- 2 * hourly_wage * operational_hours
daily_cost_3 <- 3 * hourly_wage * operational_hours
# Hitung pengurangan waktu tunggu
time_saved <- baseline_2tellers$avg_wait - scenario_3tellers$avg_wait
cost_analysis <- data.frame(
Metrik = c("Biaya Operasional/hari",
"Rata-rata Waktu Tunggu",
"Pengurangan Waktu Tunggu",
"Biaya per Menit yang Dihasilkan"),
`2_Tellers` = c(paste("Rp", format(daily_cost_2, big.mark = ".")),
paste(round(baseline_2tellers$avg_wait, 1), "menit"),
"-",
"-"),
`3_Tellers` = c(paste("Rp", format(daily_cost_3, big.mark = ".")),
paste(round(scenario_3tellers$avg_wait, 1), "menit"),
paste(round(time_saved, 1), "menit"),
paste("Rp", format(round((daily_cost_3 - daily_cost_2)/time_saved), big.mark = ".")))
)
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
knitr::kable(cost_analysis, caption = "Analisis Biaya-Manfaat")
Metrik | X2_Tellers | X3_Tellers |
---|---|---|
Biaya Operasional/hari | Rp 6e+05 | Rp 9e+05 |
Rata-rata Waktu Tunggu | 0.7 menit | 0 menit |
Pengurangan Waktu Tunggu | - | 0.7 menit |
Biaya per Menit yang Dihasilkan | - | Rp 427.629 |
Berdasarkan simulasi sistem antrian selama 20 hari kerja, diperoleh bahwa dengan 2 teller, panjang antrian rata-rata per menit adalah sekitar nasabah, dan waktu tunggu rata-rata adalah 0.7 menit. Ketika jumlah teller ditingkatkan menjadi 3, rata-rata waktu tunggu turun menjadi 0 menit, yang berarti nasabah hampir selalu langsung dilayani tanpa menunggu.Namun, peningkatan ini juga diikuti oleh kenaikan biaya operasional harian, dari Rp600.000 menjadi Rp900.000. Dengan pengurangan waktu tunggu sebesar 0.7 menit, diperoleh bahwa biaya tambahan per menit waktu tunggu yang dihilangkan adalah sekitar Rp427,63. Hal ini menunjukkan bahwa meskipun penambahan teller sangat efektif dalam mengurangi waktu tunggu, manajemen bank perlu mempertimbangkan apakah pengeluaran tambahan tersebut sepadan dengan manfaat yang diperoleh.
# Parameter sistem
arrival_rate <- 5/60 # 5 nasabah/jam -> nasabah/menit
service_rate <- 1/8 # 8 menit/nasabah -> nasabah/menit
operational_hours <- 6 # Buka 6 jam/hari
total_minutes <- operational_hours * 60
simulation_days <- 20 # Simulasi 20 hari
n_tellers <- 2 # Jumlah teller
# Fungsi simulasi dengan tracking antrian
simulate_queue <- function(day) {
set.seed(123 + day) # Seed berbeda per hari
interarrivals <- rexp(ceiling(arrival_rate * total_minutes * 1.5), arrival_rate)
arrivals <- cumsum(interarrivals)
arrivals <- arrivals[arrivals <= total_minutes]
service_times <- rexp(length(arrivals), service_rate)
departures <- queue_step(arrivals, service_times, servers = n_tellers)
# Hitung panjang antrian per menit
time_points <- 1:total_minutes
queue_lengths <- sapply(time_points, function(t) {
sum(arrivals <= t & departures > t)
})
return(data.frame(
Day = day,
Minute = time_points,
QueueLength = queue_lengths,
Hour = floor(time_points/60),
TimeLabel = sprintf("%02d:%02d", floor(time_points/60), time_points%%60)
))
}
# Hasil simulasi 20 hari
queue_data <- do.call(rbind, lapply(1:5, simulate_queue)) # Ambil 5 hari pertama untuk visualisasi
# Plot untuk 5 hari pertama
ggplot(queue_data, aes(x = Minute, y = QueueLength)) +
geom_area(fill = "steelblue", alpha = 0.7) +
facet_wrap(~Day, ncol = 1, scales = "free_x") +
labs(title = "Panjang Antrian per Menit (5 Hari Pertama)",
x = "Menit Operasional",
y = "Jumlah Nasabah dalam Antrian") +
theme_minimal() +
scale_x_continuous(breaks = seq(0, total_minutes, by = 60),
labels = function(x) sprintf("%02d:%02d", floor(x/60), x%%60)) +
geom_hline(yintercept = n_tellers, linetype = "dashed", color = "red") +
annotate("text", x = 30, y = n_tellers + 0.5,
label = "Kapasitas Teller", color = "red", size = 3)
# Grafik rata-rata Harian
# Hitung rata-rata antrian per menit
avg_queue <- aggregate(QueueLength ~ Minute + Hour + TimeLabel,
data = queue_data, mean)
ggplot(avg_queue, aes(x = Minute, y = QueueLength)) +
geom_line(color = "darkorange", size = 1.5) +
geom_ribbon(aes(ymin = 0, ymax = QueueLength), fill = "orange", alpha = 0.3) +
labs(title = "Rata-Rata Panjang Antrian per Menit (5 Hari Pertama)",
x = "Waktu Operasional",
y = "Rata-Rata Jumlah Nasabah dalam Antrian") +
theme_minimal() +
scale_x_continuous(breaks = seq(0, total_minutes, by = 60),
labels = unique(avg_queue$TimeLabel)[seq(1, 361, by = 60)]) +
geom_hline(yintercept = n_tellers, linetype = "dashed", color = "red") +
annotate("text", x = 30, y = n_tellers + 0.3,
label = "Jumlah Teller", color = "red") +
geom_vline(xintercept = c(120, 240), linetype = "dotted",
color = "blue", alpha = 0.5) +
annotate("text", x = c(120, 240), y = max(avg_queue$QueueLength) - 0.5,
label = c("Jam Sibuk Pagi", "Jam Sibuk Siang"),
color = "blue", angle = 90, vjust = -0.5)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# parameter sistem
initial_stock <- 100
order_quantity <- 50
order_freq <- 5
sim_days <- 60
lost_profit <- 50000 # Rp50.000 per karung
# fungsi simulasi dasar
simulate_stock <- function(days, order_qty, order_interval, initial) {
stock <- initial
stock_history <- numeric(days)
out_of_stock <- 0
total_lost <- 0
for (day in 1:days) {
# Penjualan harian (Uniform 8-15 karung)
sales <- sample(8:15, 1)
# Update stok
if (stock >= sales) {
stock <- stock - sales
} else {
lost <- sales - stock
total_lost <- total_lost + lost * lost_profit
stock <- 0
out_of_stock <- out_of_stock + 1
}
# Pesan ulang setiap order_interval hari
if (day %% order_interval == 0) {
stock <- stock + order_qty
}
stock_history[day] <- stock
}
return(list(
stock_history = stock_history,
out_of_stock = out_of_stock,
total_lost = total_lost
))
}
set.seed(123)
result_a <- simulate_stock(sim_days, order_quantity, order_freq, initial_stock)
# Tampilkan 10 hari pertama
head(data.frame(
Hari = 1:10,
Stok = result_a$stock_history[1:10],
Penjualan = diff(c(initial_stock, result_a$stock_history[1:10])) * -1
), 10)
## Hari Stok Penjualan
## 1 1 86 14
## 2 2 72 14
## 3 3 62 10
## 4 4 49 13
## 5 5 89 -40
## 6 6 80 9
## 7 7 71 9
## 8 8 58 13
## 9 9 48 10
## 10 10 86 -38
cat("\nTotal kehabisan stok:", result_a$out_of_stock, "hari")
##
## Total kehabisan stok: 3 hari
cat("\nTotal kerugian: Rp", format(result_a$total_lost, big.mark = "."))
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing
##
## Total kerugian: Rp 9e+05
cat("Toko kehabisan stok sebanyak:", result_a$out_of_stock, "kali dalam 60 hari")
## Toko kehabisan stok sebanyak: 3 kali dalam 60 hari
strategi_qty <- list(
"40_karung" = simulate_stock(sim_days, 40, order_freq, initial_stock),
"50_karung" = result_a, # Hasil dari (a)
"60_karung" = simulate_stock(sim_days, 60, order_freq, initial_stock)
)
qty_comparison <- data.frame(
Strategi = c("40 karung", "50 karung", "60 karung"),
Kehabisan_Stok = sapply(strategi_qty, function(x) x$out_of_stock),
Kerugian = sapply(strategi_qty, function(x) x$total_lost),
Stok_Rata2 = sapply(strategi_qty, function(x) mean(x$stock_history))
)
knitr::kable(qty_comparison, caption = "Perbandingan Strategi Jumlah Pesanan")
Strategi | Kehabisan_Stok | Kerugian | Stok_Rata2 | |
---|---|---|---|---|
40_karung | 40 karung | 17 | 8350000 | 28.21667 |
50_karung | 50 karung | 3 | 900000 | 48.43333 |
60_karung | 60 karung | 0 | 0 | 76.88333 |
Simulasi dilakukan untuk tiga strategi jumlah pesanan: 40, 50, dan 60 karung setiap 5 hari. Hasilnya menunjukkan bahwa:
Dengan mempertimbangkan efisiensi dan kerugian, strategi pemesanan 50 karung adalah yang paling seimbang dan direkomendasikan untuk Bu Sari.
strategi_freq <- list(
"3_hari" = simulate_stock(sim_days, order_quantity, 3, initial_stock),
"5_hari" = result_a, # Hasil dari (a)
"7_hari" = simulate_stock(sim_days, order_quantity, 7, initial_stock)
)
freq_comparison <- data.frame(
Strategi = c("Setiap 3 hari", "Setiap 5 hari", "Setiap 7 hari"),
Kehabisan_Stok = sapply(strategi_freq, function(x) x$out_of_stock),
Kerugian = sapply(strategi_freq, function(x) x$total_lost),
Stok_Rata2 = sapply(strategi_freq, function(x) mean(x$stock_history))
)
knitr::kable(freq_comparison, caption = "Perbandingan Strategi Frekuensi Pesanan")
Strategi | Kehabisan_Stok | Kerugian | Stok_Rata2 | |
---|---|---|---|---|
3_hari | Setiap 3 hari | 0 | 0 | 235.53333 |
5_hari | Setiap 5 hari | 3 | 900000 | 48.43333 |
7_hari | Setiap 7 hari | 20 | 9400000 | 24.70000 |
Simulasi dilakukan untuk strategi pemesanan beras setiap 3, 5, dan 7 hari dengan jumlah pesanan tetap sebanyak 50 karung. Hasilnya menunjukkan bahwa:
Dengan mempertimbangkan efisiensi dan biaya, strategi pemesanan setiap 5 hari merupakan pilihan terbaik dan direkomendasikan untuk Bu Sari.
# Gabungkan data untuk plot
plot_data <- data.frame(
Hari = 1:sim_days,
Stok = result_a$stock_history,
Stok_40 = strategi_qty$`40_karung`$stock_history,
Stok_60 = strategi_qty$`60_karung`$stock_history
)
# Plot pergerakan stok
ggplot(plot_data, aes(x = Hari)) +
geom_line(aes(y = Stok, color = "50 karung"), size = 1) +
geom_line(aes(y = Stok_40, color = "40 karung"), linetype = "dashed") +
geom_line(aes(y = Stok_60, color = "60 karung"), linetype = "dotted") +
labs(title = "Pergerakan Stok Beras (60 Hari)",
y = "Jumlah Stok",
color = "Strategi Pesanan") +
theme_minimal() +
scale_color_manual(values = c("50 karung" = "blue", "40 karung" = "red", "60 karung" = "green")) +
geom_hline(yintercept = 0, color = "black", linetype = "dashed") +
annotate("text", x = 30, y = 5, label = "Stok Habis", color = "red")