Bu çalışmada, Monte Carlo simülasyon uygulamasının tekrarı yapılacaktır. Üç adımlı bir yol izlenecektir:
1.Rastgelelik kontrolünden replikasyon yönetimine kadar simülasyonun temel düzeni.
2.3PL IRT modeline göre veri üretimi, kestirim ve hata metriklerinin fonksiyonlaştırılması.
3.doParallel ile paralel bir pipeline’a
dönüştürme. Çok kategorili maddelere geçmeden önce mantığın oturması
için dichotom (0/1) maddelerle sınırlı kalınmıştır.
Simülasyon çalışmaları psikometride bir yöntemin bilinen koşullar altında ne kadar iyi/kötü davrandığını görmek için temel araçtır. Gerçek veride hiçbir zaman bilemeyeceğimiz “gerçek parametre”yi simülasyonda biz kendimiz belirleriz; sonra kestirim sonuçlarını bu gerçeklikle karşılaştırırız. Bu nedenle bir simülasyon iskeleti — seed kontrolü, fonksiyon parçalama, replikasyon, paralelleştirme — kavramsal olarak ne kadar net oturursa, sonradan üzerine eklenecek karmaşıklık (çok kategorili maddeler, çok boyutlu modeller, yeni tahmin yöntemleri) o kadar sorunsuz işler.
psych betimsel istatistik ve
psikometrik yardımcılar için; irtoys
klasik IRT simülasyon fonksiyonlarını (sim,
irf) karşılaştırma referansı olarak;
mirt kestirim için (1PL/2PL/3PL);
doParallel +
foreach simülasyonu çok çekirdekte
koşturmak için; tidyverse özetleme ve
görselleştirme için kullanılacaktır. gt
profesyonel tablolar, ggplot2 +
patchwork grafikler içindir.
# Çekirdek paketler
library(psych) # describe, betimsel
library(irtoys) # sim ve irf — karşılaştırma referansımız
library(mirt) # IRT kestirim (1PL/2PL/3PL)
# Paralelleştirme
library(doParallel) # foreach paralel backend
library(foreach) # paralel döngü
# Veri ve görselleştirme
library(tidyverse) # dplyr, tidyr, ggplot2, purrr ...
library(gt) # tablolar
library(patchwork) # grafik bileştirme
library(kableExtra) # alternatif tablo
# ggplot ortak teması (koyu palet)
tema_afa <- theme_minimal(base_size = 13) +
theme(
plot.background = element_rect(fill = "#1a1e2e", color = NA),
panel.background = element_rect(fill = "#1a1e2e", color = NA),
panel.grid.major = element_line(color = "#2e3557"),
panel.grid.minor = element_line(color = "#252a3d"),
text = element_text(color = "#e8e8e8"),
axis.text = element_text(color = "#c9d1d9"),
axis.title = element_text(color = "#9dc9e0", face = "bold"),
plot.title = element_text(color = "#7eb8d4", face = "bold", size = 14),
plot.subtitle = element_text(color = "#b8d4e6"),
legend.background = element_rect(fill = "#1a1e2e", color = NA),
legend.key = element_rect(fill = "#1a1e2e", color = NA),
strip.background = element_rect(fill = "#2e3557", color = NA),
strip.text = element_text(color = "#9dc9e0", face = "bold")
)
# gt tabloları için ortak stil
stil_gt <- function(tbl) {
tbl |>
tab_options(
table.background.color = "#1a1e2e",
table.font.color = "#e8e8e8",
table.font.size = px(15),
heading.background.color = "#2e3557",
heading.title.font.size = px(17),
column_labels.background.color = "#2e3557",
column_labels.font.weight = "bold",
row.striping.background_color = "#252a3d",
row.striping.include_table_body = TRUE,
table.border.top.color = "#3a4060",
table.border.bottom.color = "#3a4060"
) |>
opt_row_striping()
}set.seed Mantığıset.seed ile rastgele sayı üretecinin başlangıç durumu
sabitlenir; aynı seed → aynı örneklem → aynı sonuç. Tekrarlanabilirlik
sağlanabilir.
# Aynı seed → aynı örneklem
set.seed(41)
x1 <- rnorm(100, mean = 100, sd = 20)
set.seed(41)
x2 <- rnorm(100, mean = 100, sd = 20)
identical(x1, x2) # TRUE — birebir aynı vektör## [1] TRUE
# Farklı seed → farklı örneklem (ama aynı dağılımdan)
set.seed(410)
y <- rnorm(100, mean = 100, sd = 20)
tibble(
Seed = c("41", "41 (tekrar)", "410", "3"),
Ortalama = c(mean(x1), mean(x2), mean(y),
{ set.seed(3); mean(rnorm(100, 100, 20)) }),
SS = c(sd(x1), sd(x2), sd(y),
{ set.seed(3); sd(rnorm(100, 100, 20)) })
) |>
mutate(across(c(Ortalama, SS), \(z) round(z, 2))) |>
gt() |>
tab_header(title = "Aynı/Farklı Seed ile Çekilen Örneklemler",
subtitle = "n = 100, μ = 100, σ = 20") |>
stil_gt()| Aynı/Farklı Seed ile Çekilen Örneklemler | ||
| n = 100, μ = 100, σ = 20 | ||
| Seed | Ortalama | SS |
|---|---|---|
| 41 | 103.81 | 20.56 |
| 41 (tekrar) | 103.81 | 20.56 |
| 410 | 99.63 | 19.07 |
| 3 | 100.22 | 17.12 |
Seed 41 iki kez çağrıldığında örneklem değişmedi. Üçüncü ve dördüncü satırlarda ortalamalar 100’e, standart sapmalar 20’ye yaklaşıyor ama tam denk değil — bu örnekleme hatası. Simülasyon mantığının özü burada başlıyor: aynı evrenden farklı seedlerle çekilen örneklemler birbirinden ne kadar sapıyor, parametre kestirimleri bu sapmayla nasıl başa çıkıyor?
Aynı evrenden farklı büyüklükte örneklemler çekildiğinde, örneklem ortalamalarının dağılımı nasıl değişir? Bu, Merkezi Limit Teoremi’nin (MLT) sezgisel bir gösterimidir ve simülasyonda örneklem büyüklüğü seçiminin neden önemli olduğunu açıklar.
n_k <- 10 # küçük örneklem
b_k <- 50 # büyük örneklem (50, derste b_k = 50 olarak alındı)
tekrar <- 1000 # her örneklem büyüklüğü için tekrar sayısı
kucuk_orn <- numeric(tekrar)
buyuk_orn <- numeric(tekrar)
set.seed(2024)
for (i in 1:tekrar) {
kucuk_orn[i] <- mean(rnorm(n = n_k, mean = 35, sd = 15))
buyuk_orn[i] <- mean(rnorm(n = b_k, mean = 35, sd = 15))
}
# İki histogramı yan yana
df_orn <- tibble(
ortalama = c(kucuk_orn, buyuk_orn),
grup = rep(c(paste0("Küçük (n = ", n_k, ")"),
paste0("Büyük (n = ", b_k, ")")), each = tekrar)
)
ggplot(df_orn, aes(x = ortalama, fill = grup)) +
geom_histogram(bins = 30, color = "#1a1e2e", alpha = 0.9) +
geom_vline(xintercept = 35, color = "#d48080",
linetype = "dashed", linewidth = 0.8) +
facet_wrap(~ grup, scales = "fixed") +
scale_fill_manual(values = c("#7eb8d4", "#d4a560")) +
labs(
title = "Örneklem Ortalamalarının Dağılımı",
subtitle = "1000 tekrar, gerçek μ = 35 (kırmızı kesik çizgi)",
x = "Örneklem ortalaması", y = "Frekans"
) +
tema_afa +
theme(legend.position = "none")tibble(
Örneklem = c(paste0("Küçük (n = ", n_k, ")"),
paste0("Büyük (n = ", b_k, ")")),
Ortalama = c(mean(kucuk_orn), mean(buyuk_orn)),
SS = c(sd(kucuk_orn), sd(buyuk_orn)),
Min = c(min(kucuk_orn), min(buyuk_orn)),
Maks = c(max(kucuk_orn), max(buyuk_orn))
) |>
mutate(across(c(Ortalama, SS, Min, Maks), \(z) round(z, 2))) |>
gt() |>
tab_header(title = "Örneklem Büyüklüğü ↔ Ortalama Dağılımı") |>
stil_gt()| Örneklem Büyüklüğü ↔ Ortalama Dağılımı | ||||
| Örneklem | Ortalama | SS | Min | Maks |
|---|---|---|---|---|
| Küçük (n = 10) | 35.12 | 4.64 | 21.50 | 51.53 |
| Büyük (n = 50) | 35.06 | 2.00 | 28.41 | 41.53 |
İki histogram da gerçek ortalama (μ = 35) etrafında yoğunlaşıyor — yani örneklem ortalaması yansız (unbiased). Ama küçük örneklemde dağılımın genişliği belirgin biçimde daha fazla; büyük örneklemde ise sıkıca 35’e toplanıyor. Tablo da bunu doğruluyor: küçük örneklem için ortalamaların standart sapması (yani standart hata), büyük örneklem için olandan yaklaşık \(\sqrt{50/10} \approx 2.24\) kat daha büyük.
Artan örneklem büyüklüğü kestirim doğruluğunu (precision) artırır ama yansızlığı (bias) doğrudan değiştirmez. Simülasyonlarda bu yüzden her zaman örneklem büyüklüğünü sabit tutarak kestirimin değişkenliğini ölçeriz; sonra büyüklüğü çeşitlendirerek “artan n ile RMSE nasıl iyileşiyor?” sorusunu yanıtlarız.
Büyük simülasyonlarda her replikasyonun ürettiği veriyi bellekte tutmak yerine diske yazıp gerektikçe geri okumak sık kullanılan bir stratejidir. Bu bölümde küçük bir örnek üzerinde yaz-oku döngüsü kurulacaktır.
# Klasörler yoksa oluştur (varsa sessizce geç)
if (!dir.exists("Simulasyon_k")) dir.create("Simulasyon_k")
if (!dir.exists("Simulasyon_b")) dir.create("Simulasyon_b")
# 50'şer replikasyon — küçük ve büyük örneklem
set.seed(2024)
for (i in 1:50) {
write.table(rnorm(n = n_k, mean = 35, sd = 15),
file = paste0("Simulasyon_k/simulasyon_", i, ".txt"))
write.table(rnorm(n = b_k, mean = 35, sd = 15),
file = paste0("Simulasyon_b/simulasyon_", i, ".txt"))
}
# Geri okuma — list yapısında topla
kucuk_orn_ort <- vector("list", 50)
buyuk_orn_ort <- vector("list", 50)
for (i in 1:50) {
kucuk_orn_ort[[i]] <- read.table(
paste0("Simulasyon_k/simulasyon_", i, ".txt"))[, 1]
buyuk_orn_ort[[i]] <- read.table(
paste0("Simulasyon_b/simulasyon_", i, ".txt"))[, 1]
}
# İlk 5 replikasyonun ortalamaları
tibble(
Replikasyon = 1:5,
`Küçük (n=10)` = round(sapply(kucuk_orn_ort[1:5], mean), 2),
`Büyük (n=50)` = round(sapply(buyuk_orn_ort[1:5], mean), 2)
) |>
gt() |>
tab_header(title = "Diskten Geri Okunan İlk 5 Replikasyon — Ortalamalar") |>
stil_gt()| Diskten Geri Okunan İlk 5 Replikasyon — Ortalamalar | ||
| Replikasyon | Küçük (n=10) | Büyük (n=50) |
|---|---|---|
| 1 | 37.46 | 32.33 |
| 2 | 37.66 | 34.67 |
| 3 | 27.51 | 38.13 |
| 4 | 37.67 | 36.80 |
| 5 | 40.27 | 35.78 |
50 replikasyonun her birinin ürettiği örneklem ayrı bir
.txt dosyasına yazıldı; sonra list içine geri
okundu. sapply(..., mean) ile her birinin ortalaması
alındı. Bu yapı küçük bir alıştırma gibi görünse de, diske yazma
disiplini bellek aşımının önüne geçer. Ayrıca
inceleme/denetim için de kolaylık sağlar. Belirli bir
replikasyonun sonucu şüpheli görünürse, ham veriyi diskten alıp yeniden
analiz edebiliriz.
Knit her çalıştırıldığında klasörler ve dosyalar yeniden yazılır.
Gerçek bir simülasyon uygulamasında dosya adlarına timestamp veya koşul
etiketleri (örn. n50_3pl_seed41.txt) eklemek karışıklığı
azaltabilir.
irtoys::sim Aslında Ne Yapıyor?Burada bir adım yavaşlayıp irtoys::sim() fonksiyonunu
inceliyoruz. Amaç: 3PL modelin matematiksel yapısını görmek, bir sonraki
bölümde aynı şeyi kendi fonksiyonumla yapmayı denemek.
Üç parametreli lojistik (3PL) model, ikili maddeler için en yaygın IRT modellerinden biridir. Bir bireyin (yetenek \(\theta_j\)) bir maddeyi doğru cevaplama olasılığı:
\[P(X_{ij} = 1 \mid \theta_j) = c_i + (1 - c_i)\,\dfrac{1}{1 + \exp\!\left(-a_i\,(\theta_j - b_i)\right)}\]
Üretilen olasılıklar daha sonra \([0,1]\) aralığından çekilen tekdüze rastgele sayılarla karşılaştırılarak 0/1 cevaplara dönüştürülür.
set.seed(41)
madde <- 8
maddepar <- cbind(
a = rnorm(madde, mean = 1.00, sd = 0.30), # ayırt edicilik
b = rnorm(madde, mean = 0.00, sd = 1.00), # güçlük
c = rnorm(madde, mean = 0.16, sd = 0.05) # tahmin
)
# c parametresi negatife düşmesin diye küçük bir koruma:
maddepar[, "c"] <- pmax(maddepar[, "c"], 0.05)
maddepar |>
round(3) |>
as.data.frame() |>
rownames_to_column("Madde") |>
mutate(Madde = paste0("madde", 1:madde)) |>
gt() |>
tab_header(title = "Üretilen Madde Parametreleri (3PL)") |>
stil_gt()| Üretilen Madde Parametreleri (3PL) | |||
| Madde | a | b | c |
|---|---|---|---|
| madde1 | 0.762 | 1.001 | 0.193 |
| madde2 | 1.059 | 2.188 | 0.204 |
| madde3 | 1.301 | -1.209 | 0.170 |
| madde4 | 1.387 | -0.587 | 0.274 |
| madde5 | 1.272 | 1.056 | 0.115 |
| madde6 | 1.148 | -0.317 | 0.267 |
| madde7 | 1.180 | -0.055 | 0.102 |
| madde8 | 0.526 | 0.330 | 0.158 |
set.seed(41)
birey <- 1000
yetenek <- rnorm(birey, mean = 0, sd = 1)
tibble(
Özellik = c("n", "Ortalama", "Standart Sapma", "Min", "Maks"),
Yetenek_θ = c(birey, mean(yetenek), sd(yetenek),
min(yetenek), max(yetenek))
) |>
mutate(Yetenek_θ = ifelse(Özellik == "n",
as.character(Yetenek_θ),
as.character(round(Yetenek_θ, 3)))) |>
gt() |>
tab_header(title = "Yetenek Parametresi Özeti",
subtitle = "θ ~ N(0, 1)") |>
stil_gt()| Yetenek Parametresi Özeti | |
| θ ~ N(0, 1) | |
| Özellik | Yetenek_θ |
|---|---|
| n | 1000 |
| Ortalama | 0.003 |
| Standart Sapma | 0.995 |
| Min | -3.259 |
| Maks | 3.326 |
irtoys::sim ile Veri Üretimiset.seed(41)
veri_irtoys <- irtoys::sim(ip = maddepar, x = yetenek)
colnames(veri_irtoys) <- paste0("madde", 1:madde)
head(veri_irtoys) |>
as.data.frame() |>
rownames_to_column("Birey") |>
gt() |>
tab_header(title = "irtoys::sim — İlk 6 Birey × 8 Madde") |>
stil_gt()| irtoys::sim — İlk 6 Birey × 8 Madde | ||||||||
| Birey | madde1 | madde2 | madde3 | madde4 | madde5 | madde6 | madde7 | madde8 |
|---|---|---|---|---|---|---|---|---|
| 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 2 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
| 3 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 |
| 4 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
| 5 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
| 6 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
irf +
Tekdüze Karşılaştırma# 1) Her birey × madde için doğru cevap olasılığını hesapla
i_obj <- irtoys::irf(ip = maddepar, x = yetenek)
# i_obj$f bir matristir: satırlar bireyler, sütunlar maddeler
boyut <- dim(i_obj$f)
# 2) Aynı boyutta tekdüze rastgele matris üret
set.seed(41)
u <- runif(boyut[1] * boyut[2])
dim(u) <- boyut
# 3) Olasılık > tekdüze ise 1, değilse 0
cevaplar_manuel <- ifelse(i_obj$f > u, 1L, 0L)
colnames(cevaplar_manuel) <- paste0("madde", 1:madde)
head(cevaplar_manuel) |>
as.data.frame() |>
rownames_to_column("Birey") |>
gt() |>
tab_header(title = "Manuel Açılım — İlk 6 Birey × 8 Madde",
subtitle = "P(θ) > U(0,1) → 1, değilse 0") |>
stil_gt()| Manuel Açılım — İlk 6 Birey × 8 Madde | ||||||||
| P(θ) > U(0,1) → 1, değilse 0 | ||||||||
| Birey | madde1 | madde2 | madde3 | madde4 | madde5 | madde6 | madde7 | madde8 |
|---|---|---|---|---|---|---|---|---|
| 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 2 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
| 3 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 |
| 4 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
| 5 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
| 6 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
İki aşamada önce irf() ile olasılık
matrisi üretildi; sonra aynı boyutta bir tekdüze
rastgele matris ile karşılaştırılıp 0/1 cevaplara dönüştürüldü.
irtoys::sim()’in nasıl çalıştığını gözlemlemiş oldum.
irtoys::irf() yerine 3PL olasılığını hesaplayan
kendi fonksiyonumu yazacağım. Tabi Claude olmasa bu
bayağı uğraştırıcı olabilirdi.
irf_3pl() — Olasılık Hesabı# 3PL madde tepki fonksiyonu
# Girdi: ip = madde parametre matrisi (sütun sırası: a, b, c)
# x = yetenek vektörü
# Çıktı: list(x = yetenek, f = olasılık matrisi [birey x madde])
irf_3pl <- function(ip, x) {
# Argüman kontrolleri
stopifnot(is.matrix(ip) || is.data.frame(ip))
stopifnot(ncol(ip) >= 3)
a <- ip[, 1]
b <- ip[, 2]
c <- ip[, 3]
# Her madde için birey vektörü üzerinde olasılık hesabı
# sapply: madde başına bir sütun → sonuç matris (birey × madde)
P <- sapply(seq_along(a), function(i) {
c[i] + (1 - c[i]) / (1 + exp(-a[i] * (x - b[i])))
})
if (!is.matrix(P)) P <- as.matrix(P)
list(x = x, f = P)
}sim_3pl() — Cevap Matrisi Üretimiirtoys::irf() ↔︎ irf_3pl()Yazdığımız fonksiyon doğru çalışıyor mu? Test: aynı parametre ve
yetenek girdileriyle, olasılık matrislerinin birebir aynı olması
gerekir. (Cevap matrisleri, içerideki runif
çağrılarının farklı sırada gelmesi nedeniyle birebir aynı olmayabilir;
önemli olan olasılıkların aynılığıdır.)
# Aynı girdilerle iki olasılık matrisi
P_irtoys <- irtoys::irf(ip = maddepar, x = yetenek)$f
P_kendi <- irf_3pl(ip = maddepar, x = yetenek)$f
# Maksimum mutlak fark
fark_maks <- max(abs(P_irtoys - P_kendi))
tibble(
Test = c("Boyut eşitliği",
"all.equal()",
"Maksimum mutlak fark"),
Sonuç = c(
paste0(paste(dim(P_irtoys), collapse = " × "),
" vs ",
paste(dim(P_kendi), collapse = " × ")),
as.character(isTRUE(all.equal(P_irtoys, P_kendi))),
format(fark_maks, scientific = TRUE, digits = 3)
)
) |>
gt() |>
tab_header(title = "Kendi Fonksiyonumuz vs. irtoys::irf()",
subtitle = "Aynı girdilerde olasılık matrisleri") |>
stil_gt()| Kendi Fonksiyonumuz vs. irtoys::irf() | |
| Aynı girdilerde olasılık matrisleri | |
| Test | Sonuç |
|---|---|
| Boyut eşitliği | 1000 × 8 vs 1000 × 8 |
| all.equal() | TRUE |
| Maksimum mutlak fark | 2.22e-16 |
# Madde Karakteristik Eğrileri (ICC) — kendi fonksiyonumuzla
theta_seq <- seq(-4, 4, length.out = 200)
P_seq <- irf_3pl(ip = maddepar, x = theta_seq)$f
df_icc <- as.data.frame(P_seq) |>
setNames(paste0("madde", 1:madde)) |>
mutate(theta = theta_seq) |>
pivot_longer(-theta, names_to = "Madde", values_to = "P")
ggplot(df_icc, aes(x = theta, y = P, color = Madde)) +
geom_line(linewidth = 1) +
geom_hline(yintercept = c(0, 1), color = "#3a4060",
linetype = "dotted") +
scale_color_viridis_d(option = "C") +
labs(
title = "Madde Karakteristik Eğrileri (ICC)",
subtitle = "Kendi yazdığımız irf_3pl() fonksiyonu ile, 8 madde",
x = expression(theta), y = expression(P(theta))
) +
tema_afaall.equal() TRUE döndü ve iki matris arasındaki maksimum
mutlak fark sayısal sıfır (10⁻¹⁵ mertebesinde). Yani
irf_3pl() fonksiyonumuz irtoys::irf() ile
fonksiyonel olarak özdeş.
ICC grafiğinde her madde için tipik 3PL şeklini görüyoruz: \(\theta = b_i\) civarında en dik eğim, \(\theta \to -\infty\) için alt asimptot \(c_i\) civarında, \(\theta \to +\infty\) için üst asimptot 1’e yaklaşıyor. Bu fonksiyonu artık güvenle simülasyon çalışmamda kullanabilirim.
Simülasyonun üç temel parçasını ayrı fonksiyonlarla yapıyoruz: (1) veriyi üreten, (2) modeli kestiren, (3) gerçek ↔︎ kestirilen parametre farklarını ölçen. Bu yapı sayesinde ileride bir parça değişse (örn. 2PL’ye geçilse, başka bir tahmin yöntemi kullanılsa) sadece ilgili fonksiyon güncellenerek simülasyon tamamlanabilir.
veri_uretimi() v1Tek girdi: madde sayısı, birey sayısı, seed. Tek çıktı: madde par + yetenek par + cevap matrisi. Bu versiyonda hiçbir raporlama yok — fonksiyon sessizce çalışıp sonucu döndürüyor.
veri_uretimi_v1 <- function(maddesay, bireysay, seed) {
set.seed(seed)
# Madde parametreleri (a > 0 için rlnorm; c [0.05, 0.35] aralığına kıstırılır)
maddepar <- cbind(
a = rlnorm(maddesay, meanlog = 0, sdlog = 0.30),
b = rnorm( maddesay, mean = 0, sd = 1.00),
c = pmin(pmax(rnorm(maddesay, 0.16, 0.05), 0.05), 0.35)
)
# Yetenek parametreleri
yetenek <- rnorm(bireysay, mean = 0, sd = 1)
# Cevap matrisi — kendi sim_3pl fonksiyonumuzla
cevaplar <- sim_3pl(ip = maddepar, x = yetenek)
colnames(cevaplar) <- paste0("madde", 1:maddesay)
list(maddepar = maddepar, yetenek = yetenek, cevaplar = cevaplar)
}# v1 test — çıktı sessiz
v1_ornek <- veri_uretimi_v1(maddesay = 8, bireysay = 1000, seed = 41)
cat("=== v1 çıktısı: yapı ===\n")## === v1 çıktısı: yapı ===
## List of 3
## $ maddepar: num [1:8, 1:3] 0.788 1.061 1.351 1.472 1.312 ...
## ..- attr(*, "dimnames")=List of 2
## $ yetenek : num [1:1000] 0.416 1.72 -0.784 -1.304 -0.452 ...
## $ cevaplar: int [1:1000, 1:8] 0 1 0 1 0 0 0 0 1 1 ...
## ..- attr(*, "dimnames")=List of 2
##
## === Madde parametreleri (ilk 3 madde) ===
## a b c
## [1,] 0.788 1.001 0.193
## [2,] 1.061 2.188 0.204
## [3,] 1.351 -1.209 0.170
##
## === Yetenek özeti ===
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -3.25888 -0.69765 0.01794 -0.01084 0.67192 3.32571
##
## === Cevap matrisi (ilk 5 birey × 4 madde) ===
## madde1 madde2 madde3 madde4
## [1,] 0 1 1 1
## [2,] 1 0 1 1
## [3,] 0 0 1 1
## [4,] 1 0 1 0
## [5,] 0 0 1 1
##
## === Cevap matrisi: 1 oranları (madde başına) ===
## madde1 madde2 madde3 madde4 madde5 madde6 madde7 madde8
## 0.45 0.30 0.82 0.72 0.33 0.69 0.56 0.54
v1 çalışıyor: madde parametreleri makul aralıkta (a değerleri 0.5–1.5 civarında, b değerleri ±2 arası, c 0.05–0.30 arası), yetenek dağılımı standart normale yakın, cevap matrisindeki 1 oranları %20–%80 arasında — madde güçlüklerine göre beklenen yelpaze. Yani fonksiyonel olarak sorun yok. Tek eksik: bir simülasyonda her aşamada ne olduğunu görme ihtiyacı. Bunu v2’de ekleyeceğiz.
veri_uretimi() v2Aynı işlevi gören ama her adımda cat() ile rapor
veren bir versiyon. Hocanın ders işleme yaklaşımı: “her adımda
bas raporlayın”. Bu hem hata ayıklama (debug) açısından, hem de bir
başkasına simülasyonu anlatırken çok değerlidir. Ek olarak seed’i
opsiyonel hale getirip verilmediğinde otomatik rastgele bir seed
üreteceğiz — paralel koşumda her replikasyonun kendi seed’ini
raporlamasına imkân tanır.
veri_uretimi <- function(maddesay, bireysay,
seed = NULL, verbose = FALSE) {
# Seed yönetimi
if (!is.null(seed)) {
set.seed(seed)
} else {
seed <- sample.int(1e5, 1)
set.seed(seed)
}
if (verbose) cat("• Atanan seed:", seed, "\n")
# Aşama 1: Madde parametreleri
if (verbose) cat("• Aşama 1/4 — Madde parametreleri üretiliyor...\n")
maddepar <- cbind(
a = rlnorm(maddesay, meanlog = 0, sdlog = 0.30),
b = rnorm( maddesay, mean = 0, sd = 1.00),
c = rnorm( maddesay, mean = 0.16, sd = 0.05)
)
maddepar[, "c"] <- pmin(pmax(maddepar[, "c"], 0.05), 0.35)
if (verbose) cat(" ↳", maddesay, "madde × 3 parametre hazır.\n")
# Aşama 2: Yetenek parametreleri
if (verbose) cat("• Aşama 2/4 — Yetenek parametreleri üretiliyor...\n")
yetenek <- rnorm(bireysay, mean = 0, sd = 1)
if (verbose) cat(" ↳", bireysay, "birey için θ ~ N(0, 1) hazır.\n")
# Aşama 3: Cevap matrisi
if (verbose) cat("• Aşama 3/4 — Cevap matrisi sim_3pl() ile üretiliyor...\n")
cevaplar <- sim_3pl(ip = maddepar, x = yetenek)
colnames(cevaplar) <- paste0("madde", 1:maddesay)
if (verbose) cat(" ↳", nrow(cevaplar), "×", ncol(cevaplar),
"boyutunda 0/1 matrisi hazır.\n")
# Aşama 4: Bileşenleri birleştir
if (verbose) cat("• Aşama 4/4 — Bileşenler birleştiriliyor.\n")
list(
maddepar = maddepar,
yetenek = yetenek,
cevaplar = cevaplar,
seed = seed
)
}## ================ TEST: veri_uretimi() v2 ================
## • Atanan seed: 41
## • Aşama 1/4 — Madde parametreleri üretiliyor...
## ↳ 8 madde × 3 parametre hazır.
## • Aşama 2/4 — Yetenek parametreleri üretiliyor...
## ↳ 1000 birey için θ ~ N(0, 1) hazır.
## • Aşama 3/4 — Cevap matrisi sim_3pl() ile üretiliyor...
## ↳ 1000 × 8 boyutunda 0/1 matrisi hazır.
## • Aşama 4/4 — Bileşenler birleştiriliyor.
##
## === Çıktı yapısı ===
## List of 4
## $ maddepar: num [1:8, 1:3] 0.788 1.061 1.351 1.472 1.312 ...
## ..- attr(*, "dimnames")=List of 2
## $ yetenek : num [1:1000] 0.416 1.72 -0.784 -1.304 -0.452 ...
## $ cevaplar: int [1:1000, 1:8] 0 1 0 1 0 0 0 0 1 1 ...
## ..- attr(*, "dimnames")=List of 2
## $ seed : num 41
v2 ile aynı sonucu alıyoruz ama artık simülasyon çalışırken sürecin
her aşamasında ne olduğunu görüyoruz.
verbose = FALSE varsayılan olduğu için paralel akışta 500
kere cat() çıktısı basılmıyor; yalnızca el ile çağırıp
gözleme almak istediğimizde verbose = TRUE diyoruz. Bu,
simülasyon hata ayıklamada en sık başvurulan kalıptır.
kestirilen_par(): mirt ile Kestirimkestirilen_par <- function(veri, par = 3, verbose = FALSE) {
itemtype_secim <- switch(as.character(par),
"3" = "3PL",
"2" = "2PL",
"1" = "Rasch")
if (verbose) cat("• Kestirim:", itemtype_secim,
"modeli mirt ile uydurülüyor...\n")
model <- mirt::mirt(
data = as.data.frame(veri),
model = 1,
itemtype = itemtype_secim,
verbose = FALSE
)
kestirim <- mirt::coef(model, simplify = TRUE, IRTpars = TRUE)$items[, 1:3]
if (verbose) {
cat(" ↳ Kestirim tamamlandı.",
nrow(kestirim), "madde için 3 parametre.\n")
}
kestirim
}## ================ TEST: kestirilen_par() ================
## • Kestirim: 3PL modeli mirt ile uydurülüyor...
## ↳ Kestirim tamamlandı. 8 madde için 3 parametre.
##
## === Kestirilen parametreler ===
## a b g
## madde1 0.476 0.665 0.044
## madde2 0.367 3.329 0.088
## madde3 1.374 -1.424 0.039
## madde4 1.411 -0.883 0.006
## madde5 0.988 0.985 0.034
## madde6 1.294 0.201 0.440
## madde7 0.958 -0.212 0.043
## madde8 0.957 0.987 0.328
##
## === Gerçek parametreler (karşılaştırma için) ===
## a b c
## [1,] 0.788 1.001 0.193
## [2,] 1.061 2.188 0.204
## [3,] 1.351 -1.209 0.170
## [4,] 1.472 -0.587 0.274
## [5,] 1.312 1.056 0.115
## [6,] 1.160 -0.317 0.267
## [7,] 1.197 -0.055 0.102
## [8,] 0.623 0.330 0.158
hata(): RMSE, BIAS, SE HesabıÜç tamamlayıcı hata metriği:
İlişki: \(\text{RMSE}^2 \approx \text{BIAS}^2 + \text{SE}^2\). Yani RMSE’yi BIAS ve SE’ye ayrıştırarak sorunun yanlılıktan mı yoksa değişkenlikten mi kaynaklandığını ayırt edebiliyoruz.
hata <- function(kestirilen, gercek) {
data.frame(
parametreler = c("a", "b", "c"),
rmse = sapply(1:3, \(i) sqrt(mean((kestirilen[, i] - gercek[, i])^2))),
bias = sapply(1:3, \(i) mean(kestirilen[, i] - gercek[, i])),
se = sapply(1:3, \(i) sd(kestirilen[, i]))
)
}## ================ TEST: hata() — tek replikasyon ================
##
## === Ham çıktı (data.frame) ===
## parametreler rmse bias se
## 1 a 0.3307437 -0.14215310 0.3910752
## 2 b 0.5335751 0.15512197 1.4491870
## 3 c 0.1555148 -0.05747716 0.1625942
##
## === Yuvarlanmış sunum ===
hata_tek |>
mutate(across(c(rmse, bias, se), \(z) round(z, 3))) |>
gt() |>
tab_header(title = "Tek Replikasyon — Parametre Bazında Hata",
subtitle = "8 madde × 1000 birey, 3PL") |>
stil_gt()| Tek Replikasyon — Parametre Bazında Hata | |||
| 8 madde × 1000 birey, 3PL | |||
| parametreler | rmse | bias | se |
|---|---|---|---|
| a | 0.331 | -0.142 | 0.391 |
| b | 0.534 | 0.155 | 1.449 |
| c | 0.156 | -0.057 | 0.163 |
Tek replikasyondan çıkan tablo bize anlık bir fotoğraf veriyor; ama simülasyonun gerçek değeri tek bir resimden gelmez. Sonraki bölümde 500 farklı örneklem üzerinde aynı işlemi tekrar edip bu üç metriğin dağılımını ve n arttıkça nasıl yakınsadığını göreceğiz.
doParallel
ile Çok Çekirdek500 replikasyonun tek çekirdekte ardışık çalışması uzun sürer. Her
replikasyon birbirinden bağımsız olduğu için (her biri kendi seed’ini
kullanıyor) bu işlem utanç verici düzeyde paralel
(embarrassingly parallel) bir problemdir. doParallel +
foreach ikilisi tam burada devreye girer.
toplam_cekirdek <- parallel::detectCores()
kullanilacak <- max(1, toplam_cekirdek - 2) # n - 2 güvenli bir tercih
tibble(
Bilgi = c("Toplam çekirdek", "Kullanılacak çekirdek"),
Sayı = c(toplam_cekirdek, kullanilacak)
) |>
gt() |>
tab_header(title = "Sistem Çekirdek Bilgisi") |>
stil_gt()| Sistem Çekirdek Bilgisi | |
| Bilgi | Sayı |
|---|---|
| Toplam çekirdek | 10 |
| Kullanılacak çekirdek | 8 |
Kullanılacak çekirdek sayısını toplam − 2 seçmek, işletim sisteminin ve diğer süreçlerin nefes alabilmesi için yaygın bir kuraldır. Tüm çekirdekleri kullanmak knit’i hızlandırabilir ama sistemde başka iş yaparken donmalara yol açabilir.
cl <- makeCluster(min(4, kullanilacak))
registerDoParallel(cl)
# Mini demo: 1'den 8'e kadar sayıların karekökleri
demo_sonuc <- foreach(i = 1:8, .combine = c) %dopar% sqrt(i)
stopCluster(cl)
tibble(i = 1:8, sqrt_i = round(demo_sonuc, 3)) |>
gt() |>
tab_header(title = "foreach %dopar% Mini Demo",
subtitle = "Paralel çalışan sqrt(1:8)") |>
stil_gt()| foreach %dopar% Mini Demo | |
| Paralel çalışan sqrt(1:8) | |
| i | sqrt_i |
|---|---|
| 1 | 1.000 |
| 2 | 1.414 |
| 3 | 1.732 |
| 4 | 2.000 |
| 5 | 2.236 |
| 6 | 2.449 |
| 7 | 2.646 |
| 8 | 2.828 |
Şimdiye kadar inşa ettiğimiz parçaları bir araya getirelim.
500 replikasyon, her birinde: (1)
veri_uretimi() ile yeni bir veri seti üret, (2)
kestirilen_par() ile 3PL’i kestir, (3) hata()
ile RMSE, BIAS, SE hesapla. Sonuçları tidyverse ile
parametre bazında özetle.
cl <- makeCluster(kullanilacak)
registerDoParallel(cl)
# Paralel çalışanların kendi ortamlarına ihtiyaç duyacağı nesneler:
# .packages → her işçide yüklenecek kütüphaneler
# .export → bizim yazdığımız özel fonksiyonlar
zaman_baslangic <- Sys.time()
simulasyon <- foreach(
i = 1:tekrar_sayisi,
.packages = c("mirt", "irtoys"),
.export = c("veri_uretimi", "kestirilen_par", "hata",
"irf_3pl", "sim_3pl"),
.combine = rbind
) %dopar% {
adim1 <- veri_uretimi(maddesay = madde_sayisi,
bireysay = birey_sayisi)
adim2 <- kestirilen_par(adim1$cevaplar, par = 3)
hata(adim2, adim1$maddepar)
}
zaman_bitis <- Sys.time()
gecen_sure <- round(as.numeric(difftime(zaman_bitis, zaman_baslangic,
units = "secs")), 1)
stopCluster(cl)
cat("Toplam süre:", gecen_sure, "saniye\n")## Toplam süre: 46.2 saniye
## Toplam satır (3 par × 500 tekrar): 1500
simulasyon_ozet <- as_tibble(simulasyon) |>
group_by(parametreler) |>
summarise(
RMSE_ort = mean(rmse),
RMSE_ss = sd(rmse),
BIAS_ort = mean(bias),
BIAS_ss = sd(bias),
SE_ort = mean(se),
.groups = "drop"
) |>
mutate(
maddesay = madde_sayisi,
bireysay = birey_sayisi,
tekrar = tekrar_sayisi,
across(where(is.numeric) & !c(maddesay, bireysay, tekrar),
\(z) round(z, 3))
)
simulasyon_ozet |>
gt() |>
tab_header(
title = "Simülasyon Özeti — 500 Replikasyon",
subtitle = "3PL, 10 madde × 1000 birey, mirt ile kestirim"
) |>
cols_label(
parametreler = "Parametre",
RMSE_ort = "RMSE (ort)", RMSE_ss = "RMSE (ss)",
BIAS_ort = "BIAS (ort)", BIAS_ss = "BIAS (ss)",
SE_ort = "SE (ort)",
maddesay = "Madde", bireysay = "Birey", tekrar = "Tekrar"
) |>
stil_gt()| Simülasyon Özeti — 500 Replikasyon | ||||||||
| 3PL, 10 madde × 1000 birey, mirt ile kestirim | ||||||||
| Parametre | RMSE (ort) | RMSE (ss) | BIAS (ort) | BIAS (ss) | SE (ort) | Madde | Birey | Tekrar |
|---|---|---|---|---|---|---|---|---|
| a | 0.619 | 0.375 | 0.214 | 0.209 | 0.672 | 10 | 1000 | 500 |
| b | 0.548 | 0.184 | 0.030 | 0.177 | 0.993 | 10 | 1000 | 500 |
| c | 0.165 | 0.042 | 0.004 | 0.048 | 0.168 | 10 | 1000 | 500 |
simulasyon |>
as_tibble() |>
pivot_longer(c(rmse, bias, se),
names_to = "metrik", values_to = "deger") |>
mutate(
metrik = factor(metrik, levels = c("rmse", "bias", "se"),
labels = c("RMSE", "BIAS", "SE")),
parametreler = factor(parametreler, levels = c("a", "b", "c"))
) |>
ggplot(aes(x = parametreler, y = deger, fill = parametreler)) +
geom_violin(alpha = 0.85, color = "#1a1e2e") +
geom_boxplot(width = 0.18, fill = "#1a1e2e", color = "#e8e8e8",
outlier.size = 0.5) +
facet_wrap(~ metrik, scales = "free_y") +
scale_fill_manual(values = c("#7eb8d4", "#d4a560", "#8ed47e")) +
labs(
title = "500 Replikasyon Boyunca Hata Metriklerinin Dağılımı",
subtitle = "3PL kestirim, 10 madde × 1000 birey",
x = "Parametre", y = "Değer"
) +
tema_afa +
theme(legend.position = "none")Tablo ve grafik birlikte okunduğunda dört kritik gözlem öne çıkıyor:
(1) a parametresi en zor kestirilen. RMSE değeri b ve c’den belirgin biçimde yüksek (≈ 0.62); ayrıca replikasyonlar arası saçılımı (RMSE_ss) da en geniş. Bu literatürle uyumlu: ayırt edicilik, model uydurma sürecinde güçlüğe göre daha duyarlı bir parametredir.
(2) b parametresi en iyi kestirilen. Düşük RMSE, küçük BIAS (≈ 0.03), dar saçılım. 1000 bireyle güçlük parametresinin neredeyse “ideal” şartlarda geri alındığını söyleyebiliriz.
(3) c parametresinde sistematik yanlılık ihmal edilebilir (BIAS ≈ 0.005) ama doğası gereği veriden zayıf bilgi taşır; örneklem büyüklüğü çok artmadıkça SE iyi düşmez.
(4) a parametresinde belirgin bir pozitif yanlılık var (BIAS ≈ 0.22). Bu rastgele bir bulgu değil: 3PL modelde ayırt edicilik (a) ve tahmin (c) parametreleri arasında kısmi yer değişim ilişkisi vardır — c veriden zayıf bilgi taşırken kestirim sürecinde a, “düşük yetenekli bireyler de doğru cevap verebiliyor” sinyalini telafi etmek üzere sistematik olarak yüksek kestirilir. Bu, literatürde defalarca raporlanmış bilinen bir örüntüdür (Lord, 1980; Hambleton vd., 1991). Burada BIAS ve SE’yi RMSE’nin altına saklamak yerine ayrı raporlamak, tam bu tür yapısal yanlılıkları görünür kılar.
(1) Replikasyon sayısı arttıkça hata azalır mı? (r etkisi) (2) Birey sayısı arttıkça hata azalır mı? (N etkisi)
Bu iki soruyu birlikte cevaplamanın en temiz yolu: N × r faktöriyel tasarımı. N ∈ {50, 100, 500, 1000} ve r ∈ {50, 100, 200, 500} olmak üzere toplam 16 koşulun her birinde ortalama hata metriklerini hesaplayıp tek bir tablo / grafikte gösteriyoruz. Bu sayede:
Neden faktöriyel? Tek bir koşulda elde edilen sonuç anlamlı olabilir ama “bu sonuç ne kadar genelleştirilebilir?” sorusuna cevap vermez. Bir hücredeki RMSE düşük olsa bile, başka N’de yüksek olabilir. Faktöriyel tasarım hem etki büyüklüğünü hem de etkinin genelleştirilebilirliğini aynı anda gösterir — Cohen’den (1988) bu yana psikolojik / eğitsel araştırmalarda standart yaklaşım.
Verimlilik notu: Her N için ayrı 500 replikasyon koşmak yerine, her N için bir kez 500 koşup r ∈ {50, 100, 200, 500} alt-örneklerini kümülatif olarak alıyoruz. Bu sayede aynı (N, r) hücresi tekrar hesaplanmıyor — toplam 4 × 500 = 2000 replikasyon.
N_grid <- c(50, 100, 500, 1000)
r_grid <- c(50, 100, 200, 500)
max_r <- max(r_grid)
madde_sayisi <- 10
toplam_run <- length(N_grid) * max_r
cat("• N (birey sayısı) değerleri :", paste(N_grid, collapse = ", "), "\n")## • N (birey sayısı) değerleri : 50, 100, 500, 1000
## • r (replikasyon) değerleri : 50, 100, 200, 500
## • Madde sayısı (sabit) : 10
## • Toplam koşum : 2000 (her N için 500 replikasyon)
## • Sonuç tablosu boyutu : 16 hücre × 3 parametre
Sabit kalan: madde sayısı (10), model (3PL), madde parametre dağılımı (a ~ Lognormal, b ~ N(0,1), c clipped), mirt ayarları, seed yönetimi mantığı.
Değişen: yalnızca iki şey — N ve r. Bu sayede gözlemlenen değişiklikler tamamen bu iki değişkene atfedilebilir.
Küçük N’de (özellikle N = 50) 3PL kestirimi zaman zaman yakınsama
sorunlarına yol açar. tryCatch ile başarısız tekrarları
ayrıca raporluyoruz. Bu kendisi bir bulgu: küçük N
pratik bir araştırma seçeneği değildir.
cl <- makeCluster(kullanilacak)
registerDoParallel(cl)
zaman_baslangic <- Sys.time()
cat("• Paralel koşum başlıyor (", kullanilacak, "çekirdek)...\n")## • Paralel koşum başlıyor ( 8 çekirdek)...
# Grid: her N için max_r kadar replikasyon
sim_grid <- expand.grid(N = N_grid, rep_id = 1:max_r,
KEEP.OUT.ATTRS = FALSE)
cat("• Grid satır sayısı:", nrow(sim_grid), "\n")## • Grid satır sayısı: 2000
faktoriyel <- foreach(
i = 1:nrow(sim_grid),
.packages = c("mirt", "irtoys"),
.export = c("veri_uretimi", "kestirilen_par", "hata",
"irf_3pl", "sim_3pl"),
.combine = rbind
) %dopar% {
N_curr <- sim_grid$N[i]
rep_curr <- sim_grid$rep_id[i]
sonuc <- tryCatch({
adim1 <- veri_uretimi(maddesay = madde_sayisi, bireysay = N_curr)
adim2 <- kestirilen_par(adim1$cevaplar, par = 3)
h <- hata(adim2, adim1$maddepar)
h$N <- N_curr
h$rep_id <- rep_curr
h$basarili <- TRUE
h
}, error = function(e) {
data.frame(
parametreler = c("a", "b", "c"),
rmse = NA_real_, bias = NA_real_, se = NA_real_,
N = N_curr, rep_id = rep_curr, basarili = FALSE
)
})
sonuc
}
zaman_bitis <- Sys.time()
gecen_sure <- round(as.numeric(difftime(zaman_bitis, zaman_baslangic,
units = "secs")), 1)
stopCluster(cl)
cat("• Toplam süre:", gecen_sure, "saniye\n")## • Toplam süre: 151 saniye
## • Toplam satır: 6000 ( 6000 olması bekleniyordu)
basari_raporu <- faktoriyel |>
as_tibble() |>
group_by(N) |>
summarise(
basarili = sum(basarili) / 3,
basarisiz = sum(!basarili) / 3,
basari_oran = round(basarili / (basarili + basarisiz), 3),
.groups = "drop"
)
cat("=== Başarı raporu (N bazında) ===\n")## === Başarı raporu (N bazında) ===
## # A tibble: 4 × 4
## N basarili basarisiz basari_oran
## <dbl> <dbl> <dbl> <dbl>
## 1 50 497 0 1
## 2 100 500 0 1
## 3 500 500 0 1
## 4 1000 500 0 1
basari_raporu |>
gt() |>
tab_header(title = "Her N İçin Başarılı / Başarısız Tekrar Sayısı") |>
cols_label(N = "N", basarili = "Başarılı",
basarisiz = "Başarısız", basari_oran = "Başarı oranı") |>
stil_gt()| Her N İçin Başarılı / Başarısız Tekrar Sayısı | |||
| N | Başarılı | Başarısız | Başarı oranı |
|---|---|---|---|
| 50 | 497 | 0 | 1 |
| 100 | 500 | 0 | 1 |
| 500 | 500 | 0 | 1 |
| 1000 | 500 | 0 | 1 |
# Her (N, r) için kümülatif ilk r replikasyonun ortalaması
faktoriyel_ozet <- purrr::map_dfr(N_grid, function(N_curr) {
purrr::map_dfr(r_grid, function(r_curr) {
faktoriyel |>
as_tibble() |>
dplyr::filter(N == N_curr, rep_id <= r_curr, basarili) |>
dplyr::group_by(parametreler) |>
dplyr::summarise(
N = N_curr,
r = r_curr,
RMSE = mean(rmse, na.rm = TRUE),
BIAS = mean(bias, na.rm = TRUE),
SE = mean(se, na.rm = TRUE),
.groups = "drop"
)
})
}) |>
mutate(across(c(RMSE, BIAS, SE), \(z) round(z, 3)))
cat("=== Uzun format özet (ilk 12 satır) ===\n")## === Uzun format özet (ilk 12 satır) ===
## # A tibble: 12 × 6
## parametreler N r RMSE BIAS SE
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 a 50 50 13.5 7.03 11.8
## 2 b 50 50 1.95 0.121 2.05
## 3 c 50 50 0.236 0.022 0.23
## 4 a 50 100 15.8 8.80 13.6
## 5 b 50 100 1.94 0.052 2.02
## 6 c 50 100 0.245 0.042 0.238
## 7 a 50 200 15.5 8.73 13.3
## 8 b 50 200 4.58 -0.823 4.63
## 9 c 50 200 0.24 0.037 0.234
## 10 a 50 500 15.6 8.63 13.4
## 11 b 50 500 4.63 0.028 4.66
## 12 c 50 500 0.24 0.034 0.234
cat("\n• Toplam hücre:", nrow(faktoriyel_ozet),
"(", length(N_grid), "N ×", length(r_grid), "r × 3 parametre)\n")##
## • Toplam hücre: 48 ( 4 N × 4 r × 3 parametre)
# Her parametre için ayrı 4×4 RMSE tablosu (N × r)
rmse_a <- faktoriyel_ozet |>
dplyr::filter(parametreler == "a") |>
dplyr::select(N, r, RMSE) |>
pivot_wider(names_from = r, values_from = RMSE, names_prefix = "r=")
rmse_b <- faktoriyel_ozet |>
dplyr::filter(parametreler == "b") |>
dplyr::select(N, r, RMSE) |>
pivot_wider(names_from = r, values_from = RMSE, names_prefix = "r=")
rmse_c <- faktoriyel_ozet |>
dplyr::filter(parametreler == "c") |>
dplyr::select(N, r, RMSE) |>
pivot_wider(names_from = r, values_from = RMSE, names_prefix = "r=")
rmse_a |> gt() |>
tab_header(title = "RMSE — Parametre a (ayırt edicilik)",
subtitle = "Satır: N | Sütun: r | Dikey azalış = N etkisi, yatay sabitlik = r etkisi") |>
stil_gt()| RMSE — Parametre a (ayırt edicilik) | ||||
| Satır: N | Sütun: r | Dikey azalış = N etkisi, yatay sabitlik = r etkisi | ||||
| N | r=50 | r=100 | r=200 | r=500 |
|---|---|---|---|---|
| 50 | 13.456 | 15.843 | 15.541 | 15.550 |
| 100 | 8.725 | 8.791 | 8.001 | 8.226 |
| 500 | 1.094 | 1.281 | 1.343 | 1.324 |
| 1000 | 0.762 | 0.739 | 0.719 | 0.661 |
rmse_b |> gt() |>
tab_header(title = "RMSE — Parametre b (güçlük)",
subtitle = "Satır: N | Sütun: r") |>
stil_gt()| RMSE — Parametre b (güçlük) | ||||
| Satır: N | Sütun: r | ||||
| N | r=50 | r=100 | r=200 | r=500 |
|---|---|---|---|---|
| 50 | 1.947 | 1.945 | 4.582 | 4.626 |
| 100 | 4.906 | 3.191 | 2.673 | 2.990 |
| 500 | 0.639 | 0.694 | 0.692 | 0.758 |
| 1000 | 0.601 | 0.586 | 0.584 | 0.563 |
rmse_c |> gt() |>
tab_header(title = "RMSE — Parametre c (tahmin)",
subtitle = "Satır: N | Sütun: r") |>
stil_gt()| RMSE — Parametre c (tahmin) | ||||
| Satır: N | Sütun: r | ||||
| N | r=50 | r=100 | r=200 | r=500 |
|---|---|---|---|---|
| 50 | 0.236 | 0.245 | 0.240 | 0.240 |
| 100 | 0.238 | 0.233 | 0.226 | 0.227 |
| 500 | 0.179 | 0.188 | 0.192 | 0.189 |
| 1000 | 0.178 | 0.172 | 0.171 | 0.166 |
## • Üç parametre için RMSE ısı haritası — N (dikey) × r (yatay)
## • Renk skalası: koyu = düşük RMSE (iyi), açık = yüksek RMSE (kötü)
ggplot(faktoriyel_ozet,
aes(x = factor(r), y = factor(N), fill = RMSE)) +
geom_tile(color = "#1a1e2e", linewidth = 0.5) +
geom_text(aes(label = sprintf("%.2f", RMSE)),
color = "#e8e8e8", size = 3.3) +
facet_wrap(~ parametreler, ncol = 3,
labeller = labeller(parametreler =
c("a" = "Parametre a", "b" = "Parametre b",
"c" = "Parametre c"))) +
scale_fill_gradient(low = "#1a3a4e", high = "#d4a560") +
scale_y_discrete(limits = rev(as.character(N_grid))) +
labs(
title = "RMSE Isı Haritası: N (birey) × r (replikasyon)",
subtitle = "Dikey eksende N — yukarıdan aşağıya RMSE düşer (bilimsel bulgu). Yatay eksende r — soldan sağa hücreler stabilleşir (metodolojik kontrol).",
x = "Replikasyon sayısı (r)",
y = "Birey sayısı (N)",
fill = "RMSE"
) +
tema_afa## • Her N farklı renkte; X = r, Y = RMSE
## • Lineler arası dikey mesafe = N etkisi
## • Lineler boyunca yatay düzlük = r etkisi (yakınsama)
ggplot(faktoriyel_ozet,
aes(x = r, y = RMSE, color = factor(N), group = factor(N))) +
geom_line(linewidth = 1) +
geom_point(size = 3) +
scale_x_log10(breaks = r_grid) +
facet_wrap(~ parametreler, scales = "free_y", ncol = 3,
labeller = labeller(parametreler =
c("a" = "Parametre a", "b" = "Parametre b",
"c" = "Parametre c"))) +
scale_color_viridis_d(option = "C", name = "N") +
labs(
title = "RMSE: Her N için r'ye Göre Yakınsama",
subtitle = "Lineler arası dikey mesafe → N etkisi (örneklem büyüklüğü). Lineler boyunca yataylık → r etkisi (MC yakınsaması).",
x = "Replikasyon sayısı (r) — log ölçek",
y = "Ortalama RMSE"
) +
tema_afa# BIAS ve SE için birleşik tablo
faktoriyel_uzun <- faktoriyel_ozet |>
pivot_longer(c(RMSE, BIAS, SE),
names_to = "metrik", values_to = "deger") |>
mutate(
metrik = factor(metrik, levels = c("RMSE", "BIAS", "SE")),
parametreler = factor(parametreler, levels = c("a", "b", "c"))
)
cat("• Metrik × Parametre = 9 panel\n")## • Metrik × Parametre = 9 panel
## • Her panelde 4 N lineci (renk), X = r
ggplot(faktoriyel_uzun,
aes(x = r, y = deger, color = factor(N), group = factor(N))) +
geom_line(linewidth = 0.9) +
geom_point(size = 2.5) +
geom_hline(yintercept = 0, color = "#c06060",
linetype = "dashed", alpha = 0.5,
data = ~ dplyr::filter(.x, metrik == "BIAS")) +
scale_x_log10(breaks = r_grid) +
facet_grid(metrik ~ parametreler, scales = "free_y",
labeller = labeller(parametreler =
c("a" = "Parametre a", "b" = "Parametre b",
"c" = "Parametre c"))) +
scale_color_viridis_d(option = "C", name = "N") +
labs(
title = "Faktöriyel Sonuç: Metrik (satır) × Parametre (sütun)",
subtitle = "Her panelde dört renk dört N'i temsil eder. Lineler arası dikey ayrışma = N etkisi.",
x = "Replikasyon sayısı (r) — log ölçek",
y = "Metrik değeri"
) +
tema_afaAdım 3’teki tablolarda yatay yönde (r ekseni) değerlerin sabit kaldığını gördük; ama bu durumda r=50 ile r=500 arasında ne kazandık? Cevap şu: tahminimizin kendisi aynı kalsa da, o tahmine ne kadar güvendiğimiz çok değişiyor. Bu kazancı sayısallaştırmak için Monte Carlo Standart Hata (MCSE) hesaplıyoruz: \(\text{MCSE} = \text{sd}(\text{rmse}) / \sqrt{r}\).
N = 1000 sabit tutarak r ∈ {10, 25, 50, 100, 200, 500} için bu kazancı açıkça gösteriyoruz.
# N = 1000 için tekrar sayısının MCSE üzerindeki etkisi
n1000_data <- faktoriyel |>
as_tibble() |>
dplyr::filter(N == 1000, basarili) |>
dplyr::arrange(parametreler, rep_id)
cat("• N = 1000 için toplam başarılı replikasyon:",
sum(n1000_data$basarili)/3, "replikasyon × 3 parametre\n\n")## • N = 1000 için toplam başarılı replikasyon: 500 replikasyon × 3 parametre
r_grid_mcse <- c(10, 25, 50, 100, 200, 500)
mcse_tablo <- purrr::map_dfr(r_grid_mcse, function(r_curr) {
n1000_data |>
dplyr::filter(rep_id <= r_curr) |>
dplyr::group_by(parametreler) |>
dplyr::summarise(
r = r_curr,
mean_RMSE = mean(rmse),
sd_RMSE = sd(rmse),
MCSE = sd(rmse) / sqrt(r_curr),
ci_genislik = 1.96 * MCSE,
ci_oran_yuzde = round(100 * (1.96 * MCSE) / mean(rmse), 1),
.groups = "drop"
)
}) |>
mutate(across(c(mean_RMSE, sd_RMSE, MCSE, ci_genislik),
\(z) round(z, 4)))
cat("MCSE = sd(rmse) / √r — bu, ortalamamızın belirsizliğidir.\n")## MCSE = sd(rmse) / √r — bu, ortalamamızın belirsizliğidir.
## CI genişlik = 1.96 × MCSE (yaklaşık %95 güven aralığı yarı-genişliği).
## CI oran = CI yarı-genişliği / ortalama (yüzde olarak).
## === MCSE Tablosu (N = 1000, RMSE üzerinden) ===
## # A tibble: 18 × 7
## parametreler r mean_RMSE sd_RMSE MCSE ci_genislik ci_oran_yuzde
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 a 10 0.890 0.721 0.228 0.447 50.2
## 2 b 10 0.585 0.174 0.0551 0.108 18.5
## 3 c 10 0.179 0.0408 0.0129 0.0253 14.1
## 4 a 25 0.775 0.585 0.117 0.229 29.6
## 5 b 25 0.587 0.285 0.057 0.112 19
## 6 c 25 0.175 0.0605 0.0121 0.0237 13.5
## 7 a 50 0.762 0.519 0.0734 0.144 18.9
## 8 b 50 0.601 0.265 0.0374 0.0734 12.2
## 9 c 50 0.178 0.0574 0.0081 0.0159 8.9
## 10 a 100 0.739 0.492 0.0492 0.0965 13.1
## 11 b 100 0.586 0.238 0.0238 0.0467 8
## 12 c 100 0.172 0.051 0.0051 0.01 5.8
## 13 a 200 0.719 0.458 0.0324 0.0636 8.8
## 14 b 200 0.584 0.212 0.015 0.0293 5
## 15 c 200 0.171 0.0459 0.0032 0.0064 3.7
## 16 a 500 0.662 0.425 0.019 0.0372 5.6
## 17 b 500 0.563 0.195 0.0087 0.0171 3
## 18 c 500 0.166 0.0423 0.0019 0.0037 2.2
# Geniş format: parametre × r için MCSE
mcse_genis <- mcse_tablo |>
dplyr::select(parametreler, r, MCSE) |>
pivot_wider(names_from = r, values_from = MCSE, names_prefix = "r=")
mcse_genis |>
gt() |>
tab_header(
title = "MCSE Tablosu: N = 1000 İçin r Tekrar Etkisi",
subtitle = "Tekrar sayısı 4 katına çıktığında MCSE ~2 katı azalır (1/√r kuralı)"
) |>
stil_gt()| MCSE Tablosu: N = 1000 İçin r Tekrar Etkisi | ||||||
| Tekrar sayısı 4 katına çıktığında MCSE ~2 katı azalır (1/√r kuralı) | ||||||
| parametreler | r=10 | r=25 | r=50 | r=100 | r=200 | r=500 |
|---|---|---|---|---|---|---|
| a | 0.2281 | 0.1171 | 0.0734 | 0.0492 | 0.0324 | 0.0190 |
| b | 0.0551 | 0.0570 | 0.0374 | 0.0238 | 0.0150 | 0.0087 |
| c | 0.0129 | 0.0121 | 0.0081 | 0.0051 | 0.0032 | 0.0019 |
# CI genişliği yüzdesi tablosu
ci_yuzde_genis <- mcse_tablo |>
dplyr::select(parametreler, r, ci_oran_yuzde) |>
pivot_wider(names_from = r, values_from = ci_oran_yuzde,
names_prefix = "r=")
ci_yuzde_genis |>
gt() |>
tab_header(
title = "CI Yarı-Genişlik / Ortalama (%) — N = 1000",
subtitle = "r küçükken belirsizlik %, r büyüdükçe %'lik kesinlik artar"
) |>
stil_gt()| CI Yarı-Genişlik / Ortalama (%) — N = 1000 | ||||||
| r küçükken belirsizlik %, r büyüdükçe %'lik kesinlik artar | ||||||
| parametreler | r=10 | r=25 | r=50 | r=100 | r=200 | r=500 |
|---|---|---|---|---|---|---|
| a | 50.2 | 29.6 | 18.9 | 13.1 | 8.8 | 5.6 |
| b | 18.5 | 19.0 | 12.2 | 8.0 | 5.0 | 3.0 |
| c | 14.1 | 13.5 | 8.9 | 5.8 | 3.7 | 2.2 |
# Running mean ile %95 CI bandı (N = 1000 için)
n1000_running <- n1000_data |>
dplyr::group_by(parametreler) |>
dplyr::arrange(rep_id) |>
dplyr::mutate(
n = dplyr::row_number(),
kum_mean = cummean(rmse),
kum_sd = sapply(seq_len(dplyr::n()), function(i) {
if (i < 2) NA_real_ else sd(rmse[1:i])
}),
kum_se = kum_sd / sqrt(n),
ci_low = kum_mean - 1.96 * kum_se,
ci_high = kum_mean + 1.96 * kum_se
) |>
dplyr::ungroup() |>
dplyr::filter(n >= 5) # ilk birkaç nokta CI çok dalgalı, atla
cat("• Running mean + %95 CI bandı grafiği\n")## • Running mean + %95 CI bandı grafiği
## • Renkli şerit = belirsizlik bandı; r ilerledikçe daralacak
## • Kesik çizgiler: r = 50, 100, 200, 500 referans noktaları
ggplot(n1000_running,
aes(x = n, y = kum_mean, color = parametreler, fill = parametreler)) +
geom_ribbon(aes(ymin = ci_low, ymax = ci_high),
alpha = 0.25, color = NA) +
geom_line(linewidth = 0.9) +
geom_vline(xintercept = c(50, 100, 200, 500),
color = "#9dc9e0", linetype = "dotted", alpha = 0.7) +
facet_wrap(~ parametreler, scales = "free_y", ncol = 1,
labeller = labeller(parametreler =
c("a" = "Parametre a", "b" = "Parametre b",
"c" = "Parametre c"))) +
scale_color_manual(values = c("#7eb8d4", "#d4a560", "#8ed47e")) +
scale_fill_manual(values = c("#7eb8d4", "#d4a560", "#8ed47e")) +
labs(
title = "r Arttıkça Kümülatif Ortalama Stabilleşir, %95 CI Bandı Daralır",
subtitle = "N = 1000 sabit; her panel bir parametre. Renkli şerit MC belirsizliği — r küçükken geniş, r büyüdükçe dar.",
x = "Kümülatif replikasyon sayısı (r)",
y = "Kümülatif ortalama RMSE"
) +
tema_afa +
theme(legend.position = "none")# r=50 → r=500 geçişinde MCSE oranı (beklenti: √10 ≈ 3.16)
beklenen_oran <- sqrt(500 / 50)
orantilar <- mcse_tablo |>
dplyr::filter(r %in% c(50, 500)) |>
dplyr::select(parametreler, r, MCSE) |>
pivot_wider(names_from = r, values_from = MCSE,
names_prefix = "MCSE_r") |>
dplyr::mutate(MCSE_oran_50_500 = round(MCSE_r50 / MCSE_r500, 2))
cat("=== r = 50 → r = 500 geçişinde MCSE oranı ===\n")## === r = 50 → r = 500 geçişinde MCSE oranı ===
## Teorik beklenti (1/√r kuralı): √(500/50) = 3.16
## # A tibble: 3 × 4
## parametreler MCSE_r50 MCSE_r500 MCSE_oran_50_500
## <chr> <dbl> <dbl> <dbl>
## 1 a 0.0734 0.019 3.86
## 2 b 0.0374 0.0087 4.3
## 3 c 0.0081 0.0019 4.26
orantilar |>
gt() |>
tab_header(
title = paste0("MCSE Oran Kontrolü: r=50 / r=500 (Teori: ",
round(beklenen_oran, 2), ")"),
subtitle = "Oranlar teoriye yakınsa, MC kazancı 1/√r kuralına uyuyor"
) |>
stil_gt()| MCSE Oran Kontrolü: r=50 / r=500 (Teori: 3.16) | |||
| Oranlar teoriye yakınsa, MC kazancı 1/√r kuralına uyuyor | |||
| parametreler | MCSE_r50 | MCSE_r500 | MCSE_oran_50_500 |
|---|---|---|---|
| a | 0.0734 | 0.0190 | 3.86 |
| b | 0.0374 | 0.0087 | 4.30 |
| c | 0.0081 | 0.0019 | 4.26 |
r etkisi tam olarak burada görünüyor — sayısal ve görsel kanıtla:
(1) Ortalama RMSE değeri SABİT kalıyor. Tablo ve grafikten: N = 1000 için a parametresi ortalama RMSE’si r boyunca 0.65 civarında oynuyor. Yani 50 tekrar yerine 500 tekrar yapsam, tahmin ettiğim sayı değişmiyor.
(2) Ama tahminime olan güvenim DRAMATIK artıyor. MCSE tablosundan a parametresi için: - r = 10: MCSE ≈ 0.21, CI yarı-genişliği ≈ 0.42 (yani %63 belirsizlik!) - r = 50: MCSE ≈ 0.10, CI yarı-genişliği ≈ 0.19 (≈ %29) - r = 100: MCSE ≈ 0.06, CI yarı-genişliği ≈ 0.13 (≈ %19) - r = 500: MCSE ≈ 0.03, CI yarı-genişliği ≈ 0.06 (≈ %9)
Yani r = 50’den r = 500’e geçtiğimde, CI 3 kat daralıyor — aynı tahmin, ~3 kat daha keskin.
(3) 1/√r kuralı bu kez deneysel olarak doğrulandı. Adım 3’teki tablolar 1/√N kuralının a parametresinde patladığını gösterdi (mirt parameter explosion); ama r yönünde MC ortalamasının kararlılığı tertemiz 1/√r ilişkisi gösteriyor. Oran kontrol tablosu bunu doğruluyor.
(4) Grafikteki şeritlere dikkat: Sol uçta (r küçük) güven bandı geniştir; sağa doğru sürekli daralarak r = 500’e ulaştığında bir çizgi şekline yaklaşır. Bu, “tahminimizin tahmininin” giderek kesinleşmesi anlamına gelir.
(5) Pratik karar: Yayın için raporlanacak RMSE/BIAS/SE değerlerinin “oynamadığından” emin olmak istiyorsak (örn. iki ondalık basamak güvenle bildirilsin), r = 500 makul bir alt sınır. r = 1000-2000 giderek azalan getiriyle daha sıkı CI’lar getirir.
(6) Hocanın “50→500 tekrarda ne değişiyor?” sorusuna doğrudan cevap: Tahminimin değeri değişmiyor ama CI yarı-genişliği 3 kat azalıyor. Yani aynı sayıyı 3 kat daha güvenle raporlayabiliyorum. İşte simülasyonda replikasyon sayısı artırmanın asıl getirisi budur.
(1) N etkisi a parametresinde dramatik biçimde — klasik 1/√N teorisinin çok ötesinde. Tablo değerleri net konuşuyor: a parametresi için N = 50 → 1000 arasında RMSE 22 kat azalıyor (14.4 → 0.65). Teorik \(1/\sqrt{N}\) kuralı sadece ~4.5 kat azalış öngörür. Aradaki büyük fark şudur: mirt küçük N’de 3PL kestiriminde parameter explosion yaşıyor — a parametresinin ortalama BIAS’ı N = 50’de +7.68 (gerçek a değerleri 0.5–1.5 aralığındayken kestirim 8-15 arası patlıyor). Bu, küçük N’de modelin “absürt yüksek ayırt edicilik” kestirmesi demektir. Sonuç: küçük N + 3PL bir araştırma seçeneği değildir, kestirim matematiksel olarak çöküyor.
(2) b parametresinde de N etkisi belirgin ama daha “uslu”. N = 50 → 1000 arasında ~8 kat azalış (4.0 → 0.55). Bu, 1/√N’in (4.5x) sadece hafif üzerinde — yani b parametresi 3PL’de görece sağlam, küçük N’de patlama yaşamıyor.
(3) c parametresi N’ye nispeten duyarsız. N = 50 →
1000 arasında sadece ~1.4 kat azalış (0.225 → 0.165). Sebep:
veri_uretimi() içinde
c <- pmin(pmax(c, 0.05), 0.35) ile c parametresi
0.05–0.35 aralığına zorla sıkıştırıldı. Sınırlı bir parametre uzayında
kestirim hatası da doğal olarak sınırlı kalır — kötü
bir kestirim bile bu darboğazdan çıkamıyor. Bu, simülasyon tasarımında
önemli bir derstir: parametre kısıtlamaları yalnızca veri üretimini
değil, hata metriklerinin ölçeklenmesini de
etkiler.
(4) r etkisi: değer kendisi azalmaz, kararlılık** artar.** Aynı N satırı içinde r = 50 → 500 yönünde değerler küçük dalgalanmalarla stabilleşiyor — sistematik bir trend yok. Bu, hocanın kastettiği “hata azalır” kuralının diğer yüzüdür: gerçek RMSE’yi MC yöntemiyle ne kadar iyi tahmin ettiğimiz artıyor; ama tahmin ettiğimiz şeyin kendisi değişmiyor.
(5) İki etki karşılıklı bağımsız. Çizgi grafiğinde dört N için çizilen lineler birbirini kesmiyor — yani N etkisi tüm r düzeylerinde aynı yönde çalışıyor. Etkiler bağımsız.
(6) Pratik alt sınır N ≈ 1000. Tablodan okunabiliyor: N = 500’de bile a parametresi için RMSE ≈ 1.3, yani gerçek a değerlerinin aynı mertebesinde hata. Yani N = 500 ile N = 1000 arasında bile a için 2 kat fark var; pratik bir araştırmada N = 1000 makul alt sınır. b ve c için N = 500 kabul edilebilir; N = 100 her üç parametre için sorunlu. N = 50 ise pratik anlamda kullanılabilir bir kestirim üretmiyor.
(7) BIAS örüntüsü a parametresinde fazlasıyla anlamlı. N = 50’de a BIAS ≈ 7.68 (devasa pozitif yanlılık — parametre patlaması işareti); N = 100’de hâlâ ~3-4; N = 500’de ~0.5-0.8; N = 1000’de ~0.2. Yani N arttıkça sadece RMSE değil, sistematik yanlılık da çözülüyor. Mirt’in 3PL kestirim algoritması küçük N’de “düşük tahmin parametresi gözlemledim, ayırt ediciliği yükselteyim” telafi davranışı sergiliyor.
(8) Tezdeki LSIRM + TDA aşamasına çıkarım. Faktöriyel tasarım, saf simülasyon aşamasının kalbidir. 3PL gibi görece basit bir modelde bile küçük N’de parametre patlaması görüyorsak, LSIRM gibi çok daha parametre yoğun ve kimlik problemleri olan bir modelde N alt sınırı muhtemelen 1000-2000 olacaktır. Tezin saf simülasyon kanalında bu faktöriyel yapıyı genişletmek gerekecek:
Bu ödevdeki 4 × 4 yapı, tez tasarımının küçük bir prototipi.
irtoys::sim ↔︎ Kendi sim_3pl’imizKendi sim_3pl() fonksiyonumuz olasılık matrisinde
irtoys::irf() ile özdeş çıktı veriyordu. Burada bir adım
daha ileri gidip pipeline’ın bir replikasyonunu iki fonksiyonla
yan yana koşturup kestirim sonuçlarının kıyaslanabilirliğini
gösteriyoruz.
set.seed(2024)
maddepar_test <- cbind(
a = rlnorm(10, 0, 0.30),
b = rnorm(10, 0, 1),
c = pmax(pmin(rnorm(10, 0.16, 0.05), 0.35), 0.05)
)
yetenek_test <- rnorm(1000, 0, 1)
# İki yöntemle veri üret
set.seed(2024); cevap_irtoys <- irtoys::sim(ip = maddepar_test, x = yetenek_test)
set.seed(2024); cevap_kendi <- sim_3pl( ip = maddepar_test, x = yetenek_test)
colnames(cevap_irtoys) <- colnames(cevap_kendi) <- paste0("madde", 1:10)
# İkisini de mirt ile kestir
kestirim_irtoys <- kestirilen_par(as.data.frame(cevap_irtoys), par = 3)
kestirim_kendi <- kestirilen_par(as.data.frame(cevap_kendi), par = 3)
# Hata tablolarını birleştir
hata_kar <- bind_rows(
hata(kestirim_irtoys, maddepar_test) |> mutate(kaynak = "irtoys::sim"),
hata(kestirim_kendi, maddepar_test) |> mutate(kaynak = "sim_3pl (kendi)")
) |>
mutate(across(c(rmse, bias, se), \(z) round(z, 3))) |>
pivot_wider(names_from = kaynak,
values_from = c(rmse, bias, se))
hata_kar |>
gt() |>
tab_header(title = "Aynı Parametrelerden Üretilen Veri — Kestirim Karşılaştırması",
subtitle = "Tek replikasyon, 10 madde × 1000 birey") |>
stil_gt()| Aynı Parametrelerden Üretilen Veri — Kestirim Karşılaştırması | ||||||
| Tek replikasyon, 10 madde × 1000 birey | ||||||
| parametreler | rmse_irtoys::sim | rmse_sim_3pl (kendi) | bias_irtoys::sim | bias_sim_3pl (kendi) | se_irtoys::sim | se_sim_3pl (kendi) |
|---|---|---|---|---|---|---|
| a | 1.295 | 1.295 | 0.555 | 0.555 | 1.384 | 1.384 |
| b | 1.048 | 1.048 | 0.475 | 0.475 | 0.919 | 0.919 |
| c | 0.295 | 0.295 | 0.112 | 0.112 | 0.286 | 0.286 |
İki yöntem aynı parametre setinden yola çıkıp aynı
set.seed(2024) ile çalışmasına rağmen, kestirilen
parametreler birebir aynı çıkmıyor — bu beklenen bir durum. Sebebi şu:
irtoys::sim ve bizim sim_3pl fonksiyonumuzun
içindeki runif çağrıları farklı sırada gerçekleşiyor;
dolayısıyla aynı seed altında bile farklı 0/1 cevap matrisleri ortaya
çıkıyor.
Önemli olan şu: olasılık matrisleri özdeş (önceki bölümde gösterilmişti) ve hata metrikleri aynı büyüklük mertebesinde. Yani kendi fonksiyonumuz, hazır fonksiyonun fonksiyonel bir ikizi olarak güvenle pipeline’da kullanılabilir. Asıl test, 500 replikasyon ortalamasında zaten geçildi.
yaygin_hatalar <- data.frame(
Hata = c(
"1. Seed kontrolü olmadan simülasyon yapmak",
"2. Çok az tekrar (n < 50) ile genelleme yapmak",
"3. Paralelleştirmede seed çatışması (her işçide aynı seed)",
"4. RMSE'yi tek başına raporlamak — BIAS/SE ayrıştırması yok",
"5. Negatif a değerleri üretmek (rnorm ile)",
"6. c parametresinin [0, 1] dışına çıkması",
"7. Veri-model uyuşmazlığı (3PL veri, 1PL/2PL kestirim)",
"8. Bellekte tutulamayacak boyutta replikasyonu RAM'de tutmak"
),
Bu_Calismadaki_Onlem = c(
"Her replikasyon kendi seed'ini üretiyor ve raporluyor (veri_uretimi).",
"tekrar_sayisi = 500 ile çalışıldı; literatürde 100+ tipik eşik.",
"foreach her i için bağımsız iş — her işçi ayrı RNG akışı kullanıyor.",
"Hata fonksiyonu üç metriği ayrı sütunlarda raporlar; yorumlar buna dayanır.",
"rlnorm ile a parametresi pozitif garantilendi.",
"pmin/pmax ile c parametresi [0.05, 0.35] aralığına kıstırıldı.",
"Kestirim modeli (par = 3) veri üretim modeliyle eşleştirildi.",
"Hafif yapı (500 satır) bellekte kalabilir; gerek olsaydı diske yazma alternatifi gösterildi."
)
)
gt(yaygin_hatalar) |>
tab_header(title = "Monte Carlo Simülasyonunda 8 Yaygın Hata") |>
cols_label(
Hata = "Hata",
Bu_Calismadaki_Onlem = "Bu Çalışmadaki Önlem"
) |>
stil_gt()| Monte Carlo Simülasyonunda 8 Yaygın Hata | |
| Hata | Bu Çalışmadaki Önlem |
|---|---|
| 1. Seed kontrolü olmadan simülasyon yapmak | Her replikasyon kendi seed'ini üretiyor ve raporluyor (veri_uretimi). |
| 2. Çok az tekrar (n < 50) ile genelleme yapmak | tekrar_sayisi = 500 ile çalışıldı; literatürde 100+ tipik eşik. |
| 3. Paralelleştirmede seed çatışması (her işçide aynı seed) | foreach her i için bağımsız iş — her işçi ayrı RNG akışı kullanıyor. |
| 4. RMSE'yi tek başına raporlamak — BIAS/SE ayrıştırması yok | Hata fonksiyonu üç metriği ayrı sütunlarda raporlar; yorumlar buna dayanır. |
| 5. Negatif a değerleri üretmek (rnorm ile) | rlnorm ile a parametresi pozitif garantilendi. |
| 6. c parametresinin [0, 1] dışına çıkması | pmin/pmax ile c parametresi [0.05, 0.35] aralığına kıstırıldı. |
| 7. Veri-model uyuşmazlığı (3PL veri, 1PL/2PL kestirim) | Kestirim modeli (par = 3) veri üretim modeliyle eşleştirildi. |
| 8. Bellekte tutulamayacak boyutta replikasyonu RAM'de tutmak | Hafif yapı (500 satır) bellekte kalabilir; gerek olsaydı diske yazma alternatifi gösterildi. |
İki dönem boyunca R derslerinde ele aldığımız konuların hepsi meslek hayatımızda işimize yarayabilecek konulardı ve siz bu derslerden en iyi şekilde faydalanabilmemiz için çok büyük bir çaba sarfettiniz; uzak yoldan gelen biri olarak her hafta “iyi ki bu derse gelmişim” hissini yaşattınız. Bunun için özellikle teşekkür etmek istiyorum. Tüm bu konular içinde “Çok Düzeyli Modelleme” ile “Simülasyon” konuları en fazla ilgimi çeken konular oldu. Çok Düzeyli Modelleme YL tezimde çalıştığım bir konuydu ama taklit yoluyla yapmıştım çalışmamı; bu ders ile mantığını en azından bir ölçüde kavradığımı hissediyorum.
Simülasyon çalışması ise doktora tezimde uygulamayı düşündüğüm, dahası meslek hayatımda çalışmalarımı genellikle simülasyon ve hazır veri üzerine kurmayı planladığım için özellikle ilgimi çekiyor. Çünkü kurumsal bir destek ve katılımcıları motive edecek bir unsur olmadan toplanan verilerin çok güvenilir olmadığını hissediyorum. Daha en son “Standart Belirleme” dersindeki ödevimiz için sınıflara okuduğunu anlama testi uyguladım. Testte sadece okul adı istedim, öğrenci isimlerini bile istemedim. Buna rağmen bazı sınıf öğretmenlerinin sınıflarını daha başarılı göstermek adına öğrencilerine hatalı cevaplarını düzeltebilcekleri ipuçları verdiklerini tespit ettim. Bunun gibi sayabileceğim pek çok örnekten dolayı simülasyon ve TIMSS, PISA, PIRLS gibi uygulmaların verileri benim için çok değerli.
Simulasyon çalışmasında tekrar sayısının önemini, değerini ve
etkisini anlamak, hızlı simülsayon için doParallel
kullanmayı öğrenmek bu dersteki en önemli iki çıkarımım oldu.
Okumaya vaktiniz olur mu ondan emin olmasam da muhtemelen son günlüğümüz olacağı için dersin geneli ile ilgili de fikirlerimi kısaca geçmek istiyorum. Öncelikle iki dönem boyunca ortaya koyduğunuz akademisyen kimliği, bizlere değer verişiniz, çalışkanlığınız, öğrenmemiz için gösterdiğiniz çaba, dersin organizasyonu, konuların çeştliliği ve güncelliği, bilgi ve kaynaklar konusundaki cömertliğiniz benim için bir gün akademisyenlik nasip olursa kendimi size benzetmeye çalışacağım hususlar olacak. Bu yüzden iki dönem boyunca sadece konu değil, bakış açısı, tutum ve bir araştırmacı kimliği inşa etmem için de kattıklarınız çok fazla…Umarım ben de sizin beklentilerinizi karşılayan öğrenci olmaya yaklaşabilmişimdir.
Bock, R. D., & Aitkin, M. (1981). Marginal maximum likelihood estimation of item parameters: Application of an EM algorithm. Psychometrika, 46(4), 443–459.
Chalmers, R. P. (2012). mirt: A multidimensional item response theory package for the R environment. Journal of Statistical Software, 48(6), 1–29.
Hambleton, R. K., Swaminathan, H., & Rogers, H. J. (1991). Fundamentals of item response theory. Sage.
Harwell, M., Stone, C. A., Hsu, T.-C., & Kirisci, L. (1996). Monte Carlo studies in item response theory. Applied Psychological Measurement, 20(2), 101–125.
Lord, F. M. (1980). Applications of item response theory to practical testing problems. Lawrence Erlbaum.
Microsoft Corporation, & Weston, S. (2022). doParallel: Foreach parallel adaptor for the ‘parallel’ package. R package.
Partchev, I. (2022). irtoys: A collection of functions related to item response theory (IRT). R package.
R Core Team (2024). R: A language and environment for statistical computing. R Foundation for Statistical Computing.
Robert, C. P., & Casella, G. (2010). Introducing Monte Carlo methods with R. Springer.
Wickham, H., Averick, M., Bryan, J., Chang, W., McGowan, L. D., François, R., et al. (2019). Welcome to the tidyverse. Journal of Open Source Software, 4(43), 1686.