Eigenschaften von Funktionen in R

Eine Funktion ruft einen Quelltext-Abschnitt auf, der durch Eingabewerte konfiguriert werden kann und der einen R├╝ckgabewert berechnet. Allgemeine Eigenschaften und Besonderheiten ├╝ber die Eingabewerte und den R├╝ckgabewert einer Funktion werden besprochen. Dies soll auf das n├Ąchste Kapitel vorbereiten, in dem gezeigt wird, wie man Funktionen selbst definiert.
Noch keine Stimmen abgegeben
Noch keine Kommentare

Einordnung des Artikels

Einf├╝hrung

Programme oder Skripten bestehen ├╝blicherweise aus einzelnen Anweisungen. Im Sinne der strukturierten Programmierung werden Bl├Âcke von Anweisungen zu einer Funktion zusammengefasst, um

  • den Block von Anweisungen nicht bei jedem Gebrauch wiederholen zu m├╝ssen,
  • Teile von Programmen wiederverwendbar zu machen; dies kann soweit f├╝hren, dass man sich ganze Bibliotheken aufbaut.

Den Aufruf einer Funktion kann man sich dann folgenderma├čen vorstellen (siehe auch Abbildung 1 unten):

  1. Durch den Namen der Funktion wird der entsprechende Anweisungs-Block angesprochen und ausgef├╝hrt.
  2. Die Funktion kann Eingabewerte besitzen, damit der Anweisungs-Block von au├čen konfiguriert werden kann.
  3. Die Funktion berechnet einen R├╝ckgabewert, der an der Stelle des Aufrufs der Funktion eingesetzt wird.
  4. Eine Funktion kann zus├Ątzliche Operationen ausf├╝hren, etwa Konsolenausgaben erzeugen (zum Beispiel Warnungen).

Beispiel: Im folgenden Skript wird die Funktion mean() mit dem Vektor v aufgerufen, der vorher definiert wurde. Das Ergebnis der Berechnung von mean(v) wird in der Variable x abgespeichert und ausgegeben:

v <- (1:4)
x <- mean(v)
x
# [1] 2.5

Die Funktion mean() berechnet den Mittelwert des Vektors v und daher hat Zeile 2 dieselbe Wirkung wie die Zuweisung x <- 2.5 . Weitere Aktionen f├╝hrt die Funktion mean() hier nicht aus.

Beim folgenden Aufruf von mean() wird zus├Ątzlich zur Berechnung des R├╝ckgabewertes eine Warnung ausgegeben:

v <- c("A", "B")
x <- mean(v)
x
# Warning message:
#   In mean.default(v) :
#   Argument ist weder numerisch noch boolesch: gebe NA zur├╝ck
# [1] NA

Da man von Zeichen keinen Mittelwert berechnen kann, ist in der Implementierung von mean() vorgesehen, dass der entsprechende Warn-Hinweis auf der Konsole erscheint. Als R├╝ckgabewert wird jetzt NA berechnet (Ausgabe in Zeile 7).

ÔÖŽ ÔÖŽ ÔÖŽ ÔÖŽ ÔÖŽ

Abbildung 1 versucht den Aufruf einer Funktion f() darzustellen:

  1. In einem Skript erfolgt der Funktions-Aufruf in einer Zuweisung z <- f(x, y) .
  2. Dazu werden die Eingabewerte x und y der Funktion an die Implementierung der Funktion weitergereicht.
  3. Die Implementierung von f() ist nur durch { ... } angedeutet. Hier befindet sich ein Block von Anweisungen, der x und y verwendet und einen R├╝ckgabewert berechnet.
  4. Der R├╝ckgabewert wird an der Stelle des Funktions-Aufrufs im Skript eingesetzt und wird somit der Variable z zugewiesen.

Abbildung 1: Aufruf einer Funktion f() mit zwei Eingabewerten x und y und Berechnung des R├╝ckgabewertes.Abbildung 1: Aufruf einer Funktion f() mit zwei Eingabewerten x und y und Berechnung des R├╝ckgabewertes.

So wie Funktionen bisher beschrieben wurden, werden sie in nahezu allen Programmiersprachen eingesetzt. In diesem Kapitel ├╝ber Funktionen werden einige Besonderheiten der Sprache R bez├╝glich Funktionen beschrieben. Dabei wird noch nicht auf selbstdefinierte Funktionen eingegangen; dies geschieht im n├Ąchsten Kapitel.

Der Aufruf einer Funktion

Funktionsaufrufe wurden bisher in allen Kapiteln verwendet, aber nicht eigens erkl├Ąrt. Dies soll hier nachgeholt werden. Dabei werden einige Eigenschaften von Funktionen ausdr├╝cklich genannt und erl├Ąutert, die so selbstverst├Ąndlich sind, dass sie eigentlich keiner Erkl├Ąrung bed├╝rfen. Um sp├Ąter selbst Funktionen zu definieren, ist die Kenntnis der Eigenschaften aber unentbehrlich.

Funktionen werden auf eine der beiden im Folgenden beschriebenen Arten eingesetzt:

  1. Entweder wird eine Funktion direkt aufgerufen: in der entsprechenden Zeile eines Skriptes steht nur der Funktionsaufruf.
  2. Oder die Funktion wird aufgerufen und das Ergebnis der Berechnung wird einer Variable zugewiesen.

Das folgende Skript zeigt diese beiden M├Âglichkeiten f├╝r die Funktion mean(x) :

x <- c(3, 5)

mean(x)
# [1] 4

y <- mean(x)

y
# [1] 4

In Zeile 1 wird ein Vektor x mit 2 Komponenten definiert.

Zeile 3: Der direkte Aufruf von mean(x) berechnet den Mittelwert der Komponenten von x und gibt den Mittelwert sofort auf der Konsole aus.

Zeile 6: Jetzt wird wieder mean(x) berechnet, aber das Ergebnis wird in der Variable y abgespeichert. Da in der Implementierung von mean() keine Konsolenausgabe vorgesehen ist, erscheint keine Ausgabe.

Zeile 8: Erst wenn y ausgegeben wird, erscheint das Ergebnis der Mittelwert-Berechnung auf der Konsole.

Das folgende Skript wiederholt diese Schritte f├╝r die Funktion str(); sie hat keinen R├╝ckgabewert, in ihrer Implementierung ist nur eine Konsolenausgabe vorgesehen:

x <- c(3, 5)

str(x)
#  [1:2] 3 5

y <- str(x)
# num [1:2] 3 5

y
# NULL

Zeile 3: Der Aufruf von str(x) erzeugt die Konsolenausgabe aus Zeile 4. Man beachte den Unterschied zu Zeile 4 im Skript mit der Funktion mean(): Dort wurde die Ausgabe mit [1] eingeleitet, da das Objekt ausgegeben wurde, das die Funktion mean() zur├╝ckgegeben hat. Jetzt erscheint keine [1] , da str() keinen R├╝ckgabewert besitzt.

Zeile 6 und 7: Speichert man str(x) in einem Objekt y ab, so wird beim Aufruf von str(x) wieder die Konsolenausgabe wie in Zeile 4 erzeugt; daran, dass keine f├╝hrende [1] vorhanden ist, erkennt man die Konsolenausgabe.

Zeile 9: Gibt man y aus, erh├Ąlt man NULL, da die Funktion str() keinen R├╝ckgabewert besitzt; die Konsolenausgabe erscheint nicht.

Das folgende Skript zeigt nochmal allgemein die beiden M├Âglichkeiten, wie eine Funktion f(x) eingesetzt werden kann:

# das Objekt x ist bereits gesetzt

f(x)

y <- f(x)

In Zeile 3 wird die Funktion f() mit dem Eingabewert x aufgerufen. Was wird dabei auf der Konsole ausgegeben? Dies h├Ąngt davon ab, wie f() implementiert ist:

  • Falls f() Konsolenausgaben erzeugt, werden zun├Ąchst diese ausgegeben.
  • Zuletzt wird der R├╝ckgabewert der Funktion f() ausgegeben. Wenn f() keinen R├╝ckgabewert besitzt, gibt es keine weitere Ausgabe.

Worin besteht der Unterschied zwischen den Zeile 3 und 5? In Zeile 5 wird wieder die Funktion f() mit dem Eingabewert x aufgerufen; das Ergebnis wird in der Variable y abgespeichert. Welche Konsolenausgaben werden dabei erzeugt?

  • Falls f() Konsolenausgaben erzeugt, werden sie wieder ausgegeben.
  • Der R├╝ckgabewert von f() wird nicht ausgegeben; dazu m├╝sste die Ausgabe von y angefordert werden.

Die folgende Skripte sollen den Unterschied von Zeile 3 und 5 (aus obigem Skript) f├╝r die Funktionen print() und cat() demonstrieren; dazu muss man wissen, dass print() den Eingabewert als R├╝ckgabewert besitzt, das hei├čt der Aufruf von print(x) sorgt daf├╝r, dass das Objekt x auf der Konsole ausgegeben wird. Dagegen hat cat() keinen R├╝ckgabewert; der Aufruf von cat(x) sorgt f├╝r die Ausgabe von x auf der Konsole. Wo soll dann der Unterschied sein?

x <- (1:3)

print(x)
# [1] 1 2 3

y <- print(x)
y
# [1] 1 2 3

cat(x)
# 1 2 3

z <- cat(x)
# 1 2 3

z
# NULL

Zeile 1: Testobjekt f├╝r die folgenden Funktionsaufrufe ist der Vektor x.

Zeile 3 und 4: R├╝ckgabewert von print() ist der Eingabewert, daher wird nach der [1] der Vektor x ausgegeben.

Zeile 6 bis 8: Wird print() in der Zuweisung auf der rechten Seite eingesetzt, erfolgt noch keine Ausgabe. Erst die Ausgabe von y gibt den Vektor aus, der zuvor als x gespeichert wurde.

Zeile 10 und 11: Der Aufruf von cat() gibt den Vektor x aus. Da hier kein R├╝ckgabewert ausgegeben wird, fehlt [1] in der Ausgabe.

Zeile 13 und 14: Speichert man cat(x) in einer Variable z ab, erfolgt dennoch die Konsolenausgabe ÔÇô sie wird in der Implementierung von cat() ausgel├Âst. Wieder fehlt die [1] in der Ausgabe.

Zeile 16 und 17: Gibt man jetzt z aus, erh├Ąlt man NULL, da cat() keinen R├╝ckgabewert besitzt.

Die Eingabewerte einer Funktion

Funktionen in der Programmierung haben viele Eigenschaften mit mathematischen Funktionen gemeinsam, es gibt aber auch einige wichtige Unterschiede, so dass die Namensgleichheit "Funktion" zu voreiligen Schl├╝ssen f├╝hren kann.

Die Eingabewerte einer Funktion haben in der Mathematik und in der Programmierung viele Gemeinsamkeiten und sollen zuerst n├Ąher beleuchtet werden.

In der Mathematik kann man etwa eine Funktion f durch die Angabe eines Definitionsbereiches und einer Zuordnungsvorschrift definieren. So ist etwa mit

f: R ├Ś R Ôćĺ R, f(x, y) = x2 + y2

eine quadratische Funktion auf dem Definitionsbereich Df = R ├Ś R gegeben.

Hier sind x und y die Eingabewerte, die jeden beliebigen Wert aus den reellen Zahlen R annehmen d├╝rfen. Durch den Funktionsterm f(x, y) wird der Funktionswert an der Stelle (x, y) berechnet.

In R k├Ânnen Funktionen beliebig viele Eingabewerte besitzen. Dass zur Vereinbarung einer Funktion eine Angabe des Definitionsbereiches der Eingabewerte geh├Ârt, ist nicht so streng geregelt wie in der Mathematik.

Um ein Beispiel zu nennen: In der Dokumentation unter Set Operations findet man etwa die Funktion union(x, y) . Hier sind x und y die Eingabewerte. Laut Dokumentation sind x und y Vektoren mit identischem Modus, die als Mengen interpretiert werden (doppelte Elemente werden nicht ber├╝cksichtigt); die Funktion union(x, y) berechnet die Vereinigungsmenge von x und y und gibt sie als Vektor zur├╝ck.

Im Folgenden werden die Eingabewerte einer Funktion genauer untersucht:

  • Wie m├╝ssen ihre Datentypen vereinbart werden?
  • Welche Bedeutung haben die Namen der Eingabewerte?
  • Es gibt sogenannte optionale Eingabewerte und manche Eingabewerte haben voreingestellte default-Werte.
  • M├╝ssen die Eingabewerte in einer bestimmten Reihenfolge eingegeben werden?

Datentypen der Eingabewerte

In streng typisierten Programmiersprachen m├╝ssen die Datentypen der Eingabewerte einer Funktion eindeutig festgelegt werden. Dagegen werden in R Datentypen dynamisch erkannt. Dies hat folgende Konsequenz: Man kann f├╝r jeden Eingabewert einer Funktion im Prinzip jeden beliebigen Datentyp verwenden; werden die Anweisungen der Funktion ausgef├╝hrt, wird Schritt f├╝r Schritt entschieden, ob die entsprechende Anweisung sinnvoll ausgef├╝hrt werden kann oder nicht.

Man kann dies etwa am Beispiel der Funktion union() erkl├Ąren: Die Eingabewerte sollen eigentlich Vektoren sein, die als Mengen interpretiert werden; der R├╝ckgabewert ist dann der Vektor, der der Vereinigungsmenge entspricht. Auf den ersten Blick ist es daher unsinnig, f├╝r die Eingabewerte einen Vektor und eine Matrix zu w├Ąhlen. Im folgenden Skript geschieht dies und man erh├Ąlt einen sinnvollen R├╝ckgabewert:

m <- matrix(data = (1:6), nrow = 2, byrow = TRUE)
m

v <- (7:9)

union(x = m, y = v)
# [1] 1 4 2 5 3 6 7 8 9

Da eine Matrix intern wie ein Vektor abgespeichert wird (wie die Zahlen zweidimensional anzuordnen sind, regelt das dim-Attribut), ist auch der Funktions-Aufruf aus Zeile 7 sinnvoll; es wird die Menge (1:9) ÔÇô in ungew├Âhnlicher Sortierung ÔÇô gebildet.

Etwas allgemeiner kann man jetzt fragen: Welche M├Âglichkeiten sind denkbar, wenn eine Funktion nicht die Eingabewerte erh├Ąlt, die eigentlich vorgesehen sind? Die Antwort h├Ąngt nat├╝rlich von der Implementierung der Funktion ab ÔÇô als Programmierer hat man hier einen gewissen Spielraum. Um die wichtigsten M├Âglichkeiten zu nennen:

  1. Man k├╝mmert sich bei der Implementierung nicht um die Datentypen der Eingabewerte, sondern implementiert die Funktion so wie es der gew├╝nschte Anwendungsfall erfordert. Die Entscheidung, was bei "unerw├╝nschten" Datentypen geschieht, wird dem R-Interpreter ├╝berlassen, der eventuell einen Fehler oder eine Warnung anzeigt.
  2. Man sorgt selbst daf├╝r, dass bei "unerw├╝nschten" Datentypen eine Warnung erscheint und ein geeigneter R├╝ckgabewert berechnet wird (etwa NA). Der Aufruf von mean() mit einem character-Vektor, der oben gezeigt wurde, ist ein Beispiel hierf├╝r.

Welche Variante man w├Ąhlt, sollte man in der Dokumentation der Funktion festhalten, damit ein Anwender der Funktion ├╝ber das Verhalten der Funktion Bescheid wei├č. Es ist auch empfehlenswert ein wenig in der R-Dokumentation zu bl├Ąttern und nachzulesen, wie "unerw├╝nschte" Datentypen behandelt werden oder einige Funktion daraufhin zu testen.

In einer streng typisierten Programmiersprache wird der Name einer Funktion und die Liste der Eingabewerte und ihrer Datentypen als die Signatur einer Funktion bezeichnet. In Programmiersprachen wie R, wo Datentypen dynamisch erkannt werden, ist diese Bezeichnung nicht so ├╝blich.

Namen der Eingabewerte

Ein gro├čer Vorzug der Programmiersprache R ist, dass die Eingabewerte einer Funktion Namen haben, die man beim Aufruf der Funktion angeben kann ÔÇô man sollte hier besser sagen: angeben sollte. Denn die Namen der Eingabewerte machen in vielen F├Ąllen die Quelltexte leichter lesbar.

Das folgende Beispiel mag hier noch nicht ├╝berzeugend sein:

union((1:3), (7:9))

union(x = (1:3), y = (7:9))

Die beiden Funktions-Aufrufe in Zeile 1 und 3 sind gleichwertig und f├╝hren zu einem identischen Ergebnis. Da man am Namen der Funktion ablesen kann, welche Aufgabe hier bearbeitet wird, sind die Namen x und y f├╝r die Eingabewerte kaum hilfreich.

Das folgende Beispiel zeigt, wie der Einsatz der Argument-Namen den Quelltext leichter verst├Ąndlich macht. Verwendet wird jetzt die Funktion sort(), mit der sich ein Vektor sortieren l├Ąsst:

sort(x, decreasing = FALSE)

Eingabewerte sind der zu sortierende Vektor x und ein logischer Wert decreasing, der angibt, ob die Werte von x absteigend oder aufsteigend sortiert werden sollen; der default-Wert ist decreasing = FALSE , also aufsteigende Sortierung.

Im folgenden Skript wird zun├Ąchst ein Vektor v definiert, der dann aufsteigend sortiert wird. Dies geschieht dreimal und eigentlich sind alle drei Versionen identisch:

v <- c(1, 3, 2)

v.asc <- sort(v, FALSE)
v.asc
# [1] 1 2 3

v.asc <- sort(v)
v.asc
# [1] 1 2 3

v.asc <- sort(x = v, decreasing = FALSE)
v.asc
# [1] 1 2 3

In der ersten Version (Zeile 3) werden die beiden Argumente x und decreasing gesetzt, aber die Argument-Namen werden nicht angegeben. Damit der Leser die Anweisung sort(v, FALSE) richtig versteht, muss er parat haben, dass das zweite Argument decreasing hei├čt und nicht etwa increasing.

In der zweiten Version (Zeile 7) wird das zweite Argument nicht gesetzt; da decreasing mit einem default-Wert ausgestattet ist, muss dieser nicht explizit angegeben werden, wenn man ihn einsetzen m├Âchte (mehr zu default-Werten weiter unten). F├╝r einen Leser, der nicht parat hat, wie per default sortiert wird, ist diese Anweisung nicht verst├Ąndlich.

Die dritte Version (Zeile 11) schlie├člich setzt die Argument-Namen ein und gibt sogar den eigentlich ├╝berfl├╝ssigen default-Wert an. Dadurch ist der Quelltext leicht verst├Ąndlich und erspart im Zweifelsfall ein Nachschlagen in der Dokumentation.

Tip: Setzen Sie immer die Argument-Namen. Das Lesen der Quelltexte wird dadurch unglaublich erleichtert und man kann sich besser auf seine eigentlichen Aufgaben konzentrieren.

Umgekehrt mag es als zus├Ątzlicher Aufwand erscheinen, wenn man beim Schreiben von Quelltexten immer die Argument-Namen angibt. Langfristig lohnt sich dies aber, da man dadurch nur besser mit den Funktionen vertraut wird.

Optionale Eingabewerte und default-Werte f├╝r Eingabewerte

Oben wurde beim Beispiel der Funktion sort() erkl├Ąrt, dass sie ein Argument decreasing = FALSE besitzt. In der Dokumentation (und dann auch, wenn man Funktionen selber implementiert) wird dies folgenderma├čen ausgedr├╝ckt:

sort(x, decreasing = FALSE)

Dies ist so zu lesen:

  1. Das Argument x ist verpflichtend; damit wird der Vektor eingegeben, der sortiert werden soll.
  2. Das Argument decreasing ist optional, das hei├čt es muss nicht gesetzt werden. Wird es nicht gesetzt, wird der default-Wert FALSE verwendet (und ansteigend sortiert).

Ein weiteres Beispiel, das in ├Ąhnlicher Form sehr oft auftritt, betrifft die Behandlung von NA-Werten. So besitzt zum Beispiel die Funktion mean() die Argumente x und na.rm, wobei Letzteres den default-Wert FALSE besitzt:

mean(x, na.rm = FALSE)

Die Bedeutung sollte klar sein:

  1. Das Argument x steht f├╝r den Vektor, von dessen Komponenten der Mittelwert berechnet werden soll.
  2. Das Argument na.rm regelt, wie NA-Werte behandelt werden. Im Fall von na.rm = FALSE verbleiben sie im Vektor x, im Fall von na.rm = TRUE werden sie aus dem Vektor entfernt. Wird das Argument na.rm nicht ausdr├╝cklich gesetzt, wird der default-Wert verwendet.

Gerade wenn man selber Funktionen implementiert, kennt man die typischen Anwendungsf├Ąlle der Funktionen und kann dem Anwender der Funktion durch geschicktes Setzen von optionalen Argumenten und ihren default-Werten die Arbeit erleichtern.

Aufgabe: St├Âbern Sie ein wenig in der R-Dokumentation und achten Sie darauf, f├╝r welche Argumente default-Werte gesetzt werden.

Das Argument "..." (dot-dot-dot)

Wer schon mit einer streng typisierten Programmiersprache gearbeitet hat, ist vielleicht verwirrt, wenn eine Funktion Eingabewerte wie sum() besitzt:

sum(..., na.rm = FALSE)

Das zweite Argument wurde soeben erkl├Ąrt. Aber was soll sich hinter ... verbergen? Oder genauer gefragt: Welchen Datentyp hat ein Objekt, das hier eingegeben werden soll?

Die Notation ... in der Argument-Liste einer Funktion steht meist daf├╝r, dass hier beliebig viele Objekte gesetzt werden k├Ânnen. Ob diese Objekte einen bestimmten Datentyp haben m├╝ssen, ist zun├Ąchst nicht festgelegt. Wie oben erkl├Ąrt wurde, entscheidet dar├╝ber die Implementierung der Funktion ÔÇô und entsprechende Hinweise sollten in der Dokumentation enthalten sein.

Um drei typische Beispiele zu nennen, die im Folgenden kurz erkl├Ąrt werden:

  1. Die Funktion list(...) .
  2. Die Funktion sum(..., na.rm = FALSE) .
  3. Die Funktion rep(x, ...) .

1. Beispiel: Die Funktion list(...)

Die Funktion list() dient dazu, mehrere Objekte zu einer Liste zusammenzufassen. Anders als bei einem Vektor k├Ânnen diese Objekte beliebigen Datentyp haben ÔÇô genau das macht eine rekursive Struktur aus. Das hei├čt aber, dass man sich hier nicht um die Datentypen der Argumente k├╝mmern muss, es sind sogar wiederum Listen erlaubt.

Das folgende Beispiel wurde im Kapitel ├╝ber Listen mehrfach verwendet:

book.R <- list(title = "Introduction to R", 
            author = "R-expert", year = 2018)

Hier werden also zwei Zeichenketten und eine Zahl zu einer Liste mit drei Komponenten zusammengefasst.

Man erkennt an dem Beispiel noch eine weitere Besonderheit: Das Argument ... besitzt eigentlich keinen Namen, anders als zum Beispiel die Funktion mean(x, na.rm = FALSE) , die man etwa mit mean(x = c(1, 3, 5) aufrufen k├Ânnte. Die Namen, die im Beispiel mit list() verwendet werden, sind keine Argument-Namen, sondern Namen, die dann f├╝r das Attribut names verwendet werden. Derartige Dinge regelt dann nat├╝rlich die Implementierung einer Funktion und sollte in der Dokumentation erl├Ąutert sein.

2. Beispiel: Die Funktion sum(..., na.rm = FALSE)

Das Argument ... in der Funktion sum() hat eine leicht andere Bedeutung als in der Funktion list(). Denn jetzt steht ... f├╝r die Objekte, deren Summe berechnet werden soll. Da man Summen nur von Zahlen (und nicht von Zeichenketten oder Listen) berechnen kann, m├╝ssen hier numerische Vektoren (Modus numeric) eingegeben werden. Versucht man andere Objekte zu setzen, erh├Ąlt man eine Fehlermeldung. Aber wie bei der Funktion list() k├Ânnen hier beliebig viele Objekte eingegeben werden.

3. Beispiel: Die Funktion rep(x, ...)

Die Funktion rep() dient dazu, Wiederholungen von x zu erzeugen. Mit den Argumenten ... k├Ânnen weitere Parameter gesetzt werden ÔÇô welche ist nat├╝rlich in der Dokumentation nachzulesen. Zum Beispiel das Argument times, das angibt, wie oft x wiederholt werden soll; oder das Argument each, das angibt, wie oft die einzelnen Komponenten von x wiederholt werden sollen. Hier sind einige Beispiele:

v <- rep(x = 17, times = 5)
v
# 17 17 17 17 17

v <- (1:3)
v3 <- rep(x = v, each = 3)
v3
[1] 1 1 1 2 2 2 3 3 3

In der Dokumentation ist genau festgelegt, welche Argumente anstelle von ... eingegeben werden k├Ânnen.

Die Reihenfolge der Argumente

Oben wurde schon beschrieben, dass die Eingabewerte einer Funktion Namen besitzen und dass die Quelltexte leichter lesbar sind, wenn man sie stets angibt. Die Argument-Namen erm├Âglichen eine weitere Erleichterung: Man muss nicht auf die Reihenfolge der Argumente achten, wenn man die Namen angibt.

Das folgende Beispiel zeigt drei identische Funktionsaufrufe:

rep(x = 17, times = 5)
# [1] 17 17 17 17 17

rep(times = 5, x = 17)
# [1] 17 17 17 17 17

rep(17, 5)
# [1] 17 17 17 17 17

Die ersten beiden Funktions-Aufrufe verwenden Namen, daher ist es unerheblich, in welcher Reihenfolge die Argumente gesetzt werden.

Der dritte Funktionsaufruf (Zeile 7) verwendet keine Argument-Namen; jetzt werden die Argumente in der Reihenfolge interpretiert, wie sie in der Dokumentation angegeben werden. Da das zweite Argument der Funktion ... lautet, ist bei Zeile 7 nicht eindeutig erkennbar, welches der hier erlaubten Argumente gleich 5 gesetzt wird. Daran siehrt man nochmal, wie hilfreich es ist, immer die Argument-Namen anzugeben.

Zusammenfassend kann man die Vorgehensweise des Interpreters beim Auswerten der Argumente einer Funktion so beschreiben:

  1. Sind keine Argument-Namen gesetzt, werden die Argumente in ihrer Reihenfolge gelesen und die Werte der Argument-Liste einer Funktion zugeordnet.
  2. Sind Argument-Namen gesetzt, ist die Reihenfolge irrelevant; die Zuordnung erfolgt allein durch die Namen.

Operatoren und Funktionen als Eingabewert

Bisher wurden stets Daten als Argumente einer Funktion verwendet, also Zahlen, Vektoren, Zeichenketten oder ├Ąhnliche Objekte. Es ist auch m├Âglich als Argument eine Funktion oder einen Operator zu setzen. Ein Paradebeispiel daf├╝r ist die Funktion outer(), die bereits in Das ├Ąu├čere Produkt outer() im Kapitel Matrizen in R: der Datentyp matrix ausf├╝hrlich besprochen wurde. Andere wichtige Beispiele sind die Funktionen der apply()-Familie, die sp├Ąter besprochen werden (die Funktion tapply() wurde bereits bei Faktoren besprochen, siehe Die Funktion tapply() im Kapitel Faktoren in R: Anwendungen).

Die Funktion outer(X, Y, FUN = "*", ...) besitzt zwei Vektoren X und Y als Eingabewerte (man kann auch beliebige Felder verwenden); von diesen beiden Vektoren wird das ├Ąu├čere Produkt gebildet, wobei die entsprechenden Komponenten durch den mit FUN angegebenen Operator verkn├╝pft werden. Der default-Wert f├╝r FUN ist die Multiplikation. Anstelle eines Operators kann man auch eine Funktion ├╝bergeben. Das folgende Skript zeigt einige Beispiele:

v <- (1:6)

# outer mit Operator +
m <- outer(X = v, Y = v, FUN = "+")
m
#     [,1] [,2] [,3] [,4] [,5] [,6]
# [1,]    2    3    4    5    6    7
# [2,]    3    4    5    6    7    8
# [3,]    4    5    6    7    8    9
# [4,]    5    6    7    8    9   10
# [5,]    6    7    8    9   10   11
# [6,]    7    8    9   10   11   12

# outer() mit Funktion pmax (paralleles Maximum)
m <- outer(X = v, Y = v, FUN = pmax)
m
#      [,1] [,2] [,3] [,4] [,5] [,6]
# [1,]    1    2    3    4    5    6
# [2,]    2    2    3    4    5    6
# [3,]    3    3    3    4    5    6
# [4,]    4    4    4    4    5    6
# [5,]    5    5    5    5    5    6
# [6,]    6    6    6    6    6    6

# outer() mit einer anonymen Funktion (die ein gewichtetes Mittel berechnet)
v <- (1:3)
m <- outer(X = v, Y = v, 
          FUN = function(x, y, ax, ay){
             a <- ax + ay;
             return( (ax * x + ay * y) / a )
          }, 2, 1)
m
#     [,1]     [,2]     [,3]
# [1,] 1.000000 1.333333 1.666667
# [2,] 1.666667 2.000000 2.333333
# [3,] 2.333333 2.666667 3.000000

Das erste Beispiel k├Ânnte man wie folgt interpretieren: Es wird zweimal nacheinander gew├╝rfelt und es wird f├╝r die 36 m├Âglichen Kombinationen die Augensumme gebildet.

Im zweiten Beispiel wird die maximale Augenzahl der beiden W├╝rfe gebildet.

Im dritten Beispiel wird eine anonyme Funktion an FUN ├╝bergeben; anonym bedeutet, dass die Funktion sofort implementiert wird. Wenn man in der Implementierung zuerst ax = 1 und ay = 1 setzt, wird der Mittelwert von x und y berechnet und zur├╝ckgegeben. Die Faktoren ax und ay sorgen daf├╝r, dass ein gewichteter Mittelwert von x und y berechnet wird. (Anonyme Funktionen werden dann im Kapitel ├╝ber selbstdefinierte Funktionen ausf├╝hrlich erl├Ąutert.)

Oben wurde schon gesagt, dass die Funktion outer() die Eingabewerte hat: outer(X, Y, FUN = "*", ...) . Im dritten Beispiel erkennt man jetzt auch die Verwendung des Argumentes ... in outer(): Die beiden Zahlen 2 und 1 (also die beiden letzten Argumente von outer() werden an die anonyme Funktion weitergegeben und setzen die Gewichtungen ax = 2 und ay = 1.

Aufgabe: Testen Sie den Funktionsaufruf

m <- outer(X = v, Y = v, 
          FUN = function(x, y, ax, ay){
             a <- ax + ay;
             return( (ax * x + ay * y) / a )
          }, 1, 1)

Jetzt sollte der Mittelwert berechnet werden, den man auch mit FUN = mean erh├Ąlt.

Wie man f├╝r das Argument FUN selbstdefinierte Funktionen einsetzen kann, wird dann im n├Ąchsten Kapitel gezeigt.

Der R├╝ckgabewert einer Funktion

Der Datentyp des R├╝ckgabewertes

Betrachtet man jetzt eine Zuweisung wie

y <- f(x)

unter dem Aspekt, welche Datentypen der R├╝ckgabewert von f() und damit auch y haben, so ist die Situation in R deutlich komplizierter als in streng typisierten Programmiersprachen ÔÇô manche werden die Situation auch f├╝r deutlich einfacher halten.

In einer streng typisierten Programmiersprache muss der R├╝ckgabewert einer Funktion einen eindeutigen Datentyp haben (lediglich wenn Vererbung ins Spiel kommt, sind gewisse Mehrdeutigkeiten zul├Ąssig). Und dieser Datentyp wird an die Variable y ├╝bergeben. Anders gesagt: wenn die Variable y deklariert wird, muss ihr schon der Datentyp verliehen werden, der von der Funktion f() zur├╝ckgegeben wird. Andernfalls wird die Zuweisung y <- f(x) einen Compiler-Fehler verursachen.

Dagegen werden in R Datentypen dynamisch erkannt. Dies bedeutet, dass f├╝r die Variable y kein Datentyp vereinbart werden muss (daher gibt es auch keine Deklarationen in R); die Variable y nimmt einfach denjenigen Datentyp an, der bei der Berechnung von f(x) entsteht.

Eine Folge davon, dass die Datentypen dynamisch erkannt werden, ist, dass eine Funktion in R unterschiedliche R├╝ckgabewerte haben kann ÔÇô in einer streng typisierten Sprache muss bei der Deklaration der Funktion der Datentyp des R├╝ckgabewertes vereinbart werden und ist somit eindeutig. Ein einfaches Beispiel hierf├╝r ist die Funktion print(): sie gibt das Objekt zur├╝ck, das als Eingabewert ├╝bergeben wurde. Das folgende Skript zeigt dies f├╝r unterschiedliche Datentypen:

x <- 17

str(print(x))
# [1] 17
# num 17

v <- c('A', 'B', 'C')

str(print(v))
# [1] "A" "B" "C"
# chr [1:3] "A" "B" "C"

m <- matrix(data = (1:6), nrow = 2, byrow = TRUE)

str(print(m))
#     [,1] [,2] [,3]
# [1,]    1    2    3
# [2,]    4    5    6
# int [1:2, 1:3] 1 4 2 5 3 6

Man sieht an diesen Beispielen, dass eine Funktion unterschiedliche R├╝ckgabewerte besitzen kann ÔÇô abh├Ąngig davon, welcher Eingabewert ├╝bergeben wird.

Und weiter kann man an diesem Verhalten von R sofort eine Folgerung f├╝r selbstdefinierte Funktionen ableiten: Man sollte nur in Ausnahmef├Ąllen zulassen, dass eine Funktion R├╝ckgabewerte von verschiedenen Datentypen haben kann, da sonst Zuweisungen wie y <- f(x) schwer verst├Ąndlich sind. Denn soll sp├Ąter mit y weitergerechnet werden, m├Âchte man dessen Datentyp kennen (andernfalls k├Ânnen unter bestimmten Bedingungen Fehler in der Ausf├╝hrung des Skriptes auftreten).

Funktionen ohne R├╝ckgabewert

Es ist nat├╝rlich auch m├Âglich, Funktionen zu definieren, die keinen R├╝ckgabewert besitzen; oben wurden schon die Funktionen cat() und str() besprochen, die hier als Beispiele angef├╝hrt werden k├Ânnen.

Die Implementierung einer Funktion

Mit der Implementierung einer Funktion ist derjenige Quelltext gemeint, der ausgef├╝hrt wird, wenn eine Funktion aufgerufen wird. Dies wird im n├Ąchsten Kapitel ÔÇô bei den selbstdefinierten Funktionen ÔÇô ausf├╝hrlich besprochen; jetzt sollen nur einige allgemeine Hinweise gegeben werden.

Generische Funktionen

In den bisherigen Kapiteln wurde oft gesagt, dass eine Funktion "generisch implementiert" ist; damit ist gemeint, dass f├╝r die Funktion mehrere Implementierungen existieren und je nach Datentyp des ersten Eingabewertes wird die entsprechende Implementierung aufgerufen.

1. Beispiel: head(x, n)

Als Beispiel sei die Funktion head(x, n) aus dem Paket utils genannt: Hier ist x ein R-Objekt und n eine ganze Zahl.

Ist x ein Vektor, so ist der R├╝ckgabewert von head(x, n) der Vektor, der aus den ersten n Komponenten von x besteht. Ist dagegen x eine Matrix, so ist der R├╝ckgabewert von head(x, n) die Matrix, die aus den ersten n Zeilen von x besteht.

2. Beispiel: print(x)

Ist der Eingabewert zum Beispiel ein Vektor, werden dessen Komponenten in einer Zeile ausgegeben. Falls Namen der Komponenten gesetzt sind (Attribut names) erfolgt eine tabellarische Ausgabe, die die Zuordnung der Namen zu den Komponenten erlaubt.

Bei einer Liste als Eingabewert, werden die Komponenten mit ihren Namen ausgegeben (zwischen den Komponenten befindet sich je eine Leerzeile). F├╝r jede Komponenten wiederum wird die Funktion print() aufgerufen.

ÔÖŽ ÔÖŽ ÔÖŽ ÔÖŽ

Generische Implementierung kann sogar eine weitere Bedeutung haben: F├╝r unterschiedliche Datentypen des ersten Eingabewertes kann es sogar Realisierungen mit unterschiedlichen Argument-Listen geben.

So ist zum Beispiel in der Dokumentation f├╝r die Funktion print() unter Usage zu lesen:

print(x, ...)

## S3 method for class 'factor'
print(x, quote = FALSE, max.levels = NULL,
        width = getOption("width"), ...)
        
## S3 method for class 'table'
print(x, digits = getOption("digits"), quote = FALSE,
        na.print = "", zero.print = "0", justify = "none", ...)
        
## S3 method for class 'function'
print(x, useSource = TRUE, ...)

Dies kann hier noch nicht genauer erkl├Ąrt werden, da sich hinter dem Ausdruck S3 method for class ... verbirgt, wie man in R objekt-orientierte Programmierung realisiert. Es soll nur noch folgender Hinweis gegeben werden: Mit Hilfe der Funktion methods() aus dem Paket utils

methods(generic.function, class)

kann man sich zu einer gegebenen Funktion generic.function anzeigen lassen, welche Implementierungen existieren. Oder man kann sich zu einer gegebenen Klasse class anzeigen lassen, welche Funktionen f├╝r diese Klasse existieren (Funktionen, die einer Klasse zugeordnet sind, werden meist als Methoden bezeichnet).

So wird zum Beispiel f├╝r matrix angegeben:

methods(class="matrix")
# [1] anyDuplicated as.data.frame as.raster     boxplot       coerce        determinant   duplicated    edit          head          initialize    isSymmetric  
# [12] Math          Math2         Ops           relist        subset        summary       tail          unique       
# see '?methods' for accessing help and source code

Einige dieser Funktionen wurden in den Kapiteln ├╝ber Matrizen ausf├╝hrlich vorgestellt.

Aufgabe:

Testen Sie den Einsatz von methods() f├╝r einige Funktionen, die Sie schon ├Âfters verwendet haben.

Der Quelltext einer Funktion

Oben wurden die Implementierungen der Funktion print() gezeigt; die letzte dieser Funktionen bezieht sich auf die Klasse function. Es ist naheliegend zu fragen, welche Ausgabe f├╝r eine Funktion zu erwarten ist; das Argument useSource deutet zudem darauf hin, dass man hier auf den Quelltext einer Funktion zugreifen kann.

F├╝r das Beispiel der Funktion mean() soll dies nun demonstriert werden. Dazu wird zun├Ąchst die print()-Funktion f├╝r mean() aufgerufen:

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

Die Ausgabe ist wenig hilfreich und eher enttauschend, da der Quelltext nicht gezeigt wird: Zeile 2 besagt lediglich, dass die Funktion mean() neben x das dot-dot-dot-Argument besitzt. Zeile 5 besagt, dass sie sich im Paket base befindet.

Aufschlussreicher ist es, die Funktion methods() auf mean() anzuwenden ÔÇô oben wurde gezeigt, dass sich damit die unterschiedlichen Implementierungen einer Funktion anzeigen lassen:

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

L├Ąsst man sich jetzt die Funktion mean.default() ausgeben (Zeile 1), erh├Ąlt man tats├Ąchlich ihren Quelltext:

print(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: 0x000000001a29e320>
#   <environment: namespace:base>

Es soll jetzt nicht versucht werden, diesen Quelltext nachzuvollziehen