Introduction conceptuelle à R

De EduTech Wiki
Aller à la navigation Aller à la recherche


Introduction

On se réfère à R souvent comme s'il s'agissait d'une entité unique, notamment un logiciel à utiliser pour des analyses statistiques, mais la réalité est un peu plus complexe. Cette introduction conceptuelle à R vise identifier différentes composantes de R et la manière dont elles s'articulent entre elles pour fournir un écosystème puissant et flexible. Contrairement à d'autres introductions à R qui sont basées principalement sur la découverte des particularités du langage, nous proposons ici plutôt un survol conceptuel qui puisse s'intégrer avec des approches plus pragmatiques.

Prérequis

Dans cette introduction conceptuelle, nous utiliserons l'environnement de travail typique de R, notamment avec l'utilisation de RStudio qui nous permettra de mieux cerner certains aspects comme par exemple la notion de référence symbolique, fonction, paquet, session, environnement et d'autres encore. Pour maximiser la compréhension de ces aspects, sur le plan théorique, il peut être utile d'avoir lu au préalable l'Introduction à la programmation.

Fonctionnement général de R

L'utilisation de R se fait à travers des instructions qui sont passées à un interprète en tant que Input. Ces instructions déclenchent de la computation, c'est-à-dire qu'elles sont interprétées, afin de produire un Output correspondant. Cet Ouput peut ensuite servir, éventuellement, en tant que Input pour une nouvelle computation, en créant ainsi un mécanisme cyclique représenté dans la figure suivante.

Fonctionnement général de R

Ces trois phases sont généralement effectuées au sein de la même interface graphique, par exemple à travers RStudio ou la ligne de commande qui est disponible dans la version R de base. Néanmoins, le fait que ces trois phases puissent être décomposées permet une plus grande flexibilité et des opportunités intéressantes pour chaque phase :

  1. Input
    Les instructions peuvent être produites de différentes manières, par exemple à travers du code ou même à travers une interface graphique créé ponctuellement pour mener à bien un type d'analyse spécifique (voir par exemple les ShinyApps). La production des instructions peut être faite à travers un éditeur de texte avec aide à la syntaxe, étalée sur plusieurs fichiers pour gérer la complexité d'une analyse articulée, ou encore organisée en procédures réutilisables à différents endroits et pour des projets différents.
  2. Computation
    La computation, c'est-à-dire l'interprétation des instructions de Input, peut se faire sur la même machine où les instructions ont été écrites ou sur une ou plusieurs machines différentes. Ceci permet par exemple de lancer des analyses qui nécessitent d'une grande puissance computationnelle sur des serveurs distants, qui tournent pendant plusieurs heures, voire jours, avant d'atteindre le(s) résultat(s) correspondant(s).
  3. Output
    Le résultat des instructions peut être récupéré et affiché de différentes manières, de la plus simple (e.g. résultat textuel dans la ligne de commande) à la plus complexe (e.g. la génération d'un report scientifique intégrant du texte, des figures, références, tableaux, etc.).

L'enjeu principal dans l'utilisation de R réside donc dans la création des instructions instrumentales aux résultats souhaités, un mécanisme qui peut s'avérer complexe car il présuppose la synergie des plusieurs composantes, ainsi que le respect de contraintes spécifiques au niveau de ce qui est considérée une série d'instructions interprétables par R. Nous proposons de suite une description plus approfondie des trois composantes Input-Computation-Output. Ceci nous permettra de préparer le terrain pour des concepts plus spécifiques et récurrents dans l'utilisation de R.

Input : la génération d'instructions valables

Dans la plupart des cas, la génération de l'Input en R se fait à travers l'écriture de code. Les instructions dans le contexte de la programmation sont appelées algorithmes et nous utiliserons les deux termes de manière interchangeable par la suite.

# Instruction 1
# Instruction 2
# Instruction 3
# ...

Comme dans la plupart des langages de programmation, les instructions en R se composent de trois types d'éléments fondamentaux, dont nous proposons ici une simple description pour les aborder de manière plus spécifique par la suite :

  1. Les éléments littéraux
    Dans le cas de R, les éléments littéraux correspondent dans la plupart des cas à des données, mais plus en général il s'agit de tout élément qui représente la valeur elle-même, par exemple 3 représente le chiffre trois.
  2. Les éléments symboliques
    Contrairement aux éléments littéraux, les éléments symboliques font référence à quelque chose d'autre, par exemple des données, structures de données, ou - particulièrement important - des fonctions ou procédures.
  3. Les méta-éléments relatifs au langage
    Il s'agit d'éléments qui servent au fonctionnement interne des instructions, comme par exemple les structures de contrôle ou les boucles.

Éléments littéraux

Les éléments littéraux sont tout simplement des éléments dans les instructions qui représentent exactement la valeur qu'ils affichent. Il s'agit d'une notion plus simple à comprendre qu'à expliquer, mais elle est néanmoins importante à saisir avant d'aborder les éléments symboliques.

En R il existe plusieurs types d'éléments littéraux parmi lesquels les plus fréquents sont :

  • Les nombres entiers, par exemple :
    3, 97, 123456789, etc.
  • Les nombres décimaux, par exemple :
    1.34, 29.781, etc.
  • Les suites de caractères, délimitées habituellement par des guillemets, par exemple :
    "EduTechWiki", "Pas du tout d'accord", "ID-12345", etc.
  • Les valeurs logiques :
    TRUE, FALSE (tout en majuscules et sans guillemets)
  • L'absence d'un élément
    NULL, NA (tout en majuscules et sans guillemets)
Les éléments littéraux sont souvent appelés valeurs dans beaucoup de ressources ou tutoriels.

Éléments symboliques

Les éléments symboliques, au contraire, sont des éléments qui représentent quelque chose d'autre ; on parle souvent à ce propos de référence symbolique pour mettre en évidence cette fonction de représentation. On peut diviser les éléments symboliques en R en deux grandes catégories :

  1. Les données ou structures des données
  2. Les procédures ou fonctions

Références à des données ou structures de données

La manière la plus utilisée pour créer une référence symbolique à des données ou structures des données consiste à utiliser le symbole d'affectation <-, comme par exemple dans le code suivant :

# Affectation/Déclaration d'une référence symbolique avec le symbole d'affectation "<-"
country <- "Switzerland"

Avec cette instruction nous avons créé une liaison (i.e. binding en anglais) entre la référence symbolique country et la valeur littérale, constituée par une suite de caractères, "Switzerland". Métaphoriquement, c'est comme si on avait lié un fil entre le mot country et la suite de caractères "Switzerland". Chaque fois qu'on tire le mot country, nous pouvons récupérer ce qui est de l'autre côté du fil, dans ce cas "Switzerland". Cet exemple nous permet de proposer déjà quelques considérations préliminaire sur ce mécanisme :

  1. L'association entre référence symbolique et la valeur est arbitraire.
    D'un point de vue formel, les associations suivantes sont tout à fait valables :
    country <- "Geneva"
    country <- FALSE
    country <- 3.14
    
  2. L'interprète R doit pouvoir discriminer entre une référence symbolique est une valeur littérale
    Pour cette raison, les références symboliques doivent respecter certaines caractéristiques, comme par exemple :
    • On ne peut pas utiliser des nombres entiers ou décimaux comme noms des références symboliques, car cela créerait un mis-match de ce type :
      # Code NON valable
      13 <- 17
      
    • Les références symboliques ne peuvent contenir des espaces, autrement elles seraient traitées comme deux références symboliques différentes :
      # Code NON valable
      my country <- "Switzerland"
      
    • Elles ne peuvent pas contenir le tiret haut -, car ceci correspond à l'opération de la soustraction, et l'interprète essayerait ainsi de soustraire le deuxième mot du premier :
      # Code NON valable
      my-country <- "Switzerland"
      
    • Les références symboliques peuvent se composer de tous les caractères alphanumériques et peuvent utiliser également le tiret bas et le point, à condition de ne pas commencer avec un chiffre ou le tiret bas. Il est de bonne pratique d'utiliser une stratégie cohérente pour les références symboliques composées de plusieurs mots, par exemple :
      # "Snake case" : séparation par tiret bas (ou underscore) en utilisant que des minuscules
      my_beloved_country <- "Switzerland"
      
      # "Camel case" : utilisation d'une lettre majuscule à partir du deuxième mot composé
      myBelovedCountry <- "Switzerland"
      
  3. Les références symboliques peuvent se référer à d'autres références symboliques.
    Dans ce cas, c'est comme si vous attachez un fil à un autre fil et ainsi de suite. Le risque, très élevé d'ailleurs dans des analyses complexes, est de finir par emmêler les fils et se désorienter parmi les différentes références symboliques :
    first_country <- "Switzerland"
    second_country <- "France"
    
    my_beloved_country <- first_country
    

Lorsque les données partagent une relation sémantique entre elles, elles peuvent s'organiser en structures de données. Parmi ce structures on retrouve :

  • Les vecteurs
  • Les matrices
  • Les data frames
  • Les listes

Les différences techniques entre ces structures dépassent les objectifs introductifs de cet article, mais il est néanmoins important de sensibiliser au fait que selon le type de structure utilisée, il sera possible (ou impossible) d'effectuer certaines procédures ou fonctions, et que certaines structures n'acceptent que des éléments du même type (e.g. que des suites de caractères ou que des chiffres).

Le code suivant associe à la référence symbolique my_beloved_countries un vecteur composé par 3 suite de caractères :

my_beloved_countries <- c("Switzerland", "France", "Italy")

La notation c() est un exemple de procédure ou de fonction que nous allons aborder dans le point suivant.

Références à des procedures ou fonctions

L'autre catégorie d'éléments symboliques est représentée par les procedures, également appelées routines, ou plus simplement identifiées en tant que fonctions. Si les données et structures de données jouent un rôle principalement statique, de stockage de l'information, les fonctions servent à manipuler l'information, afin de passer progressivement à des stades qui s'approchent au résultat souhaité.

En raison de leur caractère dynamique, les fonctions ont un cycle de vie qui se compose de deux stades :

  1. Une phase de définition de la fonction, dans laquelle on définit ce que la fonction est censée faire
  2. Une phase d'invocation de la fonction, dans laquelle la fonction est intégrée dans la suite d'instructions

La phase de définition est unique, c'est-à-dire qu'elle est faite une fois seulement, en amont de l'invocation ; l'invocation, au contraire, peut se faire à plusieurs reprises.

# Définition d'une référence symbolique à une procedure
my_procedure <- function () {
  # Do something
} 

# Invocation de la fonction à plusieurs reprises
my_procedure()
my_procedure()
my_procedure()

Comme on le verra plus bas dans la page, R possède déjà plusieurs fonctions qui ont été définies en amont par les créateurs du langage, et d'autres encore sont disponibles dans des paquets externes. Il est tout à fait possible d'utiliser R sans faire recours à la phase de définition des fonctions, mais en se limitant à invoquer des fonctions déjà définies. Même dans ce cas, néanmoins, il est important de bien saisir le caractère symbolique des fonctions qui suit le même principe des références symboliques aux données : l'association entre le nom de la fonction et son comportement est arbitraire, mais le nom de la fonction doit respecter les mêmes contraintes, afin que l'interprète de R puisse l'identifier en tant que telle.

L'un des intérêts principaux des fonctions et la manipulation des éléments littéraux, que ce soit directement, ou indirectement à travers les références symboliques. Le code suivant illustre ce principe à travers la fonction mean() qui accepte des données de type numérique en tant que Input et affiche la moyenne arithmétique en tant que Output :

# Fonction invoquée avec de nombres littéraux
mean(c(1, 2, 3, 4, 5, 6))

# Fonction invoquée avec une référence symbolique
my_numbers <- c(1, 2, 3, 4, 5, 6)
mean(my_numbers)

Méta-éléments relatifs au langage

Pour qu'un langage de programmation puisse fonctionner, il faut également des repères internes qui permettent de combiner les instructions. Nous avons déjà vu un de ces éléments avec le symbole d'affectation <-. Ce symbole n'est ni une valeur littéral, car il ne représente pas vraiment ces deux caractères, ni une référence symbolique, car il ne se réfère à aucun autre élément. Il s'agit plutôt d'un élément interne au langage qui permet de définir une référence symbolique :

reference_symbolique <- "valeur littéral"

Un langage de programmation présente plusieurs méta-éléments, dont les plus fréquents sont par exemple :

  • Les structures de contrôle
    Elles permettent d'exécuter (ou pas) certaines instructions en fonction de conditions spécifiques
    # Récupérer l'heure actuelle sous forme 00-23
    current_hour <- format(Sys.time(), "%H")
    
    # Afficher un message en fonction de l'heure actuelle
    if(current_hour < 12) {
      print("Good morning!")
    } else {
      print("Good afternoon!")
    }
    
  • Les boucles
    Elles répètent des instructions pour un nombre définit de fois, par exemple :
    # Afficher le message "I am counting... X" pour dix fois en substituant X par l'itération actuelle
    for(n in 1:10) {
      print(paste("I am counting...", n ))
    }
    

Il est tout à fait possible d'utiliser R en utilisant un nombre très limité de méta-éléments, par exemple seulement le symbole d'affectation <-. Mais parfois il est nécessaire d'avoir des instructions plus complexes, et à ce moment les méta-éléments deviennent indispensables.

Combiner éléments littéraux, symboliques et méta-éléments

L'intérêt principale du codage réside dans la possibilité de combiner les éléments littéraux et symboliques, et éventuellement les méta-éléments. Par exemple, dans une analyse standard dans le cadre des sciences sociales, on peut identifier les étapes suivantes :

 1 # 1. Créer une référence symbolique à des données et utiliser une procédure pour récupérer les données depuis un fichier
 2 my_data <- read.csv(file="my_file_with_data.csv", header=TRUE, sep=",")
 3 
 4 # 2. Contrôler certaines caractéristiques des données
 5 if(nrow(my_data) < 10) {
 6   print("The data has less than 10 rows, is it worth it?")
 7 }
 8 
 9 # 3. Manipuler les données à l'aide d'autres procédures pour atteindre les finalités souhaitées
10 print(my_data)
11 summary(my_data)

Même sans connaître tous les détails du code, nous pouvons néanmoins décrire son fonctionnement à l'aide des notions que nous avons vues plus haut :

  • Nous créons une référence symbolique nommée my_data
  • Nous utilisons une fonction appelée read.csv() qui acceptent certains arguments, comme par exemple le chemin/nom du fichier
  • Ce fichier contient très probablement une structure de données, c'est-à-dire des éléments littéraux organisés d'une certaine manière, par exemple en lignes et colonnes. Par conséquent, la référence symbolique my_data est maintenant liée à une structure de données.
  • À travers le méta-élément if(...) { ... }, combiné à la fonction nrow(...), nous pouvons contrôler si les données ont moins de 10 lignes, et le cas échéant afficher un message d'alerte (lignes 5 à 7)
  • À travers des procédures comme print(...) ou summary(...) nous pouvons manier les données selon les intérêts de l'analyse (lignes 10-11)

Computation : l'interprétation d'une série d'instructions

La phase de computation est celle qui ne nécessite pas d'intervention humaine, car elle est automatisée et déterminée par les inputs qui ont été données par les instructions. La computation se fait à travers la lecture des instructions et leur évaluation. Bien que la lecture des instructions se fasse de manière linéaire, de gauche à droite et du haut vers le bas, l'évaluation du code suit des règles de précédence qui sont établi par le langage de programmation. Par exemple dans le mécanisme d'affectation de la référence symbolique sum_the_numbers, l'addition des deux chiffres est faite en amont de l'affectation :

sum_the_numbers <- 10 + 20

De cette manière, sum_the_numbers équivaut à 30. S'il n'y avait pas de mécanisme de précédence dans l'évaluation du code, on aurait eu d'abord l'affectation à 10, et ensuite l'addition de 20 en dehors de l'affectation :

sum_the_numbers <- 10
sum_the_numbers + 20

Dans ce cas, la valeur de sum_the_numbers serait restée à 10, et l'addition suivante de 20 n'aurait pas été retenue par la référence symbolique.

Un autre exemple de ce mécanisme concerne l'emboîtement des fonctions à l'intérieur d'autres fonctions : une pratique assez fréquente dans l'utilisation de R qui peut donner des soucis mêmes à des utilisateurs expérimentés. Par exemple dans le code

min(sum(1, 2), sum(3, 4))

L'ordre d'exécution des fonctions est le suivant :

  1. D'abord la somme entre 1 et 2 : sum(1, 2) = 3
  2. Ensuite la somme entre 3 et 4 : sum(3, 4) = 7
  3. Et enfin le choix du chiffre minimal entre les deux : min(3, 7)

Contrairement à la lecture humaine qui se fait de gauche à droite, l'exécution des instructions se fait plutôt de l'intérieur vers l'extérieur :

# Lecture humaine
first(second(third(symbolic_reference)))

# Exécution du script
third(second(first(symbolic_reference)))

L'un des enjeux majeurs dans la programmation à travers du code est celui de définir l'évaluation des étapes intermédiaires dans le bon ordre, afin que les procedures suivantes puissent s'appuyer sur les étapes précédentes. Si ceci peut sembler élémentaire dans le contexte d'un nombre limité d'instructions, cet aspect devient primordiale lors que l'analyse est étalée dans un grand nombre d'instructions.

Computation et environnement

Interface de RStudio qui affiche les éléments disponibles dans l'environnement, qui est vide pour l'instant.

La phase de computation est étroitement liée à l'un des concepts clés de l'utilisation de R : l'environnement. Si on se réfère souvent aux algorithmes (i.e. les instructions) comme les étapes dans une recette de cuisine, dans la même similitude l'environnement correspond à l'état de la cuisine, et de ses éléments, à un moment donné dans le temps. En d'autres termes, lorsque les instructions se réfèrent à des éléments, ces éléments doivent :

  1. Exister dans l'environnement pour que l'interprète puisse les identifier, les récupérer et les intégrer dans la computation ;
  2. Refléter leur état au moment précis dans lequel ils sont utilisés, c'est-à-dire refléter les éventuelles manipulations qui ont été apportées à l'élément par des instructions précédentes (e.g. même si on se réfère toujours à la viande dans une recette de cuisine, elle ne sera pas dans le même état au début de la recette, quand elle sort du frigo, ou à la fin, après qu'elle a été cuite).

L'utilisation de RStudio est très utile pour l'explication de ce concept, car le logiciel consacre une partie de l'interface à l'environnement. Cette partie de l'interface (dans l'image a côté), qui se trouve habituellement sur le côté droit, affiche les éléments qui font partie de l'environnement après l'exécution des instructions. Au début, lorsque aucune instruction n'a pas encore été exécutée, l'environnement global résulte vide (même si on verra plus bas que ce n'est pas vraiment le cas).

Instructions et environnement

Le rapport entre instructions et environnement peut se faire de 4 manières :

  1. Aucune relation entre les instructions et l'environnement
    C'est le cas le moins intéressant et qui est rarement utilisé dans la réalité, mais il est utile de le citer au niveau conceptuel. Vous avez ce cas de figure lorsque vos instructions ne contiennent aucun référence symbolique, comme par exemple dans un calcul entre chiffres littéraux :
    146 + 173
    
    Si vous lancez cette instruction, vous allez obtenir le résultat ([1] 319), mais l'environnement ne sera pas concerné. Vous pouvez en avoir la confirmation en regardant le panneau Environment en RStudio qui n'aura pas changé.
  2. Modifier l'état de l'environnement
    Les instructions agissent sur l'environnement en modifiant son contenu. Par exemple :
    • Ajouter un élément à l'environnement à travers l'affectation d'une nouvelle référence symbolique
      new_element_in_environment <- "I have been added to the environment"
      
      Lorsque vous exécutez cette instruction, vous pouvez bien noter que dans le panneau Environment de RStudio apparaît maintenant un nouveau élément.
      Nouveau élément dans l'environnement
    • Mettre à jour la valeur d'un élément dans l'environnement
      new_element_in_environment <- c(10, 20, 30, 40, 50)
      
      Si l'élément existe déjà, sa valeur peut être changée dans n'importe quel autre type de donnée. Dans ce cas, par exemple, la référence new_element_in_environment est passée d'une suite de caractères à un vecteur numérique.
      Les éléments dans l'environnement peuvent être affectés à des nouvelles valeurs.
    • Supprimer un élément de l'environnement
      rm(new_element_in_environment)
      
      Les éléments peuvent être supprimés, par exemple en utilisant la fonction rm(), abbreviation de remove. De cette manière, il ne sont plus disponibles dans l'environnement et la mémoire qui leur avait été dédiée est libérée.
  3. Récupérer des informations depuis l'environnement
    Cette opération est plus étroitement liée à la phase de Output que nous verrons plus bas, mais elle figure également ici car on peut la combiner avec de la computation, par exemple dans l'exemple suivant :
    # Créer une référence symbolique à un vecteur numérique avec des notes
    my_grades <- c(5.5, 4.25, 6, 5.75, 4)
    # Récupérer la note la plus élevée
    max(my_grades)
    
    La fonction max(my_grade) récupère depuis l'environnement les informations stockées dans la référence symbolique my_grade, mais ne modifie pas son contenu, ni apporte d'autres changements à l'environnement.
  4. Récupérer des informations depuis l'environnement et les utiliser pour modifier l'environnement
    Il s'agit de la stratégie la plus puissante, car elle combine les deux précédentes. L'environnement est adapté progressivement en s’étayant sur l'information précédente pour obtenir la suivante. C'est en général de cette manière que les algorithmes sont construits. D'autre part, c'est également la procedure la plus complexe, car elle nécessite de faire recours à la pensée computationnelle afin de pouvoir réduire la complexité du processus à des instructions plus simples.

Paquets et environnement

L'environnement global n'est pas vraiment vide, il contient déjà à la base des paquets qui permettent d'utiliser des données, références symboliques et, surtout, des fonctions disponibles out of the box

Un autre élément qui est utile à comprendre l'environnement est le concept de paquet, ou package en anglais.

Un paquet est une collection de données, références symboliques et/ou fonctions qui partagent une relation sémantique, généralement identifiable par une finalité plus ou moins large.

Plus haut dans la page, nous avons fait référence au fait que l'environnement global de R, au début, est vide, mais en spécifiant que ceci ne correspond pas vraiment à la vérité. On peut maintenant mieux expliciter cet aspect, qui nous permettra également de comprendre pourquoi R est considéré, à la base, un environnement pour l'analyse statistique. Pour ce faire, nous allons encore une fois faire recours à la partie de l'interface consacrée à l'environnement de RStudio.

À côté du label Global environment s'affiche une flèche vers le bas. Lorsqu'on clique sur cette flèche, on obtient une liste d'éléments qui partagent ce format: package:nom-du-paquet. Chacun de ces éléments de la liste représente justement un paquet qui est disponible out of the box lorsque vous démarrez R. Comme vous pouvez le noter depuis l'image à côté, le premier paquet s'appelle package:stats. Si vous cliquez sur son nom, vous obtenez la liste de tous les éléments qui composent le paquet. Ces éléments sont catégorisé de différentes manières, par exemple le paquet stats propose des Values et des Functions. La différence entre ces éléments n'est pas fondamentale au niveau conceptuel ; l'important est plutôt de retenir qu'il s'agit de références symboliques que nous pouvons intégrer directement dans nos instructions, car l'environnement est conscient de leurs existence et, lorsqu'ils les rencontrent dans l'exécution des instructions, il peut les reconnaître et les intégrer dans la computation.

La présence du paquet stats, de plus, rend explicite pourquoi R est considéré un environnement pour les analyses statiques. En effet, ce paquet contient des fonctions, comme par exemple aov(), acronyme de Analysis of Variance (Analyse de la variance), qui sont normalement utilisées dans l'analyse statistique. D'autres paquets disponibles out of the box incluent par exemple des jeux des données (package:datasets) ou des fonctionnalités graphiques (package:graphics). La fonction mean(), que nous avons utilisée plus haut dans la page, appartient par exemple au paquet package:base. On retrouve dans cet exemple le concept de référence arbitraire qui peut être facilement testée en essayant d'utiliser la fonction average() au lieu de mean() :

numbers <- c(1, 2, 3, 4, 5)
mean(numbers) # Donne le résultat 3
average(numbers) # Donne l'erreur Error in average(numbers) : could not find function "average"

La fonction average() n'est pas reconnue par l'interprète car, contrairement à mean(), elle ne fait pas partie de l'environnement.

Implémenter l'environnement avec des paquets externes

L'articulation entre environnement et paquets nous permet également d'aborder l'un des aspects les plus importants de R : l'utilisation de paquets externes. Utiliser un paquet externe revient tout simplement à peupler l'environnement avec des éléments qui ont été créés par quelqu'un d'autres, et que nous pouvons par la suite utiliser dans notre propre code. Ce mécanisme se fait en deux parties :

  1. Installer le nouveau paquet sur la machine/environnement de travail que nous utilisons
    Cette opération est à effectuer normalement seulement une fois par machine/environnement de travail, même s'il peut s'avèrer nécessaire de la répéter par exemple suite à l'installation d'une nouvelle version de R ou des problèmes de compatibilité. L'installation peut s'effectuer de différentes manières, notamment :
    • À travers du code :
      # Installer le paquet "ggplot2"
      install.packages("ggplot2")
      
    • À travers le menu de RStudio : Tools > Install packages. Il suffit à ce moment de saisir les premières lettre du paquets pour obtenir une liste de suggestions. Vous pouvez noter que, une fois lancé la commande, la console de RStudio affiche tout simplement l'équivalent du code vu plus haut.
    • À travers l'onglet Packages de l'interface graphique de RStudio qui dispose d'un bouton Install. Ce bouton ouvre la même fenêtre modale du point précédent et se traduit exactement dans le même code.
    Dans tous les cas, cette opération aboutit au même résultat : le code du paquet est téléchargé depuis une source externe (par exemple le dépôt officiel CRAN) et mémorisé sur la machine/environnement de travail de l'utilisateur.
  2. Charger (i.e. load en anglais) le paquet dans l'environnement global
    Cette opération doit s'effectuer à chaque fois qu'on souhaite utiliser le paquet dans la session de travail courante (voir point suivant). Encore une fois, elle peut se faire de différentes manières, même si, contrairement à l'installation ou les modalités sont plus ou moins équivalentes, pour cette phase le code est la manière la plus indiquée :
    • À travers le code :
      # Charger le paquet "ggplot2" (qui a été installé au préalable)
      library(ggplot2)
      
    • À travers l'onglet Packages de l'interface graphique de RStudio, en cochant la case à côté du nom du paquet souhaité. Encore une fois, cette opération aboutit tout simplement au même code du point précédent.
    Dans les deux cas, l'opération consiste à rendre disponible à l'environnement global les éléments contenus dans le paquet. Une fois chargé le paquet, vous pouvez bien noter qu'il apparaît avec les paquets disponibles out of the box (voir image plus bas). Ceci confirme que l'environnement est conscient de son existence et que vous pouvez à ce moment utiliser les références symboliques (données et/ou fonctions) du paquet.
    Une fois chargé le paquet, il apparaît dans la liste des paquets disponibles dans l'environnement global.

Le fait que les paquets externes doivent être installés et chargés est parfois vécu comme inutile et rébarbative, surtout par des personnes s'approchant à R pour la première fois et sans une expérience similaire, par exemple, avec des plug-ins dans des logiciels ou des éléments similaires dans d'autres langages (voir par exemples les bibliothèques JavaScript). Cependant ce mécanisme est rendu indispensable par les nombreuses utilisations différentes qu'on peut faire de R, ce qui se traduit par un écosystème très large de paquets externes. Si R avait toujours à disposition tout le code nécessaire aux différentes paquets, il serait beaucoup plus lourd et beaucoup de ressources (espace et computation) seraient utilisées inutilement.

Pour éviter d'oublier de charger des paquets, il est de bonne pratique de les charger tous au début du code, un paquet par ligne :

# Charger tous les paquets nécessaires
library(...)
library(...)
library(...)

# Écrire les instructions
...
...

Computation et session

L'utilisation de R entre deux pôles : programmation interactive et scripting automatisé. Avec l'expérience, le scripting automatisé présente plusieurs avantages.

Un autre concept très important dans l'utilisation de R est le principe de session. Pour bien comprendre cet aspect, il est utile de définir deux modalités d'utilisation de R que nous pouvons placer sur un continuum entre deux pôles (voir figure à côté) :

  • Programmation interactive d'une part, ce qui correspond à écrire et évaluer le code de manière progressive, ligne après ligne (ou presque), afin d'aboutir enfin au résultat souhaité ;
  • Scripting automatisé de l'autre, ce qui correspond à lancer une commande qui évalue toutes les instructions sans interruption jusqu'à ce que l'ensemble du code de l'analyse a été évalué.

Lorsqu'on utilise surtout la programmation interactive, il est nécessaire de pouvoir accéder aux éléments qui font partie de l'environnement, afin de pouvoir les utiliser progressivement dans l'analyse. Comme on l'a vu plus haut, l'environnement est modifié en fonction des instructions qui sont exécutées et par conséquent son état change dans le temps.

Nous pouvons par conséquent définir une session comme une sorte de cycle de vie de l'environnement, pendant lequel l'environnement s'adapte aux besoins du projet.

Dans cette perspective, avec l'expérience il est de bonne pratique de créer des scripts automatisés qui déterminent ce cycle de vie du début à la fin. Cette démarche à l'avantage de pouvoir répliquer l'environnement exactement de la même manière à chaque exécution du script.

D'ailleurs, l'une des commandes les plus utilisés par les utilisateurs fréquents de R concerne justement la termination de la session courante et la création d'un nouveau cycle de vie, qui démarre avec l'environnement global vide (à l'exception des paquets qu'on a vu plus haut). Cette commande est disponible dans l'interface RStudio :

  • À travers le menu Session > Restart R
  • Le raccourci du clavier
    Ctrl + Shift + F10 pour Windows
    Cmd + Shift + F10 sur Mac

L'utilisation de cette commande, combiné à l'écriture du code source dans des fichiers réutilisable, permet de libérer l'environnement de tout élément non répertorié dans le code (e.g. crée pour un essaie) et d'éviter par conséquent que la session ne puisse pas être reproduite exactement de la même manière dans le futur.

Computation et problèmes (debug)

La phase de computation est également celle dans laquelle les problèmes, ou bugs, apparaissent. On peut identifier deux grandes catégories de problèmes :

  1. Interprétation impossible
    Dans ce cas, l'interprète R n'arrive pas à comprendre les instructions et par conséquent ne peut pas exécuter la computation. L'interprétation s'arrête à un instant précis dans l'exécution et affiche un message d'erreur.
  2. Interprétation non conforme aux intentions
    Dans ce cas, l'interprète R arrive à déceler les instructions, mais l'exécution est difforme par rapport aux attentes de la personne qui les a écrites. Il s'agit d'une sorte de problème de communication entre la personne et l'interprète. Contrairement aux erreurs de la catégorie précédente, l'interprétation ne s'arrête pas, ce qui rend ce type d'erreurs plus difficile à déceler.

Dans le reste de cette section nous approfondirons les deux catégories à l'aide de quelques simples exemples.

Interprétation impossible

Il existe différents types d'erreurs dans le code qui rendent l'interprétation du code impossible, et par conséquent arrêtent l'exécution de la computation, comme par exemple :

  1. Les erreurs de syntaxe
    Il s'agit d'erreurs très fréquents qui vont interromper le script et afficher un message d'erreur. Dans le code suivant, par exemple :
    # Code non valide
    > a <_ 6
    Error: unexpected input in "a <_"
    
    l'interprète ne reconnaît pas le symbole <_ (tiré-bas au lieu de l'affectation <-). Par conséquent, il arrête l'exécution de la computation et affiche le message d'erreur à la console.
  2. Les instructions incomplètes
    Lorsque les instructions ne respectent pas exactement le pattern, mais n'ont pas d'erreurs syntaxique, l'interprète R les considère en tant que incomplètes et par conséquent propose de pouvoir les continuer avec un + au début de la ligne suivante. Dans l'exemple, l'instruction
    mean(c(1,2,3)
    
    manque d'une parenthèse ) finale. Pour cette raison, la ligne de commande affichera le code suivant :
    > mean(c(1,2,3)
    +
    
    Le + signifie que l'interprète s'attende encore des instructions. Si vous ajoutez la ) finale, le code s'exécutera normalement.
  3. Référence symbolique inexistante ou non existante dans l'environnement
    Une autre source d'erreur très commune concerne l'utilisation d'une référence symbolique, y compris les fonctions, qui est totalement inexistante ou qui n'a pas été chargé dans l'environnement (e.g. le paquet n'a pas été appelé avec library(...). Par exemple :
    # Fonction (très probablement) inexistante à cause d'un simple erreur de syntaxe (meean au lieu de mean)
    > meean()
    Error in meean() : could not find function "meean"
    
    # Fonction non existante car le paquet n'a pas été chargé
    > ggplot()
    Error in ggplot() : could not find function "ggplot"
    
    Vous pouvez noter que le message d'erreur est exactement le même, car l'interprète ne peut pas faire de distinction entre les deux fonctions. Elles sont les deux inexistantes pour lui. Par contre, l'utilisateur sait que la fonction ggplot() existe, mais il a oublié de chargé le paquet correspondant avec library(ggplot2)

Interprétation non conforme aux intentions

L'interprétation non conforme aux intentions se passe lorsqu'il y a un mis-match entre l'intention de l'utilisateur et ce qui est computé, sans qu'il y ait des erreurs formels qui arrêtent l'exécution. Ce type de bugs sont très difficile à identifier, souvent même pour des utilisateurs expérimentés, car ils dépendent des intentions spécifiques au code en question. On peut néanmoins donner quelques exemples pour faciliter la compréhension de cet aspect :

# L'utilisateur veut calculer la moyenne de ses 6 notes d'un cours pour voir s'il réussit, mais il oublie de les mettre dans un vecteur
my_mean <- mean(3, 5.5, 5.5, 4.75,  5.75, 6)
# Contrôler si la moyenne est supérieur à 4
if(my_mean < 4) { 
  print("See you next year!")
} else { 
  print("Yes, you've got it!")
}

Le résultat de cette computation est "See you next year", même si la moyenne de l'utilisateur est en réalité de 5.08, donc largement suffisante pour passer le cours ! Un contrôle sur la valeur de my_mean permet de s'apercevoir que, contrairement aux attentes, elle est de 3 ! Pour comprendre ce résultat il faut connaître le fonctionnement de la fonction mean(...), plus particulièrement le fait qu'elle :

  1. s'attende à un vecteur en tant que premier argument
  2. accepte des autres arguments après le premier

Le bug s'explique par le fait que l'interprète de R considère le premier chiffre 3 en tant que vecteur (d'un seul élément dans ce cas), et tous les autres chiffres des arguments de la fonction. Donc il calcule la "moyenne de 3", qui est effectivement 3. Par conséquent my_mean assume la valeur de 3 et, dans la structure de contrôle suivante, my_mean est mineur de 4, d'où le message "See you next year".

Éviter ce type de bugs est beaucoup plus compliqué par rapport à rencontrer des erreurs de syntaxe ou qui en tout cas arrête l'exécution du script. Surtout dans des longs scripts automatisés, la tâche de trouver l'instruction qui génère le bug peut nécessiter de beaucoup de temps et d'effort. Il est également très difficile de donner des conseils pour identifier ces problèmes. En général, les éléments suivants peuvent d'être d'aide :

  • Commencer avec une approche "programmation interactive" qui exécute chaque instructions à la fois et observe le résultat avant d'intégrer le script automatisé.
  • Restreindre le plus possible les outcomes probables de vos résultats, tout en sachant bien évidemment que si vous connaissiez déjà le résultat final, la computation serait inutile. Mais si vous vous attendez une moyenne autour de 50 et vous obtenez 500, il y a probablement une erreur quelque part.
  • Faire relire son code par un collègue ou superviseur. Dans ce cas il faut souvent vaincre une certaine réticence à montrer du code, mais on peut tout simplement penser que le code est un élément du projet à part entière, tout comme les références théoriques ou l'écriture du texte.
  • Avec l'expérience, il y a la possibilité de mettre en place des systèmes de test automatisés, mais ceci dépasse largement le contexte introductif de cet article. Nous le citons néanmoins pour montrer que même les utilisateurs expérimentés ont raison de douter de leurs propres computations.

Output : la mise à disposition du résultat souhaité

Distinction entre Outputs persistants et volatils à travers l'interface graphique de RStudio

Input et Computation sont les antécédents nécessaires à ce qui, dans la plupart de cas, représente l'objectif de l'utilisation de R : obtenir le résultat souhaité. On peut identifier deux grandes types de Output :

  1. Les Outputs provisoires ou à court terme
    Ils représentent des formes intermédiaires qui sont instrumentales - par exemple, en s'approchant progressivement - au résultat souhaité ;
  2. Les Outputs définitifs ou à long terme
    Ils représentent des formes durables dans le temps, comme par exemple le résultat d'une analyse à insérer dans un article, un graphique à ajouter à une présentation, un fichier nettoyé de données, etc.

Si vous utilisez RStudio, il est possible de schématiser cette distinction à travers l'interface graphique du logiciel, tout en sachant qu'il existe des recouvrements selon les contextes et habitudes des utilisateurs. Néanmoins, on peut identifier :

  • Le code dans les fichiers comme étant destiné à une persistance dans le temps, et par conséquent plutôt en relation avec des Outputs définitifs ou à long terme
  • Le code dans la console comme étant volatile (il disparaît à une nouvelle session de R), et par conséquent plutôt en relation avec des Outputs provisoires ou à court termes.

La partie de l'interface consacrée aux graphiques peut également être considérée comme en relation avec les Outputs volatiles, car idéalement pour la persistance des représentations graphiques il faudrait plutôt les sauvegarder en tant que fichiers (e.g. .png ou .svg).

De suite, nous proposons quelques exemples qui aident à mieux définir la distinction entre les deux catégories.

Outputs provisoire ou à court terme

Ce type de Output est associé en prévalence avec la programmation interactive, c'est-à-dire l'interpellation fragmentée et progressive d'une instruction après l'autre, afin d'obtenir singulièrement des éléments intérêts, comme par exemple :

  • Un aperçu des données à disposition (structure, donnée manquantes ou fausses, etc.)
  • Représentations graphiques exploratoires pour comprendre les données (distribution, stratification, etc.)
  • Réduction des données à indices représentatifs (mesures de tendance centrale comme moyenne ou médiane, écart-type, étendu, etc.)
  • Indices relatifs à la modélisation des données ou inférence statistique (fit, précision, taille de l'effet, p-valeur, etc.)
  • Etc.

Outputs définitifs ou à long terme

Ce type de Output est associé en prévalence avec le scripting automatisé, c'est-à-dire le processus de computation d'un résultat final qui est destiné à persister dans sa forme, par exemple :

  • Nettoyer ou réorganiser un jeu de données qui est ensuite sauvegardé dans un autre fichier
  • Sauvegarder une image ou un graphique dans un fichier correspondant (e.g. image matricielle ou image vectorielle)
  • Générer un article de recherche complet qui intègre plusieurs types d'éléments (texte, images, tableaux, ...)
  • Générer un site web ou un blog
  • Créer une application ou une visualisation des données interactives
  • Etc.

Conclusion

Cette page a proposé une introduction conceptuelle à R en suivant le schéma de base de la programmation Input-Computation-Output. À travers cette organisation, nous avons illustré comme :

  • la phase de Input consiste à formuler les instructions valables pour obtenir le résultat souhaité à travers l'écriture de code qui respecte les règles syntaxique du langage de programmation R :
  • la phase de computation concerne l'évaluation des instructions par l'interprète de R. Ce processus mobilise différents éléments qui doivent s'aligner correctement pour ne pas générer des erreurs ou des résultats différents des intentions.
  • la phase de Output recompense les efforts avec le résultat souhaité qui peut assumer différentes formes, des plus simples au plus complexes.

Une compréhension conceptuelle de R n'est pas suffisante sans la pratique. Néanmoins, les notions abordées dans cet article peuvent vous accompagner dans la pratique en tant que référence pour identifier processus, difficultés et potentialités de l'utilisation de R, que ce soit pour utiliser R dans la recherche, utiliser R dans l'enseignement et la formation ou dans d'autres contextes encore.