UP | HOME

Travaux Pratiques #2
Algo & Prog avec R

Table des matières

1. Travail au CRIPS (Petit Valrose)

Vous allez travailler sur des ordinateurs sous le système d’exploitation Windows (2ème étage) ou Linux (3ème étage). Peut-être utilisez-vous un ordinateur MacOS-X (basé sur UNIX comme Linux) ? Aucune importance puisque les logiciels qui nous serviront à programmer en R fonctionnent sur tous ces systèmes quasiment à l’identique. Nous supposons que vous savez utiliser votre ordinateur pour copier/déplacer des fichiers et lancer un logiciel ou un navigateur Web (Firefox, Chrome, etc).

Vous êtes donc devant votre machine. Vos notes de cours sont à portée de main et vos neurones commencent leurs échanges électro-chimiques, bravo !

En principe la machine est allumée, et en veille. En pressant une touche, elle devrait se réveiller et l’écran s’allumer. Appelez votre enseignant si ce n’est pas le cas, ou regardez comment font vos voisins … Vous arrivez sur une petite « fenêtre de dialogue » qui vous demande un nom d’utilisateur et un mot de passe (le vôtre).

Le bureau de Windows sert à abriter (le temps d’une séance de TP seulement) les fichiers des étudiants. Le bureau de Linux est sauvegardé d’une session à l’autre.

Nous vous demandons d’acheter une « clé USB » ou d’apprendre à utiliser un espace de stockage dans les nuages.

  • Votre clef USB est un disque amovible : travaillez directement sur votre clé USB qui sera votre disque dur et espace de travail durant tous les TP.
  • Votre espace de stockage est accessible grâce à votre navigateur : télécharger votre code source depuis cet espace au début de chaque séance et n’oubliez pas de le télécharger dans cet espace à la fin.

Passons dans l’éditeur

À partir de maintenant, le travail se fait dans l’éditeur, et non plus dans le top level comme au premier TP.

Lorsque l’on vous demandera de sauver le contenu de votre éditeur, vous opterez pour un fichier avec un nom explicite, par exemple tp01.R.

N.B. Vous prendrez l’habitude de n’utiliser des caractères accentués français ou d’espacement qu’à l’intérieur des chaînes de caractères, et jamais dans les noms de fonctions ou de variables ou de fichiers. Vous risquez sinon, d’avoir de désagréables surprises …

2. Maximum d’une somme

Calcul du résultat par une fonction

Définissez la fonction Somme2Max(x,y,z) prenant trois entiers x, y, z et retournant la somme des deux plus grands.

  1. Écrivez une première version en emboîtant des if,
  2. Écrivez une seconde version utilisant les fonctions prédéfinies max ou min.
## Avec des if
Somme2Max <- function(x,y,z) {
  if(x > y) {
    if(y > z) {return(x+y)}
    else {return(x+z)}
  } else {
    if(x > z) {return(y+x)}
    else {return(y+z)}
  }
  ## Dans tous les cas, 2 comparaisons et une addition
}
## Avec min
Somme2Max <- function(x,y,z) {x+y+z-min(x,y,z)}
## Avec max
Somme2Max <- function(x,y,z) {max(x+y,y+z,x+z)}

À chaque fois, testez votre fonction grâce aux appels ci-dessous.

Somme2Max(1, 2, 3)
Somme2Max(1, 3, 2)
Somme2Max(2, 1, 3)
Somme2Max(2, 3, 1)
Somme2Max(3, 1, 2)
Somme2Max(3, 2, 1)

Affichage du résultat à l’écran

Définissez la fonction PrintSomme2Max(x,y,z) faisant afficher la somme des deux plus grands des entiers x, y, z. Cette fonction n’aura aucun résultat (ne confondez pas l’effet et le résultat d’une fonction !).

PrintSomme2Max <- function(x,y,z) {
  cat('Le résultat de l\'appel de fonction Somme2Max(',x,', ',y,', ',z,') est ',Somme2Max(x,y,z), '.\n', sep = '')
}
PrintSomme2Max(3,5,7)
PrintSomme2Max(3,5,7)
Le résultat de l'appel de fonction Somme2Max(3, 5, 7) est 12
## à méditer ...
PrintSomme2Max <- function(x,y,z) {
  sm <- quote(Somme2Max(x,y,z))
  print(sm)
  print(eval(sm))
}
PrintSomme2Max(3,5,7)
Somme2Max(x, y, z)
[1] 12

3. Génération aléatoire d’un nombre   KEY

Nous allons utiliser une fonction sample tirant au hasard des éléments dans une collection, on dit aussi aléatoires. Voici 3 manières équivalentes de tirer un dé à 6 face, afficher un entier aléatoire entre [1,6]. Tapez les instructions ci-dessous.

sample(6, size=1)
sample(1:6, size=1)
sample(c(1, 2, 3, 4, 5, 6), size=1)

Faites afficher un entier aléatoire de [100,200] avec un appel à sample.

sample(100:200, size = 1)

Entier pair

La fonction RandPair(n) ci-dessous prend un entier n ≥ 0 et retourne un entier pair aléatoire de [0,n].

RandPair <- function(n) {
  return(2*sample(n%/%2, size = 1))
}

Malheureusement, cette fonction est buggée : elle ne respecte sa spécification. Votre tâche consiste à corriger cette fonction. Faites suivre la définition de votre fonction d’une instruction pour la tester.

RandPair <- function(n) 2*sample(0:(n%/%2), size = 1) ## Attention, les parenthèses sont importantes!
cat('Comportement défini pour n = 0 : ',RandPair(0), '\n')
cat('Voici un entier pair aleatoire de [0,2] :',RandPair(2), '\n')
cat('Voici un entier pair aleatoire de [0,20] :',RandPair(20), '\n')
Comportement défini pour n = 0 :  0
Voici un entier pair aleatoire de [0,2] : 2
Voici un entier pair aleatoire de [0,20] : 12

Dans un ensemble

  1. Définissez une fonction MonteCarlo() sans argument retournant au hasard 2, 3 ou 5. Testez-la plusieurs fois. Indice : utilisez la fonction c.
  2. Définissez une fonction LasVegas() retournant 2, 3 ou 5 mais de manière truquée : 2 avec 1 chance sur 6, ou bien 3 avec 1 chance sur 3, ou bien 5 avec 1 chance sur 2. Testez-la une dizaine de fois… Les résultats sont-ils conformes à ce que l’on attend ?
  3. Testez la commande table(replicate(1000, LasVegas())) ? À quoi sert-elle ?

Lisez la documentation help(sample) avant d’essayer de répondre aux questions.

MonteCarlo <- function() sample(c(2, 3, 5), size = 1)
cat('MonteCarlo() --> ',MonteCarlo(), '\n')

## Version 1 en dupliquant les éléments
LasVegas <- function() sample(c(2, 3, 3, 5, 5, 5), size = 1)
cat('LasVegas() --> ',LasVegas(), '\n')

## Version 2 en utilisant l'argument optionel prob
LasVegas <- function() sample(c(2, 3, 5), prob = c(1/6, 1/3, 1/2), size = 1)

table(replicate(100, LasVegas()))
MonteCarlo() -->  5
LasVegas() -->  3

 2  3  5
18 36 46

Avec un nombre de chiffres fixé

Définissez une fonction RandChiffres(n) prenant un entier n ≥ 1 et retournant un entier aléatoire non nul contenant exactement n chiffres. Testez-la plusieurs fois …

## les entiers non nuls ayant n chiffres sont les elements de [10**(n-1),10**n-1]
RandChiffres <- function(n) sample(seq(10**(n-1), 10**n-1), 1)
replicate(5, RandChiffres(4))
[1] 6275 4690 5349 1333 8659

4. Circuit électrique

Dans le cours d’électricité du lycée, vous avez sans doute vu que :

  • la résistance équivalente de deux résistors R_1 et R_2 en série vaut R = R_1 + R_2,
  • tandis que si les résistors sont placés en parallèle, leur résistance globale vérifie 1/R=1/R_1+1/R_2.

Un électronicien travaille avec la portion de circuit suivante contenant trois résistors. Programmez la fonction Circuit1(r1,r2,r3) retournant la résistance équivalente de ce circuit.
A.N. Pour r1=5 Ω, r2=100 Ω et r3=25 Ω, le résultat est 25 Ω.

       +----------+               +----------+
+------+    R1    +-------+-------+    R2    +------+---+
       +----------+       |       +----------+      |
                          |                         |
                          |       +----------+      |
                          +-------+    R3    +------+
                                  +----------+

Maintenant, programmez la fonction Circuit2(R1,R2,R3) retournant la résistance équivalente de ce circuit.

         +----------+                    +----------+
-+-------+    R1    +------+---+-+-------+    R2    +------+---+
 |       +----------+      |     |       +----------+      |
 |                         |     |                         |
 |       +----------+      |     |       +----------+      |
 +-------+    R2    +------+     +-------+    R3    +------+
 |       +----------+      |             +----------+
 |                         |
 |       +----------+      |
 +-------+    R3    +------+
         +----------+

Indice : définir une fonction auxiliaire Serie(r1, r2) (respectivement Parallele(r1, r2)) qui calcule la résistance globale de deux résistors en série (respectivement en parallèle).

Serie <- function(r1,r2) {r1+r2}
Parallele <- function(r1,r2) {return(r1 * r2 / (r1 + r2))}
# Le circuit est vue comme une composition de sous-circuits
circuit1 <- function(r1,r2,r3) {
  return(Serie(r1,Parallele(r2,r3)))
}
## \u03a9 : Unicode !
cat('La resistance totale du circuit 1 est',circuit1(5,100,25),'\u03a9.\n')

circuit2 <- function(r1,r2,r3) {
  return(Serie(Parallele(r1,Parallele(r2,r3)), Parallele(r2,r3)))
}
cat('La resistance totale du circuit 2 est',circuit2(5,100,25),'\u03a9.\n')
La resistance totale du circuit 1 est 25 Ω.
La resistance totale du circuit 2 est 24 Ω.

5. Conversion du temps

Programmez une fonction hconv(n) prenant un entier n > 0 représentant un nombre de secondes. L’effet de cette fonction est l’affichage d’une ligne exprimant la conversion de n secondes en heures-minutes-secondes.

hconv <- function(n) {
  s <- n %% 60;
  h <- n %/% 60;
  m <- h %% 60;
  h <- h %/% 60;
  cat(sprintf("%d -> %02d:%02d:%02d\n",n,h,m,s))
}
hconv(4567)
hconv(3601)
hconv(123456789)
4567 -> 01:16:07
3601 -> 01:00:01
123456789 -> 34293:33:09

6. Impôt sur le revenu   UCANCODE

Supposons que l’impôt sur le revenu annuel soit calculé par tranches de la manière suivante.

  • Un salarié ne paye rien pour les 8000 premiers euros qu’il gagne.
  • Il paye 10% sur chaque euro gagné entre 8000 € et 25000 €,
  • et enfin 20% sur chaque euro gagné au-dessus de 25000 €.
  1. Définissez la fonction Tranche(s,b,h,p) retournant l’impôt dû pour un salaire annuel s dans la tranche [b,h] dont le pourcentage est p %.
  2. Définissez la fonction Impot(s) retournant l’impôt total dû pour un salaire annuel s.
  3. Modifiez la fonction Impot(s) pour arrondir le montant de l’impôt au centime inférieur.
  4. Testez plus finement votre fonction grâce à l’exercice UCAnCODE.
Tranche <- function(s, b, h, p) {
  if(s < b) return(0)   # rien
  else if (s <= h) return( (s - b) * p / 100) # une portion de la tranche
  else return((h - b) * p / 100)     # toute la tranche
}
ArrondiCentime <- function(x) floor(100 * x) / 100
Impot <- function(s) ArrondiCentime(Tranche(s,8000,25000,10)+Tranche(s,25000,s, 20))

Tranche(1500,2000,3000,10)
Tranche(2500,2000,3000,10)
Tranche(5000,2000,3000,10)
Tranche(5000,3000,3000,10)
Impot(40000)
Tranche <- function(s,b,h,p) max( min(s, h) - b, 0) * p / 100
[1] 0
[1] 50
[1] 100
[1] 0
[1] 4700
Tranche(1500,2000,3000,10)
Tranche(2500,2000,3000,10)
Tranche(4000,2000,3000,10)
Tranche(5000,3000,3000,10)
Impot(40000)
[1] 0
[1] 50
[1] 100
[1] 0
[1] 4700

7. Calcul de l’hypoténuse

  1. Calculez l’hypoténuse d’un triangle connaissant les deux côtés de l’angle droit (a et b).
  2. Demander à l’utilisateur de saisir les valeurs a et b à l’aide de la fonction scan()
  3. Afficher et formatter le résultat avec les fonctions cat.

    cat("Veuillez entrer les longueurs des deux côtés de l’angle droit :\n")
    a <- scan( n = 1)
    b <- scan( n = 1)
    hypo <- sqrt(a**2 + b**2)
    cat("Longueur de l'hypoténuse :", hypo, '\n')
    

8. Évaluation des arguments d’une fonction   HOME

Il y a deux sortes de fonctions en R.

  • Les fonctions prédéfinies
    abs
    typeof(abs)
    
    function (x)  .Primitive("abs")
    [1] "builtin"
  • Les fonctions que vous programmez vous-même
    foo <- function(x) {x+1}
    typeof(foo)
    
    [1] "closure"

Les opérateurs sont des fonctions.

En R, même les opérateurs sont des fonctions ! Par exemple, + est un opérateur, mais c’est aussi une fonction.

2 + 2
'+'(2,2)
2 == 3
'=='(2,3)
0 || 1
'||'(0,1)

Évaluation paresseuse des arguments d’une fonction

Par exemple, si f est une fonction, au moment du calcul de f(a,b), l’évaluation des paramètres a et b de la fonction ne se fait pas avant que les résultats de cette évaluation ne soient réellement nécessaires. Ce mécanisme s’appelle l’évaluation paresseuse.

Si je définis f sous la forme :

foo <- function(x,y) {x}

quelle sera le résultat de foo(0, Sys.sleep(5)) ? foo(Sys.sleep(5), 0) ? Comment R va-t-il obtenir ce résultat, vite ou lentement ?

Une fonction n’évalue ses arguments qu’en cas de besoin quand elle exécute son corps. foo(0,Sys.sleep(5)) ne dort pas inutilement pendant 5 secondes en R. Par contre, cela serait le cas en Python.

Par ailleurs, remarquez que la procédure Sys.sleep renvoie une valeur spéciale : Invisible NULL.

Utilisation d’arguments par défaut

Un autre avantage de l’évaluation paresseuse est que vous pouvez définir des arguments par défaut mutuellement récursif ce qui permet d’implémenter des interfaces adaptatives. Par exemple, voici une fonction (voir ici) qui calcule la représentation d’un point en coordonnées polaires et cartésiennes. Vous pouvez spécifier le point dans l’un ou l’autre des systèmes de coordonnées.

polar <- function(x = r * cos(theta), y = r * sin(theta),
                  r = sqrt(x*x + y*y), theta = atan2(y, x)) return(c(x, y, r, theta))

## Calcule la paire (x,y)
polar(1,1)
## Calcule la paire (r, theta)
polar(r=sqrt(2), theta=pi/4)
## Attention, l'appel déclenche une erreur si le calcul des coordonnées est impossible.
polar(r=1)
[1] 1.0000000 1.0000000 1.4142136 0.7853982

[1] 1.0000000 1.0000000 1.4142136 0.7853982

Error in atan2(y, x) :
  la promesse est déjà en cours d'évaluation : référence récursive d'argument par défaut ou problème antérieur ?

C’est donc un mécanisme puissant, mais dont il faut se méfier. En pratique, la définition des arguments par défaut doit rester simple.

9. Fonction « Lambda »   HARD HOME

  1. Définissez en R la fonction \(f(x)=\frac{sin(x)}{\sqrt{x^4+1}}\).
  2. Calculez une valeur approchée de la dérivée seconde \(f^{\prime\prime}(\sqrt{2})\). Réponse : approximativement 0.036…
## Prenons une fonction f particuliere :
f <- function(x) sin(x)/sqrt(x**4 + 1)

## Calcul de la derivee premiere de f quelconque en un point x avec une precision de h
Deriv <- function(f, x, h = 2**(-10)) (f(x + h) - f(x)) / h
## formule vue au lycee...

cat('f\'(sqrt(2)) =',Deriv(f,sqrt(2)), '\n')

## Probleme : pour exprimer que la derivee seconde est la derivee de la derivee, j'ai
## besoin d'obtenir la FONCTION DERIVEE f' et pas seulement sa valeur en un point f'(x).
## En R, on définit une sous-fonction a l'interieur de Deriv et en rendant en resultat cette sous-fonction.
## C'est ce que les specialistes nomment une "fermeture" :
## http://fr.wikipedia.org/wiki/Fermeture_(informatique)

Deriv <- function(f, h = 2**(-10)) return (function(x) (f(x + h) - f(x)) / h)
## Du coup, je peux exprimer mes maths sans etat d'ame :
df <- Deriv(f)
d2f <- Deriv(df)
cat('f"(sqrt(2)) =',d2f(sqrt(2)), '\n')    # et zou ! Intellectuel non ?...

## Par contre, je vais rapidement avoir des problèmes de calcul numérique
df <- Deriv(f, h = 2**(-12))
d2f <- Deriv(df, h = 2**(-12))
cat('f"(sqrt(2)) =',d2f(sqrt(2)), '\n')    # et zou ! Problèmatique non ?...

## Une solution est de dériver symboliquement
df <- D(quote(sin(x) / sqrt(x^4 + 1)), 'x')
d2f <- D(df, 'x')
cat('la dérivée seconde de la fonction est\n')
print(d2f)
x <- sqrt(2)
cat('f"(sqrt(2)) =',eval(d2f), '\n')    # et zou ! Intriguant non ?...
f'(sqrt(2)) = -0.4300162
f"(sqrt(2)) = 0.03795618
f"(sqrt(2)) = 0.03691183
la dérivée seconde de la fonction est
-(sin(x)/sqrt(x^4 + 1) + cos(x) * (0.5 * (4 * x^3 * (x^4 + 1)^-0.5))/sqrt(x^4 +
    1)^2 + ((cos(x) * (0.5 * (4 * x^3 * (x^4 + 1)^-0.5)) + sin(x) *
    (0.5 * (4 * (3 * x^2) * (x^4 + 1)^-0.5 + 4 * x^3 * (-0.5 *
        (4 * x^3 * (x^4 + 1)^-1.5)))))/sqrt(x^4 + 1)^2 - sin(x) *
    (0.5 * (4 * x^3 * (x^4 + 1)^-0.5)) * (2 * (0.5 * (4 * x^3 *
    (x^4 + 1)^-0.5) * sqrt(x^4 + 1)))/(sqrt(x^4 + 1)^2)^2))
f"(sqrt(2)) = 0.03656271

Si cet exercice vous passe par-dessus la tete, ce n’est pas si grave que cela. Vous y reviendrez plus tard ! simple question de maturité…

Created: 2023-11-13 lun. 10:42