8  Concepts-clés

8.1 La console de R : R GUI

Quand vous ouvrez R ou RStudio, vous êtes nez à nez avec un “invite de commande” et un curseur :

> 

R, canal historique

R attend que vous tapiez des commandes avec vos jolis doigts et que vous pressiez <Entrée> pour exécuter la commande. Le résultat s’affiche alors :

1+1
[1] 2

Une commande peut se dérouler sur plusieurs lignes comme on le verra plus tard (typiquement lors de la définition d’une fonction ou d’un pipe de fonctions) mais quand vous pressez <Entrée> l’ensemble de ces commandes doit être syntaxiquement correct, sinon une erreur vous sera retournée.

Vous pouvez également taper plusieurs commandes sur la même ligne, séparées par ; mais je vous le déconseille.

Vous pouvez naviguer dans votre historique de commandes avec les touches <Haut> et <Bas> de votre clavier. Vous pouvez aussi effacer votre console (sans effacer l’historique) avec <Ctrl>+L ou <Cmd>+L sous Mac.

8.2 Scripts

Si vous êtes dans RStudio, vous aurez remarqué plusieurs fenêtres dont l’une est la Console de R (en bas à gauche sur la capture d’écran) que nous venons de voir, ainsi qu’une autre, généralement au dessus, dans laquelle vous pouvez ouvrir des Scripts au format .R, c’est à dire en fichier texte.

Vous aurez remarqué que dans le panneau “Console”, le même texte de “bienvenue” apparaît et c’est normal : après tout, RStudio n’est ni plus ni moins qu’une console avec des outils autour.

Vous pouvez parfaitement tout faire dans le panneau “Console” et ne pas utiliser le reste si vous chérissez l’ambiance des années 1990s. Mais à ce compte là, utilisez plutôt RGui, c’est à dire ce que nous avons vu précédemment. RGui tout seul n’a plus vraiment de sens dans la vie quotidienne actuelle de l’analyse de données.

Vous pouvez aller faire un tour dans les Préférences de RStudio et en paramétrer l’aspect, notamment pour avoir un fond sombre et ne pas abimer vos jolis yeux.

L’interface de RStudio

Le workflow typique est le suivant :

  1. vous tapez des commandes dans la console, vous essayez, vous tâtonnez jusqu’à ce que vous en soyez satisfait·e de chaque résultat intermédiaire ;
  2. vous sauvez ces commandes dans un script que vous pouvez commenter, organiser, etc. ;
  3. Vous pouvez ensuite reproduire toutes vos analyses grâce à ce script, les recycler, les amender que ce soit demain, dans six mois ou sur une autre machine.

Un concept central est que R n’a pas de mémoire d’un jour à l’autre si vous le fermez. Alors oui, vous pouvez toujours “sauver votre espace de travail” mais je vous le déconseille fortement. La reproductibilité de vos analyses est en jeu. Nous allons apprendre à faire sans et nous verrons pourquoi c’est une force.

Reprenons. Un script est donc une collection de commandes qui permettent de reproduire vos analyses. Excel peut déjà aller se rhabiller.

Pour le moment, nous allons bidouiller dans la console, sans rassembler nos commandes en un script, mais nous y viendrons vite.

8.3 Commentaires

Tout ce qui suit un # est ignoré par R, ce qui permet de commenter vos scripts comme ci-après. Faites un usage massif de commentaires. Votre vous-même de dans six mois, les reviewers ou pire encore vos collègues, vous en seront reconnaissants.

8.4 Arithmétique

En premier lieu, R est une calculette. Vous pouvez copier-coller les blocs de code directement dans votre console R :

# back to school
1+2
3-4
2/3
2*2

# beyond +, -, /, *
2^4 
sqrt(9) # equivalent to 9^(1/2)
7%%3

# precedence rules apply
(1.5-2)*4
1.5-2*4
[1] 3
[1] -1
[1] 0.6666667
[1] 4
[1] 16
[1] 3
[1] 1
[1] -2
[1] -6.5

Tous les opérateurs arithmétiques courants sont disponibles :

  • +, -, *, / pour l’arithmétique de base
  • ^ pour élever au carré et sqrt pour la racine carrée
  • %% pour le modulo, etc.

Vous venez d’utiliser - peut-être sans le savoir -, votre première fonction : sqrt, pour square root. Les fonctions ont souvent des noms explicites et sont, généralement, suivies de parenthèses dans lesquelles glisser une ou des valeurs.

En réalité, toutes les opérations ci-dessus sont des fonctions, y compris un banal +1. Nous y reviendrons mais cela nous vaut la première maxime de sagesse populaire à garder dans un coin de la tête :

En R, tout est fonction.

Vous pouvez également utiliser des parenthèses pour “emboiter” des opérations dans le bon ordre. Sans parenthèses, les règles de précédence2 classiques s’appliquent comme dans l’exemple ci-dessus : une multiplication sera calculée avant une addition.

8.5 Variables et assignation

Comme on pouvait s’en douter, R a une mémoire et c’est donc plus qu’une calculette : c’est une calculatrice.

Pour stocker une valeur dans une variable nous utilisons l’opérateur d’assignation : <-3

plop <- 3
plop^2
[1] 9

D’autres opérateurs d’assignation existent (->, =, <<-, etc.) mais je vous conseille - calmement mais fermement - de vous en tenir au bon vieux <-4.

Naturellement, si vous assignez une nouvelle valeur à une variable, celle-ci est remplacée :

plop <- 2
plop+3
[1] 5

Et bien entendu vous pouvez combiner les variables :

plip <- 7
plop*plip
[1] 14

Vous pouvez lister les variables existantes avec la fonction ls(). L’onglet “Environnement” dans RStudio permet aussi de les visualiser, plus convivialement et de façon plus détaillée.

Pour afficher la valeur d’une variable, et plus généralement d’un objet, il suffit de taper son nom dans la console et d’appuyer sur <Entrée>. En coulisses, R appelle alors automatiquement la fonction print.

R a tendance à faire beaucoup de choses en coulisses. Dans l’ensemble, cela se traduit par un caractère raisonnablement intuitif pour vous. Sachez simplement qu’une opération aussi naturelle qu’une assignation à la volée comme on vient de le faire est une hérésie pour un esprit informaticien orthodoxe, habitué à déclarer une variable et son type avant d’y assigner une valeur.

8.6 Bien nommer ses variables

Mal nommer les choses c’est ajouter au malheur du monde – Albert Camus

À propos du nommage de vos variables, soyez explicites mais compacts et évitez : les caractères spéciaux, les majuscules, les points (utilisez _) et les noms réservés comme pi.

Je vous conseille l’anglais, même approximatif, partout et tout le temps (commentaires, noms de variables, etc.).

# good names
mod1
mod1_spain
N_perm

# bad names
my.model # . is for methods
MY_MODEL # calm down
My_Model # Camel Case works but not favoured
my_model_after_lda_a_and_data_subset2_flavourB # headaches and typos garanteed

# invalid or error-prone names
34_data 
pi <- 4    # works but very bad idea
print <- 2 # same
&italy

Ce qui nous amène à une deuxième maxime populaire:

There are only two hard things in Computer Science: cache invalidation and naming things. – Phil Karlton

Pour assigner plus d’une valeur à une variable, la fonction c, pour concatenate, est votre amie :

toto <- c(1, 2, 3, 4, 5)
toto*2
[1]  2  4  6  8 10

Jusqu’ici toutes les variables assignées étaient des scalaires, c’est à dire des variables ne comprenant qu’une valeur unique.

toto n’est pas un scalaire mais un vecteur de nombres, qui peut s’écrire en ligne, c’est à dire en une seule et unique dimension. Je pose ça là, nous y reviendrons bien vite.

Quelques fonctions bien utiles pour visualiser et décrire des variables en R :

length(toto) 
[1] 5
head(toto, 2) # show the first 2 values
[1] 1 2
tail(toto, 3) # show the last 3 values
[1] 3 4 5

Nous voyons que certaines fonctions acceptent plus d’une seule valeur au sein de leurs parenthèses : on parle d’argument. Quand plus d’un seul argument est passé à une fonction, on les sépare par des virgules. Certaines fonctions peuvent/doivent aussi être appelées à vide, comme on l’a vu avec ls().

8.7 Séquences régulières

Vous m’avez vu (me) taper “à la main”, des séries de nombres telles que c(1, 2, 3, 4, 5). On constate une certaine régularité dans cette séquence des premiers entiers naturels. Jusqu’à 5, on peut imaginer la taper à la main, mais imaginons que nous ayons besoin d’aller jusqu’à 100 ou même à 37427. Doit bien y avoir quelqu’un·e qui a pensé à une fonction pour faire ça non ? Ceci nous amène à une double maxime, peut-être les deux plus importantes de toute cette formation.

Si tu penses que tu es en train de faire quelque chose de répétitif et/ou stupide, il existe à coup sûr une façon plus intelligente de faire

De façon plus compacte :

Un·e bon·ne programmeur·se est une grosse feignasse

R possède toutes les fonctions dont on peut rêver pour générer séquences régulières et nombres aléatoires. Commençons par les séquences régulières et la versatile fonction seq qui prend au minimum deux arguments pour le point de départ et le point d’arrivée :

seq(1, 10)
 [1]  1  2  3  4  5  6  7  8  9 10

Générér une séquence d’entiers naturels est une opération si banale en programmation que l’on peut faire encore pus court avec l’opérateur :

1:5
[1] 1 2 3 4 5
-1:4
[1] -1  0  1  2  3  4

De la même façon, vous pouvez répéter une ou des valeurs avec rep :

tonton <- 1:5
rep(tonton, 2)
 [1] 1 2 3 4 5 1 2 3 4 5
rep(tonton, each=3)
 [1] 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5

À ce point de votre existence, vous devriez vous poser les questions suivantes :

  • faut-il se rappeler de tout ça ?
  • ces histoires de paramètres de fonction, attends… ah oui, le Monsieur a dit d’apeller ça des arguments, c’est bien joli mais on les trouve où ?
  • et d’ailleurs il doit bien y avoir une documentation quelque part pour tout ce bazar ?
  • quand est-ce qu’on fait une pause ?

R, comme tout langage de programmation, possède sa documentation. Tout est documenté et en premier lieu les fonctions. Nous y arrivons bientôt mais parlons d’abord plus en détail des fonctions.

8.8 Interlude clavier

  • Toutes les commandes tapées depuis l’ouverture de R/RStudio sont dans votre console. Pour l’effacer, pressez <Ctrl> + <L>. Vos objets sont conservés.
  • Pour naviguer dans votre historique, côté console, pressez les flèches <Haut> et <Bas>.
  • Pour compléter un nom de fonction ou d’argument, pressez <Tab>
  • Une fois que vous avez la fonction qui vous intéresse, positionnez-vous au sein de ses parenthèses et pressez <Tab> de nouveau : la liste des arguments apparaît avec la portion de doc consacrée.
  • Dans RStudio, allez à peu près tout tester dans le menu Code. Vous y trouverez des fonctions très utiles (“Reindent lines”, “Reformat code” par exemple). À droite de la commande se trouve aussi les raccourcis clavier. Utilisez-les !

8.9 Fonctions

Les fonctions sont généralement abordées plus tard mais je crois non seulement en vous mais aussi qu’elles doivent être démystifiées précocément.

8.9.1 Que sont les fonctions ?

Les fonctions sont des unités de code qui font quelque chose d’utile. Le plus souvent on envoie une valeur et on en récupère une autre. sqrt() par exemple renvoie la racine carrée de la valeur passée en argument.

Les arguments, séparés par des virgules, définissent les “options” de la fonction concernée. Une fonction peut avoir zéro, un, plusieurs et même un nombre indéfini d’argument.

8.9.2 Écrire ses fonctions : function

Vous pouvez définir vos propres fonctions avec la fonction function. On va encapsuler une portion de code entre des accolades { et la nommer. Définissons une fonction qui ajoute 3 à un argument que l’on va appeller x (vous pouvez essayer avec y ou toto :

plus3 <- function(x) {
  x+3
}

plus3(5)
[1] 8
plus3(1:3)
[1] 4 5 6

Les fonctions permettent de ne pas copier-coller bêtement du code, de dupliquer des lignes. À terme, cela vous permettra d’avoir du code non dupliqué, moins propice à des erreurs de frappes. Aussi, si vous changez d’avis, vous pourrez changer la définition de fonction et, chaque fois qu’elle sera appelée en aval, le nouveau comportement sera appliqué. Nous reviendrons sur ces bonnes pratiques à la toute fin de cette formation.

8.9.3 Notions d’environnement

La valeur x ci-dessus, dans la définition de fonction, est une variable utilisée uniquement dans l’environnement de la fonction plus3. Un environnement est un espace de travail mémoire isolé du reste du monde et qui n’existe que durant l’exécution de la fonction.

Autrement dit, si la variable x est déja assignée dans l’environnement global, c’est à dire en dehors de la fonction, dans la console si on veut, et bien ce x global ne sera ni modifié, ni même utilisé. Un exemple :

x <- 45
plus3(5)
[1] 8

Naturellement vous pouvez tout de même utiliser votre x global comme argument de la fonction plus3. Ce x global va être utilisé localement par la fonction puis vous être retourné :

plus3(x)
[1] 48

8.9.4 Documentation des fonctions : ?

Toute fonction déjà disponible en R ou un package a forcément une page d’aide dédiée à laquelle on accède avec : ?nom_de_la_fonction.

Revenons à notre fonction seq bien pratique pour créer des sequences régulières. En tapant ?seq on accède au contenu suivant :

Sequence Generation

Description
Generate regular sequences. seq is a standard generic with a default method. seq.int is a primitive which can be much faster but has a few restrictions. seq_along and seq_len are very fast primitives for two common cases.

## Default S3 method:
seq(from = 1, to = 1, by = ((to - from)/(length.out - 1)),
    length.out = NULL, along.with = NULL, ...)

seq.int(from, to, by, length.out, along.with, ...)

seq_along(along.with)
seq_len(length.out)

Arguments
[...]

Details
[...]

Value
[...]

See also
[...]

Examples
[...]

Toutes les pages d’aides ont la même structure et possèdent les mêmes sections. Regardons-les de plus près :

  • Description : ce que la fonction fait
  • Usage : la fonction “déployée” c’est à dire avec tous ses arguments. Parfois plusieurs fonctions sont regroupées dans une même age d’aide, comme c’est le cas pour seq. Ces fonctions peuvent être des variantes avec des noms différents ou des méthodes c’est à dire des fonctions au comportement différent selon le type d’objet sur lequel elles opèrent.
  • Arguments : un descriptif de tous les arguments disponibles. La classe et le format de chacun d’eux est mentionnée.
  • Details : souvent un remède à l’insomnie mais les subtilités d’implémentation sont là, souvent cachées au détour d’une phrase.
  • Value : ce que la fonction retourne.
  • References : où s’en référer si vous n’en avez pas assez
  • See Also : fonctions connexes, très pratique pour enrichir son vocabulaire et trouver son bonheur.
  • Examples : peut être la plus utile de toutes avec ses exemples d’utilisation que vous pouvez copier-coller ou même appeller directement depuis la console avec example("nom_de_la_fonction"). Vous pouvez essayer example("plot") par exemple.
  • En pied de page, vous avez également une information qui sera utile plus tard : le package et sa version dans lequel se trouve être cette fonction. Pour seq, on est dans le package base dont toutes les autres fonctions sont indexées dans le lien “Index”.

Certaines pages d’aide, surtout pour le langage lui-même, sont plutôt des résumés du fonctionnement et sont un peu moins intuitives à trouver, par exemple ?Arithmetic, ?Special, Syntax. D’autres fonctions, par exemple pour les opérateurs, doivent être encadrées de guillemets arrières (`), par exemple ?`+` ou la mise en abyme de ?`?`. Enfin, il existe d’autres ressources comme les “vignettes”, plus conviviales, surtout pour les packages les plus récents. Nous y reviendrons.

Les pages d’aide sont souvent compactes et obscures mais l’information que vous cherchez est probablement là. On apprend beaucoup à lire ces pages d’aide même si à première vue cette littérature n’est guère attrayante.

Enfin, la variante ??(quoiquoiquoi), raccourci de help.search("quoiquoiquoi") permet de chercher toutes les occurences de quoiquoiquoi dans toutes les pages d’aide de R.

8.9.5 Arguments : noms, positions et valeurs par défaut

Après avoir consulté ?seq on peut par exemple préciser le point de départ (from), le point d’arrivée (to), le pas (by) et la longueur totale du vecteur à créer (length.out).

Vous constaterez que from est défini avec une valeur par défaut (from=1). Ainsi, si vous omettez sa valeur et ne spécifiez que to, from prendra sa valeur par défaut. Ces deux commandes sont donc équivalentes :

seq(from=1, to=5)
[1] 1 2 3 4 5
seq(to=5)
[1] 1 2 3 4 5

Vous pouvez également abréger le nom des arguments, moyennant que l’abbréviation soit univoque, c’est à dire que le nom de l’argument que vous abrégez ne soit pas identique à celui d’un autre argument. Ainsi from et length.out peuvent être abrégés en fr et length:

seq(fr=0, to=2, length=5)
[1] 0.0 0.5 1.0 1.5 2.0

Ce n’est jamais une bonne idée mais si vous utilisez le nom complet ou une abbréviation des arguments vous pouvez changer leur ordre. Ainsi seq(length=5, to=2, fr=0) sera équivalent à la commande précédente.

Vous pouvez même omettre le nom des arguments comme on l’a fait dans les sections précédentes sans le mentionner. Dans ce cas, les arguments sont passés positionnellement et doivent être renseignés dans l’ordre tel que défini dans la section ‘Usage’ de leur documentation:

seq(-3, 4, 12)
[1] -3

8.10 Concept de recyclage

toto <- 1:5
toto*3
[1]  3  6  9 12 15

Il n’aura pas échappé à votre sagacité que dans le précédent exemple, ou une multiplicatio est opérée entre deux objets de tailles différentes (cinq valeurs et une seule), R vous a compris et a multiplié chaque élément de toto par 3.

C’est l’idée, omniprésente, de recyclage. Ça ne parait pas grand chose mais c’est souvent bien pratique et, hélas, quelques fois glissant. Par exemple, si vous multipliez deux vecteurs non-conformes, c’est à dire ni de même longueur, ni multiples l’un de l’autre, des effets indésirables peuvent se manifester. Ou pire encore, rester cachés.

toto <- c(1, 2, 3, 4)
tata <- c(5, 4, 3)
toto*tata
Warning in toto * tata: longer object length is not a multiple of shorter
object length
[1]  5  8  9 20

De nos jours, R a tendance à émettre des Warnings quand un recyclage exotique est impliqué. Lisez les messages et autres warnings ! Celui-ci est plutôt explicite mais si vous n’y comprenez goutte, copiez-collez le message dans un moteur de recherche.

8.11 Indexation [ : saisir et changer des valeurs

Indexer une ou des valeurs c’est sélectionner un sous-ensemble de valeurs dans une variable pour en faire quelque chose.

L’opérateur d’indexation est le crochet droit : [, qui tel Dupont et Dupond vont par paires. À gauche du crochet, la variable; à l’intérieur l’indice ou les indices.

tutu <- c(7, 12, 2, 5, 4)
tutu[1]
[1] 7
tutu[3]
[1] 2
tutu[length(tutu)] # take the last value of tutu, no matter tutu' length
[1] 4
tutu[c(2, 4)]
[1] 12  5

L’indexation peut se combiner avec l’assignation si on ne veut pas seulement saisir les données mais en faire quelque chose :

tutu
[1]  7 12  2  5  4
tutu[c(2, 3)] <- c(-1, -3)
tutu
[1]  7 -1 -3  5  4
tutu[c(2, 3)] <- 0.5  # indexing, assignation and recycling combined!
tutu
[1] 7.0 0.5 0.5 5.0 4.0

Ce type d’indexation est dit “positive” : l’indice réfère aux positions que l’on veut.

Bien pratique, l’opération d’indexation “négative” sélectionne ce que l’on ne veut pas.

tutu
[1] 7.0 0.5 0.5 5.0 4.0
tutu[-1]
[1] 0.5 0.5 5.0 4.0
tutu[-c(1, 3)] <- pi
tutu
[1] 7.000000 3.141593 0.500000 3.141593 3.141593

Et comme vous vous en doutiez, on peut également utiliser une variable pour indexer :

toto <- c(6, 5, 4, 3)
tata <- c(2, 3)
toto[tata]
[1] 5 4
toto[-tata]
[1] 6 3

Le concept d’indexation est absolument central en R, et en programmation en général.

L’indexation peut varier à la marge avec des indices dans plusieurs dimensions comme nous le verrons plus loin (iris[2, 3]), voire même des doubles crochets ([[), mais le comportement présenté ici reste invariable. Youpi !

8.12 Opérateurs de comparaison et logiques

L’indexation est une belle occasion de parler des opérateurs de comparaison, très utiles pour filter vos données. L’idée est qu’on teste d’abord une condition dont on peut se servir pour indexer

tonton <- c(4, 8, 2, 9, 1, 3, 5)
test <- tonton < 5  # tests a condition
test
[1]  TRUE FALSE  TRUE FALSE  TRUE  TRUE FALSE
which(test) # returns indices of TRUE
[1] 1 3 5 6
# this can be used to index
tonton[which(test)]
[1] 4 2 1 3
# or more directly
tonton[test] # filter elements of tonton lower than 5
[1] 4 2 1 3
# conditions can be combined
tonton[tonton < 5 & tonton >=2] # same with lower than 5 AND higher or equal to 2
[1] 4 2 3
tata <- c(7:1)
tonton[tata %in% c(3, 4, 5)] # takes the elements of tonton for which those of that are in 3:5
[1] 2 9 1

Voilà une liste de tous les opérateurs de comparaison (?Comparison) :

opérateur signification
< strictement inférieur
<= inférieur ou égal
> strictement supérieur
>= supérieur ou égal
== égal
!= différent
%in% dans l’ensemble

Et à y être celle pour les opérateurs logiques (?Logic) sur lesquels nous reviendrons :

opérateur signification
! NOT
& AND (élément par élément)
&& AND
| OR (élément par élément)
|| OR
xor(x, y) OR (exclusif)

Parfois on peut également avoir besoin de any, all5.

8.13 Classes d’objets

8.13.1 class

Accrochez-vous à votre voisin·e, nous abordons un concept clé. Jusqu’ici nous n’avons manipulé que des nombres, avec ou sans assignation à une variable. D’autres classes d’objets existent en R.

On peut accéder à la classe d’un vecteur avec la fonction class. On voit que les chaînes de caractères sont des character pour R:

class(toto)
[1] "numeric"

Imaginons que nous mesurions des individus et que nous enregistrions leurs prénoms, sexe, stature et si, oui ou non, ils ont subi une formation à R. Ces quatres variables auront des natures différents :

  • prenom : sera plutôt des lettres
  • stature : sera un nombre décrivant leur taille
  • sexe : sera une étiquette pouvant prendre une et une seule des valeurs suivantes {femme/homme/autre}
  • formation : sera un descripteur de type vrai/faux que l’on traduira en TRUE/FALSE.

Nous avons déja enregistré trois individus :

prenom        stature   genre     formation
Hildegarde    178       femme     TRUE
Jean-Jacques  163       homme     FALSE
Victor        184       autre     TRUE

8.13.2 character

Tentons de créer la première variable, c’est à dire la première colonne, à la main :

prenom <- c("Hildegarde", "Jean-Jacques", "Victor")
prenom
[1] "Hildegarde"   "Jean-Jacques" "Victor"      

R ne fait pas d’histoires et nous a crée un vecteur de chaînes de caractères !

8.13.3 numeric

Créons maintenant le vecteur stature, on sait faire :

stature <- c(178, 163, 184)
stature
[1] 178 163 184
class(stature)
[1] "numeric"

Tous les vecteurs que nous avons crée jusqu’ici, prenom mis à part, était donc des numeric. Précisions que des variantes de numeric existent : double, integer, etc. mais vous n’aurez peut être jamais à vous en soucier.

8.13.4 factor

La colonne genre est un peu particulière puisque elle est une chaîne de caractères mais elle ne peut prendre que des valeurs définies, à savoir une et une seule valeur de l’ensemble : {homme, femme, autre}. La classe factor est là pour ça et la fonction factor permet de créer notre variable sexe :

genre <- factor(c("femme", "homme", "autre"))
class(genre)
[1] "factor"
genre
[1] femme homme autre
Levels: autre femme homme

Votre œil aiguisé aura détecté deux différences par rapport à prenom : l’absence de guillemets autour de chaque valeur et une ligne supplémentaire qui indique les valeurs possibles, les levels de ce vecteur.

Petite digression : les facteurs en R sont très pratiques mais assez piégeux. Nous aurons l’occasion d’y revenir mais avant ça, évacuons les dernier·e·s informaticien·ne·s pur sang de la salle. Imaginons qu’un nouveau level, une nouvelle catégorie, doive être créée pour la variable genre, disons licorne. Tentons l’opération candidement :

genre2 <- c(genre, "licorne")
genre2
[1] "2"       "3"       "1"       "licorne"
class(genre2)
[1] "character"

Malédiction (apparente)6 que nous expliquerons plus tard.

8.13.5 logical

Laissons reposer les facteurs pour l’instant et continuons notre création de variable avec la colonne formation:

formation <- c(TRUE, FALSE, TRUE)
class(formation)
[1] "logical"
formation
[1]  TRUE FALSE  TRUE

Voici une classe très utile, les logical, souvent issus de tests et de comparaisons logiques, comme survolé précédemmet par exemple :

stature > 180
[1] FALSE FALSE  TRUE

Nous avons donc crée nos quatre colonnes :

prenom
[1] "Hildegarde"   "Jean-Jacques" "Victor"      
stature
[1] 178 163 184
genre
[1] femme homme autre
Levels: autre femme homme
formation
[1]  TRUE FALSE  TRUE

Nous avons vu autant de classes différentes (character, numeric, factor, logical respectivement) et, bonne nouvelle, on a quasiment fait le tour des classes ! Il nous en reste deux, très voisines : list et data.frame.

8.13.6 list

Une liste est un vecteur dont les éléments peuvent être de classes et de longueurs différentes, dont d’autres listes. Autrement dit, c’est la structure de données universelle en R :

list(toto="A", tata=c(1, 3))
$toto
[1] "A"

$tata
[1] 1 3
list(prenom, stature)
[[1]]
[1] "Hildegarde"   "Jean-Jacques" "Victor"      

[[2]]
[1] 178 163 184

Vous pouvez comparer le résultat à c(prenom, stature). Observez aussi au passage les doubles crochets droits ([[), dont vous n’aurez bien plus peur.

8.13.7 data.frame

Un type de liste bien pratique, devenu archi central en R moderne, est le data.frame qui est une liste dont la double particularité est que tous ses éléments sont nommés et de même longueur. Une façon un peu alambiquée de dire qu’il nous aura fallu tout cela pour réinventer en R le tableau excel !7. Nous y reviendrons abondamment.

Comme vous pouvez vous en douter, personne n’importe les données comme cela, on préférera lire directement une table .csv ou .xlsx. Minute papillon, on y vient.

data <- data.frame(prenom=prenom,
                   stature=stature,
                   genre=genre,
                   formation=formation)
data
        prenom stature genre formation
1   Hildegarde     178 femme      TRUE
2 Jean-Jacques     163 homme     FALSE
3       Victor     184 autre      TRUE
class(data)
[1] "data.frame"

8.13.8 is.* et as.*

Les fonctions is.* permettent de tester les classes :

is.logical(formation)
[1] TRUE
is.factor(prenom)
[1] FALSE
is.data.frame(data)
[1] TRUE

Et les fonctions as.* permettent de convertir les classes, lorsque la conversion est pertinente :

as.factor(prenom)
[1] Hildegarde   Jean-Jacques Victor      
Levels: Hildegarde Jean-Jacques Victor
as.character(genre)
[1] "femme" "homme" "autre"

8.14 Indexation multiple [,

Revenons à notre data.frame data, un objet en deux dimensions : il possède des lignes et des colonnes. L’indexation vue précédemment sur des vecteurs, des objets en une seule dimension, fonctionne dans le même esprit mais il nous faut renseigner les indices pour chaque dimension. Si l’un des indices n’est pas renseigné, toutes les positions concernées sont retournées mais il ne faut pas oublier pour autant la virgule !

Par convention la première dimension est celle des lignes, la deuxième celle des colonnes. Quelques exemples ci-dessous qui ne font qu’extraire les données. Nous pourrions aussi les modifier avec l’opérateur d’assignation <- comme vu précédemment.

data[1, ] # first row
      prenom stature genre formation
1 Hildegarde     178 femme      TRUE
data[, 1] # first column
[1] "Hildegarde"   "Jean-Jacques" "Victor"      
data[-2, ] # everything but the second row
      prenom stature genre formation
1 Hildegarde     178 femme      TRUE
3     Victor     184 autre      TRUE
data[c(1, 3), -3] # first and third row, all columns but the third
      prenom stature formation
1 Hildegarde     178      TRUE
3     Victor     184      TRUE

Vous aurez noté qu’en ne sélectionnant qu’une colonne, par exemple data[, 1] ci-dessus, on perd la nature de data.frame pour revenir à la classe d’origine de la colonne. Souvent pratique, parfois glissant. Pour ne pas perdre la classe d’origine, il suffit d’ajouter drop=FALSE à votre opération d’indexation, après tous les indices :

data[, 2, drop=FALSE]
  stature
1     178
2     163
3     184

Les colonnes uniques peuvent aussi se sélectionner avec l’opérateur $ qui permet d’accéder à un élément de liste, pourvu qu’il soit nommé. Les data.frames étant des listes nommées, cela fonctionne :

data$stature
[1] 178 163 184

Les doubles crochets droits [[ permettent eux aussi de sélectionner positionnellement, ou nominativement, l’élément de liste concerné. Les deux commandes ci-dessous seront équivalentes :

data[[3]]
[1] femme homme autre
Levels: autre femme homme
data[["genre"]]
[1] femme homme autre
Levels: autre femme homme

8.15 Indexation de liste : [ versus [[

L’indexation avec une simple ou une double paire de crochets est souvent source d’incompréhension d’autant que sur des vecteurs le résultat est le même :

toto <- 5:3
toto
[1] 5 4 3
toto[2]
[1] 4
toto[[2]]
[1] 4

Gardez à l’esprit que la double paire de crochets droits ([[) ne s’utilise que sur des listes (data.frame y compris donc).

Pour expliquer simplement la différence entre [ et [[, disons qu’une liste est un train de marchandise sans locomotive avec un ou plusieurs wagons.

  • [ permet de sélectionner un wagon, le résultat est toujours un train, certes minimaliste
  • [[ permet de sélectionner le contenu du wagon qui n’est donc plus un train mais une vache, une palette ou des voyageurs :
tata <- list(wagon1="a", wagon2=1:3)
# [ picks a list element
tata[2]
$wagon2
[1] 1 2 3
class(tata[2])
[1] "list"
# [[ picks a list element AND drops the list
tata[[2]]
[1] 1 2 3
class(tata[[2]])
[1] "integer"

Notons que $ est équivalent à [[:

tata$wagon2
[1] 1 2 3

8.16 matrix

Par souci de complétude, mentionnons les matrices qui sont des tableaux rectangulaires de nombres. Elles se créent avec la fonction matrix et en spécifiant les valeurs de remplissage et le nombre de lignes et/ou de colonnes :

m <- matrix(c(3, 1, 9.2, 6, 7, 0), nrow=2)
m
     [,1] [,2] [,3]
[1,]    3  9.2    7
[2,]    1  6.0    0

Les matrix et les data.frame possèdent de nombreux points communs. On peut notamment accéder à leurs dimensions, noms de lignes et colonnes avec les mêmes fonctions :

dim(m)
[1] 2 3
nrow(m)
[1] 2
ncol(m)
[1] 3
rownames(m) <- c("plop", "plip")
rownames(m) # idem for colnames
[1] "plop" "plip"

Une opération courante sur des matrices consiste à faire des calculs marginaux, par exemple calculer la somme par colonnes. La famille apply permet ce type de calcul. On va renseigner trois arguments (voir ?apply et ses exemples) : l’objet sur lequel travailler; la dimension sur laquelle calculer (1 pour les lignes, 2 pour les colonnes); et enfin la fonction à appliquer, sans parenthèses :

apply(m, 2, sum)
[1]  4.0 15.2  7.0

8.17 array

Par souci de complétude, l’idée de matrice se généralise dans des dimensions supérieures à 2. Pour le dire autrement, une matrix est un array à deux dimensions.

Les array se créent dans le même esprit : les valeurs de remplissage d’abord, puis on précise les dimensions. Ci-dessous, un array de deux tranches de matrices de 2x3.

a <- array(data=c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), dim=c(2, 3, 2))
a
, , 1

     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6

, , 2

     [,1] [,2] [,3]
[1,]    7    9   11
[2,]    8   10   12

L’indexation fonctionne ici aussi. Par exemple si l’on veut la valeur de la première ligne, troisième colonne, deuxième tranche :

a[1, 3, 2]
[1] 11

8.18 Fonctions utiles

8.18.1 Sur numeric

Quelques fonctions utiles pour décrire des vecteurs numériques (numeric) :

x <- c(5, 4, 3, 2, 1)
length(x) 
min(x)
max(x)
range(x)  # shortcut for c(min(x), max(x))
median(x) # shortcut for quantile(x, probs=0.5)
sum(x)
mean(x)   # average
sd(x)     # standard deviation
var(x)    # variance
[1] 5
[1] 1
[1] 5
[1] 1 5
[1] 3
[1] 15
[1] 3
[1] 1.581139
[1] 2.5

8.18.2 Sur factor

f1 <- factor(c("apple", "banana", "banana", "pear", "grape", "grape"))
f2 <- factor(c("yellow", "yellow", "yellow", "green", "red", "green"))

length(f1)  # length
levels(f1)  # levels, as character
nlevels(f1) # number of levels, shortcut for length(level(f1))
table(f1)   # count occurences
table(f1, f2) # cross-tabulate factors
[1] 6
[1] "apple"  "banana" "grape"  "pear"  
[1] 4
f1
 apple banana  grape   pear 
     1      2      2      1 
        f2
f1       green red yellow
  apple      0   0      1
  banana     0   0      2
  grape      1   1      0
  pear       1   0      0

8.18.3 Sur character

bla1 <- "tonton"
bla2 <- "tata"

nchar(bla1) # count characters
paste(bla1, bla2, sep=" et ") # see also paste0
toupper(bla1) # convert to upper case. see also tolower()
gsub("o", "i", bla1) # replace all 'o' with 'i' in bla1. see ?grep
substr(bla1, 2, 4) # character from positions 2 to 4
abbreviate(bla1) # abbreviate
[1] 6
[1] "tonton et tata"
[1] "TONTON"
[1] "tintin"
[1] "ont"
tonton 
"tntn" 

Les derniers exemples montrent des manipulations de chaînes de caractères. Le package stringr remplace avantageusement ces approches “historiques”. Nous y reviendrons.

8.18.4 Sur data.frame

dim(data)
nrow(data)
ncol(data) # equivalent to length(data)
summary(data) # a summary, column-wise
head(data) # show only the first rows
tail(data) # show only the last rows
# View(data) # show an interactive viewer for your data.frame
str(data)  # show the structure of your data.frame
[1] 3 4
[1] 3
[1] 4
    prenom             stature        genre   formation      
 Length:3           Min.   :163.0   autre:1   Mode :logical  
 Class :character   1st Qu.:170.5   femme:1   FALSE:1        
 Mode  :character   Median :178.0   homme:1   TRUE :2        
                    Mean   :175.0                            
                    3rd Qu.:181.0                            
                    Max.   :184.0                            
        prenom stature genre formation
1   Hildegarde     178 femme      TRUE
2 Jean-Jacques     163 homme     FALSE
3       Victor     184 autre      TRUE
        prenom stature genre formation
1   Hildegarde     178 femme      TRUE
2 Jean-Jacques     163 homme     FALSE
3       Victor     184 autre      TRUE
'data.frame':   3 obs. of  4 variables:
 $ prenom   : chr  "Hildegarde" "Jean-Jacques" "Victor"
 $ stature  : num  178 163 184
 $ genre    : Factor w/ 3 levels "autre","femme",..: 2 3 1
 $ formation: logi  TRUE FALSE TRUE

graphics:::plot.data.frame

set.seed(2329)
runif(10, -1, 1)
 [1]  0.34341687 -0.89818619  0.86519883  0.05414381 -0.95204424  0.42169961
 [7]  0.09957463  0.27190793 -0.84168929  0.72023581

8.19 Générer des nombres aléatoires

8.19.1 Au sein d’une séquence existante

La fonction sample permet d’échantillonner au sein d’un vecteur existant. Vous devenz préciser ce vecteur, puis le nombre de tirages à effectuer, avec ou sans remise.

Si vous n’avez pas de dé, en voilà un :

sample(1:6, size=1)
[1] 6

Avec size=3, replace=TRUE vous pouvez même jouer au 421. Ou encore générer un tirage de loto sans les boules qui s’agitent :

sample(1:49, size=6, replace=FALSE)
[1]  9 47 36 14  4 34

Chaque élément du vecteur a autant de chance de sortir qu’un autre. Si vous préférez le scrabble, vous pouvez aussi utiliser sample mais en calibrant l’argument probs sur la fréquence des lettres dans la langue française. N’allez donc pas vous taper l’alphabet à la main, jeter un oeil à letters et LETTERS.

8.19.2 Distributions existantes

Au delà des vecteurs existants, vous pouvez générer des nombres aléatoires issus d’une distribution. Toutes les distributions disponibles sont listées dans la bien nommée ?Distriutions.

Ces fonctions sont nommées de la façon suivante {r, p, q, d}nom_abregé_distrib. La première lettre désigne la variante désirée des différentes fonctions pour une distribution donnée, selon que l’on veuille générer des nombres, la densité de probabilité, les quantiles associés, etc.

Pour générer 10 nombres aléatoires compris entre -1 et 1 on peut par exemple :

runif(10, 0, 1) # see ?runif
 [1] 0.1193610 0.4918118 0.7741212 0.3069952 0.7218488 0.4027434 0.4372506
 [8] 0.8760479 0.8498670 0.4134920

Dans le même esprit on peut tirer 1000 nombres issus d’une distribution normale de moyenne 5 et d’écart-type 3 avec la commande suivante :

x <- rnorm(1e3, 5, 3)
mean(x)
[1] 5.028203
sd(x)
[1] 3.014893

1e3 est la notation dite “ingénieur” parfaitement comprise par R. Ici, on a 1 suivi de 3 zéros, soit 10^3 soit 1000. Vous constaterez également que la moyenne est à peu près de 5 et l’écart type à peu près égal à 3.

Si vous réexécutez cette commande, vous aurez un autre vecteur avec des valeurs différentes mais également à peu près centré sur 5.

y <- rnorm(1e3, 5, 3)
mean(x) - mean(y)
[1] -0.01312834

Si vous désirez générer des nombres aléatoires certes, mais les mêmes, par exemple d’une session à l’autre ou (c’est la même chose) sur l’ordinateur de votre collègue, c’est possible avec la fonction set.seed.

8.20 Premiers graphes

Décrire un vecteur de nombres avec mean et sd mais faire des jolis graphes c’est mieux. La commande plot est la fonction de base pour ce faire :

plot(x)

R a compris que chaque valeur du vecteur x devait être plottée sur l’axe de ordonnées et en a déduit que l’axe des abscisses devait être la série de 1 à 1000. Ici un histogramme serait plus approprié pour décrire la distribution de notre vecteur de nombres aléatoires :

hist(y)

On voit qu’il est bien centré sur 5. Si l’on augmente le nombre de valeurs générées ainsi que le nombres d’intervalles (breaks en anglais), on retrouve une bien belle gaussienne :

hist(rnorm(1e5, 5, 3), breaks=50, main="Un histogramme")

Les graphiques de base de R sont un peu surannés depuis l’avènement de gpplot2 que nous verrons en détail plus loin mais ils ont encore leur mot à dire, même s’il ne s’exprimeront pas longuement ici.

Voyons tout de même ce que l’on peut faire avec iris, l’un des nombreux jeux de données livrés avec R8.

head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa
plot(Petal.Length~Petal.Width, data=iris, col=Species,
     xlab="Largeur de pétale (cm)", ylab="Longueur de pétale (cm)",
     main="Les fameux iris de Fisher (voire d'Anderson)")

En petit aguichage ggplot2 voilà ce qu’on peut obtenir dans le même temps :

library(ggplot2)
ggplot(iris) + 
  aes(x=Petal.Width, y=Petal.Length, col=Species) + 
  geom_jitter(alpha=0.5) +
  geom_smooth(method="lm", formula="y~x") +
  facet_grid(~Species) +
  xlab("Largeur de pétale (cm)") + 
  ylab("Longueur de pétale (cm)") +
  ggtitle("Les fameux iris de Fisher (voire d'Anderson)") +
  theme_linedraw()


  1. essayez donc `+`(1, 2)↩︎

  2. voir ?Syntax↩︎

  3. `<-`(toto, 4); toto*2 puisqu’on vous dit que tout est fonction !↩︎

  4. L’assignation -> est acceptable dans certains contextes, notamment en combinaison avec un pipe %>%. = est plutôt réservés aux arguments de fonctions ou, éventuellement, à des méta-paramètres en début de script. <<- est en revanche proscrit car il assigne dans l’environnement global. Utilisé dans une fonction, il remplacera dans l’environnement global la valeur de cette variable, si elle existe. Si vous pensez en avoir besoin, c’est généralement (>99.9%) que votre script/fonction est mal fichue.↩︎

  5. %notin% et none n’existent pas en R mais on peut facilement les composer avec !(a %in% b) et !(all(...)). Et si vous y tenez vraiment : `%notin%` <- function(x, y) !(x %in% y)↩︎

  6. Il y a encore plus “drôle”: c(genre, 0)*2↩︎

  7. Ne dites à personne que j’ai écrit ça !↩︎

  8. voir ?datasets.↩︎

❤ Placé dans le domaine public par Vincent Bonhomme