Le typage des valeurs : les listes simples

Dans l’article Le typage des variables : les types de base nous avons vu le concept de vecteur (vector) et différents types de base.

Nous allons explorer aujourd’hui le type list.

Pour les python addicts (je vous vois !), il s’agirait d’une collection de type… list… (en ordre fixe et mutable)

Petit rappel sur les vecteurs.

Un vecteur est une structure de données ayant un type unique et un nombre quelconque de valeurs individuelles.

list est un des types de base, un vecteur de type list définit donc une collection de valeurs de type list.

La particularité de ce type est qu’il peut encapsuler n’importe quel autre type de données.

Voici pour rappel un vecteur de nombres (type float) :

si on voulait utiliser une list, on obtiendrait :

On voit donc que le principal intérêt d’une list est que le type est porté par l’item et non la collection. Les éléments qui la composent n’ont pas à être tous du même type ou de la même classe.

Il est tout à fait possible de faire :

> list(1L,2,"3",1i)
[[1]]
[1] 1                   # type integer

[[2]]
[1] 2                   # type float

[[3]]
[1] "3"                 # type character

[[4]]
[1] 0+1i                # type complex

Mais plus encore, list accepte tout les types et toutes les classes de variables en son sein. Ainsi, il est possible d’y insérer des vecteurs de tous types et de toutes tailles y compris des list permettant ainsi une structure récursive :

> list(c(1,2,3),c("A","B"),function(x){print("x")},list(4,"C"))
[[1]]
[1] 1 2 3

[[2]]
[1] "A" "B"

[[3]]
function(x){print("x")}

[[4]]
[[4]][[1]]
[1] 4

[[4]][[2]]
[1] "C"

(notez la list en position 4 et sa présentation à l’écran indiquant l’index dans la list et la sous-list)

Et voici sa représentation structurelle :

Créer une liste

Nous avons déjà vu un moyen de créer une list : le constructeur list(...) attendant un nombre quelconque de valeurs qui seront insérées, dans l’ordre, au sein de la liste.

Il est possible d’initialiser une liste vide en appelant list() sans paramètres. Attention, à la différence d’un vecteur créé par exemple avec c(), un item de la list peut être NULL :

> c(1,NULL,3)
[1] 1 3                        # vecteur de 2 float
> list(1,NULL,3)
[[1]]
[1] 1

[[2]]                          # list de 3 items dont la 2ème est NULL
NULL

[[3]]
[1] 3

Cela entraine de plus un effet de bord : une list(1,NULL,3) ne peut pas être coercée à un vecteur numérique car NULL n’est pas de type numérique mais de type NULL (un type pour lui tout seul) :

> as.numeric(list(1,NULL,3))
Error: 'list' object cannot be coerced to type 'double'

A noter aussi que si vous passez un vecteur à list(), vous ne créez pas une liste contenant les valeurs du vecteur mais une liste possédant un item unique contenant le vecteur en entier :

> list(c(1,2))
[[1]]
[1] 1 2

C’est une erreur classique quand on débute qui vaut souvent beaucoup d’arrachage de cheveux… permettant d’introduire le chapitre suivant :

Convertir en liste

Comme pour les autres types, il existe une fonction as.list() tentant de convertir son argument en liste. On peut donc y passer tous les types de vecteurs qui seront décomposés en autant d’items qu’ils ont de valeurs.

> as.list(c(1,2,3)) # Conversion d'un vecteur (de type float implicite) en list

Attention cependant à son usage avec un factor : l’intérêt du factor est perdu car les levels sont dupliqués aussi et non partagés. Il conviendra donc de préférer as.list(as.character(facteur)) pour se débarrasser de la structure factorisée sous-jacente.

> as.factor(c(1,2,3))
[1] 1 2 3
Levels: 1 2 3
> as.list(as.factor(c(1,2,3)))
[[1]]
[1] 1
Levels: 1 2 3

[[2]]
[1] 2
Levels: 1 2 3

[[3]]
[1] 3
Levels: 1 2 3

Tester une liste

is.list() permet de tester si la données est une liste. Il s’agit là de tester le type d’une donnée (la manière dont elle est stockée et accédée), cela veut dire qu’un objet hérité du type list amènera une réponse « Vrai »/TRUE. C’est le cas par exemple pour un objet de type data.frame :

> is.list(list(1,2,3))
[1] TRUE
> is.list(data.frame())
[1] TRUE
> class(data.frame())
[1] "data.frame"
> typeof(data.frame())
[1] "list"                # Raison pour laquelle is.list() est TRUE

On peut donc utiliser la totalité des fonctionnalités adaptées aux listes sur la classe data.frame. Nous reviendrons sur les data.frame dans un article dédié (voire plusieurs) tant il y a à dire.

Accéder au contenu d’une liste

En lecture

Il est possible d’atteindre le contenu d’une list via un adressage ou subsetting :

Pour récupérer le contenu du 2ème emplacement, il suffit de faire :

> list(1,2,3)[[2]]
[1] 2

Notez les doubles-crochets qui demandent à retourner le contenu de l’item (ici un vecteur d’une seule valeur numérique : 2) et non le vecteur de list (une nouvelle liste qui contiendrait ici l’item 2).

Si nous ne précisons pas les [[ ]] mais mettons des crochets simples [ ], nous récupérons l’item (de type list) en lui-même :

> typeof(list(1,2,3)[2])
[1] "list"
> typeof(list(1,2,3)[[2]])
[1] "double"

Il est donc possible de « subsetter » une list comme tout autre vecteur, c’est à dire de restreindre une liste à certains de ses items grâce à la notation [] en y précisant un vecteur de type logique (True => garder, False => retirer) ou un vecteur entier (retournant une list contenant les items dans l’ordre du vecteur) :

> list(1,2,3)[c(T,F,T)]
[[1]]
[1] 1

[[2]]
[1] 3
> a[c(3,2)]
[[1]]
[1] 3

[[2]]
[1] 2

En écriture

Tout comme les autres types, il est possible de modifier les vecteurs contenus grâce à []<- :

# On crée une liste contenant 1,2,3
> a <- list(1,2,3)
> a
[[1]]
[1] 1

[[2]]
[2]

[[3]]
[1] 3

# On subsette grâce à un masque logique et on modifie le contenu via <-
> a[c(T,F,T)]<-"A"
> a
[[1]]
[1] "A"

[[2]]
[1] 2

[[3]]
[1] "A"

En cas de liste récursive, il est tout à fait possible de modifier en profondeur :

> a <- list("1",list("21","22",list("231","232","233")))
> a[[2]][[3]][2]<-"A"
> a
[[1]]
[1] "1"

[[2]]
[[2]][[1]]
[1] "21"

[[2]][[2]]
[1] "22"

[[2]][[3]]
[[2]][[3]][[1]]
[1] "231"

[[2]][[3]][[2]]                # Le remplacement
[1] "A"

[[2]][[3]][[3]]
[1] "233"

Il faut comprendre a[[2]][[3]][2] comme :

Accède au contenu de l’item list n°2, dans cet item accède au contenu de l’item list n°3, dans cet item accède à l’item n°2 (et remplace-le)

Attention à bien mettre des [[ ]] afin de « rentrer » dans l’item (en dernier, on peut utiliser indifféremment [ ] et [[ ]]).

Ajouter des items

Il n’est pas possible d’ajouter des items per se car ce n’est pas la logique de R. Par contre, il est tout à fait possible de manipuler un vecteur et le réaffecter à la même variable dans le plus pur style R:

> a <- list(1,2)
> a
[[1]]
[1] 1

[[2]]
[1] 2

> a <- c(a,list(3))
> a
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

Dans les faits, on concatène (c()) 2 vecteurs de type list, a et un nouveau vecteur contenant 3 et on réaffecte ce nouveau vecteur à a.

Supprimer des items

Nous avons déjà vu comment faire : il suffit de subsetter (sélectionner les items qui nous intéressent) et réaffecter. Attention, cela change assez logiquement l’indexation des items (sans changer l’ordre cependant).

> a <- list(1,2,3)
> a <- a[2:3]
> a
[[1]]
[1] 2

[[2]]
[1] 3

Nous avons fait le tour des principales particularités des listes simples. La prochaine fois nous étudierons des variantes de listes, les listes nommées (et un petit mot en passant pour les dotted lists)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *