# path to the testPackage directory
packagePath <- "testPackage"
print(getwd())
[1] "/home/jchiquet/work/StateOfTheR/finistR2018/finistr2018_website"

Objectif

L’objectif est de faire un package qui soit:

  • Ecrit de manière standardisée (utilisation de lintr)
  • Documenté (utilisation du package roxygen2)
  • Testé (utilisation du package testhat)
  • Transportable d’une plateforme à l’autre (utilisation de la plateforme Travis, )
  • Potentiellement capable de faire un qqplot correct en ggplot, mais ça, c’est du bonus!

Création du package

Création d’un package par défaut. On le met sur github.

  • New Project -> From Empty directory -> R package

Cela crée un dossier minimal contenant:

  • Un fichier DESCRIPTION

    Package: testPackage
    Type: Package
    Title: What the Package Does (Title Case)
    Version: 0.1.0
    Author: Who wrote it
    Maintainer: The package maintainer <yourself@somewhere.net>
    Description: More about what it does (maybe more than one line)
    Use four spaces when indenting paragraphs within the Description.
    License: What license is it under?
    Encoding: UTF-8
    LazyData: true
    Imports:
    car
    RoxygenNote: 6.1.0
    Suggests: knitr,
    rmarkdown,
    testthat
    VignetteBuilder: knitr
  • Un dossier R qui contiendra toutes les fonctions du package;

  • Un dossier man qui contiendra les fichiers .Rd (la documentation) des fonctions du package. Le package central est devtools qui réunit un ensemble de fonctions pour la création d’un package.

Le package testPackage

Ce package a pour objectif d’obtenir de manière ultra rapide les estimations dans un modèle de régression linéaire. Il ne contient qu’une seule et unique fonction, centrale, la fonction estimate, codée comme ceci:

estimate <- function(y,x){
  return(lm(y ~ x)$coefficients)
}

Afin de pouvoir utiliser ce package, il faut le “construire” (avec le bouton Build dans RStudio, ou Ctrl-Shift-B), ou encore:

devtools::build(packagePath)

Il est important de vérifier régulièrement que tout va bien dans le package,

  • Que la doc est bien renseignée;

  • Que les fonctions (et les exemples associées) tournent;

  • Tout simplement que le package se construit toujours!

Pour ça il faut faire de réguliers check.

devtools::check(packagePath)

Déployer rapidement un package sur son github personnel

Pré-requis : git bien configuré sur la machine (en particulier user.name et user.email !)

Expliqué dans https://r-mageddon.netlify.com/post/writing-an-r-package-from-scratch/ à partir de Step 6.

Utilise le package usethis et créer automatiquement votre repository github complet du package en… 3 lignes de commande R.

library(usethis)
devtools::use_git()
usethis::browse_github_pat()

Github s’ouvre dans le browser, le but est de générer un token afin de lier votre compte github et le package. Les paramètres de création du token sont déjà cochés par défaut, copier le token généré et l’insérer à la place de xxxtokenxxx ci-dessous.

devtools::use_github(protocol="https", auth_token="xxxtokenxxx")

Standardisation du code

J’ai envie de faire en sorte que ce package soit proprement écrit, c’est à dire selon certaines normes d’écritures. Ces normes d’écriture sont rassemblées dans le package lintr.

library(lintr)
lint_package(packagePath)

Il est possible de Je peux ainsi modifier mon code en conséquence en ajoutant un espace entre x et y.

Documentation

Aide aux fonctions

Un bon package doit être bien documenté. Pour cela, il faut installer le package roxygen2 qui offre une syntaxe user-friendly permettant de commenter le code tout en faisant la doc.

#' Ultra fast equivalent of fonction coeff()
#'
#' Mimicks coeff() function but with greater SWAG
#'
#' @param x regressors
#' @param y observations
#'
#' @return Estimated coefficients of linear regression, using least square loss
#' @seealso lm function (deprecated)
#' @examples
#' x <- seq(0, 1, length = 10);
#' y <- 1 + 2 * x + rnorm(length(x), 0, 0.1)
#' estimate(y, x)
#' @export
estimate <- function(y,x){
  return(lm(y ~ x)$coefficients)
}

Vignettes

On peut également créer une vignette (en Rmarkdown). Il faut faire attention à ne pas placer le fichier vignette.rmd n’importe où. Toutes les structures nécessaires, en plus d’un squelette à reprendre pour faire sa vignette sont créés par la fonction

devtools::use_vignette()

Cela créé un dossier contenant un fichier .Rmd, le squelette de votre vignette

---
title: "Pour un package propre et transportable"
author: "Marie, Pierre"
date: "2018-09-10"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Vignette Title}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

# Objectif

L'objectif est de faire un package qui soit:

Test des fonctions

Une fonction étant amenée à évoluer, il est utile de garantir qu’au cours du développement du package, elle respecte toujour un certain cahier des charges.

Pour cela on crée des fonction tests.

La première bonne démarche est de ne pas modifier la structure de package de manière anarchique. À cette fin, on utilise à nouveau devtools, et le package testthat.

library(testthat)
devtools::use_testthat()

Cette fonction créé un dossier tests qui contiendra un fichier testthat.R

library(testthat)
library(testPackage) 
test_check("testPackage")

Ce script a pour but d’effectuer tous les tests présents dans le package.

Ceux ci doivent être placés dans un dossier créé par devtools::use_testthat, le dossier tests/testthat. L’architecture des tests est détaillée sur .

En gros, l’idée est de coder des tests unitaires simples (des expectations), au niveau minimal (dans les fonctions). Ces expectations, une fois assemblées donnent un test sur la fonction (le test). Chaque test doit être encapsulé dans un script de type test_nomdutest.R, enregistré dans tests/testthat. Le préfixe test_ étant essentiel. Un fichier test comprend la fonction test_that qui regroupe tous les tests unitaires:

#fichier test_estimate.R
library(testthat)
test_that("If it quacks like a duck, it's a duck", {
  expect_equal("quack", "quack")
  expect_is(estimate(rnorm(10), rnorm(10)), class = "numeric")
})

La fonction devtools::test testera alors tous les fichiers de ce type

devtools::test(packagePath)

Il existe aussi un autre package pour les test unitaires: RUnit. Nous ne l’avons pas explorer.

Il est possible de recenser la proportion des fonctions testées, grâce au package covr. On a ainsi une idée de la couverture des tests du package.

covr::package_coverage(packagePath)

Cet outil est un indicateur (il ne dit rien de la qualité des tests).

Travis

Travis peut être utiliser pour lancer systématiquement des tests lorsque l’on met à jour un repo git. Il faut se créer un compte sur travais en lien avec son compte github.

Ensuite, on trouve de bonnes explications très simples sur (https://juliasilge.com/blog/beginners-guide-to-travis/)

Il faut créer un fichier .travis.yml qui va indiquer à travis comment lancer les tests. Pour le faire on peut utiliser la commande

devtools::use_travis()

cette commande crée le fichier

  # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r

language: R
sudo: false
cache: packages

Il faut ajouter le fichier dans le fichier .Rbuildignore pour qu’il soit ignoré lors de la construction du paquet ( (c) Paul Bastide).

Par défaut, les warnings sont traités comme des ereurs, pour éviter ce comportement par défaut il faut ajouter warnings_are_errors: false dans le fichier .travis.yml