Funktionen

Wir kennen Funktionen ja schon aus den EDV-Veranstaltungen.

Zum Beispiel macht die sum-Funktion mit einem Vektor das, was der Name sagt:

sum(c(1,2,3))
## [1] 6
1 + 2 + 3
## [1] 6

Funktionen können wir auch selbst definieren. Eine Funktion, die uns begrüßt könnte zum Beispiel wie folgt aussehen:

greet_me <- function(){
  return('Hello! Nice to see you!')
}

Wenn wir jetzt die greet_me-Funktion aufrufen, sehen wir:

greet_me()
## [1] "Hello! Nice to see you!"

In der Funktionsdefinition können wir ein paar Teile wiederkennen. Zum Einen ist da der greet_me <--Teil, den wir ja schon als Objektzuweisung kennen. Wir weisen also dem Ergebnis eines Ausdrucks den Namen greet_me zu.

Funktions-Objekte werden also genauso wie Datensätze und Vektoren als eine Kombination von Namen und zugehörigem Inhalt definiert.

Dabei erstellt die Funktion function() einen Objektinhalt, der aus body und formals besteht und in einem environment definiert ist.

Der body ist der Teil der Funktion, der definiert, was passieren soll und wird in R in geschweiften Klammern hinter der function-Funktion definiert In unserem Beispiel besteht der body aus dem Aufruf, einen Text zurückzugeben:

body(greet_me)
## {
##     return("Hello! Nice to see you!")
## }

Die formals sind die Argumente, die bei der Ausführung des bodys genutzt werden sollen.

In unserem Beispiel haben wir noch keine Argumente berücksichtigt:

formals(greet_me)
## NULL

Wir könnten die Funktion aber neu definieren, so dass sie einen gegebenen Namen begrüßt. Dafür geben wir der function-Funktion ein Argument, das der Name des erwarteten Arguments sein soll:

greet_someone <- function(name){
  return(paste0('Hello ',name,'! Nice to see you!'))
}

greet_someone('Marvin')
## [1] "Hello Marvin! Nice to see you!"

Wenn wir uns jetzt nochmal die formals ausgeben lassen, sehen wir, dass ein Argument vorgesehen ist:

formals(greet_someone)
## $name

Der letzte Teil einer Funktionsdefinition ist das environment. Damit ist der Namensraum gemeint, auf den die Funktion zugreifen kann:

environment(greet_someone)
## <environment: R_GlobalEnv>

In unserem Beispiel wurde die Funktion in der laufenden R-Session definiert (wie die allermeisten Funktionen, die wir dieses Semester nutzen werden). Der Output R_GlobalEnv heißt also, dass die Funktion auf Objekte in der Haupt-R-Session zurückgreifen kann.

Praktisch heißt das, dass wir zum Beispiel Konstanten einmal außerhalb einer Funktion definieren können, die diese dann nutzen kann:

n <- 10

greet_someone_n_times <- function(name) {
  return(rep(paste0('Hello ', 
                    name, 
                    '! Nice to see you!'), 
             n))
}

greet_someone_n_times('Marvin')
##  [1] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
##  [3] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
##  [5] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
##  [7] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
##  [9] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"

Was das aber nicht heißt ist, dass die Funktion eine externe Variable ändern kann:

greet_someone_n_times <- function(name) {
  
  n <- 3
  
  return(rep(paste0('Hello ', 
                    name, 
                    '! Nice to see you!'), 
             n))
}

greet_someone_n_times('Marvin')
## [1] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
## [3] "Hello Marvin! Nice to see you!"
n
## [1] 10

Das liegt daran, dass die Funktion beim Aufrufen eine Kopie des globalen Environments als eigene Variablenumgebung zugewiesen bekommt und diese Kopie auch verändern kann. Die Kopie wird aber bei Beenden der Funktion verworfen, so dass Änderungen in der Funktion nicht erhalten bleiben.

Dabei werden die Argumente der Funktion dem Environment der Funktion hinzugefügt, wobei im Zweifelsfall gleichnamige Objekte für die Funktion überschrieben werden:

x <- 1:10
y <- 11:20

my_function <- function(x,y){
  return(c(x,y))
}

my_function(21,22)
## [1] 21 22

Aufgabe

  1. Erstelle eine Funktion mit dem Namen my_mean, die den Mittelwert eines gegebenen Vektors berechnet.

  2. Was ergibt der folgende Aufruf:

dummy_function <- function(a,b){
  a * b
}

c <- dummy_function(1,2)

formals(dummy_function)
  1. Was ist am Ende des folgenden Aufrufs in number abgelegt?
number <- 'a number'

important_calculation <- function(number){
  number <- 42 / number * number
  return(number)
}

important_calculation(5)
## [1] 42
Antworten

1.

my_mean <- function(x){
  return(sum(x)/length(x))
}

2.

## $a
## 
## 
## $b

3.

'a number'

Geschachtelte Funktionen

Um den Code übersichtlicher zu halten, können Teile von Funktionen in andere Funktionen ausgegliedert werden. Dabei nutzen wir, dass unsere Funktionen auch auf das global Environment zugreifen und Funktionen ja auch nur Arten von Objekten sind.

Wenn wir unsere Funktionen greet_someone und greet_someone_n_times von vorhin nochmal angucken sehen wir, dass ein Teil des Codes in beiden auftaucht:

print(greet_someone)
## function(name){
##   return(paste0('Hello ',name,'! Nice to see you!'))
## }
print(greet_someone_n_times)
## function(name) {
##   
##   n <- 3
##   
##   return(rep(paste0('Hello ', 
##                     name, 
##                     '! Nice to see you!'), 
##              n))
## }

Wir können greet_someone_n_times jetzt so umschreiben, dass sie greet_someone benutzt:

greet_someone_n_times <- function(name){
  n <- 3
  
  return(rep(greet_someone(name), 
             n))
}


greet_someone_n_times('Marvin')
## [1] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
## [3] "Hello Marvin! Nice to see you!"

Aufgabe

  1. Erstelle eine my_var Funktion, die die unkorrigierte Varianz eines Vektors (\(S^2 = {1\over{n}} \sum_{i=1}^n(x_i -M_X)^2\)) berechnet. Benutze für den Mittelwert deine Mittelwerts-Funktion aus dem letzten Aufgaben-Block.
Antworten

1.

my_var <- function(x){
  m_x <- my_mean(x)
  
  x <- (x - m_x)^2
  
  return(1/length(x) * sum(x))
}

Optionale Argumente

Unsere greet_someone_n_times-Funktion sieht ja im Moment wie folgt aus:

print(greet_someone_n_times)
## function(name){
##   n <- 3
##   
##   return(rep(greet_someone(name), 
##              n))
## }

Ungünstig daran ist noch, dass das n bei jedem Aufruf als Konstante neu definiert ist.

Um dieses n anpassen zu können, können wir es als zweites Argument übergeben, das beim Aufruf festgelegt werden kann:

greet_someone_n_times <- function(name, n){
  return(rep(greet_someone(name), 
             n))
}

greet_someone_n_times('Marvin',3)
## [1] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
## [3] "Hello Marvin! Nice to see you!"

Da wir uns aber das Tippen sparen wollen und nicht jedes Mal unseren Standard-Fall (n=3) explizit machen wollen, können wir dem Argument einen Standardwert zuweisen.

Dadurch machen wir aus dem n ein optionales Argument, wie wir es ja auch schon aus anderen Situationen kennen:

greet_someone_n_times <- function(name, n=3){
  return(rep(greet_someone(name), 
             n))
}

greet_someone_n_times('Marvin')
## [1] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
## [3] "Hello Marvin! Nice to see you!"
greet_someone_n_times('Marvin', 5)
## [1] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
## [3] "Hello Marvin! Nice to see you!" "Hello Marvin! Nice to see you!"
## [5] "Hello Marvin! Nice to see you!"