Les bases

Packages indispensables

Pour créer vos propres packages R, les packages usethis, devtools et roxygen vous faciliterons la vie. Commencez donc par les installer:

  install.packages("usethis")
  install.packages("devtools")
  install.packages("roxygen2")
  library(usethis)
  library(devtools)
  library(roxygen2)

Création de l’arborescence de fichier

Créer votre “package directory” contenant les fichiers indispensables à un package R. Supposons que nous voulions créer un package ProjetDataMining qui contiendra un certain nombre de fonctions de base qui nous serons utiles pour le projet (calcul d’un RMSE, MAPE, validation croisée, sous-échantillonnage…).

On commence par créer le dossier “ProjetDataMining” dans notre dossier parent:

mydir <- "/Users/yannig/Documents/Enseignement/2019_2020/M2statML/Rpackages/"
mypackage <- "ProjetDataMining"
path <- file.path(mydir, mypackage)
unlink(path, recursive=TRUE)

my_description<-list("Title" = "Data Mining Project R package",
                     "Version" ="0.0",
                     "Authors@R"= "person('Yannig', 'Goude', email = 'yannig.goude@edf.fr', role = c('aut', 'cre'))",
                     "Description" = "basis R functions for the Data Mining Project, M2 StatML",
                     "License" = "GPL-3"
                     )

# my_description<-list("Title" = "Data Mining Project R package",
#                      "Version" ="0.0",
#                      "Authors@R"= "person('Yannig', 'Goude', email = 'yannig.goude@edf.fr', role = c('aut', 'cre'))",
#                      "Author"= "Yannig Goude <yannig.goude@edf.fr>", 
#                      "Maintainer"= "Yannig Goude <yannig.goude@edf.fr>",
#                      "Description" = "basis R functions for the Data Mining Project, M2 StatML",
#                      "License" = "GPL-3"
#                      )


create_package(path, my_description, open=FALSE)

✔ Creating ‘/Users/yannig/Documents/Enseignement/2019_2020/M2statML/Rpackages/ProjetDataMining/’ ✔ Setting active project to ‘/Users/yannig/Documents/Enseignement/2019_2020/M2statML/Rpackages/ProjetDataMining’ ✔ Creating ‘R/’ ✔ Writing ‘DESCRIPTION’ Package: ProjetDataMining Title: Data Mining Project R package Version: 0.0 Authors@R (parsed): * Yannig Goude yannig.goude@edf.fr [aut, cre] Description: basis R functions for the Data Mining Project, M2 StatML License: GPL-3 Encoding: UTF-8 LazyData: true ✔ Writing ‘NAMESPACE’ ✔ Setting active project to ‘

Les champs ‘Package’, ‘Version’, ‘License’, ‘Description’, ‘Title’, ‘Author’, et ‘Maintainer’ sont obligatoires. Les autres sont optionnels. Les champs ‘Author’ et ‘Maintainer’ peuvent être générés automatiquement à partir de ‘Authors@R’.

Nous avons maintenant dans notre répertoire “…/Rpackages” un dossier “ProjetDataMining” comprenant les fichiers “DESCRIPTION”, “NAMESPACE”, “ProjetDataMining.Rproj” et un dossier vide “R”:

Drawing
Arborescence de base

Ajout de fonctions

Nous pouvons maintenant ajouter des donctions dans le dossier “…ProjetDataMining/R”. Par exemple la fonctions RMSE ci-dessous:

rmse<-function(y,ychap,digits=3)
{
  return(signif(sqrt(mean((y-ychap)^2,na.rm=TRUE))
                ,digits=digits))
}
Drawing

Documentation

Le package roxygen2 simplifie grandement cette étape, en rendant les choses simples et intuitives. L’aide d’une fonction peut être éditée sous forme de commentaires avant chaque fonction dans le fichier “.R” correspondant. Le manuel d’aide est ensuite généré lors de la compilation du package.

Reprenons la fonction RMSE, voilà un exemple de rédaction de l’aide:

#' Root Mean Square Error
#'
#' compute the Root Mean Square Error between a vector y and its forecast yhat
#'
#' @param y the observations to be predicted
#' @param yhat the predictions
#' @param digits the precision in number of digits
#'
#' @return a positive real number the RMSE
#'
#' @examples
#' y<-rnorm(10)
#' yhat<-rep(0,10)
#' rmse(y,yhat,digits=4)
#' @author Yannig Goude <yannig.goude@edf.fr>
#' @export

rmse<-function(y,yhat,digits=3)
{
  return(signif(sqrt(mean((y-yhat)^2,na.rm=TRUE)),digits=digits))
}

il est possible, dans un même fichier .R d’inclure plusieurs fonctions chacune étant documentée.

Ajouter des données

Pour ajouter des données, il suffit d’utiliser la fonction devtools::use_data dont voici un exemple d’utilisation avec les données manipulées en TP:

data0<-read.table("/Users/yannig/Documents/Enseignement/2019_2020/M2statML/TP/data_conso_hebdo0.txt", header=TRUE)
data1<-read.table("/Users/yannig/Documents/Enseignement/2019_2020/M2statML/TP/data_conso_hebdo1.txt", header=TRUE)
elec_consumption <- rbind(data0, data1) 
setwd("/Users/yannig/Documents/Enseignement/2019_2020/M2statML/Rpackages/ProjetDataMining/")
usethis::use_data(elec_consumption)

✔ Setting active project to ‘/Users/yannig/Documents/Enseignement/2019_2020/M2statML/Rpackages/ProjetDataMining’ ✔ Creating ‘data/’ ✔ Saving ‘elec_consumption’ to ‘data/elec_consumption.rda’

Un dossier data contenant les données est ainsi créé à la racine de votre package:

Drawing

Pour documenter les données, il faut créer un fichier .R portant le même nom que votre jeu de données et le placer dans le répertoire /R de votre package.

#' Weekly electricity consumption in France from 1996 to 2009 in MW 
#' meteo and socio-economic variables related to it
#'
#' @format A data frame with 731 rows and 11 variables:
#' \describe{
#'   \item{Time}{time index, in number of weeks}
#'   \item{Day}{Day}
#'   \item{Month}{Month}
#'   \item{Year}{Year}
#'   \item{NumWeek}{The position of the week along the year, from 1/52 the first week to 1 the last week of each year}
#'   \item{Load}{Weekly electricity consumption in France in MW }
#'   \item{Load1}{Lagged one weekly electricity consumption}
#'   \item{Temp}{Temperature in celsius degree}
#'   \item{Temp1}{Lagged one Temperature}
#'   \item{IPI}{Monthly production index in France, provided by INSEE}
#'   \item{IPI_CVS}{Monthly production index in France corrected from seasonal variations, provided by INSEE}
#'   ...
#' }
#' @source \url{EDF R\&D}
"elec_consumption"

Rq: si le fichier DESCRIPTION comprend l’instruction LazyData: true, alors les datasets du package ne sont pas chargés en mémoire lors du chargement du package mais quand vous les chargez explicitement.

Compiler la documentation

Il suffit simplement, pour générer la documentation usuelle de R de lancer les instructions suivantes dans la console:

setwd(path)
document()

Writing NAMESPACE Writing NAMESPACE Writing elec_consumption.Rd Writing rmse.Rd

Cela va automatiquement ajouter un fichier .Rd dans le répertoire man et ajouter un fichier NAMESPACE à la racine du package.

Compiler le package puis l’installer

La encore, c’est très simple. La commande build() permet de compiler le package et de générer le fichier “.tar.gz” de votre package. La commande install() installera votre package parmi vos librairie R.

setwd(path)
build(, quiet=T)

[1] “/Users/yannig/Documents/Enseignement/2019_2020/M2statML/Rpackages/ProjetDataMining_0.0.tar.gz”

install()

checking for file ‘/Users/yannig/Documents/Enseignement/2019_2020/M2statML/Rpackages/ProjetDataMining/DESCRIPTION’ …

✔ checking for file ‘/Users/yannig/Documents/Enseignement/2019_2020/M2statML/Rpackages/ProjetDataMining/DESCRIPTION’

─ preparing ‘ProjetDataMining’:

checking DESCRIPTION meta-information …

✔ checking DESCRIPTION meta-information

─ checking for LF line-endings in source and make files and shell scripts

─ checking for empty or unneeded directories ─ looking to see if a ‘data/datalist’ file should be added

─ building ‘ProjetDataMining_0.0.tar.gz’

Running /Library/Frameworks/R.framework/Resources/bin/R CMD INSTALL
/var/folders/p9/qdsfdn8d4mscm5ckr7h6wgbc0000gn/T//Rtmpm3gZDO/ProjetDataMining_0.0.tar.gz
–install-tests * installing to library ‘/Library/Frameworks/R.framework/Versions/3.6/Resources/library’ * installing source package ‘ProjetDataMining’ … ** using staged installation ** R ** data *** moving datasets to lazyload DB ** byte-compile and prepare package for lazy loading ** help *** installing help indices ** building package indices ** testing if installed package can be loaded from temporary location ** testing if installed package can be loaded from final location ** testing if installed package keeps a record of temporary installation path * DONE (ProjetDataMining)

Vous pouvez ensuite charger votre package et l’exploiter comme un package R du CRAN!

library(ProjetDataMining)
?rmse
y <- rnorm(10)
yhat <- rep(0, 10)
rmse(y, yhat, digits=4)

[1] 0.8491

data("elec_consumption")
?elec_consumption

plot(elec_consumption$Load, type='l')

Metadata: le fichier DESCRIPTION

Dépendances

Dans le fichier DESCRIPTION, il est possible de décrire les diverses dépendances de votre package à d’autres packages R. Il y a 2 notions de dépendance: stricte et faible, gérées respectivement par les champs Imports et Suggests.

Le champ Imports liste les packages dont votre package a nécessairement besoin pour fonctionner. Cela signifie qu’à chaque fois que votre package sera installé, les packages listés ici le seront aussi s’ils ne le sont pas déjà. Il faut noter qu’ajouter cette dépendance ne signifie pas que votre package sera chargé dans l’environnement automatiquement (i.e que l’instruction library(dependant_package) n’est pas automatiquement réalisée). Pour éviter des problèmes, les appels à des fonctions d’autres packages doivent être effectués explicitement dans votre code: package::function().

Le champ Suggests liste les packages pouvant être utilisés par votre package mais pas nécessaire à son fonctionnement. Par exemple des packages contenant des jeux de données tests, ou des packages utilisés dans des fonctions non fondamentales de votre package. Les packages listés dans le champ Suggests ne sont pas importés automatiquement.

Dans le fichier DESCRIPTION vous pouvez gérer ces dépendances en complétant les champs “à la main” ainsi:

Imports:
  opera,
  forecast,

Suggests:
  knn,
  ranger,

Vous pouvez également utiliser la fonction usethis::use_package("opera", "forecast") resp. usethis::use_package("knn", "ranger", type = "suggests") pour le faire.

setwd(path)
usethis::use_package("opera", "forecast")
build(, quiet=T)
install()

Vous pouvez également préciser une version spécifique d’un package:

Imports:
  opera (>= 1.0),

Il existe également le champ Depends pour exprimer des dépendances. Avant R 2.14.0 cela permettait d’exprimer la dépendance à d’autres packages, il faut dorénavant lui préférer Imports ou Suggests. On peut également utiliser Depends pour préciser une version de R spécifique dont dépend le package, par exemple R (>= 3.0.0). Plus précisément, lorsqu’on ajoute un package dans le champ Depends ce package est automatiquement attaché à votre session (ie que l’instruction library(dependant_package) est effectuée) alors que dans le champ Imports il est juste chargé (chargement du code, des données du packages, des DLLs, S3 et S4 mais n’est pas dans le searchpath).

Prenons l’exemple du package mgcv, le fichier de description de ce package est le suivant:

Package: mgcv
Version: 1.8-17
Author: Simon Wood <simon.wood@r-project.org>
Maintainer: Simon Wood <simon.wood@r-project.org>
Title: Mixed GAM Computation Vehicle with GCV/AIC/REML Smoothness
        Estimation
Description: GAMs, GAMMs and other generalized ridge regression with 
             multiple smoothing parameter estimation by GCV, REML or UBRE/AIC. 
             Includes a gam() function, a wide variety of smoothers, JAGS 
             support and distributions beyond the exponential family.
Priority: recommended
Depends: R (>= 2.14.0), nlme (>= 3.1-64)
Imports: methods, stats, graphics, Matrix
Suggests: splines, parallel, survival, MASS
LazyLoad: yes
ByteCompile: yes
License: GPL (>= 2)
NeedsCompilation: yes
Packaged: 2017-02-06 11:03:57 UTC; sw283
Repository: CRAN
Date/Publication: 2017-02-08 21:30:16
Built: R 3.3.2; x86_64-apple-darwin13.4.0; 2017-02-09 11:36:50 UTC; unix
Archs: mgcv.so.dSYM

Le searchpath de ma session R avant le chargement de mgcv est:

search()

[1] “.GlobalEnv” “package:ProjetDataMining” [3] “devtools_shims” “package:roxygen2”
[5] “package:devtools” “package:usethis”
[7] “package:stats” “package:graphics”
[9] “package:grDevices” “package:utils”
[11] “package:datasets” “package:methods”
[13] “Autoloads” “package:base”

et après son chargement:

library(mgcv)
search()

[1] “.GlobalEnv” “package:mgcv”
[3] “package:nlme” “package:ProjetDataMining” [5] “devtools_shims” “package:roxygen2”
[7] “package:devtools” “package:usethis”
[9] “package:stats” “package:graphics”
[11] “package:grDevices” “package:utils”
[13] “package:datasets” “package:methods”
[15] “Autoloads” “package:base”

on constate que les packages mgcv et nlme (champ Depends) ont été ajoutés au searchpath et pas le package matrix (champ Imports).

Titre et description

  • Title: description en une ligne de ce que fait le package.
  • Description: description plus complète d’environ un paragrape.

Auteur

Le champs Authors@R permet de rentrer les informations sur les auteurs/maintainer d’un package. C’est un champs contenant du code R exploitant la fonction person():

Authors@R: person("First", "Last", email = "first.last@example.com", role = c("aut", "cre"))

Il est aussi possible d’avoir 2 champs Author et Maintainer.

Chaque package R doit avoir au moins un auteur et un maintainer, l’auteur doit avoir une adresse email.

Metadata: le fichier NAMESPACE

Le fichier NAMESPACE contient les informations de contexte associé aux objets de votre package R. Par exemple, si l’une de vos fonctions fait appel à une fonction d’un autre package, afin d’éviter toute ambiguité il est préférable de préciser ce package en question. D’un autre côté, si vous souhaitez que votre fonction soit accessible à l’extérieur de votre package ou uniquement en interne vous pouvez le préciser dans le NAMESPACE. C’est les rôle des instructions imports et exports. Les imports définissent comment une fonction d’un package trouve une fonction d’un autre package. Les exports définissent quelles fonctions sont accessibles à l’extérieur de votre package.

Il est recommandé d’exporter un nombre minimal de fonction, cela facilite l’usage de votre package en limitant les interférences et conflits avec d’autres packages.

Il existe 10 instructions de NAMESPACE:

  • exports(): export de fonctions
  • exportPattern(): export de toutes les fonctions matchant un pattern donné
  • exportClasses(): export des classes S4
  • exportMethods(): export des méthodes S4
  • S3method(): export des méthodes S3
  • import(): import de toutes les fonctions d’un package
  • importFrom(): import de certaines fonctions d’un package
  • importClassesFrom(): import de classe S4
  • importMethodsFrom(): import de méthode S4
  • useDynLib() import d’une fonction en C

Voilà un extrait de NAMESPACE du package opera:

# Generated by roxygen2: do not edit by hand
S3method(mixture,default)
export(mixture)
importFrom(graphics,axis)
importFrom(stats,lowess)
importFrom(stats,rnorm)

Le NAMESPACE peut soit être renseigné à la main soit avec roxygen2 en utilisant la syntaxe @export, @import

Recommandations

Lorsque vous éditer un package il faut avoir en tête que votre code doit être portable et utilisable par d’autres, dans des environnements différents du votre. Entre autre, il faut éviter de modifier l’environnement R dans vos fonctions, cela rend le code plus complexe. Les fonctions suivantes sont à proscrire dans un package:

  • library, require: ces fonctions modifient le “search path” de R, et donc quels fonctions sont dans l’environnement global. Pour gérer les dépendances à d’autres package il est préférable d’utiliser le fichier DESCRIPTION de votre package.

  • source(): modifie également l’environnement courant.

  • si vous modifiez l’environnment global avec par exemple options() ou par(), il est recommandé de sauvegarder les paramètres précédant et de les réinitialiser après votre traitement avec la fonction on.exit() qui exécute une expression quand la fonction courante se termine

param<-par()
oldpar<-param$mai
on.exit(par(oldpar))