############################################################################### ## Inhalt: ## Kapitel 3 (Programmierung) des R-Kurses von Ruckdeschel & Kohl ############################################################################### ############################################################################### ## 1. Kontrollstrukturen ############################################################################### ########################################################### ## 1.1 Bedingtes Ausführungen von R-Code ########################################################### ########################################################### ## 1. Einfaches Beispiel: Absolutbetrag ## Falls eine numerische Variable x positiv ist, gebe diese unverändert ## als Variable y zurück. Falls Sie jedoch negativ ist, gebe y = -x zurück ########################################################### ####################################### ## Spezialfall: x ist nur eine einzelne Zahl; d.h., ein Vektor der Länge 1 ####################################### ## Die if-Anweisung ?'if' x <- 2 if(x > 0) y <- x else y <- -x y ## oder übersichtlicher if(x > 0){ y <- x }else{ y <- -x } y ## Der 'else'-Teil der if-Anweisung ist optional ## Im Beispiel: Sollte innerhalb eines Programms in einem früheren Teil bereits y <- x ## so genügt später dann if(x < 0) y <- -x y ## Die Funktion ifelse ## Für die if ... else ... Anweisung gibt es auch ein Kurzform ?ifelse y <- ifelse(x > 0, x, -x) y ####################################### ## Was machen wir, wenn x nicht nur eine einzelne Zahl, sondern ## ein numerischer Vektor der Länge n > 1 ist? ####################################### ## Die if-Anweisung x <- -5:5 ## wir erhalten einen logischen Vektor x > 0 ## was passiert bei if ... else ...? if(x > 0) y <- x else y <- -x x y ## es wird nur das erste Element von x > 0 berücksichtigt, wobei ## x[1] = -5 und somit y <- -x ausgeführt wird. ## In manchen Situationen helfen die Funktion ?all ?any ## Da viele Zahlen am Computer (d.h. binär) nicht exakt darstellbar sind, ## zum Testen der Gleichheit von Zahlen besser ?all.equal ## Im vorliegenden Beispiel helfen uns diese Funktionen jedoch nicht! ## Erste Möglichkeit: ## Wir verwenden eine Schleife: nein!!! ## Wenn es irgendwie möglich ist, vermeiden wir dies!!! ## Genaueres später in der Sitzung ... ## Zweite Möglichkeit: ## Die Funktion ifelse arbeitet auch mit Vektoren y <- ifelse(x > 0, x, -x) x y ## Dritte Möglichkeit: ## Der Einsatz von Indikatoren (= logischer Vektor) ## Werfe einen genaueren Blick auf die Implementierung von ifelse ifelse ## Wir machen dies analog, wir schließen jedoch aus, dass wir NAs ## in x haben y <- x ind <- x < 0 ## Indikator y[ind] <- -x[ind] x y ## Natürlich gibt es in diesem 'künstlichen' Fall auch die Möglichkeit y <- abs(x) y ########################################################### ## 2. Zweites Beispiel: Mehrere Alternativen ## Je nach Situation wollen Sie ein anderes Verfahren ## aufrufen ########################################################### ## 1. Möglichkeit: Verwendung mehrere if-Anweisungen method <- 'Maximum' if(method == 'Mean'){ mean(x) } if(method == 'Median'){ median(x) } if(method == 'Maximum'){ max(x) } if(method == 'Minimum'){ min(x) } ## 2. Möglichkeit: switch-Anweisung ?switch res <- switch(method, 'Mean' = mean(x), 'Median' = median(x), 'Maximum' = max(x), 'Minimum' = min(x)) res ## man kann auch noch eine Methode hinzufügen, die in allen ## sonstigen Fällen ausgeführt wird; d.h., falls es keine ## der benamten Methoden gewählt wird method <- 'Summe' res <- switch(method, 'Mean' = mean(x), 'Median' = median(x), 'Maximum' = max(x), 'Minimum' = min(x), {warning('Das Verfahren ', method, ' ist nicht implementiert\n', 'Es wird daher x unverändert zurückgegeben.\n'); x}) res ## man kann die Alternativen auch aufgrund der Position ansprechen, also res <- switch(3, 'Mean' = mean(x), 'Median' = median(x), 'Maximum' = max(x), 'Minimum' = min(x), {warning('Das Verfahren ', method, ' ist nicht implementiert\n', 'Es wird daher x unverändert zurückgegeben.\n'); x}) res ## dann gibt es jedoch nicht die Möglichkeit eine Methode für eine Methode, ## die in allen sonstigen Fällen aufgerufen wird res <- switch(5, 'Mean' = mean(x), 'Median' = median(x), 'Maximum' = max(x), 'Minimum' = min(x), {warning('Das Verfahren ', method, ' ist nicht implementiert\n', 'Es wird daher x unverändert zurückgegeben.\n'); x}) res ## bzw. res <- switch(6, 'Mean' = mean(x), 'Median' = median(x), 'Maximum' = max(x), 'Minimum' = min(x), {warning('Das Verfahren ', method, ' ist nicht implementiert\n', 'Es wird daher x unverändert zurückgegeben.\n'); x}) res ########################################################### ## 1.2 Wiederholtes Ausführungen von R-Code ########################################################### ########################################################### ## Einfaches Beispiel: Summation ## Wir wollen die Zeilensummen einer Matrix X berechnen. ########################################################### ## 1. Möglichkeit: for-Schleife ?'for' X <- matrix(rnorm(200), nrow = 10, ncol = 20) ## rnorm erzeugt normalverteilte Zufallszahlen ## Wir müssen also die Zeilen und die Spalten der Matrix durchlaufen ## d.h., wir haben sogar eine doppelte for-Schleife ## Wichtig: Legen Sie die Ergebnisvariable immer vorher an! ## Ein sukzessives Erweitern in jedem Schleifendurchlauf etwa ## summe <- c(summe, 'Ergebnis von Durchlauf i') ## ist deutlich langsamer!!! ## Sollten Sie die Dimension des Ergebnisses nicht kennen, ## sollte man trotzdem so vorgehen. Entweder man ist in der Lage ## eine Variable anzulegen, deren Dimension auf jeden Fall größer ## ist als die Dimension des Ergebnisses oder man legt eine Variable, ## von der man meint, Sie hätte ausreichende Dimension. Man prüft ## dann innerhalb der Berechnungen, ob die Dimension der Ergebnisvariable ## überschritten wird. Falls ja, vergrößert man die Dimension der ## Ergebnisvariable (z.B. verdoppeln) entsprechend. summe <- numeric(nrow(X)) ## nun zur for-Schleife for(i in 1:nrow(X)){ for(j in 1:ncol(X)){ summe[i] <- summe[i] + X[i,j] } } summe ## Die for-Schleife ist also insbesondere dann, die geeignete Wahl, ## wenn man genau weiß, wie oft eine Berechnung durchgeführt werden ## muss. ## Man kann aber nicht nur mit den Indizes for-Schleifen konstruieren, ## sondern auch mit den Namen; d.h., z.B. Y <- X colnames(Y) <- paste('Spalte', 1:20) Y summe.Y <- numeric(nrow(Y)) for(i in 1:nrow(Y)){ for(Name in colnames(Y)){ summe.Y[i] <- summe.Y[i] + Y[i,Name] } } summe.Y ## 2. Möglichkeit: while-Schleife ?'while' ## Solange eine Bedingung erfüllt ist, führe eine Anweisung aus ... ## Diese sind z.B. dann die geeignete Wahl, wenn man einen Algorithmus hat ## der iterativ an Genauigkeit gewinnt. Man verwendet dann als Bedingung ## für den Abbruch eine Genauigkeitsschranke. Im vorliegenden Beispiel ## ist der Einsatz der while-Schleife etwas konstruiert ... summe <- numeric(nrow(X)) i <- 0 while(i < nrow(Y)){ i <- i+1 j <- 0 while(j < ncol(Y)){ j <- j+1 summe[i] <- summe[i] + X[i,j] } } summe ## 3. Möglichkeit: repeat-Schleife ?'repeat' ## Die repeat-Schleife ist ähnlich zur while-Schleife. Während jedoch ## die while-Schleife bereits vor dem ersten Durchlauf die Bedingung ## prüft, geschieht dies in der repeat-Schleife erst innerhalb ## bzw. nach dem ersten Durchlauf. ## Zusätzlich benötigt man den Befehl 'break', mit dem man ## jede Schleife (also auch for- oder while-Schleife) verlassen kann. summe <- numeric(nrow(X)) i <- 1 repeat{ j <- 1 repeat{ summe[i] <- summe[i] + X[i,j] j <- j+1 if(j > ncol(X)) break } i <- i+1 if(i > nrow(X)) break } summe ## 4. Möglichkeit: Unter dem Einsatz von 'sum' ## Wir zeigen dies nur noch mit Hilfe der for-Schleife. Analog könnte ## auch wieder die while- oder die repeat-Schleife verwendet werden. summe <- numeric(nrow(X)) for(i in 1:nrow(X)){ summe[i] <- sum(X[i,]) } summe ## 5. Möglichkeit: Verwende 'sum' und 'apply' (summe <- apply(X, 1, sum)) ## das zusätzliche Klammern führt zur Ausgabe des Ergebnisses ## 6. Möglichkeit: Verwende rowSums (effizienteste Möglichkeit) (summe <- rowSums(X)) ####################################### ## Noch ein weiterer Befehl ist im Zusammenhang mit Schleifen wichtig ## nämlich der Befehl 'next'. Mit diesem wird nicht die Schleife ## verlassen, sondern nur die aktuelle Iteration. ####################################### ############################################################################### ## 2. Vermeidung von Schleifen ############################################################################### ## Im Gegensatz zu C oder Fortran ist R/S eine sog. Interpretersprache ## d.h., eine Schleife wird nicht kompiliert, sondern während der Laufzeit ## wird der Code jeweils neu interpretiert. Dies führt zu deutlich ## größeren Laufzeiten als im Fall von compiliertem Code! ## D.h., Schleifen sind wann immer möglich zu vermeiden!!!! ## Versuchen Sie immer Ihre Probleme vektor- oder matrix-wertig zu ## formulieren, indem Sie Matrixoperationen, Indikatoren, ifelse, pmin, ## pmax, outer, rowSums, colSums, rowMeans, colMeans, etc. verwenden. ############################################################################### ## 3. Funktionen ############################################################################### ## Wir befassen uns nun mit dem Schreiben eigener Funktionen ?'function' ## Eine Funktion besteht aus einer Liste von Argumenten 'arglist' und einem ## Funktionskörper 'body' ########################################################### ## Einfaches Beispiel: Klippen einer Funktion ## Wir wollen ein Funktion, die max(-M, min(x, M)) ## berechnet. Diese soll vektorwertig agieren können. ########################################################### ## 1. Möglichkeit: mit pmin und pmax myfun1 <- function(x, M){ ## evtl. mit Default z.B. M = 5 return(pmax(-M, pmin(x, M))) } x <- seq(-10, 10, length = 20) ## Aufrufmöglichkeiten myfun1(x = x, M = 5) myfun1(M = 5, x = x) myfun1(x, 5) myfun1(5, x) ## falsch, nicht das Gewünschte! ## oder auch ohne return ## dann wird der letzte Ausdruck, der innerhalb der Funktion ## ausgewertet wird, zurückgegeben. myfun2 <- function(x, M){ ## evtl. mit Default z.B. M = 5 pmax(-M, pmin(x, M)) } myfun2(x, M = 5) myfun21 <- function(x, M = 5){ pmax(-M, pmin(x, M)) } myfun21(x) ## 2. Möglichkeit: mit Indikatoren myfun3 <- function(x, M){ ind1 <- x > M ind2 <- x < -M x[ind1] <- M x[ind2] <- -M return(x) } myfun3(x, M = 5) ## Achtung: Die (globale) Variable x ist durch die Veränderungen ## innerhalb der Funktion nicht betroffen! ## Innerhalb der Funktion gibt es eine lokale Kopie der Variable! x ## 3. Möglichkeit: mit zusätzlicher Fehlerkontrolle myfun4 <- function(x, M){ if(missing(x) | missing(M)) stop('Sie müssen x und M spezifizieren!') if(!is.numeric(x) | !is.numeric(M)) stop('x und M müssen numerische Variablen sein!') if(length(M) > 1){ warning('Die Länge von M ist größer 1. Es wird nur das erste Element von M verwendet.') M <- M[1] } return(pmax(-M, pmin(x, M))) } myfun4() myfun4(x) myfun4(x, 'test') ## Die Funktion 'missing' kann z.B. auch verwendet, um innerhalb der ## Funktion default-Werte zu setzen. ## z.B.: if(missing(M)) M <- 5 ########################################################### ## Erweiterung des Beispiels: Klippen einer Funktion ## Wir wollen nun max(-M, min(f(x, ???), M)) ## Außer dem Argument x kennen wir die weiteren Argumente ## von f nicht! ## Hierzu gibt es in R das spezielle Argument '...'. ## Die Funktion soll wieder vektorwertig agieren können. ########################################################### myfun5 <- function(x, M, f, ...){ pmax(-M, pmin(f(x, ...), M)) } y <- seq(0.1, 2, length = 20) myfun5(y, M = 1, f = log, base = exp(1)) myfun5(y, M = 1, f = log, base = 2) ########################################################### ## Noch ein paar Besonderheiten, die bei der Definition ## und den Aufrufen zu beachten sind. Hierzu schreiben ## wir myfun5 um. myfun6 <- function(x, Schranke, ..., Funktion){ pmax(-Schranke, pmin(Funktion(x, ...), Schranke)) } ## Mögliche Aufrufe myfun6(y, 1, 2, Funktion = log) myfun6(x = y, 1, base = 2, Funktion = log) myfun6(x = y, S = 1, Funktion = log, base = 2) myfun6(x = y, Sch = 1, Fun = log, base = 2) ## D.h., Argumente vor dem '...' Argument werden auch ## durch partielles 'Matching' richtig erkannt. ## Argumente nach '...' müssen exakt angegeben werden!