Logiciel R, séances de perfectionnement
en 4 demi-journées
Séance 4 : Exports, interaction utilisateur, programmation et automatisation
gilles.hunault "at" univ-angers.fr
Table des matières cliquable
2. Définition des fonctions en R
Il est possible d'afficher toutes les solutions via ?solutions=1 et de toutes les masquer avec via ?solutions=0.
1. Notion de script R
Qu'est-ce qu'un script R ? Est-ce forcément un programme ? Est-ce forcément une fonction ?
Commenter le script suivant :
data <- read.xls("essai.xls") age <- na.omit(data[,"AGE"]) moyAge <- moy(age)Est-ce un "bon" script ? Comment l'améliorer ? Peut-on en faire une fonction ? Quels en seraient les paramètres ?
Solution : masquer la solution
Un script est en quelque sorte un programme puisque c'est un ensemble d'instructions que R exécute dans l'ordre des lignes. Ce n'est pas forcément une fonction, mais cela peut contenir des définitions et des appels de fonctions. Le script proposé ne définit aucune fonction.
Ce script vient lire un fichier Excel nommé essai.xls et calcule la moyenne de la colonne nommée AGE après avoir enlevé les valeurs manquantes dans cette colonne AGE.
Est-ce un "bon" script ? Oui, presque s'il s'agit juste de caculer la moyenne de l'age. Mais il y avait plus court, si c'était juste un calcul "à la volée" :
mean( read.xls("essai.xls")$AGE, na.rm=TRUE )Le "presque" vient de l'absence de l'instruction library(gdata) sans laquelle le script ne fonctionne pas car, en standard, le package gdata n'est pas chargé.
Un "vrai" script comporte en général des commentaires, un peu d'explications pour dire ce qu'on fait, qui l'a fait. On essaie en général de grouper les noms de fichiers, de colonnes en début de script, de façon à pouvoir facilement s'en servir sur d'autres données, comme par exemple :
# calcul de moyenne sur des données Excel avec éventuellement des données manquantes # (gH) Novembre 2014 # on utilise le package gdata pour lire les données library(gdata) # paramètres : nom du fichier et nom de la colonne nomFic <- "essai.xls" nomCol <- "AGE" # lecture des données et élimination des données manquantes data <- read.xls(nomFic) colData <- na.omit(data[,nomCol]) # calcul de la moyenne moyData <- moy(colData)On pourrait améliorer le script en le transformant en une fonction qui prend comme paramètres le nom du fichier et le nom de la colonne à traiter, soit :
##################################################################################### moySansNA <- function(nomFic="",nomCol) { ##################################################################################### # calcul de moyenne sur des données Excel avec éventuellement des données manquantes # (gH) Novembre 2014 if (missing(nomFic) | (nomFic=="")) { cat(" moySansNA : calcul de moyenne sur des données Excel \n") cat(" avec éventuellement des données manquantes\n\n") cat("syntaxe : moySansNA(nomFic,nomCol)\n") cat('exemple : moySansNA("essai.cls","AGE")\n') cat("remarque : ne fonctionne qu'avec une seule colonne à la fois.\n") return(invisible(NULL)) } # fin si # on utilise le package gdata pour lire les données library(gdata) # on teste si le fichier existe if (!file.exists(nomFic)) { cat("désolé, le fichier ",nomFic," n'existe pas.\n") stop("fin de fonction moySansNA\n\n") } # fin si # lecture des données et élimination des données manquantes data <- read.xls(nomFic) vals <- na.omit(data[,nomCol]) # calcul de la moyenne moyVals <- moy(vals) return(invisible(moyVals)) } # fin de la fonction moySansNAVoici des exemples d'utilisation :
> moySansNA() moySansNA : calcul de moyenne sur des données Excel avec éventuellement des données manquantes syntaxe : moySansNA(nomFic,nomCol) exemple : moySansNA("essai.cls","AGE") remarque : ne fonctionne qu'avec une seule colonne à la fois. > moySansNA("essay.xls","AGE") désolé, le fichier essay.xls n'existe pas. Erreur dans moySansNA("essay.xls", "AGE") : fin de fonction moySansNA > moySansNA("essai.xls","AGE") > m <- moySansNA("essai.xls","AGE") > print(m) 33.7013
2. Définition des fonctions en R
Application : écrire une fonction qui renvoie le nombre de valeurs NA présents dans un vecteur. Comment l'appliquer à chacune des colonnes d'une matrice ou d'un data frame ?
Compléter en renvoyant ensuite à la fois le nombre de valeurs NA et le pourcentage correspondant. Appliquer également à chacune des colonnes d'une matrice ou d'un data frame.
On pourra utiliser le fichier Excel essai.xls pour tester les fonctions.
Solution : masquer la solution
Pour calculer le nombre de NA dans un vecteur V on peut se contenter de l'instruction : sum(is.na(V)). Puisque les fonctions utilisées dans ce calcul, à savoir sum() et is.na() sont vectorielles, on peut utiliser apply() pour l'appliquer aux colonnes d'une structure. Voici la fonction associée et son utilisation (fichier nbnav1.r) :
nbNA <- function(v) { return(sum(is.na(v))) } # nombre de NA dans v print( nbNA( c(1,3,8,NA,2,NA,5) ) ) library(gdata) age <- read.xls("essai.xls")$AGE print( nbNA( age )) data <- read.xls("essai.xls") print( apply(X=data,F=nbNA,M=2) )[1] 2 [1] 3 IDEN AGE TAILLE POIDS POULS SYS DIA CHOL 0 3 0 0 1 1 1 0Renvoyer le nombre de NA et le pourcentage correspondant demande un plus gros effort de rédaction (fichier nbnav2.r) :
##################################################################################### nbNApct <- function(v) { ##################################################################################### # calcule du nombre de données NA dans un vecteur et calcul du pourcentage correspondant # (on ne teste pas si le vecteur est vide) nbna <- sum(is.na(v)) pct <- 100*nbna/length(v) # on pourrait utiliser une liste avec # return(list(nbNA=nbna,pctNA=pct)) # mais un vecteur nommé est "mieux en retour" vres <- c(nbna,pct) names(vres) <- c("nbNA","pctNA") return(vres) } # fin de fonction nbNApct ##################################################################################### # # exemples d'utilisation : # ##################################################################################### cat(" pour un vecteur quelconque : \n") print( nbNApct( c(1,3,8,NA,2,NA,5) ) ) cat(" pour age : \n") library(gdata) age <- read.xls("essai.xls")$AGE print( nbNApct( age )) cat(" pour toutes les colonnes d'un data frame\n") data <- read.xls("essai.xls") print( apply(X=data,F=nbNApct,M=2) ) # et avec une matrice : md <- matrix(round(100*runif(15)), nrow=5,ncol=3) md[4,2] <- md[1,2] <- NA print(md) print( apply(X=md,F=nbNApct,M=2) )pour un vecteur quelconque : nbNA pctNA 2.00000 28.57143 pour age : nbNA pctNA 3.00 3.75 pour toutes les colonnes d'un data frame IDEN AGE TAILLE POIDS POULS SYS DIA CHOL nbNA 0 3.00 0 0 1.00 1.00 1.00 0 pctNA 0 3.75 0 0 1.25 1.25 1.25 0 [,1] [,2] [,3] [1,] 63 NA 88 [2,] 80 53 25 [3,] 42 48 83 [4,] 59 NA 73 [5,] 20 30 54 [,1] [,2] [,3] nbNA 0 2 0 pctNA 0 40 0On a fait le choix ici d'utiliser un vecteur pour renvoyer les résultats. Il est classique d'utiliser plutôt une liste, mais cela «gâche» un peu les sorties directes de résultats sur écran avec apply() et il est conseillé d'utiliser alors sapply(). Attention : sapply() ne fonctionne bien qu'avec des listes -- donc avec des data frames -- en entrée mais pas avec des matrices (fichier nbnav3.r) :
##################################################################################### nbNApct <- function(v) { # VERSION AVEC LISTE EN RETOUR ! ##################################################################################### # calcule du nombre de données NA dans un vecteur et calcul du pourcentage correspondant # (on ne teste pas si le vecteur est vide) nbna <- sum(is.na(v)) pct <- 100*nbna/length(v) return(list(nbNA=nbna,pctNA=pct)) } # fin de fonction nbNApct ##################################################################################### cat(" pour un vecteur quelconque : \n") print( nbNApct( c(1,3,8,NA,2,NA,5) ) ) cat(" pour age : \n") library(gdata) age <- read.xls("essai.xls")$AGE print( nbNApct( age )) cat(" pour toutes les colonnes\n") data <- read.xls("essai.xls") print( apply(X=data,F=nbNApct,M=2) ) cat(" pour toutes les colonnes avec sapply :\n") data <- read.xls("essai.xls") print( sapply(X=data,F=nbNApct) ) # cela ne marche pas très bien avec une matrice md <- matrix(round(100*runif(15)), nrow=5,ncol=3) md[4,2] <- md[1,2] <- NA print(md) print( sapply(X=md,F=nbNApct) )pour un vecteur quelconque : $nbNA [1] 2 $pctNA [1] 28.57143 pour age : $nbNA [1] 3 $pctNA [1] 3.75 pour toutes les colonnes $IDEN $IDEN$nbNA [1] 0 $IDEN$pctNA [1] 0 $AGE $AGE$nbNA [1] 3 $AGE$pctNA [1] 3.75 $TAILLE $TAILLE$nbNA [1] 0 $TAILLE$pctNA [1] 0 $POIDS $POIDS$nbNA [1] 0 $POIDS$pctNA [1] 0 $POULS $POULS$nbNA [1] 1 $POULS$pctNA [1] 1.25 $SYS $SYS$nbNA [1] 1 $SYS$pctNA [1] 1.25 $DIA $DIA$nbNA [1] 1 $DIA$pctNA [1] 1.25 $CHOL $CHOL$nbNA [1] 0 $CHOL$pctNA [1] 0 pour toutes les colonnes avec sapply : IDEN AGE TAILLE POIDS POULS SYS DIA CHOL nbNA 0 3 0 0 1 1 1 0 pctNA 0 3.75 0 0 1.25 1.25 1.25 0 [,1] [,2] [,3] [1,] 43 NA 86 [2,] 92 77 80 [3,] 78 14 55 [4,] 50 NA 77 [5,] 1 33 65 [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] nbNA 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 pctNA 0 0 0 0 0 100 0 0 100 0 0 0 0 0 0
3. Interaction utilisateur et gestion de liste de fichiers
Ecrire une fonction qui affiche le nombre de valeurs manquantes des colonnes d'un ou plusieurs fichiers Excel passés en paramètre. Si aucun paramètre, la fonction doit afficher le gestionnaire de fichiers pour qu'on puisse sélectionner le fichier. On pourra utiliser les fichier Excel essai.xls et war2.xls pour tester la fonction.
Solution : masquer la solution
Un examen détaillé de la solution ci-dessous montre que nous avons utilisé une fonction anonyme pour calculer le nombre de NA et que le parcours de vecteurs de chaines de caractères est d'une grande simplicité en R (fichier nbnav4.r) :
####################################################################### nbna <- function(ldf) { # ldf : liste de fichiers ####################################################################### library(gdata) # si pas de paramètre, on utilise le gestionnaire de fichiers # pour sélectionner les fichiers à utiliser if (missing(ldf)) { ldf <- file.choose() } # pour chacun des fichiers présents dans ld, on affiche le nombre de NA par colonne for (fic in ldf) { cat("Fichier : ",fic,"\n") dat <- read.xls(fic) print( apply(X=dat,F=function(x) { return(sum(is.na(x))) },M=2) ) } # fin de boucle pour sur fic } # fin de fonction nbna # exemples d'utilisation : nbna("essai.xls") liste <- c("essai.xls","war2.xls") nbna(liste) # not run (comme ils disent) : # nbna()Fichier : essai.xls IDEN AGE TAILLE POIDS POULS SYS DIA CHOL 0 3 0 0 1 1 1 0 Fichier : essai.xls IDEN AGE TAILLE POIDS POULS SYS DIA CHOL 0 3 0 0 1 1 1 0 Fichier : war2.xls CODE LnYCAP POLITY INEQ MANU MCP REGPOL REGOIL REGREF 0 0 0 0 0 0 0 0 0Il aurait été possible d'écrire une solution plus fonctionnelle et sans boucle explicite avec une fonction locale (traiteFic) comme (fichier nbnav5.r) :
####################################################################### nbna <- function(ldf) { # ldf : liste de fichiers ####################################################################### library(gdata) traiteFic <- function(nomFic) { cat("Fichier : ",nomFic,"\n") dat <- read.xls(nomFic) print( apply(X=dat,F=function(x) { return(sum(is.na(x))) },M=2) ) return(invisible(NULL)) } # fin de fonction traiteFic # si pas de paramètre, on utilise le gestionnaire de fichiers # pour sélectionner les fichiers à utiliser if (missing(ldf)) { ldf <- file.choose() } # pour chacun des fichiers présents dans ld, on affiche le nombre de NA par colonne sapply(X=ldf,F=traiteFic) } # fin de fonction nbna # exemples d'utilisation : res <- nbna("essai.xls") liste <- c("essai.xls","war2.xls") res <- nbna(liste) # not run (comme ils disent) : # nbna()Fichier : essai.xls IDEN AGE TAILLE POIDS POULS SYS DIA CHOL 0 3 0 0 1 1 1 0 Fichier : essai.xls IDEN AGE TAILLE POIDS POULS SYS DIA CHOL 0 3 0 0 1 1 1 0 Fichier : war2.xls CODE LnYCAP POLITY INEQ MANU MCP REGPOL REGOIL REGREF 0 0 0 0 0 0 0 0 0Mais un "puriste" aurait sans doute préféré la solution suivante sans fonction interne explicite (fichier nbnav6.r) :
####################################################################### nbna <- function(ldf) { # ldf : liste de fichiers ####################################################################### library(gdata) # si pas de paramètre, on utilise le gestionnaire de fichiers # pour sélectionner les fichiers à utiliser if (missing(ldf)) { ldf <- file.choose() } # pour chacun des fichiers présents dans ld, on affiche le nombre de NA par colonne sapply( X=ldf, F=function(nomFic) { cat("Fichier : ",nomFic,"\n") dat <- read.xls(nomFic) print( apply(X=dat,F=function(x) { return(sum(is.na(x))) },M=2) ) return(invisible(NULL)) } ) # fin de sapply } # fin de fonction nbna # exemples d'utilisation : res <- nbna("essai.xls") liste <- c("essai.xls","war2.xls") res <- nbna(liste) # not run (comme ils disent) : # nbna()Fichier : essai.xls IDEN AGE TAILLE POIDS POULS SYS DIA CHOL 0 3 0 0 1 1 1 0 Fichier : essai.xls IDEN AGE TAILLE POIDS POULS SYS DIA CHOL 0 3 0 0 1 1 1 0 Fichier : war2.xls CODE LnYCAP POLITY INEQ MANU MCP REGPOL REGOIL REGREF 0 0 0 0 0 0 0 0 0
4. Références bibliographiques pour l'ensemble des séances
Que lire pour approfondir tout cela ?
Solution : masquer la solution
Plein de choses !
Il faut d'abord choisir si on veut approfondir le langage R, les statistiques ou la programmation. Ensuite, il n'y a plus que l'embarras du choix.
Voir par exemple ma question sur les livres pour R et sa réponse associée dans mes pages de programmation R avancée.
Code-source php de cette page. Retour à la page principale du cours.
Retour à la page principale de (gH)