Valid XHTML     Valid CSS2    

Introduction à la programmation R (exercices)

Séance 4 : Boucles et itérations

                     gilles.hunault "at" univ-angers.fr

 

Table des matières cliquable

  1. Equivalent TANT QUE d'une boucle POUR

  2. Nombre d'occurrences du maximum avec puis sans boucle

  3. Sommes par colonne

  4. Des boucles TANT QUE surprenantes

  5. Calculs complexes sur séries de fichiers

 
Il est possible d'afficher toutes les solutions via ?solutions=1 et de toutes les masquer avec via ?solutions=0.

 

1. Equivalent TANT QUE d'une boucle POUR

Ecrire en R une boucle TANT QUE équivalente à la boucle POUR suivante


     # une boucle POUR simple
     
     nbEtap <- 10 # nombre d'étapes en tout
     for (indEtap in (1:nbEtap)) {
       cat("étape ",sprintf("%2d",indEtap),"sur",nbEtap,"\n")
       # [...] suite des calculs de l'étape
     } # fin pour indEtap
     
     cat("après la boucle, l'indice indEtap vaut ",indEtap,"\n")
     

Solution :  

Voici une solution possible :


     # une boucle TANT QUE pour remplacer la boucle POUR simple
     
     nbEtap  <- 10                                 # nombre d'étapes en tout
     indEtap <-  1                                 # initialisation
     
     while (indEtap <= nbEtap) {
       cat("étape ",sprintf("%2d",indEtap),"sur",nbEtap,"\n")
       # [...] suite des calculs de l'étape
       indEtap <- indEtap + 1                      # incrémentation
     } # fin pour indEtap
     
     indEtap <- indEtap - 1                        # décrémentation pour correspondre à la boucle POUR
     cat("après la boucle, l'indice indEtap vaut ",indEtap,"\n")
     

Ce n'est en général pas une bonne idée que d'utiliser la valeur d'un indice de boucle en dehors de la boucle. Un indice de boucle doit être une variable locale qui ne doit pas exister en dehors de la boucle.

 

2. Nombre d'occurrences du maximum avec puis sans boucle

On veut trouver le maximum du vecteur V et son nombre d'occurrences. Ecrire une première boucle POUR qui détermine le maximum et une seconde boucle POUR qui calcule le nombre d'occurrences.

Réécrire ensuite tout cela avec une seule boucle POUR puis enfin donner la "bonne" solution R sans boucle explicite.

Serait-ce beaucoup plus compliqué de renvoyer la ou les positions du maximum dans le vecteur ?

Solution :  

Voici la première solution demandée :


     # nombre de valeurs
     
       nbVal <- length(V)
     
     # boucle 1 : calcul du maximum
     
       maxV <- V[ nbVal ]
       for (indv in (1:(nbVal-1))) {
         valC <- V[ indv ]
         if (valC>maxV) { # nouveau maximum
           maxV <- valC
         } # fin si
       } # fin pour indv
     
     # boucle 2 : nombre d'occurrences
     
       nbOcc <- 0
       for (indv in (1:nbVal)) {
         if (V[ indv] == maxV) {
           nbOcc <- nbOcc + 1
         } # fin si
       } # fin pour indv
     
     # affichage
     
       cat("le maximum est",maxV,"vu",nbOcc,"fois\n")
     

Puis la seconde :


     # nombre de valeurs
     
       nbVal <- length(V)
     
     # calcul du maximum et du nombre d'occurrences
     # en une seule boucle
     
       maxV  <- V[ nbVal ]
       nbOcc <- 1
       for (indv in (1:(nbVal-1))) {
         valC <- V[ indv ]
         if (valC>maxV) { # nouveau maximum
           maxV  <- valC
           nbOcc <- 1     # on recommence à compter
         } else {
           if (valC==maxV) { # nouvelle occurrence
              nbOcc <- nbOcc + 1
           } # fin si valC==maxV
         } # fin si valC>maxV
       } # fin pour indv
     
     # affichage
     
       cat("le maximum est",maxV,"vu",nbOcc,"fois\n")
     

Enfin, voici la "vraie" solution qui utilise la fonction max() et le filtrage vectoriel :


     # calcul du maximum
     
       maxV <- max(V)
     
     # nombre d'occurrences
     
       nbOcc <- sum( V==maxV )
     
     # affichage
     
      cat("le maximum est",maxV,"vu",nbOcc,"fois\n")
     

Vouloir déterminer la ou les positions du maximum est à peine plus compliqué. C'est un peu délicat parce qu'on ne connait pas à l'avance la taille du vecteur qui contiendra les positions. Voir l'exercice 3 de notre cours numéro 2 de programmation R avancée et sa solution.

La solution R vectorielle tient en une seule instruction :


     posmax <- which( V==maxV )
     
     cat("les positions du maximum sont :")
     cat("   ",paste(posmax,collapse=" "),"\n")
     
     

 

3. Sommes par colonne

On dispose d'une matrice de valeurs numériques, comme par exemple celle définie par


     m <- matrix(1:12,nrow=2,byrow=TRUE)
     

Calculer la somme de chaque colonne avec une boucle POUR puis sans boucle POUR à l'aide de la fonction apply(). Quelle est enfin la "bonne" solution R ?

Solution :  

Voici la solution avec une boucle POUR explicite


     # calculs des sommes par colonne avec une boucle POUR explicite
     
     nbCol   <- ncol(m)
     somcols <- rep(NA,nbCol)
     
     for (indc in (1:nbCol)) {
       somcols[indc] <- sum( m[,indc] )
     } # fin pour indc
     

La solution avec apply() utilise une boucle POUR implicite


     ## calculs des sommes par colonne avec la fonction apply
     
     # pas besoin de nommer les paramètres, il s'agit de l'ordre
     # utilisé dans la définition de la fonction apply :
     
       apply(m,2,sum)
     
     # mieux, utilisation du nom des paramètres :
     
       apply(X=m,MARGIN=2,FUN=sum)
     
     # acceptable et reconnu par R :
     
       apply(X=m,M=2,F=sum)
     
     # sans doute la meilleure solution car elle suit
     # l'ordre somme colonnes matrice :
     
       apply(FUN=sum,MARGIN=2,X=m)
     

La "vraie" solution fait appel à la fonction colSums() :


     # calculs des sommes par colonne
     
     colSums(m)
     

 

4. Des boucles TANT QUE surprenantes

Que font les boucles TANT QUE suivantes ?


     # Des boucles TANT QUE surprenantes
     #
     ####################################
     
     # une boucle TANT que non infinie
     # -------------------------------
     
     n1 <- 1
     n2 <-  n1/10
     while (abs(n1-n2)>0) {
       n1  <-  n2
       n2  <-  n2/10
     cat(n1,n2,"\n")
     } # # fin de tant que
     
     
     # une boucle TANT qui va à l'infini
     # -------------------------------
     
     n <- 1
     while (!is.infinite(n)) {
       n <- 10*n
       cat(n,"\n")
     } ; # fin tant que
     

Solution :  

La première boucle permet de voir à quel moment R confond un très petit nombre avec 0 (puisque 0=0/10). La seconde montre à quel moment R confond un très grand nombre avec l'infini. Voici un extrait de l'exécution de ces boucles qui, heureusement, ne durent pas longtemps :


     # boucle TANT QUE numéro 1
     
     > n1 = 1
     > n2 = n1/10
     > while (abs(n1-n2)>0) {
     +   n1  = n2
     +   n2  = n2/10
     + cat(n1,n2,"\n")
     + } # # fin de tant que
     
     0.1 0.01
     0.01 0.001
     0.001 1e-04
     1e-04 1e-05
     1e-05 1e-06
     1e-06 1e-07
     [...]
     1e-315 1e-316
     1e-316 9.999997e-318
     9.999997e-318 9.999987e-319
     9.999987e-319 9.999889e-320
     9.999889e-320 9.999889e-321
     9.999889e-321 9.980126e-322
     9.980126e-322 9.881313e-323
     9.881313e-323 9.881313e-324
     9.881313e-324 0
     0 0
     >
     
     # boucle TANT QUE numéro 2
     
     > n <- 1
     > while (!is.infinite(n)) {
     +   n <- 10*n
     +   cat(n,"\n")
     + } ; # fin tant que
     
     10
     100
     1000
     10000
     1e+05
     1e+06
     1e+07
     [...]
     1e+307
     1e+308
     Inf
     >
     

On pourra consulter les variables et utiliser les fonctions suivantes pour avoir plus de détails sur les limitations machines et la version de R utilisée :


     # variables (noter le point an début)
     
     .Machine
     .Platform
     version
     
     # fonctions
     
     Sys.info()
     sessionInfo()
     

 

5. Calculs complexes sur séries de fichiers

On veut traiter n fichiers, disons ficSerie01.txt, ficSerie02.txt, ficSerie03.txt... mais ce pourrait être bien sûr des fichiers Excel. Une fois le traitement de chaque fichier effectué, on veut obtenir un tableau et un fichier .CSV résumé des traitements. Le traitement consiste à renvoyer le nombre de lignes et le plus petit age et le plus grand age. Peut-on aussi calculer la moyenne des ages et la moyenne générale des ages ainsi ?

Solution :  

Par rapport à l'exemple de la séance 4, il suffit de modifier ce que renvoie la fonction qui traite les fichiers et d'augmenter le data frame résultat, soit le code 


     # traitement d'un fichier :
     # on détermine son nombre de lignes
     # on trouve le min et le max de l'age
     # on renvoie des NA si le fichier n'est pas vu
     
     traiteFichier <- function( nomFic ) {
     
       cats(paste("Traitement du fichier",nomFic))
       if (!file.exists(nomFic)) {
          cat("fichier",nomFic,"non vu.\n")
          dataRet <- list(nbl=NA,minage=NA,maxage=NA)
       } else {
          dataFic <- read.table(nomFic,head=TRUE)
          nblc    <- nrow(dataFic)
          mina    <- min(dataFic$AGE)
          maxa    <- max(dataFic$AGE)
          cat("il y a",nblc,"lignes de données dans",nomFic,"\n")
          cat("age min =",mina,"; age max =",maxa,"\n")
          dataRet <- list(nbl=nblc,minage=mina,maxage=maxa)
       } # fin si
     
       return(dataRet)
     
     } # fin de fonction traiteFichier
     
     # traitement d'une série de fichiers
     # à l'aide de la fonction traiteFichier()
     # on sauvegarde dans un tableau les informations
     # renvoyées, on les affiche
     # et on les exporte dans un fichier .CSV
     
     # désignation des fichiers
     
     nbfic        <- 3
     nomsFichiers <- paste("ficSerie",sprintf("%02d",1:nbfic),".txt",sep="")
     
     # structure d'accueil des résultats (data fram)
     
     tabRes        <- data.frame(matrix(NA,nrow=nbfic+1,ncol=4))
     names(tabRes) <- c("fichier","nombre de lignes","age min","age max")
     
     # boucle de traitement et remplissage de la structure d'accueil
     
     idf <- 0 # numéro courant de fichier
     for (nomfic in nomsFichiers) {
         idf  <- idf + 1
         lRet <- traiteFichier( nomfic )
         tabRes[ idf, 1 ] <- nomfic
         tabRes[ idf, 2 ] <- lRet$nbl
         tabRes[ idf, 3 ] <- lRet$minage
         tabRes[ idf, 4 ] <- lRet$maxage
     } # fin pour nomfic
     
     cats("Analyse globale des fichiers")
     
     # ajout de la moyenne sur l'ensemble des fichiers
     
     tabRes[ (nbfic+1), 1 ] <- "global"
     tabRes[ (nbfic+1), 2 ] <- mean(as.numeric(tabRes[(1:nbfic),2]))
     tabRes[ (nbfic+1), 3 ] <- min(as.numeric(tabRes[(1:nbfic),3]))
     tabRes[ (nbfic+1), 4 ] <- max(as.numeric(tabRes[(1:nbfic),4]))
     
     # affichage et export
     
     cat("Voici le tableau résumé\n")
     print(tabRes)
     
     nomCsv <- "tabRes.csv"
     write.csv(x=tabRes,file=nomCsv)
     cat("\nrésultats écrits dans",nomCsv,"\n")
     

Exemple d'exécution avec les fichiers cités :


     
     Traitement du fichier ficSerie01.txt
     ====================================
     
     il y a 100 lignes de données dans ficSerie01.txt
     age min = 11 ; age max = 78
     
     Traitement du fichier ficSerie02.txt
     ====================================
     
     il y a 30 lignes de données dans ficSerie02.txt
     age min = 11 ; age max = 78
     
     Traitement du fichier ficSerie03.txt
     ====================================
     
     il y a 40 lignes de données dans ficSerie03.txt
     age min = 12 ; age max = 93
     
     Analyse globale des fichiers
     ============================
     
     Voici le tableau résumé
              fichier nombre de lignes age min age max
     1 ficSerie01.txt        100.00000      11      78
     2 ficSerie02.txt         30.00000      11      78
     3 ficSerie03.txt         40.00000      12      93
     4         global         56.66667      11      93
     
     résultats écrits dans tabRes.csv
     

Fichier CSV produit :


     "","fichier","nombre de lignes","age min","age max"
     "1","ficSerie01.txt",100,11,78
     "2","ficSerie02.txt",30,11,78
     "3","ficSerie03.txt",40,12,93
     "4","global",56.6666666666667,11,93
     

Pour la moyenne de l'age, on ne peut pas procéder de la même manière parce que la moyenne des moyennes n'est pas la moyenne générale. Il faudrait calculer une moyenne pondérée des moyennes par fichier... Si le calcul n'est pas associatif, il n'est pas possible de traiter séparément les fichiers, c'est bien le problème des fameux map/reduce lié au Big Data : la somme et le max sont associatifs, pas la moyenne...

 

 

Code-source php de cette page. Retour à la page principale du cours.

 

 

retour gH    Retour à la page principale de   (gH)