Introduction à la programmation R (exercices)
Séance 3 : Conditions logiques et tests
gilles.hunault "at" univ-angers.fr
Table des matières cliquable
4. Vérification de valeurs dans un fichier Excel
5. Remplacements dans un vecteur
Il est possible d'afficher toutes les solutions via ?solutions=1 et de toutes les masquer avec via ?solutions=0.
1. Comparaison de valeurs (1)
On dispose de deux variables nommées valA et valB. Sans utiliser les fonctions de R, écrire des instructions qui mettent dans la variable petit la plus petite variable (la variable de plus petit contenu) et dans grand la plus grande variable. Donner ensuite la "bonne" solution avec les fonctions de R.
Solution : masquer la solution
Un seul test logique suffit ici :
################################################# # # # à partir de valA et valB, # # on met dans petit la plus petite valeur # # et dans grand la plus grande valeur # # # ################################################# ################################################# # solution 1 sans fonction de R ################################################# if (valA<valB) { petit <- valA grand <- valB } else { petit <- valB grand <- valA } # fin si ################################################# # solution 2 toujours sans fonction de R ################################################# petit <- valB grand <- valA if (valA<valB) { petit <- valA grand <- valB } # finsi ################################################# # solution 3 avec fonctions de R ################################################# petit <- min( valA, valB ) grand <- max( valA, valB )
2. Comparaison de valeurs (2)
On dispose de trois variables nommées valA, valB et valC. Sans utiliser les fonctions de R, écrire des instructions qui mettent dans la variable tpetit la plus petite variable, dans milieu la variable intermédiaire et dans tgrand la plus grande variable. Donner ensuite la "bonne" solution avec les fonctions de R.
Et si on avait plus de trois valeurs à trier ?
Remarque : on supposera les valeurs distinctes.
Solution : masquer la solution
Ici, les choses se compliquent car il y a 6 cas à envisager...
################################################# # # # à partir de valA, valB et valC, # # on met dans tpetit la plus petite valeur # # dans milieu la valeur du milieu # # et dans tgrand la plus grande valeur # # # ################################################# ################################################# # solution 1 sans fonction de R (et sans else) ################################################# # les cas sont triés par ordre alphabétique : # valA valB valC puis valA valC valB etc. if ( (valA<valB) & (valB<valC) ) { tpetit <- valA milieu <- valB tgrand <- valC } # fin si cas 1 if ( (valA<valC) & (valC<valB) ) { tpetit <- valA milieu <- valC tgrand <- valB } # fin si cas 2 if ( (valB<valA) & (valA<valC) ) { tpetit <- valB milieu <- valA tgrand <- valC } # fin si cas 3 [...] # il y a 6 cas en tout à écrire if ( (valC<valA) & (valA<valB) ) { tpetit <- valC milieu <- valA tgrand <- valB } # fin si cas 6 #################################################### # solution 2 sans fonction de R avec des si emboités #################################################### if (valA<valB) { if (valB<valC) { tpetit <- valA milieu <- valB tgrand <- valC } else { tpetit <- valA milieu <- valC tgrand <- valB } # fin si valB<valC } else { if (valA<valC) { [...] # il y a 6 cas en tout à écrire } # fin si valA<valB ################################################# # solution 3 sans fonction de R avec si en cascade ################################################# if ( (valA < valB) & (valB < valC) ) { # ordre valA, valB, valC } else { if ((valA < valC) et (valC < valB) ) { # ordre valA, valC, valB } else { if ((valB < valA) et (valA < valC)) { # ordre valB, valA, valC } else { if ((valB < valC) et (valC < valA)) [...] } # fINSI (valB < valC) et (valC < valA) } # finsi (valB < valA) et (valA < valC) } # FINSI (valA < valC) et (valC < valB) } # FINSI (valA < valB) et (valB < valC) ################################################# # solution 4 -- non évidente -- avec fonctions de R ################################################# tpetit <- min( valA, min(valB,valC) ) milieu <- min( max(valA,valB), min( max(valA,valC),max(valB,valC) ) ) tgrand <- max( valA, max(valB,valC) ) ################################################# # solution 5 avec fonctions de R ################################################# tri <- sort( c(valA,valB,valC) ) tpetit <- tri[ 1 ] milieu <- tri[ 2 ] tgrand <- tri[ 3 ]La dernière solution, qui met tous les éléments dans un vecteur et utilise la fonction sort() pour trier les éléments est la plus générale et couvre aussi le cas de variables égales. Ouf, pas besoin de faire 3, 4, 5... structures SI emboitées !
On notera que pour vérifier ces algorithmes on ne peut pas se contenter de vérifier que cela "marche" sur un exemple. Il faut bien tester les 6 cas avant d'être sûr de la validité de l'algorithme (ou du programme). Ainsi l'instruction "tentante"
milieu <- min( max(valA,valB), max(valB,valC) )met bien "ce qu'il faut" dans milieu dans 4 cas sur 6. Si on ne vérifie pas tout, ce qui est fastidieux car il faut être exhaustif, on risque de s'en rendre compte après avoir exécuté le programme plusieurs fois et avoir utilisé de faux résultats.
Pour les plus courageux et les plus courageuses, on pourra essayer de refaire cet exercice si on ajoute un ou plusieurs NA dans le vecteur...
3. Filtrage vectoriel
Soit v le vecteur défini par
v <- c( 1, 8, 2, -5, 15, 6, 9, -1, 4)Donner les expressions R qui fournissent :
le dernier élément de v ;
les trois derniers éléments de v ;
les éléments de v de rang pair.
Solution : masquer la solution
Puisque length(v) est à la fois la longueur et l'indice du dernier élément de v, v[ length(v) ] est le dernier élément de v.
Pour les trois derniers éléments de v, deux solutions au moins sont faciles à trouver :
# les trois derniers éléments de v, solution 1 lng <- length(v) v[ (lng-2) : lng ] # les trois derniers éléments de v, solution 2 lng <- length(v) v[ - (1:(lng-3)) ]Dans la mesure où R dispose de l'opérateur %% qui renvoie le modulo, c'est-à-dire le reste de la division entière, les éléments de v de rang pair sont a priori donnés par l'expression
v[ 1:length(v) %% 2 == 0 ]car "pair" équivaut à "dont le reste de la division par 2 est zéro". Toutefois cette expression est peu lisible et certainement peu compréhensible, sauf à bien connaitre la priorité entre opérateurs. Ajouter des parenthèses ne simplifie en rien l'expression :
v[ ((1:length(v)) %% 2) == 0 ]Il est au final beaucoup plus lisible d'écrire cela en plusieurs affectations :
ind <- 1:length(v) # indices de v rem <- ind %% 2 # restes de la division par 2 print( v[ rem == 0 ] ) # affichage pour les rangs pairsAttention à la notation 1:length(v) car elle réserve des suprises si le vecteur est de longueur nulle. On lui préfère en général la fonction seq_along() :
# définissons V > v <- c( 1, 8, 2, -5, 15, 6, 9, -1, 4) # voici ses indices > 1:length(v) 1 2 3 4 5 6 7 8 9 # et avec seq_along() ? > seq_along(v) 1 2 3 4 5 6 7 8 9 ## c'est la catastrophe si length(v) renvoie zéro : # un nouveau v > v <- NULL # seq_along() est alors correct > seq_along(v) integer(0) # mais pas la séquence avec length(v) > 1:length(v) 1 0
4. Vérification de valeurs dans un fichier Excel
On veut lire le fichier Excel nommé elfNA.xls et savoir combien de cases sont non vides pour la colonne AGE. Comment réaliser cela avec R ?
Solution : masquer la solution
Une case vide correspond à la valeur NA. Il suffit donc d'écrire :
# lecture du fichier Excel library(gdata) elf <- read.xls("http://forge.info.univ-angers.fr/~gh/wstat/Programmation_R/Programmation_introduction/elfNA.xls") ## nombre de cases non vides (solution 1) ## -------------------------------------- # comptage de cases vides pour la colonne AGE nbcv <- sum( is.na(elf$AGE) ) # on en déduit le nombre de cases non vides nv <- length(elf$AGE) - nbcv ## nombre de cases non vides (solution 2) ## -------------------------------------- nv <- length( na.omit(elf$AGE) )On remarquera que la deuxième solution n'utilise pas de test logique en apparence.
5. Remplacements dans un vecteur
On voudrait convertir les cases non vides de la colonne AGE et remplacer par "jeune" tout AGE<20 et par "vieux" sinon. Quel code R faut-il écrire ?
Reprendre ensuite avec "jeune" pour AGE<20, "adulte" pour AGE entre 20 et 60 (bornes incluses) et "vieux" pour AGE>60.
On pourra simuler 50 nombres entiers entre 1 et 99 pour AGE à l'aide de l'expression :
AGE <- round( runif(50,min = 1,max=99) )Solution : masquer la solution
R est riche en fonctions. Si une solution avec ifelse() convient pour deux valeurs, il faut avoir recours à cut() pour plus de deux modalités, sachant que le remplacement par filtre vectoriel est sans doute plus simple à lire :
############################################# # # # conversions d'age en classes d'age # # # ############################################# # solution A1 pour jeune<20 et vieux sinon # --------------------------------------- ageCL1 <- ifelse(age<20,"jeune","vieux") # solution A2 pour jeune<20 et vieux sinon # --------------------------------------- ageCL2 <- rep("vieux",length(age)) ageCL2[ is.na(age) ] <- NA ageCL2[ age<20 ] <- "jeune" identical(ageCL2, ageCL2) # renvoie TRUE ## ======================================================= # solution B1 pour jeune<20, adulte entre 20 et 60, vieux>60 # --------------------------------------------------------- ageCL3 <- ifelse(age<20,"jeune",ifelse(age>60,"vieux","adulte")) # solution B2 pour jeune<20, adulte entre 20 et 60, vieux>60 # --------------------------------------------------------- ageCL4 <- cut(x=age, breaks=c(0,20,60,100), labels=c("jeune","adulte","vieux") ) # fin de cut identical(ageCL3, ageCL4) # renvoie FALSE : ageCL4 est un facteur identical(factor(ageCL3,levels=c("jeune","adulte","vieux")),ageCL4) # TRUE # solution B3 pour jeune<20, adulte entre 20 et 60, vieux>60 # --------------------------------------------------------- ageCL5 <- rep("vieux",length(age)) ageCL5[ is.na(age) ] <- NA ageCL5[ age<=60 ] <- "adulte" ageCL5[ age<20 ] <- "jeune" # solution B4 pour jeune<20, adulte entre 20 et 60, vieux>60 # --------------------------------------------------------- ageCL6 <- rep("adulte",length(age)) ageCL6[ age<20 ] <- "jeune" ageCL6[ age>60 ] <- "vieux" ageCL6[ is.na(age) ] <- NA # solution B5 pour jeune<20, adulte entre 20 et 60, vieux>60 # --------------------------------------------------------- ageCL7 <- rep(NA,length(age)) ageCL7[ age<20 ] <- "jeune" ageCL7[ (age>=20) & (age<=60) ] <- "adulte" ageCL7[ age>60 ] <- "vieux"Remarques : lorsque la fonction ifelse() rencontre NA, elle renvoie NA. Si on ne fournit pas de labels à la fonction cut(), elle utilise les bornes de classes comme labels où les parenthèses signifient borne non comprise alors que les crochets signifient borne comprise comme dans :
> print(levels(ageCL4)) "(0,20]" "(20,60]" "(60,100]"
6. Utiliser AND ou imbriquer ?
En logique mathématique, on apprend que a ET b est équivalent à b ET a . Est-ce que pour R les codes a & b et b & a sont équivalents ? Comment le prouver ou au contraire l'infirmer, sachant que a et b sont des expressions R ? Quelle est la différence entre & et && en R ?
Solution : masquer la solution
La logique mathématique et le traitement des expressions logiques en informatique sont très proches mais différents. Mathématiquement, pour évaluer a ET b, on évalue a puis on évalue b et enfin on effectue le ET logique sur les deux résultats précédents d'évaluation.
C'est exactement ce que fait R quand il rencontre a & b. Par contre, l'évaluation du code a && b se fait sur le mode coupe-circuit : si a renvoie FAUX, ce n'est pas la peine d'évaluer b parce que FAUX et n'importe quoi renvoie toujours FAUX. Donc les expressions a && b et b && a ne sont pas strictement équivalentes alors que a & b et b & a sont "assez" équivalentes pour des expressions simples.
Il faut donc plutot interpréter le code
if (a && b) traitement() } # fin si sur a et bcomme le code suivant, avec une imbrication, où on voit bien qu'il n'y a pas symétrie d'utilisation entre a et b :
if (a) { if (b) { traitement() } # fin si sur b } # fin si sur aC'est pourquoi vous ne devez jamais utiliser && avec des vecteurs, comme le montre le code suivant, sous peine d'obtenir non pas un vecteur de résultats logiques calculés terme à terme mais juste une seule valeur logique :
# comparaison incorrecte de vecteurs # à cause du mode "court-circuit" > print( c(TRUE,FALSE) && c(TRUE,TRUE) ) TRUE # comparaison correcte de vecteurs > print( c(TRUE,FALSE) & c(TRUE,TRUE) ) TRUE FALSESi maintenant a et b sont des appels de fonctions qui modifient des variables globales (il s'agit bien sûr ici d'une hérésie qui mérite le bûcher, mais cela risque peut-être un jour de vous arriver, donc autant le savoir pour mieux l'éviter), alors il y a encore moins équivalence entre a & b et b & a comme le montre le code suivant où a correspond à l'appel de f() et b à celui de g() :
x <- 0 f <- function() { cat("f ") ; x <<- 100 ; return( FALSE) } g <- function() { cat("g ") ; x <<- "oui" ; return( FALSE) } if ( f() & g() ) { NULL } cat("1. a et b, x vaut ",x,"\n") if ( g() & f() ) { NULL } cat("2. a et b, x vaut ",x,"\n") if ( f() && g() ) { NULL } cat("3. a et b, x vaut ",x,"\n") if ( g() && f() ) { NULL } cat("4. a et b, x vaut ",x,"\n")Voici le résultat de son exécution -- si vous ne le saviez pas, l'expression <<- utilise l'environnement parent, donc ici x est une variable globale :
f g 1. a et b, x vaut oui g f 2. a et b, x vaut 100 f 3. a et b, x vaut 100 g 4. a et b, x vaut ouiOn voit clairement qu'avec & les deux fonctions sont appelées alors qu'avec && seule la première fonction est utilisée. De plus à chaque fois on n'obtient pas le même résultat suivant qu'on commence par a ou par b.
7. Fonctions à résultats logiques de R
Comment fait-on en R pour tester si un mot est présent dans une liste de mots ou dans un vecteur de mots avec la fonction grep() ? Et avec l'opérateur %in% ?
On veut savoir si au moins un des éléments du vecteur V numérique est positif. Trouver une solution basée sur V>0 et sum. Pourquoi la fonction any() est-elle plus adaptée ?
On veut être sûr que tous les éléments du vecteur V numérique sont positifs. Trouver une solution basée sur V>0, sum et length(V). Pourquoi la fonction all() est-elle plus adaptée ?
A quoi sert la fonction all.equal() ?
Solution : masquer la solution
La fonction grep() permet de connaitre la ou les positions d'un mot dans une liste ou dans un vecteur de mots comme on peut le voir avec le résultat d'exécution ci-dessous :
# recherche d'un mot dans une liste ou un vecteur de mots > mot <- "le" > ldm <- strsplit("le chat et le chien courent.", " ") > print(ldm) [[1]] [1] "le" "chat" "et" "le" "chien" "courent." > grep(mot,unlist(ldm)) # liste 1 4 > grep("che",ldm[[1]]) # vecteur integer(0) > grep("t$",ldm[[1]]) # expression régulière 2 3 > grep("bateau",ldm[[1]]) # pas vu ! integer(0)Le premier résultat renvoyé est 1 4 parce que "le" est trouvé comme premier et quatrième mot dans la liste ldm. Le deuxième résultat renvoyé montre que grep() sait aussi rechercher des sous-chaines : le vecteur résultat 2 5 signifie que "ch" est trouvé dans les deuxième et cinquième mots du vecteur ldm[[1]]. grep() sait utiliser les expressions régulières : le troisième résultat indique que les mots 2 et 3 finissent par "t", ce qui se décrit par l'expression régulière t$.
Lorsque la fonction grep() ne trouve pas ce qu'on cherche, elle renvoie integer(0) c'est-à-dire un vecteur de longueur 0. Il suffit donc d'écrire if (length(grep(...)>0) pour savoir si la recherche a été fructueuse.
Si on veut juste vérifier que le mot est exactement présent dans un vecteur, on peut utiliser l'opérateur %in% :
# teste de la présence d'un mot dans un VECTEUR de mots > mot <- "le" > vec <- strsplit("le chat et le chien courent.", " ")[[1]] > if ( mot %in% vec ) { print("vu !") } vu ! > print( "ch" %in% vec ) FALSE > print( "bateau" %in% vec ) FALSEDans les exercices de la séance 8, on montrera que %in% est vraiment beaucoup plus rapide que grep().
Pour savoir si au moins un des éléments du vecteur V numérique est positif, on peut se contenter, pour des vecteurs de petite longueur, de l'expression if (sum(V>0)>0). En effet, V>0 renvoie un ensemble de valeurs logiques TRUE et FALSE remplacés automatiquement par 1 et 0 à cause de l'appel de sum(). S'il y a au moins un élément positif, il y a au moins un TRUE donc au moins un 1, et la somme est forcément positive.
Cette solution n'est pas optimisée parce qu'on teste tous les éléments de V. Avec un vecteur d'un million d'éléments tous négatifs sauf le premier, on effectue un million de tests. C'est pourquoi la fonction any() est plus adaptée et sans doute plus rapide : elle renvoie VRAI dès qu'elle trouve un élément correspondant (mais sans dire où ; c'est l'expression which(V>0) qui en donne les positions). Dans les exercices de la séance 8, on calculera à quel point any(v>0) est plus rapide que sum(v>0)>0.
De la même façon, tous les éléments de V sont positifs si sum(v>0) est égal à la longueur de V. On peut donc le tester via if (sum(V>0)==length(V)) pour des vecteurs de petite longueur ou prendre l'habitude d'utiliser dès le départ if (all(V>0)).
Comme son nom l'indique, la fonction all.equal() permet de tester si deux objets sont égaux. Si vous vous souvenez bien, au chapitre précédent, nous avions, dans le calcul des contributions relatives pour la comparaison d'effectifs observés et d'effectifs théoriques, deux variables numériquement égales, mais l'une était nommée pas l'autre. La fonction all.equal() est capable de l'indiquer :
> print( identical( tdk2$statistic, sum(df$Cntr)) ) FALSE > print( identical( as.numeric(tdk2$statistic), sum(df$Cntr)) ) TRUE > all.equal( tdk2$statistic, sum(df$Cntr) ) "names for target but not for current"De même qu'elle peut détecter des différences de longueur, de structure... :
> all.equal( 1:10, 1:11 ) "Numeric: lengths (10, 11) differ" > all.equal( 1:10, list(1:10) ) "Modes: numeric, list" "Lengths: 10, 1" "target is numeric, current is list"Si vous pensez avoir construit deux variables complétement équivalentes, surtout s'il s'agit de structures, all.equal() peut vous le dire ou expliquer ce qu'elle trouve de différent... une fois qu'identical() est passé par là, bien sûr.
Voici donc quelques autres détections obtenues grâce à all.equal() :
> all.equal( 1:10, c(0,1:9) ) "Mean relative difference: 0.1818182" > all.equal( 1, "1" ) "Modes: numeric, character" "target is numeric, current is character" > all.equal( list(1:5), list(x=1:5) ) "names for current but not for target" > all.equal( list(x=1:5), list(y=1:5) ) "Names: 1 string mismatch" > all.equal( 1:2, c(1L,2L) ) TRUE > all.equal( 1:2, c(1.0,2) ) TRUE > all.equal( 1:2, c(1,2) ) TRUE # allez, une petite surprise pour la route : > identical(1:2,c(1,2)) FALSE
Code-source php de cette page. Retour à la page principale du cours.
Retour à la page principale de (gH)