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”:
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))
}
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:
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 fonctionsexportPattern()
: export de toutes les fonctions matchant un pattern donnéexportClasses()
: export des classes S4exportMethods()
: export des méthodes S4S3method()
: export des méthodes S3import()
: import de toutes les fonctions d’un packageimportFrom()
: import de certaines fonctions d’un packageimportClassesFrom()
: import de classe S4importMethodsFrom()
: import de méthode S4useDynLib()
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()
oupar()
, il est recommandé de sauvegarder les paramètres précédant et de les réinitialiser après votre traitement avec la fonctionon.exit()
qui exécute une expression quand la fonction courante se termine
param<-par()
oldpar<-param$mai
on.exit(par(oldpar))