wake-up-neo.com

Wie benutze ich Listen in R richtig?

Kurzer Hintergrund: Viele (die meisten?) Gängigen Programmiersprachen haben zumindest eine Handvoll ADTs [abstrakte Datentypen] gemeinsam, insbesondere

  • string (eine Folge von Zeichen)

  • Liste (eine geordnete Sammlung von Werten) und

  • Kartenbasierter Typ (ein ungeordnetes Array, das Schlüssel zu Werten zuordnet)

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 Elternklasse für R ist data.frame), Und zweitens: Ich konnte einfach nicht verstehen, wie sie funktionierten, zumindest nicht gut genug, um sie korrekt in meinem Code zu verwenden.

Zum einen schien es mir, dass der Datentyp von R list eine unkomplizierte Implementierung des Map-ADT war (dictionary in Python, NSMutableDictionary in Objective C, hash in Perl und Ruby, object literal in Javascript und so weiter).

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

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

Und Sie greifen auf die Elemente einer R-Liste zu, genau wie auf die eines Python= Dictionary, z. B. x['ev1']. Ebenso können Sie nur die '- Schlüssel abrufen ' oder nur die ' Werte ' von:

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 s sind auch im Gegensatz zu andere ADTs vom Kartentyp (aus den Sprachen, die ich sowieso gelernt habe). Ich vermute, dass dies eine Folge der anfänglichen Spezifikation für S ist, d. H. Eine Absicht, eine Daten-/Statistik-DSL [domänenspezifische Sprache] von Grund auf zu entwerfen.

drei signifikante Unterschiede zwischen R list und Mapping-Typen in anderen weit verbreiteten Sprachen (z. B. Python, Perl, JavaScript):

first, list s in R sind eine order Auflistung, genau wie Vektoren, auch wenn die Werte verschlüsselt sind (dh die Schlüssel können ein beliebiger Hashwert sein (nicht nur sequentielle ganze Zahlen). Fast immer ist der Mapping-Datentyp in anderen Sprachen ungeordnet.

second, list s können von Funktionen zurückgegeben werden, obwohl Sie beim Aufruf der Funktion nie ein list übergeben haben, und obwohl Die Funktion, die die list zurückgegeben hat, enthält keinen (expliziten) list - Konstruktor 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

A Drittel Besonderes Merkmal von Rs list: Es scheint nicht, dass sie Mitglieder eines anderen ADT sein können, und wenn Sie dies versuchen, wird der primäre Container gezwungen zu einem list. 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 ist es hier nicht, die Sprache zu kritisieren oder wie sie dokumentiert ist; Ebenso schlage ich nicht vor, dass irgendetwas an der list - Datenstruktur oder deren Verhalten falsch ist. Alles, was ich korrigieren möchte, ist, dass ich verstehe, wie sie funktionieren, damit ich sie richtig in meinem Code verwenden kann.

Hier sind die Dinge, die ich besser verstehen möchte:

  • Welche Regeln legen fest, wann ein Funktionsaufruf einen list - Ausdruck zurückgibt (z. B. den oben angegebenen strsplit - Ausdruck)?

  • Wenn ich einem list keine expliziten Namen zugebe (z. B. list(10,20,30,40)), sind die Standardnamen nur fortlaufende Ganzzahlen, die mit 1 beginnen? (Ich gehe davon aus, aber ich bin mir nicht sicher, ob die Antwort Ja lautet, da wir diese Art von list andernfalls nicht zu einem Vektor mit einem Aufruf von unlist zwingen könnten.)

  • Warum geben diese beiden unterschiedlichen Operatoren [] Und [[]] Das Ergebnis same 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 ​​dasselbe Ergebnis zurück?

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

    x2 = list(1:4)

Bitte verweisen Sie mich nicht auf die R-Dokumentation ( ?list , R-intro ) - Ich habe es sorgfältig gelesen und tut es nicht Helfen Sie mir, die oben genannten Fragen zu beantworten.

(Zuletzt habe ich von einem R-Paket (verfügbar auf CRAN) mit dem Namen hash erfahren und begonnen, das konventionell kartentypisches Verhalten über ein implementiert S4 Klasse; Ich kann dieses Paket auf jeden Fall empfehlen.)

303
doug

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

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

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

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

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

Das sind also völlig andere Listen.

R-Listen ähneln stark der Datenstruktur einer Hash-Map , da jeder Indexwert einem beliebigen Objekt zugeordnet werden kann. Hier ist ein einfaches Beispiel einer Liste, die 3 verschiedene Klassen enthält (einschließlich einer Funktion):

> 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" ...

Zum Schluss noch eine Bemerkung dazu: Es ist anzumerken, dass ein data.frame ist wirklich eine Liste (aus dem data.frame Dokumentation):

Ein Datenrahmen ist eine Liste von Variablen mit der gleichen Anzahl von Zeilen mit eindeutigen Zeilennamen für die Klasse "data.frame".

Deshalb Spalten in einem data.frame kann unterschiedliche Datentypen haben, Spalten in einer Matrix jedoch nicht. 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
 a   b  
[1,] "1" "a"
[2,] "2" "b"
[3,] "3" "c"
[4,] "4" "d"
> class(d[,1])
[1] "character"

Beachten Sie, wie 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"
139
Shane

Lassen Sie mich Ihre Fragen der Reihe nach beantworten und einige Beispiele nennen:

1) Eine Liste wird zurückgegeben, wenn die return-Anweisung 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 werden einfach nicht gesetzt:

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

) Sie geben nicht dasselbe zurück. Dein Beispiel gibt

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

wo x[1] gibt das erste Element von x zurück, das mit x identisch ist. Jeder Skalar ist ein Vektor der Länge eins. Auf der anderen Seite x[[1]] gibt das erste Element der Liste zurück.

4) Schließlich unterscheiden sich die beiden Elemente darin, dass sie jeweils eine Liste mit vier Skalaren und eine Liste mit einem einzelnen Element (das ist zufällig ein Vektor aus vier Elementen) erstellen.

61

Nur um einen Teil Ihrer Fragen zu beantworten:

Dieser Artikel behandelt bei der Indizierung 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)' item 1 eine einzelne Ganzzahl, aber x[[1]] Gibt eine einzelne 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
34
JD Long

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

Warum geben diese beiden Ausdrücke nicht dasselbe 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:

x3 = as.list(1:4)

Was zwingt den Vektor 1:4 in eine Liste.

13
Alex Brown

Um dem noch einen Punkt hinzuzufügen:

R hat eine Datenstruktur, die dem Python - Dikt in dem hash - Paket entspricht. Sie können darüber in diesem Blogbeitrag aus 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')]
<hash> containing 2 key-value pairs.
  bar : 2
  foo : 1

In Bezug auf die Benutzerfreundlichkeit ist die Klasse hash einer Liste sehr ähnlich. Bei großen Datenmengen ist die Leistung jedoch besser.

11
Shane

Du sagst:

Zum anderen können Listen von Funktionen zurückgegeben werden, obwohl Sie beim Aufrufen der Funktion nie eine Liste übergeben haben und obwohl die Funktion keinen List-Konstruktor enthält, z.

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

Und ich denke, Sie schlagen vor, dass dies ein Problem ist (?). Ich bin hier, um dir zu sagen, warum es kein Problem ist :-). Ihr Beispiel ist ein bisschen einfach: Wenn Sie die Zeichenfolge aufteilen, haben Sie eine Liste mit Elementen, die 1 Element lang sind, sodass Sie wissen, dass x[[1]] Dasselbe wie unlist(x)[1] ist. . Was aber, wenn das Ergebnis von strsplit in jedem Bin Ergebnisse unterschiedlicher Länge zurückgibt? Einfach einen Vektor (gegen eine Liste) zurückzugeben, reicht überhaupt nicht aus.

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: Gibt eine Liste zurück) können Sie den 2. "Teil" der 3. Zeichenfolge ermitteln, z. B .: x[[3]][2]. Wie können Sie das gleiche mit xx tun, nachdem die Ergebnisse "entwirrt" wurden (unlist- bearbeitet)?

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

ist nicht dasselbe, 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)
5
JeremyS

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

  • sie machen keine Annahmen über einen übergreifenden Typ (oder vielmehr den Typ aller möglichen Datensätze, für die Aritäts- und Feldnamen verfügbar sind).
  • ihre Felder können anonym sein (dann greifen Sie in strikter Reihenfolge der Definitionen auf sie zu).

Der Name "Datensatz" würde mit der Standardbedeutung von "Datensätzen" (auch Zeilen genannt) in der Datenbanksprache in Konflikt geraten, und möglicherweise ist dies der Grund, warum sich ihr Name selbst vorgeschlagen hat: als Listen (von Feldern).

warum tun diese beiden verschiedenen Operatoren, [ ], und [[ ]], das gleiche Ergebnis zurückgeben?

x = list(1, 2, 3, 4)
  1. [ ] bietet Sub-Setting-Betrieb. Im Allgemeinen hat die Untergruppe eines Objekts den gleichen Typ wie das ursprüngliche Objekt. Deshalb, x[1] liefert eine Liste. Ähnlich x[1:2] ist 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 als [[ ]] bietet keine Untereinstellung wie [ ].

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

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

  1. Vektoren sind die Atome von R. ZB rpois(1e4,5) (5 Zufallszahlen), numeric(55) (Länge-55-Vektor über Doppelte) und character(12) (12 leer Strings) sind alle "basic".

  2. Entweder Listen oder Vektoren können names haben.

    > n = numeric(10)
    > n
     [1] 0 0 0 0 0 0 0 0 0 0
    > names(n)
    NULL
    > names(n) = LETTERS[1:10]
    > n
    A B C D E F G H I J 
    0 0 0 0 0 0 0 0 0 0
    
  3. Vektoren setzen voraus, dass alle Daten vom selben Datentyp sind. Schau dir das an:

    > i = integer(5)
    > v = c(n,i)
    > v
    A B C D E F G H I J           
    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
       A    B    C    D    E    F    G    H    I    J                          
    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 unterschiedliche Datentypen enthalten, wie in anderen Antworten und der Frage des OP selbst zu sehen ist.

Ich habe Sprachen (Ruby, Javascript) gesehen, in denen "Arrays" variable Datentypen enthalten können, aber zum Beispiel in C++ müssen "Arrays" alle denselben Datentyp haben. Ich glaube, das ist eine Sache mit Geschwindigkeit und Effizienz: Wenn Sie eine numeric(1e6) haben, kennen Sie die Größe und die Position jedes Elements a priori ; Wenn das Ding "Flying Purple People Eaters" in einem unbekannten Slice enthalten könnte, müssen Sie die Daten analysieren, um grundlegende Fakten darüber zu erfahren.

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


Zu Ihrer zweiten Frage:

Listen können von Funktionen zurückgegeben werden, obwohl Sie beim Aufrufen der Funktion nie eine Liste übergeben haben

Funktionen geben andere Datentypen zurück, als sie ständig eingegeben werden. plot gibt einen Plot zurück, obwohl kein Plot als Eingabe verwendet wird. Arg gibt ein numeric zurück, obwohl es ein complex akzeptiert hat. Usw.

(Und was strsplit betrifft: Der Quellcode ist hier .)

1
isomorphismes

Obwohl dies eine ziemlich alte Frage ist, muss ich sagen, dass sie genau das Wissen berührt, das ich bei meinen ersten Schritten in R vermisst habe - d. H. Wie ich Daten in meiner Hand als Objekt in R ausdrücken oder aus vorhandenen Objekten auswählen kann. Es ist für einen R-Neuling nicht einfach, von Anfang an "in einer R-Box" zu denken.

Also habe ich selbst angefangen, Krücken zu verwenden, die mir sehr geholfen haben, herauszufinden, welches Objekt für welche Daten verwendet werden soll, und mir im Grunde die Verwendung in der realen Welt vorzustellen.

Obwohl ich die Frage nicht genau beantworte, kann der folgende kurze Text dem Leser helfen, der gerade mit R begonnen hat und ähnliche Fragen stellt.

  • Atomvektor ... Ich habe diese "Sequenz" für mich selbst genannt, keine Richtung, nur Sequenz gleicher Typen. [ Teilmengen.
  • Vektor ... Sequenz mit einer Richtung aus 2D, [ Teilmengen.
  • Matrix ... Bündel von Vektoren gleicher Länge, die Zeilen oder Spalten bilden, [ Teilmengen nach Zeilen und Spalten oder nach Reihenfolge.
  • Arrays ... geschichtete Matrizen, die 3D bilden
  • Datenrahmen ... eine 2D-Tabelle wie in Excel, in der ich Zeilen oder Spalten sortieren, hinzufügen oder entfernen oder Arit erstellen kann. Operationen mit ihnen, erst nach einiger Zeit erkannte ich wirklich, dass DataFrame eine clevere Implementierung von list ist, wobei ich mit [ Zeilen und Spalten, aber auch mit [[ unterteilen kann.
  • Liste ... um mir selbst zu helfen Ich dachte über Liste ab tree structure Nach, wobei [i] Ganze Zweige auswählt und zurückgibt und [[i]] Artikel aus dem Zweig zurückgibt. Und da es sich um tree like structure Handelt, können Sie sogar ein index sequence Verwenden, um jedes einzelne Blatt eines sehr komplexen list mit seinem [[index_vector]] Zu adressieren. Listen können einfach oder sehr komplex sein und verschiedene Objekttypen zu einem zusammenfassen.

Für lists haben Sie also mehr Möglichkeiten, wie Sie je nach Situation ein leaf auswählen können, wie im folgenden Beispiel dargestellt.

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 Denkweise hat mir sehr geholfen.

1
Petr Matousu