Chapitre 4 Comment renforcer la réplicabilité?

4.1 Conflits entre packages

Lorsqu’on utilise une fonction issue d’un package, il est recommandé de mettre le nom du package suivi de ::. Cela alourdit quelque peu le code mais permet à la personne reprenant celui-ci de comprendre quelle fonction issue de quel package est utilisée. En effet, cela permet d’être certain que l’on exécute le même script que la personne ayant écrit le code, indépendamment de son environnement. Par exemple, le code suivant:

library(dplyr)
library(MASS)
cars %>% select(dist) %>% head()

produira l’erreur suivante:

## Error in select(., dist): unused argument (dist)

alors que le code suivant:

# On attache les *packages* dans ce sens
library(MASS)
library(dplyr)
cars %>% select(dist) %>% head()

fonctionnera bien.

##   dist
## 1    2
## 2   10
## 3    4
## 4   22
## 5   16
## 6   10

Le code est le même dans les deux cas à l’exception de l’ordre dans lequel les packages sont chargés. Pour éviter cela, la syntaxe avec :: est préférable:

## cars %>% dplyr::select(dist)  %>% head()

fonctionnera quels que soient les packages chargés en mémoire et l’ordre dans lequel ils l’ont été.

Pour les développeurs de packages L’utilisation de l’opérateur :: évite de charger un package. En développement de package on privilégiera systématiquement cette forme. Les packages appelés sous cette forme sont à mettre dans le champ Imports et, il est préférable de n’avoir aucun package dans le champ Depends.

En production L’utilisation de l’opérateur :: est particulièrement importante en production où le code doit être robuste quelque soit l’environnement d’un utilisateur. Une bonne pratique est de systématiquement associer une chaîne de production à une série de fonctions gérées sous forme de packages (cf. formation INSEE Travail collaboratif sous R).

Les utilisateurs de Python sont plus habitués à cette syntaxe rigoureuse:

# Code Python
import numpy as np
np.array([1, 2, 3])

Il n’y a pas d’ambiguïté ici: np.array renvoie à la fonction array dans l’espace np qui est un raccourci comme on l’a déclaré en ligne 1 de numpy. Adopter une approche similaire en R avec les :: est fondamental en équipe.

4.2 Utiliser l’écriture vectorielle

R est un langage vectoriel, c’est-à-dire que les fonctions sont optimisées pour s’appliquer à des vecteurs ou dataframe/matrices (qui sont une collection de vecteurs de taille identique). Il est donc préférable d’éviter les boucles en R qui ne sont pas adaptées à ce langage. Il faut tirer au maximum parti de la vectorialisation de R.

Lorsqu’on doit appliquer de multiples reprises une fonction, on utilisera les fonctions de la famille *apply, par exemple lapply, plutôt qu’une boucle for. Le livre de @efficientR pourra être consulté sur le sujet.

4.3 Gérer la casse

Si vous ne souhaitez pas avoir trop de problème de casse, choisissez la norme de code en underscore (_) et lorsque vous importez des données, transformez tous les noms de variables en minuscules (à l’aide de la commande tolower). Exemple: names(mon_dataframe) <- tolower(names(mon_dataframe)).

4.4 A éviter

  • ne pas utiliser les fonctions attach et detach. On retrouve parfois ce type de gestion de base dans des exemples, mais ce n’est pas robuste pour un statisticien-économiste qui doit souvent gérer plusieurs bases à la fois (dont les noms des variables sont parfois identiques).
  • ne pas utiliser eval(parse(.)) (si vous retrouvez ce genre de recommandation dans des forums, essayez de l’éviter dans la plupart des cas). En effet, il existe généralement une alternative à cette syntaxe qui permet d’utiliser une syntaxe plus claire et plus légère, en faisant appel à des caractéristiques spécifiques des structures de données. Par ailleurs, un code utilisant eval(parse(.)) est plus lourd et plus difficile à débugguer.
  • quand on fait du subsetting en base R, c’est à dire lorsque l’on sélectionne des lignes/colonnes spécifiques grâce à des conditions booléennes, il faut éviter d’imbriquer de nombreuses conditions, cela devient rapidement illisible.
  • Essayer de ne pas utiliser == dans un if ou un while. On écrira if (identical(x, 1)) {...} ou if (isTRUE(x)) {...} plutôt que if (x == 1) {...} ou if (x == TRUE) {...}. Les raisons sont expliquées ici, et liées à la précision numérique de R qui peut amener, par erreur, à déclarer une égalité quand elle n’existe pas.