This book is in Open Review. I want your feedback to make the book better for you and other readers. To add your annotation, select some text and then click the on the pop-up menu. To see the annotations of others, click the in the upper right hand corner of the page

Cours 22 Function creation

22.1 Étapes de développement :

  1. Planifier le travail (pas de programmation encore) :
  • définir clairement la tâche à accomplir par la fonction et la sortie qu’elle doit produire,
  • prévoir les étapes à suivre afin d’effectuer cette tâche,
  • identifier les arguments devant être fournis en entrée à la fonction.
  1. Développer le corps de la fonction
  • Écrire le programme par étapes, d’abord sans former la fonction, en commentant bien le code et en travaillant sur des mini-données test.
  • Pour chaque petite étape ou sous-tâche, tester interactivement si le programme produit le résultat escompté (tester souvent en cours de travail, ainsi il y a moins de débogage à faire).
  1. Créer la fonction à partir du programme développé.

  2. Documenter la fonction.

  3. Tester la fonction : sauvegarder nos tests et bien les structurer, car ils serviront souvent.

  4. Si nous rencontrons des comportements indésirables lors des tests, déboguer la fonction :

  • cerner le ou les problèmes,
  • apporter les correctifs nécessaires à la fonction (que ce soit dans son corps ou dans liste de ses arguments),
  • adapter la documentation et les tests au besoin,
  • faire tourner de nouveau les tests,
  • répéter ces sous-étapes jusqu’à ce que les tests ne révèlent plus aucun problème à régler ou aucune amélioration à apporter.

22.2 Structure :

nomdemafonction<-function(variable1,variable2...)
{
  #ici on met le contenu de la fonction (généralement on effectue des transformations aux variables passées en argument)
  
  
  
  
  
  return(Variabledesortie) ## il s'agit du résultat que va renvoyer la fonction (non négligeable!)! c'est bien le but d'une fct!
}

#une fois la fonction créée on peut l'utiliser:
nomdemafonction(varA,varB,...)

Une fct normalement constituée ne travaille directement que sur ses arguments et non sur les objets stockés dans la console. Tous les objets qui seront utilisés dans le script doivent avoir été créés dans le script. Les seules exceptions à cette règle sont les arguments qui eux sont définis dans l’en-tête.

Exemple

## Calculer le coefficient de variation (CV) (=écart type/moyenne) d'une série de valeur.



cv <- function(x){      ## x est un vecteur contenant une série de valeurs
  moy <- mean(x)        ## moyenne de x
  s <- sd(x)            ## ecart type de x
  rslt <- s/moy         ## calcul du CV
  rslt                  #la fonction retourne le résultat
}

22.3 Nommer ses arguments

Noms habituels : x, y, z: vectors. w: a vector of weights. df: a data frame. i, j: numeric indices (typically rows and columns). n: length, or number of rows. p: number of columns.

Règle : Si les arguments des fonctions appelées sont donnés de la forme name = object, ils peuvent être écris dans n’importe quel ordre. Dans le cas contraire, il faut respecter l’ordre des arguments.

fun1 <- function(data, data.frame, graph, limit) {
  [function body omitted]
}

## Alors la fonction peut être invoquée de plusieurs manières, par exemple:

ans <- fun1(d, df, TRUE, 20) ## arguments dans l'ordre
ans <- fun1(data=d, limit=20, graph=TRUE, data.frame=df) ## arguments dans le désordre et donc nommés

22.4 Valeurs par défaut

fun1 <- function(data, data.frame, graph=TRUE, limit=20) { ... } #on attribue une valeur à l'argument dès l'écriture

22.5 évaluation de la fonction

return() renvoie la dernière valeur calculée

nom_de_fonction <- function(arguments) {
  instructions
  return(valeur) #non négligeable! c'est bien le but d'une fct!
}

22.6 Vérification de la classe des arguments

class(x)

22.7 L’argument ‘fun’

= passer une fonction a une autre fonction en argument

col_summary <- function(df, fun) {
  out <- vector("double", length(df))
  for (i in seq_along(df)) {
    out[i] <- fun(df[[i]])
  }
  out
}
col_summary(df, median)
col_summary(df, mean)

22.8 L’argument ‘…’

Il est possible d’utiliser le symbole ‘…’ (ellipsis) dans les paramètres d’une fct pour indiquer que tous les paramètres supplémentaires seront transmis aux autres fonctions internes.

fun1 <- function(data, data.frame, graph=TRUE, limit=20, ...) {
  [omitted statements]
  if (graph)
    par(pch="*", ...)
  [more omissions]
}

22.9 Types de fonction

22.9.1 Appel récursif

La récursivité est une démarche qui fait référence à l’objet-même de la démarche à un moment du processus (=poupée russe) Une fct peut s’appeller elle-même, tant qu’il existe une condition d’arrêt.

## Scalaire : nombre réel
## Vecteur : serie de valeurs

## Sur scalaire
factorielle <- function(n) {
  if (n==1) resultat <- 1 ## scalaire booléen       ## arrêt de la récursion (n=1 est la condition d'arrêt)
  else resultat <- factorielle(n-1)*n ## appel récursif (si n différent de 1)
  return(resultat)
}
## Sur vecteur
factorielle <- function(n) {
  indice <- (n == 1) ## vecteur de booléens
  if (all(indice)) return(n) ## arrêt de la récursion (n=1 est la condition d'arrêt)
  n[!indice] <- n[!indice]*factorielle(n[!indice] - 1) ## appel récursif (si n différent de 1)
  return(n)
}

22.9.2 Fct anonyme dans une autre fonction

outer(x, y, function(x, y) x * y^2) ## une fonction est mise en argument d'une autre fonction
##      [,1] [,2] [,3]
## [1,]   16   25   36
## [2,]   32   50   72
## [3,]   48   75  108

22.10 Retourner un résultat

  • Une fonction retourne le résultat de la dernière expression du corps de la fct. -> donc il ne faut pas que la dernère expression soit une affectation (<-), sinon on ne pourra pas affecter le résultat à un objet

  • On peut retourner un résultat spécifique, à n’importe quel endroit de la fonction avec la fct return() Si le résultat que l’on veut retourner est codé par la dernière expression, return() est inutile. “return” se met plutot à la fin d’une fct, sauf cas exeptionel (if…), car la fct n’affiche plus les msg/stop si le return s’est effectué.

  • Lorsqu’une fonction doit retourner plusieurs résultats, il est en général préférable d’avoir recours à une liste nommée.

  • Retourner plsrs outputs : Outputs <- list(objt1 = objt1, objt2 = objt2) return(Outputs)

Si une fct retourne plsrs ouputs, elle doit toujours les retourner. S’il ne sont générés que sous condition, créer un objet vide du m^me nom lorsque la condition n’est pas respectée.

22.11 Fonctions de message et d’arrêt de fonction

  • message() = message diagnostique (juste informatif)
  • warning() = message d’erreur (problème non rédhibitoire (ex: syntax))
  • stop() = message d’erreur + arrêt du code (rédhibitoire)

“stop” est une fonction d’erreur, si c’est juste une question de prérequis, il faut utiliser “if” pas “stop”. Un “stop” dans une fonction interne arrête également la fonction dans laquelle elle est inscrite.

“try” : essaye la fonction interne sans stopper la fonction englobante. “catch” : capture le résultat du “try” quel qu’il soit.

  • verbose : par défaut = TRUE dans R donc si on veut tjrs tout afficher il ne sert à rien de le mentionner

  • Pour demander confirmation à l’utilisateur : readline(“pay attention to me!(press enter to continue)”)

22.12 Interaction avec l’utilisateur

fun <- function() {
  ANSWER <- readline("Are you a satisfied R user? ") ## question
  
  if (substr(ANSWER, 1, 1) == "n") ## check the answer
    cat("This is impossible.  YOU LIED!\n") ## re-prompt
  else
    cat("I knew it.\n")
}
if(interactive()) fun()

22.13 Iteration

https://r4ds.had.co.nz/iteration.html

  • itérer n fois : replicate(n, expr, simplify = F) (renvoie une liste) ou purrr::rerun(n, expr) ou purrr::map(1:n, ~ expr) ou lapply(seq_len(n), function(x) fun())
## fun0 a function
## fun1 a a function to iterate fun0 with a n argument
fun1 <- function(n, x) replicate(n, fun0(x = x))
fun1(n, x)
set.seed(1)
fun0 <- function(x) as.list(matrix(c(1, 1, 1, x + rnorm(1)), nrow = 2))
n <- 1000
fun0(1)

library(microbenchmark)

mb <- microbenchmark(
  repl = {replicate(n, fun0(1), simplify = FALSE)},
  reru = {purrr::rerun(n, fun0(1))},
  map = {purrr::map(1:n, ~fun0(1))},
  lap = {lapply(seq_len(n), function(x) fun0(1))},
  times = 1000L
)

mb

mb <- microbenchmark(
  suit = {1:n},
  rep = {rep(1, n)},
  seq_len = {seq_len(n)},
  seq_along = {seq_along (n)}, ## le + rapide
  c = {c(1:n)},
  times = 1000L
)

mb
  • repeat() : répète à l’infini si on ne met pas une condition et un break dedans.
  • Boucle for ou boucle while
  • Encapsuler une boucle dans une fonction
  • apply family (base) : apply(), lapply(), tapply(), etc
  • map family (purrr) (+ rapides car écrite en C): map() (makes a list), map_lgl() (logical vector), map_int() (integer vector), map_dbl() (double vector), map_chr() (character vector). Les map ne produisent que des vecteurs, pas de matrice) Peuvent apliquer des fonctions ou des formules (https://r4ds.had.co.nz/iteration.html).

Les boucles for ne sont plus lentes depuis de nombreuses années. Le principal avantage de l’utilisation de fonctions comme map() n’est pas la vitesse, mais la clarté : elles rendent votre code plus facile à écrire et à lire.

  • Modif de l’output : transposer une liste (inverser la tructure) : purrr::transpose()

22.14 Parallelisation

https://nceas.github.io/oss-lessons/parallel-computing-in-r/parallel-computing-in-r.html https://bookdown.org/rdpeng/rprogdatascience/parallel-computation.html

Why : for time and memory

1 processor = multiple cores = multiple computations to be executed at the same time. R is sequential and uses 1 processor

Plus simple de paralléliser des taches indépendantes, 1 par coeur. 1 tâche = 1 fct

  • Packages : parallel, future, foreach, doParallel
  • diffèrent selon les syst d’exploit on dirait

Parallel functions : - detectCores() : détecte le nbr de coeur de la machine. - mclapply() : utiliser plsrs coeurs sur un ordinateur local (= parallelized version of lapply) (pas sous windows) - makeCluster() and clusterApply() : utiliser plusieurs processeurs sur des machines locales (et distantes) - clusterExport() : à distance

Sous windows on ne peut pas paralléliser en local il faut donc un cluster : - créer un cluster : cl <- makePSOCKcluster(nbr de copies de R à créer) (à défaut d’avoir des coeurs il crée plsrs copies de R) - créer l’envmt du cluster : clusterExport(cl, varlist = c(“var1”, “var2”), envir = environment()) - paralléliser : parLapply(cl, seq_along(iter), myfun) - arrêter le cluster : stopCluster(cl)

https://stackoverflow.com/questions/17196261/understanding-the-differences-between-mclapply-and-parlapply-in-r

library(parallel)
cl <- makePSOCKcluster(4) ## créer un cluster
myfun <- function(i) { Sys.sleep(1); i } ## the function
parLapply(cl, 1:8, myfun) ## parallelization
stopCluster(cl) ## stop the cluster

Sur des boucles : - foreach sequential operator : %do% - doParallel parallelizable operator : %dopar%

## nonparallel for loop
for (i in 1:3) {
  print(sqrt(i))
}
## nonparallel foreach loop
library(foreach)
system.time({
  foreach (i=1:3, .combine=c) %do% { ## .combine=c si on veut unlist l'output, .combine=rbind si on veut un df 
    sqrt(i)
  }
})
## doParallel = foreach parallel adaptor for the 'parallel' package
library(doParallel)
numCores <- parallel::detectCores(logical = FALSE) ## FALSE to have the physical cores

system.time({
  registerDoParallel(numCores)
  foreach (i=1:3) %dopar% {
    sqrt(i)
  }
})
# with foreach
cores <- detectCores()
j = length(lases)

# Create clusters
cl <- parallel::makeCluster(cores)
doSNOW::registerDoSNOW(cl)

# Progressbar
if(interactive()) pb <- txtProgressBar(max = j, style = 3) # if(interactive()) pour qu’elles n’apparaissent pas dans les documents markdown 
progress <- function(n) if(interactive()) setTxtProgressBar(pb, n)
opts <- list(progress = progress)

# The loop
output <- foreach::foreach(
  i=1:j,
  .packages = c("magrittr"), .options.snow = opts
) %dopar% {
  print(i) # to debug
  las_to_dem(lases[i], 
             las_path = las_path,
             mnt_path = mnt_path)
  
}

# close the progressbar
close(pb)
# close the cluster
stopCluster(cl)

When you’re done, clean up the cluster : stopImplicitCluster()

22.15 Choses à savoir

  • Une fct a un environnement propre (ses var sont locales et ne vont pas dans l’envmt de travail): ainsi pas de conflit. Eviter donc d’utiliser des objets provenant de l’envmt de travail. Pour cela le mieux est de garder son environnement global tjrs vide (vider tt l’envrmt global : rm(list = ls()))

  • On peut définir une fonction à l’intérieur (= fonction interne) d’une autre fonction. Cette fonction sera locale à la fonction dans laquelle elle est définie.

22.16 Débogage tips

  • Lorsque Browse[1]> s’affiche dans la console, on peut écrire des commandes utilisant l’environnement de la fonction en bug, et donc la travailler.

  • Un booléen T/F ne marche pas si des NA existe

  • Lorsqu’une fct ne retourne pas le résultat attendu, placer des commandes print à l’intérieur de la fct, afin de suivre les valeurs prises par les différentes variables. On peut même écrire print(1) après la 1ère ligne, print(2) après la 2eme, ainsi de suite pour savoir à quelle ligne se trouve l’erreur.

  • Quand ce qui précède ne fonctionne pas, ne reste souvent plus qu’à exécuter manuellement la fonction :

  • définir dans l’espace de travail tous les arguments de la fonction,

  • puis exécuter le corps de la fonction ligne par ligne. La vérification du résultat de chaque ligne permet généralement de retrouver la ou les expressions qui causent problème.

ou

  • masquez avec le « ## » toutes les commandes que vous venez d’éditer ou rajouter, sauf la 1ere, Sauvez, et testez le fichier afin de voir si cette ligne est correcte. Si « source » fonctionne, enlevez le prochain dièse. N’oubliez pas de sauver et de « sourcer » à chaque test.

22.17 Où écrire ses fcts ?

Dans un fichier .R indépendant nom de fichier = nom de la fonction

22.18 Comment les utiliser ?

Enregistrer le code de la fct dans l’envmt : source(“nomfct/file.R”)

22.19 Plusieurs fonctions internes - passation d’infos

## Main : fonction englobante
main <- function(inventory){
  inventory.volume <- getvolume(inventory)
  rm(inventory)
  inventory.final <- filtervolume(patate = inventory.volume)
  rm(inventory.volume)
  return(inventory.final)
}

## Fonctions internes
getvolume <- function(inventory){
  inventory <- inventory %>% 
    mutate(v = 2*d)
  return(inventory)
}

filtervolume <- function(patate){
  poireau <- patate %>% 
    filter(v > 0)
  return(poireau)
}

## test
inventory <- data.frame(id = 1:10, d = rnorm(10))
main(inventory)