Valid XHTML     Valid CSS2    

Introduction à la programmation R (exercices)

Séance 2 : Affectations, structures de données et affichages

                     gilles.hunault "at" univ-angers.fr

 

Table des matières cliquable

  1. Permutation (simple) de variables

  2. «Règles» d'écriture des noms de variables

  3. Calculs et affichages avec le symbole deux-points

  4. Pourcentages et normalisation

  5. Valeurs spéciales en R

  6. La fonction rep

  7. Souligner une phrase

  8. Les subtilités de la fonction cat()

  9. Calcul du χ2 d'adéquation avec contributions

10. Arbre syntaxique abstrait d'une expression

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

 

1. Permutation (simple) de variables

On dispose de deux valeurs a et b dont le contenu peut être numérique, texte ou autre (ce sont peut-être des listes). Trouver une façon simple de sauvegarder le contenu de chaque variable avant de les permuter, sans doute en quatre instructions (commentaires non compris).

Quelle est la solution classique en trois instructions, nommée "permutation circulaire" ?

Solution :  

Voici la solution "tranquille" où on sauvegarde chaque variable dans une autre variable


     # permutation des variables a et b
     
     aCopie <- a
     bCopie <- b
     
     # recopie
     
     a <- bCopie
     b <- aCopie
     

Puis la "fameuse" solution classique en trois instructions


     # permutation des variables a et b
     # méthode classique de la permutation circulaire
     
     temporaire <- a
     a          <- b
     b          <- temporaire
     

 

2. «Règles» d'écriture des noms de variables

Il est d'usage d'utiliser au moins trois ou quatre lettres pour les identifiants de variables, comme nbl ou nblignes pour indiquer le nombre de lignes, au lieu d'un simple n. Quelles sont les conventions d'écriture ? Laquelle fournit les identificateurs les plus lisibles ?

Solution :  

La convention camelCase et ses variantes écrit les mots collés avec initiale majuscule là où la convention snake_case les sépare avec un tiret souligné. D'autres notations sont possibles avec des points. R autorise même les noms de variables avec des espaces, ce qui n'est absolument pas pratique.

Utiliser des conventions de nommage présente de nombreux avantages (voir aussi naming conventions). Une pratique classique consiste à utiliser juste ou trois 4 lettres, comme un sigle, avec une tdc (table de correspondance) en début d'algorithme.


     # camelCase
     
     nombreDeLignes <- 10
     
     # snake_case
     
     nombre_de_lignes <- 10
     
     # point_case
     
     nombre.de.lignes <- 10
     
     # tdc (table de correspondance)
     
     nbl <- 10 # Nombre De Lignes
     

Il nous semble que la lisibilité est une notion subjective et qu'il est donc difficile de prétendre qu'une convention de nommage est vraiment meilleure qu'une autre. Dans les faits, certains langages imposent plus ou moins des conventions mais «chacun fait ce qui lui plait» au final.

 

3. Calculs et affichages avec le symbole deux-points

On suppose que n contient 10. Trouver comment générer :

  • tous les nombres de 1 à n2

  • le carré des nombres de 1 à n

  • les n premiers nombres pairs, impairs

  • les n premières puissances de 10

  • les n premières lettres de l'alphabet

Solution :  

Voici les réponses, sans plus d'explications 


     1:(n*n)         # tous les nombres de 1 à n² (autre solution 1:(n**2))
     
     (1:n)**2        # le carré des nombres de 1 à n
     
     2*(1:n)         # les n premiers nombres pairs (même si 2*1:n est plus court)
     
     2*(1:n) - 1     # les n premiers nombres impairs
     
     10**(1:n)       # les n premières puissances de 10
     
     LETTERS[1:n]    # les n premières lettres de l'alphabet
     

 

4. Pourcentages et normalisation

On suppose que le vecteur v contient des valeurs numériques. Trouver les instructions qui permettent d'afficher 

  • le vecteur normalisé issu de v (on divise par le max, la plus grande valeur est 1)

  • le vecteur centré issu de v (on soustrait la moyenne, la moyenne devient 0)

  • les pourcentages associés aux valeurs de v avec 2 décimales

Voici, si v contient les nombres 5 12 8, ce qu'on veut obtenir :


     vecteur original
     5 12 8
     
     vecteur normalisé (max=1)
     
     0.4166667 1.0000000 0.6666667
     
     vecteur centré (moyenne=0)
     
     -3.3333333  3.6666667 -0.3333333
     
     pourcentages avec deux décimales
     
     " 20.00 %" " 48.00 %" " 32.00 %"
     

Solution :  

Ces calculs ne requièrent que des affectations, bien sûr, et des appels de fonctions de R :


     # le vecteur v
     
     v <- c(5,12,8)
     
     # normalisation par le max
     
     vnormd  <- v/max(v)
     
     # centrage
     
     vcentrd <- v - moy(v)
     
     # les pourcentages pour v avec deux décimales
     
     sdv  <- sum(v)                     # somme des valeurs
     pct  <- 100*v/sum(v)               # pourcentages numériques
     fpct <- sprintf("%6.2f %%",pct)    # formatage
     
     # en une seule instruction (non conseillé ?)
     
     fpct <- sprintf("%6.2f %%",100*v/sum(v))
     

 

5. Valeurs spéciales en R

On veut créer le vecteur resultats. Sa première valeur est 5, sa troisième valeur est 8 mais pour l'instant on ne connait pas la valeur numéro deux. Comment faire ?

Dans le cours de la séance 2, comme dans les autres cours, il y a de nombreux liens sur l'aide en ligne des fonctions en R sous les mots en vert. Après avoir relu celle sur NULL, NaN, Inf, essayer de construire des expressions qui renvoient ces valeurs. Quelle est leur "classe" ? Peut-on leur ajouter 1, les utiliser dans des comparaisons ? Compléter avec les valeurs TRUE et FALSE.

Solution :  

Une valeur inconnue se note NA. De façon un peu surprenante, la classe de NA est logical comme pour TRUE (vrai) et FALSE (faux). Ajouter 1 à NA renvoie NA, faire une comparaison avec NA aussi.

NULL est identique au vecteur vide c() qu'il ne faut pas confondre avec la liste vide list(). La classe de NA est NULL. Ajouter 1 à NULL renvoie un peu mystérieusement numeric(0). Faire une comparaison avec NULL renvoie logical(0).

Une façon simple d'atteindre l'infini (!) est de chercher à calculer 1/0. La classe de Inf est numeric. Ajouter 1 à Inf renvoie Inf. Il n'y a que deux infinis pour R : Inf et -Inf.

Pour produire NaN, on peut chercher à calculer 0/0. La classe de NaN est numeric. Ajouter 1 à NaN renvoie NaN. Comparer 1 à NaN renvoie NA (encore fallait-il y penser !).


     ########################################
     #                                      #
     # la valeur NA                         #
     #                                      #
     ########################################
     
     > v <- c(5,NA,8)
     
     > sum(v)
       NA
     
     > min(v)
       NA
     
     > 1 + NA
       NA
     
     > 1 < NA
       NA
     
     ########################################
     #                                      #
     # la valeur NULL                       #
     #                                      #
     ########################################
     
     > x <- c()
     
     > identical( NULL, x)
       TRUE
     
     > identical( NULL, list() )
       FALSE
     
     > 1 + NULL
       numeric(0)
     
     > 1 < NULL
       logical(0)
     
     ########################################
     #                                      #
     # les valeurs infinies                 #
     #                                      #
     ########################################
     
     > z <- 1/0
       Inf
     
     > class(z)
       "numeric"
     
     > 1+z
       Inf
     
     > 1<z
       TRUE
     
     > identical(z,z)
       TRUE
     
     > identical(1/0,2/0)
       TRUE
     
     > identical(-1/0,2/0)
       FALSE
     
     > -1/0
       -Inf
     
     ########################################
     #                                      #
     # la valeur NaN                        #
     #                                      #
     ########################################
     
     > n <- NaN
       NaN
     
     > class(n)
       "numeric"
     
     > NaN+1
       NaN
     
     > NaN<1
       NA
     

Je vous laisse imaginer ce que peut donner Inf+NaN et Inf+NULL -- à savoir NaN et numeric(0) -- sachant qu'on ne fait pas en général ces calculs exprès, mais qu'on les obtient si des variables sont mal définies, si une lecture de fichier a échoué etc.

 

6. La fonction rep

Après avoir lu l'aide sur la fonction rep() et la fonction vector() essayer de produire un vecteur qui contient 10 fois la valeur 0 puis un autre vecteur qui contient 20 valeurs à savoir des 0 et des 1 en alternance puis 11 fois 0 suivis de 10 fois 1. Enfin, essayer de produire le vecteur 1 -2 3 -4...

Au passage, comment afficher un vecteur en colonne, avec le numéro de l'élément devant sa valeur comme ci-dessous ?


     [1]   1
     [2]  -2
     [3]   3
     [4]  -4
     [5]   5
     

Que contient la variable x si on la définit par x <- vector(length=8) ?

Solution :  

Voici les expressions demandées baties sur rep :


     # 10 fois la valeur 0
     
     rep(0,10)           # forme courte
     rep(x=0,times=10)   # forme longue nommée
     
     # 20 valeurs 0 et 1 en alternance
     
     rep( c(0,1), 10 )
     
     # 11 zéros suivi de 10 un
     
     rep( c(0,1) , c(11,10) )
     c( rep(0,11), rep(1,10) )
     
     # 1 -2 3 -4... n
     
     (1:n)*rep( c(1,-1),n/2) # si n pair...
     

Définir un vecteur en utilisant explicitement la fonction vector() n'est sans doute pas une bonne idée puisque la valeur par défaut est FALSE. De même écrire vector(mode="numeric",length=8) pour générer 8 fois 0 n'est sans doute pas très explicite par rapport à rep(0,times=8).

Pour forcer un affichage en colonne, on peut utiliser la fonction cbind() :


     # un vecteur "aléatoire"
     
     > x <- runif(5)
     
     # affichage classique
     
     > print( x )
       [1] 0.56070243 0.17023535 0.01200287 0.04084967 0.90089396
     
     
     # affichage en colonne
     
     > print( cbind(x) )
     
                   x
     [1,] 0.56070243
     [2,] 0.17023535
     [3,] 0.01200287
     [4,] 0.04084967
     [5,] 0.90089396
     
     

La fonction cbind() est surtout pratique pour afficher la correspondance entre numéro de colonne et nom de colonne :


     > data(iris)  # chargement des données
     
     # un affichage pas très facile à utiliser
     
     > print(names(iris))
     [1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"
     
     # un "joli" affichage avec cbind
     
     > print( cbind(names(iris)), quote = FALSE )
     
     [1,] Sepal.Length
     [2,] Sepal.Width
     [3,] Petal.Length
     [4,] Petal.Width
     [5,] Species
     

 

7. Souligner une phrase

On voudrait souligner avec le caractère "égal" un titre ou une phrase, comme dans l'exemple suivant où on a souligné le texte "un exemple de soulignement". Comment réaliser cela avec un seul appel de la fonction cat() ? On utilisera, «bien sûr», les fonctions nchar(), rep() et paste().


     
     un exemple de soulignement
     ==========================
     
     

Solution :  

Il faut produire autant de caractères "égal" qu'il y a de caractères dans la phrase indiquée. C'est justement le role de la fonction nchar() que de donner le nombre de caractères d'une chaine. La première tentative avec nchar() et rep() est presque la bonne :


     
     > phrase   <- "un exemple de soulignement"
     
     > nbCar    <- nchar(phrase)
     
     > souligne <- rep("=",nbCar)
     
     > print(souligne)
       "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="
     

Comme rep() renvoie un vecteur de caractères "égal", on utilise paste() avec le paramètre collapse pour n'avoir qu'une seule chaine de caractères :


     
     > phrase   <- "un exemple de soulignement"
     
     > nbCar    <- nchar(phrase)
     
     > souligne <- paste(rep("=",nbCar),collapse="")
     
     > cat(phrase,"\n",souligne,"\n",sep="")
     
     un exemple de soulignement
     ==========================
     

 

8. Les subtilités de la fonction cat()

Est-ce que l'instruction cat(x,y) est équivalente aux deux instructions cat(x) ; cat(y) ?

Comment doit-on afficher des informations sur deux lignes différentes, comme par exemple Bonjour et Bonsoir ?

Quels sont les autres paramètres de la fonction cat() ?

Solution :  

cat(x) ; cat(y) n'est pas équivalent à cat(x,y) parce que le séparateur par défaut de cat() est un espace. Donc cat("Bonjour\n","Bonsoir\n") produit un espace de plus devant Bonsoir que les deux instructions cat("Bonjour\n") et cat("Bonsoir\n"). Il est donc prudent d'écrire une ligne à la fois et de mettre \n comme dernier paramètre :


     # essai 1
     
     > cat("Bonjour\n","Bonsoir\n")
     Bonjour
      Bonsoir
     
     # essai 2
     
     > cat("Bonjour\n")
     Bonjour
     
     > cat("Bonsoir\n")
     Bonsoir
     
     # essai 3
     
     > cat("Bonjour\n","Bonsoir\n",sep="")
     Bonjour
     Bonsoir
     

Les autres paramètres principaux de cat() sont sep pour changer de séparateur file pour écrire dans un fichier au lieu de l'écran, même si utiliser sink() avec le paramètre split mis à TRUE est plus "intelligent". Voir l'exercice 2 de la séance 5 pour plus de détails à ce sujet.

 

9. Calcul du χ2 d'adéquation avec contributions

On dispose d'un vecteur Vobs d'effectifs observés et d'un vecteur Vthe d'effectifs théoriques. Comment effectuer le calcul et le cumul des carrés pondérés des différences relatives entre valeurs observées et valeurs théoriques (ou "contributions") que l'on triera par ordre croissant de pourcentage par rapport au total des contributions en n'utilisant que des affectations ?

Avec quelle fonction de R peut-on vérifier que le résultat de ces calculs est correct ?

On pourra, pour les vecteurs numériques définis par Vobs <- c(18,55,21,12,4) et Vthe <- c(6.875,27.5,41.25,27.5,6.875), s'inspirer de la présentation suivante pour afficher les résultats :


     Ind   Vthe Vobs     Dif      Cntr       Pct     Cumul
       2 27.500   55 -27.500 27.500000 42.060623  42.06062
       1  6.875   18 -11.125 18.002273 27.534066  69.59469
       3 41.250   21  20.250  9.940909 15.204394  84.79908
       4 27.500   12  15.500  8.736364 13.362069  98.16115
       5  6.875    4   2.875  1.202273  1.838849 100.00000
     

Solution :  

Puisque R est vectoriel, chaque colonne doit se calculer avec une seule instruction. Afin d'obtenir un bel affichage, on regroupe les variables crées dans un data frame. Les colonnes de ce data frame sont automatiquement nommées par R avec le nom des variables utilisées. Voici ces calculs :


     ## comparaisons entre valeurs observées et théoriques
     
     Vobs <- c(18,55,21,12,4)
     Vthe <- c(6.875,27.5,41.25,27.5,6.875)
     
     # on va trier, donc on garde les positions d'origine
     
     Ind   <- 1:length(Vthe)  # indices
     Dif   <- Vthe - Vobs     # différences
     Cntr  <- Dif*Dif/Vthe    # "contributions"
     
     # utiliser un data frame est le plus "propre" pour bien présenter les résultats
     
     df <- data.frame(Ind,Vthe,Vobs,Dif,Cntr)
     print(df,row.names=FALSE)
     

Arrivé ici, nous disposons donc déjà du tableau de résultats suivant :


     Ind   Vthe Vobs     Dif      Cntr
       1  6.875   18 -11.125 18.002273
       2 27.500   55 -27.500 27.500000
       3 41.250   21  20.250  9.940909
       4 27.500   12  15.500  8.736364
       5  6.875    4   2.875  1.202273
     

Il reste donc à à trier le tableau par contribution, ce qu'on réalise à l'aide la fonction order(), et à calculer les pourcentages de contributions, ce que l'on peut faire avec le code suivant (on notera les deux notation df[,"Pct"] et df$Pct pour accéder aux colonnes :


     # tri par contribution décroisssante
     
     idx <- order(Cntr,decreasing=TRUE)
     df  <- df[idx,]
     
     # ajout des pourcentages et du cumul
     
     df[,"Pct"] <- 100*df$Cntr/sum(Cntr)
     df$Cumul   <- cumsum(df$Pct)
     

Pour vérifier nos résultats, on peut comparer la somme de la colonne Cntr à la valeur du χ2 fourni par R via la fonction chisq.test() du package stats :


     > cat(" somme des contributions : ",sum(df$Cntr),"\n")
     
       somme des contributions :  65.38182
     
     > tdk2 <- chisq.test(x=Vobs,p=Vthe/sum(Vthe))
     > print( tdk2 )
     
      Chi-squared test for given probabilities
      data:  Vobs  X-squared = 65.382, df = 4, p-value = 2.138e-13
     

On voit alors qu'on trouve la même valeur 65.382 à l'arrondi près du calcul, mais il est préférable de le vérifier avec le code suivant :


     > print( identical( as.numeric(tdk2$statistic), sum(df$Cntr)) )
       TRUE
     

Ici, nous sommes obligés d'utiliser as.numeric() parce que la statistique de test est une variable nommée ; du coup, sa simple comparaison avec la somme des contributions échoue :


     > print( identical( tdk2$statistic, sum(df$Cntr)) )
       FALSE
     
     > names( tdk2$statistic)
       "X-squared"
     

Au passage, rappelons que le code de nombreuses fonctions R est consulatable. On trouvera ci-dessous celui de la fonction chisq.test() dont le coeur de calcul STATISTIC <- sum((x-E)^2/E) correspond exactement à notre calcul vectoriel.


     > print( chisq.test )
     
     function (x, y = NULL, correct = TRUE, p = rep(1/length(x), length(x)),
         rescale.p = FALSE, simulate.p.value = FALSE, B = 2000)
     {
         DNAME <- deparse(substitute(x))
         if (is.data.frame(x))
             x <- as.matrix(x)
         if (is.matrix(x)) {
             if (min(dim(x)) == 1L)
                 x <- as.vector(x)
         }
         if (!is.matrix(x) && !is.null(y)) {
             if (length(x) != length(y))
                 stop("'x' and 'y' must have the same length")
             DNAME2 <- deparse(substitute(y))
             xname <- if (length(DNAME) > 1L || nchar(DNAME, "w") >
                 30)
                 ""
             else DNAME
             yname <- if (length(DNAME2) > 1L || nchar(DNAME2, "w") >
                 30)
                 ""
             else DNAME2
             OK <- complete.cases(x, y)
             x <- factor(x[OK])
             y <- factor(y[OK])
             if ((nlevels(x) < 2L) || (nlevels(y) < 2L))
                 stop("'x' and 'y' must have at least 2 levels")
             x <- table(x, y)
             names(dimnames(x)) <- c(xname, yname)
             DNAME <- paste(paste(DNAME, collapse = "\n"), "and",
                 paste(DNAME2, collapse = "\n"))
         }
         if (any(x < 0) || anyNA(x))
             stop("all entries of 'x' must be nonnegative and finite")
         if ((n <- sum(x)) == 0)
             stop("at least one entry of 'x' must be positive")
         if (simulate.p.value) {
             setMETH <- function() METHOD <<- paste(METHOD, "with simulated p-value\n\t (based on",
                 B, "replicates)")
             almost.1 <- 1 - 64 * .Machine$double.eps
         }
         if (is.matrix(x)) {
             METHOD <- "Pearson's Chi-squared test"
             nr <- as.integer(nrow(x))
             nc <- as.integer(ncol(x))
             if (is.na(nr) || is.na(nc) || is.na(nr * nc))
                 stop("invalid nrow(x) or ncol(x)", domain = NA)
             sr <- rowSums(x)
             sc <- colSums(x)
             E <- outer(sr, sc, "*")/n
             v <- function(r, c, n) c * r * (n - r) * (n - c)/n^3
             V <- outer(sr, sc, v, n)
             dimnames(E) <- dimnames(x)
             if (simulate.p.value && all(sr > 0) && all(sc > 0)) {
                 setMETH()
                 tmp <- .Call(C_chisq_sim, sr, sc, B, E)
                 STATISTIC <- sum(sort((x - E)^2/E, decreasing = TRUE))
                 PARAMETER <- NA
                 PVAL <- (1 + sum(tmp >= almost.1 * STATISTIC))/(B +
                     1)
             }
             else {
                 if (simulate.p.value)
                     warning("cannot compute simulated p-value with zero marginals")
                 if (correct && nrow(x) == 2L && ncol(x) == 2L) {
                     YATES <- min(0.5, abs(x - E))
                     if (YATES > 0)
                       METHOD <- paste(METHOD, "with Yates' continuity correction")
                 }
                 else YATES <- 0
                 STATISTIC <- sum((abs(x - E) - YATES)^2/E)
                 PARAMETER <- (nr - 1L) * (nc - 1L)
                 PVAL <- pchisq(STATISTIC, PARAMETER, lower.tail = FALSE)
             }
         }
         else {
             if (length(dim(x)) > 2L)
                 stop("invalid 'x'")
             if (length(x) == 1L)
                 stop("'x' must at least have 2 elements")
             if (length(x) != length(p))
                 stop("'x' and 'p' must have the same number of elements")
             if (any(p < 0))
                 stop("probabilities must be non-negative.")
             if (abs(sum(p) - 1) > sqrt(.Machine$double.eps)) {
                 if (rescale.p)
                     p <- p/sum(p)
                 else stop("probabilities must sum to 1.")
             }
             METHOD <- "Chi-squared test for given probabilities"
             E <- n * p
             V <- n * p * (1 - p)
             STATISTIC <- sum((x - E)^2/E)
             names(E) <- names(x)
             if (simulate.p.value) {
                 setMETH()
                 nx <- length(x)
                 sm <- matrix(sample.int(nx, B * n, TRUE, prob = p),
                     nrow = n)
                 ss <- apply(sm, 2L, function(x, E, k) {
                     sum((table(factor(x, levels = 1L:k)) - E)^2/E)
                 }, E = E, k = nx)
                 PARAMETER <- NA
                 PVAL <- (1 + sum(ss >= almost.1 * STATISTIC))/(B +
                     1)
             }
             else {
                 PARAMETER <- length(x) - 1
                 PVAL <- pchisq(STATISTIC, PARAMETER, lower.tail = FALSE)
             }
         }
         names(STATISTIC) <- "X-squared"
         names(PARAMETER) <- "df"
         if (any(E < 5) && is.finite(PARAMETER))
             warning("Chi-squared approximation may be incorrect")
         structure(list(statistic = STATISTIC, parameter = PARAMETER,
             p.value = PVAL, method = METHOD, data.name = DNAME, observed = x,
             expected = E, residuals = (x - E)/sqrt(E), stdres = (x -
                 E)/sqrt(V)), class = "htest")
     }
     <bytecode: 0x559be56e1b28>
     <environment: namespace:stats>
     

 

10. Arbre syntaxique abstrait d'une expression

Que fait la fonction ast du package lobstr ?

Installer ce package et exécuter les instructions suivantes


     library("lobstr")
     
     # expression 1
     
     ast( n <- 8 )
     
     # expression 2
     
     ast( print( n[5] <- "2" ) )
     
     # expression 3
     
     ast(
     
      {
         x <- 3 ;
         if ( x > 0) {
           cat(" positif ")
         } else {
           cat(x," n'est pas positif ")
         } # fin de si
      }
     
     ) # fin de ast
     
     

Solution :  

La fonction ast fournit la représentation interne d'une instruction. Cela permet entre autres de voir que l'affectation, l'indexation en crochets et même l'instruction if sont des fonctions comme les autres.

     non su

 

 

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

 

 

retour gH    Retour à la page principale de   (gH)