Listen in R: Anwendungen

Funktionen aus den Basis-Paketen von R werden besprochen, mit denen Listen verarbeitet werden können. Insbesondere wird dabei auf die rekursive Struktur von Listen eingegangen.

Einordnung des Artikels

Dieses Kapitel setzt den sicheren Umgang mit Vektoren und die Kenntnis der Funktionen zum Erzeugen von Listen und den Zugriff auf ihre Komponenten voraus, also insbesondere die Inhalte der Kapitel Vektoren in R: der Datentyp vector, Vektoren in R: Anwendungen und Listen in R: der Datentyp list.

Einführung

Hier werden die wichtigsten Funktionen vorgestellt, mit denen Listen verarbeitet werden können.

Die beiden Funktionen c() und as.list() gehören eigentlich zum Kapitel Listen in R: der Datentyp list. Da ihr Verständnis aber die Spitzfindigkeiten in der Unterscheidung zwischen Vektoren und Listen voraussetzen (wie sie zum Beispiel unter Die Funktionen is.vector() und is.list() in Listen in R: der Datentyp list diskutiert wurden), werden sie erst hier besprochen.

Viele der hier diskutierten Funktionen berücksichtigen, dass Listen eine rekursive Struktur haben. Die vorgestellten Beispiele sind möglichst einfach gewählt, um sie leicht nachvollziehbar zu machen. Dabei wird vermutlich nicht klar, wie rekursive Strukturen in tatsächlichen Anwendungen aussehen könnten. Als Paradebeispiel stelle man sich dazu – das bereits öfters zitierte – Dateisystem vor: in R könnte man ein komplettes Dateisystem (mit all seinen Ordnern, Unterordnern und Dateien) in einer Liste abbilden. Die hier vorgestellten Funktionen sollten auch dafür einsetzbar sein!

Weitere wichtige Funktion, mit denen über die Komponenten einer Liste iteriert werden kann wie lapply() und sapply(), werden erst im Kapitel über die Familie der apply()-Funktionen besprochen, siehe Die Familie der apply-Funktionen in R Teil 1: Verarbeitung von Listen mit lapply(), sapply(), vapply() und rapply().

Funktionen für Listen

Die Funktion c()

Wie die Funktion c() zum Erzeugen von Vektoren eingesetzt werden kann, wurde bereits ausführlich besprochen (siehe Die Funktion c() (combine) in Vektoren in R: der Datentyp vector). Dabei ist zu beachten:

  • Falls die Komponenten, die an c() übergeben werden, unterschiedlichen Modus haben, wird gemäß der Hierarchie logical < integer < double < character der größte Modus ausgewählt; bevor der neue Vektor erzeugt wird, werden eventuell Typumwandlungen vorgenommen.
  • Die Funktion c() kann eingesetzt werden, um Namen für die Komponenten zu setzen.
  • Die Argumente von c() können ihrerseits Vektoren sein.

Die folgenden Beispiele sollten bekannt sein:

v <- c('1', 2, TRUE)

# Typumwandlung:
mode(v)
# [1] "character"

# Setzen von names:
e3 <- c(x1 = 0, x2 = 0, x3 = 1)
str(e3)
# Named num [1:3] 0 0 1
# - attr(*, "names")= chr [1:3] "x1" "x2" "x3"

# Kombination von Vektoren:
v4 <- c(e3, x4 = 5)
str(v4)
# Named num [1:4] 0 0 1 5
# - attr(*, "names")= chr [1:4] "x1" "x2" "x3" "x4"

In obigen Beispielen wird stets ein Vektor erzeugt; der Test auf eine Liste mit is.list() ergibt immer FALSE.

Die Funktion c() ist generisch implementiert und kann auch für Listen eingesetzt werden. Man sollte dies besser im Zusammenhang mit oben genannter Hierarchie verstehen: Diese Hierarchie wird jetzt um den Modus "list" erweitert:

logical < integer < double < character < list

Sobald ein Argument in c() den Modus "list" besitzt, wird dieser als größter Modus gewählt. Das folgende Beispiel demonstriert dies:

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

book.R.ed <- c(book.R, edition = 2)

is.list(book.R.ed)
# [1] TRUE

str(book.R.ed)
# List of 4
# $ title  : chr "Introduction to R"
# $ author : chr "R-expert"
# $ year   : num 2018
# $ edition: num 2

book.R.ed
# $title
# [1] "Introduction to R"
# 
# $author
# [1] "R-expert"
# 
# $year
# [1] 2018
# 
# $edition
# [1] 2

Zur Erklärung:

Zeile 1: Die Funktion list() wird eingesetzt, um eine Liste book.R zu erzeugen.

Zeile 4: Mit der Funktion c() werden diese Liste und eine Zahl kombiniert. Da die ursprüngliche Liste 3 Komponenten hat, sollte eine Liste mit 4 Komponenten entstehen.

Zeile 6: Mit is.list() wird überprüft, dass es sich tatsächlich um eine Liste handelt.

Zeile 9 und 16: Die Ausgaben bestätigen die Vermutungen über book.R.ed und zeigen zudem, dass das Attribut names gesetzt ist.

Das letzte Beispiel ist noch leicht zu verstehen: Werden eine Liste und eine Zahl mit c() kombiniert, wird die Zahl als weitere Komponente an die Liste angehängt (bei der Verwendung von c() mit Vektoren hätte man das analoge Verhalten erwartet).

Aber was passiert, wenn zwei Listen – etwa mit jeweils 3 Komponenten – kombiniert werden? Jetzt sind zwei Möglichkeiten denkbar:

  1. Es entsteht eine Liste mit 6 Komponenten.
  2. Es entsteht eine Liste mit 2 Komponenten; jede dieser Komponenten ist eine Liste mit 3 Komponenten.

Wie sofort gezeigt wird, kann die zweite Möglichkeit nicht realisiert werden; man kann lediglich das Verhalten von c() über das weitere Argument recursive = FALSE steuern (der default-Wert ist gleich FALSE); im Fall von recursive = TRUE wird aber ein Vektor erzeugt.

Das folgende Skript definiert 2 Bücher, jeweils als Liste mit 3 Komponenten. Die 2 Bücher werden mit c() kombiniert; zuerst ohne das Argument recursive zu setzen, dann mit recursive = TRUE .

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

book.Cpp <- list(title = "Introduction to C++", 
            author = "C++-expert", year = 2017)

books <- c(book1 = book.R, book2 = book.Cpp)

length(books)       # 6
str(books)
# List of 6
# $ book1.title : chr "Introduction to R"
# $ book1.author: chr "R-expert"
# $ book1.year  : num 2018
# $ book2.title : chr "Introduction to C++"
# $ book2.author: chr "C++-expert"
# $ book2.year  : num 2017

books.rec <- c(book1 = book.R, book2 = book.Cpp, recursive = TRUE)

length(books.rec)       # 6
str(books.rec)
# Named chr [1:6] "Introduction to R" "R-expert" "2018" "Introduction to C++" "C++-expert" "2017"
# - attr(*, "names")= chr [1:6] "book1.title" "book1.author" "book1.year" "book2.title" ...

Man erkennt:

  • Mit dem default-Wert recursive = FALSE erzeugt c() eine Liste, in der die Komponenten der beiden ursprünglichen Listen zusammengehängt werden; sie hat 6 Komponenten (Zeile 7 bis 17)
  • Mit recursive = TRUE erzeugt c() einen character-Vektor mit 6 Komponenten.

Man kann das Verhalten des Argumentes recursive folgendermaßen erklären:

  • Die Funktion c() versucht für die eingegebenen Werte einen gemeinsamen Modus zu bestimmen.
  • Gibt es einen atomaren Modus (gemäß der Hierarchie logical < integer < double < character) für alle Eingabewerte, wird ein Vektor mit diesem Modus gebildet.
  • Ist jetzt recursive = TRUE gesetzt, wird dieses Verhalten an die Komponenten weitergereicht, wenn diese Listen sind. Und die Listen aus dem Beispiel oben (also die beiden Bücher) können in einen character-Vektor verwandelt werden: es entstehen zwei character-Vektoren mit je 3 Komponenten, die zu einem Vektor mit 6 Komponenten kombiniert werden.
  • Ist aber recursive = FALSE , wird das Verhalten nicht weitergereicht (es wird also nicht versucht die Argumente in Vektoren zu verwandeln); die Argumente von c() werden sofort zu einer Liste mit 6 Komponenten kombiniert.

Ein ganz ähnliches Verhalten wird auch die Funktion unlist() zeigen, die weiter unten besprochen wird.

Aufgabe:

Wie lassen sich die beiden Bücher aus dem obigen Skript zu einer Liste mit 2 Komponenten kombinieren?

Erzeugen von Listen mit as.list()

Inzwischen sind zahlreiche Eigenschaften einer Liste vorgestellt worden; dabei sollte insbesondere die obige Behauptung, nach der Listen eine Verallgemeinerung von Vektoren sind, verständlicher geworden sein. Man erwartet dann aber, dass es eine einfache Möglichkeit gibt, einen Vektor in eine Liste zu verwandeln. Dies geschieht mit der Funktion as.list(); sie sorgt dafür, dass ein gegebener Vektor die innere Struktur einer Liste erhält; insbesondere können dann Komponenten-Namen vereinbart werden. Allerdings ist der Ausgangspunkt ein Vektor – und darin haben alle Komponenten einen identischen Datentyp.

Das folgende Beispiel erzeugt einen Vektor (Zeile 1), verwandelt ihn in eine Liste (Zeile 2) und gibt sie aus (Zeile 4, Ausgaben in Zeile 5 bis 12). Nachträglich werden die Komponenten-Namen gesetzt (Zeile 14) und die Liste erneut ausgegeben (Zeile 15 bis 23):

v <- c("Introduction to R", "R-expert", "Statistics")
l <- as.list(v)

l
# [[1]]
# [1] "Introduction to R"
#
# [[2]]
#[1] "R-expert"
#
# [[3]]
# [1] "Statistics"

names(l) <- c("title", "author", "tag")
l
# $title
# [1] "Introduction to R"
#
# $author
# [1] "R-expert"
#
# $tag
# [1] "Statistics"

Die Funktion lengths()

Die Funktion length() ist für jedes R-Objekt definiert und gibt die Anzahl der Komponenten an. Da eine Liste eine rekursive Struktur ist, kann man nach der Länge jeder Komponente fragen. Dies erledigt die Funktion lengths(x): sie erzeugt einen "integer"-Vektor v, dessen Länge mit der Anzahl der Komponenten von x übereinstimmt. Und jede Komponente von v stimmt mit der Länge der entsprechenden Komponente von x überein.

Im folgenden Beispiel wird eine Liste book.OOP mit 3 Komponenten erzeugt, wobei die zweite Komponente ein Vektor der Länge zwei ist. Der von lengths(x = book.OOP) erzeugte Vektor ist dann (1, 2, 1).

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

v <- lengths(x = book.OOP)
# title author   year 
# 1      2      1 

str(v)
# Named int [1:3] 1 2 1
# - attr(*, "names")= chr [1:3] "title" "author" "year"

An der Ausgabe des Vektors v und seiner Struktur erkennt man, dass die Namen der Komponenten von der Liste an den Vektor weitergegeben werden; dies ist das default-Verhalten von lengths(). Möchte man dies unterbinden, muss man lengths(x, use.names = FALSE) verwenden.

Die Funktionen head() und tail()

Die Funktionen head() und tail() wurden bereits für Vektoren und Matrizen ausführlich besprochen (siehe insbesondere Abschneiden eines Vektors mit head() oder tail() in Vektoren in R: Anwendungen). Da Listen wie Vektoren indiziert sind, lässt sich alles, was dort gesagt wurde, auf Listen übertragen. Das folgende Beispiel soll die wichtigste Anwendung von head() und tail() zeigen. Es wird wieder die Buch-Liste erzeugt, die aus einem Titel und drei Büchern besteht. Anschließend wird diese Liste zerlegt:

  • in den Titel (als Liste) und
  • die eigentliche Buch-Liste (bestehend aus drei Büchern):
# 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)

books.title <- head(x = books, n = 1)

str(books.title)
# List of 1
# $ title: chr "Programming"

books.list <- tail(x = books, n = 3)

str(books.list)
# List of 3
# $ 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

# Titel des ersten Buches:
books.list$book1$title
# [1] "Introduction to R"

Die Funktionen head() und tail() besitzen als Eingabewerte (siehe Zeile 13 und 19):

  • die zu kürzende Liste x und
  • die Zahl n, die angibt, wie viele Komponenten ausgewählt werden.

An der Ausgabe der Struktur der mit head() und tail() gebildeten Listen (siehe Zeile 15 und 21) erkennt man, dass tatsächlich tiefe Kopien der ausgewählten Elemente erzeugt werden. Mit tiefer Kopie ist gemeint, dass die Elemente vollständig – also auch mit ihren Attributen – kopiert werden. Bei einer flachen Kopie werden keine eingebetteten Daten übernommen.

Die Funktion append()

Die Funktion append() wurde schon bei Vektoren vorgestellt (siehe Aufbau eines Vektors aus Komponenten in Vektoren in R: der Datentyp vector). Sie erlaubt es, zu einem Objekt x nach einem gegebenen Index after die Werte values hinzuzufügen. Zurückgegeben wird dann das verlängerte Objekt (x bleibt unverändert).

append(x, values, after = length(x))

Da Listen wie Vektoren indiziert sind, kann diese Operation auch mit Listen ausgeführt werden. Allerdings ergibt sich aus der Tatsache, dass Listen rekursive Struktur haben, ein Problem: Wird in eine Liste eine weitere Liste mittels append() eingefügt, so ist nicht klar, welches Objekt erzeugt wird.

Man kann dieses Problem am Beispiel der bereits mehrfach verwendeten Buch-Liste beschreiben. Zu einer bestehenden Buch-Liste soll ein weiteres Buch hinzugefügt werden. Dieses weitere Buch ist selber eine Liste. Die Anwendung von append() könnte zu zwei verschiedenen Ergebnissen führen:

  • Entweder wird die Liste um eine Komponente verlängert (das hinzugefügte Buch ist die neue Komponente).
  • Oder die Liste wird um die Anzahl der Komponenten des neuen Buches verlängert, das heißt die Komponenten des neuen Buches werden der bestehenden Liste hinzugefügt.

Das folgende Beispiel zeigt, dass tatsächlich die zweite Version verwirklicht wird.

# 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 (ein Titel und 3 Bücher)

# neues Buch:
book.Java <- list(title = "Introduction to Java", 
            author = "Java-expert", year = 2018)

# Hinzufügen des neuen Buches zur Liste books 
# (Anhängen am Ende der Liste - verwendet den default-Wert von after):
books.new <- append(x = books, values = book.Java)

length(books.new)           # 7

str(books.new)
# List of 7
# $ 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
# $ title : chr "Introduction to Java"
# $ author: chr "Java-expert"
# $ year  : num 2018

Sowohl an der Länge als auch an der Ausgabe der Struktur von books.new erkennt man (Zeile 23 und 25), dass nicht eine, sondern 3 neue Komponenten hinzugefügt wurden.

Möchte man die erste Version erzwingen, muss das Buch book.Java , das hinzugefügt werden soll und das schon eine Liste ist, nochmals in eine Liste verpackt werden (book.Java ist dann einziges Element einer Liste). In dieser Version wird book.Java wie die anderen Bücher behandelt, die bereits in der Liste enthalten waren. Den Namen des Buches muss man natürlich selber setzen.

Und möchte man mehrere Bücher gleichzeitig zur bestehenden Liste hinzufügen, müssen diese Bücher in einer Liste verpackt werden

Aufgabe:

Schreiben Sie ein Skript, das aus books eine Liste mit 5 Komponenten macht, wobei die 5. Komponente das neue Buch book.Java ist und den Namen boook4 besitzt.

Hinzufügen und Löschen eines Elementes einer Liste

Mit der Funktion append() wurde bereits eine Möglichkeit vorgestellt, wie man einer Liste ein Element (oder mehrere Elemente) hinzufügen kann. Manchmal ist dies einfacher durch den direkten Zugriff auf die Komponenten, der

  • entweder über den Index und den Operator [[.]] ,
  • oder über den Namen und den Operator $ erfolgen kann.

Auch zum Löschen einer Komponente gibt es zwei Möglichkeiten:

  • entweder indem die betreffende Komponente gleich NULL gesetzt wird (falsch wäre, sie gleich NA zu setzen),
  • oder indem sie mit Hilfe ihres negativen Index aus der Liste beseitigt wird. Dazu muss dann der Operator [.] verwendet werden, da nicht der Wert der Komponente sondern die gesamte Komponente (als Paar name = value) entfernt werden soll.

In den folgenden Skript werden diese 4 Operationen gezeigt. Dazu wird jeweils von der Buch-Liste books ausgegangen, die im folgenden Skript erzeugt wird. Die Liste enthält einen Titel und drei Bücher. Zu ihr wird später entweder das Buch book.Java hinzugefügt oder eine Komponente gelöscht.

# 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)

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


# Buch-Liste (Java fehlt):
books <- list(title = "Programming", book1 = book.R, 
            book2 = book.Cpp, book3 = book.OOP)

length(books)           # 4

Hinzufügen einer Komponente mit dem Operator [[.]]

Da die Liste books 4 Komponenten hat, kann das Buch book.Java als 5. Komponente hinzugefügt werden. Allerdings hat diese neue Komponente dann noch keinen Namen, der gesondert definiert werden muss.

# Hinzufügen eines Buches mit [[.]]
books[[5]] <- book.Java

length(books)           # 5

length(names(books))    # 5

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

names(books)[5] <- "book4"

str(books)
# List of 5
# $ 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
# $ book4:List of 3
# ..$ title : chr "Introduction to Java"
# ..$ author: chr "Java-expert"
# ..$ year  : num 2018

Erklärungsbedürftig sind hier vielleicht:

Zeile 6: Dass books nach dem Setzen der 5. Komponente in Zeile 3 jetzt 5 Komponenten hat, ist noch klar. Aber warum gibt es auch 5 Namen, obwohl kein Name für book.Java gesetzt wurde?

Zeile 8 und 9: Die Lösung liefert die Ausgabe der Namen. Der 5. Name wurde mit dem default-Wert einer Zeichenkette, also dem leeren String "", belegt.

Zeile 11: Der Name der 5. Komponente wird mit der replacement-Version von names() auf book4 gesetzt.

Zeile 13: Die Ausgabe der Struktur von books zeigt jetzt die 4 Bücher mit ihren Namen.

Hinzufügen einer Komponente mit dem Operator $

Da der Zugriff auf die Komponenten einer Liste auch mit dem Operator $ erfolgen kann, lässt sich die im obigen Skript erledigte Aufgabe auch anders erledigen – diese Lösung ist sogar einfacher, da jetzt zwei Operationen gleichzeitig ausgeführt werden:

  • das neue Buch book.Java wird hinzugefügt,
  • dem Buch wird eine Name verliehen.

Das folgende Skript zeigt dies:

# Hinzufügen eines Buches mit $
books$book4 <- book.Java

length(books)   # 5

books[[5]]
# $title
# [1] "Introduction to Java"
# 
# $author
# [1] "Java-expert"
# 
# $year
# [1] 2018

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

Man erkennt:

Zeile 2: Durch die Operation books$book4 <- book.Java wird

  • eine neue Komponente erzeugt (also ein Paar name = value),
  • der neuen Komponente der Name book4 gegeben,
  • als Wert die rechte Seite von Zeile 2 zugewiesen (hier book.Java).

Zeile 4 und 6: Die Ausgaben zeigen, dass tatsächlich die Liste verlängert wurde und dass das neue Buch die 5. Komponente ist.

Zeile 16: Die Namen sind gesetzt wie gewünscht.

Löschen von Komponenten einer Liste

Wie oben gesagt, gibt es zwei Möglichkeiten, wie man aus einer Liste eine Komponente entfernen kann:

  • die Komponente wird gleich NULL gesetzt,
  • auf die Komponente wird mit einem negativen Index zugegriffen.

Bei der ersten Version ist zu beachten, dass tatsächlich NULL verwendet werden muss und nicht NA (bei NA bleibt die Länge der Liste unverändert):

# Löschen eines Buches
books$book3 <- NULL

length(books)           # 3

books[[3]] <- NULL

length(books)           # 2

books[[2]] <- NA

length(books)   # 2

books[[2]]    # NA

names(books)
# [1] "title"  ""

Zur Erklärung:

Zeile 2: Die Liste hat anfangs 4 Komponenten. Die Komponente mit Namen book3 wird gleich NULL gesetzt.

Zeile 4: Die Länge von books ist jetzt gleich 3.

Zeile 6. Der Zugriff auf die zu löschende Komponente kann auch mit Hilfe von [[.]] erfolgen.

Zeile 10: Die zweite Komponente der Buch-Liste (also book1) wird gleich NA gesetzt.

Zeile 12 bis 17: Man erkennt, dass dadurch die Länge der Buch-Liste unverändert bleibt, die zweite Komponente eben gleich NA ist und keinen Namen besitzt.

Beim Löschen durch einen Zugriff mit negativem Index ist zu beachten: Da jede Komponente ein Paar der Art name = value ist, gilt:

  • der Operator [[.]] greift auf den Wert der Komponente zu,
  • der Operator [.] greift auf die Komponente zu.

Soll also eine gesamte Komponente entfernt werden, benötigt man den Operator [.] :

books <- books[-5]

length(books)   # 4

# books <- books[[-4]]   # Fehler

Zeile 1: Durch books[-5] wird eine neue Liste gebildet, in der die 5. Komponente aus der ursprünglichen Liste fehlt (Zeile 1, rechte Seite). Diese neue Liste wird der Variable books zugeordnet.

Zeile 3: Die Buch-Liste wurde tatsächlich verkürzt.

Zeile 5: Ist auskommentiert, da sie einen Syntax-Fehler enthält (Anzahl der Klammern beachten!).

Die Funktion modifyList()

Im letzten Unterabschnitt wurde gezeigt, wie man die Komponenten einer Liste löschen kann oder neue Komponenten hinzufügt. Möchte man eine Liste auf einen neuen Stand bringen, muss man nicht diese beiden Operationen kombinieren; die Funktion modifyList() erledigt dies in einem Schritt.

Sie befindet sich im package utils und besitzt drei Eingabewerte:

modifyList(x, val, keep.null = FALSE)

Dabei ist:

  • x die Liste, die verändert werden soll.
  • val eine Liste mit den neuen Einträgen.
  • keep.null gibt an, ob Werte, die in val gleich NULL sind) in der neu erzeugten Liste gleich NULL sind.

Der Rückgabewert ist eine neue, veränderte Liste; die Ausgangs-Liste x bleibt unverändert.

Im folgenden Beispiel wird ein Buch als Liste erzeugt (Zeile 2). Eine weitere Liste enthält (Zeile 5):

  • einen neuen Titel für das Buch (die Komponente mit dem Namen title),
  • eine neu hinzukommende Komponente mit der Auflage des Buches (mit dem Namen edition).
# Buch:
book.R <- list(title = "Introduction to R", author = "R-expert", year = 2018)

# update mit:
book.R.2ed <- list(title = "Introduction to R, Extended Version", edition = 2)

# neue Version des Buches.
book.R.new <- modifyList(x = book.R, val = book.R.2ed)

book.R
# $title
# [1] "Introduction to R"
# 
# $author
# [1] "R-expert"
# 
# $year
# [1] 2018

book.R.new
# $title
# [1] "Introduction to R, Extended Version"
# 
# $author
# [1] "R-expert"
# 
# $year
# [1] 2018
# 
# $edition
# [1] 2

An der Ausgabe erkennt man:

  • Die alte Version des Buches bleibt unverändert (Zeile 10).
  • In der neuen Version des Buches wurde
    • die Komponente title erneuert (Zeile 2),
    • die Komponente edition neu hinzugefügt (Zeile 30).

Der Eingabewert keep.null besitzt den default-Wert FALSE; setzt man ihn auf TRUE, werden Komponenten aus dem ursprünglichen Buch entfernt, die in der neuen Version ausdrücklich gleich NULL gesetzt sind:

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

# update mit:
book.R.2ed <- list(title = "Introduction to R, Extended Version", 
            author = NULL, edition = 2)

book.R.new <- modifyList(x = book.R, val = book.R.2ed, keep.null = TRUE)

book.R.new
# $title
# [1] "Introduction to R, Extended Version"
# 
# $author
# NULL
# 
# $year
# [1] 2018
# 
# $edition
# [1] 2

Hätte man die Komponente author nicht gleich NULL gesetzt (in Zeile 5), sondern einfach weggelassen, erhält man das Verhalten von oben.

Die Funktion unlist(): Einebnen einer Liste

Wie schon mehrfach betont wurde, haben Listen eine rekursive Struktur, was bedeutet, dass ihre Komponenten beliebigen Datentyp besitzen dürfen – insbesondere sind wiederum Listen zugelassen. Dies kann bei der Auswertung einer Liste eher hinderlich sein, da man die Daten erst aufbereiten muss – etwa um Funktionen wie sum() oder mean() anzuwenden. Oftmals kann man eine Liste x leicht in einen Vektor verwandeln, der dann als Eingabewert für eine Funktionen zur Auswertung dient. Die Funktion unlist(x) leistet genau dies: sie erzeugt aus einer Liste einen Vektor, also eine atomare Struktur.

Und wie die weitere Diskussion zeigt, ist unlist() mächtiger als eine Typumwandlung. Vorerst ein einfaches Beispiel:

l <- list( a = (1:2), b = (1:3) )

str(l)
# List of 2
# $ a: int [1:2] 1 2
# $ b: int [1:3] 1 2 3

v <- unlist(x = l)
str(v)
#  Named int [1:5] 1 2 1 2 3
# - attr(*, "names")= chr [1:5] "a1" "a2" "b1" "b2" ...

is.list(v)              # FALSE
is.vector(v)            # TRUE
length(v)               # 5

sum(l)          # NA
sum(v)          # 9

Zur Erklärung:

Zeile 1: Es wird zuerst eine Liste gebildet, die aus zwei Vektoren unterschiedlicher Länge besteht. Die beiden Vektoren haben als Komponenten der Liste die Namen a und b; sie haben zwar identischen Modus, aber unterschiedliche Längen.

Zeile 8 bis 11: Mit der Funktion unlist() wird die Liste in einen Vektor v verwandelt. An der Ausgabe von str(v) (Zeile 9 bis 11) erkennt man, wie die Namen der Liste an den Vektor v weitergegeben werden.

Zeile 13 bis 15: Dass es sich jetzt tatsächlich um einen Vektor und keine Liste handelt, wird mit is.list() und is.vector() überprüft (Zeile 13 und 14); die Länge des Vektors ist die Summe der Längen der Vektoren, aus denen l zusammengesetzt war (Zeile 15).

Zeile 17 und 18: In Zeile 17 erkennt man, dass die Funktion sum() nicht auf die Liste l angewendet werden kann, aber auf den Vektor v (Zeile 18): es werden alle 5 Komponenten addiert.

In diesem Beispiel war es offensichtlich, wie die Liste zu einem Vektor eingeebnet wird. Das folgende Beispiel zeigt die Wirkung von unlist(), wenn in der Liste tatsächlich unterschiedliche Datentypen vorkommen – hier sind es Zeichenketten, eine Zahl und ein Vektor von Zeichenketten:

book.R <- list(title = "Introduction to R", 
            author = "R-expert", year = 2018, tags = c("R", "Programming", "Statistics"))
            
v_book.R <- unlist(x = book.R)

is.list(v_book.R)           # FALSE
is.vector(v_book.R)         # TRUE

is.vector(v_book.R, mode = "character")         # TRUE
length((v_book.R))          # 6 (Beachte: tags wird in 3 Komponenten verwandelt)
names(v_book.R)             # [1] "title"  "author" "year"   "tags1"  "tags2"  "tags3"

str(v_book.R)
# Named chr [1:6] "Introduction to R" "R-expert" "2018" "R" "Programming" "Statistics"
#  - attr(*, "names")= chr [1:6] "title" "author" "year" "tags1" ...

Die Liste hatte ursprünglich 4 Komponenten, wobei die 4. Komponente "tags" ein Vektor mit 3 Komponenten ist. Durch die Funktion unlist() (Zeile 4) entsteht tatsächlich ein Vektor (Zeile 7) und keine Liste (Zeile 6). Die 3. Komponente der Liste war ursprünglich eine Zahl, sie wird in eine Zeichenkette umgewandelt (Zeile 9), da dies der einzige Datentyp ist, mit dem alle Komponenten dargestellt werden können.

Die Ausgabe des Vektors mit str() (Zeile 13 bis 15) zeigt die 6 Komponenten als Zeichenketten und die Namen, die von der Liste übernommen wurden beziehungsweise welche 3 Namen aus "tags" erzeugt wurden.

Sind die Datentypen der Komponenten der einzuebnenden Liste unterschiedlich, so ist wieder die bereits bekannte Hierarchie der Datentypen zu beachten: logical < integer < double < character < list. Aus dieser Hierarchie lässt sich der gemeinsame Datentyp ablesen, in den im Zweifelsfall konvertiert wird; im Beispiel oben enthielt die Liste Zahlen und Zeichenketten, daher ist der gemeinsame Datentyp "character".

Diese Hierarchie — allerdings nur von logical bis character — wurde ausführlich bei den Zeichen besprochen (Einführung in R: Zeichen in Einführung in R: Zeichen). Warum die Hierarchie mit list schließt, wird im Zusammenhang mit dem Argument recursive von unlist() verständlich.

Die Funktion unlist() besitzt noch zwei weitere Argumente:

  • recursive
  • use.names

Im Allgemeinen kann unlist() daher mit drei Argumenten aufgerufen werden; recursive und use.names erwarten einen logischen Wert, die den default-Wert TRUE besitzen:

unlist(x, recursive = TRUE, use.names = TRUE)

Die Bedeutung von use.names sollte klar sein: wird es FALSE gesetzt, werden die Namen der Komponenten nicht an den erzeugten Vektor weitergegeben.

Die Bedeutung von recursive zeigt das folgende Beispiel, in dem eine Buch-Liste erzeugt wird – also eine Liste, die selber aus zwei Listen besteht:

book.R <- list(title = "Introduction to R", author = "R-expert", 
            year = 2018, tags = c("R", "Programming", "Statistics"))
book.Cpp <- list(title = "Introduction to C++", author = "C++-expert", 
            year = 2017, tags = c("C++", "Programming", "OOP"))

books <- vector("list", length = 2)
books[[1]] <- book.R
books[[2]] <- book.Cpp

str(books)
# List of 2
# $ :List of 4
# ..$ title : chr "Introduction to R"
# ..$ author: chr "R-expert"
# ..$ year  : num 2018
# ..$ tags  : chr [1:3] "R" "Programming" "Statistics"
# $ :List of 4
# ..$ title : chr "Introduction to C++"
# ..$ author: chr "C++-expert"
# ..$ year  : num 2017
# ..$ tags  : chr [1:3] "C++" "Programming" "OOP"

length(books)           # 2

v_books <- unlist(x = books)

str(v_books)
# Named chr [1:12] "Introduction to R" "R-expert" "2018" "R" "Programming" "Statistics" 
            # "Introduction to C++" "C++-expert" "2017" ...
# - attr(*, "names")= chr [1:12] "title" "author" "year" "tags1" ...

v_books

# title                author                  year                 tags1 
# "Introduction to R"            "R-expert"                "2018"                   "R" 
# tags2                 tags3                 title                author 
# "Programming"          "Statistics" "Introduction to C++"          "C++-expert" 
# year                 tags1                 tags2                 tags3 
# "2017"                 "C++"         "Programming"                 "OOP" 

length(v_books)             # 12
is.vector(v_books)          # TRUE
is.list(v_books)            # FALSE

# nrec steht für not recursive
v_nrec_books <- unlist(x = books, recursive = FALSE)    

str(v_nrec_books)
# List of 8
# $ title : chr "Introduction to R"
# $ author: chr "R-expert"
# $ year  : num 2018
# $ tags  : chr [1:3] "R" "Programming" "Statistics"
# $ title : chr "Introduction to C++"
# $ author: chr "C++-expert"
# $ year  : num 2017
# $ tags  : chr [1:3] "C++" "Programming" "OOP"

v_nrec_books

# $title
# [1] "Introduction to R"
# 
# $author
# [1] "R-expert"
# 
# $year
# [1] 2018
# 
# $tags
# [1] "R"           "Programming" "Statistics" 
# 
# $title
# [1] "Introduction to C++"
# 
# $author
# [1] "C++-expert"
# 
# $year
# [1] 2017
# 
# $tags
# [1] "C++"         "Programming" "OOP"  

length(v_nrec_books)                # 8
is.vector(v_nrec_books)         # TRUE
is.list(v_nrec_books)           # TRUE

v__nn_books <- unlist(books, use.names = FALSE)         # nn steht für no names

str(v_nn_books)
# chr [1:12] "Introduction to R" "R-expert" "2018" "R" "Programming" "Statistics" ...

Zur Erklärung:

Zeile 1 bis 8: Zuerst werden zwei Bücher als Listen erzeugt (book.R und book.Cpp ); die jeweils vierte Komponente ist ein Vektor mit 3 character-Komponenten. Aus den zwei Büchern wird die Liste books von Büchern erzeugt. Man beachte dabei, dass in den Büchern Namen für die Komponenten gesetzt sind; in der Liste books werden keine Namen gesetzt.

Zeile 10 bis 21: Die Ausgabe der Struktur von books zeigt:

  • books ist eine Liste mit 2 Komponenten,
  • diese zwei Komponenten besitzen keine Namen,
  • diese zwei Komponenten sind ihrerseits Listen (rekursive Struktur) — jetzt aber mit jeweils 4 Komponenten und das Attribut names ist für alle dieser Komponenten gesetzt.

Zeile 25: Die Funktion unlist() wird auf books angewendet und dabei vbooks_ erzeugt; der default-Wert für recursive ist gleich TRUE.

Zeile 27 bis 30: Die Ausgabe der Struktur von vbooks_ zeigt:

  • vbooks_ ist ein Vektor mit 12 Komponenten — und nicht mit 8 Komponenten, wie man vielleicht erwarten könnte.
  • Dies bedeutet aber, dass jedes der zwei Bücher in einen Vektor mit 6 Komponenten eingeebnet wurde; die Komponente tags wurde also in 3 Komponenten aufgespalten. Anders formuliert: bei jedem Weiterreichen von unlist() an die Komponenten wurde wiederum recursive = TRUE gesetzt.
  • Betrachtet man die Namen der 12 Komponenten, so erkennt man, dass einfach die Namen verwendet wurden, die in book.R und book.Cpp gesetzt waren beziehungsweise dass neue Namen wie tag1, tag2 , tag3 aus dem Vektor namens tag erzeugt wurden.

Zeile 32 bis 39: Die Ausgabe von vbooks_ zeigt die 12 Komponenten des Vektors als name = value-Paare (jedes Paar beansprucht zwei Zeilen und die Paare werden nacheinander ausgegeben, hier folgt nach jeweils 4 Paaren ein Zeilenumbruch).

Zeile 46: Die Funktion unlist() wird wieder auf vbooks_ angewendet, jetzt aber mit recursive = FALSE .

Zeile 48 bis 57: Man erkennt, dass jetzt kein Vektor, sondern eine Liste mit 8 Komponenten entsteht. Damit sollte auch klar sein, warum in der Hierarchie der Datentypen an letzter Stelle list steht.

Zeile 89 bis 92: Das Argument use.names = FALSE sollte sich selbst erklären.

Zusammenfassung:

  • Die Funktion unlist() mit dem Argument recursive = TRUE (default-Wert) erzeugt aus der Buch-Liste einen Vektor: das Einebnen der Liste zu einem Vektor wird rekursiv an die eingebetteten Listen weitergereicht.
  • Die Funktion unlist() mit dem Argument recursive = FALSE ebnet nur die Listen der höchsten Ebene ein. Da aber auf der höchsten Ebene selber Listen stehen und der Befehl zum Einebnen nicht weitergereicht wurde, werden die Listen der zweiten Ebene zu einer einzigen Liste zusammengefasst. Die Funktion unlist() erzeugt jetzt eine Liste und keinen atomaren Vektor.

Die Funktion relist()

Die Funktion relist() kann die Wirkung von unlist() (teilweise) rückgängig machen. Das heißt der Vektor, der mit unlist() erzeugt wurde, kann wieder in eine Liste verwandelt werden. Mit der Einschränkung teilweise soll gesagt werden, dass nicht alle Typumwandlungen rückgängig gemacht werden können.

Damit relist() arbeiten kann, benötigt man eine Vorlage – das skeleton. Dies ist die Vorlage für die zu bildende Liste, die mit unsinnigen Inhalt gefüllt werden kann, sie muss lediglich die richtige Struktur besitzen.

Somit hat relist() zwei Eingabewerte:

  • das Argument skeleton, das man sich vor der Anwendung von relist() zurechtlegen muss und
  • das Argument flesh, das ist der Vektor, der in eine Liste verwandelt werden soll.

Das folgende Skript zeigt, was relist() kann und was es nicht kann:

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

# Einebnen zu einem Vektor:
book.v <- unlist(book)

book.v
#       title              author                year 
# "Introduction to R"          "R-expert"              "2018"

is.list(book.v)     # FALSE

# Skeleton:
book.sk <- list(title = "R", author = "R", year = 1)

# Erzeugen einer Liste aus book.v:

book.l <- relist(skeleton = book.sk, flesh = book.v)
book.l
# $title
# [1] "Introduction to R"
# 
# $author
# [1] "R-expert"
# 
# $year
# [1] "2018"

is.list(book.l)     # TRUE

# Anderes Skeleton:
book.sk2 <- list(title = "R", author = "R")

book.l2 <- relist(skeleton = book.sk2, flesh = book.v)
book.l2
# $title
# [1] "Introduction to R"
# 
# $author
# [1] "R-expert"

Man beachte, dass im Vektor book.v die Komponente year nicht mehr als Zahl, sondern als Zeichen abgespeichert wurde (siehe Zeile 7 bis 9). Diese Typumwandlung kann durch relist() nicht rückgängig gemacht werden (siehe Zeile 27).

Weitere Informationen über relist() findet man unter ?relist im package utils unter relist.

Zusammenfassung

Funktionen zum Erzeugen von Listen

Die folgende Tabelle beschreibt de hier besprochenen Funktionen zum Erzeugen von Listen (weitere Funktionen wurden schon in Listen in R: der Datentyp list besprochen).

Funktion Beschreibung
c(..., recursive = FALSE, use.names = TRUE) Die in ... angegebenen Objekte werden aneinandergehängt. Wenn sie einen gemeinsamen atomaren Modus besitzen, wird ein Vektor mit diesem Modus erzeugt; wenn eine Liste enthalten ist, wird eine Liste erzeugt. Der Eingabewert recursive bestimmt, wie Listen behandelt werden, wenn sie in den zu kombinierenden Objekten enthalten sind.
as.list(x) Verwandelt das Objekt x in eine Liste.

Die folgende Tabelle zeigt eine kurze Beschreibung der Rückgabewerte der Funktionen aus obiger Tabelle sowie den Datentyp des Rückgabewertes:

Funktion Rückgabewert Datentyp
c(..., recursive = FALSE, use.names = TRUE) Vektor oder Liste, gebildet aus den Objekten, die in ... angegeben wurden. Vektor oder Liste
as.list(x) Verwandelt das Objekt x in eine Liste. Liste

Funktionen für Listen

Die folgende Tabelle zeigt die Übersicht über die weiteren, oben besprochenen Funktionen für Listen:

Funktion Beschreibung
lengths(x, use.names = TRUE) Gibt für jede Komponente von x die Anzahl der Komponenten an.
head(x, n = 6) Erzeugt eine Liste aus den ersten n Komponenten von x.
tail(x, n = 6) Erzeugt eine Liste aus den letzten n Komponenten von x.
?head Weitere Informationen über die Funktionen head() und tail() im package utils.
append(x, values, after = length(x)) An die Liste x wird values angehängt; eingefügt wird sie nach dem Index after (dessen default-Wert ist length(x)).
modifyList(x, val, keep.null = FALSE) Die Liste x wird mit den Komponenten der Liste val aktualisiert. Für keep.null = TRUE werden Komponenten, die in val gleich NULL gesetzt sind, aus x entfernt.
?modifyList Weitere Informationen über die Funktionen modifyList() im package utils.
unlist(x, recursive = TRUE, use.names = TRUE) Die Funktion unlist() ebnet eine Liste zu einem Vektor ein. Das Argument recursive bestimmt, wie eingebettete Listen behandelt werden.
relist(flesh, skeleton) Der Vektor flesh wird gemäß der Vorlage skeleton (eine Liste) in eine Liste verwandelt.
?relist Weitere Informationen über die Funktionen relist() im package utils.

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
lengths(x, use.names = TRUE) Vektor mit der Länge von x; jede Komponente des Vektors gibt die Länge der entsprechenden Komponente von x an. Gibt für jede Komponente von x die Anzahl der Komponenten an. "integer"-Vektor
head(x, n = 6) Liste mit den ersten n Komponenten von x. Liste
tail(x, n = 6) Liste mit den letzten n Komponenten von x. Liste
append(x, values, after = length(x)) An die Liste x wird values angehängt; eingefügt wird sie nach dem Index after (dessen default-Wert ist length(x)). Liste
modifyList(x, val, keep.null = FALSE) Es wird eine neue Liste erzeugt, wozu die Liste x mit den Komponenten der Liste val aktualisiert wird. Die Liste x bleibt dabei unverändert. Für keep.null = TRUE werden Komponenten, die in val gleich NULL gesetzt sind, aus x entfernt. Liste
unlist(x, recursive = TRUE, use.names = TRUE) Die Liste x wird (wenn möglich) zu einem Vektor eingeebnet. Das Argument recursive bestimmt, wie eingebettete Listen behandelt werden. Vektor oder Liste
relist(flesh, skeleton) Der Vektor flesh wird gemäß der Vorlage skeleton (eine Liste) in eine Liste verwandelt. Liste
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.