Il a été dit plus haut que le fonctions dans R sont un outil important du langage. Le logiciel est riche de fonctions dans l'environnement de base, mais l'utilisateur a néanmoins la possibilité d'adjoindre ses propres fonctions pour faire correspondre les possibilités du logiciel à ses besoins. La manière de procéder est la suivante:
nom_fonction<-function(parametres) { instructions }
Dans cette définition, 'nom_fonction' est le nom qu'on souhaite donner à la fonction, 'parametres' est une liste de paramètres (éventuellement vide) séparés le cas échéant par des virgules, et 'instructions' est une série d'instructions qui définit ce que fait la fonction. La valeur de la fonction est alors la valeur correspondant à la dernière instruction exécutée. Nous allons donner un exemple d'une telle définition:
combin<-function(r,n){factorial(n)/factorial(r)/factorial(n-r)}
A partir de cette définition, il est possible d'utiliser la fonction 'combin' en lui fournissant les deux paramètres habituels, à savoir le nombre d'éléments sélectionnés 'r' et le nombre total d'éléments 'n'. Le second exemple est un exemple un peu plus sophistiqué:
racines<-function(a,b,c){
delta<-b**2-4*a*c;
if (delta<0){
c(); #Pas de racine
}
else{
if (delta>0){
c((-b-sqrt(delta))/2/a,(-b+sqrt(delta))/2/a); # Deux racines
}
else{
c(-b/2/a); #Une seule racine
};
};
}
Plusieurs remarques pour expliquer le fonctionnement de cette fonction. Tout d'abord, la fonction est écrite sur plusieurs lignes, ce qui rend le code plus lisible. Pour introduire la fonction de cette manière, on tape la première ligne (soit 'racines<-function(a,b,c) {' ) puis on valide. R réalise que la définition n'est pas complète et propose à l'utilisateur de continuer la définition en affichant le caractère '+'. Les lignes suivantes sont introduites jusqu'à ce que la fonction soit complètement introduite. R signale alors qu'il est prêt à accepter de nouvelles commandes en affichant le caractère '>' habituel. Venons en au code lui-même. La fonction s'appelera 'racines', et comptera donc 3 arguments, représentés dans cet ordre par 'a', 'b' et 'c'. La première chose qui est faite est de calculer b²-4ac. Cette quantité (qui devrait vous rappeler quelque chose...) est stockée dans une variable nommée 'delta'. Signalons ici que la définition de la variable 'delta' n'est utilisable qu'a l'intérieur du code de la fonction. Par conséquent, invoquer la valeur de 'delta' en dehors du code de la fonction 'racines' ne donnerait aucun résultat. Une variable de ce type, c'est-à-dire définie dans le code d'une fonction, est dite variable locale de cette fonction. On peut définir n'importe quelle type de variable locale, et même éventuellement une fonction locale. Le reste du code de la fonction est une structure du langage R permettant de conditionner l'exécution d'une partie du code à la réalisation d'une condition. La syntaxe de cette structure est la suivante:
if (condition) { instructions_1 } else { instructions_2 }
Pour évaluer cette structure du langage, R procède de la manière suivante:Dans l'exemple qui nous concerne, 'instructions_1' est constitué de la seule instruction 'c( )', et 'instructions_2' est constitué également d'une seule instruction, qui est à nouveau une structure conditionnelle. Cette dernière est évaluée de la même manière que la première: si la condition (delta>0) est vraie, l'instruction 'c((-b-sqrt(delta))/2/a,(-b+sqrt(delta))/2/a)' est exécutée, et dans le cas contraire, l'instruction 'c(-b/2/a)' est exécutée. A ce stade, vous aurez sans doute reconnu l'algorithme de calcul des racines d'un polynôme du second degré ax² + bx + c = 0. La fonction retourne un vecteur qui ne contient rien si (b²-4ac) est négatif, un vecteur avec une seule valeur si (b²-4ac) est nul, et un vecteur avec 2 valeurs si (b²-4ac) est positif.
Un dernier exemple de fonction est celui de l'intégrale: nous allons calculer numériquement l'intégrale de f(x) entre deux bornes, notées 'a' et 'b'. Le principe est très simple: on additionne les surfaces des petits trapèzes dont les 4 sommets sont les points [x , 0], [x , f(x)], [x+d , 0], [x , f(x+d)] où d est un petit incrément. On fera varier x de 'a' à 'b', et d sera un paramètre qu'on pourra réduire pour rendre l'intégrale plus précise (mais plus longue à calculer) si nécessaire. La fonction s'écrit comme suit:
integrale<-function(f,a,b,d){
if (a>b){
-integrale(f,b,a,d)
}
else{
if(a==b){
0
}
else {
if (a+d>b){
(b-a)*(f(b)+f(a))/2
}
else{
trapeze<-d*(f(a)+f(a+d))/2
a<-a+d
trapeze+integrale(f,a,b,d)
}
}
}
}
La première ligne nous est maintenant familière: 'integrale' sera le nom de la fonction, qui comptera 4 arguments, respectivement 'f', 'a', 'b' et 'd'. 'a' et 'b' représentent les limites inférieures et supérieures d'intégration, et 'd' représente l'incrément. 'f' représentera la fonction qu'on souhaite intégrer. Comme on souhaite calculer l'intégrale pour x compris entre 'a' et 'b', il est clair qu'on s'attend à ce que 'b' soit supérieur à 'a'. Si ce n'est pas le cas, il suffit d'inverser le sens d'intégration (de 'b' vers 'a') tout en changeant le signe de l'intégrale. C'est ce qui est fait à la troisième ligne. Par ailleurs, si les deux bornes d'intégration sont identiques, la surface (et donc l'intégrale) est nulle (lignes 6 et 7). Le test suivant regarde si on n'a pas atteint la limite droite (c'est-à-dire 'b') en ajoutant l'incrément 'd' à 'a'. Si c'est le cas, il suffit de définir le trapèze comme ayant une base entre 'a' et 'b' plutôt qu'entre 'a' et 'a+d', ce qui est fait en ligne 11. Sinon, l'idée est de calculer l'intégrale de 'a' à 'b' en sommant la surface du trapèze allant de 'a' à 'a+d' et l'intégrale de 'a+d' à 'b'. La surface du trapèze est calculée en ligne 14 et l'addition des deux surfaces est faite en ligne 16. Remarquez que dans cette définition de la fonction 'integrale', il est fait à deux reprises référence à la fonction 'integrale'... Une fonction qui s'auto-référence est appelée fonction récursive. Pour expliciter la manière dont R fait fonctionner ce type de définition, nous allons illustrer le calcul sur un exemple théorique: supposons donc qu'on veut intégrer f entre 0 et 1 avec un d qui vaut 0.3. Dans ce cas, on demanderait:
integrale(f,0,1,0.3)
integrale(f,0,1,0.3) = trap_1 + integrale(f,0.3,1,0.3)
integrale(f,0,1,0.3) = trap_1 + trap_2 + integrale(f,0.6,1,0.3)
integrale(f,0,1,0.3) = trap_1 + trap_2 + trap_3 + integrale(f,0.9,1,0.3)
integrale(f,0,1,0.3) = trap_1 + trap_2 + trap_3 + trap_4
integrale(f,0,1,0.3) = 0.15*f(0) + 0.3*(f(0.3)+f(0.6)) + 0.20*f(0.9) + 0.05*f(1.0)
Les fonctions définies par l'utilisateur s'emploient exactement de la même manière que les fonctions fournies par le système. Ainsi, par exemple, après avoir introduit les trois fonctions données dans le paragraphe précédent, on peut utiliser ces fonctions de la manière suivante:
n<-10
r<-3
Crn<-combin(r,n)
Crn
## [1] 120
qui est bien le nombre de combinaisons possibles de 3 pris parmi 10.
racines(1,4,3)
## [1] -3 -1
racines(1,4,4)
## [1] -2
racines(1,4,5)
## NULL
On peut vérifier que:
droite<-function(x){x+2}
integrale(droite,0,1,0.3)
## [1] 2.5
normale_standard<-function(z){exp(-0.5*(z**2))/sqrt(2*pi)}
integrale(normale_standard,-1.645,1.645,0.05) # A peu près 90%
## [1] 0.899959
Il est clair que si des fonctions (et/ou des variables) telles que celles créées plus haut ont un intérêt qui s'étend au dela de la session en cours, il est plus simple de mettre toutes ces fonctions dans un fichier externe pour permettre à R de "recharger" ces fonctions, si nécessaire, dans une prochaine session. C'est le concept de librairie de fonctions. Bien que la méthode expliquée dans le premier paragraphe de la présente introduction puisse suffire pour les besoins du cours, des procédures plus élaborées existent et sont d'ailleurs fortement utilisées par les utilisateurs du langage pour mettre toutes sortes d'extensions à disposition des utilisateurs. Vous pouvez vous faire une idée des librairies (appelées "packages") existantes en jetant un oeil sur le miroir belge du répertoire des packages R. Notez que toutes ces librairies sont gratuitement accessibles !