Code
library(tidyverse)
library(stats)
library(broom)Tablice wielodzielcze: Wnioskowanie dokładne, symulacyjne oraz testy z hipotezami złożonymi
Analiza tablic wielodzielczych (contingency tables) jest kluczowym elementem statystyki w badaniu zależności między zmiennymi jakościowymi. W klasycznym podejściu często stosuje się test \(\chi^2\) Persona, jednak posiada on ograniczenia przy małych liczebnościach oczekiwanych.
W niniejszym projekcie skupię się na alternatywnych metodach wnioskowania: od testu dokładnego Fishera, poprzez metody symulacyjne (Monte Carlo), aż po konstruowanie specyficznych testów permutacyjnych dla hipotez kierunkowych oraz złożonych (logika “i” / “lub”).
library(tidyverse)
library(stats)
library(broom)Tablica 2x2 to najprostszy przypadek badania zależności, gdzie sprawdzamy np. czy wystąpienie efektu zależy od przynależności do grupy.
Test dokładny Fishera opiera się na rozkładzie hipergeometrycznym. W przeciwieństwie do testu \(\chi^2\), nie opiera się on na założeniach asymptotycznych, co czyni go idealnym dla małych prób. Zakładając ustalone sumy brzegowe, prawdopodobieństwo otrzymania danej konfiguracji liczb w tablicy \(2 \times 2\) wyraża się wzorem:\[P = \frac{\binom{a+b}{a} \binom{c+d}{c}}{\binom{n}{a+c}}\]
#| code-fold: show
tablica_2x2 <- matrix(c(8, 2, 3, 9), nrow = 2,
dimnames = list(Grupa = c("Lek", "Placebo"),
Wynik = c("Poprawa", "Brak")))
tidy(fisher.test(tablica_2x2))# A tibble: 1 × 6
estimate p.value conf.low conf.high method alternative
<dbl> <dbl> <dbl> <dbl> <chr> <chr>
1 10.4 0.0300 1.19 158. Fisher's Exact Test for Count… two.sided
x_wykres <- 0:11
gęstość_hyper <- dhyper(x_wykres, 11, 11, 10)
df_fisher <- data.frame(Sukcesy = x_wykres, P = gęstość_hyper)
ggplot(df_fisher, aes(x = Sukcesy, y = P)) +
geom_col(fill = kolor_niebieski, alpha = 0.6) +
geom_col(data = filter(df_fisher, Sukcesy == 8), fill = kolor_czerwony) +
labs(title = "Rozkład prawdopodobieństwa w teście Fishera",
subtitle = "Czerwony słupek oznacza naszą zaobserwowaną wartość (8 sukcesów)",
x = "Liczba sukcesów w grupie 'Lek'", y = "Prawdopodobieństwo") +
theme(
panel.background = element_rect(fill = tlo_wykresu),
plot.background = element_rect(fill = tlo_wykresu),
text = element_text(color = kolor_tekstu_i_osi),
axis.text = element_text(color = kolor_tekstu_i_osi)
)Gdy tablica jest większa lub chcemy uniknąć złożoności obliczeniowej rozkładu hipergeometrycznego dla dużych danych, stosujemy symulacje. Generujemy tysiące losowych tablic o tych samych sumach brzegowych i sprawdzamy, jaki odsetek z nich wykazuje silniejszą zależność niż nasza tablica zaobserwowana.
#| code-fold: show
test_sim <- chisq.test(tablica_2x2, simulate.p.value = TRUE, B = 10000)
test_sim
Pearson's Chi-squared test with simulated p-value (based on 10000
replicates)
data: tablica_2x2
X-squared = 6.6, df = NA, p-value = 0.0296
Standardowe testy (jak \(\chi^2\)) badają ogólną niezależność (hipoteza dwustronna). Często jednak badacz stawia hipotezę kierunkową, np.: “Prawdopodobieństwo sukcesu w Grupie A jest większe niż w Grupie B”.
W przypadku tablic \(2 \times 2\) możemy badać iloraz szans (Odds Ratio). Jeśli \(OR > 1\), sugeruje to pozytywną zależność. Poniższy kod obrazuje, jak za pomocą symulacji ocenić istotność jednostronną.
#| code-fold: show
or_obs <- (tablica_2x2[1,1] * tablica_2x2[2,2]) / (tablica_2x2[1,2] * tablica_2x2[2,1])
sim_kierunkowy <- function(tab, n_sim = 5000) {
successes <- sum(tab[,1])
total_a <- sum(tab[1,])
total_b <- sum(tab[2,])
n_total <- sum(tab)
wyniki_or <- replicate(n_sim, {
draw_a <- rhyper(1, successes, n_total - successes, total_a)
draw_b <- successes - draw_a
(draw_a * (total_b - draw_b)) / ((total_a - draw_a) * draw_b)
})
p_value <- mean(wyniki_or >= or_obs, na.rm = TRUE)
return(list(OR_obs = or_obs, p_val = p_value))
}
sim_kierunkowy(tablica_2x2)$OR_obs
[1] 12
$p_val
[1] 0.0148
#| code-fold: show
n_sim_plot <- 10000
successes <- sum(tablica_2x2[,1]); total_a <- sum(tablica_2x2[1,]);
total_b <- sum(tablica_2x2[2,]); n_total <- sum(tablica_2x2)
sim_ors <- replicate(n_sim_plot, {
d_a <- rhyper(1, successes, n_total - successes, total_a)
d_b <- successes - d_a
(d_a * (total_b - d_b)) / ((total_a - d_a) * d_b)
})
df_or <- data.frame(OR = sim_ors[is.finite(sim_ors)])
ggplot(df_or, aes(x = OR)) +
geom_density(fill = kolor_niebieski, alpha = 0.4, color = kolor_niebieski) +
geom_vline(xintercept = or_obs, color = kolor_czerwony, linetype = "dashed", size = 1) +
labs(title = "Symulowany rozkład Ilorazu Szans (OR) pod H0",
subtitle = "Linia przerywana to zaobserwowany efekt (OR > 1)",
x = "Odds Ratio", y = "Gęstość") +
theme(
panel.background = element_rect(fill = tlo_wykresu),
plot.background = element_rect(fill = tlo_wykresu),
text = element_text(color = kolor_tekstu_i_osi),
axis.text = element_text(color = kolor_tekstu_i_osi)
)Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
W tablicach wielowymiarowych (np. \(2 \times 2 \times 2\)) lub przy wielu tablicach \(2 \times 2\) możemy chcieć sprawdzić hipotezy złożone, np. “Zależność występuje w mieście X LUB w mieście Y”. ### 4.1 Symulacja dla hipotezy złożonej “LUB” Załóżmy badanie w dwóch oddzielnych centrach medycznych. Interesuje nas, czy w którymkolwiek z nich lek zadziałał istotnie.
centrum_1 <- matrix(c(10, 2, 4, 8), 2)
centrum_2 <- matrix(c(5, 5, 6, 4), 2)
n_iter <- 5000
istotnosc_lub <- replicate(n_iter, {
s1 <- chisq.test(matrix(rbinom(4, 20, 0.5), 2), simulate.p.value = T)$p.value
s2 <- chisq.test(matrix(rbinom(4, 20, 0.5), 2), simulate.p.value = T)$p.value
s1 < 0.05 | s2 < 0.05
})
wynik_lub <- mean(istotnosc_lub)Zjawisko to wiąże się z problemem porównań wielokrotnych – prawdopodobieństwo popełnienia błędu I rodzaju wzrasta, gdy testujemy alternatywę “LUB”. ## 5. Wizualizacja wyników symulacji Poniżej przedstawiam rozkład statystyki \(\chi^2\) uzyskany drogą symulacji dla tablicy o zadanych brzegach, w porównaniu z teoretycznym rozkładem gęstości.
n_sim <- 5000
statystyki <- replicate(n_sim, {
m <- r2dtable(1, rowSums(tablica_2x2), colSums(tablica_2x2))[[1]]
chisq.test(m, correct = FALSE)$statistic
})
df_sim <- data.frame(chi_val = statystyki)
ggplot(df_sim, aes(x = chi_val)) +
geom_histogram(aes(y = after_stat(density)), bins = 30, fill = "steelblue", alpha = 0.6) +
stat_function(fun = dchisq, args = list(df = 1), color = "red", linewidth = 1) +
labs(title = "Symulowany vs Teoretyczny rozkład Chi-kwadrat (df=1)",
x = "Wartość statystyki", y = "Gęstość") +
theme(
panel.background = element_rect(fill = tlo_wykresu),
plot.background = element_rect(fill = tlo_wykresu),
text = element_text(color = kolor_tekstu_i_osi),
axis.text = element_text(color = kolor_tekstu_i_osi)
)Wnioskowanie dla tablic wielodzielczych przy użyciu metod nieklasycznych pozwala na:
Metody symulacyjne są szczególnie cenne w dobie dużej mocy obliczeniowej, gdyż pozwalają na “zobaczenie” rozkładu statystyki bez konieczności spełniania rygorystycznych założeń testów parametrycznych.