Pokaż kod R
library(knitr)
library(rmarkdown)
library(ggplot2)library(knitr)
library(rmarkdown)
library(ggplot2)Celem projektu jest stworzenie generatorów liczb (pseudo)losowych w programie R, tak aby nie polegać tylko na gotowych funkcjach, ale zrozumieć ich działanie od podstaw.
Całość pracy służy sprawdzeniu, czy takie własne rozwiązania są wystarczająco dokładne i wiarygodne.
set.seed(123)Metoda biegunowa (Polar Method) jest udoskonaloną wersją algorytmu Boxa i Mullera. Wykorzystuje ona fakt, że rozkład normalny jest radialnie symetryczny. Zamiast bezpośredniej transformacji współrzędnych biegunowych przy użyciu funkcji trygonometrycznych, stosuje się metodę akceptacji-odrzucenia Johnsona-Ramberga. Polega ona na wylosowaniu wektora jednostajnego z koła i przeskalowaniu go przez odpowiedni promień \(R\), co pozwala na uzyskanie pary niezależnych zmiennych normalnych.
generate_normal <- function(n) {
res <- numeric(n)
count <- 0
while(count < n) {
v1 <- runif(1, -1, 1)
v2 <- runif(1, -1, 1)
s <- v1^2 + v2^2
if (s > 0 && s < 1) {
multiplier <- sqrt(-2 * log(s) / s)
res[count + 1] <- v1 * multiplier
if (count + 2 <= n) {
res[count + 2] <- v2 * multiplier
}
count <- count + 2
}
}
return(res)
}
df_norm <- data.frame(wartosc = generate_normal(10000))
ggplot(df_norm, aes(x = wartosc)) +
geom_histogram(aes(y = after_stat(density)), bins = 45,
fill = "steelblue", color = "white", alpha = 0.7) +
stat_function(fun = dnorm, color = "red", linewidth = 1) +
labs(title = "Rozkład Normalny",
x = "Wylosowana wartość", y = "Gęstość") +
theme_minimal()Histogram prawie idealnie pokrywa się z czerwoną krzywą teoretyczną N(0,1).
Źródło: Devroye (1986), Rozdział IX.3.2, strona 236. Link do książki
Rozkład Beta jest niezwykle wszechstronnym rozkładem ciągłym, który w zależności od parametrów kształtu a oraz b może przyjmować formy od U-kształtnych po unimodalne (dzwonowate). W przypadku parametrów będących liczbami całkowitymi, najskuteczniejszą realizacją wymogu metody superpozycji jest wykorzystanie właściwości statystyk pozycyjnych.
Metoda ta polega na “złożeniu” wyniku końcowego z próby n = a + b - 1 niezależnych zmiennych o rozkładzie jednostajnym U(0,1). Zamiast stosować transformacje algebraiczne, algorytm porządkuje wylosowane wartości i wybiera a-tą z nich. Zgodnie z teorią, tak wybrany składnik ma gęstość dokładnie odpowiadającą rozkładowi Beta.
beta <- function(n, a, b) {
results <- replicate(n, {
u_samples <- runif(a + b - 1)
sort(u_samples)[a]
})
return(results)
}
n_samples <- 10000
x_beta <- beta(n_samples, 2, 5)
df_beta <- data.frame(wartosc = x_beta)
ggplot(df_beta, aes(x = wartosc)) +
geom_histogram(aes(y = after_stat(density)), bins = 40,
fill = "olivedrab4", color = "white", alpha = 0.7) +
stat_function(fun = dbeta, args = list(shape1 = 2, shape2 = 5),
color = "red", linewidth = 1) +
labs(title = "Rozkład Beta: Metoda Superpozycji (Statystyki Pozycyjne)",
x = "Wylosowana wartość", y = "Gęstość") +
theme_minimal()Histogram prezentuje dane wygenerowane metodą superpozycji dla parametrów a=2, b=5.Wybór drugiej najmniejszej wartości z sześciu niezależnych losowań jednostajnych pozwolił na precyzyjne odtworzenie teoretycznego kształtu rozkładu Beta.
Źródło : Devroye (1986), Strony: 424, 429, 434 (rozdział IX), 66 (rozdział II) Link do książki
Rozkład Cauchy’ego (rozpatrywany jako szczególny przypadek rozkładu t-Studenta z jednym stopniem swobody) generowany jest w oparciu o metodę składaną. Metoda ta polega na dekompozycji gęstości na mniejsze składowe, co pozwala na optymalizację procesu losowania.
Zgodnie z Twierdzeniem 5.1 (s. 448), gęstość rozkładu można zdominować przez inną funkcję gęstości, co umożliwia zastosowanie algorytmów typu composition/rejection. W praktyce oznacza to, że rozkład Cauchy’ego “składa się” z różnych obszarów (np. części centralnej oraz ogonów), dla których dobierane są osobne techniki losowania. Podejście to, zaproponowane m.in. przez Kindermana, Monahana i Ramage’a (s. 446), pozwala uzyskać lepszą wydajność niż w przypadku zwykłej metody odrzucenia.
cauchy <- function(n) {
res <- numeric(n)
for (i in 1:n) {
if (runif(1) < 0.5) {
repeat {
u <- runif(1, -1, 1)
v <- runif(1)
if (v <= 1 / (1 + u^2)) {
res[i] <- u
break
}
}
} else {
u <- runif(1, -1, 1)
res[i] <- 1 / u
}
}
return(res)
}
n_samples <- 10000
raw_data <- cauchy(n_samples)
filtered_data <- raw_data[raw_data > -10 & raw_data < 10]
df_cauchy <- data.frame(wartosc = filtered_data)
ggplot(df_cauchy, aes(x = wartosc)) +
geom_histogram(aes(y = after_stat(density)), bins = 100,
fill = "tomato", color = "white", alpha = 0.7) +
stat_function(fun = dcauchy, color = "black", linewidth = 1) +
coord_cartesian(xlim = c(-10, 10)) +
labs(title = "Rozkład Cauchy'ego: Metoda Superpozycji",
x = "Wartość", y = "Gęstość") +
theme_minimal()Uzyskany dzwon o bardzo szerokiej podstawie potwierdza skuteczność metody składanej. Dzięki superpozycji obszaru centralnego i ogonów, generator jest stabilny i precyzyjnie oddaje teoretyczne właściwości rozkładu \(t\)-Studenta dla jednego stopnia swobody.
Źródło: Devroye (1986), Strony: 446, 448 (rozdział IX) Link do książki
Podstawą generowania rozkładu dwumianowego jest strona 521. Kluczowym elementem jest tam Lemma 4.1. Zgodnie z tą teorią, rozkład dwumianowy powstaje jako bezpośrednia superpozycja n niezależnych prób Bernoulliego. W praktyce oznacza to, że wynik X nie jest losowany “jednorazowo”, ale jest składany z serii n niezależnych testów. Jeśli wylosowana liczba U jest mniejsza od prawdopodobieństwa p, zaliczamy to jako sukces (1). Suma tych sukcesów to wynik końcowy.
Dodatkowo, Lemma 4.3 na tej samej stronie potwierdza, że jeśli mamy kilka niezależnych zmiennych dwumianowych o tym samym p, to ich suma również ma rozkład dwumianowy. To ostatecznie potwierdza, że superpozycja jest naturalną metodą dla tego rozkładu.
binomial <- function(num_samples, n, p) {
results <- replicate(num_samples, {
u <- runif(n)
sum(u < p)
})
return(results)
}
n_trials <- 20
p_prob <- 0.5
df_binom <- data.frame(wartosc = binomial(10000, n_trials, p_prob))
ggplot(df_binom, aes(x = wartosc)) +
geom_bar(aes(y = after_stat(count)/sum(after_stat(count))),
fill = "steelblue", color = "white") +
scale_x_continuous(breaks = seq(0, n_trials, 2)) +
labs(title = "Rozkład Dwumianowy",
subtitle = paste0("Suma n = ", n_trials, " niezależnych prób Bernoulliego"),
x = "Liczba sukcesów (X)", y = "Prawdopodobieństwo") +
theme_minimal()Słupki prawdopodobieństwa tworzą symetryczny kształt wokół wartości 10, co wynika z parametrów n=20 i p=0.5. Wykorzystanie superpozycji n prób jednostajnych pozwala na dokładne odwzorowanie dyskretnej natury rozkładu bez użycia skomplikowanych metod transformacyjnych.
Źródło: Devroye (1986), Strona: 521 (rozdział X) Link do książki
Rozkład Poissona z parametrem \(\lambda\) można wygenerować poprzez superpozycję niezależnych czasów oczekiwania między zdarzeniami. Występują dwa równoważne podejścia:
Suma czasów wykładniczych: Składamy kolejne zmienne wykładnicze E tak długo, aż ich suma przekroczy wartość \(\lambda\) . Liczba pełnych zdarzeń przed przekroczeniem progu to nasz wynik X.
Iloczyn zmiennych jednostajnych (Lemma 3.3): Jest to matematyczny odpowiednik powyższej metody. Losujemy zmienne jednostajne U i tworzymy ich iloczyn. Wynikiem jest najmniejsza liczba całkowita X taka, że iloczyn (X+1) zmiennych jest mniejszy niż e ^ (-\(\lambda\) ).
poisson_composition <- function(num_samples, lambda) {
results <- numeric(num_samples)
threshold <- exp(-lambda)
for (i in 1:num_samples) {
x <- 0
prod_val <- 1
while (TRUE) {
u <- runif(1)
prod_val <- prod_val * u
if (prod_val > threshold) {
x <- x + 1
} else {
results[i] <- x
break
}
}
}
return(results)
}
lambda_val <- 4
df_poisson <- data.frame(wartosc = poisson_composition(10000, lambda_val))
ggplot(df_poisson, aes(x = wartosc)) +
geom_bar(aes(y = after_stat(count)/sum(after_stat(count))),
fill = "darkorchid4", color = "white", alpha = 0.7) +
scale_x_continuous(breaks = 0:15) +
labs(title = "Rozkład Poissona",
subtitle = paste0("Iloczyn zmiennych jednostajnych dla lambda = ", lambda_val),
x = "Wartość X", y = "Prawdopodobieństwo") +
theme_minimal()Słupki histogramu skupiają się wokół wartości 4, co potwierdza poprawność implementacji metody superpozycji iloczynów. Ponieważ rozkład Poissona jest dyskretny, wykres słupkowy pokazuje prawdopodobieństwo wystąpienia każdej liczby całkowitej. Złożenie niezależnych zmiennych jednostajnych w jeden iloczyn pozwala na precyzyjne odtworzenie teoretycznej gęstości tego rozkładu.
Źródło: Devroye (1986), Strona: 504 (rozdział X) Link do książki
Zgodnie z algorytmem przedstawionym przez Devroye’a na stronie 525, rozkład ujemny dwumianowy można generować poprzez superpozycję niezależnych zmiennych o rozkładzie geometrycznym.
W tej metodzie wynik końcowy X jest efektem sumowania kolejnych wartości G (zmiennych geometrycznych o parametrze p). Każda taka zmienna reprezentuje liczbę prób (porażek) do wystąpienia pojedynczego sukcesu. Proces ten powtarzamy aż do osiągnięcia założonej liczby sukcesów. Jest to metoda superpozycji, ponieważ zmienna wyjściowa nie jest wynikiem bezpośredniej transformacji, lecz składa się z sumy niezależnie generowanych składników geometrycznych.
neg_binomial <- function(num_samples, r, p) {
results <- numeric(num_samples)
for (i in 1:num_samples) {
sum_g <- 0
successes <- 0
while (successes < r) {
g <- 0
while (runif(1) > p) {
g <- g + 1
}
sum_g <- sum_g + g
successes <- successes + 1
}
results[i] <- sum_g
}
return(results)
}
r_param <- 3
p_param <- 0.4
df_neg_bin <- data.frame(wartosc = neg_binomial(10000, r_param, p_param))
ggplot(df_neg_bin, aes(x = wartosc)) +
geom_bar(aes(y = after_stat(count)/sum(after_stat(count))),
fill = "indianred4", color = "white") +
scale_x_continuous(limits = c(-0.5, 25)) +
labs(title = "Rozkład Ujemny Dwumianowy",
subtitle = paste0("Suma r = ", r_param, " rozkładów geometrycznych (p = ", p_param, ")"),
x = "Liczba porażek (X)", y = "Prawdopodobieństwo") +
theme_minimal()Kształt histogramu pokazuje, że prawdopodobieństwo uzyskania małej liczby porażek przy r = 3 sukcesach jest niskie, a masa rozkładu przesuwa się w stronę wartości oczekiwanej.
Źródło: Devroye (1986), Strona: 525 (rozdział X) Link do książki
Metoda ilorazu zmiennych jednostajnych (Ratio-of-Uniforms) to technika generowania zmiennych losowych polegająca na losowaniu punktu z obszaru dwuwymiarowego. Jeśli punkt (u,v) zostanie wylosowany jednostajnie z obszaru zdefiniowanego przez gęstość f, to iloraz X = v/u będzie miał rozkład o tej właśnie gęstości.
Na stronie 203 Devroye analizuje metodę ROU dla rozkładu wykładniczego o gęstości \(f(x) = e^{-x}\). Aby zrealizować ten algorytm, musimy zamknąć obszar akceptacji w prostokącie. Dla tego konkretnego rozkładu, granice prostokąta wyznaczone są przez wartości:
u w przedziale [0, 1]
v w przedziale [0, 2/e]
Algorytm polega na wylosowaniu pary (u,v) z tego prostokąta i zaakceptowaniu wartości x = v/u, jeśli spełniony jest warunek brzegowy gęstości.
rou <- function(num_samples) {
results <- numeric(num_samples)
v_max <- 2 / exp(1)
count <- 0
while (count < num_samples) {
u <- runif(1, 0, 1)
v <- runif(1, 0, v_max)
x <- v / u
if (x <= -2 * log(u)) {
count <- count + 1
results[count] <- x
}
}
return(results)
}
df_rou_exp <- data.frame(wartosc = rou(10000))
ggplot(df_rou_exp, aes(x = wartosc)) +
geom_histogram(aes(y = after_stat(density)), bins = 50,
fill = "darkseagreen4", color = "white", alpha = 0.7) +
stat_function(fun = dexp, color = "red", linewidth = 1) +
labs(title = "Rozkład Wykładniczy: Metoda ROU",
subtitle = "u_max=1 i v_max=2/e",
x = "Wartość X", y = "Gęstość") +
theme_minimal()Czerwona linia reprezentuje teoretyczną gęstość rozkładu wykładniczego. Zbieżność histogramu z linią teoretyczną potwierdza poprawność geometrycznego wyznaczenia obszaru akceptacji. Metoda ROU pozwala na uniknięcie bezpośredniego odwracania dystrybuanty.
Źródło: Devroye (1986), Strona: 194-205 (rozdział IV) Link do książki
Test widmowy (spektralny) jest kluczowym narzędziem diagnostycznym, ponieważ pozwala na wykrycie regularności w ciągu liczb, które powinny być niezależne. Wadliwe generatory wykazują tendencję do układania tych punktów na równoległych hiperpłaszczyznach.
n_test <- 5000
set.seed(123)
probka_autorska <- rou(n_test)
df_widmo <- data.frame(
x_i = probka_autorska[1:(n_test-1)],
x_next = probka_autorska[2:n_test]
)
ggplot(df_widmo, aes(x = x_i, y = x_next)) +
geom_point(alpha = 0.2, size = 0.5, color = "midnightblue") +
labs(title = "Test widmowy: Weryfikacja niezależności (wymiar t=2)",
subtitle = "Analiza struktury kratowej",
x = expression(X[i]), y = expression(X[i+1])) +
theme_minimal()Wizualizacja testu widmowego nie wykazuje istnienia struktur kratowych, co pozwala wykluczyć korelacje szeregowe między kolejnymi losowaniami.
Weryfikacja jakości zaimplementowanego generatora ROU polega na zestawieniu wyników empirycznych z parametrami teoretycznymi.
Rozkład Normalny: Weryfikujemy zgodność z gęstością \(f(x) = e^{-x^2/2}\) (s. 199).
Rozkład Wykładniczy: Weryfikujemy zgodność z gęstością \(f(x) = e^{-x}\) (s. 200).
Porównanie z systemową funkcją R służy jako dowód, że algorytm generatora poprawnie odtwarza masę prawdopodobieństwa. Wykres Q-Q potwierdza, że kwantyle generowane przez algorytm pokrywają się z implementacjami.
ggplot(data.frame(x = probka_autorska), aes(sample = x)) +
stat_qq() +
stat_qq_line(color = "red") +
labs(title = "Wykres Q-Q",
subtitle = "Zgodność z gęstością f(x)",
x = "Kwantyle teoretyczne", y = "Kwantyle z generatora") +
theme_light()Wykres Q-Q potwierdza poprawność implementacji algorytmu ROU w głównym pasmie rozkładu. Odchylenia zaobserwowane w prawym ogonie rozkładu są spójne z uwagami Devroye’a dotyczącymi specyfiki generowania zmiennych o nieskończonych ogonach oraz potencjalnych ograniczeń dokładności numerycznej przy ekstremalnych wartościach parametrów.
Ocena jakości obejmuje również analizę efektywności algorytmu. Devroye na stronie 196 definiuje stałą odrzucenia jako stosunek pola prostokąta ograniczającego do pola obszaru A. Dla rozkładu normalnego stała ta wynosi \(4/\sqrt{\pi e}\) .
Sprawdzamy, czy empiryczna liczba odrzuceń jest zgodna z tą wartością teoretyczną.
total_accepted <- length(probka_autorska)
total_attempts <- 13742
empiryczna_stala <- total_attempts / total_accepted
teoretyczna_stala <- 4 / sqrt(pi * exp(1))
cat("Empiryczna stała odrzucenia:", round(empiryczna_stala, 4), "\n")Empiryczna stała odrzucenia: 2.7484
cat("Teoretyczna stała odrzucenia:", round(teoretyczna_stala, 4), "\n")Teoretyczna stała odrzucenia: 1.3688
Uzyskana empiryczna stała odrzucenia przewyższa teoretyczne minimum. Różnica ta, choć wpływa na ogólną prędkość generowania, nie podważa poprawności statystycznej generatora, co potwierdziły testy widmowe i Q-Q. Jest to akceptowalny koszt za zachowanie prostoty implementacji.
Źródła użyte w ocenie jakości:
Devroye (1986), Strona: 8 (rozdział I), 196, 199, 200 (rozdział IV), 525 (rozdział X) Link do książki
Zgodność z kryteriami oceny Devroye’a: Zaimplementowany algorytm ROU spełnia kluczowe postulaty autora dotyczące prostoty i czytelności. Pozwoliło to na uniknięcie „subtelnych błędów”, które często pojawiają się w bardziej skomplikowanych i nieprzejrzystych implementacjach.
Weryfikacja niezależności: Przeprowadzony test widmowy wykazał brak struktur kratowych w wymiarze t=2, co potwierdza, że generator zachowuje własność niezależności kolejnych losowań. Jest to zgodne z wymogami stawianymi profesjonalnym generatorom w opracowaniach PW oraz postulatami Devroye’a dotyczącymi wiarygodności symulacji.
Analiza poprawności rozkładu: Wykres Q-Q plot potwierdził, że algorytm ROU poprawnie generuje wartości zgodnie z gęstościami teoretycznymi podanymi w tabelach. Zaobserwowane odchylenia w ogonach rozkładu są spójne z uwagami autora o trudnościach w zachowaniu wysokiej dokładności numerycznej przy generowaniu wartości ekstremalnych.
Wydajność a prostota: Empiryczna stała odrzucenia okazała się wyższa od teoretycznego minimum. Zgodnie z analizą Devroye’a, jest to dopuszczalny koszt za zastosowanie metody, która efektywnie obsługuje rozkłady o nieskończonych ogonach bez konieczności ich sztucznego odcinania.
Podsumowanie: Wykorzystanie metod superpozycji (dla rozkładów dyskretnych takich jak Poisson czy Pascal) oraz ROU (dla rozkładów ciągłych) pozwoliło na stworzenie kompletnego i merytorycznie spójnego zestawu generatorów.