Diagnose-Funktionen für Funktionen in R

Bisher wurden für alle Datentypen Diagnose-Funktionen vorgestellt, die über Form und Inhalt von Objekten Aufschluss geben. Für Funktionen gibt es ebenso eine Reihe von Diagnose-Funktionen, die zu weiteren Konzepten der objekt-orientierte und funktionale Programmierung führen oder spezielle Konzepte von R betreffen. Vorgestellt werden die wichtigsten Diagnose-Funktionen für Funktionen, wobei nicht alle weiterführenden Konzepte im Detail besprochen werden können.

Einordnung des Artikels

Einführung

Übersicht

In den beiden Artikeln Eigenschaften von Funktionen in R und Selbstdefinierte Funktionen in R (UDF = User Defined Functions) wurde vorgestellt:

  • Eigenschaften von Funktionen, die man kennen muss, um Funktionen in eigenen Skripten einzusetzen,
  • Eigenschaften und Techniken, die man benötigt, um selbst Funktionen zu definieren.

Um selber Aufgaben sinnvoll in Funktionen auszulagern und diese zu implementieren, also die Sprache R im Sinne der strukturierten Programmierung einzusetzen, sollten die dort vermittelten Kenntnisse ausreichen. Irgendwann wird man aber ein tieferes Verständnis für Funktionen benötigen, wozu in diesem und dem nächsten Kapitel einige fortgeschrittene Themen rund um Funktionen behandelt werden. Eine erschöpfende Behandlung von Funktionen können auch diese Kapitel nicht liefern, aber sie geben zahlreiche Anregungen, um selbständig in der Dokumentation zu stöbern und weiterführende Konzepte kennenzulernen.

Insbesondere wird sich zeigen, dass Funktionen wie andere Objekte behandelt werden können. Bisher dienten Objekte dazu Daten abzuspeichern, wozu man einen geeigneten Datentyp wählen musste (wie Zeichenketten, Vektoren, Listen, Dataframes). Funktionen wurden bisher als Zusammenfassungen von Anweisungen betrachtet, die mit Objekten konfiguriert werden können. Dieses Kapitel wird zeigen, dass die Unterscheidung zwischen Objekten und Funktionen deutlich aufgeweicht werden muss:

  • Funktionen können selbst Eingabewert oder Rückgabewert einer Funktion sein.
  • Funktionen können als Komponenten einer Liste abgespeichert werden.
  • Funktionen können daher selbst als Objekte aufgefasst werden.

Weiterführende Konzepte

In diesem Kapitel (ebenso im nächsten Kapitel Spezielle selbstdefinierte Funktionen in R) werden mehrere Begriffe eingeführt, die aus der objekt-orientierten oder funktionalen Programmierung stammen oder spezifische Konzepte von R beschreiben. Sie werden schon jetzt genannt und kurz beschrieben. Wer mit entsprechenden Konzepten noch nicht vertraut ist, wird diese Beschreibungen zu kurz und vielleicht nichtssagend finden; weiter unten werden sie dann ausführlicher erklärt. Wie an den Kurzbeschreibungen schon abzulesen ist, führen diese Konzepte tief in die objekt-orientierte und die funktionale Programmierung sowie in die interne Arbeitsweise von R und können hier nicht erschöpfend erklärt werden.

  1. Generisch implementierte Funktion: Funktion, für die es mehrere Implementierungen gibt. Die Auswahl der Implementierung richtet sich nach der Klasse des Objektes, das der Funktion übergeben wird.
  2. Methode: Funktion, die einer Klasse zugeordnet ist.
  3. Primitive Funktion: Funktion, die in C implementiert ist; auf ihren Quelltext kann nicht zugegriffen werden.
  4. Umgebung: Jedes Objekt in R ist einer Umgebung zugeordnet; um Namenskonflikte zu vermeiden, darf in einer Umgebung ein Name nicht mehrfach vorkommen.

Die print()-Funktion

Die Funktion print() ist generisch implementiert und es gibt eine Version für den Fall, dass der Eingabewert x eine Funktion ist. Hierzu einige Beispiele:

1. Beispiel: Die selbstdefinierte Funktion weightedMean()

Wie im Kapitel Selbstdefinierte Funktionen in R (UDF = User Defined Functions) wird zum Testen eine Version der Funktion weightedMean() verwendet, hier eine simple Implementierung:

weightedMean <- function(x, m){
  return(sum(x * m) / sum(m))
}

print(x = weightedMean)
# function(x, m){
#   return(sum(x * m) / sum(m))
# }

Zeile 1 bis 3: Die einfache Definition der Funktion weightedMean() – da nicht mit ihr gearbeitet werden soll, reicht diese Implementierung.

Zeile 5 bis 8: Die print()-Funktion wird mit x = weightedMean aufgerufen. Man beachte, dass dazu die Funktion wie eine Variable behandelt werden kann, das heißt es wird der Name der Funktion eingesetzt; es wäre falsch, den Namen in Anführungsstriche zu setzen: x = "weightedMean" .

Die Ausgabe zeigt genau die rechte Seite der Definition von weightedMean aus Zeile 1 bis 3.

2. Beispiel: Die Funktion sum() aus dem Paket base

Der Aufruf von print() für die Funktion sum() erzeugt ein womöglich unerwartetes Ergebnis:

print(x = sum)
# function (..., na.rm = FALSE)  .Primitive("sum")

Zeile 2: Man erkennt die Eingabewerte der Funktion sum(). Anstelle der Implementierung steht aber .Primitive("sum") . Das bedeutet, dass sum() eine primitive Funktion ist, also nicht in R sondern in C implementiert ist. Und dieser C-Quelltext wird nicht angezeigt.

Der zweite Teil der Ausgabe von print(x = sum) ist dann so zu verstehen: Wird die Funktion sum() aufgerufen, werden die Eingabewerte mit Hilfe von .Primitive() an die entsprechende C-Implementierung weitergereicht.

3. Beispiel: Die Funktion mean() aus dem Paket base

Auch bei der Funktion mean() erhält man eine vermutlich unerwartete Ausgabe (Zeile 1 bis 5):

print(mean)
# function (x, ...) 
#   UseMethod("mean")
# <bytecode: 0x0000000004808520>
#   <environment: namespace:base>

methods(generic.function = "mean")
# [1] mean.Date     mean.default  mean.difftime mean.POSIXct  mean.POSIXlt 
# see '?methods' for accessing help and source code

print(x = mean.default)
# function (x, trim = 0, na.rm = FALSE, ...) 
# {
#   if (!is.numeric(x) && !is.complex(x) && !is.logical(x)) {
#     warning("argument is not numeric or logical: returning NA")
#     return(NA_real_)
#   }
#   if (na.rm) 
#     x <- x[!is.na(x)]
#   if (!is.numeric(trim) || length(trim) != 1L) 
#     stop("'trim' must be numeric of length one")
#   n <- length(x)
#   if (trim > 0 && n) {
#     if (is.complex(x)) 
#       stop("trimmed means are not defined for complex data")
#     if (anyNA(x)) 
#       return(NA_real_)
#     if (trim >= 0.5) 
#       return(stats::median(x, na.rm = FALSE))
#     lo <- floor(n * trim) + 1
#     hi <- n + 1 - lo
#     x <- sort.int(x, partial = unique(c(lo, hi)))[lo:hi]
#   }
#   .Internal(mean(x))
# }
# <bytecode: 0x000000000480a068>
#   <environment: namespace:base>

Zeile 2 ist noch verständlich, da sie wieder die Eingabewerte der Funktion mean() anzeigt.

Zeile 3 besagt, dass mean() eine generisch implementierte Funktion ist.

Zeile 5 ist auch wieder verständlich: hier wird ausgesagt, dass mean() im Paket base enthalten ist. Genauer wird hier die Umgebung definiert, in der sich mean() befindet: es ist der namespace base.

Zeile 7 bis 9: Ruft man jetzt die Funktion methods() auf, und zwar mit dem Argument der generischen Funktion mean(), so erhält man alle Versionen der Funktion mean(). Sie ist für verschiedene Klassen, zum Beispiel Date implementiert; kann das Argument x von mean() keiner der angegebenen Klassen zugeordnet werden, wird die Funktion mean.default() aufgerufen.

Zeile 11 bis 37: Ruft man jetzt print() mit dem Argument x = mean.default auf, erhält man

  • die Eingabewerte von mean.default(),
  • die Implementierung von mean.default(),
  • und wiederum die Information über die Umgebung, in der die Funktion definiert ist.

Der Modus einer Funktion: mode()

Bei der Besprechung der Datentypen in R wurde schon mehrmals deren Hierarchie verwendet:

logical < integer < double < character < list

Diese Hierarchie der Modi ist insbesondere für Typumwandlungen wichtig: So kann zum Beispiel die Zahl 17 in die Zeichenkette '17' umgewandelt werden; die umgekehrte Umwandlung ist auch möglich. Aber nicht jede Zeichenkette kann in eine Zahl verwandelt werden: in welche Zahl sollte man etwa 'x' umwandeln?

Dass in der Hierarchie list enthalten ist – und ganz rechts steht –, ist auf den ersten Blick vielleicht unverständlich: Versucht man Objekte mit den Modi logical < integer < double < character mit Hilfe von c() zu einem Vektor zusammenzufassen, wird der "größte" Modus aus der Hierarchie ausgewählt und zum gemeinsamen Modus gemacht; dabei werden Objekte mit "kleinerem" Modus umgewandelt. Versucht man Objekte mit c() zusammenzufassen, unter denen sich eine Liste befindet (mit Modus list), so kann nur eine Liste (und kein Vektor) entstehen. Daher ist es sinnvoll, den Modus list in diese Hierarchie aufzunehmen.

Um die in R verfügbaren Datentypen zu verstehen, reicht es die Hierarchie logical < integer < double < character zu kennen (mit dem bereits gegebenen Hinweis, dass die Modi raw und complex nicht besprochen werden). Kann man diese Modi verwenden, um den Modus einer Funktion zu definieren? Eine Funktion besitzt Eingabewerte und einen Rückgabewert. Allerdings kann es beliebig viele Eingabewerte geben, deren Datentypen zudem bei der Definition der Funktion nicht festgelegt werden müssen, und der Rückgabewert kann unterschiedliche Datentypen annehmen. Somit sind die Modi logical < integer < double < character ungeeinet, um eine Funktion zu charakterisieren. Ebenso der Modus list: Wie soll eine Funktion mit einer Liste in Verbindung gebracht werden?

Für Funktionen wird daher ein eigener Modus function eingeführt.

Das folgende Skript ruft die Funktion mode() für Funktionen auf:

weightedMean <- function(x, m){
  return(sum(x * m) / sum(m))
}

mode(weightedMean)
# [1] "function"

mode(mean)
# [1] "function"

Man erkennt: es macht keinen Unterschied, ob eine Funktion selbstdefiniert ist oder aus den Standard-Paketen stammt, der Modus ist function.

Um es gleich vorwegzunehmen: Neben function gibt es weitere Modi, die im Zusammenhang mit Funktionen eingeführt werden müssen und die sich nicht auf die bisherigen Modi logical < integer < double < character zurückführen lassen.

Die is-dot-Funktionen is.function() und is.primitive()

In den letzten Unterabschnitten wurde gezeigt, dass für Funktionen der eigene Modus function existiert und es wurde auf die Besonderheit der primitiven Funktionen hingewiesen. Um zu testen, ob ein Objekt eine Funktion oder sogar eine primitive Funktion ist, gibt es die beiden is-dot-Funktionen:

is.function(x)
is.primitive(x)

mit denen ein beliebiges Objekt x getestet werden kann; der Rückgabewert ist ein logischer Wert.

Da Funktionen als Eingabewerte von anderen Funktionen verwendet werden können, wird man zur Prüfung der Eingabewerte insbesondere is.function() einsetzen.

Die Struktur str() und Attribute einer Funktion

Bisher hat sich bei allen Datentypen die Struktur str() eines Objektes als wertvolles Hilfsmittel herausgestellt, um Informationen sowohl über den Datentyp als auch den aktuellen Wert des Objektes auszugeben. Für die selbstdefinierte Funktion weightedMean() und sum() soll die Struktur untersucht werden (Zeile 6 und 11) und es sollen die Attribute ausgegeben werden (Zeile 14 und 21):

weightedMean <- function(x, m){
  # TODO: Prüfung der Eingabewerte
  return(sum(x * m) / sum(m))
}

str(weightedMean)
# function (x, m)   
#   - attr(*, "srcref")=Class 'srcref'  atomic [1:8] 1 17 4 1 17 1 1 4
# .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x00000000063156f8> 

str(sum)
# function (..., na.rm = FALSE) 

attributes(weightedMean)
# $srcref
# function(x, m){
#   # TODO: Prüfung der Eingabewerte
#   return(sum(x * m) / sum(m))
# }

attributes(sum)
# NULL

Struktur: In beiden Fällen wird die Liste der Eingabewerte angezeigt (Zeile 7 und 12).

Für sum() erhält man keine weitere Information; für weightedMean() erkennt man, dass die Funktion das Attribut srcref besitzt. Die Abkürzung srcref steht für source reference und verweist auf den Quelltext der Funktion weightedMean().

Lässt man die Attribute von weightedMean() und sum() ausgeben, erkennt man:

Zeile 14 bis 19: Die Funktion weightedMean() hat das Attribut srcref, das die Definition der Funktion beinhaltet.

Zeile 21 und 22: Die Funktion sum() besitzt kein Attribut.

Der Aufruf von ls() innerhalb einer Funktion

Die Funktionen ls() und objects() wurden bereits in Eigenschaften von R und Vorbereitungen vorgestellt; dort wurde gesagt, dass sie die Namen aller definierten Objekte zurückgeben (zusammengefasst in einem character-Vektor). Dies war dort richtig, weil lediglich Skripte als unstrukturierte Abfolge von Anweisungen geschrieben wurden.

Verwendet man die Funktionen ls() und objects() innerhalb von Funktionen, kann man damit sehr gut den Begriff der Umgebung erklären: Der Aufruf von ls() (und gleichwertig objects()) führt zu einem anderen Ergebnis je nachdem ob sie außerhalb oder innerhalb einer Funktion aufgerufen werden. Grob kann man dies folgendermaßen erklären: Die Umgebung ist in R diejenige Datenstruktur, die die Gültigkeitsbereiche der Objekte festlegt. Ein Aufruf von ls() (oder objects()) zeigt diejenigen Objekte an, die sich in der Umgebung befinden, in der der Aufruf stattfindet.

Das folgende Beispiel zeigt die Objekte in zwei Umgebungen:

  1. Die Objekte in der globalen Umgebung (global environment), die man mit dem workspace gleichsetzen kann.
  2. Die Objekte innerhalb einer selbstdefinierten Funktion; sie werden meist als die lokalen Variablen der Funktion bezeichnet.
rm(list = ls())

ls()
# character(0)
objects()
# character(0)

weightedMean <- function(x, m){
  cat("ls(): ", ls(), "\n")
  str(ls())
  cat("objects(): ", objects(), "\n")
  return(sum(x * m) / sum(m))
}

weightedMean(x = (1:3), m = c(1, 1, 3))
# ls():  m x 
#  chr [1:2] "m" "x"
# objects():  m x 
# [1] 2.4

ls()
# [1] "weightedMean"
str(ls())
# chr "weightedMean"
objects()
# [1] "weightedMean"

rm(list = ls())

weightedMean <- function(x, m){
  z <- sum(x * m)
  weights <- sum(m)
  
  cat("ls(): ", ls(), "\n")
  cat("objects(): ", objects(), "\n")
  return(z / weights)
}

weightedMean(x = (1:3), m = c(1, 1, 3))
# ls():  m weights x z 
# objects():  m weights x z 
# [1] 2.4

ls()
# [1] "weightedMean"

Zeile 1: Es werden alle Objekte im workspace gelöscht.

Zeile 3 bis 6: Sowohl ls() als auch objects() zeigen keine Objekte an; man erkennt, dass die Namen der Objekte als character-Vektoren ausgegeben werden sollen.

Zeile 8 bis 13: Die Funktion weightedMean() wird definiert. Eigentlich soll sie nur den Rückgabewert aus Zeile 12 berechnen. Es sind zusätzlich Ausgaben vorgesehen:

  • Die Ausgabe von ls() (Zeile 9).
  • Die Ausgabe der Struktur von ls() (Zeile 10).
  • Die Ausgabe von objects() (Zeile 11).

Zeile 15: Die Funktion weightedMean() wird aufgerufen. Die Ausgaben in den Zeilen 16 bis 18 stammen von den cat()-Befehlen.

  • Die Ausgabe von ls(): man erhält (in alphabetischer Reihenfolge) die Variablen m und x (Zeile 16).
  • Die Ausgabe der Struktur von ls(): character-Vektor mit zwei Komponenten (Zeile 17).
  • Die Ausgabe von objects(): sie ist identisch zur Ausgabe von ls() (Zeile 18).

Der Rückgabewert von weightedMean() wird in Zeile 19 angezeigt.

Zeile 21: Ruft man jetzt – also nicht mehr innerhalb von weightedMean() – die Funktion ls() auf, erhält man als einziges definiertes Objekt weightedMean; die Variablen m und x werden nicht angezeigt.

Man beachte: An der Ausgabe von ls() kann man nicht zwischen einer Funktion und einer Variable unterscheiden; als Objekte erscheinen sie hier gleichwertig.

Zeile 23 und 25: Der Aufruf der Struktur von ls() und von objects() müssen nicht nochmals erklärt werden.

Zeile 28: Es werden wieder alle Objekte aus dem workspace gelöscht.

Zeile 30 bis 37: Es wird eine andere Version von weightedMean() implementiert; sie berechnet den Rückgabewert etwas umständlcher, indem Zwischenergebnisse z und weights berechnet werden. Nach deren Berechnung folgen wieder die Ausgaben von ls() und objects() (Zeile 34 und 35).

Zeile 39 bis 42: Beim Aufruf von weightedMean() werden jetzt vier Objekte angezeigt: Die beiden Eingabewerte m und x und zusätzlich die lokal definierten Variablen weights und z. Zeile 42 zeigt wieder den Rückgabewert von weightedMean().

Zeile 44: Außerhalb der Funktion weightedMean() ist wiederum nur das Objekt weightedMean definiert.

Erklärung für die unterschiedlichen Ausgaben von ls():

Das Verhalten der Funktion ls() ist – wenn sie ohne Argumente aufgerufen wird –, abhängig davon, in welcher Umgebung sie aufgerufen wird:

  1. Legt man ein neuese Skript an, definiert dort Objekte, und ruft dann ls() auf, so befindet man sich in der globalen Umgebung (global environment).
  2. Definiert man eine Funktion, so wird innerhalb dieser eine neue Umgebung angelegt; sie enthält die Eingabewerte und alle Objekte, die innerhalb der Implementierung der Funktion definiert werden. Die Umgebung, aus der die Funktion aufgerufen wird, würde man als den parent frame bezeichnen (der hier mit der globalen Umgebung übereinstimmt).

Damit sind natürlich nicht alle Eigenschaften von Umgebungen erklärt; mehr Informationen dazu findet man unter ?environment im package base.

Die Argument-Liste einer Funktion: args()

Möchte man schnell wissen, welche Argumente eine Funktion erwartet, kann man mit Hilfe der Funktion args() die Argument-Liste ausgeben:

weightedMean <- function(x, m){
  return(sum(x * m) / sum(m))
}

args(name = weightedMean)
# function (x, m) 
#   NULL

args(name = "weightedMean")
# function (x, m) 
#   NULL

weightedMean <- function(x, m = 1){
  return(sum(x * m) / sum(m))
}

args(name = weightedMean)
# function (x, m = 1) 
#   NULL

args(name = mean)
# function (x, ...) 
#   NULL

args(name = print)
# function (x, ...) 
#   NULL

args(name = print.default)
# function (x, digits = NULL, quote = TRUE, na.print = NULL, print.gap = NULL, 
#           right = FALSE, max = NULL, useSource = TRUE, ...) 
#   NULL

Dass jeweils NULL angegeben wird, steht für den Körper (oder Rumpf) der Funktion und wird im nächsten Abschnitt erklärt.

Man erkennt:

  • Die Funktion args() kann sowohl für selbstdefinierte Funktionen als auch für Funktionen aus den Standard-Paketen angewendet werden (etwa Zeile 5 und 21).
  • Der Name kann, muss aber nicht, als Zeichenkette angegeben werden (siehe Zeile 9).
  • Besitzt ein Argument einen default-Wert, wird dieser angezeigt (siehe Zeile 13 und 17).
  • Man kann bei generisch implementierten Funktionen spezielle Versionen untersuchen (siehe Zeile 25 und 29).

Der Rückgabewert der Funktion args() ist eine Funktion, also ein Objekt vom Modus function():

weightedMean <- function(x, m){
  return(sum(x * m) / sum(m))
}

is.function(args.weightedMean)     # TRUE
is.primitive(args.weightedMean)    # FALSE

str(args.weightedMean)
# function (x, m)

Dass mit args() nicht die gesamte Funktion angezeigt wird, wird im nächsten Unterabschnitt deutlicher: Mit args() wird die Liste der Eingabewerte angezeigt, dagegen werden der Körper der Funktion und ihre Umgebung nicht angezeigt (daher die Ausgabe NULL in der jeweils zweiten Zeile der Ausgabe beim Aufruf von args()).

Die Bestandteile einer Funktion: Argument-Liste, Körper, Umgebung

Der Zugriff auf die Bestandteile einer Funktion

Eine Funktion hat drei Bestandteile:

Bestandteil der Funktion Funktion
Argument-Liste formals()
Körper (oder Rumpf) body()
Umgebung (environment) environment()

Mit Körper (oder Rumpf) ist natürlich die Implementierung der Funktion gemeint; die Umgebung einer Funktion ist nach den bisherigen Untersuchungen nur schwer zu verstehen, dies wird unten näher erklärt.

In der rechten Spalte der Tabellen stehen die Funktionen, mit denen der entsprechende Bestandteil einer Funktion aufgerufen werden kann.

Das folgende Skript zeigt für die Funktion weightedMean() diese drei Bestandteile und untersucht ihre Struktur:

weightedMean <- function(x, m){
  return(sum(x * m) / sum(m))
}

formals(fun = weightedMean)
# $x
# 
# 
# $m

str(formals(fun = weightedMean))
# Dotted pair list of 2
# $ x: symbol 
# $ m: symbol 

body(fun = weightedMean)
# {
#   return(sum(x * m)/sum(m))
# }

str(body(fun = weightedMean))
# language {  return(sum(x * m)/sum(m)) }
# - attr(*, "srcref")=List of 2
# ..$ :Class 'srcref'  atomic [1:8] 1 31 1 31 31 31 1 1
# .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x0000000005b58868> 
#   ..$ :Class 'srcref'  atomic [1:8] 2 3 2 29 3 29 2 2
# .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x0000000005b58868> 
#   - attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x0000000005b58868> 
#   - attr(*, "wholeSrcref")=Class 'srcref'  atomic [1:8] 1 0 3 1 0 1 1 3
# .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x0000000005b58868> 

environment(fun = weightedMean)
# <environment: R_GlobalEnv>

str(environment(fun = weightedMean))
# environment: R_GlobalEnv>

Zeile 1 bis 3: Es wird wieder die einfache Version von weightedMean() verwendet.

Zeile 5 bis 9: Die Argument-Liste sieht aus wie die Ausgabe einer Liste und hat die beiden Komponenten x und m.

Zeile 11 bis 14: Die Struktur zeigt aber, dass es sich um eine pairlist handelt. Diese ist eng verwandt mit einer Liste und wird meist nur intern von R verwendet. Näheres zu pairlist findet man unter ?list im package base. Weiter erkennt man, dass x und m Symbole (symbol) sind, was hier nicht erklärt werden soll. Kurz: Dabei handelt es sich um einen weiteren Modus, der den Namen eines Objektes angibt. Denn der formale Parameter in der Argument-Liste hat noch keinen Datentyp und keinen Wert; diese erhalten sie erst beim Aufruf der Funktion. Mehr dazu in er Dokumentation unter ?name im Paket base.

Zeile 16 bis 19: Die Funktion body() gibt die Implementierung der Funktion weightedMean() aus. Wie bei formals() wird durch die Funktion body() nicht nur eine Zeichenkette erzeugt, die die Implementierung beinhaltet; welchen Datentyp der Rückgabewert von body() hat, erhält man durch die Ausgabe der Struktur.

Zeile 21 bis 30: Die Ausgabe der Struktur von body(fun = weightedMean) wirkt zunächst sehr verwirrend und soll auch nicht im Detail erklärt werden. Der Rückgabewert von body() ist vom Typ language, die weiteren Eigenschaften lassen darauf schließen, wie der Quelltext von weightedMean() intern verarbeitet wird. Auch der Typ language kann hier nicht erklärt werden, siehe etwa ?typeof und ?is.language in der Dokumentation im Paket base.

Zeile 32 und 33: Mit der Funktion environment() wird die Umgebung der Funktion weightedMean() festgestellt; damit ist der Rahmen gemeint, in dem die Funktion definiert ist. Man erkennt, dass es die globale Umgebung (R_GlobalEnv) ist. Dies ist schlicht der workspace_, in dem man gerade arbeitet.

Zeile 35 und 36: Untersucht man den Rückgabewert von environment() erhält man ein Objekt vom Typ environment mit dem Wert R_GlobalEnv.

Formale Argumente und aufrufende Argumente

Zum besseren Verständnis von Funktionen sollte man – wie auch schon der letzte Unterabschnitt mit der Funktion formals() gezeigt hat – zwischen formalen Argumenten (formal arguments) und aufrufenden Argumenten (calling arguments) unterscheiden.

Das folgende Skript zeigt nochmals eine simple Implementierung von weightedMean() und dessen Aufruf:

weightedMean <- function(x, m){
  return(sum(x * m) / sum(m))
}

z <- weightedMean(x = (1:3), m = c(1, 1, 3))

In Zeile 1 wird die Funktion definiert; dabei sind x und m die beiden Eingabewerte, die man besser als formale Argumente bezeichnen sollte.

Sie haben ihren Gültigkeitsbereich in der Implementierung (also innerhalb der geschweiften Klammern) und können dort wie gegebene Größen verwendet werden, obwohl sie nirgends initialisiert wurden. Auch ihr Datentyp ist unbekannt – obwohl man beim Implementieren immer einen bestimmten Anwendungsfall abdecken möchte, der bestimmte Datentypen voraussetzt.

Erst wenn die Funktion aufgerufen wird (Zeile 5) werden die Variablen x und m initialisiert. Um x und m von den formalen Argumenten aus Zeile 1 zu unterscheiden, sollte man sie in Zeile 5 besser als aufrufende Argumente bezeichnen.

Der Unterschied zwischen print() und body()

Im Beispiel oben, in dem die Funktion body() eingeführt wurde, ist schwer zu ersehen, worin der Unterschied zwischen der Ausgabe des Quelltextes mit print() und body() sein soll.

Der erste Unterschied bezieht sich auf den Rückgabewert:

  • Die Funktion print() gibt immer das Objekt zurück, das ausgegeben wird.
  • Dagegen erzeugt die Funktion body() ein language-Objekt, das man zum Beispiel dazu verwenden könnte, eine neue Funktion zu erzeugen.

Der zweite Unterschied bezieht sich auf die Ausgabe:

  • Die Funktion print() gibt den Quelltext so aus, wie er bei der Definition der Funktion eingegeben wurde, insbesondere mit allen Kommentaren und Formatierungen.
  • Die Funktion body() entfernt Kommentare und spezielle Text-Formatierungen. (Das Attribut srcref, das bei einer Funktion automatisch gesetzt wird, verweist wie oben gesagt auf den Quelltext einer Funktion; hier ist derjenige Quelltext abgespeichert, der bei der Implementierung eingegeben wurde.)

Im folgenden Skript enthält die Funktion weightedMean() einen Kommentar im Quelltext. In der Ausgabe mit body() fehlt der Kommentar, nicht in der Ausgabe mit print().

weightedMean <- function(x, m){
  # Prüfung der Eingabewerte
  return(sum(x * m) / sum(m))
}

body(fun = weightedMean)
# {
#   return(sum(x * m)/sum(m))
# }
# Beachte: Kommentare werden entfernt

print(weightedMean)
# function(x, m){
#   # Prüfung der Eingabewerte
#   return(sum(x * m) / sum(m))
# }

Funktionen mit identischen Namen

Da es schon in den Basis-Paketen eine unüberschaubare Anzahl von Funktionen gibt, kann es leicht passieren, dass man einen bereits bestehenden Funktions-Namen wählt. Oder es kann vorkommen, dass man nachträglich ein zusätzliches Paket installiert und dadurch Namenskonflikte auftreten. Daher sollte man wissen, wie sich diese Konflikte vermeiden lassen.

Das folgende Skript implementiert eine Funktion mean(), die lediglich:

  • eine Konsolen-Ausgabe erzeugt,
  • die eigentliche Berechnung an die Funktion mean() aus dem Paket base weiterreicht.
mean <- function(x, ...){
  message("Weiterreichen der Argumente an mean()")
  return(base::mean(x = x, ...))
}

mean(x = (1:4))
# Weiterreichen der Argumente an mean()
# [1] 2.5

environment(fun = mean)
# <environment: R_GlobalEnv>

rm(list = ls())

environment(fun = mean)
# <environment: namespace:base>

Zeile 1 bis 4: Die selbstdefinierte Funktion mean() besitzt genau die Eingabewerte, die auch die Funktion mean() aus dem Paket base besitzt.

Zeile 3: Durch base::mean(x = x, ...) wird die Funktion mean() aus dem Paket base angesprochen. Man beachte, dass x im return-statement doppelt vorkommt: Das x auf der rechten Seite der Zuweisung x = x ist das formale Argument x; denn in Zeile 1 ist x ein Eingabewert und somit wird der Wert von x von außen an die Funktion mean() übergeben und ist innerhalb der Implementierung eine lokale Variable. Dagegen ist x auf der linken Seite der Zuweisung das Argument der Funktion mean() aus dem Paket base. Insgesamt führt die Zuweisung x = x dazu, dass x von der selbstdefinierten Funktion mean() an mean() aus dem Paket base weitergereicht wird.

Zeile 6: Beim Aufruf von mean() ist jetzt nicht klar, welche der beiden Funktionen aufgerufen wird: mean() aus dem Paket base oder die selbstdefinierte Version. An den Ausgaben erkennt man:

  • Die Konsolen-Ausgabe wird angezeigt, folglich wurde die selbstdefinierte Funktion aufgerufen.
  • Der Mittelwert wird richtig angegeben; das Weiterreichen der Argumente an base::mean() hat also funktioniert (in der selbstdefinierten Version war keine Berechnung vorgesehen).

Zeile 10: Lässt man sich die Umgebung von mean() anzeigen, erhält man die globale Umgebung <environment: R_GlobalEnv> .

Zeile 13: Es werden alle Objekte aus dem workspace gelöscht.

Zeile 15: Lässt man sich jetzt mit environment() die Umgebung von mean() anzeigen, erhält man <environment: namespace:base> .

Die Ausgabe aus Zeile 16 zeigt, dass die Pakete nicht nur verwendet werden, um für den Leser der Dokumentation eine übersichtliche Struktur zu schaffen. Sondern der Name eines Paketes ist zugleich ein sogenannter Namensraum (namespace), mit dem eine Umgebung angesprochen werden kann.

Wird wie in Zeile 10 nach environment(fun = mean) gefragt, so wird zunächst in der globalen Umgebung nach einem Objekt namens mean gesucht. Da dieses in Zeile 1 bis 4 definiert wurde, wird als Umgebung von mean die globale Umgebung angegeben.

Nachdem aber alle Objekte aus der globalen Umgebung gelöscht wurden (Zeile 13), wird in anderen Umgebungen nach dem Objekt mean gesucht. Daher wird jetzt als Umgebung der Namensraum base (<environment: namespace:base> ) angegeben.

Umgekehrt sollte klar sein: Umgebungen werden so organisiert, dass sie niemals zwei Objekte mit identischem Namen enthalten.

Aufgabe:

Testen Sie obige selbstdefinierte Version von mean(), wenn Sie im return-statement nicht auf das Paket base verweisen.

Achtung: Sie sollten diesen Test nur durchführen, wenn Sie wissen, wie Sie eine Endlos-Schleife beenden können (meist ein roter Knopf in der Entwicklungsumgebung).

Zusammenfassung: Diagnose-Funktionen

Die folgende Tabelle zeigt die oben besprochenen Diagnose-Funktionen.

Achtung: Bei den hier gezeigten Funktionen sind nicht immer alle Eingabewerte angeführt, sondern nur diejenigen, die oben besprochen wurden. Für eine ausführliche Beschreibung der Funktionen ist die Dokumentation zu Rate zu ziehen.

Funktion Beschreibung
print(x) Das Verhalten von print() ist davon abhängig, von welcher Art die Funktion x ist: Bei selbstdefinierten Funktionen wird die komplette Definition ausgegeben, bei primitiven Funktionen wird nur die Argument-Liste ausgegeben (und der Hinweis, dass es sich um eine primitive Funktion handelt), bei generisch implementierten Funktionen muss man die spezielle Version aufrufen, um den Quelltext zu sehen.
mode(x) Gibt den Modus einer Funktion an (als character): "function" .
str(x) Ausgabe der Struktur einer Funktion x; ausgegeben wird immer die Argument-Liste, bei selbstdefinierten Funktionen sind zusätzliche Attribute über den Quelltext gesetzt, die angezeigt werden.
attributes(x) Attribute einer Funktion x: Bei selbstdefinierten Funktionen ist das Attribut $srcref gesetzt, bei anderen Funktionen NULL.
ls() Zeigt alle Objekte an, die sich in der Umgebung befinden, in der ls() aufgerufen wird.
args(name) Ausgabe der Argument-Liste einer Funktion name.
formals(fun) Argument-Liste von fun.
body(fun) Körper (oder Rumpf) einer Funktion: Implementierung
environment(fun) Umgebung einer Funktion fun.

Die folgende Tabelle beschreibt die Rückgabewerte der Funktionen und ihre Datentypen:

Funktion Rückgabewert Datentyp
print(x) Die Funktion x wird zurückgegeben. Objekt mit Modus function
mode(x) Gibt den Modus einer Funktion an (als character): "function" . Zeichenkette (character-Vektor der Länge 1)
str(x) Kein Rückgabewert: nur Ausgabe der Struktur auf der Konsole. NULL
attributes(x) Attribute einer Funktion x: Bei selbstdefinierten Funktionen ist das Attribut $srcref gesetzt, bei anderen Funktionen NULL. Liste der Attribute oder NULL
ls() character-Vektor aller Objekte, die sich in der Umgebung befinden, in der ls() aufgerufen wird. character-Vektor
args(name) Argument-Liste einer Funktion name ohne Körper (NULL). Funktion (Modus function)
formals(fun) pairlist der Argument von fun. pairlist
body(fun) Körper (oder Rumpf) einer Funktion: Implementierung Objekt vom Typ language
environment(fun) Umgebung einer Funktion fun. Objekt vom Typ environment
Alle Kommentare
Durch die Nutzung dieser Website erklären Sie sich mit der Verwendung von Cookies einverstanden. Unsere Datenschutzbestimmungen können Sie hier nachlesen