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
8. Les subtilités de la fonction cat()
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 : afficher la 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 <- aCopiePuis 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 : afficher la 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 LignesIl 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 : afficher la 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 : afficher la 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 : afficher la 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 NAJe 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] 5Que contient la variable x si on la définit par x <- vector(length=8) ?
Solution : afficher la 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.90089396La 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 : afficher la 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 : afficher la 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 BonsoirLes 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.00000Solution : afficher la 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.202273Il 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-13On 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)) ) TRUEIci, 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 astSolution : afficher la solution
Code-source php de cette page. Retour à la page principale du cours.
Retour à la page principale de (gH)