Schleifen
Bei so gut wie allen Simulationsproblemen stehen wir vor der Situation, dass wir eine Operation, zum Beispiel das Generieren einer gewissen Zahl an Zufallswerten und die Berechnung der zugehörigen Teststatistik, mehrere hundert Mal ausführen wollen.
Wir könnten jetzt die Operation mehrere hundert mal in unser Skript schreiben, damit würden wir aber zum Einen Fehler einladen, zum Anderen viel zu viel Lebenszeit verschwenden.
Deswegen gibt es in so gut wie jeder Programmiersprache irgendeine Form von Ausdrücken, die eine iterative Wiederholung eines Ausdrucks ermöglichen.
Ein Beispiel für die Anweisung einer solchen iterativen Wiederholung sind Schleifen. In R gibt es davon drei Arten:
repeat
- die flexibelste Schleife, die so lange wiederholt bis sie mitbreak
unterbrochen wirdwhile
- eine Schleife, die zu Beginn jeder Wiederholung einen logischen Test durchführt und bei Zutreffen die Operation wiederholtfor
- die unflexibelste der drei Möglichkeiten.for
iteriert einen Wert durch einen Vektor oder eine Liste, bis alle Einträge einmal dran waren. Das heißt, dass wir eine feste Laufzeit und damit den einfachsten Umgang haben, weswegen wir auchfor
in diesem Kurs verwenden werden.
Die for
-Syntax sieht dabei wie folgt aus:
for(value in vector){
do something }
Um jetzt Beispielsweise alle Werte von eins bis zehn durch zu laufen und jeden Wert auszugeben können wir den folgenden Ausdruck benutzen:
for(i in seq_len(10)){
print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10
Wie man an diesem Beispiel schon sehen kann, können wir in der Schleife auf den i
-Wert zugreifen. Das können wir zum Beispiel benutzen, um die ersten zehn Zahlen der Fibonacci-Reihe zu berechnen, in der jeder Wert die Summe der zwei vorhergegangenen ist:
<- numeric(10)
fib for(i in seq_along(fib)){
if(i<3){ # die ersten zwei Stellen müssen Einsen sein
<- 1
fib[i] else{
}<- fib[i-1] + fib[i-2] # nimm die letzten zwei Einträge und summier sie auf
fib[i]
}
} fib
## [1] 1 1 2 3 5 8 13 21 34 55
In diesem Beispiel ist noch ein weiteres Programmier-Prinzip sichtbar, die Allokation des Ergebnis-Vektors vor der Berechnung der Werte. Damit ist einfach gemeint, dass wir den leeren fib
-Vektor erstellt haben, bevor wir mit der Schleife angefangen haben.
Der Grund für dieses Vorgehen ist, dass jedes Anlegen eines Vektors einer bestimtmen Größe ein bisschen Rechenzeit kostet. Wenn wir von vornherein festlegen, wie lang der Vektor werden soll, müssen wir nur einen Vektor anlegen. Wenn wir stattdessen wie im folgenden Beispiel in jeder Iteration den Vektor vergrößern, erstellt R implizit in jeder Iteration einen neuen Vektor.
<- 1
fib for(i in seq_len(10)){
if(i<3){
<- 1
fib[i] else{
}<- fib[i-1] + fib[i-2]
fib[i]
}
} fib
## [1] 1 1 2 3 5 8 13 21 34 55
Bei 10 Stellen ist der Unterschied noch nicht wirklich bemerkbar. Wenn wir jetzt aber das ganze in Funktionen verpacken und für längere Sequenzen laufen lassen und die Laufzeit stoppen sehen wir den Unterschied:
<- function(n){
fib_alloc <- numeric(n)
fib for(i in seq_along(fib)){
if(i<3){
<- 1
fib[i] else{
}<- fib[i-1] + fib[i-2]
fib[i]
}
}return(fib)
}
<- function(n){
fib_no_alloc <- 1
fib for(i in seq_len(n)){
if(i<3){
<- 1
fib[i] else{
}<- fib[i-1] + fib[i-2]
fib[i]
}
}return(fib)
}<- Sys.time()
start <- fib_alloc(100000)
a <- Sys.time() - start
runtime_alloc <- Sys.time()
start <- fib_no_alloc(100000)
b <- Sys.time() - start
runtime_no_alloc
runtime_alloc
## Time difference of 0.04131174 secs
runtime_no_alloc
## Time difference of 0.06902266 secs
Mehr als die Hälfte der Zeit geht für das Erstellen des neuen Vektors drauf!
Aufgabe
In QM-2 habt Ihr im Rahmen des zentralen Grenzwertsatzes gelernt, dass die Verteilungsfunktion der z-Transformation der n-ten Summe einer Reihe von unabhängigen Zufallsvariablen für wachsendes n schwach gegen die Standardnormalverteilung konvergiert.
Schreibe eine Funktion, die für eine gegebene Anzahl an Summen und eine gegebene Verteilungsklasse (und den entsprechenden Parametern inklusive der Stichprobengröße) einen Vektor mit den entsprechenden Summen zurückgibt. Nutze dafür deine Funktion aus der letzten Aufgabe.
Nutze diese Funktion dann um ein Histogramm mit 5000 dieser z-transformierten Summen zu erstellen.
Antwort
<- function(N,
gen_central_lim_vec
distribution,
n,df = 1,
lambda = 25,
rate = 5) {
<- numeric(N)
ret_vec for(i in seq_len(N)){
<- sum(gen_distributed_values(distribution,
ret_vec[i]
n,
df,
lambda,
rate))
}return(ret_vec)
}
tibble(sample = scale(gen_central_lim_vec(5000, 'exp', 1000,rate = 5))) %>%
ggplot(aes(x = sample)) +
geom_histogram(binwidth = .1,
color = 'grey',
fill = 'white')