Programmer en R
Session de formation, décembre 2013
gilles.hunault "at" univ-angers.fr
Enoncés pour la séance numéro 1
Qu'est-ce que programmer en R par rapport à la programmation classique, disons en C, C++ ou Java ? Quelles différences avec la programmation Perl, Php, Ruby, Python ?
Que faut-il programmer en R ? Quand doit-on programmer en R ?
Pourquoi dit-on que tout est fonctionnellement objet en R, même les fonctions, l'affectation et l'indexation ?
Comment définir une «fonction nommée» ?
Définir une fonction nommée carre() qui calcule le carré d'un objet et expliquer comment on peut s'en servir sur les différentes structures de données de R.
Application : définir une fonction nommée pvalt() qui renvoie la p-value d'un test t et l'appliquer à la comparaison des dix premières lignes des deux premières colonnes des données iris.
Qu'appelle-t-on une «fonction anonyme» et comment s'en sert-on ?
Application : comment calculer les moyennes en colonne d'un jeu de données avec des valeurs NA, comme par exemple les colonnes 2 à 4 du fichier diabete.dar sachant que la colonne Pnum est un identifiant ?
Rappeler les types de données et les structures de données disponibles en R. On pourra utiliser les objets suivants (qu'on peut copier/coller dans une session R) :
obj01 <- 1 # à ne pas confondre avec obj01 <- 1L avec un "L" en fin obj02 <- 2.0 obj03 <- "Age" obj04 <- TRUE obj05 <- NA obj06 <- Inf obj07 <- NaN obj08 <- 1:2 obj09 <- c(1,2) obj10 <- c(obj01,obj02) obj11 <- c(1,NA) obj12 <- c(obj01,obj03) obj13 <- list(obj01,obj02) obj14 <- list(a=obj01,b=obj02) obj15 <- function(x) { return( x*x ) } obj16 <- vector(mode="integer",length="3") obj17 <- array(data=1:4,dim=c(2:3)) obj18 <- matrix(data=1:6,nrow=2,ncol=3) # data=1:4 est incorrect obj19 <- as.data.frame(obj17) obj20 <- as.data.frame(obj18)Au passage, comment supprimer les 20 objets créés ?
Y a-t-il une autre valeur particulière en R ? Que vaut TRUE*TRUE ? Combien y a-t-il de fonctions de conversion ? et de test ? et d'affichage ? et de résumé ?
Que vaut c(1,2,"3",4) ? Si m est une matrix de valeurs numériques, que fait m[1,2] <-"5" ?
Comment appliquer un calcul à tous les éléments d'une structure ? Rappeler au passage la différence entre 1:3 et c(1,2,3), entre [i] et [[i]]. Donner un exemple d'utilisation de apply(), lapply(), mapply(), sapply(). Y a-t-il d'autres fonctions basées sur apply ?
Expliquer les instructions suivantes :
# les mots ldm <- unlist( strsplit(x="le chat mange la souris",split=" ")) # version courte nchar(ldm) # un peu mieux avec sapply ? sapply(FUN=function(x) { nchar(x) },X=ldm) # on n'est pas obligé de tout détailler sapply(FUN=nchar,X=ldm)Quel est le rapport entre tapply(), lapply() et split() ?
Si mdata1, mdata2, mdata3... sont des dataframes comment obtenir la liste de tous les objets dont le nom commence par mdata ? Comment avoir rapidement leurs dimensions ?
Exécuter avec un papier et un crayon les instructions suivantes et détailler ce qu'on obtient.
# partie 1 a <- 12 b <- 3 sov <- list(a=a,b=b) a <- sov$b b <- sov$a # partie 2 a <- a + b b <- a - b a <- a - b # partie 3 c <- 5 p <- min(a,min(b,c)) g <- max(a,max(b,c)) m <- max( min(a,b), min(a,c) ) # partie 4 a <- 8 b <- 6 pg <- 0 if (a<b) { pg <- c(a,b) } # fin de si 1 if (a<b) { p <- a g <- b } else { p <- b g <- a } # fin de si 2 # partie 5 pg <- (-1) p <- (-2) g <- (-3) if (a<b) # attention 1 pg <- c(a,b) if (a<b) # attention 2 p <- a g <- b if (a<b) # attention 3 p <- a g <- b else p <- b g <- aQuelles fonctions de R permettent d'éviter de programmer si l'on veut :
- trouver la [première] position du maximum dans un vecteur,
- dichotomiser une vecteur (par exemple pour répartir des valeurs d'age en deux classes "jeune" et "vieux"),
- calculer les moyennes, mininima, maxima et écart-types des colonnes d'une matrice,
- diviser chaque ligne d'une matrice par sa somme [en ligne],
- convertir une colonne d'un data frame (par exemple de pouces en cms) ?
On génèrera, pour tester les calculs correspondants, un vecteur d'entiers, une matrice d'entiers, un dataframe. Pour ce dernier, on viendra, toujours sans programmation, produire des noms de ligne comme Ind_001, Ind_002...
Comment fait-on pour numéroter rapidement les éléments d'une structure, d'un vecteur par exemple, ou les noms de colonne d'une matrice ou d'un dataframe ?
Quel choix a-t-on pour définir et utiliser les paramètres d'une fonction ? Qu'est-ce que l'ellipse notée ... ?
Comment tester si les paramètres d'une fonction sont présents ? Pourquoi dit-on qu'une fonction en R est une variable ?
Quel choix a-t-on pour renvoyer les valeurs calculées par une fonction ? A quoi sert la fonction invisible() ?
Détailler ce qui se passe pour l'extrait de session R suivante :
# définition des fonctions carre <- function(x) { return(x*x) } # pour mémoire f <- function(x) carre(x) g <- function(x=NA) { if (is.list(x)) { return( lapply(x,carre) ) } else { return( carre(x) ) } # fin de si } # fin de fonction g h <- function(x=NA,graphique=FALSE,...) { if (is.list(x)) { return( lapply(x,carre) ) } else { y <- carre(x) if (graphique) { plot( y, ...) } # fin de si sur graphique return( y ) } # fin de si sur is.list } # fin de fonction h # utilisation des fonctions f # not run: f() f(3) f(1:5) # not run: f(list(a=2,b=2)) g() g(3) g(list(a=2,b=2)) li <- list(a=2,b=2) h h() h(3) h(1:5) h(li) h( 1:5, TRUE ) h(x=1:5, graphique=TRUE ) h(x=1:5, graphique=TRUE,col="red",pch=20 )Rappeler au passage comment on se sert de source() et de sink(). Que fait la fonction sinksrc() qui fait partie de statgh.r ?
Rappeler les actions élémentaires de controle de flux.
Ecrire une boucle avec un test pour déterminer le maximum d'un vecteur puis écrire une deuxième boucle pour compter combien de fois on trouve ce maximum. Réécrire cela en une seule boucle puis trouver une solution sans boucle.
Comment connaitre la liste des objets d'un package avec leur classe ? On écrira une fonction ls2() qu'on appliquera à "base" puis à "stats" et à "utils".
Dans le même genre d'idées, comment connaitre les différents jeux de données d'un package avec leur(s) classe(s) et leur(s) dimension(s) ? On écrira une fonction data2() qu'on appliquera à "datasets" puis à "ade4". On s'arrangera pour "bien" afficher la dimension des objets.
Comment choisir entre plusieurs implémentations d'une fonction qui réalisent la même action ? Pourquoi faut-il privilégier les actions vectorielles de R ? Quelles sont les autres bonnes pratiques en R ? Ecrire une fonction duree() pour savoir combien de temps dure l'éxécution d'une commande, d'un script...
Voici une liste de livres qui traitent de R :
Quels autres livres peut-on lire sur R en général et en français ? et sur la programmation en R ? Ou quels autres documents du Web ?
Quel(s) site(s) Web en français donne(nt) la syntaxe des fonctions de base de R rangées par catégorie ?
Quels sites Web anglais ou français sont dédiés à R en-dehors du CRAN ?
Trouve-on sur Youtube des vidéos pour apprendre à utiliser R ?
Quels sites permettent de trouver des fonctions ou des packages par mot-clé ?
Solutions pour la séance numéro 1
Programmer en R, ce n'est pas, comme en C, C++ ou en Java, écrire un programme principal, mais plutôt écrire des fonctions, c'est-à-dire des sous-programmes. Au lieu d'un programme, on utilise en général un code-source qui réalise des appels à des fonctions. Il n'y a donc pas au départ de fonction plus générale ou plus importante qu'une autre, ni de compilation à effectuer.
Contrairement à C, C++, Java, mais comme Perl, Php, Ruby, Python, R est non typé explicitement et ne se compile pas. On peut donc changer de type (numérique, caractère...) en cours de route, on n'a pas besoin de déclarer les variables qu'on veut utiliser, ce qui peut être source d'erreurs. La notion d'environnement (session, variables...) est particulière à R. C'est ce qui permet de l'utiliser en interactif, dans une interface comme Rstudio ou Rgui, ou dans une sur-interface comme Rcmdr ou Rkward.
Programmer en R peut servir à automatiser une série de calculs ou de transformations, à interagir avec l'utilisateur. On doit programmer dès qu'on a affaire à des actions répétitives ou longues à saisir au clavier. Tout est programmable : les calculs, les graphiques, les affichages, les opérations sur fichiers, l'interaction avec l'utilisateur...
On peut en R redéfinir une fonction déja définie (contrairement à Php), comme en Ruby et Python, et on peut même -- ce qui peut se révéler très dangereux -- redéfinir les fonctions primitives.
On dit que tout est fonctionnellement objet en R, y compris les fonctions parce que tout est implémenté et géré de la même façon : les données comme les fonctions, avec une syntaxe objet (ou "notation") fonctionnelle :
# tout est objet, même les fonctions # et tout s'écrit de la même façon x <- 1 print( x ) y <- x print( class(y) ) ca <- function(x) { return(x*x) } print( ca ) cb <- ca print( class(cb) )Les notations particulières pour l'affectation et l'indexation sont une facilité d'écriture alors qu'il existe une «vraie» notation fonctionnelle associée :
# en R, l'affectation est une fonction "<-"(x,1) # équivalent à x <- 1 # une autre écriture : assign("x",1) # pour mémoire, l'écriture "au bout et vers la droite" est aussi autorisée 1 -> x # l'indexation est aussi fonction print( "["(letters,3) ) # équivalent à print( letters[3] )La plus grande différence avec les autres langages de programmation est que tous les indices des structures commencent à 1.
Pour définir une fonction nommée, il faut réaliser une affectation avec comme partie gauche le nom de la fonction à créer, puis utiliser le mot function, mettre des parenthèses, ajouter des paramètres s'il y en a besoin avec éventuellement des valeurs par défaut, puis écrire le corps de la fonction entre accolades et mettre return avec des parenthèses pour indiquer l'objet renvoyé, qui peut être une simple valeur ou quelque chose de plus complexe. Par exemple, pour le calcul du carré, on devrait écrire
carre <- function(x) { return(x*x) }Il est possible de raccourcir le corps de la fonction en omettant les accolades et en mettant comme dernière (ou comme seule) instruction le calcul à renvoyer. Ainsi, on pourrait écrire
carre <- function(x) x*xUne écriture aussi concise est utile dans le cas d'une fonction intégrée à un appel de fonction, mais cette écriture est dangereuse et «fainéante». Il vaut mieux lui préférer le code suivant :
carre <- function(x) { # calcule le carré de son argument y <- x*x return(y) } # fin de fonction carreNous reviendrons sur ces écritures, mais un grand principe de la programmation robuste est qu'il faut préférer la lisibilité, la facilité de relecture et la maintenance du code à la facilité d'écriture. Il faut viser l'écriture de programmes robustes et lisibles et non pas de programmes vite tapés sur le clavier.
Pour se servir de cette fonction carre() il suffit de l'appeler là où on utiliserait le carré des valeurs. Cela peut donc être dans le cadre d'un calcul simple ou d'un affichage interactif (en console), dans le cadre d'une instruction d'affectation. Il n'est pas obligatoire ici de nommer le paramètre (mais il vaudra mieux le faire quand il y en aura plus d'un). On peut aussi utiliser juste le nom de la fonction en tant que paramètre quand cela est permis. Le calcul effectué par notre fonction étant "vectoriel", c'est-à-dire applicable à une structure, notre fonction est elle-même vectorielle. Voici des exemples d'utilisation :
## définition de la fonction carre <- function(x) { # calcule le carré de son argument y <- x*x return(y) } # fin de fonction carre # ------------------------------------------------------ # calcul interactif carre(5) # suffisant, mais carre(x=5) est OK aussi # affichage en interactif cat("le carré de 8 est ",sprintf("%05d",carre(x=8)),"\n") # noter %05d au lieu de %5d # à l'intérieur d'une affectation lesCarresPlusUn <- 1 + carre(1:10) # en tant que paramètre lapply(X=list(a=1,b=3,c=5),FUN=carre)Et leurs résultats :
[1] 25 le carré de 8 est 00064 # non affiché : [1] 2 5 10 17 26 37 50 65 82 101 $a [1] 1 $b [1] 9 $c [1] 25Pour définir la fonction pvalt() qui doit renvoyer la p-value du test t, il faut commencer par comprendre l'objet renvoyé par la fonction t.test(), puis trouver comment extraire la composante voulue pour ensuite écrire la fonction et enfin vérifier qu'elle renvoie bien ce qu'on voulait :
# toutes les données iris data(iris) # chargement "fainéant" (lazy loading) du data.frame iris # juste les deux premières colonnes et les dix premières lignes lesd <- iris[ (1:10) , (1:2) ] # plus "propre" que iris[1:10,1:2] # effectuons le test et essayons de comprendre où est mémorisée # la p-value tt <- t.test(lesd) print(tt) # la p-value est donc 8.008e-15 # quelques essais pour comprendre ce que renvoie la fonction t.test() print(class(tt)) print(is.list(tt)) print(names(tt)) print(tt$p.value) # c'est bien cela # définissons rapidement la fonction demandée pvalt <- function(test) { return( test$p.value ) } # vérifions que c'est bien ce qu'il faut : identical( tt$p.value, pvalt(tt) ) # doit renvoyer TRUE> # toutes les données iris > > data(iris) # chargement "fainéant" (lazy loading) du data.frame iris > # juste les deux premières colonnes et les dix premières lignes > > lesd <- iris[ (1:10) , (1:2) ] # plus "propre" que iris[1:10,1:2] > # effectuons le test et essayons de comprendre où est mémorisée > # la p-value > > tt <- t.test(lesd) > print(tt) # la p-value est donc 8.008e-15 One Sample t-test data: lesd t = 21.5729, df = 19, p-value = 8.008e-15 alternative hypothesis: true mean is not equal to 0 95 percent confidence interval: 3.688668 4.481332 sample estimates: mean of x 4.085 > print(class(tt)) [1] "htest" > print(is.list(tt)) [1] TRUE > print(names(tt)) [1] "statistic" "parameter" "p.value" "conf.int" "estimate" "null.value" "alternative" "method" "data.name" > print(tt$p.value) # c'est bien cela [1] 8.007948e-15 > # définissons la fonction demandée > > pvalt <- function(test) { return( test$p.value ) } > # vérifions que c'est bien ce qu'il faut : > > identical( tt$p.value, pvalt(tt) ) # doit renvoyer TRUE [1] TRUEUne fonction anonyme correspond à la seule partie droite de définition de la fonction. Comme on l'utilise sans affectation, elle n'a pas de nom. Une telle fonction s'utilise généralement comme paramètre dans un appel de fonction. Ainsi, le dernier calcul de la question précédente, avec la fonction carre() appliquée à une liste, pourrait se réaliser par
lapply(X=list(a=1,b=3,c=5),FUN=function(x) x*x)Si on se rend compte qu'on utilise plusieurs fois la même fonction anonyme, il est conseillé d'en faire une fonction nommée. Cela gagne du temps et de la maintenance de code.
Pour les données diabete.dar proposées, la fonction anonyme utilisée permet de spécifier na.rm=TRUE à la volée :
# lecture de données sur Internet url <- "http://forge.info.univ-angers.fr/~gh/wstat/Introduction_R/diabete.dar" diaTout <- read.table(url,header=TRUE,row.names=1) # on se restreint aux colonnes 2 à 4, qui contiennent des NA dia <- diaTout[,(2:4)] # on ne peut pas calculer la moyenne à cause des NA cat("Moyenne en colonne, version 1\n") (res <- apply(X=dia,MARGIN=2,FUN=mean) ) # les parenthèses forcent l'affichage # on utilise une fonction anonyme définie "juste au passage" cat("Moyenne en colonne, version 2\n") (res <- apply(X=dia,MARGIN=2,FUN=function(x) { return(mean(x,na.rm=TRUE)) } ) ) #### on aurait pu se contenter de #### #### (res <- apply(X=dia,MARGIN=2,FUN=function(x) mean(x,na.rm=TRUE)) ) ####Moyenne en colonne, version 1 chol stab.glu hdl NA 106.6725 NA Moyenne en colonne, version 2 chol stab.glu hdl 207.84577 106.67246 50.44527Les types de données sont les constantes, les objets spéciaux, les valeurs logiques, les nombres et les chaines de caractères. Les structures de données sont les vecteurs, les matrices, les listes et les "data frames" (qui sont des listes particulières). On connait la nature d'un objet grâce aux fonctions class(), typeof(), mode() et storage.mode().
source("objets.r",echo=TRUE,print.eval=TRUE) analyse <- function(x) { return( c( class(x), typeof(x), mode(x), storage.mode(x) ) ) } mdr <- rbind( # matrice des résultats analyse(obj01) , analyse(obj02) , analyse(obj03) , analyse(obj04) , # R autorise ce genre d'écriture lisible analyse(obj05) , # et compréhensible analyse(obj06) , analyse(obj07) , analyse(obj08) , analyse(obj09) , analyse(obj10) , analyse(obj11) , analyse(obj12) , analyse(obj13) , analyse(obj14) , analyse(obj15) , analyse(obj16) , analyse(obj17) , analyse(obj18) , analyse(obj19) , analyse(obj20) ) # fin de rbind colnames(mdr) <- c("class()","typeof()","mode()","storage.mode()") row.names(mdr) <- paste("obj",sprintf("%02d",1:nrow(mdr)),sep="") cat("\nVoici les caractéristiques des objets\n") print( mdr, quote=FALSE ) cat("\nUne façon courte de voir les objets et leur type utilise ls.str() :\n") ls.str(pattern="^obj[0-9]*$")> obj01 <- 1 > obj02 <- 2.0 > obj03 <- "Age" > obj04 <- TRUE > obj05 <- NA > obj06 <- Inf > obj07 <- NaN > obj08 <- 1:2 > obj09 <- c(1,2) > obj10 <- c(obj01,obj02) > obj11 <- c(1,NA) > obj12 <- c(obj01,obj03) > obj13 <- list(obj01,obj02) > obj14 <- list(a=obj01,b=obj02) > obj15 <- function(x) { return( x*x ) } > obj16 <- vector(mode="integer",length="3") > obj17 <- array(data=1:4,dim=c(2:3)) > obj18 <- matrix(data=1:6,nrow=2,ncol=3) # data=1:4 est incorrect > obj19 <- as.data.frame(obj17) > obj20 <- as.data.frame(obj18) Voici les caractéristiques des objets class() typeof() mode() storage.mode() obj01 numeric double numeric double obj02 numeric double numeric double obj03 character character character character obj04 logical logical logical logical obj05 logical logical logical logical obj06 numeric double numeric double obj07 numeric double numeric double obj08 integer integer numeric integer obj09 numeric double numeric double obj10 numeric double numeric double obj11 numeric double numeric double obj12 character character character character obj13 list list list list obj14 list list list list obj15 function closure function function obj16 integer integer numeric integer obj17 matrix integer numeric integer obj18 matrix integer numeric integer obj19 data.frame list list list obj20 data.frame list list list Une façon courte de voir les objets et leur type utilise ls.str() : obj01 : num 1 obj02 : num 2 obj03 : chr "Age" obj04 : logi TRUE obj05 : logi NA obj06 : num Inf obj07 : num NaN obj08 : int [1:2] 1 2 obj09 : num [1:2] 1 2 obj10 : num [1:2] 1 2 obj11 : num [1:2] 1 NA obj12 : chr [1:2] "1" "Age" obj13 : List of 2 $ : num 1 $ : num 2 obj14 : List of 2 $ a: num 1 $ b: num 2 obj15 : function (x) obj16 : int [1:3] 0 0 0 obj17 : int [1:2, 1:3] 1 2 3 4 1 2 obj18 : int [1:2, 1:3] 1 2 3 4 5 6 obj19 : 'data.frame': 2 obs. of 3 variables: $ V1: int 1 2 $ V2: int 3 4 $ V3: int 1 2 obj20 : 'data.frame': 2 obs. of 3 variables: $ V1: int 1 2 $ V2: int 3 4 $ V3: int 5 6Pour supprimer un objet, on utilise la fonction rm(). Ainsi rm(x) supprime x. Pour supprimer un ensemble d'objets, on utilise rm( list=...). Ici, on peut donc écrire rm(list=ls(pattern="^obj*")) pour supprimer les objets nommés obj01, obj02... obj20. Pour tout supprimer, ce qui peut être très dangereux, il suffit donc d'écrire : rm(list=ls(pattern="*")).
Il existe aussi un objet spécial nommé NULL pour lequel notre fonction analyse() renvoie 4 fois NULL.
TRUE*TRUE vaut 1 car TRUE vaut 1 et FALSE vaut 0. C'est ce qui permet de réaliser des comptages comme sum(obj18>3).
Les fonction de conversion s'appellent «as point quelque chose». Ecrire apropos("as") renvoie le nom de toutes les fonctions qui contiennent la chaine as, y compris xtfrm.AsIs. L'instruction apropos("^as") renvoie le nom de toutes les fonctions qui commencent par as, y compris assign ; pour avoir celles qui commencent vraiment par as"point", il faut utiliser apropos("^as\\."). Il y en a plus d'une centaine. On notera que contrairement à help(), apropos() oblige à utiliser des guillemets pour préciser ce qu'on cherche.
Les fonctions de test commencent par is., celles d'affichage par print. et celles de résumé par summary. ; nous définissons la fonction concatv() suivante pour afficher leurs noms en parallèle :
tc1 <- apropos("^as\\.") # apropos("^as") est incorrect à cause de asc, assign, asS3... tc2 <- apropos("^is\\.") tc3 <- apropos("^plot\\.") tc4 <- apropos("^print\\.") tc5 <- apropos("^summary\\.") ############################################################## concatv <- function(ldtc) { ############################################################## lngMax <- max(sapply(X=ldtc,FUN=length)) lngList <- length(ldtc) mres <- matrix("",nrow=lngMax,ncol=lngList) for (indElt in seq(ldtc)) { elt <- ldtc[[indElt]] lngElt <- length(elt) mres[ (1:lngElt), indElt ] <- elt # c'est plus lisible que ## mres[1:length(ldtc[[indElt]]),indElt] <- ldtc[[indElt]] } # fin pour indElt print(mres,quote=FALSE) } # fin de fonction concatv ############################################################## # démonstration : concatv( list(tc1,tc2,tc3,tc4,tc5) )[,1] [,2] [,3] [,4] [,5] [1,] as.array is.array plot.default print.anova summary.aov [2,] as.array.default is.atomic plot.density print.AsIs summary.aovlist [3,] as.call is.call plot.design print.by summary.connection [4,] as.character is.character plot.ecdf print.condition summary.data.frame [5,] as.character.condition is.complex plot.function print.connection Summary.data.frame [6,] as.character.Date is.data.frame plot.lm print.data.frame summary.Date [7,] as.character.default is.double plot.mlm print.Date Summary.Date [8,] as.character.error is.element plot.new print.default summary.default [9,] as.character.factor is.empty.model plot.spec print.density Summary.difftime [10,] as.character.hexmode is.environment plot.spec.coherency print.difftime summary.factor [11,] as.character.numeric_version is.expression plot.spec.phase print.DLLInfo Summary.factor [12,] as.character.octmode is.factor plot.stepfun print.DLLInfoList summary.glm [13,] as.character.POSIXt is.finite plot.ts print.DLLRegisteredRoutines summary.infl [14,] as.character.srcref is.function plot.TukeyHSD print.factor summary.lm [15,] as.complex is.infinite plot.window print.family summary.manova [16,] as.data.frame is.integer plot.xy print.formula summary.matrix [17,] as.data.frame.array is.language print.ftable summary.mlm [18,] as.data.frame.AsIs is.leaf print.function Summary.numeric_version [19,] as.data.frame.character is.list print.glm Summary.ordered [20,] as.data.frame.complex is.loaded print.hexmode summary.POSIXct [21,] as.data.frame.data.frame is.logical print.infl Summary.POSIXct [22,] as.data.frame.Date is.matrix print.integrate summary.POSIXlt [23,] as.data.frame.default is.mts print.libraryIQR Summary.POSIXlt [24,] as.data.frame.difftime is.na print.listof summary.proc_time [25,] as.data.frame.factor is.na<- print.lm summary.srcfile [26,] as.data.frame.integer is.na.data.frame print.logLik summary.srcref [27,] as.data.frame.list is.na<-.default print.NativeRoutineList summary.stepfun [28,] as.data.frame.logical is.na<-.factor print.noquote summary.table [29,] as.data.frame.matrix is.name print.numeric_version [30,] as.data.frame.model.matrix is.nan print.octmode [31,] as.data.frame.numeric is.na.numeric_version print.packageInfo [32,] as.data.frame.numeric_version is.na.POSIXlt print.POSIXct [33,] as.data.frame.ordered is.null print.POSIXlt [34,] as.data.frame.POSIXct is.numeric print.proc_time [35,] as.data.frame.POSIXlt is.numeric.Date print.restart [36,] as.data.frame.raw is.numeric.difftime print.rle [37,] as.data.frame.table is.numeric.POSIXt print.simple.list [38,] as.data.frame.ts is.numeric_version print.srcfile [39,] as.data.frame.vector is.object print.srcref [40,] as.Date is.ordered print.summaryDefault [41,] as.Date.character is.package_version print.summary.table [42,] as.Date.date is.pairlist print.table [43,] as.Date.dates is.primitive print.terms [44,] as.Date.default is.qr print.ts [45,] as.Date.factor is.R print.warnings [46,] as.Date.numeric is.raster [47,] as.Date.POSIXct is.raw [48,] as.Date.POSIXlt is.recursive [49,] as.dendrogram is.relistable [50,] as.difftime is.single [51,] as.dist is.stepfun [52,] as.double is.symbol [53,] as.double.difftime is.table [54,] as.double.POSIXlt is.ts [55,] as.environment is.tskernel [56,] as.expression is.unsorted [57,] as.expression.default is.vector [58,] as.factor [59,] as.formula [60,] as.function [61,] as.function.default [62,] as.graphicsAnnot [63,] as.hclust [64,] as.hexmode [65,] as.integer [66,] as.list [67,] as.list.data.frame [68,] as.list.Date [69,] as.list.default [70,] as.list.environment [71,] as.list.factor [72,] as.list.function [73,] as.list.numeric_version [74,] as.list.POSIXct [75,] as.logical [76,] as.logical.factor [77,] as.matrix [78,] as.matrix.data.frame [79,] as.matrix.default [80,] as.matrix.noquote [81,] as.matrix.POSIXlt [82,] as.name [83,] as.null [84,] as.null.default [85,] as.numeric [86,] as.numeric_version [87,] as.octmode [88,] as.ordered [89,] as.package_version [90,] as.pairlist [91,] as.person [92,] as.personList [93,] as.POSIXct [94,] as.POSIXct.date [95,] as.POSIXct.Date [96,] as.POSIXct.dates [97,] as.POSIXct.default [98,] as.POSIXct.numeric [99,] as.POSIXct.POSIXlt [100,] as.POSIXlt [101,] as.POSIXlt.character [102,] as.POSIXlt.date [103,] as.POSIXlt.Date [104,] as.POSIXlt.dates [105,] as.POSIXlt.default [106,] as.POSIXlt.factor [107,] as.POSIXlt.numeric [108,] as.POSIXlt.POSIXct [109,] as.qr [110,] as.raster [111,] as.raw [112,] as.relistable [113,] as.roman [114,] as.sigcode [115,] as.single [116,] as.single.default [117,] as.stepfun [118,] as.symbol [119,] as.table [120,] as.table.default [121,] as.ts [122,] as.vector [123,] as.vector.factorc(1,2,"3",4) renvoie "1" "2" "3" "4" car R utilise un typage non explicite et "normalise" en quelque sorte le typage d'un vecteur qui doit forcément être homogène. Ce ne serait pas le cas avec list(1,2,"3",4). En conséquence, si on doit avoir un élément de type caractère dans une matrice, il faut convertir la matrice en data frame pour éviter d'obtenir une matrice de caractères :
# les parenthèses forcent l'affichage # l'affectation multiple simple est OK # mais attention : (a <- 1 + b <- 2) est interdit # on pourrait écrire (a <- 1 + (b <- 2)) mais cela devient vite fastidieux > (m1 <- as.data.frame(m2 <- matrix(1:12,nrow=4))) # c'est m1 qui est affichée V1 V2 V3 1 1 5 9 2 2 6 10 3 3 7 11 4 4 8 12 > m2 # remarquer le nom des lignes et des colonnes [,1] [,2] [,3] [1,] 1 5 9 [2,] 2 6 10 [3,] 3 7 11 [4,] 4 8 12 > m1[3,2] <- "?" > m2[3,2] <- "?" > print(m1) V1 V2 V3 1 1 5 9 2 2 6 10 3 3 ? 11 4 4 8 12 > print(m2) [,1] [,2] [,3] [1,] "1" "5" "9" [2,] "2" "6" "10" [3,] "3" "?" "11" [4,] "4" "8" "12" > print(m2,quite=FALSE) # pas d'erreur détectée pour quite ! [,1] [,2] [,3] [1,] "1" "5" "9" [2,] "2" "6" "10" [3,] "3" "?" "11" [4,] "4" "8" "12" > print(m2,quote=FALSE) [,1] [,2] [,3] [1,] 1 5 9 [2,] 2 6 10 [3,] 3 ? 11 [4,] 4 8 12Une lecture attentive des fonctions summary.* précédentes montre des noms de fonctions presque identiques, comme summary.Date() et Summary.Date(). En fait, certaines fonctions sont génériques (modèles objets de fonctions), d'autres sont les "vraies" fonctions à utiliser :
> da <- date() > print( summary.Date(da) ) Length Class Mode 1 character character > # non exécuté : print( Summary.Date(da) ) > ## Erreur dans Summary.Date(da) : objet '.Generic' introuvable ############################################################## > # code-source des fonctions ############################################################## > print( summary.Date ) # ------------------------------------------------------------ function (object, digits = 12L, ...) { x <- summary.default(unclass(object), digits = digits, ...) if (m <- match("NA's", names(x), 0)) { NAs <- as.integer(x[m]) x <- x[-m] attr(x, "NAs") <- NAs } class(x) <- c("summaryDefault", "table", oldClass(object)) x } <bytecode: 0x3cdff90> <environment: namespace:base> ############################################################## > print( Summary.Date ) # ------------------------------------------------------------ function (..., na.rm) { ok <- switch(.Generic, max = , min = , range = TRUE, FALSE) if (!ok) stop(gettextf("%s not defined for \"Date\" objects", .Generic), domain = NA) val <- NextMethod(.Generic) class(val) <- oldClass(list(...)[[1L]]) val } <bytecode: 0x3602378> <environment: namespace:base>Comme on peut le voir sur l'affichage de la solution pour l'exercice 4, (1:2) est de classe integer, de type integer et de mode numeric alors que c(1,2) est de classe numeric, de type double et de mode numeric. La simple indexation par crochet pour une liste renvoie toujours une liste alors que la double indexation par crochets pour une liste peut renvoyer un vecteur. Avec un simple crochet, on peut accéder à plusieurs éléments, comme par exemple demo[c("a","b")] alors que demo[[c("a","b")]] renvoie une erreur indiquant indice hors limites si demo est la variable définie ci-dessous :
demo <- list(a=1:5,b=list(3.1,12),c="un exemple") cat("\nAvec un crochet\n") demo[1] demo[2] demo[3] class(demo[1]) class(demo[2]) cat("\nAvec deux crochets\n") demo[[1]] demo[[2]] demo[[3]] class(demo[[1]]) class(demo[[2]]) cat("\nComposants nommés\n") demo["a"] demo["b"] demo["c"] class(demo["a"]) class(demo["b"]) # not run: demo[["a"]] # not run: demo[["b"]] # not run: demo[["c"]] cat("\nNature selon les crochets\n") cat(" demo[1] est une liste ? : ",is.list(demo[ 1 ]),"\n") # TRUE cat(" demo[[1]] est une liste ? : ",is.list(demo[[1]]),"\n") # FALSE identical(demo[1],demo[[1]]) # FALSE identical(demo[1],demo$a) # FALSE identical(demo[[1]],demo$a) # TRUE identical(demo["a"],demo$a) # FALSE identical(demo[1][[1]],demo$a) # TRUEAvec un crochet $a [1] 1 2 3 4 5 $b $b[[1]] [1] 3.1 $b[[2]] [1] 12 $c [1] "un exemple" [1] "list" [1] "list" Avec deux crochets [1] 1 2 3 4 5 [[1]] [1] 3.1 [[2]] [1] 12 [1] "un exemple" [1] "integer" [1] "list" Composants nommés $a [1] 1 2 3 4 5 $b $b[[1]] [1] 3.1 $b[[2]] [1] 12 $c [1] "un exemple" [1] "list" [1] "list" Nature selon les crochets demo[1] est une liste ? : TRUE demo[[1]] est une liste ? : FALSE [1] FALSE [1] FALSE [1] TRUE [1] FALSE [1] TRUEIl y a un air de famille mais aussi des différences entre les fonctions qui contiennent le mot apply.
apply() s'applique aux marges (lignes ou colonnes, spécifiées respectviement par MARGIN=1 et MARGIN=2) d'un vecteur ou d'une matrice. lapply(), comme son nom l'indique, s'applique à une liste et renvoie une liste. sapply() simplifie les résultats de lapply et renvoie un vecteur.
On peut noter qu'il existe aussi rapply() et eapply() dont l'aide et les exemples doivent être suffisants pour comprendre que rapply applique récursivement une fonction à une liste et que eapply applique une fonction à tous les éléments d'un environnement.
Enfin, R dispose aussi de tapply() et vapply() qui permettent respectivement d'appliquer une fonction à un «tableau irrégulier» (ragged array) et d'utiliser sapply avec une sortie prédéfinie. Voir example(tapply) et example(vapply). Nous les utiliserons un peu plus tard.
Si on exécute la commande apropos("apply"), R indique aussi qu'il existe dendrapply() et kernapply().
# une matrice de données numériques nbl <- 4 # lignes nbc <- 3 # colonnes mdata <- matrix(data=1:12,nrow=nbl) row.names(mdata) <- paste("Lig",1:nbl,sep="") colnames(mdata) <- paste("Col",1:nbc,sep="_") print(mdata) # moyenne en ligne avec apply cat(" apply : moyenne en ligne \n") print(apply(X=mdata,MARGIN=1,FUN=mean)) # apply(mdata,1,mean) est moins lisible, sans doute # il existe une fonction pour cela : rowMeans # moyenne en colonne avec apply cat(" apply : moyenne en colonne \n") print(apply(X=mdata,MARGIN=2,FUN=mean)) # il existe une fonction pour cela : colMeans # une liste avec des données numériques et une valeur NA str(ldata <- list(a=5,b=c(3,8,2),c=8,d=5,e=c(6,7,NA))) # str est mieux que ls.str dans certains cas # lapply de base : moyenne de chaque composante de la liste cat("lapply de mean\n") print(lapply(X=ldata,FUN=mean)) # lapply avec fonction anonyme pour gérer les NA cat("lapply de mean avec na.rm=TRUE (solution 1)\n") print(lapply(X=ldata,FUN=function(v) mean(v,na.rm=TRUE) )) # mieux : lapply avec paramètre supplémentaire pour la fonction cat("lapply de mean avec na.rm=TRUE (solution 2)\n") print(lapply(X=ldata,FUN=mean,na.rm=TRUE)) # objet de classe "list" # sapply cat(" le s de sapply signifie : simplification\n") print(sapply(X=ldata,FUN=mean,na.rm=TRUE)) # objet de classe "numeric" # mapply cat(" le m de mapply signifie : multivarié\n") print(mapply(rep,1:4,8:11)) serie1 <- 1:3 serie2 <- serie1*10 serie3 <- serie1**2 print(rbind(serie1,serie2,serie3)) cat(" mapply avec 2 arguments\n") print(mapply(FUN=function(x,y) x+y ,serie1,serie2)) cat(" mapply avec 3 arguments\n") print(mapply(FUN=function(x,y,z) x+y+z,serie1,serie2,serie3)) # # non présentés pour l'instant : tapply, vapply, rapply, eapply #Col_1 Col_2 Col_3 Lig1 1 5 9 Lig2 2 6 10 Lig3 3 7 11 Lig4 4 8 12 apply : moyenne en ligne Lig1 Lig2 Lig3 Lig4 5 6 7 8 apply : moyenne en colonne Col_1 Col_2 Col_3 2.5 6.5 10.5 List of 5 $ a: num 5 $ b: num [1:3] 3 8 2 $ c: num 8 $ d: num 5 $ e: num [1:3] 6 7 NA lapply de mean $a [1] 5 $b [1] 4.333333 $c [1] 8 $d [1] 5 $e [1] NA lapply de mean avec na.rm=TRUE (solution 1) $a [1] 5 $b [1] 4.333333 $c [1] 8 $d [1] 5 $e [1] 6.5 lapply de mean avec na.rm=TRUE (solution 2) $a [1] 5 $b [1] 4.333333 $c [1] 8 $d [1] 5 $e [1] 6.5 le s de sapply signifie : simplification a b c d e 5.000000 4.333333 8.000000 5.000000 6.500000 le m de mapply signifie : multivarié [[1]] [1] 1 1 1 1 1 1 1 1 [[2]] [1] 2 2 2 2 2 2 2 2 2 [[3]] [1] 3 3 3 3 3 3 3 3 3 3 [[4]] [1] 4 4 4 4 4 4 4 4 4 4 4 [,1] [,2] [,3] serie1 1 2 3 serie2 10 20 30 serie3 1 4 9 mapply avec 2 arguments [1] 11 22 33 mapply avec 3 arguments [1] 12 26 42Les instructions proposées à la question 5 viennent créer la liste des mots d'une phrase et comptent le nombre de lettres de chaque mot. La fonction nchar() est vectorielle, mais avec sapply, les calculs sont plus explicites. Les deux dernières instructions sont équivalentes.
> # les mots > > ldm <- unlist( strsplit(x="le chat mange la souris",split=" ")) > # version courte > > nchar(ldm) [1] 2 4 5 2 6 > # un peu mieux avec sapply ? > > sapply(FUN=function(x) { nchar(x) },X=ldm) le chat mange la souris 2 4 5 2 6 > # on n'est pas obligé de tout détailler > > sapply(FUN=nchar,X=ldm) le chat mange la souris 2 4 5 2 6tapply() est un peu la composition de lapply() et split(), mais en mieux. En voici un exemple : on dispose de 30 personnes, 20 hommes et 10 femmes (variable SEXE) et on veut connaitre la moyenne de l'age par sexe, par niveau d'études et par croisement (sexe,étude). Les données sont dans le fichier personnes.dar.
NUM SEXE AGE ETUD M001 Femme 62 Secondaire M002 Homme 60 Niveau_bac M003 Femme 31 Secondaire M004 Femme 27 Secondaire M005 Homme 22 Supérieur M006 Femme 07 Niveau_bac M007 Femme 19 Supérieur M008 Femme 53 Niveau_bac M009 Homme 62 Secondaire M010 Femme 63 Secondaire M011 Homme 65 Secondaire M012 Femme 11 Niveau_bac M013 Homme 78 Secondaire M014 Homme 20 Supérieur M015 Femme 48 Niveau_bac M016 Femme 5 Secondaire M017 Homme 39 Niveau_bac M018 Homme 28 Niveau_bac M019 Homme 14 Niveau_bac M020 Homme 11 Secondaire M021 Homme 17 Supérieur M022 Homme 25 Niveau_bac M023 Homme 39 Niveau_bac M024 Homme 8 Secondaire M025 Homme 31 Secondaire M026 Homme 22 Secondaire M027 Homme 47 Secondaire M028 Homme 68 Secondaire M029 Homme 38 Secondaire M030 Homme 47 Secondaire# lecture des données pers <- lit.dar("personnes.dar") # tris à plat élémentaires print(table(pers$SEXE)) print(table(pers$ETUD)) # moyenne par sexe "à la main" attach(pers) cats("Moyenne des ages pour les hommes et les femmes","-") print(mean(pers[SEXE=="Femme","AGE"])) print(mean(pers[SEXE=="Homme","AGE"])) detach(pers) # split permet de découper et renvoie une liste cats("Utilisation de split") decoupe <- split(x=pers$AGE,f=pers$SEXE) print(decoupe) # on peut alors utiliser lapply : cats("Utilisation de lapply") moyAges <- lapply(X=decoupe,FUN=mean) print(moyAges) # le tout en une seule fois avec les deux facteurs ensemble : cats("Tableau résumé") moys <- matrix( lapply( X=split(x=pers$AGE,f=list(pers$SEXE,pers$ETUD)), FUN=mean ), ncol=length(levels(pers$ETUD))) print(moys) # attention : on ne peut pas exécuter # round(moys,2) # avec tapply, le tout en plus simple : cats("Utilisation de tapply") moys <- tapply( X=pers$AGE,INDEX=list(pers$SEXE,pers$ETUD), FUN=mean ) print(moys) cats("Tableau des moyennes arrondies en années :") print(round(moys,2))Femme Homme 10 20 Niveau_bac Secondaire Supérieur 10 16 4 Moyenne des ages pour les hommes et les femmes ---------------------------------------------- [1] 32.6 [1] 37.05 Utilisation de split ==================== $Femme [1] 62 31 27 7 19 53 63 11 48 5 $Homme [1] 60 22 62 65 78 20 39 28 14 11 17 25 39 8 31 22 47 68 38 47 Utilisation de lapply ===================== $Femme [1] 32.6 $Homme [1] 37.05 Tableau résumé ============== [,1] [,2] [,3] [1,] 29.75 37.6 19 [2,] 34.16667 43.36364 19.66667 Utilisation de tapply ===================== Niveau_bac Secondaire Supérieur Femme 29.75000 37.60000 19.00000 Homme 34.16667 43.36364 19.66667 Tableau des moyennes arrondies en années : ========================================== Niveau_bac Secondaire Supérieur Femme 29.75 37.60 19.00 Homme 34.17 43.36 19.67La fonction ls() permet d'avoir une liste (caractère) des objets correspondant à pattern. Avec ^mdata on obtient les objets dont le nom commence par mdata. Pour utiliser dim() sur chacun des objets, il faut passer de caractère à expression à l'aide de eval() et parse() :
# les données aleaEntier <- function(x) { return(round(runif(n=1,min=5,max=10))) } # pour générer toujours les mêmes données, décommenter la ligne suivante # set.seed(1234) mdata1 <- matrix(nrow=aleaEntier(), ncol=aleaEntier() ) mdata2 <- matrix(nrow=aleaEntier(), ncol=aleaEntier() ) mdata3 <- matrix(nrow=aleaEntier(), ncol=aleaEntier() ) mdata4 <- matrix(nrow=aleaEntier(), ncol=aleaEntier() ) dim(mdata1) dim(mdata2) dim(mdata3) dim(mdata4) autremdata <- mdata1 # ne commence pas par mdata # dim("mdata1") est incorrect, il faut exécuter # dim(eval(parse(text="mdata1"))) donc pour la liste : t(sapply(FUN=function(x) { dim(eval(parse(text=x))) },X=ls(pattern="^mdata")))[1] 7 8 [1] 6 6 [1] 10 5 [1] 9 5 [,1] [,2] mdata1 7 8 mdata2 6 6 mdata3 10 5 mdata4 9 5La partie 1 et la partie 2 du code proposé viennent permuter les contenus de a et de b. Ainsi au début de la partie 1, a et b valent respectivement 12 et 3 ; à la fin de la partie 1, a et b valent respectivement 3 et 12. A la fin de la partie 2, a et b valent à nouveau respectivement 12 et 3.
p, m et g signifient respectivement petit, milieu et grand. A partir de a, b et c, la partie 3 met le plus petit des trois nombres dans p, les plus grand des trois dans g et celui du milieu dans m. On aurait aussi pu utiliser
d <- sort( c(a,b,c) ) p <- d[1] m <- d[2] g <- d[3]Les 4 premières instructions de la partie 4 (qui se terminent par } # fin de si 1) mettent 0 dans pg quoi qu'il arrive et ensuite mettent dans pg a suivi de b si a est inférieur à b. Il est prudent d'initialiser toute variable. Sans l'instruction pg <- 0 la variable pg n'existerait pas pour a supérieur ou égal à b. La dernière instruction de la partie 4, qui correspond à 7 lignes de texte, met dans p et g respectivement le plus petit et le plus grand des deux nombres a et b.
Dans la partie 5, il n'y a pas d'accolade pour délimiter les parties alors et sinon. C'est une mauvaise pratique : il faut toujours mettre des accolades. La deuxième instruction qui suit # attention 2 est toujours exécutée, ce qui n'est sans doute pas ce que voulait réaliser le programmeur. Par contre le code qui suit # attention 3 est refusé par R qui y voit un else inattendu : else n'est pas une instruction, mais seulement une partie d'une instruction if. Ne pas avoir mis de parenthèse oblige R à déduire qu'il n'y a pas de else.
Il s'agit des fonctions which.max, ifelse, apply, colMeans, sweep et transform définies dans le package base. La conclusion qui s'impose est qu'il est conseillé de bien connaitre les fonctions des packages fondamentaux que sont base, stats et graphics avant de programmer «à tout-và». Voici des exemples d'utilisation de ces fonctions.
# première position du maximum dans un vecteur vect <- c(1,8,7,2,5,8,1) cat("Dans ",vect," le maximum (",max(vect),") est en position ",which.max(vect),"\n\n",sep=" ") # pour avoir toutes les positions : which( vect==max(vect) ) # dichotomisation d'un vecteur numérique via un seuil fixé vectBin <- ifelse( vect<3, "précoce", "tardif") cat("Voici le comptage des \"jeunes\" et des \"vieux\" (seuil=3) \n") print( table( vectBin ) ) # création d'une matrice numérique nblig <- 3 nbcol <- 4 mat <- matrix(data=1:12,nrow=nblig) colnames(mat) <- paste("Col",1:nbcol,sep="") row.names(mat) <- paste("Lig",1:nblig,sep="") cat("\nMatrice de départ :\n") print(mat) # ajout des min, max, moy et ect des colonnes newMat <- rbind( apply(X=mat,MARGIN=2,FUN=min), colMeans(mat), # donc équivalent à apply(X=mat,FUN=mean,MARGIN=2) apply(mat,2,max), # plus court, mais plus dangereux apply(X=mat,MARGIN=2,FUN=sd) , # surtout pas sd(mat), c'est l'écart-type général mat ) # fin de rbind row.names(newMat)[1:4] <- c("Min","Moy","Max","Ect") cat("\nMatrice après ajout des calculs en colonne\n") print(newMat) # division des lignes d'une matrice par leur somme [en ligne] cat("\nMatrice après division en ligne\n") print( sweep(x=mat,MARGIN=1,STATS=rowSums(mat),FUN="/") ) # noter x= pas X= # transformation : conversion de pouces en cm dans un data frame df <- as.data.frame(mat[,1:2]) names(df) <- c("Poids (kg)","Taille (pouces)") row.names(df) <- paste("Ind",sprintf("%03d",1:nrow(df)),sep="_") cat("\nDonnées initiales en \"data frame\":\n") print(df) cat("\nDonnées après conversion des pouces en cm\n") print(transform(df,"Taille (cms)"=df[,2]*2.54)) # colnames(df)[2] <- "height" # transform(df,taille=height*2.54) avec des colonnes nomméesDans 1 8 7 2 5 8 1 le maximum ( 8 ) est en position 2 Voici le comptage des "jeunes" et des "vieux" (seuil=3) vectBin précoce tardif 3 4 Matrice de départ : Col1 Col2 Col3 Col4 Lig1 1 4 7 10 Lig2 2 5 8 11 Lig3 3 6 9 12 Matrice après ajout des calculs en colonne Col1 Col2 Col3 Col4 Min 1 4 7 10 Moy 2 5 8 11 Max 3 6 9 12 Ect 1 1 1 1 Lig1 1 4 7 10 Lig2 2 5 8 11 Lig3 3 6 9 12 Matrice après division en ligne Col1 Col2 Col3 Col4 Lig1 0.04545455 0.1818182 0.3181818 0.4545455 Lig2 0.07692308 0.1923077 0.3076923 0.4230769 Lig3 0.10000000 0.2000000 0.3000000 0.4000000 Données initiales en "data frame": Poids (kg) Taille (pouces) Ind_001 1 4 Ind_002 2 5 Ind_003 3 6 Données après conversion des pouces en cm Poids..kg.. Taille..pouces. Taille..cms. Ind_001 1 4 10.16 Ind_002 2 5 12.70 Ind_003 3 6 15.24Il suffit d'utiliser la fonction cbind() qui crée une matrice ; son affichage provoque une numérotation automatique par défaut :
> data(mtcars) > head(mtcars) mpg cyl disp hp drat wt qsec vs am gear carb Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2 Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1 > cbind(names(mtcars)) [,1] [1,] "mpg" [2,] "cyl" [3,] "disp" [4,] "hp" [5,] "drat" [6,] "wt" [7,] "qsec" [8,] "vs" [9,] "am" [10,] "gear" [11,] "carb"Pour définir les paramètres d'une fonction, on doit impérativement les nommer. Par contre, il est facultatif de leur donner une valeur par défaut avec =. Mettre l'ellipse dans la définition des paramètres signifie que la fonction peut acepter (et transmettre) d'autres paramètres que ceux explicitement fournis. Un exemple d'utilisation de l'ellipse sera fourni à l'exercice 13. On peut tester si un paramètre est présent (ou plutôt absent) avec missing(paramètre). La liste des paramètres est fournie par formals(fonction) mais on peut aussi utiliser args(fonction) pour connaitre l'entête de la fonction ; le texte de la fonction est donné par body(fonction). Une fonction est une variable : on peut l'affecter, la copier, la supprimer...
# ce fichier est nommé fonction.r ; on l'exécute par : # # source(file="fonction.r",encoding="latin1",echo=TRUE) # # 1. définition d'une fonction carre <- function(x) { # calcule le carré de son argument y <- x*x return(y) } # fin de fonction carre # 2. une fonction est une variable print( carre ) copieDeCarre <- carre # 3. entête (signature) de la fonction print( formals(carre) ) print( args(carre) ) # 4. corps (texte) de la fonction print( body(carre) ) # un exemple plus conséquent print( formals(lm) ) print( args(lm) )> # ce fichier est nommé fonction.r ; on l'exécute par : > # > # source(file="fonction.r",encoding="latin1",echo=TRUE) > # > > # 1. définition d'une fonction > carre <- function(x) { + + # calcule le carré de son argument + + y <- x*x + + return(y) + + } # fin de fonction carre > # 2. une fonction est une variable > > print( carre ) function(x) { # calcule le carré de son argument y <- x*x return(y) } > copieDeCarre <- carre > # 3. entête (signature) de la fonction > > print( formals(carre) ) $x > print( args(carre) ) function (x) NULL > # 4. corps (texte) de la fonction > > print( body(carre) ) { y <- x * x return(y) } > # un exemple plus conséquent > > print( formals(lm) ) $formula $data $subset $weights $na.action $method [1] "qr" $model [1] TRUE $x [1] FALSE $y [1] FALSE $qr [1] TRUE $singular.ok [1] TRUE $contrasts NULL $offset $... > print( args(lm) ) function (formula, data, subset, weights, na.action, method = "qr", model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE, contrasts = NULL, offset, ...) NULLPour utiliser les paramètres d'une fonction, on peut donner leur valeur par position ou avec leur nom. R permet d'utiliser des noms abbrégés à condition qu'il n'y ait pas ambiguité :
# 1. définition de f f <- function(xLong=-1,xEncorePlusLong) { if (missing(xEncorePlusLong)) { cat("\nCalcul impossible : il faut donner deux paramètres, \n") cat(" à savoir xLong et xEncorePlusLong.\n") } else { cat("la somme de ",xLong," et de ", xEncorePlusLong," est ",xLong+xEncorePlusLong,"\n") } # fin si } # fin de fonction f # 2. utilisations de f formals(f) # liste des paramètres args(f) # entête f() # sans paramètre f(xLong=2,xEncorePlusLong=3) # utilisation stricte des paramètres (nommés) f(xEncorePlusLong=3,xLong=2) # utilisation des paramètres nommés (sans ordre) f(2,3) # utilisation par position f(xE=3) # utilisation d'abbréviation de la valeur par défaut f(2,xE=3) # utilisation d'abbréviation non ambigue f(2,x=3) # utilisation ambigue non autorisée> # 1. définition de f > f <- function(xLong=-1,xEncorePlusLong) { if (missing(xEncorePlusLong)) { cat("\nCalcul impossible : il faut donner deux paramètres, \n") cat(" à savoir xLong et xEncorePlusLong.\n") } else { cat("la somme de ",xLong," et de ", xEncorePlusLong," est ",xLong+xEncorePlusLong,"\n") } # fin si } # fin de fonction f > # 2. utilisations de f > formals(f) # liste des paramètres $xLong -1 $xEncorePlusLong > args(f) # entête function (xLong = -1, xEncorePlusLong) NULL > f() # sans paramètre Calcul impossible : il faut donner deux paramètres, à savoir xLong et xEncorePlusLong. > f(xLong=2,xEncorePlusLong=3) # utilisation stricte des paramètres (nommés) la somme de 2 et de 3 est 5 > f(xEncorePlusLong=3,xLong=2) # utilisation des paramètres nommés (sans ordre) la somme de 2 et de 3 est 5 > f(2,3) # utilisation par position la somme de 2 et de 3 est 5 > f(xE=3) # utilisation d'abbréviation de la valeur par défaut la somme de -1 et de 3 est 2 > f(2,xE=3) # utilisation d'abbréviation non ambigue la somme de 2 et de 3 est 5 > f(2,x=3) # utilisation ambigue non autorisée Erreur dans f(2, x = 3) : l'argument 2 correspond à plusieurs arguments formelsAu niveau du renvoi des valeurs calculées, R n'autorise qu'une seule expression. Pour renvoyer plusieurs valeurs, il faut choisir entre vecteur (éventuellement avec noms), matrice, liste ou "objet" plus sophistiqué. Une liste avec ses composantes nommées est souvent un bon choix, qui évite les erreurs d'indice :
# une fonction qui renvoie min et max dans un vecteur minEtmax <- function(x) { return( c(min(x),max(x)) ) } # fin de fonction minEtmax # utilisation : il faut se rappeler de l'ordre de renvoi vect <- c(1,8,15,2,50,7) mm <- minEtmax(vect) cat(" min et max de ",paste(vect,collapse=" "),":",paste(mm,collapse=" "),"\n") vmin1 <- mm[1] # il faut se rappeler de l'indice 1 # une fonction qui renvoie min et max dans un vecteur nommé minEtmaxN <- function(x) { vn <- c(min(x),max(x)) names(vn) <- c("min","max") return( vn ) } # fin de fonction minEtmaxN mmN <- minEtmaxN(vect) vmin2 <- mmN["min"] # pas besoin de se rappeler de l'indice # une fonction qui renvoie min et max dans une liste minEtmaxL <- function(x) { lst <- list(min=min(x),max=max(x)) return( lst ) } # fin de fonction minEtmaxL mmL <- minEtmaxL(vect) vmin3 <- mmL$min> # une fonction qui renvoie min et max dans un vecteur > minEtmax <- function(x) { return( c(min(x),max(x)) ) } # fin de fonction minEtmax > # utilisation : il faut se rappeler de l'ordre de renvoi > > vect <- c(1,8,15,2,50,7) > mm <- minEtmax(vect) > cat(" min et max de ",paste(vect,collapse=" "),":",paste(mm,collapse=" "),"\n") min et max de 1 8 15 2 50 7 : 1 50 > vmin1 <- mm[1] # il faut se rappeler de l'indice 1 > # une fonction qui renvoie min et max dans un vecteur nommé > > minEtmaxN <- function(x) { vn <- c(min(x),max(x)) names(vn) <- c("min","max") return( vn ) } # fin de fonction minEtmaxN > mmN <- minEtmaxN(vect) > vmin2 <- mmN["min"] # pas besoin de se rappeler de l'indice > # une fonction qui renvoie min et max dans une liste > > minEtmaxL <- function(x) { lst <- list(min=min(x),max=max(x)) return( lst ) } # fin de fonction minEtmaxL > mmL <- minEtmaxL(vect) > vmin3 <- mmL$minA l'usage, une liste avec noms se révèle être l'objet le plus simple et le plus souple à utiliser pour le renvoi des valeurs : on peut renvoyer des valeurs de nature différente, les objets renvoyés sont nommés...
Quand on écrit des fonctions, il est tentant de les utiliser juste pour voir ce qu'elles font. Lorsqu'elles renvoient des valeurs, leur return(...) affiche en interactif souvent trop de valeurs. Utiliser return(invisible(...) permet de masquer l'affichage du return() s'il n'y a pas affectation.
# cette fonction renvoie beaucoup de valeurs # en interactif, vu() les affiche... vu <- function() { cat(" simulation (1)...\n") ; return( runif(10) ) } # fin de fonction vu # cette fonction renvoie beaucoup de valeurs # en interactif, pasvu() n'affiche rien pasvu <- function() { cat(" simulation (2)...\n") ; return( invisible(runif(10)) ) } # fin de fonction pasvu # démonstration : vu() pasvu() # les deux fonctions ont le même comportement # en cas d'affectation x1 <- vu() x2 <- pasvu()> # cette fonction renvoie beaucoup de valeurs > # en interactif, vu() les affiche... > > vu <- function() { + cat(" simulation (1)...\n") ; + re .... [TRUNCATED] > # cette fonction renvoie beaucoup de valeurs > # en interactif, pasvu() n'affiche rien > > pasvu <- function() { + cat(" simulation (2)...\n") ; .... [TRUNCATED] > # démonstration : > > vu() simulation (1)... [1] 0.64978773 0.02535503 0.48913900 0.71141735 0.65846263 0.26194920 0.28039553 0.82349795 0.71213578 0.21841828 > pasvu() simulation (2)... > # les deux fonctions ont le même comportement > # en cas d'affectation > > x1 <- vu() simulation (1)... > x2 <- pasvu() simulation (2)...Trois fonctions sont définies puis utilisées : f, g et h.
f est définie avec une syntaxe courte, sans valeur par défaut. Utiliser f() provoquera une erreur : Erreur dans x * x : 'x' est manquant. Aucun test sur le type du seul argument x n'est effectué. Donc f(TRUE) renverra 1 et pour f("oui") on aura : Erreur dans x * x : argument non numérique pour un opérateur binaire. Si on tape juste f, R renvoie le code source de la fonction. f(3) renvoie le carré de 3, donc 9. f(1:5) renvoie les carrés respectifs des entiers de 1 à 5. Enfin, f(list(a=2,b=2)) provoquerait l'erreur : Erreur dans x * x : argument non numérique pour un opérateur binaire.
g est définie avec NA comme valeur par défaut et teste si l'argument x est une liste. Dans ce cas, R applique la fonction carré à chacun des éléments de la liste à l'aide de lapply ; sinon, on renvoie le calcul de carre(x) -- s'il est valide. Si on tape juste g(), R passe dans le else et renvoie carre(NA) soit NA.
h est définie avec deux paramètres (x et graphique) et l'ellipse. Si x est une liste, R applique la fonction carré à chacun des éléments de la liste à l'aide de lapply ; sinon, on met dans y le résultat de carre(x) et si graphique est mis à vrai, on trace y à l'aide des paramètres éventuels passé dans l'ellipse. Ainsi h(x=1:5, graphique=TRUE,col="red",pch=20 ) trace carre(1:5) en rouge avec des points pleins (pch=20).
------------------------------------------ calculs avec f function(x) carre(x) [1] 9 [1] 1 4 9 16 25 ------------------------------------------ calculs avec g [1] NA [1] 9 $a [1] 4 $b [1] 4 ------------------------------------------ calculs avec h function(x=NA,graphique=FALSE,...) { if (is.list(x)) { return( lapply(x,carre) ) } else { y <- carre(x) if (graphique) { plot( y, ...) } # fin de si sur graphique return( y ) } # fin de si sur is.list } # fin de fonction h [1] NA [1] 9 [1] 1 4 9 16 25 $a [1] 4 $b [1] 4 [1] 1 4 9 16 25 [1] 1 4 9 16 25 [1] 1 4 9 16 25Voici les deux graphes produits :
Pour la fonction h, si x n'est pas une liste, on renvoie son carré et, si x est une liste, on renvoie aussi son carré via apply.
source() vient lire et exécuter le code source. Il y a de nombreuses options, voir par exemple notre introduction à R, à l'exercice 1 de la séance 2.
sink() permet de rediriger l'affichage vers un fichier.
sinksrc() réalise les deux opérations. On peut donc à la fois voir ce qui est exécuté et obtenir automatiquement un fichier "lst" de l'exécution comme en SAS. Le choix de .sor comme extension du fichier-texte d'exécution permet de laisser chaque utilisateur décider du logiciel qu'il veut associer à cette extension. Un choix astucieux peut être de prendre Firefox pour afficher...
Les actions élémentaires de controle de flux se nomment if/else (alternative si/sinon), switch (structure de cas), for (boucle pour), while (boucle tant que), repeat (boucle répéter jusqu'à). La gestion des erreurs ou "exceptions" se fait avec try et tryCatch.
Pour trouver le maximum d'un vecteur de valeurs, il suffit dans une boucle de comparer le maximum trouvé jusque là avec la valeur courante, d'où le code R
# détermination de maxV, plus grand élément du vecteur V nbe <- length(V) maxV <- V[1] # il est prudent d'initialiser avec le premier élément de V for (idc in (2:nbe)) { eltc <- V[idc] # élément courant if (eltc>maxV) { maxV <- eltc } # fin si } # fin pour idcPour calculer le nombre d'occurences de ce maximum dans le vecteur, il suffit dans une boucle d'incrémenter le nombre d'occurrences à chaque fois que l'on trouve le maximum, d'où le code R
# comptage du nombre d'occurence du plus grand élément du vecteur V # sachant que ce plus grand élément est dans maxV nbe <- length(V) # nombre d'éléments nbo <- 0 # nombre d'occurences for (idc in (1:nbe)) { if (V[idc]==maxV) { nbo <- nbo + 1 } # fin si } # fin pour idcLes codes R précédents utilisent deux boucles et sont donc deux fois plus lents qu'un seul parcours de boucle. Pour déterminer en une seule fois le maximum et son nombre d'occurence, il suffit de remarquer que pour chaque élément du vecteur il y a trois possibilités : pour une valeur inférieure au maximum, il n'y a rien à faire ; pour une valeur égale au maximum trouvé, il faut incrémenter le nombre d'occurences ; pour une valeur supérieure, il faut en faire le nouveau maximum et donc mettre le nombre d'ocurences à 1, d'où le code R
# détermination de maxV, plus grand élément du vecteur V # et comptage du nombre d'occurences (nbo) en une seule boucle nbe <- length(V) # nombre d'élements maxV <- V[1] # maximum nbo <- 1 # nombre d'occurences for (idc in (2:nbe)) { # eltc = V[idc] est l'élément courant eltc <- V[idc] if (eltc>maxV) { # nouveau maximum, donc une seule occurence maxV <- eltc nbo <- 1 } else { if (eltc==maxV) { # une nouvelle occurence du maximum nbo <- nbo + 1 } # fin si numéro 2 } # fin si numéro 1 } # fin pour idcLa bonne solution consiste bien sur à profiter des opérations vectorielles de R et donc de ne pas écrire de boucle explicite :
# détermination de maxV, plus grand élément du vecteur V # et comptage du nombre d'occurences sans utiliser de boucle maxV <- max(V) nbo <- sum( V==maxV ) # juste pour savoir : sum( V==max(V) )Après quelques essais en mode interactif, il est possible de se rendre compte que :
pour appliquer ls() à un package, il faut que ce package soit chargé,
la fonction ls() renvoie un vecteur de chaines de caractères,
il peut arriver que certains objets aient plusieurs classes, comme par exemple l'objet DNase du package datasets.
La syntaxe de ls() pour voir les objets du package XXX est ls("package:XXX"). Il faut donc construire avec paste() la chaine de caractères du paramètre avant de l'exécuter avec eval(). Une fonction que l'on définit a pour type closure et function comme classe. Pour les primitives R et les fonctions dans les packages, cela devient function ou, pour les méthodes S4, standardGeneric. Nous avons donc séparé l'affichage des fonctions et des objets d'une autre classe. Pour connaitre la classe de chaque objet, il faut exécuter la fonction class() sur chaque objet. Plutôt que d'évaluer l'appel de class, nous avons copié/créé l'objet en jdd à l'aide de la fonction get() avant de consulter la classe de jdd.
La fonction cats() ne fait pas partie de R standard. C'est une de nos fonctions usuelles qui sert à souligner un texte pour plus de lisibilité.
################################################################# ls2 <- function(package="base") { ################################################################# # affiche les tailles des objets disponibles dans le package via ls() cats(paste("Objets du package",package)) # il faut d'abord charger le package phr <- paste('ldp <- library("',package,'")',sep="") ## pour debug : cat(" voici le code exécuté : *** ",phr," *** \n") eval(parse(text=phr)) # si on ne trouve pas le nom du package dans ldp # c'est qu'on n'a pas pas réussi à le charger if (!package %in% ldp) { cat("Package ",package," non chargé.\n") return(invisible(NULL)) } # fin si phr <- paste('oip <- ls("package:',package,'")',sep="") ## pour debug : cat(" voici le code exécuté : *** ",phr," *** \n") eval(parse(text=phr)) cat(" il y a ",length(oip)," objets dans ce package\n\n") # préparation de la structure pour les résultats mres <- matrix(NA,nrow=length(oip),ncol=3) mres <- data.frame(mres) colnames(mres) <- c("name","functionClass","other") row.names(mres) <- paste("obj_",sprintf("%03d",1:nrow(mres)),sep="") # remplissage de la structure nd <- 0 for (idd in oip) { nd <- nd + 1 mres[nd,1] <- idd jdd <- get(idd) clj <- class(jdd) if (length(clj)==1) { if (class(jdd) %in% c("function","standardGeneric","closure")) { mres[nd,2] <- clj } else { mres[nd,3] <- clj } # fin si } else { mres[nd,3] <- paste(clj,collapse=" & ") } # fin si } # fin pour mres[is.na(mres)] <- "" print(mres,right=FALSE) } # fin de fonction ls2 ############################################################ ls2() # équivalent à ls2("base") ls2("stats") ls2("utils")Objets du package base ====================== il y a 1167 objets dans ce package name functionClass other obj_001 ^ function obj_002 ~ function obj_003 < function obj_004 <<- function obj_005 <= function obj_006 <- function obj_007 = function [...] Objets du package stats ======================= il y a 493 objets dans ce package name functionClass other obj_001 acf function obj_002 acf2AR function obj_003 add1 function obj_004 addmargins function obj_005 add.scope function obj_006 aggregate function obj_007 aggregate.data.frame function obj_008 aggregate.default function [...] Objets du package utils ======================= il y a 198 objets dans ce package name functionClass other obj_001 ? function obj_002 adist function obj_003 alarm function obj_004 apropos function obj_005 aregexec function obj_006 argsAnywhere function [...]Il y a une petite difficulté avec la fonction data() : elle fait une évaluation "paresseuse", au sens informatique de lazyLoad(). Il faut donc exécuter la fonction data() sur chaque objet avant de pouvoir consulter les dimensions de l'objet. Voici quelques essais pour comprendre comment utiliser l'objet renvoyé par data() :
# chargeons les données du package lesd <- data(package="datasets") # essayons de voir ce qu'on y trouve print( class(lesd) ) print( str(lesd) ) print( lapply(X=lesd,FUN=head) ) # ce qui nous intéresse est donc dans la rubrique results resd <- lesd$results print( class(resd) ) print( colnames(resd) ) # il faut exploiter la colonne Item, mais il y a parfois plusieurs mots lesi <- resd[,"Item"] print(cbind(head(lesi))) # quelques essais : data("AirPassengers",package="datasets") print(class(AirPassengers)) data("BOD",package="datasets") print(class(BOD))> # chargeons les données du package > > lesd <- data(package="datasets") > # essayons de voir ce qu'on y trouve > > print( class(lesd) ) [1] "packageIQR" > print( str(lesd) ) List of 4 $ title : chr "Data sets" $ header : NULL $ results: chr [1:206, 1:4] "datasets" "datasets" "datasets" "datasets" ... ..- attr(*, "dimnames")=List of 2 .. ..$ : NULL .. ..$ : chr [1:4] "Package" "LibPath" "Item" "Title" $ footer : NULL - attr(*, "class")= chr "packageIQR" NULL > print( lapply(X=lesd,FUN=head) ) $title [1] "Data sets" $header NULL $results Package LibPath Item Title [1,] "datasets" "/usr/lib/R/library" "AirPassengers" "Monthly Airline Passenger Numbers 1949-1960" [2,] "datasets" "/usr/lib/R/library" "BJsales" "Sales Data with Leading Indicator" [3,] "datasets" "/usr/lib/R/library" "BJsales.lead (BJsales)" "Sales Data with Leading Indicator" [4,] "datasets" "/usr/lib/R/library" "BOD" "Biochemical Oxygen Demand" [5,] "datasets" "/usr/lib/R/library" "CO2" "Carbon Dioxide Uptake in Grass Plants" [6,] "datasets" "/usr/lib/R/library" "ChickWeight" "Weight versus age of chicks on different diets" $footer NULL > # ce qui nous intéresse est donc dans la rubrique results > > resd <- lesd$results > print( class(resd) ) [1] "matrix" > print( colnames(resd) ) [1] "Package" "LibPath" "Item" "Title" > # il faut exploiter la colonne Item, mais il y a parfois plusieurs mots > > lesi <- resd[,"Item"] > print(cbind(head(lesi))) [,1] [1,] "AirPassengers" [2,] "BJsales" [3,] "BJsales.lead (BJsales)" [4,] "BOD" [5,] "CO2" [6,] "ChickWeight" > # quelques essais : > > data("AirPassengers",package="datasets") > print(class(AirPassengers)) [1] "ts" > data("BOD",package="datasets") > print(class(BOD)) [1] "data.frame"Pour construire notre fonction data2() il faut donc charger le package passé en paramètre. L'objet renvoyé est de classe packageIQR, ce qui ne semble pas très facile à utiliser, sauf qu'il s'agit d'un objet qui est une liste. La composante results contient les objets à interroger. Il s'agit d'une matrice dont la colonne "Item" contient le nom de l'objet ou des objets qui nous intéressent. Un test sur la classe pour savoir s'il y a une longueur ou des lignes et des colonnes et le tour est joué...
################################################################# data2 <- function(package="datasets") { ################################################################# # affiche les tailles des données disponibles via data() cats(paste("Jeux de données du package",package)) dfd <- data(package=package) ndd <- dfd$results[,"Item"] if (length(ndd)==0) { cat("\n aucun jeu de données vu dans ce package.\n\n") } else { cat("\n il y a",length(ndd),"jeu de données dans ce package.\n\n") # préparation de la matrice des résultats mres <- matrix(nrow=length(ndd),ncol=6) mres <- data.frame(mres) colnames(mres) <- c("name","class","length","nrow","ncol","name2") row.names(mres) <- paste("data_",sprintf("%03d",1:nrow(mres)),sep="") nd <- 0 for (idd in ndd) { nd <- nd + 1 jdd <- strsplit(x=idd,split=" ")[[1]][1] # le premier mot kdd <- strsplit(x=idd,split=" ")[[1]][2] # le deuxieme mot avec des parenthes eventuelles kdd <- gsub("[()]","",kdd) if (is.na(kdd)) { phr <- paste(" data(",jdd,",package='",package,"')",sep="") } else { phr <- paste(" data(",kdd,",package='",package,"')",sep="") } # finsi ## pour debug : cat(" voici le code exécuté : *** ",phr," *** \n") eval(parse(text=phr)) ddd <- get(jdd) mres[nd,2] <- paste(class(ddd),collapse=" ") if (!is.null(length(ddd))) { mres[nd,3] <- length(ddd) if (length(class(ddd))==1) { if (class(ddd)=="matrix") { mres[nd,3] <- NA } if (class(ddd)=="data.frame") { mres[nd,3] <- NA } } # finsi } # finsi if (!is.null(nrow(ddd))) { mres[nd,4] <- nrow(ddd) } # finsi if (!is.null(ncol(ddd))) { mres[nd,5] <- ncol(ddd) } # finsi mres[nd,6] <- kdd mres[nd,1] <- jdd } # fin pour # on trie par nom de jeu de données idx <- order(toupper(row.names(mres))) mres <- mres[idx,] mres[is.na(mres)] <- "" # on reformate les chaines à gauche lm1 <- max(nchar(mres[,1])) mres[,1] <- substr(paste(mres[,1],copies("_",lm1)),1,lm1) lm2 <- max(nchar(mres[,2])) mres[,2] <- substr(paste(mres[,2],copies("_",lm2)),1,lm2) lm6 <- max(nchar(mres[,6])) mres[,6] <- substr(paste(mres[,6],copies("_",lm6)),1,lm6) print(mres) } # fin si } # fin de fonction data2 ############################################################ data2() ; # équivalent à data2("datasets") ; data2("ade4") ;Jeux de données du package datasets =================================== il y a 206 jeu de données dans ce package. name class length nrow ncol name2 data_001 AirPassengers _______ ts ________________________________________________ 144 ___________ data_002 BJsales _____________ ts ________________________________________________ 150 ___________ data_003 BJsales.lead ________ ts ________________________________________________ 150 BJsales ____ data_004 BOD _________________ data.frame ________________________________________ 6 2 ___________ data_005 CO2 _________________ nfnGroupedData nfGroupedData groupedData data.frame 5 84 5 ___________ data_006 ChickWeight _________ nfnGroupedData nfGroupedData groupedData data.frame 4 578 4 ___________ data_007 DNase _______________ nfnGroupedData nfGroupedData groupedData data.frame 3 176 3 ___________ data_008 EuStockMarkets ______ mts ts matrix _____________________________________ 7440 1860 4 ___________ [...] Jeux de données du package ade4 =============================== il y a 105 jeu de données dans ce package. name class length nrow ncol name2 data_001 abouheif.eg _ list _____ 6 data_002 acacia ______ data.frame 32 15 data_003 aminoacyl ___ list _____ 5 data_004 apis108 _____ data.frame 180 10 data_005 ardeche _____ list _____ 6 data_006 arrival _____ list _____ 2 [...]Les listings complets pour la liste des objets et des données sont ici et là.
Pour choisir entre plusieurs solutions R, il est en général de conseillé de préférer les solutions les plus rapides à l'exécution tout en gardant un bon niveau de lisibilité. Les solutions les plus courtes à écrire ne sont donc pas forcément les meilleures. Les opérations vectorielles de base, souvent programmées en C, sont en général plus rapides que des boucles explicites. Une bonne pratique consiste à optimiser les appels par référence, à initialiser les vecteurs et les matrices au lieu de les augmenter dynamiquement, et à utiliser une fonction duree() pour essayer de voir ce qui ralentit l'exécution. Dans la série d'exercices numéro 4, on verra comment "profiler" les appels internes de fonction. Le code suivant utilise une seule fois V[idc] :
# détermination de maxV, plus grand élément du vecteur V # et comptage du nombre d'occurences (nbo) en une seule boucle nbe <- length(V) # nombre d'élements maxV <- V[1] # maximum nbo <- 1 # nombre d'occurences for (idc in (2:nbe)) { # eltc = V[idc] est l'élément courant eltc <- V[idc] if (eltc>maxV) { # nouveau maximum, donc une seule occurence maxV <- eltc nbo <- 1 } else { if (eltc==maxV) { # une nouvelle occurence du maximum nbo <- nbo + 1 } # fin si numéro 2 } # fin si numéro 1 } # fin pour idcalors que le code suivant utilise trois fois V[idc], ce qui peut ralentir l'exécution, surtout si on utilise une expression au lieu d'un simple indice comme idc :
# détermination de maxV, plus grand élément du vecteur V # et comptage du nombre d'occurences en une seule boucle nbe <- length(V) # nombre d'élements maxV <- V[1] # maximum nbo <- 1 # nombre d'occurences for (idc in (2:nbe)) { if (V[idc]>maxV) { maxV <- V[idc] nbo <- 1 } else { if (V[idc]==maxV) { nbo <- nbo + 1 } # fin si numéro 2 } # fin si numéro 1 } # fin pour idcConcernant la durée d'exécution, il y a en R une fonction nommée Sys.time(). Il suffit donc de trouver comment l'appeler avant et après l'exécution du code à tester pour connaitre le temps d'éxcéution du code. Voici un exemple de fonction duree() et un exemple ce qu'il ne faut pas faire, via la fonction bad() et ce qu'il faut faut faire avec la fonction good(). On notera l'ellipse comme seul paramètre de cette fonction duree() :
# exemple de fonction duree duree <- function(...) { tempsDeb <- Sys.time() eval.parent(...) tempsFin <- Sys.time() cat("durée = ",tempsFin-tempsDeb,"s\n") } # fin de fonction duree # une fonction lente bad <- function(n) { v <- c() i <- 1 while (i<=n) { v <- c(v,i) i <- i + 1 } # fin tant que return(v) } # fin de fonction bad # une fonction rapide good <- function(n) { return(1:n) } # fin de fonction good # vérifions les temps d'exécutions : nbVal <- 3*10**4 duree( good(nbVal) ) # moins de 0.001 secondes duree( bad(nbVal) ) # environ 2.9 secondes # solution intermédiaire entre <- function(n) { vect <- rep(NA,n) for (idv in 1:n) { vect[idv] <- idv } # fin de pour return(vect) } # fin de fonction entre duree( entre(nbVal) ) # environ 0.13 secondesdurée = 7.843971e-05 s durée = 2.83047 s durée = 0.1272614 sUne bonne pratique est souvent de prévoir la dimension des résultats, de construire une structure adaptée et de remplir cette structure, comme nous avons fait avec les fonctions ls2() et data2() car R alloue alors la mémoire en début de script. Modifier une structure en cours de script oblige R à effectuer une copie complète de la structure en mémoire, ce qui peut se révéler long si la structure est importante. A l'exercice 4 de la séance 2 nous le démontrerons avec des temps d'exécution dans un rapport de 1 à 10.
En fait, il y a de nombreux ouvrages sur R, sur l'initiation à R pour les statistiques. Par contre peu d'ouvrages récents sont complets car R comporte beaucoup de fonctions, même si on se restreint aux packages de base.
# nombre d'objets dans les packages cat(" il y a ",length(ls("package:base"))," objets dans le package base\n") # il faut charger les packages avant de pouvoir utiliser ls sur ces packages # sauf pour base et utils qui sont chargés automatiquement library(gdata) library(tools) opg <- length(ls("package:gdata")) opt <- length(ls("package:tools")) opu <- length(ls("package:utils")) # on peut aussi nommer les composantes des vecteurs cats("pour les autres packages :") nbo <- c(opg,opt,opu) names(nbo) <- c("gdata","tools","utils") print(nbo)il y a 1167 objets dans le package base pour les autres packages : ========================== gdata tools utils 64 97 198On trouvera dans le tableau suivant des liens vers la liste des objets de ces packages.
Package Nb_objets Lien base 1167 lls_base.sor gdata 64 lls_gdata.sor tools 97 lls_tools.sor utils 198 lls_utils.sor
On peut toutefois citer comme ouvrages assez exhaustifs et dédiés plus à R qu'aux statistiques :
En ce qui concerne la programmation, il y a peu d'ouvrages dédiés, sauf les ouvrages de Matloff et Gentleman :
Enfin, de nombreux ouvrages donnent des exemples généraux de calculs statistiques avec des programmes R, comme :
P. Teetor J. Adler
N. Matloff R. Gentleman
Y. Cohen B. Everitt Pour finir, en français, nous conseillons les livres de Cornillon et Husson, la série "Pratique R" chez Springer, et «bien sûr» R, l'essentiel, la traduction de R in a nutshell aux éditions Pearson :
Cornillon et al. Husson et al. Adler Cornillon, Matzner-Lober Robert, Casella Aragon Comme site en français, un bon choix nous semble celui d'Aymeric Duclert à l'adresse :
http://www.duclert.org/Aide-memoire-R/Le-langage/Introduction.php
Toujours en français, le site abcdr allstat pourrait être intéressant s'il était plus fourni. La page de Ricco Rakotomalala contient de nombreux liens avec des commentaires. Et bien sûr, le site ADE4 du PBIL est une mine de renseignements avec ses cours et TD pour R. Enfin, le site zoonek est assez intéressant dans la mesure où il présente aussi des commandes Unix pour les fichiers R, mais il n'est plus maintenu depuis 2004.
En anglais, il y a sans doute une petite dizaine de sites à connaitre. Le site ucr de l'université de Californie a de nombreuses rubriques (parcourir les onglets) sur R pour la bioinformatique. finzi est une référence majeure pour chercher des informations sur R. Quick-R est sobre et assez pratique. Enfin, le site crantastic contient des informations intéressantes sur les packages.
Comme vidéos, nous conseillons en francais celles de Lausanne et en anglais german accent (!), celles de Revolution ,ramstatvid et les vidéos qui accompagnent celles de R. Brown.
Lorsqu'on cherche des fonctions R ou des packages R, à part le CRAN et ses "taskviews", le meilleur site de recherche est sans doute R site search.
Enfin, le site de Bioconductor est bien sûr incontournable pour la bioinformatique, dès qu'il s'agit de génomique ou de puces à ADN ou de NGS.
Retour à la page principale de (gH)