Wie verwendet man Listen in R richtig?

Kurzer Hintergrund: Viele (die meisten?) Modernen Programmiersprachen, die weit verbreitet sind, haben mindestens eine Handvoll von ADTs [abstrakte Datentypen] gemeinsam, insbesondere

In der Programmiersprache R sind die ersten beiden als character bzw. vector implementiert.

Als ich anfing, R zu lernen, waren zwei Dinge von Anfang an offensichtlich: list ist der wichtigste Datentyp in R (weil es die data.frame für das R data.frame ), und zweitens konnte ich einfach nicht verstehen, wie sie sind funktionierte, zumindest nicht gut genug, um sie korrekt in meinem Code zu verwenden.

Zum einen schien mir der list Datentyp von R eine einfache Implementierung der Map ADT ( dictionary in Python, NSMutableDictionary in Objective C, hash in Perl und Ruby, object literal in Javascript usw.).

Zum Beispiel erstellen Sie sie genau wie ein Python-Wörterbuch, indem Sie Schlüssel-Wert-Paare an einen Konstruktor übergeben (was in Python keine list ):

 x = list("ev1"=10, "ev2"=15, "rv"="Group 1") 

Und Sie greifen auf die Elemente einer R-Liste genauso zu wie auf ein Python-Wörterbuch, zB x['ev1'] . Ebenso können Sie nur die “Schlüssel” oder nur die “Werte” abrufen , indem Sie:

 names(x) # fetch just the 'keys' of an R list # [1] "ev1" "ev2" "rv" unlist(x) # fetch just the 'values' of an R list # ev1 ev2 rv # "10" "15" "Group 1" x = list("a"=6, "b"=9, "c"=3) sum(unlist(x)) # [1] 18 

aber R- list sind auch anders als andere Karten-Typ-ADTs (aus den Sprachen, die ich sowieso gelernt habe). Meine Vermutung ist, dass dies eine Konsequenz der ursprünglichen Spezifikation für S ist, dh eine Absicht, eine DSL-Daten / Statistik [domänenspezifische Sprache] von Grund auf zu entcasting.

drei signifikante Unterschiede zwischen R- list und Mapping-Typen in anderen gebräuchlichen Sprachen (zB Python, Perl, JavaScript):

Erstens , list s in R ist eine geordnete Sammlung, genau wie Vektoren, obwohl die Werte getastet sind (dh die Schlüssel können alle auswertbaren Werte sein, nicht nur sequentielle Ganzzahlen). Fast immer ist der Zuordnungsdatentyp in anderen Sprachen ungeordnet .

Zweitens kann die list s von functionen zurückgegeben werden, obwohl Sie beim Aufruf der function nie in einer list , und obwohl die function, die die list hat, keinen (expliziten) list . Natürlich können Sie damit umgehen Dies in der Praxis durch das Wrapping des zurückgegebenen Ergebnisses in einen Aufruf zu unlist ):

 x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character' class(x) # returns 'list', not a vector of length 2 # [1] list 

Ein drittes merkwürdiges Merkmal von Rs list s: Es scheint nicht, dass sie Mitglieder eines anderen ADT sein können, und wenn Sie das versuchen, wird der primäre Container zu einer list gezwungen. Z.B,

 x = c(0.5, 0.8, 0.23, list(0.5, 0.2, 0.9), recursive=TRUE) class(x) # [1] list 

meine Absicht hier ist nicht, die Sprache zu kritisieren oder wie sie dokumentiert ist; Ich behaupte auch nicht, dass mit der list oder ihrem Verhalten etwas nicht in Ordnung ist. Alles, was ich nachher korrigieren möchte, ist mein Verständnis davon, wie sie funktionieren, damit ich sie richtig in meinem Code verwenden kann.

Hier sind die Dinge, die ich gerne besser verstehen würde:

  • Welche Regeln bestimmen, wann ein functionsaufruf eine list strsplit (z. B. strsplit Ausdruck oben)?

  • Wenn ich einer list nicht explizit Namen list(10,20,30,40) (zB list(10,20,30,40) ) sind die Standardnamen nur sequentielle Ganzzahlen, die mit 1 beginnen? (Ich nehme an, aber ich bin weit davon entfernt, sicher zu sein, dass die Antwort ja ist, sonst wären wir nicht in der Lage, diese Art von list zu einem Vektor mit einem Aufruf zu unlist zu unlist .)

  • Warum geben diese beiden verschiedenen Operatoren [] und [[]] dasselbe Ergebnis zurück?

    x = list(1, 2, 3, 4)

    Beide Ausdrücke geben “1” zurück:

    x[1]

    x[[1]]

  • Warum geben diese beiden Ausdrücke nicht das gleiche Ergebnis zurück?

    x = list(1, 2, 3, 4)

    x2 = list(1:4)

Bitte weisen Sie mich nicht auf die R-Dokumentation ( ?list , R-intro ) hin – ich habe sie sorgfältig gelesen und es hilft mir nicht, die Art von Fragen zu beantworten, die ich oben erwähnt habe.

(Zuletzt habe ich kürzlich von einem R-Paket (verfügbar auf CRAN) erfahren und es begonnen, das hash Code verwendet, der herkömmliches Map-Type-Verhalten über eine S4-class implementiert; ich kann dieses Paket sicherlich empfehlen.)

    Nur um den letzten Teil Ihrer Frage anzusprechen, da dies wirklich den Unterschied zwischen einer list und einem vector in R hervorhebt:

    Warum geben diese beiden Ausdrücke nicht das gleiche Ergebnis zurück?

    x = Liste (1, 2, 3, 4); x2 = Liste (1: 4)

    Eine Liste kann jede andere class als jedes Element enthalten. Sie können also eine Liste erstellen, in der das erste Element ein Zeichenvektor ist, das zweite ein Datenrahmen usw. In diesem Fall haben Sie zwei verschiedene Listen erstellt. x hat vier Vektoren der Länge 1. x2 hat einen Vektor der Länge 4:

     > length(x[[1]]) [1] 1 > length(x2[[1]]) [1] 4 

    Das sind also völlig unterschiedliche Listen.

    R-Listen ähneln sehr stark einer Hash-Map- Datenstruktur, da jeder Indexwert einem beliebigen Objekt zugeordnet werden kann. Hier ist ein einfaches Beispiel für eine Liste, die 3 verschiedene classn (einschließlich einer function) enthält:

     > complicated.list < - list("a"=1:4, "b"=1:3, "c"=matrix(1:4, nrow=2), "d"=search) > lapply(complicated.list, class) $a [1] "integer" $b [1] "integer" $c [1] "matrix" $d [1] "function" 

    Da das letzte Element die Suchfunktion ist, kann ich es so nennen:

     > complicated.list[["d"]]() [1] ".GlobalEnv" ... 

    Als letzter Kommentar dazu: Es sollte beachtet werden, dass ein data.frame wirklich eine Liste ist (aus der data.frame Dokumentation):

    Ein Datenrahmen ist eine Liste von Variablen mit der gleichen Anzahl von Zeilen mit eindeutigen Zeilennamen, angegeben in der class “data.frame”.

    Aus diesem Grund können Spalten in einem data.frame unterschiedliche Datentypen haben, während Spalten in einer Matrix dies nicht können. Als Beispiel versuche ich hier eine Matrix mit Zahlen und Zeichen zu erstellen:

     > a < - 1:4 > class(a) [1] "integer" > b < - c("a","b","c","d") > d < - cbind(a, b) > d ab [1,] "1" "a" [2,] "2" "b" [3,] "3" "c" [4,] "4" "d" > class(d[,1]) [1] "character" 

    Beachten Sie, dass ich den Datentyp in der ersten Spalte nicht in numerisch ändern kann, da die zweite Spalte Zeichen enthält:

     > d[,1] < - as.numeric(d[,1]) > class(d[,1]) [1] "character" 

    In Bezug auf Ihre Fragen, lassen Sie mich sie in der Reihenfolge ansprechen und einige Beispiele geben:

    1 ) Eine Liste wird zurückgegeben, wenn und wenn die return-statement eine hinzufügt. Erwägen

      R> retList < - function() return(list(1,2,3,4)); class(retList()) [1] "list" R> notList < - function() return(c(1,2,3,4)); class(notList()) [1] "numeric" R> 

    2 ) Namen sind einfach nicht gesetzt:

     R> retList < - function() return(list(1,2,3,4)); names(retList()) NULL R> 

    3 ) Sie geben nicht das Gleiche zurück. Dein Beispiel gibt

     R> x < - list(1,2,3,4) R> x[1] [[1]] [1] 1 R> x[[1]] [1] 1 

    wobei x[1] das erste Element von x zurückgibt – was gleich ist wie x . Jeder Skalar ist ein Vektor der Länge eins. Auf der anderen Seite gibt x[[1]] das erste Element der Liste zurück.

    4 ) Schließlich sind die beiden unterschiedlich, denn sie erstellen jeweils eine Liste mit vier Skalaren und eine Liste mit einem einzelnen Element (das zufällig ein Vektor aus vier Elementen ist).

    Nur um eine Teilmenge Ihrer Fragen zu beantworten:

    Dieser Artikel zur Indexierung behandelt die Frage nach dem Unterschied zwischen [] und [[]] .

    Kurz gesagt [[]] wählt ein einzelnes Element aus einer Liste aus und [] gibt eine Liste der ausgewählten Elemente zurück. In Ihrem Beispiel ist x = list(1, 2, 3, 4)' Element 1 ist eine einzelne Ganzzahl, aber x[[1]] gibt eine einzige 1 zurück und x[1] gibt eine Liste mit nur einem Wert zurück.

     > x = list(1, 2, 3, 4) > x[1] [[1]] [1] 1 > x[[1]] [1] 1 

    Ein Grund, warum die Listen so funktionieren, wie sie es tun (geordnet), ist die Notwendigkeit eines geordneten Containers, der an jedem Knoten irgendeinen Typ enthalten kann, was die Vektoren nicht tun. Listen werden für eine Vielzahl von Zwecken in R wiederverwendet, einschließlich der Bildung der Basis eines data.frame , der eine Liste von Vektoren beliebigen Typs (aber gleicher Länge) ist.

    Warum geben diese beiden Ausdrücke nicht das gleiche Ergebnis zurück?

     x = list(1, 2, 3, 4); x2 = list(1:4) 

    Wenn Sie zu @ Shanes Antwort hinzufügen möchten, versuchen Sie Folgendes, wenn Sie dasselbe Ergebnis erhalten möchten:

     x3 = as.list(1:4) 

    Dies führt den Vektor 1:4 in eine Liste ein.

    Um nur einen weiteren Punkt hinzuzufügen:

    R hat eine Datenstruktur, die dem Python-Diktat im hash Paket entspricht . Sie können darüber in diesem Blogpost von der Open Data Group lesen. Hier ist ein einfaches Beispiel:

     > library(hash) > h < - hash( keys=c('foo','bar','baz'), values=1:3 ) > h[c('foo','bar')]  containing 2 key-value pairs. bar : 2 foo : 1 

    In Bezug auf die Benutzerfreundlichkeit ist die hash class einer Liste sehr ähnlich. Aber die performance ist für große Datensätze besser.

    Du sagst:

    Zum anderen können Listen aus functionen zurückgegeben werden, obwohl Sie beim Aufruf der function nie in einer Liste übergeben wurden, und obwohl die function keinen List-Konstruktor enthält, z.

     x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character' class(x) # => 'list' 

    Und ich schätze, Sie vermuten, dass dies ein Problem ist (?). Ich bin hier, um dir zu sagen, warum das kein Problem ist :-). Ihr Beispiel ist ein bisschen einfach, denn wenn Sie den String-Split unlist(x)[1] , haben Sie eine Liste mit Elementen, die 1 Element lang sind. Sie wissen also, dass x[[1]] dasselbe ist wie unlist(x)[1] . Was aber, wenn das Ergebnis von strsplit Ergebnisse unterschiedlicher Länge in jedem Fach strsplit Einfach einen Vektor (vs. eine Liste) zurückgeben, wird überhaupt nicht funktionieren.

    Zum Beispiel:

     stuff < - c("You, me, and dupree", "You me, and dupree", "He ran away, but not very far, and not very fast") x <- strsplit(stuff, ",") xx <- unlist(strsplit(stuff, ",")) 

    Im ersten Fall ( x : der eine Liste zurückgibt), können Sie sagen, was der 2. "Teil" der 3. Zeichenkette war, zB: x[[3]][2] . Wie könntest du dasselbe mit xx jetzt wo die Ergebnisse "entwirrt" wurden ( unlist )?

     x = list(1, 2, 3, 4) x2 = list(1:4) all.equal(x,x2) 

    ist nicht das Gleiche, weil 1: 4 dasselbe ist wie c (1,2,3,4). Wenn Sie möchten, dass sie gleich sind, dann:

     x = list(c(1,2,3,4)) x2 = list(1:4) all.equal(x,x2) 

    In Bezug auf Vektoren und das Hash / Array-Konzept aus anderen Sprachen:

    1. Vektoren sind die Atome von R. ZB sind rpois(1e4,5) (5 Zufallszahlen), numeric(55) (Länge-Nullvektor über verdoppelt) und character(12) (12 leere Zeichenfolgen), sind alle “grundlegend “.

    2. Entweder Listen oder Vektoren können names .

       > n = numeric(10) > n [1] 0 0 0 0 0 0 0 0 0 0 > names(n) NULL > names(n) = LETTERS[1:10] > n ABCDEFGHIJ 0 0 0 0 0 0 0 0 0 0 
    3. Vektoren erfordern, dass alle den gleichen Datentyp haben. Schau dir das an:

       > i = integer(5) > v = c(n,i) > v ABCDEFGHIJ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 > class(v) [1] "numeric" > i = complex(5) > v = c(n,i) > class(v) [1] "complex" > v ABCDEFGHIJ 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 
    4. Listen können verschiedene Datentypen enthalten, wie in anderen Antworten und der Frage des OP selbst zu sehen ist.

    Ich habe Sprachen gesehen (Ruby, Javascript), in denen “Arrays” variable Datentypen enthalten können, aber zum Beispiel müssen in C ++ “Arrays” alle denselben Datentyp haben. Ich glaube, das ist eine Geschwindigkeit / Effizienz-Sache: Wenn Sie eine numeric(1e6) , kennen Sie ihre Größe und die Position jedes Elements a priori ; Wenn das Ding "Flying Purple People Eaters" in einer unbekannten Scheibe enthalten könnte, dann müsst ihr tatsächlich Dinge analysieren, um grundlegende Fakten darüber zu wissen.

    Bestimmte Standard-R-Operationen sind auch sinnvoller, wenn der Typ garantiert ist. Zum Beispiel macht cumsum(1:9) Sinn, während cumsum(list(1,2,3,4,5,'a',6,7,8,9)) nicht, ohne dass der Typ garantiert doppelt ist.


    Zu Ihrer zweiten Frage:

    Listen können von functionen zurückgegeben werden, obwohl Sie sie beim Aufruf der function nie in einer Liste übergeben haben

    functionen geben unterschiedliche Datentypen zurück, als sie ständig eingegeben werden. plot gibt ein Plot zurück, obwohl es kein Plot als Eingabe benötigt. Arg gibt einen numeric Arg zurück, obwohl ein complex akzeptiert wurde. Etc.

    (Und für strsplit : der Quellcode ist hier .)

    Wenn es hilft, neige ich dazu, “Listen” in R als “Datensätze” in anderen Pre-OO-Sprachen zu konzipieren:

    • Sie treffen keine Annahmen über einen übergreifenden Typ (oder vielmehr die Art aller möglichen Datensätze von Arity- und Feldnamen).
    • ihre Felder können anonym sein (dann greifen Sie auf sie nach strenger Definitionsreihenfolge zu).

    Der Name “record” würde mit der Standardbedeutung von “records” (aka rows) im database-Sprachgebrauch kollidieren, und deshalb könnte sich ihr Name als Listen (von Feldern) anbieten.

    Obwohl dies eine ziemlich alte Frage ist, muss ich sagen, dass sie genau das Wissen berührt, das ich während meiner ersten Schritte in R vermisste – dh wie ich Daten in meiner Hand als Objekt in R express kann oder wie ich aus vorhandenen Objekten auswählen kann. Ein R-Neuling denkt nicht von Anfang an “in einer R-Box”.

    Also habe ich selbst angefangen, unten Krücken zu benutzen, die mir sehr geholfen haben, herauszufinden, welches Objekt für welche Daten verwendet werden sollte, und sich im Grunde den realen Gebrauch vorzustellen.

    Obwohl ich keine genauen Antworten auf die Frage gebe, könnte der folgende kurze Text dem Leser helfen, der gerade mit R angefangen hat und ähnliche Fragen stellt.

    • Atomvektor … Ich nannte diese “Sequenz” für mich selbst, keine Richtung, nur eine Sequenz von gleichen Typen. [ Teilmengen.
    • Vector … Sequenz mit einer Richtung von 2D, [ Teilmengen.
    • Matrix … Bündel von Vektoren mit gleicher Länge, die Zeilen oder Spalten bilden, [ Teilmengen nach Zeilen und Spalten oder nach Sequenz.
    • Arrays … geschichtete Matrizen bilden 3D
    • Dataframe … eine 2D-Tabelle wie in Excel, in der ich Zeilen, Spalten oder Zeilen sortieren, hinzufügen oder entfernen kann. Operationen mit ihnen, erst nach einiger Zeit erkannte ich wirklich, dass Dataframe ist eine clevere Implementierung der list wo ich Teilmenge mit [ durch Zeilen und Spalten, sondern auch mit [[ .
    • Liste … um mir selbst zu helfen, dachte ich über Liste als tree structure wobei [i] ganze Zweige auswählt und zurückgibt und [[i]] das Element aus der Verzweigung zurückgibt. Und weil es eine tree like structure , können Sie sogar eine index sequence , um jedes einzelne Blatt einer sehr komplexen list mit seinem [[index_vector]] . Listen können einfach oder sehr komplex sein und verschiedene Arten von Objekten zu einem zusammenfassen.

    Für lists Sie also mehr Möglichkeiten haben, ein leaf abhängig von der Situation auszuwählen, wie im folgenden Beispiel.

     l < - list("aaa",5,list(1:3),LETTERS[1:4],matrix(1:9,3,3)) l[[c(5,4)]] # selects 4 from matrix using [[index_vector]] in list l[[5]][4] # selects 4 from matrix using sequential index in matrix l[[5]][1,2] # selects 4 from matrix using row and column in matrix 

    Diese Art zu denken hat mir sehr geholfen.

    Warum geben diese beiden verschiedenen Operatoren [ ] und [[ ]] dasselbe Ergebnis zurück?

     x = list(1, 2, 3, 4) 
    1. [ ] bietet Untereinstellungen. Im Allgemeinen wird die Untergruppe eines Objekts vom selben Typ wie das ursprüngliche Objekt sein. Daher stellt x[1] eine Liste bereit. Ähnlich ist x[1:2] eine Teilmenge der ursprünglichen Liste, daher ist es eine Liste. Ex.

       x[1:2] [[1]] [1] 1 [[2]] [1] 2 
    2. [[ ]] dient zum Extrahieren eines Elements aus der Liste. x[[1]] ist gültig und extrahiert das erste Element aus der Liste. x[[1:2]] ist nicht gültig, da [[ ]] keine Untereinstellung wie [ ] vorsieht.

        x[[2]] [1] 2 > x[[2:3]] Error in x[[2:3]] : subscript out of bounds