Listen in R: der Datentyp list

Listen sind in R die grundlegende rekursive Struktur: anders als bei einem Vektor, bei dem alle Komponenten einen identischen Datentyp besitzen müssen, ist für die Komponenten einer Liste ein beliebiger Datentyp zulässig - sie können sogar selber wieder Listen sein. Vorgestellt werden Funktionen zum Erzeugen von Listen, der Zugriff auf die Komponenten einer Liste, Diagnose-Funktionen für Listen und das Attribut names.

Einordnung des Artikels

IIm nächsten Kapitel werden dann zahlreiche Operationen und Funktionen vorgestellt, mit denen Listen weiterverarbeitet werden können.

Einführung

In der Übersicht über Datentypen in R wurden Listen als das Paradebeispiel für eine rekursive Struktur vorgestellt. Um sich zu vergegenwärtigen, was man unter einer rekursiven Struktur versteht, ist es vielleicht besser nochmals die atomaren Strukturen zu charakterisieren, die bisher besprochen wurden, nämlich Vektor, Matrix und Feld:

  • Bei einem Vektor werden beliebig viele Elemente (oder Komponenten) zusammengefügt und indiziert angeordnet. Über den Index kann man auf die Elemente zugreifen. Der Datentyp Vektor setzt aber voraus, dass alle Elemente des Vektors einen identischen Datentyp besitzen.
  • Bei einer Matrix werden die Elemente eines Vektors in Zeilen und Spalten angeordnet. Dies geschieht durch das Attribut dim, das die zweidimensionale Anordnung der Elemente regelt. Wie bei einem Vektor müssen alle Elemente den identischen Datentyp besitzen.
  • Bei einem Feld kommt lediglich hinzu, dass die Anordnung der Elemente in beliebig vielen Dimensionen erfolgen kann.

Die zunehmende Komplexität der Datentypen Vektor, Matrix und Feld entsteht also nur durch die höherdimensionale Anordnung der Komponenten (die durch den Dimensionsvektor definiert wird).

Mit rekursiver Struktur soll ausgedrückt werden, dass die Forderung nach einem identischen Datentyp aller Elemente aufgegeben wird; die Indizierung der Komponenten ist wie bei einem Vektor gegeben. In diesem Sinne ist eine Liste eine Verallgemeinerung eines Vektors. Und wie bei einem Vektor gibt es zwei Möglichkeiten, wie man auf die Komponenten zugreifen kann:

  • entweder über den Index,
  • oder über die Namen der Komponenten – sofern die Namen zuvor vergeben wurden.

Das einfache Beispiel des Dateisystems kann eine rekursive Struktur veranschaulichen: Jeder Ordner kann sowohl Unter-Ordner als auch Dateien enthalten. Ein Unter-Ordner wiederum kann – wie ein Ordner – Unter-Ordner und Dateien enthalten. Und wäre ein Dateisystem eine atomare Struktur, so könnte ein Ordner lediglich Dateien enthalten, aber keine Unterordner.

Viele Funktionen in R sind generisch implementiert, das heißt dass sie mit unterschiedlichen Objekten aufgerufen werden können und dass intern die an den jeweiligen Datentyp angepasste Version aufgerufen wird. Entsprechend gibt es viele Funktionen für Listen, die bereits bei Vektoren besprochen wurden und bei denen es nicht schwer fällt zu erraten (oder in einem kleinen Skript zu testen), wie sie für Listen arbeiten. Im Folgenden werden daher einige Funktionen für Listen nur sehr oberflächlich besprochen. Ausführlich besprochen werden diejenigen Funktionen, die bei Vektoren noch nicht vorgekommen sind und insbesondere Funktionen, die die rekursiven Struktur einer Liste nutzen.

Erzeugen von Listen

Übersicht

Zum Erzeugen von Listen gibt es drei Möglichkeiten, die in folgender Tabelle kurz beschrieben sind:

Funktion Beschreibung
vector(mode = "list", length) Erzeugt eine leere Liste gegebener Länge
list(...) Das Argument ... steht dafür, dass beliebig viele Objekte zu einer Liste zusammengefasst werden. Die Datentypen dieser Objekte sind frei wählbar.
as.list(x) Das Objekt x wird in eine Liste umgewandelt (coercion).

Die ersten beiden Funktionen werden im Folgenden ausführlich besprochen; für die dritte Möglichkeit as.list(x) wurden bisher keine geeigneten Datentypen für x behandelt, die man in eine Liste verwandeln könnte (Faktoren und Funktionen).

Unten folgt zwar ein Abschnitt über den Zugriff auf die Komponenten einer Liste, in dem die möglichen Zugriffsarten systematisch erklärt werden. Allerdings werden sie schon jetzt auftreten, wenn Listen erzeugt, ihre Komponenten gesetzt und Listen ausgegeben werden.

Die Funktion vector()

Bei Vektoren wurde bereits eine Funktion vorgestellt, die den Speicherplatz für einen Vektor zu gegebenem Modus und gegebener Länge reserviert und die Komponenten des Vektors mit default-Werten belegt (siehe Die Funktion vector() in Vektoren in R: der Datentyp vector). Ein Beispiel ist der Vektor v von Zahlen mit fünf Komponenten:

v <- vector(mode = "numeric", length = 5)

v
# [1] 0 0 0 0 0

Die Komponenten des numerischen Vektors werden jeweils mit dem default-Wert 0 belegt.

Die Funktion vector(mode, length) besitzt für das Argument mode den default-Wert "logical" und für length den Wert 0:

# Die Funktion vector() mit default-Werten:

vector(mode = "logical", length = 0)

Für den default-Wert "logical" und andere atomare Typen (also "integer", "numeric" und "character") wurde die Funktion vector() bereits bei den Vektoren besprochen.

Die Funktion vector() kann auch verwendet werden, um eine Liste einer bestimmten Länge zu erzeugen; die Anzahl der Komponenten ist dann festgelegt, die Komponenten sind alle gleich NULL. Dazu setzt man mode = "list" . (Die Spitzfindigkeit, in welchem Sinne diese Liste gleichzeitig ein Vektor ist, wird später bei den Diagnose-Funktionen für Listen gezeigt.)

Die mit vector(mode = "list", length) erzeugte Liste hat zwar eine Länge, aber noch keine Komponenten; diese werden nachträglich gesetzt. Das folgende Beispiel zeigt dies:

l <- vector(mode = "list", length = 3)

is.list(l)			# TRUE
length(l)			# 3

l
# [[1]]
# NULL
# 
# [[2]]
# NULL
# 
# [[3]]
# NULL

# Setzen der Komponenten:

l[[1]] <- "x"
l[[2]] <- 5
l[[3]] <- c(1, 0, 1)

l
# [[1]]
# [1] "x"
# 
# [[2]]
# [1] 5
# 
# [[3]]
# [1] 1 0 1

Zur Erklärung:

Zeile 1: Die Liste l wird erzeugt; sie besitzt 3 Komponenten, die allerdings noch nicht gesetzt werden.

Zeile 3 und 4: Die is-dot-Funktion is.list() liefert für l das Ergebnis TRUE. Die Länge der Liste ist gleich 3.

Zeile 6 bis 14: Die Ausgabe von l ist jetzt noch unverständlich – aber man kann leicht erraten, welche Bedeutung die doppelten Klammern haben: Da eine Liste von einem Vektor unterschieden werden soll, erfolgt der Zugriff auf die Komponenten durch die doppelten eckigen Klammern. Also steht zum Beispiel l[[1]] für die erste Komponente der Liste l. Und wie zu erwarten, ist sie – wie die anderen Komponenten – gleich NULL.

Zeile 18 bis 20: Die drei Komponenten der Liste l werden gesetzt; dazu werden – um eine typische Liste zu erzeugen – drei verschiedene Datentypen verwendet. Wie in der Ausgabe wird mit den doppelten eckigen Klammern auf die Komponenten zugegriffen.

Zeile 22 bis 30: Die Ausgabe der Liste l zeigt jetzt die drei Komponenten, die zuvor gesetzt wurden.

Die Funktion list() und der Zugriff mittels $

Bei Vektoren wurde bereits erklärt, dass der Zugriff auf die Komponenten entweder über den Index oder über die Namen erfolgen kann. Das folgende Beispiel demonstriert Letzteres:

v <- c(x1 = 1, x2 = 0, x3 = 1)

v
# x1 x2 x3 
# 1  0  1 

v[["x1"]]
# [1] 1

# Fehler:
v[[x1]]
# Error: object 'x1' not found

names(v)
# [1] "x1" "x2" "x3"

attributes(v)
# $names
# [1] "x1" "x2" "x3"

Zeile 1: Die Funktion c() wird in der Version verwendet, die es erlaubt den Komponenten des Vektors Namen zu geben.

Zeile 3: Die Ausgabe des Vektors erfolgt jetzt in tabellarischer Form mit Namen und Werten.

Zeile 7: Der Zugriff auf die Komponenten über den Namen. Zu beachten sind dabei zwei Dinge: es müssen die doppelten eckigen Klammern verwendet werden und der Name muss als Zeichenkette übergeben werden (also mit Anführungsstrichen); fehlen die Anführungsstriche, erhält man eine Fehlermeldung wie in Zeile 11 und 12. Beim Setzen der Namen in c() sind die Anführungsstriche nicht nötig.

Zeile 14: Das Attribut names ist gesetzt und bildet einen "character"-Vektor.

Zeile 17: Außer names ist kein weiteres Attribut gesetzt. Nebenbei: wie gleich verständlich wird, zeigt die Ausgabe der Attribute eine Liste.

Ganz ähnlich wie die Namen bei Vektoren eingesetzt werden können, werden sie auch bei Listen eingesetzt. Das folgende Beispiel zeigt den typischen Fall, wie eine Liste erst erzeugt wird und wie dann auf die Komponenten der Listen mit Hilfe ihrer Namen (oder besser Komponenten-Namen) zugegriffen wird; anders als bei Vektoren wird für den Zugriff über die Namen der Operator $ verwendet:

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

# Ausgabe:
book_R
# $title
# [1] "Introduction to R"
# 
# $author
# [1] "R-expert"
# 
# $year
# [1] 2018

# Zugriff mit Namen:
book_R$title			# "Introduction to R"
book_R$author			# "R-expert"
book_R$year				# 2018

book_R[["title"]]			# "Introduction to R"

# Fehler:
book_R[[title]]
# Error in book_R[[title]] : invalid subscript type 'closure'

# Zugriff mit Index:
book_R[[1]]				# "Introduction to R"

Zur Erklärung:

Zeile 1: Mit Hilfe der Funktion list() wird eine Liste erzeugt und unter einem (frei wählbaren) Namen abgespeichert. Die Komponenten sind durch Kommas getrennt und besitzen jeweils die Form name = value . Man beachte, dass hier das Gleichheitszeichen = und nicht der Zuweisungs-Operator <- verwendet werden muss.

Zeile 4 bis 12: Die Ausgabe der Liste zeigt zuerst den Namen der Komponente (erkennbar am Zeichen $ , Zeile 5, 8 und 11) und den Wert der Komponente (Zeile 6, 9 und 12). Zwischen den Komponenten wird eine Leerzeile eingefügt (Zeile 7 und 10).

Zeile 15 bis 17: Möchte man nun gezielt auf einen Eintrag einer Liste zugreifen, geschieht dies mit dem Zugriffs-Operator $ : Über den Namen der Komponente wird dessen Wert aus dem Listen-Objekt herausgeholt.

Zeile 19: Gleichwertig ist der Zugriff über den Namen (als Zeichenkette) in doppelten eckigen Klammern, der bei Vektoren beschrieben wurde. Der Zugriff mit dem Operator $ ist aber vorzuziehen, da man die Anführungsstriche weglassen kann.

Zeile 22: Weglassen der Anführungsstriche führt tatsächlich zu einem Fehler.

Zeile 26: Man kann sich die Einträge der Liste auch durchnumeriert denken (wie in einem Vektor). Anstelle des Operators $ erfolgt der Zugriff auf die einzelnen Einträge mit Hilfe des Index über die doppelte eckige Klammer wie bei book_R[[1]] . Der Zugriff über den Komponenten-Namen ist natürlich vorzuziehen, da dieser bei vielen und großen Listen zu leichter lesbaren Quelltexten führt (daher auch bei der Wahl der Komponenten-Namen auf treffende Bezeichnungen achten!). Warum der Zugriff mit doppelten eckigen Klammern und nicht mit einfachen eckigen Klammern (wie bei Vektoren) erfolgt, wird sofort klar werden.

Die Funktion list() kann auch ohne Argument verwendet werden; es wird dann eine Liste der Länge null erzeugt, die selbstverständlich noch keine Komponenten-Namen und Werte besitzt. Diese können dann nachträglich gesetzt werden, wie das folgende Beispiel zeigt:

book_R <- list()
length(book_R)          # 0

book_R[["title"]] <- "Introduction to R"

length(book_R)          # 1
book_R$title            # "Introduction to R"

In Zeile 1 wird eine leere Liste erzeugt; ihre Länge wird mit length() ausgegeben (Zeile 2). Anschließend wird der Komponenten-Name title gesetzt und mit dem Wert "Introduction to R" belegt (Zeile 4). Die folgenden Ausgaben zeigen die Länge der Liste und den Wert der Komponente title (Zeile 6 und 7).

Im Allgemeinen besitzt die Funktion list() die Form:

list(...)

Dabei bedeuten ... , dass der Funktion beliebig viele R-Objekte übergeben werden können, die dann zu einer Liste zusammengefasst werden. Die Namen müssen dabei nicht gesetzt werden; sie sind nur optional. Werden sie nicht gesetzt, fehlt die Möglichkeit des Zugriffs auf die Komponenten über die Namen; es ist nur der Zugriff über den Index möglich:

book_R <- list("Introduction to R", "R-expert", 2018)

book_R[[1]]
# [1] "Introduction to R"

book_R$title
# NULL

Der Zugriff auf die Werte der Komponenten erfolgt jetzt nur noch über den Index mit Hilfe der doppelten eckigen Klammern (Zeile 3). Der Zugriff mit dem Komponenten-Namen ist jetzt nicht mehr möglich (Zeile 6).

Es sollte aber auch klar sein, dass die Definition einer Liste ohne Komponenten-Namen zu schwerer verständlichen Quelltexten führt.

Zugriff auf die Komponenten einer Liste

Wie man auf die Komponenten einer Liste zugreift, wurde eigentlich im vorhergehenden Abschnitt an verschiedenen Stellen schon gesagt; die verschiedenen Zugriffsmöglichkeiten sollen hier systematisch dargestellt werden.

Der Zugriff über den Index mit doppelten eckigen Klammern

Da die Komponenten einer Liste wie die eines Vektors indiziert sind, ist dieser Zugriff immer möglich. Er hat aber den Nachteil, dass er zu schwer lesbaren Quelltexten führt, da man als Leser nur selten parat hat, welcher Index zu welcher Komponente gehört; der Zugriff über die Namen der Komponenten ist meist besser verständlich.

Im folgenden Skript wird eine Liste ohne Namen definiert und auf die Komponenten mittels [[.]] zugegriffen:

book_R <- list("Introduction to R", "R-expert", 2018)

book_R[[1]]
# 1] "Introduction to R"
book_R[[2]]
# "R-expert"
book_R[[3]]
# [1] 2018
book_R[[4]]
# Error in book_R[[4]] : subscript out of bounds

Bei einem Vektor ist es möglich, auf mehrere Komponenten gleichzeitig zuzugreifen und einen Teilvektor zu bilden (Index-Vektor). Beim Zugriff auf die Komponenten einer Liste mittels [[.]] ist dies nicht möglich. Es sollte auch klar sein warum: da die Komponenten unterschiedliche Datentypen haben, kann man sie nicht zu einem Teilvektor zusammenfassen. Dass man sie zu einer Teil-Liste zusammenfassen kann, wird weiter unten gezeigt.

Das folgende Skript zeigt, wie man einen Teilvektor eines Vektors bilden kann, und dass die entsprechende Operation bei Listen nicht möglich ist:

v <- c(1, 0, 1)

# Auswahl der 2. und 3. Komponente:
v.23 <- v[c(2, 3)]
v.23
# [1] 0 1

book_R <- list("Introduction to R", "R-expert", 2018)

# Auswahl der 1. und 2. Komponente:
book_R[[c(1,2)]]
# Error in book_R[[c(1, 2)]] : subscript out of bounds

Der Zugriff über den Namen mit $

Sind bei einer Liste Namen der Komponenten gesetzt, kann man ihn verwenden, um auf die Komponenten zuzugreifen; man benötigt dazu den Zugriffs-Operator $ (Zeile 4 bis 6):

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

# Zugriff mit Namen:
book_R$title            # "Introduction to R"
book_R$author           # "R-expert"
book_R$year             # 2018

book_R[["title"]]           # "Introduction to R"
book_R[["author"]]          # "R-expert"
book_R[["year"]]            # 2018

book_R[[c("title", "year")]]
# Error in book_R[[c("title", "year")]] : subscript out of bounds

Alternativ kann auch der Name der Komponente in die doppelten eckigen Klammern geschrieben werden (Zeile 8 bis 10); dabei ist aber zu beachten, dass die Namen als Zeichenketten mit Anführungsstrichen geschrieben werden (beim Zugriff mit $ ist dies nicht nötig).

Wie beim Zugriff mit [[.]] ist es hier nicht möglich, mit Index-Vektoren zu arbeiten (siehe Zeile 12 und 13).

Abkürzung der Komponenten-Namen

Beim Zugriff auf die Werte von Komponenten mittels ihres Namens kann der Name auch abgekürzt werde – sofern er noch eindeutig ist. Im folgenden Beispiel reicht es als Komponenten-Namen t anzugeben, da es keine weitere Komponente mit t als Anfangsbuchstabe gibt:

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

book_R$t
# [1] "Introduction to R"

Im folgenden Beispiel ist der Zugriff mit t nicht mehr eindeutig und liefert NULL (Zeile 3 und 4:

book_R <- list(title = "Introduction to R", author = "R-expert", year = 2018, tags = c("R", "Programming", "Statistics"))

book_R$t
# NULL

book_R$tags
# [1] "R"           "Programming" "Statistics"

book_R$ta
# [1] "R"           "Programming" "Statistics"

Wird dagegen der volle Name tags angegeben oder auch nur die eindeutige Abkürzung ta, erhält man die Werte der tags des Buches (Zeile 6,7, 9 und 10).

Erzeugen von Teil-Listen mit einfachen eckigen Klammern

Der Operator [.] ist vom Operator [[.]] zu unterscheiden; man muss sich dazu nur vergegenwärtigen, dass eine Liste üblicherweisse aus Paaren der Art name = value besteht:

  • Mit [.] wird auf ein Paar name = value zugegriffen und diejenige Teil-Liste erzeugt, die genau dieses Paar enthält.
  • Mit [[.]] wird auf den Wert (value) der Komponente zugegriffen und ein Objekt mit dessen Datentyp erzeugt.

Und da man natürlich nicht nur Teil-Listen der Länge 1 erzeugen kann, ist es beim Operator [.] möglich, mit Index-Vektoren zu arbeiten und beliebig lange Teil-Listen zu erzeugen.

Das folgende Skript zeigt den Einsatz von [.] und die Eigenschaften der dabei entstehenden Objekte:

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

# Teil-Liste der Länge 1:
book_R[1]
# $title
# [1] "Introduction to R"

is.list(book_R[1])
# [1] TRUE

# zum Vergleich:
is.list(book_R[[1]])
# [1] FALSE

# Teil-Liste der Länge 2:
book_R[c(1, 3)]
# $title
# [1] "Introduction to R"
# 
# $year
# [1] 2018

Listen als rekursive Struktur

Bei den bisher verwendeten Beispielen für Listen wurden stets für die Komponenten der Liste atomare Strukturen eingesetzt – meist waren es Zahlen oder Zeichenketten. In diesem Unterabschnitt sollen Beispiele für Listen gezeigt werden, in denen die Komponenten der Liste entweder Vektoren oder wiederum Listen sind. Dabei wird auch gezeigt, wie auf die Elemente der eingebetteten Objekte zugegriffen wird.

Vektor als Komponente einer Liste

Oben wurde schon mehrmals eine Liste gebildet, um die Eigenschaften eines Buch in einem R-Objekt zusammenzufassen. Dieses Beispiel wird nun erweitert: ein Element der Liste (nämlich author) soll ein Vektor sein.

book_OOP <- list(title = "Object Oriented Programming", author = c("R-expert", "C++-expert"), year = 2016)

book_OOP$author
# [1] "R-expert"   "C++-expert"

length(book_OOP)
# [1] 3

length(book_OOP$author)
# [1] 2

book_OOP$author[2]
# [1] "C++-expert"

book_OOP[[2]][2]
# [1] "C++-expert"

Lässt man sich jetzt den Autor ausgeben, erhält man einen Vektor mit zwei Einträgen (Zeile 3 und 4).

Weiter erkennt man:

  • Wird die Länge der Liste mit length(book_OOP) abgefragt (Zeile 6), erhält man die Anzahl der Komponenten der Liste auf der höchsten Ebene; die eingebetteten Elemente werden nicht gezählt.
  • Dagegen liefert length(book_OOP$author) die Anzahl der Autoren, also die Länge des eingebetteten Vektors (Zeile 9).
  • Der Zugriff auf die Komponenten des eingebetteten Vektors erfolgt dann wie üblich mit dem Operator [.] : siehe Zeile 12, wo der zweite Autor ausgegeben wird.
  • Der letzte Zugriff kann auch wie in Zeile 15 erfolgen: Mit book_OOP[[2]] wird auf die zweite Komponente der Liste zugegriffen (author) und mit [2] wird aus diesem Vektor die zweite Komponente ausgegeben.

Liste als Komponente einer Liste

Damit endlich zu einer echt rekursiven Struktur: Im folgenden Beispiel wird eine Liste von Listen gebildet. Dazu werden zuerst mehrere Bücher als Listen definiert; diese Bücher werden schließlich zu einer Buch-Liste zusammengefasst:

# Bücher:
book_R <- list(title = "Introduction to R", author = "R-expert", year = 2018)
book_Cpp <- list(title = "Introduction to C++", author = "C++-expert", year = 2017)
book_OOP <- list(title = "Object Oriented Programming", author = c("R-expert", "C++-expert"), year = 2016)

# Buch-Liste:
books <- list(title = "Programming", book1 = book_R, book2 = book_Cpp, book3 = book_OOP)

length(books)
# [1] 4

books$book1$author
# [1] "R-expert"

books$book3$author[2]
# [1] "C++-expert"

Die Liste books , die in Zeile 7 gebildet wird, besitzt 4 Komponenten:

  • title als Zeichenkette und
  • 3 Listen, die für die Bücher stehen.

Die Ausgabe der Länge von books ist daher gleich 4 (Zeile 9).

Zeile 12 und 15 zeigen den Zugriff auf die Komponenten der eingebetteten Listen, die nach den Erklärungen oben verständlich sein sollten.

Die Funktionen is.atomic() und is.recursive()

Um festzustellen, ob ein R-Objekt x atomare oder rekursive Struktur hat, gibt es die beiden Funktionen

is.atomic(x)
is.recursive(x)

Die Funktionen testen dabei nicht, ob ein Objekt tatsächlich eine rekursive Struktur hat (wie eine Liste mit Listen als Komponenten), sondern nur ob eine potentiell rekursive Struktur vorliegt (also auch eine Liste mit atomaren Elementen gilt als rekursiv):

# atomare Struktur:
v <- c(1, 0, 1)

is.atomic(v)             # TRUE
is.recursive(v)          # FALSE

# potentiell rekursive Struktur:
l <- list(17, "x")

is.atomic(l)             # FALSE
is.recursive(l)          # TRUE

# echt rekursive Struktur:
# Bücher:
book_R <- list(title = "Introduction to R", author = "R-expert", year = 2018)
book_Cpp <- list(title = "Introduction to C++", author = "C++-expert", year = 2017)
book_OOP <- list(title = "Object Oriented Programming", author = c("R-expert", "C++-expert"), year = 2016)

# Buch-Liste:
books <- list(title = "Programming", book1 = book_R, book2 = book_Cpp, book3 = book_OOP)

is.atomic(books)             # FALSE
is.recursive(books)          # TRUE

Ausblick: Definition von Klassen mit Hilfe von Listen

Bisher wurden zwar keine sonderlich komplexen Beispiele von Listen gezeigt, aber es sollte folgendes klar sein: Listen bieten die Möglichkeit, beliebige R-Objekte zusammenzufassen und abzuspeichern.

Wer schon mit objekt-orientierter Programmierung vertraut ist, wird hier sofort die Möglichkeit erkennen, Klassen – also selbstdefinierte Datentypen – zu bilden. Da allerdings eine Klasse aus Datenelementen und Methoden besteht, ist auch klar, dass eine Liste nur die Zusammenfassung der Datenelemente zu einem neuen Datentyp erlaubt. Wie auch Methoden in den Datentyp integriert werden, kann hier noch nicht erklärt werden – dazu sind weitere Kenntnisse über Funktionen in R nötig.

Diagnose-Funktionen für Listen

Die Funktion length()

Wie oben schon gezeigt wurde, gibt die Funktion length(x) die Anzahl der Komponenten einer Liste x an – und zwar nur die Anzahl auf der höchsten Ebene. Wenn diese Komponenten selber aus mehreren Elementen bestehen, werden diese nicht gezählt. Das folgende Skript zeigt dies nochmal am Beispiel zweier Bücher, die als Listen definiert sind: einmal mit einem Autor, einmal mit einem Vektor von zwei Autoren.

# Buch mit einem Autor:
book_R <- list(title = "Introduction to R", author = "R-expert", year = 2018)

length(book_R)
# [1] 3

# Buch mit zwei Autoren:
book_OOP <- list(title = "Object Oriented Programming", author = c("R-expert", "C++-expert"), year = 2016)

length(book_OOP)
# [1] 3

Die Funktion print()

Auch die Funktion print() zur Ausgabe einer Liste wurde oben schon verwendet und kurz erklärt. Da inzwischen der Zugriffs-Operator $ für die Komponenten einer Liste erklärt wurde, ist die Ausgabe besser verständlich. Das folgende Skript zeigt ein typisches Beispiel:

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

# Ausgabe:
book_R
# $title
# [1] "Introduction to R"
# 
# $author
# [1] "R-expert"
# 
# $year
# [1] 2018

Vergegenwärtigt man sich, dass die Komponenten einer Liste name = value-Paare sind, erkennt man:

  • Es werden alle Komponenten aufgeführt.
  • Die Anzahl der Komponenten wird nicht ausdrücklich angegeben (man muss selber zählen oder die Funktion length() bemühen).
  • Für jede Komponente wird sowohl der Name (erkennbar an $ ) als auch der Wert angegeben (der Wert ist im Allgemeinen ein Vektor, daher beginnt die Ausgabe mit [1] , in diesem Beispiel haben alle Vektoren die Länge 1).
  • Zur besseren Erkennbarkeit der einzelnen Komponenten, steht zwischen den Komponenten eine Leerzeile.

Hat eine Liste einen echten Vektor als Komponente, so ändert sich nicht viel (siehe Zeile 8):

book_OOP <- list(title = "Object Oriented Programming", author = c("R-expert", "C++-expert"), year = 2016)

# Ausgabe:
# $title
# [1] "Object Oriented Programming"
# 
# $author
# [1] "R-expert"   "C++-expert"
# 
# $year
# [1] 2016

Interessant ist noch der Fall, dass eine Liste eine eingebettete Liste enthält:

# Bücher:
book_R <- list(title = "Introduction to R", author = "R-expert", year = 2018)
book_Cpp <- list(title = "Introduction to C++", author = "C++-expert", year = 2017)
book_OOP <- list(title = "Object Oriented Programming", author = c("R-expert", "C++-expert"), year = 2016)

# Buch-Liste:
books <- list(title = "Programming", book1 = book_R, book2 = book_Cpp, book3 = book_OOP)

# Ausgabe:
books
# $title
# [1] "Programming"
# 
# $book1
# $book1$title
# [1] "Introduction to R"
# 
# $book1$author
# [1] "R-expert"
# 
# $book1$year
# [1] 2018
# 
# 
# $book2
# $book2$title
# [1] "Introduction to C++"
# 
# $book2$author
# [1] "C++-expert"
# 
# $book2$year
# [1] 2017
# 
# 
# $book3
# $book3$title
# [1] "Object Oriented Programming"
# 
# $book3$author
# [1] "R-expert"   "C++-expert"
# 
# $book3$year
# [1] 2016

Die Liste books enthält 4 Komponenten, die erste ist atomar, die anderen drei sind wiederum Listen. Man erkennt:

  • Die eingebetteten Liste werden wie durch den Aufruf von print() ausgegeben (die Namen der eingebetteten Komponenten sind am doppelten $ erkennbar).
  • Folgen als Komponenten zwei Listen aufeinander, werden 2 Leerzeilen ausgegeben.

Das letzte Beispiel soll noch eine Liste zeigen, in der die Komponenten-Namen nicht gesetzt sind:

book_R <- list("Introduction to R", "R-expert", 2018)

Ausgabe:
book_R
# [[1]]
# [1] "Introduction to R"
# 
# [[2]]
# [1] "R-expert"
# 
# [[3]]
# [1] 2018

Es wird nicht NULL für die fehlenden Namen angegeben, sondern die Komponenten werden durch ihren Index (in doppelten eckigen Klammern [[.]] ) gekennzeichnet; die Angabe der Werte ist wie üblich.

Die Funktion str()

Wie bei Vektoren lässt sich die Struktur einer Liste mit Hilfe der Funktion str() anzeigen. Es werden in sehr übersichtlicher Form die wichtigsten Eigenschaften einer Liste angezeigt. Da eine Liste als rekursive Struktur sehr komplex sein kann, reicht jetzt eine Zeile für die Ausgabe nicht mehr aus.

Es folgt wieder das Beispiel eines Buches, das durch eine Liste beschrieben wird; die letzte Komponente ist hier ein "character"-Vektor:

book_R <- list(title = "Introduction to R", author = "R-expert", year = 2018, tags = c("R", "Programming", "Statistics"))

str(book_R)
# List of 4
#  $ title : chr "Introduction to R"
#  $ author: chr "R-expert"
#  $ year  : num 2018
#  $ tags  : chr [1:3] "R" "Programming" "Statistics"

Man erkennt:

  • den Datentyp des Objektes (Zeile 4),
  • die Anzahl der Komponenten (Zeile 4),
  • die Namen der Komponenten (erkennbar wieder an $ ),
  • die Datentypen der Werte der Komponenten,
  • falls eine Komponente ein Vektor ist, die Menge der Indizes (Zeile 8),
  • die Werte der Komponenten.

Für den Fall, dasss eine Liste eingebettete Listen enthält, lautet die Ausgabe von str():

# Bücher:
book_R <- list(title = "Introduction to R", author = "R-expert", year = 2018)
book_Cpp <- list(title = "Introduction to C++", author = "C++-expert", year = 2017)
book_OOP <- list(title = "Object Oriented Programming", author = c("R-expert", "C++-expert"), year = 2016)

# Buch-Liste:
books <- list(title = "Programming", book1 = book_R, book2 = book_Cpp, book3 = book_OOP)

str(books)
# List of 4
# $ title: chr "Programming"
# $ book1:List of 3
# ..$ title : chr "Introduction to R"
# ..$ author: chr "R-expert"
# ..$ year  : num 2018
# $ book2:List of 3
# ..$ title : chr "Introduction to C++"
# ..$ author: chr "C++-expert"
# ..$ year  : num 2017
# $ book3:List of 3
# ..$ title : chr "Object Oriented Programming"
# ..$ author: chr [1:2] "R-expert" "C++-expert"
# ..$ year  : num 2016

Wie bei print() wird str() rekursiv aufgerufen, das heißt auch für die eingebetteten Listen wird str() angezeigt.

Die Funktion mode()

Bei Vektoren gibt die Funktion mode() den Modus der Komponenten an. Da eine Liste Elemente von beliebigem Datentyp zusammenfassen kann, ist es nicht möglich, einen eindeutigen Modus der Elemente anzugeben. Stattdessen wird als Modus "list" angegeben (siehe Zeile 7 unten) – auch wenn alle Elemente identischen Modus besitzen.

Im folgenden Beispiel wird eine Liste erzeugt, die so konstruiert ist, dass eigentlich ein Vektor der geeignete Datentyp ist; aber hier kann die Funktion mode() besser erklärt werden:

l <- vector(mode = "list", length = 3)

l[[1]] = 5
l[[2]] = 17
l[[3]] = 0

mode(l)
# [1] "list"

is.vector(l)
# [1] TRUE
is.list(l)
# [1] TRUE

Die Funktion mode() kann daher sinnvoll sein, wenn man ein Objekt untersuchen muss, bei dem nicht klar ist, ob es ein Vektor oder eine Liste ist. Die Funktionen is.vector() und is.list() sind dazu zwar auch geeignet, man muss aber eine gewisse Spitzfindigkeit beachten. So wie diese beiden Funktionen oben eingesetzt werden (Zeile 10 und 12), helfen sie nicht um zwischen einer Liste und einem Vektor zu unterscheiden.

Die Funktionen is.vector() und is.list()

Um die Funktionen is.vector() und is.list() besser zu verstehen, muss man sich vergegenwärtigen, dass eine Liste eine Verallgemeinerung eines Vektors ist: Wie bei einem Vektor kann man auf die Elemente einer Liste über den Index zugreifen. Allgemeiner ist die Liste dadurch, dass die Elemente unterschiedlichen Datentyp besitzen können.

Am Beispiel oben war schon zu sehen, dass für eine Liste l sowohl die Funktion is.vector(l) als auch is.list(l) den Wert TRUE liefern. Dies suggeriert, dass diese beiden Funktionen eher nicht eingesetzt werden sollten, um zwischen Vektor und Liste zu unterscheiden. Dies ist aber falsch und die Erklärung wurde zum Teil schon oben damit gegeben, dass eine Liste eigentlich ein Vektor von Modus "list" ist.

Wenn man jetzt noch weiß, dass de Funktion is.vector() im Allgemeinen zwei Eingabewerte besitzt – nämlich das zu untersuchende Objekt x und den Modus mode -, dann ist sofort klar, wie man zwischen Vektor und Liste unterscheiden kann. Das folgende Skript zeigt dies:

# Liste mit den Komponenten 5, 17, 0:
l <- vector(mode = "list", length = 3)

l[[1]] = 5
l[[2]] = 17
l[[3]] = 0

# Vektor mit den Komponenten 5, 17, 0
v <- c(5, 17, 0)

# Untersuchen von l und v:

is.vector(x = l, mode = "list")
# [1] TRUE
is.vector(x = l, mode = "numeric")
# [1] FALSE
is.vector(x = v, mode = "list")
# [1] FALSE
is.vector(x = v, mode = "numeric")
# [1] TRUE

Man sollte die Funktion is.vector() besser so lesen: Sie dient dazu zu testen, ob ein Objekt x einen bestimmten Modus mode besitzt:

is.vector(x, mode = "any")

Wird der Modus nicht gesetzt, wird als default-Wert "any" verwendet; dies bedeutet, dass geprüft wird, ob ein beliebiger atomarer Modus vorliegt.

Attribute einer Liste

Wie in den Beispielen immer wieder gezeigt wurde, ist es sinnvoll bei Listen den Komponenten Namen zu geben – der Zugriff über die Namen ist im Quelltext meist leichter verständlicha als über den Index. Angezeigt werden können die Namen mit Hilfe der Funktion names(). Zudem sollte man wissen, dass das Attribut names zu den speziellen Attributen gehört, die nur so eingesetzt werden sollen, wie sie in der Dokumentation beschrieben werden.

In den folgenden Beispielen wird gezeigt, welche Werte die Funktionen names() und attributes() bei Listen typischerweise anzeigen. Zuerst wird eine Liste untersucht, in der die Namen ausdrücklich gesetzt wurden:

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

names(book_R)
# [1] "title"  "author" "year"  

attributes(book_R)
# $names
# [1] "title"  "author" "year" 

str(attributes(book_R))
# List of 1
# $ names: chr [1:3] "title" "author" "year"

Man erkennt an diesem Beispiel:

  • Die Namen bilden einen "character"-Vektor (Zeile 4).
  • Die Reihenfolge der Namen stimmt mit der Reihenfolge überein, wie die Namen in list() in Zeile 1 gesetzt wurden.
  • Es ist nur das Attribut names gesetzt.
  • Die Ausgabe der Attribute zeigt die typische Ausgabe einer Liste.
  • Diese Liste besitzt eine Komponente mit dem Namen names und als Wert den "character"-Vektor aus Zeile 4.
  • Die Ausgabe der Struktur von attributes(book_R) (Zeile 10) bestätigt, dass es sich um eine Liste handelt.

Falls in der Funktion list() die Namen nicht gesetzt werden, ist das Attribut names gleich NULL:

book_R <- list("Introduction to R", "R-expert", 2018)

names(book_R)
# NULL

attributes(book_R)
# NULL

Zuletzt soll noch eine Liste von Listen untersucht werden:

# Bücher:
book_R <- list(title = "Introduction to R", author = "R-expert", year = 2018)
book_Cpp <- list(title = "Introduction to C++", author = "C++-expert", year = 2017)
book_OOP <- list(title = "Object Oriented Programming", author = c("R-expert", "C++-expert"), year = 2016)

# Buch-Liste:
books <- list(title = "Programming", book1 = book_R, book2 = book_Cpp, book3 = book_OOP)

names(books)
# [1] "title" "book1" "book2" "book3"

attributes(books)
# $names
# [1] "title" "book1" "book2" "book3"

Man erkennt, dass nur die Attribute der Komponenten der höchsten Ebene angezeigt werden (und nicht die der eingebetteten Listen).

Die Funktion summary()

Bei Vektoren hat die Funktion summary(object) eine kleine statistische Auswertung des Vektors object geliefert. Für Listen lässt sich das nicht implementieren, da die Komponenten einer Liste unterschiedliche Datentypen haben können. Wie das folgende Skript zeigt, liefert die Funktion summary() nur Informationen über Name, Länge und Datentypen der Komponenten; man erhält keine Information über die Werte der Komponenten. Die Klasse einer Komponente ist jetzt noch nicht verständlich (und wird im folgenden Beispiel nicht verwendet). Die Information wird tabellarisch ausgegeben.

# Bücher:
book_R <- list(title = "Introduction to R", author = "R-expert", year = 2018)
book_Cpp <- list(title = "Introduction to C++", author = "C++-expert", year = 2017)
book_OOP <- list(title = "Object Oriented Programming", author = c("R-expert", "C++-expert"), year = 2016)

# Buch-Liste:
books <- list(title = "Programming", book1 = book_R, book2 = book_Cpp, book3 = book_OOP)

length(books)   # 4

summary(books)
# Length Class  Mode     
# title 1      -none- character
# book1 3      -none- list     
# book2 3      -none- list     
# book3 3      -none- list

Wenn man mehr an formalen Eigenschaften einer Liste als an den Inhalten interessiert ist, liefert summary() prägnantere Informationen als die Funktion str().

Zusammenfassung

Erzeugen von Listen und Zugriff auf die Komponenten einer Liste

Die folgende Tabelle zeigt Funktionen zum Erzeugen von Listen und wie auf die Komponenten einer Liste zugegriffen werden kann:

Funktion Beschreibung
vector(mode = "list", length = 0) Die Funktion vector() erzeugt einen echten Vektor, wenn ein atomarer Modus angegeben wird. Der Modus "list" steht für eine Liste; der default-Wert für die Länge der Liste ist 0.
list(...) Die Punkte ... stehen für beliebige Objekte, die zu einer Liste zusammengefasst werden. Die Objekte können dabei in der Form name = value eingegeben werden, wodurch sofort das Attribut names gesetzt wird.
lst[[idx]] Zugriff auf den Wert der Komponente idx der Liste lst; dabei muss idx ein gültiger Index der Liste lst sein. (Index-Vektoren können hier nicht eingesetzt werden.)
lst[["name"]] Zugriff auf den Wert der Komponente (als name = value-Paar) mit Namen "name" der Liste lst; nur möglich, wenn es eine Komponente mit Namen "name" gibt.
lst$name Zugriff auf den Wert der Komponente (als name = value-Paar) mit Namen "name" der Liste lst; nur möglich, wenn es eine Komponente mit Namen "name" gibt. Beim Zugriff mit $ müssen die Anführungsstriche nicht gesetzt werden.
lst[idx] Zugriff auf die Komponente mit Index idx der Liste lst; dabei muss idx ein gültiger Index der Liste lst sein. (Hier ist es möglich, mit Index-Vektoren zu arbeiten.)
lst["name"] Zugriff auf die Komponente mit Namen "name" der Liste lst; nur möglich, wenn es eine Komponente mit Namen "name" gibt.

Die folgende Tabelle zeigt die Rückgabewerte der Funktionen zum Erzeugen von Listen und der Zugriffe auf die Komponenten einer Liste:

Funktion Rückgabewert Datentyp
vector(mode = "list", length = 0) Liste mit gegebener Länge length; die Komponenten sind dabei noch nicht gesetzt (ein Zugriff darauf liefert NULL). Liste
list(...) Liste, deren Komponenten ihren Index durch die Reihenfolge der Argumente erhalten; werden die Argumente in der Form name = value eingegeben, ist das Attribut names gesetzt. Liste
lst[[idx]] Wert der Komponente idx der Liste lst. Datentyp des Wertes der Komponente idx der Liste lst
lst[["name"]] Wert der Komponente mit Namen "name" der Liste lst. Datentyp des Wertes der Komponente mit Namen "name"
lst$name Wert der Komponente mit Namen "name" der Liste lst. Datentyp des Wertes der Komponente mit Namen "name"
lst[idx] Teil-Liste von lst (falls idx ein einziger Index ist, hat die Teil-Liste die Länge 1; falls idx ein Index-Vektor ist, hat die Teil-Liste die Länge des Index-Vektors). Liste
lst["name"] Liste, die eine Komponente enthält, nämlich die Komponente mit Namen "name" der Liste lst. Liste der Länge 1

Diagnose-Funktionen für Listen

Die folgende Tabelle zeigt die Übersicht über die Diagnose-Funktionen für Listen:

Funktion Beschreibung
is.atomic(x) Untersucht, ob das Objekt x atomar ist. (Bei einer Liste x wird immer FALSE zurückgegeben, auch wenn sie wie ein atomares Objekt aufgebaut ist.)
is.recursive(x) Untersucht, ob das Objekt x rekursiv aufgebaut ist. (Bei einer Liste wird immer TRUE zurückgegeben.)
mode(x) Modus eines Objektes: Bei atomaren Vektoren der Modus der Komponenten, bei einer Liste "list".
is.vector(x, mode = "logical") Untersucht, ob ein gegebenenes Objekt x ein Vektor mit dem Modus mode ist (default-Wert ist "logical"); eine Liste ist ein Vektor mit Modus "list".
is.list(x) Untersucht, ob ein gegebenenes Objekt x eine Liste ist.
length(x) Anzahl der Komponenten von x.
print(x) Ausgabe von x; bei einer Liste x werden alls Komponenten durch eine Leerzeile getrennt ausgegeben. Die Ausgabe einer Komponente zeigt Namen und Wert der Komponente; ist die Komponente selber eine Liste, wird diese wiederum mit print() ausgegeben.
str(x) Ausgabe der Struktur einer Liste x (Information über Datentyp und Werte der Komponenten)
names(x) Ausgabe der Namen der Komponenten einer Liste x (falls ein Name nicht gesetzt ist, wird für die entsprechende Komponente NULL angegeben).
summary(x) Ausgabe einer Tabelle, in der für die Komponenten der Liste dargestellt wird: Name der Komponente, ihre Länge, ihre Klasse, ihr Modus. (Der Name ist gleich NULL, falls er nicht gesetzt ist.)

Die folgende Tabelle zeigt eine kurze Beschreibung der Rückgabewerte der Diagnose-Funktionen für Listen sowie den Datentyp des Rückgabewertes:

Funktion Rückgabewert Datentyp
is.atomic(x) Untersucht, ob das Objekt x atomar ist. (Bei einer Liste x wird immer FALSE zurückgegeben, auch wenn sie wie ein atomares Objekt aufgebaut ist.) logischer Wert
is.recursive(x) Untersucht, ob das Objekt x rekursiv aufgebaut ist. (Bei einer Liste wird immer TRUE zurückgegeben.) logischer Wert
mode(x) Modus eines Objektes: Bei atomaren Vektoren der Modus der Komponenten, bei einer Liste "list". Zeichenkette
is.vector(x, mode = "logical") Untersucht, ob ein gegebenenes Objekt x ein Vektor mit dem Modus mode ist (default-Wert ist "logical"); eine Liste ist ein Vektor mit Modus "list". logischer Wert
is.list(x) Untersucht, ob ein gegebenenes Objekt x eine Liste ist. logischer Wert
length(x) Anzahl der Komponenten von x Zahl
print(x) Rückgabe und Ausgabe von x Datentyp von x
str(x) kein Rückgabewert, sondern Ausgabe der Struktur einer Liste x (Information über Datentyp und Werte der Komponenten) "NULL": kein Rückgabewert
names(x) Vektor der Namen der Komponenten einer Liste x (falls ein Name nicht gesetzt ist, wird für die entsprechende Komponente NULL angegeben). Vektor aus Zeichenketten
summary(x) Tabelle, in der für die Komponenten der Liste dargestellt wird: Name der Komponente, ihre Länge, ihre Klasse, ihr Modus. (Der Name ist gleich NULL, falls er nicht gesetzt ist.) Tabelle (table), genauer: Klasse "summaryDefault" "table"
Alle Kommentare
Durch die Nutzung dieser Website erklären Sie sich mit der Verwendung von Cookies einverstanden. Außerdem werden teilweise auch Cookies von Diensten Dritter gesetzt. Genauere Informationen finden Sie in unserer Datenschutzerklärung sowie im Impressum.