2010-01-12 19 views
276

Uno sguardo al contesto: Molti (? La maggior parte) dei linguaggi di programmazione contemporanei in uso diffuso hanno almeno una manciata di ADT [tipi di dati astratti] in comune, in particolare,Come utilizzare correttamente gli elenchi in R?

  • stringa (una sequenza composta da personaggi)

  • lista (un insieme ordinato di valori), e

  • mappa basata tipo (un array non ordinato che mappa le chiavi a valori)

Nel linguaggio di programmazione R, i primi due sono implementate rispettivamente character e vector,.

Quando ho iniziato a imparare R, due cose erano evidenti quasi dall'inizio: list è il più importante tipo di dati in R (perché è la classe padre per la R data.frame), e in secondo luogo, io non riuscivo a capire come hanno funzionato, almeno non abbastanza bene da usarli correttamente nel mio codice.

Per prima cosa, è sembrato a me che tipo di dati list di R era un'implementazione semplice della mappa ADT (dictionary in Python, NSMutableDictionary in Objective C, hash in Perl e Ruby, object literal in Javascript, e così via).

Per esempio, si creano loro solo come se fosse un dizionario Python, passando coppie chiave-valore a un costruttore (che in Python è dict non list):

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

E si accede ai capi di un elenco R proprio come faresti con quelli di un dizionario Python, ad es. x['ev1']. Allo stesso modo, è possibile recuperare solo le 'chiavi' o semplicemente i 'valori' da :

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 

ma R list s sono anche differenza altri ADT mappa di tipo (tra le lingue Ho imparato comunque). La mia ipotesi è che questa sia una conseguenza delle specifiche iniziali per S, cioè l'intenzione di progettare un DSL di dati/statistiche [linguaggio specifico per il dominio] da zero.

tre differenze significative tra R list s ed i tipi di mappatura in altre lingue in uso diffuso (ad esempio ,. Python, Perl, JavaScript):

primi, list s in R sono un ordinato collezione, proprio come i vettori, anche se i valori sono codificati (cioè, le chiavi possono essere qualsiasi valore hastable non solo interi sequenziali). Quasi sempre, il tipo di dati di mappatura in altre lingue è non ordinato.

seconda, list s può essere restituito dalle funzioni anche se mai passato in un list quando hai chiamato la funzione, e anche se la funzione che ha restituito il list non contiene un (esplicito) list costruttore (Naturalmente, si può fare con questo, in pratica, avvolgendo il risultato restituito in una chiamata a 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 

una terza caratteristica peculiare della list s di R : è d Non sembra che possano essere membri di un altro ADT e, se provi a farlo, il contenitore primario viene forzato a list. Per esempio,

x = c(0.5, 0.8, 0.23, list(0.5, 0.2, 0.9), recursive=TRUE) 

class(x) 
# [1] list 

mia intenzione qui non è criticare la lingua o come viene documentato; allo stesso modo, non sto suggerendo che ci sia qualcosa di sbagliato nella struttura dei dati list o come si comporta. Tutto quello che voglio è correggere la mia comprensione di come funzionano, così posso usarli correttamente nel mio codice.

Ecco il genere di cose che vorrei capire meglio:

  • Quali sono le regole che determinano quando una chiamata di funzione restituirà un list (ad esempio, strsplit espressione recitato sopra)?

  • Se non assegnare esplicitamente nomi a un list (ad esempio, list(10,20,30,40)) sono i nomi predefiniti solo interi sequenziali iniziando con 1? (Presumo, ma sono tutt'altro che certo che la risposta è sì, altrimenti non saremmo in grado di forzare questo tipo di list ad un vettore w/una chiamata a unlist.)

  • Perché questi due diversi operatori, [] e [[]], restituire lo stesso risultato?

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

    entrambe le espressioni restituiscono "1":

    x[1]

    x[[1]]

  • Perché queste due espressioni non ritorno lo stesso risultato?

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

    x2 = list(1:4)

Per favore non mi puntano alla documentazione R (?list, R-intro) - Ho letto con attenzione e non aiutarmi a rispondere il tipo di domande Ho recitato poco sopra.

(da ultimo, di recente ho appreso di e cominciò con un pacchetto R (disponibile sul CRAN) chiamato hash che implementa convenzionale comportamento mappa di tipo tramite una classe S4, posso sicuramente consigliare questo pacchetto.)

+1

Con 'x = list (1, 2, 3, 4)', entrambi non restituiscono lo stesso risultato: 'x [1]' e 'x [[1]]'. Il primo restituisce una lista e la seconda restituisce un vettore numerico. Scorrendo sotto, mi sembra che Dirk sia stato l'unico a rispondere correttamente a questa domanda. –

+1

Non ho notato nessuno espandere il tuo elenco di modi in cui 'lista' in R non è come un hash. Ne ho un altro che penso sia degno di nota. 'list' in R può avere due membri con lo stesso nome di riferimento. Considera che 'obj <- c (list (a = 1), list (a = 2))' è valido e restituisce una lista con due valori nominati di 'a'. In questo caso, una chiamata per 'obj [" a "]' restituirà solo il primo elemento della lista corrispondente. È possibile ottenere un comportamento simile (forse identico) a un hash con un solo elemento per i nomi di riferimento che utilizzano ambienti in R., ad es. 'x <- new.env(); x [["a"]] <- 1; x [["a"]] <- 2; x [["a"]] ' – russellpierce

risposta

127

Proprio per affrontare l'ultima parte della sua domanda, dal momento che i punti davvero fuori la differenza tra un list e vector in R:

Perché queste due espressioni non restituiscono lo stesso risultato?

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

Un elenco può contenere qualsiasi altra classe come ciascun elemento. Quindi puoi avere una lista in cui il primo elemento è un vettore di caratteri, il secondo è un frame di dati, ecc. In questo caso, hai creato due elenchi diversi. x ha quattro vettori, ciascuna di lunghezza 1. x2 ha 1 vettore di lunghezza 4:

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

Quindi questi sono completamente diverse liste.

elenchi R sono molto simili a hash map struttura dati dal fatto che ciascun valore di indice può essere associato a qualsiasi oggetto.Ecco un semplice esempio di un elenco che contiene 3 classi diverse (tra cui una funzione):

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

Dato che l'ultimo elemento è la funzione di ricerca, che posso chiamare in questo modo:

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

Come commento finale sul: va notato che una data.frame è davvero una lista (dalla documentazione data.frame):

un frame di dati è una lista di variabili dello stesso numero di righe con uniq nomi di riga ue, data classe ‘ 'data.frame'’

Ecco perché le colonne di un data.frame possono avere diversi tipi di dati, mentre le colonne in una matrice non può. Come esempio, qui tento di creare una matrice con numeri e caratteri:

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

Nota come non posso cambiare il tipo di dati nella prima colonna a numerico perché la seconda colonna ha caratteri:

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

Questo aiuta, grazie. (A proposito, il tuo esempio re 'lista complicata', come forse già saprai, è il modo standard per replicare l'istruzione 'switch' in C++, Java, ecc. In lingue che non ne hanno uno, probabilmente un buon modo per fare questo in R quando ho bisogno di). +1 – doug

+6

Esatto, anche se esiste una utile funzione 'switch' in R che può essere utilizzata a tale scopo (si veda' help (switch) '). – Shane

55

Per quanto riguarda le tue domande, lascia che le indirizzi in ordine e fornisca alcuni esempi:

) Un elenco viene restituito se e quando l'istruzione di reso ne aggiunge uno. Considerare

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> 

) I nomi non sono semplicemente impostati:

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

) non restituiscono la stessa cosa. Il vostro esempio dà

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

dove x[1] restituisce il primo elemento del x - che è lo stesso di x. Ogni scalare è un vettore di lunghezza uno. D'altra parte x[[1]] restituisce il primo elemento della lista.

) Infine, i due sono diversi tra creano, rispettivamente, un elenco contenente quattro scalari e una lista con un unico elemento (che sembra essere un vettore di quattro elementi).

+1

Molto utile, grazie. (Nell'articolo 1 della tua risposta - sono d'accordo, ma quello che avevo in mente era built-in come "strsplit", non funzioni create dall'utente). In ogni caso, +1 da me. – doug

+1

@doug Informazioni sull'articolo 1 Penso che l'unico modo sia di controllare l'aiuto per una funzione specifica, sezione "Valore". Come in '? Strsplit':" Un elenco della stessa lunghezza di x ". Ma dovresti considerare che ci può essere una funzione per restituire valori diversi dipendenti da argomenti (ad esempio, sapply può restituire lista o vettore). – Marek

33

solo per prendere un sottoinsieme delle vostre domande:

This article sulla indicizzazione affronta la questione della differenza tra [] e [[]].

In breve [[]] seleziona un singolo elemento da un elenco e [] restituisce un elenco degli elementi selezionati. Nell'esempio, x = list(1, 2, 3, 4)' l'elemento 1 è un numero intero singolo ma x[[1]] restituisce un singolo 1 e x[1] restituisce un elenco con un solo valore.

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

> x[[1]] 
[1] 1 
+0

A proposito, 'A = array (11:16, c (2,3)); A [5] 'è 15, nella matrice * flat *?! – denis

11

Un elenco di motivi funziona come fa (ordinato) per rispondere all'esigenza di un contenitore ordinato che possa contenere qualsiasi tipo su qualsiasi nodo, che i vettori non fanno. Le liste sono riutilizzate per una varietà di scopi in R, tra cui la formazione della base di un data.frame, che è un elenco di vettori di tipo arbitrario (ma della stessa lunghezza).

Perché queste due espressioni non restituiscono lo stesso risultato?

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

Per aggiungere a @ risposta di Shane, se si voleva ottenere lo stesso risultato, provare:

x3 = as.list(1:4) 

che converte il vettore 1:4 in una lista.

8

Tu dici:

Per un altro, le liste possono essere restituiti dalle funzioni anche se non hai mai passati come lista quando hai chiamato la funzione , e anche se la funzione non contiene un lista costruttore, esempio,

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

E immagino suggerisci che questo è un problema (?). Sono qui per dirti perché non è un problema :-). Il tuo esempio è un po 'semplice, in quanto quando fai lo split-split, hai una lista con elementi lunghi 1 elemento, quindi sai che x[[1]] è lo stesso di unlist(x)[1]. Ma cosa succede se il risultato di strsplit ha restituito risultati di diversa lunghezza in ciascun contenitore. Il semplice ritorno di un vettore (rispetto a un elenco) non lo farà affatto.

Per esempio:

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, ",")) 

Nel primo caso (x: che restituisce una lista), si può dire quello che la "parte" 2 ° del 3 ° stringa di stato, ad esempio: x[[3]][2].Come hai potuto fare lo stesso usando xx ora che i risultati sono stati "svelati" (unlist -ed)?

11

solo per aggiungere un punto in più a questo:

R ha una struttura di dati equivalente al dict Python in the hash package. Puoi leggere a riguardo in this blog post from the Open Data Group. Ecco un semplice esempio:

> 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 termini di usabilità, la classe hash è molto simile a un elenco. Ma le prestazioni sono migliori per i set di dati di grandi dimensioni.

+1

Sono a conoscenza del pacchetto hash - è menzionato nella mia domanda originale come proxy adatto per il tipo di hash tradizionale. – doug

+0

Si noti inoltre che l'uso di hash :: hash è di utilità discutibile relativa agli ambienti con hash, https://rpubs.com/rpierce/hashBenchmarks. – russellpierce

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

non è lo stesso perché 1: 4 è uguale a c (1,2,3,4). Se si desidera che siano gli stessi allora:

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

Per quanto riguarda i vettori e il concetto di hash/array da altre lingue:

  1. vettori sono gli atomi di R. Ad esempio, rpois(1e4,5) (5 casuale numeri), numeric(55) (lunghezza-55 zero vector over double) e character(12) (12 stringhe vuote), sono tutti "di base".

  2. Gli elenchi oi vettori possono avere 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 
    A B C D E F G H I J 
    0 0 0 0 0 0 0 0 0 0 
    
  3. I vettori richiedono che tutto sia dello stesso tipo di dati. Guarda questo:

    > 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. Le liste possono contenere diversi tipi di dati, come si è visto in altre risposte e domanda del PO stesso.

lingue che ho visto (rubino, javascript), in cui le "matrici" possono contenere tipi di dati variabili, ma per esempio in C++ "matrici" devono essere tutti uguali tipo di dati. Credo che questa sia una cosa di velocità/efficienza: se si dispone di un numeric(1e6) si conosce la sua dimensione e la posizione di ogni elemento a priori; se la cosa potrebbe contenere "Flying Purple People Eaters" in una porzione sconosciuta, è necessario analizzare effettivamente le informazioni di base su di essa.

Alcune operazioni R standard hanno anche più senso quando il tipo è garantito. Ad esempio, cumsum(1:9) ha senso mentre lo cumsum(list(1,2,3,4,5,'a',6,7,8,9)) non lo fa, senza che il tipo sia garantito come doppio.


Per quanto riguarda la seconda domanda:

Le liste possono essere restituiti dalle funzioni anche se non hai mai passato in un elenco quando hai chiamato la funzione

funzioni restituiscono tipi di dati diversi rispetto vengono inseriti sempre. plot restituisce un grafico anche se non richiede una trama come input. Arg restituisce un numeric anche se ha accettato un complex. Eccetera.

(E per quanto riguarda strsplit: il codice sorgente è here.)

1

Se aiuta, tendo a concepire "liste" in R come "record" in altre lingue pre-OO:

  • non fanno ipotesi su un tipo di overarching (o piuttosto sul tipo di tutti i record possibili di qualsiasi nome di campo e aritmetica sono disponibili).
  • i loro campi possono essere anonimi (quindi li si accede per ordine di definizione rigoroso).

Il nome "record" si scontrerebbe con il significato standard di "record" (cioè righe) nel linguaggio di database, e potrebbe essere questo il motivo per cui il loro nome si è suggerito: come elenchi (di campi).

0

perché questi due operatori diversi, [ ] e [[ ]], restituiscono lo stesso risultato?

x = list(1, 2, 3, 4) 
  1. [ ] fornisce operazione di impostazione sub. In generale sottoinsieme di qualsiasi oggetto avrà lo stesso tipo dell'oggetto originale. Pertanto, x[1] fornisce un elenco. Allo stesso modo x[1:2] è un sottoinsieme dell'elenco originale, quindi è un elenco. Ex.

    x[1:2] 
    
    [[1]] [1] 1 
    
    [[2]] [1] 2 
    
  2. [[ ]] è per l'estrazione di un elemento dalla lista. x[[1]] è valido ed estrae il primo elemento dall'elenco. x[[1:2]] non è valido come [[ ]] non fornisce un'impostazione secondaria come [ ].

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

Alhough questo è abbastanza vecchia domanda devo dire che è a contatto con esattamente la conoscenza che mi mancava durante i miei primi passi nel campo della R - vale a dire come esprimere i dati in mano come oggetto in R o come seleziona da oggetti esistenti. Sin dal principio, non è facile per un novizio di R pensare "in una scatola R".

Così ho iniziato a utilizzare le stampelle in basso, il che mi ha aiutato molto a scoprire quale oggetto utilizzare per quali dati e in sostanza a immaginare l'utilizzo del mondo reale.

Sebbene non fornisca risposte esatte alla domanda, il breve testo qui sotto potrebbe aiutare chi ha appena iniziato con R e sta facendo domande simili.

  • Vettore atomico ... Ho chiamato quella "sequenza" per me stesso, nessuna direzione, solo la sequenza degli stessi tipi. [ sottoinsiemi.
  • Vettore ... sequenza con una direzione da 2D, [ sottoinsiemi.
  • Matrice ... gruppo di vettori con la stessa lunghezza che forma righe o colonne, [ sottoinsiemi per righe e colonne o per sequenza.
  • Matrici ... matrici a strati che formano 3D
  • Dataframe ... una tabella 2D come in Excel, dove posso ordinare, aggiungere o rimuovere righe o colonne o fare arit.operazioni con loro, solo dopo un po 'ho veramente riconosciuto che dataframe è un'implementazione intelligente di list in cui è possibile impostare sotto [ per righe e colonne, ma utilizzando anche [[.
  • Elenco ... per aiutare me stesso ho pensato di elencare come da tree structure dove [i] seleziona e restituisce interi rami e [[i]] restituisce oggetto dal ramo. E poiché è tree like structure, puoi anche usare un index sequence per indirizzare ogni singola foglia su un complesso list usando il suo [[index_vector]]. Le liste possono essere semplici o molto complesse e possono unire insieme vari tipi di oggetti in uno solo.

Quindi per lists si può finire con più modi come selezionare un leaf a seconda della situazione, come nel seguente esempio.

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 

Questo modo di pensare mi ha aiutato molto.

Problemi correlati