Valid XHTML     Valid CSS2    

Introduction à la programmation avec R

                gilles.hunault "at" univ-angers.fr

Cours 3 - Conditions logiques et tests

 

Table des matières cliquable

  1. Valeurs logiques et tests en R

  2. Filtrage vectoriel

  3. Applications aux matrices et dataframes

  4. Spécificités du langage R

 
Exercices :           énoncés            solutions           [Retour à la page principale du cours]

1. Valeurs logiques et tests en R

R dispose de deux valeurs logiques nommées FALSE et TRUE qui valent numériquement 0 et 1. Les opérateurs qui renvoient des valeurs logiques sont très classiquement < et >. Il faut leur adjoindre <= et >= et aussi == (bien noter qu'il y a deux signes "égal") pour tester l'égalité. Pour des variables simples, les connecteurs usuels nommés NON, ET, OU s'écrivent respectivement !, &, |. Il est très fortement conseillé d'utiliser des parenthèses pour séparer ces comparaisons logiques. Voici quelques exemples d'utilisation :


     x <- 5
     
     x==2        # FALSE
     x==5        # TRUE
     
     !(x>4)      # FALSE
     
     x<2         # FALSE
     x>=5        # TRUE
     x>3         # TRUE
     
     (x>3) & (x<10) # TRUE
     (x>0) | (x==2) # TRUE
     
     TRUE + 1     # 2
     FALSE*FALSE  # 0
     !7           # FALSE
     
     

Pour réaliser un test logique, on utilise une structure algorithmique dite d'alternative qui, en fonction d'une condition (opération à résultat logique), effectue une bloc d'instructions. Voici les deux formes algorithmiques, nommées respectivement SI_ALORS et SI_ALORS_SINON :


     ##############################################
     #                                            #
     # structure "si_alors"                       #
     #                                            #
     ##############################################
     
     SI (condition) ALORS
     
        [...] # bloc d'instructions exécutées
              # si la condition est vraie
     FINSI
     
     ##############################################
     #                                            #
     # structure "si_alors_sinon"                 #
     #                                            #
     ##############################################
     
     SI (condition) ALORS
     
        [...] # bloc d'instructions exécutées
              # si la condition est vraie
     
     SINON
     
        [...] # bloc d'instructions exécutées
              # si la condition est fausse
     FINSI
     

La traduction en R se fait à l'aide des mots if, else et des accolades { et } pour délimiter les blocs d'instruction. De telles structures permettent donc de modifier l'exécution en séquence des instructions.


     ##############################################
     #                                            #
     # structure "si_alors" en R                  #
     #                                            #
     ##############################################
     
     if (condition) {
     
        [...] # bloc d'instructions exécutées
              # si la condition est vraie
     }
     
     ##############################################
     #                                            #
     # structure "si_alors_sinon" en R            #
     #                                            #
     ##############################################
     
     if (condition) {
     
        [...] # bloc d'instructions exécutées
              # si la condition est vraie
     
     } else {
     
        [...] # bloc d'instructions exécutées
              # si la condition est fausse
     }
     

Voici deux exemples en R. On remarquera que nous avons (ce qui est très fortement conseillé) commenté les fins de SI.


     # STRUCTURE si Exemple 1
     # ----------------------
     
     # on indique quand on a commencé à traiter le fichier numéro 100
     # (nbf est le numéro de fichier en cours de traitement)
     
     if (nbf==100) {
       cat(" fichier numéro ",nbf," atteint. \n")
       cat(" date et heure : ",date(),"\n\n")
     } # fin si nbf=100
     
     # STRUCTURE si Exemple 2
     # ----------------------
     
     if (valCour>maxLoc) {
     
       # on met à jour le maximum local via la valeur courante
     
       maxLoc <- valCour
       nbInf  <- 0
     
     } else {
     
       nbInf  <- nbInf + 1
     
     } # fin si
     

L'instruction stop en R permet de quitter le script en cours. Cela peut se révéler utile, par exemple si le fichier que l'on veut traiter n'est pas trouvé (noter le mot si dans cette phrase). Voici comment s'en servir :


     ## série de calculs sur un fichier nommé FN
     
     if (!file.exists(FN)) {
       cat("ATTENTION ! Le fichier ",FN," n'est pas présent.\n\n") ;
       stop(" donc arrêt du programme.\n") ;
     } # fin de si
     
     # si on arrive ici, c'est que le fichier existe
     
     ## suite des calculs...
     

La valeur NA de R qui signifie Not Available, soit Non Accessible en français est aussi une valeur logique ce qui complique les cas à tester :


     # NA est aussi une valeur logique
     
     x <- NA
     print(class(x)) # renvoie "logical"
     
     # un test raté avec x :
     # R répond
     #   Error in if (x < 0) { :
     #   valeur manquante là où TRUE / FALSE est requis
     
     if (x<=0) {
       cat(" x est négatif ou nul\n")
     } else {
       cat(" x n'est pas négatif ou nul...\n")
       cat(" mais il n'est peut-être pas positif")
       cat(" ni même numérique.\n")
     } # fin si
     
     # un test réussi avec x
     # R répond x est indéterminé (NA).
     
     if (is.na(x)) {
          cat(" x est indéterminé (NA).\n")
     } else {
        if (x<0) {
          cat(" x est strictement négatif\n")
        } else {
          cat(" x est positif ou nul.\n")
        } # fin si x<0
     } # fin si is.na(x)
     

2. Filtrage vectoriel

R est beaucoup plus puissant avec les tests logiques qu'on ne l'imagine car on peut coupler l'indexation avec ces tests et donc réaliser des affectations conditionnelles.

Pour accéder à un élément dans un vecteur, R fournit la notation "crochets" : ainsi x[k] correspond à l'élément numéro k sachant que, contrairement à la plupart des autres langages de programmation, R commence la numérotation à 1. Donc x[0] -- qui existe par ailleurs en R -- ne correspond pas au premier élément de x.

La notation crochets ou indexation permet de spécifier plusieurs éléments. Ainsi [1:n] correspond aux n premiers éléments. Un indice entier mais négatif signifie tout sauf cet indice donc (1:5)[ - c(3,4) ] renvoie 1 2 5.

Lorsqu'on fournit entre crochets pour une variable de type vecteur un vecteur de même longueur que cette variable composé de 0 et de 1, R extrait les valeurs correspondant à 1. Par exemple (1:3)[ c(0,1,0) ] renvoie 2. Comme nous avons expliqué que les valeurs logiques FALSE et TRUE valent numériquement 0 et 1, il est facile de comprendre que R fournit un mécanisme de filtrage très puissant avec les expressions logiques vectorielles.

Voici des exemples :


     # le vecteur v
     #   pos  1  2  3   4   5  6  7   8  9
     
     v  <- c( 1, 8, 2, -5, 15, 6, 9, -1, 4)
     
     # filtre des négatifs
     
                          #    pos   1     2     3      4    5     6     7      8    9
     v<0                  # renvoie FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE
                          #    num   0     0     0      1    0     0     0      1    0
     
     # extraction des négatifs
     
     v[ v<0 ]             # renvoie  -5 -1
     
     # comptage du nombre de positifs
     
     sum( v>0 )           # renvoie 7
     
     # les éléments de v entre 3 et 10
     
     flt1 <-  (v>3)
     flt2 <-  (v<10)
     v[ flt1 & flt2 ]    # renvoie  8 6 9 4
     

3. Applications aux matrices et dataframes

En plus des vecteurs, R dispose de deux structures de données nommées matrix et data frame qui définissent des tableaux rectangulaires avec des lignes et des colonnes. Il y a toutefois des grandes différences entre ces deux structures. Les matrices sont homogènes en type et les lignes et les colonnes y sont équivalentes en fonctionnement alors que les data frames sont des listes particulières : les éléments de la liste sont les colonnes du tableau, avec la contrainte que toutes les colonnes doivent avoir le même nombre d'éléments. Un data frame est donc particulièrement adapté à contenir un fichier de données pour des études statistiques, avec des colonnes de types éventuellement différents.

Toutes les fonctions de R qui lisent des fichiers comme la fonction read.table(), la fonction read.xls() (du package gdata) renvoient des data frames.

Les conditions logiques et le filtrage vectoriel permettent d'extraire facilement des sous-ensembles de ces tableaux de données comme le montrent les exemples ci-dessous pour le fichier de données elf.txt :


     # lecture du fichier elf.txt
     #
     
     elf <- read.table("http://forge.info.univ-angers.fr/~gh/wstat/Programmation_R/Programmation_introduction/elf.txt",head=TRUE)
     
     # extrait des données
     
     > head(elf)
     
        NUM SEXE AGE PROF ETUD REGI USAG
     1 M001    1  62    1    2    2    3
     2 M002    0  60    9    3    4    1
     3 M003    1  31    9    4    4    1
     4 M004    1  27    8    4    1    1
     5 M005    0  22    8    4    1    2
     6 M006    1  70    4    1    1    1
     
     > tail(elf)
     
         NUM SEXE AGE PROF ETUD REGI USAG
     94 M094    0  12   12    2    1    0
     95 M095    1  31    6    4    0    0
     96 M096    1  17   12    3    1    0
     97 M097    1  39    1    2    1    0
     98 M098    0  62    6    3    1    0
     99 M100    1  48    9    4    2    0
     
     # filtre "femme"
     
     flt1 <- elf$SEXE==1
     
     # filtre "jeune"
     
     flt2 <- elf$AGE <20
     
     # nombre de femmes
     
     sum( flt1 )
     
     # nombre de jeunes
     
     sum( flt2 )
     
     # nombre de femmes jeunes
     
     sum( flt1 & flt2 )
     
     # extraction du tableau des femmes jeunes
     
     fj1 <- elf[ (flt1 & flt2) , ]
     
     # autre solution
     
     fj2 <- subset(elf, flt1 & flt2)
     
     # attention à ne pas écrire
     
     fj3 <- subset(elf, SEXE=1, AGE<20)
     

4. Spécificités du langage R

En R, les indices commencent à 1. C'est une révolution par rapport aux autres langages de programmation qui ne comptent pas en position mais en décalage (offset) à partir du début du tableau. Cela facilite beaucoup les calculs.

A cause de la valeur NA -- qui est spécifique à R, la comparaison entre valeurs en R est compliquée :


     ## un vecteur x logique
     
     x <- c( NA, FALSE, TRUE )
     
     ## table AND obtenue par outer(x, x, "&")
     
            <NA> FALSE  TRUE
     <NA>     NA FALSE    NA
     FALSE FALSE FALSE FALSE
     TRUE     NA FALSE  TRUE
     
     ## table OR obtenue par outer(x, x, "|")
     
           <NA> FALSE TRUE
     <NA>    NA    NA TRUE
     FALSE   NA FALSE TRUE
     TRUE  TRUE  TRUE TRUE
     

Heureusement il y a de nombreuses fonctions comme is.na() et na.omit() qui permettent de détecter et d'enlever ces valeurs NA. On trouve aussi, comme pour la fonction sum(), une option na.rm=TRUE qui enlève (localement) ces valeurs NA.

R dispose de nombreuses fonctions logiques :


     > apropos("^is.")
     
      [1] "is.array"              "is.atomic"             "isatty"                "isBaseNamespace"       "is.call"               "is.character"          "isClass"
      [8] "isClassDef"            "isClassUnion"          "is.complex"            "is.data.frame"         "isdebugged"            "is.double"             "is.element"
     [15] "is.empty.model"        "is.environment"        "is.expression"         "is.factor"             "is.finite"             "is.function"           "isGeneric"
     [22] "isGrammarSymbol"       "isGroup"               "isIncomplete"          "is.infinite"           "is.integer"            "islands"               "is.language"
     [29] "is.leaf"               "is.list"               "is.loaded"             "is.logical"            "is.matrix"             "is.mts"                "is.na"
     [36] "is.na<-"               "is.na.data.frame"      "is.na<-.default"       "is.na<-.factor"        "is.name"               "isNamespace"           "is.nan"
     [43] "is.na.numeric_version" "is.na.POSIXlt"         "is.null"               "is.numeric"            "is.numeric.Date"       "is.numeric.difftime"   "is.numeric.POSIXt"
     [50] "is.numeric_version"    "is.object"             "ISOdate"               "ISOdatetime"           "isOpen"                "is.ordered"            "isoreg"
     [57] "is.package_version"    "is.pairlist"           "is.primitive"          "is.qr"                 "is.R"                  "is.raster"             "is.raw"
     [64] "is.recursive"          "is.relistable"         "isRestart"             "isS4"                  "isSealedClass"         "isSealedMethod"        "isSeekable"
     [71] "is.single"             "is.stepfun"            "is.symbol"             "isSymmetric"           "isSymmetric.matrix"    "is.table"              "isTRUE"
     [78] "is.ts"                 "is.tskernel"           "is.unsorted"           "is.vector"             "isVirtualClass"        "isXS3Class"
     

Comme nous l'avons dit précédemment, les structures de données de base sont les vecteurs et les listes. Il faut néanmoins bien connaitre les data frames et les matrices (et leurs "pièges") pour écrire de "beaux" programmes concis :


     # quelques pièges des data frames
     # -------------------------------
     
     > v1 <- 1:2                # nos données
     > v2 <- c("oui","non")     # class(v2) renvoie "character"
     
     > d <- data.frame(v1,v2)   # attention, voici le data frame
     
     > class(d)                 # sa classe
     
     [1] "data.frame"
     
     > is.list(d)               # est-ce une liste ?
     
     [1] TRUE                   # oui !
     
     > is.data.frame(d)         # est-ce aussi un data frame ?
     
     [1] TRUE                   # oui, bien sûr
     
     > is.vector(d$v1)          # quelle classe pour d$v1 ?
     
     [1] TRUE                   # facile
     
     > is.vector(d$v2)          # et pour d$v2 ?
     
     [1] FALSE                  # perdu, data.frame() convertit en facteur par défaut
     
     > d$v1                     # la preuve :
     
     [1] 1 2
     
     > d$v2
     
     [1] oui non
     Levels: non oui
     
     > is.factor(d$v2)          # vérfication
     
     [1] TRUE
     
     # quelques pièges des matrices
     # -------------------------------
     
     > m <- matrix(c(v1,v1*2,v1**2),nrow=3)
     
     
     > is.matrix(m)             # facile
     
     [1] TRUE
     
     > is.vector((m[,2]))       # aussi simple
     
     [1] TRUE
     
     > is.integer((m[,2]))      # plus difficile
     
     [1] FALSE                  # car 1 et 2 sont des réels (ce n'est pas 1L et 2L)
     
     > m[1,2] <- "0"
     
     > is.factor((m[,2]))       # pas de transformation en facteur
     
     [1] FALSE
     
     > is.character((m[,2]))    # mais conversion de TOUTE la matrice en caractères
     
     [1] TRUE
     

Il faut noter que R permet d'affecter des parties de vecteur ou de matrice grâce à l'indexation et au filtrage logique. Ainsi l'instruction :


     v[ (v<5) ] <- 0
     

vient remplacer dans v toutes les valeurs strictements inférieures à 5 par 0.

Enfin, il existe sous R plusieurs fonctions qui évitent de faire des tests logiques pour trouver des valeurs, les remplacer... comme les fonctions ifelse(), which(), grep(), sub()...

 
Exercices :           énoncés            solutions           [Retour à la page principale du cours]

 

 

retour gH    Retour à la page principale de   (gH)