Parcours
/
R
R
/
Programme
/
Fonctions
Fo

Fonctions en R

1 exercice

À propos de Fonctions

Les fonctions ont été présentées dans le concept Bases, avec des exemples comme celui-ci :

squareit <- function(x) {
    x * x
}

squareit(3)
#> [1] 9

# shorter form
squareit_short <- function(x) x ^ 2

Si l'on regarde de plus près la définition de squareit, on peut identifier plusieurs éléments :

  • La fonction prend un argument formel x.
  • Elle possède un corps, généralement entre accolades { }.
  • Une fois l'objet fonction créé, il est affecté à une variable squareit.

En R, une fonction est un objet de première classe, au même titre que les nombres et les strings. Ainsi, squareit <- function... est une affectation, syntaxiquement identique à x <- 42.

Advanced

Accéder aux composants d'une fonction est rare en usage normal, mais assez facile.

class(squareit)
#> [1] "function"
formals(squareit)
#> $x

body(squareit)
#> {
#>     x * x
#> }

Les arguments obtenus avec formals() ressemblent à une liste, avec la syntaxe $x. En fait, le type est pairlist, un type particulier de liste contenant des paires clé-valeur.

Le body est du code exécutable, de type language (ce n'est probablement pas quelque chose dont tu as besoin de te préoccuper).

Arguments

R ne fait pas de distinction claire entre arguments positionnels et arguments nommés, contrairement à d'autres langages de script comme Python et Julia.

Lors d'un appel de fonction, on peut passer les valeurs par position ou par nom. Cette seconde méthode est utile pour les fonctions complexes qui prennent beaucoup d'arguments, dont l'ordre est difficile à retenir.

f <- function(x, y) x / y

# call positionally
f(4, 2)
#> [1] 2

# call by name
f(y = 2, x = 4)
#> [1] 2

Arguments optionnels

On peut définir des valeurs par défaut pour les arguments dans la définition de la fonction, mais celles-ci doivent venir après tous les arguments qui n'ont pas de valeur par défaut.

On peut alors choisir d'accepter la valeur par défaut ou de la remplacer.

g <- function(x, y = 2) x / y

# default y value
g(6)
#> [1] 3

# explicit y value
g(6, 3)
#> [1] 2

Arguments supplémentaires

Pour accepter un nombre arbitraire d'arguments supplémentaires, utilise un ... (points de suspension) dans la définition. Il est possible de convertir les valeurs supplémentaires d'un appel de fonction en vecteur, mais poursuis ta lecture pour découvrir une autre façon d'utiliser ces « dot args » (appelés « varargs » dans plusieurs autres langages).

var_f <- function(x, y, ...) {
  print(c(...))
}

var_f(2, 3, "opt1", "opt2")
#> [1] "opt1" "opt2"

Environnement de la fonction

Plus haut, on a dit que les arguments formels et le corps sont tous deux des composants d'une fonction.

En fait, il existe un troisième composant : l'environnement dans lequel la fonction est définie.

On peut l'illustrer avec le cas des fonctions imbriquées :

outer_func <- function(x) {
  inner_func <- function(y) {
    x * y
  }
  
  inner_func(3)
}

outer_func(5)
#> [1] 15

L'appel de fonction passe x = 5 à la fonction externe, et cette valeur est disponible dans le corps de cette fonction.

La fonction interne fait partie du corps de la fonction externe et a accès à la valeur de x. Autrement dit, x = 5 se trouve dans l'environnement de la fonction interne.

Techniquement, on appelle cela une fermeture.

L'environnement est particulièrement important avec les « dot args », car les valeurs fournies de cette façon peuvent être transmises aux appels de fonction dans le corps de la fonction. La fonction externe n'a pas besoin de savoir ni de se soucier de ce que signifient les « dot args ».

f_var <- function(x, ...) {
  sum(x, ...)
}

x <- c(1, 2, NA, 6)

# for sum(), na.rm defaults to FALSE
f_var(x)
#> [1] NA

# pass through the na.rm value
f_var(x, na.rm = TRUE)
#> [1] 9

Cette technique est largement utilisée par les bibliothèques du Tidyverse, comme stringr. Beaucoup de fonctions de stringr sont une surcouche conviviale autour de fonctions de bas niveau de stringi et de R de base. Les arguments supplémentaires fournis aux fonctions str_*() sont simplement transmis à ces fonctions de plus bas niveau.

Les fonctions comme valeurs de retour

Une fonction externe peut définir une fonction interne (nommée ou anonyme, comme décrit ci-dessous) et l'utiliser comme valeur de retour.

La fonction renvoyée inclura l'environnement dans lequel elle a été définie.

times_y <- function(x) {
  # anonymous function - see next section
  \(y) x * y
}

f <- times_y(3)
class(f)
#> [1] "function"

f(4)
#> [1] 12

Fonctions anonymes

Quand on définit une fonction, on lie généralement l'objet fonction obtenu à une variable :

x <- 2

# function definition captures x from the environment
doubleit_short <- function(y) x * y

Cela facilite l'utilisation de la fonction plus loin dans le script, mais cette liaison n'est pas obligatoire. Une fonction courte, utilisée une seule fois, peut être utile dans le contexte immédiat. Sans liaison de nom, on parle de fonction anonyme.

L'usage des fonctions anonymes est si courant que (depuis R v4.1.0) il existe une syntaxe abrégée pour les définir : remplace le mot function par un antislash \.

Un exemple de cette syntaxe a été utilisé dans la section précédente, sur les valeurs de retour.

Copie lors de la modification

R permet d'affecter une valeur à des éléments individuels d'un vecteur. Si on passe un vecteur comme argument d'une fonction et qu'on le modifie dans le corps de la fonction avant de le renvoyer, on obtient un vecteur modifié.

Mais qu'est-il arrivé au vecteur d'origine ?

f <- function(vec) {
  vec[1] <- 42
  vec
}

vals <- c(1, 3, 4)

# f() returns a modified vector
f(vals)
#> [1] 42  3  4  

# the original vector is unchanged.
vals
#> [1] 1 3 4

R est un langage conçu pour la science des données. Collecter ces données peut coûter beaucoup de temps, d'efforts et potentiellement des sommes vertigineuses : il est important de ne pas les corrompre !

La règle générale (à quelques exceptions près) est la copie lors de la modification. Si un objet (comme un vecteur) est modifié d'une manière qui pourrait poser problème par la suite, R renvoie une copie modifiée et laisse l'original intact.

Copier de grandes structures de données peut être coûteux en calcul, mais c'est généralement le moindre mal quand l'alternative est la corruption des données.

Caution

Méfie-toi des opérations qui conduisent à copier plusieurs fois les mêmes données.

Pour paraphraser (l'auteur du Tidyverse) Hadley Wickham :

Les boucles ne sont pas lentes en soi, mais elles rendent dangereusement facile l'insertion d'opérations lentes à l'intérieur de la boucle.

La vectorisation ou les fonctions d'ordre supérieur peuvent t'aider à te protéger de ce genre de tueur de performances.

Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet

Apprends Fonctions