web-dev-qa-db-fra.com

Comment utiliser correctement les listes dans R?

Bref arrière-plan: de nombreux langages de programmation contemporains largement utilisés ont au moins quelques ADT [types de données abstraits] en commun, en particulier,

  • string (une séquence composée de caractères)

  • list (une collection ordonnée de valeurs), et

  • type basé sur une carte (un tableau non ordonné qui mappe les clés sur des valeurs)

Dans le langage de programmation R, les deux premiers sont implémentés sous la forme character et vector, respectivement.

Quand j'ai commencé à apprendre R, deux choses étaient évidentes presque dès le début: list est le type de données le plus important dans R (car c'est la classe parente pour R data.frame), et deuxièmement, je ne comprenais tout simplement pas comment elles fonctionnaient du moins pas assez pour les utiliser correctement dans mon code.

D'une part, il m'a semblé que le type de données list de R était une implémentation simple de la carte ADT (dictionary en Python, NSMutableDictionary en Objective C, hash en Perl et Ruby, object literal en Javascript, etc.).

Par exemple, vous les créez comme un dictionnaire Python, en transmettant des paires clé-valeur à un constructeur (qui, dans Python, est dict et non list):

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

Et vous accédez aux éléments d’une liste R comme vous le feriez à ceux d’un dictionnaire Python, par exemple, x['ev1']. De même, vous pouvez récupérer uniquement les 'clés' ou uniquement les 'valeurs' en:

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

mais R lists sont aussi contrairement à autres types de données cartographiques (parmi les langues que j'ai apprises) en tous cas). J’imagine que c’est une conséquence de la spécification initiale pour S, c’est-à-dire une intention de concevoir un DSL de données/statistiques [langage spécifique au domaine] à partir de la base.

trois différences significatives entre R lists et les types de mappage dans d'autres langues d'usage courant (par exemple, Python, Perl, JavaScript):

en premier , lists dans R sont une collection ordonnée , tout comme les vecteurs, même si les valeurs sont entrées (les clés peuvent toute valeur hashable, pas seulement les entiers séquentiels). Presque toujours, le type de données de mappage dans les autres langues est non ordonné .

second , lists peut être renvoyé par des fonctions même si vous n'avez jamais passé un list lorsque vous avez appelé la fonction, et même si la La fonction qui a retourné le list ne contient pas de constructeur (explicite) list (Bien entendu, vous pouvez gérer cela en pratique en encapsulant le résultat renvoyé dans un appel à 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

Une troisième caractéristique particulière des lists de R: il ne semble pas qu'ils puissent être membres d'un autre ADT, et si vous essayez de le faire, le conteneur principal est contraint à un list . Par exemple.,

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

class(x)
# [1] list

mon intention ici n'est pas de critiquer le langage ou la manière dont il est documenté; De même, je ne dis pas qu'il y a un problème avec la structure de données list ou son comportement. Tout ce que je veux, c’est corriger, c’est ma compréhension de leur fonctionnement afin que je puisse les utiliser correctement dans mon code.

Voici le genre de choses que je voudrais mieux comprendre:

  • Quelles sont les règles qui déterminent quand un appel de fonction retournera une expression list (par exemple, l'expression strsplit citée ci-dessus)?

  • Si je n'attribue pas explicitement de noms à un list (par exemple, list(10,20,30,40)), les noms par défaut sont-ils uniquement des entiers séquentiels commençant par 1? (Je suppose, mais je suis loin d'être certain que la réponse est oui, sinon nous ne pourrions pas contraindre ce type de list à un vecteur avec un appel à unlist.)

  • Pourquoi ces deux opérateurs différents, [] et [[]], renvoient-ils le résultat identique ?

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

    les deux expressions retournent "1":

    x[1]

    x[[1]]

  • pourquoi ces deux expressions non renvoient-elles le même résultat?

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

    x2 = list(1:4)

Merci de ne pas me diriger vers la documentation R ( ?list , R-intro ) - je l'ai lu attentivement et cela ne m'aide pas à répondre au type de questions que je viens de réciter au dessus de.

(Enfin, j'ai récemment découvert et commencé à utiliser un package R (disponible sur CRAN) appelé hash qui implémente le comportement conventionnel de type carte via une classe S4 ; Je peux certainement recommander ce forfait.)

303
doug

Juste pour répondre à la dernière partie de votre question, car cela montre bien la différence entre un list et vector dans R:

Pourquoi ces deux expressions ne donnent-elles pas le même résultat?

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

Une liste peut contenir n'importe quelle autre classe en tant qu'élément. Vous pouvez donc avoir une liste dont le premier élément est un vecteur de caractères, le deuxième un bloc de données, etc. Dans ce cas, vous avez créé deux listes différentes. x a quatre vecteurs, chacun de longueur 1. x2 a 1 vecteur de longueur 4:

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

Donc, ce sont des listes complètement différentes.

Les listes R ressemblent beaucoup à ne carte de hachage en ce que chaque valeur d'index peut être associée à n'importe quel objet. Voici un exemple simple de liste contenant 3 classes différentes (y compris une fonction):

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

Étant donné que le dernier élément est la fonction de recherche, je peux l'appeler comme suit:

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

Pour terminer, il faut noter qu'un data.frame est vraiment une liste (de la documentation data.frame):

Une trame de données est une liste de variables du même nombre de lignes avec des noms de ligne uniques, dans la classe '' "data.frame" '.

C'est pourquoi les colonnes d'un data.frame peuvent avoir différents types de données, alors que les colonnes d'une matrice ne le peuvent pas. A titre d'exemple, je tente ici de créer une matrice avec des nombres et des caractères:

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

Notez que je ne peux pas changer le type de données dans la première colonne en numérique car la deuxième colonne contient des caractères:

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

En ce qui concerne vos questions, laissez-moi les aborder dans l’ordre et donner quelques exemples:

1) Une liste est renvoyée si et quand l'instruction de retour en ajoute une. Considérer

 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) Les noms ne sont tout simplement pas définis:

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

) Ils ne retournent pas la même chose. Votre exemple donne

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

x[1] renvoie le premier élément de x - qui est identique à x. Chaque scalaire est un vecteur de longueur un. Par contre, x[[1]] renvoie le premier élément de la liste.

4) Enfin, les deux sont différents puisqu'ils créent respectivement une liste contenant quatre scalaires et une liste avec un seul élément (qui se trouve être un vecteur de quatre éléments).

61
Dirk Eddelbuettel

Juste pour prendre un sous-ensemble de vos questions:

Cet article sur l'indexation aborde la question de la différence entre [] et [[]].

En bref, [[]] sélectionne un seul élément dans une liste et [] renvoie une liste des éléments sélectionnés. Dans votre exemple, x = list(1, 2, 3, 4)' l'élément 1 est un entier unique, mais x[[1]] renvoie un seul 1 et x[1] renvoie une liste avec une seule valeur.

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

> x[[1]]
[1] 1
34
JD Long

Une des raisons pour lesquelles les listes fonctionnent comme elles le font (ordonné) est de répondre au besoin d'un conteneur ordonné pouvant contenir n'importe quel type sur n'importe quel nœud, ce que ne font pas les vecteurs. Les listes sont réutilisées dans R, à diverses fins, notamment pour former la base d'un data.frame, qui est une liste de vecteurs de type arbitraire (mais de même longueur).

Pourquoi ces deux expressions ne donnent-elles pas le même résultat?

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

Pour ajouter à la réponse de @ Shane, si vous voulez obtenir le même résultat, essayez:

x3 = as.list(1:4)

Ce qui contraint le vecteur 1:4 dans une liste.

13
Alex Brown

Juste pour ajouter un point à cela:

R a une structure de données équivalente au Python dict in le package hash . Vous pouvez lire à ce sujet dans ce billet de blog du groupe Open Data . Voici un exemple simple:

> 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

En termes de facilité d'utilisation, la classe hash est très similaire à une liste. Mais les performances sont meilleures pour les grands ensembles de données.

11
Shane

Vous dites:

D'autre part, les listes peuvent être renvoyées à partir de fonctions même si vous ne l'avez jamais passée dans une liste lorsque vous avez appelé la fonction, et même si la fonction ne contient pas de constructeur de liste, par exemple,

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

Et je suppose que vous suggérez que c'est un problème (?). Je suis ici pour vous dire pourquoi ce n'est pas un problème :-). Votre exemple est un peu simple, en ce sens que lorsque vous effectuez la division de chaînes, vous disposez d'une liste d'éléments de 1 élément, ce qui vous permet de savoir que x[[1]] est identique à unlist(x)[1]. Mais que se passe-t-il si le résultat de strsplit renvoie des résultats de longueur différente dans chaque groupe. Renvoyer un vecteur (par rapport à une liste) ne suffira pas.

Par exemple:

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

Dans le premier cas (x: qui renvoie une liste), vous pouvez indiquer quelle était la deuxième "partie" de la troisième chaîne, par exemple: x[[3]][2]. Comment pouvez-vous faire la même chose en utilisant xx maintenant que les résultats ont été "démêlés" (unlist- ed)?

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

n'est pas le même parce que 1: 4 est le même que c (1,2,3,4). Si vous voulez qu'ils soient identiques, alors:

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

Si cela peut aider, j'ai tendance à concevoir des "listes" dans R comme des "enregistrements" dans d'autres langues antérieures à OO:

  • ils ne font aucune hypothèse sur un type global (ou plutôt sur le type de tous les enregistrements possibles d'arits et de noms de champs disponibles).
  • leurs champs peuvent être anonymes (vous y accédez par ordre de définition strict).

Le nom "record" se heurterait à la signification standard de "records" (ou rangées) dans le jargon des bases de données, ce qui explique peut-être pourquoi leur nom s'est suggéré lui-même: sous forme de listes (de champs).

pourquoi ces deux opérateurs différents, [ ] et [[ ]], retournent-ils le même résultat?

x = list(1, 2, 3, 4)
  1. [ ] permet d'effectuer des sous-réglages. En général, les sous-ensembles de tout objet auront le même type que l'objet d'origine. Par conséquent, x[1] fournit une liste. De la même façon, x[1:2] est un sous-ensemble de la liste d'origine, donc une liste. Ex.

    x[1:2]
    
    [[1]] [1] 1
    
    [[2]] [1] 2
    
  2. [[ ]] sert à extraire un élément de la liste. x[[1]] est valide et extrait le premier élément de la liste. x[[1:2]] n'est pas valide car [[ ]] ne fournit pas de sous-réglage comme [ ].

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

En ce qui concerne les vecteurs et le concept de hachage/matrice d’autres langues:

  1. Les vecteurs sont les atomes de R. Par exemple, rpois(1e4,5) (5 nombres aléatoires), numeric(55) (vecteur de longueur 55 double sur zéro) et character(12) (12 chaînes vides), sont tous " de base".

  2. Les listes ou les vecteurs peuvent avoir 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. Les vecteurs exigent que tout soit du même type de données. Regarde ça:

    > 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. Les listes peuvent contenir différents types de données, comme indiqué dans d'autres réponses et la question du PO elle-même.

J'ai vu des langues (Ruby, javascript) dans lesquelles les "tableaux" peuvent contenir des types de données variables, mais par exemple en C++, les "tableaux" doivent être tous du même type de données. Je pense que c’est une question de rapidité/d’efficacité: si vous avez une numeric(1e6) vous connaissez sa taille et l’emplacement de chaque élément a priori ; si la chose peut contenir "Flying Purple People Eaters" dans une tranche inconnue, vous devez alors analyser le contenu pour en connaître les bases.

Certaines opérations standard R ont également plus de sens lorsque le type est garanti. Par exemple, cumsum(1:9) est logique, alors que cumsum(list(1,2,3,4,5,'a',6,7,8,9)) n'a pas de sens, sans que le type soit garanti être double.


En ce qui concerne votre deuxième question:

Les listes peuvent être renvoyées à partir de fonctions même si vous n’avez jamais passé de liste lorsque vous avez appelé la fonction.

Les fonctions renvoient des types de données différents de ceux qu'elles entrent tout le temps. plot renvoie un tracé même s'il ne prend pas un tracé en entrée. Arg renvoie un numeric même s'il a accepté un complex. Etc.

(Et comme pour strsplit: le code source est ici .)

1
isomorphismes

Bien que cette question soit assez ancienne, je dois dire qu'elle touche exactement les connaissances qui me manquaient lors de mes premières étapes dans R - c.-à-d. Comment exprimer des données dans ma main en tant qu'objet dans R ou comment sélectionner des objets existants. Il n’est pas facile pour un novice de penser "dans une boîte de dialogue" dès le début.

J'ai donc moi-même commencé à utiliser des béquilles ci-dessous, ce qui m'a beaucoup aidé à déterminer quel objet utiliser pour quelles données et, en gros, à imaginer une utilisation dans le monde réel.

Bien que je ne donne pas de réponses exactes à la question, le court texte ci-dessous peut aider le lecteur qui vient de commencer avec R et pose des questions similaires.

  • Vecteur atomique ... J'ai appelé cette "séquence" pour moi-même, pas de direction, juste une séquence du même type. [ sous-ensembles.
  • Vecteur ... séquence avec une direction à partir de 2D, [ sous-ensembles.
  • Matrice ... groupe de vecteurs de même longueur formant des lignes ou des colonnes, [ sous-ensembles par lignes et colonnes ou par séquences.
  • Tableaux ... matrices en couches formant 3D
  • Dataframe ... un tableau 2D comme dans Excel, où je peux trier, ajouter ou supprimer des lignes ou des colonnes ou créer des arits. Après quelques temps, j'ai vraiment reconnu que dataframe est une implémentation intelligente de list, où je peux utiliser un sous-ensemble en utilisant [ par des lignes et des colonnes, mais même en utilisant [[.
  • Liste ... pour m'aider, j'ai pensé à la liste à partir de tree structure[i] sélectionne et retourne des branches entières et [[i]] renvoie un élément de la branche. Et comme il s’agit de tree like structure, vous pouvez même utiliser un index sequence pour adresser chaque feuille à une list très complexe en utilisant son [[index_vector]]. Les listes peuvent être simples ou très complexes et peuvent mélanger différents types d'objets en un seul.

Donc, pour lists, vous pouvez trouver plus de moyens de sélectionner un leaf en fonction de la situation, comme dans l'exemple suivant.

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

Cette façon de penser m'a beaucoup aidé.

1
Petr Matousu