Programmer sous R - V1.2 - TONIC
Transcription
Programmer sous R - V1.2 - TONIC
Programmer sous R Version 1.2 (mars 2013) F. Aubry, INSERM U825, Toulouse Table des matières INSTALLATION ..................................................................................................................... 5 CHOIX DU SITE MIROIR ............................................................................................................ 5 AIDE EN LIGNE ........................................................................................................................ 5 CHOIX DE L’ÉDITEUR EN LIGNE ............................................................................................... 6 MISE À JOUR ............................................................................................................................ 6 Chargement des packages .................................................................................................. 6 Activation des packages ..................................................................................................... 7 Mise à jour des packages ................................................................................................... 7 Mise à jour des versions de R............................................................................................. 7 R COMMANDER ....................................................................................................................... 8 SAUVEGARDE ET RÉCUPÉRATION DES SESSIONS ...................................................................... 8 FONCTIONS ET MÉTHODES, 1ÈRE PARTIE .................................................................... 9 ACCÈS À L’AIDE EN LIGNE ....................................................................................................... 9 ARGUMENTS DES FONCTIONS ................................................................................................ 10 LES MÉTHODES ...................................................................................................................... 10 LES TYPES DE DONNÉES.................................................................................................. 12 FONCTIONS GÉNÉRIQUES ET VALEURS SPÉCIALES .................................................................. 12 LES TYPES ATOMIQUES .......................................................................................................... 13 Nombres ........................................................................................................................... 13 Chaînes de caractères ...................................................................................................... 14 Les logiques ou booléens.................................................................................................. 15 VECTEURS ............................................................................................................................. 16 Constructeur ..................................................................................................................... 16 Accès aux éléments ........................................................................................................... 17 Opérations sur les vecteurs .............................................................................................. 18 Quelques fonctions utilitaires........................................................................................... 18 FACTEURS ............................................................................................................................. 19 Constructeur ..................................................................................................................... 20 Contrastes......................................................................................................................... 21 Utilitaires ......................................................................................................................... 25 MATRICES ............................................................................................................................. 25 Constructeur ..................................................................................................................... 25 Sélectionner des éléments d’une matrice ......................................................................... 25 Opérations ........................................................................................................................ 26 Utilitaires ......................................................................................................................... 27 SÉRIES TEMPORELLES ............................................................................................................ 28 TABLES ................................................................................................................................. 28 LISTES ................................................................................................................................... 28 Constructeur ..................................................................................................................... 28 Accès aux éléments de la liste .......................................................................................... 29 Utilitaires ......................................................................................................................... 29 DATE ..................................................................................................................................... 30 DATA FRAME ......................................................................................................................... 30 Construction et accès aux éléments ................................................................................. 30 Facteurs ............................................................................................................................ 30 Programmer R - Version 1.2 (mars 2013) F. Aubry p. 2 Utilitaires ......................................................................................................................... 31 Jointure de deux data.frame ............................................................................................. 33 FORMULE .............................................................................................................................. 35 Construction de la partie gauche ..................................................................................... 36 Construction générale de la partie droite ........................................................................ 36 Autres arguments.............................................................................................................. 38 Utilitaires ......................................................................................................................... 39 SCRIPTS ................................................................................................................................. 41 CHARGER DU CODE ............................................................................................................... 41 AFFECTATION D’UNE VALEUR À UNE VARIABLE .................................................................... 41 STRUCTURES DE CONTRÔLE .................................................................................................. 42 Tests .................................................................................................................................. 42 Boucles ............................................................................................................................. 43 Fonctions d’itérations ...................................................................................................... 44 Gestion des erreurs .......................................................................................................... 46 VISUALISER LES VALEURS ..................................................................................................... 47 Généralités ....................................................................................................................... 47 Visualiser à la console ..................................................................................................... 47 Imprimer sur la console ................................................................................................... 48 GESTION DES FICHIERS, LIRE ET ÉCRIRE DES DONNÉES SUR DISQUE ....................................... 48 Système de fichiers ........................................................................................................... 48 Accès aux fichiers de données .......................................................................................... 49 FONCTIONS : MANIPULER ET CONTRÔLER LES ARGUMENTS ................................................... 52 Définition de la fonction ................................................................................................... 52 L’argument spécial ... ....................................................................................................... 53 Contrôler la valeur des arguments................................................................................... 54 Gérer des fonctions comme argument .............................................................................. 56 Créer de nouveaux opérateurs binaires ........................................................................... 57 OPTIONS ................................................................................................................................ 59 MISE AU POINT DU SCRIPT ..................................................................................................... 60 Gestion générale .............................................................................................................. 60 Diagnostic de sortie en erreur ......................................................................................... 60 Diagnostic en ligne........................................................................................................... 60 LES DISTRIBUTIONS.......................................................................................................... 62 LES GRAPHIQUES .............................................................................................................. 63 LES FENÊTRES GRAPHIQUES .................................................................................................. 63 LES GRAPHIQUES DE BASE ..................................................................................................... 63 LE PACKAGE LATTICE ............................................................................................................ 64 EXERCICES RÉCAPITULATIFS ...................................................................................... 65 EXERCICE RÉCAPITULATIF I................................................................................................... 65 EXERCICE RÉCAPITULATIF II : OPÉRATIONS SUR DES MATRICES CREUSES ............................. 66 SOLUTION DES EXERCICES ............................................................................................ 67 EXERCICE PAGE 14 : .............................................................................................................. 67 EXERCICE PAGE 26 : .............................................................................................................. 67 EXERCICE PAGE 31 : .............................................................................................................. 69 EXERCICE PAGE 36 : .............................................................................................................. 69 Programmer R - Version 1.2 (mars 2013) F. Aubry p. 3 EXERCICE PAGES 43 ET 44 : ................................................................................................... 69 EXERCICES PAGES 53 ET 54 : ................................................................................................. 73 EXERCICE RÉCAPITULATIF I................................................................................................... 76 EXERCICE RÉCAPITULATIF II : OPÉRATIONS SUR DES MATRICES CREUSES ............................. 83 LES PACKAGES ESSENTIELS.......................................................................................... 87 QUELQUES RÉFÉRENCES ................................................................................................ 89 DOCUMENTS ACCESSIBLES À PARTIR DU SITE DE R................................................................ 89 AUTRES DOCUMENTS, BLOGS, FAQ…................................................................................... 89 Je remercie Jérôme Llido pour sa relecture du document. Si vous remarquez des erreurs ou des imprécisions, ou si vous voulez améliorer le document par vos remarques, vos expériences, des exemples…, n’hésitez pas à m’en faire part à l’adresse suivante (codée sous forme d’une chaîne de caractères) : '\u0066\u006C\u006F\u0072\u0065\u006E\u0074\u002E\u0061\u0075\u0062\u0072\u0079\u 0040\u0069\u006E\u0073\u0065\u0072\u006D\u002E\u0066\u0072' Programmer R - Version 1.2 (mars 2013) F. Aubry p. 4 R tourne sur toutes les plates-formes, Windows 32 et 64 bits, Apple et Unix. Il peut être téléchargé sur le site de R (http://www.r-project.org/). On télécharge alors le cœur puis les ‘packages’ d’intérêt. R natif propose une console alphanumérique et l’appel des procédures se fait donc ligne à ligne. Il est possible d’écrire des scripts et des fonctions. L’objectif de ce document est de donner les bases pour le faire. Dans les noms des variables, des fonctions ou des arguments de fonction, le point n’a aucun sens particulier. De ce fait, les noms du type ma.variable, is.logical ou lower.tail sont valides. ATTENTION : R est sensible à la casse. De ce fait, a et A référencent deux variables ou fonctions différentes. Installation Aller sur le site de R, ou l’un des sites miroir, et charger l’installation de l’exécutable qui s’installe alors comme tout exécutable en fonction du système d’exploitation. Choix du site miroir On peut facilement choisir son site miroir par défaut, ce qui fait que la question du choix du site miroir ne se posera plus. Il suffit de modifier les fichiers Rprofile.site dans le répertoire etc du programme R : # set a CRAN mirror : par exemple celui de Montpellier local({r <- getOption( "repos") r["CRAN"] <- "http:// ftp.igh.cnrs.fr/pub/CRAN" options( repos=r)}) Aide en ligne Pour l’aide en ligne, R donne deux possibilités, 1) de l’obtenir sous forme de fenêtres de texte, 2) de l’avoir sous forme de pages HTML dans le navigateur par défaut. Cette seconde option est plus pratique. Elle est facilement programmable par défaut dans le fichier Rprofile.site : options( help_type="html") Si on choisit l’aide sous forme de pages HTML, R utilise la boucle local localhost (n° IP 127.0.0.1) pour dialoguer avec le navigateur par défaut. En conséquence : 1) l’aide ne peut pas s’afficher si le navigateur travaille hors connexion ; il faut donc décocher cette option et réessayer ; 2) on ne peut plus naviguer dans l’aide si on a quitté R. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 5 On peut aussi accéder à une aide en ligne à partir du répertoire doc/html du programme R, en lisant dans le navigateur le fichier index.html (file://${R_HOME}/doc/html/index.html1). Il existe aussi des fichiers d’aide dans le répertoire doc/manual. On trouvera un certain nombre de référence à des manuels sur le site officiel de R, à la page ‘Manuals’ et ‘Contributed documentation’. Un des documents de référence est celui d’Emmanuel Paradis dont il existe une version anglaise et une version française. Choix de l’éditeur en ligne On peut aussi choisir l’éditeur de texte à appeler à partir de R : options( editor=cheminDAccesALEditeur) Tinn-R est un éditeur spécialement développé pour R (http://www.sciviews.org/Tinn-R ou http://sourceforge.net/projects/tinn-r) qui intègre une fonction d'aide à la saisie des arguments dans une fonction R (R-card). De plus, il permet de travailler avec n'importe quelle interface graphique de R dont la console standard Rgui (chemin d'accès à spécifier dans le volet R du menu Options, item Main/Applications) et d'envoyer ou d'exécuter pas à pas du code. Pour plus de détails, voir l'aide proposée avec Tinn-R. RStudio est un nouvel environnement complet de développement (en anglais IDE ou integrated development environment) libre spécialement développé pour R (http://www.rstudio.com). C’est celui qui est à recommandé actuellement. Il existe d’autres éditeurs permettant cette interaction. Pour plus de détails voir la page consacrée à ce sujet sur le forum francophone des utilisateurs du logiciel R hébergé par le Cirad (Centre de recherche agronomique pour le développent) à l’adresse http://forums.cirad.fr/logiciel-R/faq.php (adresse du forum aux questions). Mise à jour Chargement des packages 1) Lancer R 2) Dans le menu Packages, cliquez sur Installer les packages i) Sauf si un site miroir par défaut n’est pas défini, choisir le site miroir (i.e., France, Montpellier) ii) Choisir les packages à installer. 1 ${R_HOME} doit être remplacé par le chemin d’accès au répertoire d’installation de R. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 6 Activation des packages Les packages peuvent être activés manuellement par la commande library( nomDuPackage) qui peut prendre la forme require( nomDuPackage). Si l’appel au package est fait dans un script destiné à être diffusé, on peut alors utiliser le code suivant if( !require( nomDuPackage)) { install.packages( "nomDuPackage") } Il n’est besoin de charger le package en mémoire qu’une fois par session. Si le package est utilisé systématiquement, il est préférable de la charger automatiquement à l’ouverture de la session. On peut automatiser le lancement de deux manières différentes i) ajouter la ligne de commande à la fin du fichier texte Rprofile.site ii) a) dans son propre répertoire de base ($HOME pour Unix, Mes Documents pour Windows), créer le fichier texte .Rprofile [le nom du fichier commence par un point] b) dans ce fichier, y mettre la ligne de commande. Mise à jour des packages Elle se fait par l’item Mise à jour des packages du menu Packages. Cependant, R refuse de mettre à jour les packages actifs, c’est-à-dire ceux utilisés dans la session. Il faut donc décharger les packages de la mémoire. Le plus simple est de sortir de R et de le relancer sans les packages. Si les packages sont lancés automatiquement, il faut d’abord mettre les lignes de lancement des packages en commentaire (i.e., mettre un # en début de ligne), relancer R, mettre à jour les packages, ressortir de R et supprimer les commentaires. En effet, R ne met pas à jour les packages actifs. S’il y a peu de packages à modifier et qu’on connait les noms, on peut aussi utiliser la commande detach : detach( package:nomDuPackage, unload=TRUE). Quelquefois, les packages mis à jour ont été compilés par une release ou une version ultérieure de celle couramment utilisée. De ce fait quand on essaie de les lancer par la commande library( nomDuPackage) on obtient un message d’alerte du type Le package nomDuPackage a été compilé par la version XX.YY Généralement, il peut quand même fonctionner correctement mais il est préférable de se méfier et de charger la nouvelle version/release de R (cf. infra) Mise à jour des versions de R Régulièrement, le consortium R met à jour le logiciel. Les mises à jour peuvent être mineures et elles sont alors indiquées par un changement du numéro de release (par exemple, R.14.0 à R.14.1), soit elles sont majeures et conduisent à la mise à disposition d’une nouvelle version (par exemple, R.14.1 à R.15.0). R n’offre pas d’alerte automatique pour indiquer ces mises à jour et il faut aller régulièrement les vérifier sur le site. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 7 Quand on installe une nouvelle mise à jour, R n’écrase pas l’ancienne version contrairement à ce qui se passe pour les mises à jour des packages, mais crée un nouvel environnement qui ne contient aucun des packages supplémentaires, autres que ceux de base. Pour recréer son propre environnement, il suffit alors de copier tous les répertoires contenus par le répertoire library de l’ancienne version et qui contient tous les packages utilisés, dans le répertoire library de la nouvelle version, sans écraser ceux qui existent déjà et qui correspondent aux packages de base livrés avec la version. Ensuite, il suffit de mettre à jour les packages comme expliqué dans le paragraphe précédent. Actuellement, pour les utilisateurs de Windows seulement, Tal Galili a développé un package qui permet une mise à jour automatique de R à télécharger depuis le site de R. Voir http://www.r-bloggers.com/updating-r-from-r-on-windows-using-the-installrpackage/?utm_source=feedburner&utm_medium=email&utm_campaign=Feed%3A+RBlo ggers+%28R+bloggers%29 R Commander John Fox a développé une interface R commander, plus orientée vers l’utilisateur qui est à mi-chemin entre la commande ligne à ligne et l’interface offert par Statistica®. Elle propose l’accès aux principales fonctions sans avoir à écrire de lignes de commande. Je conseille de l’installer. Pour cela, il suffit de charger le package Rcmdr ainsi que tous les packages RcmdPlugin.xxx. Par défaut, il faut réinstaller le package à chaque fois qu’on lance R. Ceci peut se faire par la commande : library( Rcmdr) Cependant, ce n’est pas pratique. On peut donc automatiser son lancement par une des procédures décrites ci-dessus. Pour l’utilisation R commander on peut par exemple consulter "Analyses statistiques de base avec R et Rcmdr comme interface graphique" de Christian Jost (http://cognition.upstlse.fr/_christian/poly/stats/TP-BS15M-Rcmdr.pdf). Sauvegarde et récupération des sessions Lors d’une session un peu longue, on peut vouloir créer des points de sauvegarde et de restauration intermédiaire. Ceux-ci peuvent être anonymes et seront alors rechargés automatiquement à chaque lancement de R : - fichier .rData (sauver l’environnement de travail du menu Fichier) ; - fichier .rhistory (sauver l’historique des commandes du menu Fichier). On peut aussi donner des noms à ces fichiers tout en conservant les extensions. Cependant, contrairement aux fichiers anonymes, ceux-ci devront être explicitement relus pour que leur contenu soit utilisable sauf si on programme une fonction automatique pour le faire (fonction .First). Si on ne veut sauver que quelques données, il faut alors utiliser la fonction save. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 8 On peut vouloir automatiser ces sauvegardes à chaque sortie de R en créant une fonction nommée .Last. Fonctions et méthodes, 1ère partie Nous verrons plus loin comment coder le corps des fonctions. L’objectif de ce paragraphe est d’expliquer le codage des appels aux fonctions2 afin de mieux comprendre la documentation. Accès à l’aide en ligne Il existe trois niveaux d’aide en ligne : 1) recherche générale sur le site CRAN de R, ce qui nécessite une connexion Web, par la fonction RSiteSerach( "nomRecherche") ; 2) recherche générale locale parmi les packages installés par la commande help.search dont une utilisation simplifiée s’écrit ??nomDeLItem ; R génère alors une fenêtre texte avec tous les items trouvés proches de l’item cherché ; 3) recherche de la documentation d’une fonction particulière : ?nomDeLaFonction parmi les packages actifs en mémoire, ?nomDuPackage::nomDeLaFonction parmi les packages en mémoire, ?nomDuPackage:::nomDeLaFonction parmi les packages installés. Quelques aides particulières ?Startup sur l’initialisation de R au début d’une session ?Syntax sur la syntaxe de R et la précédence des opérateurs ?Arithmetic sur les opérateurs arithmétiques ?Comparison sur les opérateurs de comparaison sur les structures de commande (boucles, répétitions…) ?Control ?Extract sur les opérateurs d’accès aux éléments de vecteurs, matrices… ?Logic sur les opérateurs et données logiques ?NumericConstants sur les constantes numériques ?Paren sur les parenthèses ?Quotes sur les caractères d’échappement sur les mots réservés ?Reserved Dans ce document, je liste un certain nombre de fonctions en donnant leur utilisation. Je n’explicite que rarement tous les arguments des fonctions ni tous leurs comportements selon leurs arguments. Je conseille de se reporter à la documentation en ligne ?nomDeLaFonction. 2 N.B. : dans la suite de la documentation, quand je parlerai d’utiliser la fonction f pour effectuer une certaine tâche (par exemple, pour lister l’ensemble des objets présents en mémoire), cela signifie qu’il est faut appeler la fonction en donnant les valeurs nécessaires à ses arguments si nécessaire (dans l’exemple, on écrira simplement ls()). Programmer R - Version 1.2 (mars 2013) F. Aubry p. 9 Arguments des fonctions Une fonction peut avoir des arguments nommés explicitement. Par exemple, la fonction atan2 a deux arguments nommés respectivement x et y. Elle peut aussi avoir des arguments anonymes en nombre variable. Ceux-ci sont alors codés par le symbole spécial ... (cf. la fonction list). Elle peut aussi mélanger les deux types d’arguments. Dans ce cas, les arguments anonymes peuvent être listés en premier comme dans la fonction max, en dernier (cf. apply) ou au milieu des arguments (cf. aggregate). Cette dernière option est possible car si les arguments peuvent être passés à la fonction par position comme dans la majorité des langages informatiques comme par exemple, dans l’appel suivant à la fonction lm : form <- … don <- read.table( arguments) lm( form, don) form est obligatoirement une formule3 et don un data.frame4 puisque c’est dans cet ordre qu’ils sont défini dans la signature de la fonction. Ils peuvent aussi être passés par nom. Dans ce cas, l’ordre est quelconque : lm( data=don, formula=form). On passe généralement par position le premier argument de la fonction qui peut avoir un rôle spécial (cf. infra, la notion de méthode) tandis que les autres arguments sont passés soit par identificateur : lm( form, data=don). soit par position. Les arguments peuvent aussi avoir des valeurs par défaut qui sont codés de la forme : nomDeLArgument=valeurParDefaut 5 dans la signature de la fonction. La valeur par défaut peut être une valeur immédiate (un nombre, une chaîne de caractères, une fonction…) ou une fonction des arguments précédemment définis (cf. l’argument scores de la fonction contr.poly). Les méthodes R est un langage orienté objet basé sur la notion de classes d’objet, c’est-à-dire que chaque données utilisées par R appartient à au moins une classe (accessible par la fonction class). Il y a deux versions du codage des classes, les classes de type S3, le codage le plus ancien, et celles de type S4. Les objets de type S3 sont largement basés sur les list et donc leurs membres sont accessibles via l’opérateur $ (p.ex., lm.result <- lm( ... ; lm.result$coefficients sont les paramètres estimés) tandis que nommés slots, ils sont accessibles par l’opérateur @ pour les objets S4 (p.ex., lmer.result <- lmer( ... ; lmer.result@fixef). 3 cf. infra cf. infra 5 On appelle signature d’une fonction la ligne qui définit ses arguments et leur type ainsi que le type de retour. C’est donc la signature de la fonction qui est donnée dans l’aide. 4 Programmer R - Version 1.2 (mars 2013) F. Aubry p. 10 L’avantage des classes est la notion de méthode qui est une fonction générique dont le comportement dépend de l’objet auquel est elle est appliquée. Par exemple, pour avoir un résumé d’une analyse faite par lm ou lmer, on utilisera la méthode summary. Pour les objets de type S3, la méthode générique (p.ex., anova, summary) fait appel à une méthode spécifique de nom nomDeLaMetodeGenerique.nomDeLaClasse (p.ex., anova.lm, anova.aov...). Ceci explique pourquoi certaines pages comme celle de lm renvoient à des descriptions de fonction ayant ce nom composite. Cependant, l’utilisateur n’a généralement pas besoin de connaître ces noms spécifiques, sauf pour accéder à la documentation. L’appel explicite est plus compliqué pour les objets de classe S4. Pour plus de détails sur les types de classes et leur codage, par exemple : - pour les classes de type S3 : Classes S3. (http://www.duclert.org/Aide-memoire-R/Le-langage/Classes-S3.php)6 - pour les classes de type S4 : Petit manuel de S4, Programmation orienté objet sous R. Christophe Genlini. (http://cran.r-project.org/doc/contrib/Genolini-PetitManuelDeS4.pdf) 6 Cette page est l’une d’un site consacré à la programmation sous R accessible à l’adresse http://www.duclert.org/. Il faut noter qu’il y a aussi une page consacrée aux classes de type S4. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 11 Les types de données Les types de données sont définis par leur longueur, leurs dimensions pour certaines, leur mode et leur classe. De plus, chaque donnée peut avoir des attributs spécifiques qui seront utilisés par R pour manipuler la donnée ou définis par l’utilisateur. Il n’est pas conseillé de modifier les attributs spécifiques car cela peut conduire à des comportements aberrants. Fonctions génériques et valeurs spéciales La longueur (fonction length) donne le nombre d’éléments qui constituent la donnée. Le mode définit la nature de l’élément, c’est-à-dire les opérations susceptibles d’être appliquées à la donnée. Il est accessible par la fonction mode. Généralement, le mode fait référence à un type atomique. Par exemple, mode( a) est "numeric" signifie qu’on peut appliquer à la variable a toutes les opérations arithmétiques classiques. La classe définit le comportement de l’objet, c’est-à-dire les opérations de manipulation applicable à cet objet, ce qu’on appelle dans la terminologie objet, les méthodes. Elle est accessible par la fonction class. On peut utiliser la fonction isS4 pour vérifier que la classe de l’objet en question est du type S4. Alors que le mode définit les opérations applicables aux éléments (ou membres) de la donnée, la classe définit donc les opérations applicables à la donnée considérée comme un tout. Par exemple : mode( mat) donne "numeric" class( mat) donne "matrix" mode( a) donne "numeric" class( a) donne "numeric" signifie que la commande mat <- mat + a a un sens (on ajoute la valeur de a à chaque élément de la matrice) puisque les modes sont identiques. De plus, on peut accéder au nombre de lignes et de colonnes de mat (fonction dim) mais cela n’a pas de sens pour a puisque a n’est pas de la classe matrix. La fonction attributes donne la liste des attributs de la donnée et leur valeur tandis que la fonction attr permet de manipuler les attributs spécifiques ou définis par l’utilisateur. Certains attributs spécifiques sont accessibles par des fonctions spéciales. Par exemple, l’attribut dim d’une matrice est accessible par la fonction dim, ce qui est plus pratique. Dans R, il existe une différence entre la notion de membre/slot et d’attribut. Le membre/slot contient la valeur de la donnée tandis que les attributs contiennent des informations sur la donnée elle-même. Par exemple, mat <- matrix( 0, ncol=10, nrow=20, dimnames=list( nomLignes, nomColonnes)) sera une matrice nulle de 20 lignes et de 10 colonnes, dont les noms des lignes seront données par le vecteur nomLignes et celui des colonnes, par le vecteur nomColonnes. Elle aura deux attributs dim (les dimensions) et dimnames (les noms des lignes et des colonnes). Il faut donc manipuler les attributs d’un objet avec précaution. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 12 La fonction str donne la structure de la donnée, c’est-à-dire ses composantes et ses attributs. La méthode summary donne un résumé de la donnée qui dépend à la fois de sa classe et de son mode. Par exemple, pour un vecteur de nombres, elle renvoie le minimum, le maximum, les 1er, 2ème (médiane) et 3ème quantiles ainsi que la moyenne et pour un vecteur de chaînes de caractères, elle renvoie la longueur, le mode et la classe. Pour tester si une donnée est d’un type particulier, on peut utiliser une fonction is.nomDuType. Par exemple, pour tester si a est un entier, on écrira is.integer( a) ou si a est une matrice, on utilisera is.matrix( a). Il faut cependant être prudent dans l’utilisation de ces tests. En effet, si a est une matrice de nombre, is.numeric( a) sera aussi vrai. Pour les classes de données n’ayant pas ce type de fonction, on peut utiliser la fonction générique inherits. Pour convertir une donnée dans un autre type, on dispose des fonctions as.nomDuTypeCible. Par exemple si a est une chaîne de caractères représentant le chiffre 1, as.integer( a) sera l’entier 1. Si la conversion n’est pas valide, la fonction retournera une erreur. Enfin, il faut noter que R reconnaît deux valeurs qui ont un rôle spécial dans la manipulation des données : - la valeur NULL de longueur nulle qui signifie qu’aucune valeur n’y est affectée ; - la valeur NA (Not Available) de longueur égale à 1 qui signifie que la valeur est inconnue7. Il ne faut pas confondre les deux valeurs. En effet, si vec.1 est un vecteur et qu’on crée un second vecteur vec.2 qui contient tous les éléments de vec.1 auxquels on ajoute la valeur NULL, alors vec.2 et vec.1 sont identiques. Si on ajoute la valeur NA, alors la longueur de length( vec.2) = length( vec.1) + 1, la dernière valeur étant NA. Pour tester qu’une variable n’est pas initialisée, c’est-à-dire si sa valeur est NULL, on peut tester si sa longueur est nulle (length( var) == 0) mais il est plus explicite d’utiliser la fonction is.null. Le test à la valeur NA se fait par la fonction is.na. Les types atomiques Nombres La fonction mode retourne "numeric". Il existe deux classes, la classe "numeric" ou "double" pour les nombres flottants et la classe "integer" pour les entiers. Le codage des nombres complexes est possible. Les nombres complexes s’écrivent sou la forme nombreComplexe <- partieReelle + partieImaginaire i8 7 Pour un codage plus précis de la valeur manquante, consulter la page de la documentation par ?NA. À noter qu’il ne faut pas de blanc entre la valeur de la partie imaginaire et le symbole i. Je l’ai inséré pour rendre compréhensible la lecture de l’instruction. 8 Programmer R - Version 1.2 (mars 2013) F. Aubry p. 13 Exemple 1i^2 donne -1+0i Pour les détails voir la documentation ( ?complex). R dispose de deux valeurs spéciales, l’une pour coder moins l’infini (-Inf) et l’autre pour coder plus l’infini (Inf). Ces valeurs peuvent être aussi testées par les fonctions is.finite et is.infinite. R dispose aussi d’une valeur spéciale pour coder le résultat d’opérations mathématiques qui ne sont pas des nombres comme la division par zéro : NaN. Cette valeur peut être testée par la fonction is.nan. Enfin R propose une variable spéciale de nom pi qui contient la valeur de la constante pi. Cette valeur peut être écrasée par l’utilisateur s’il réinitialise la variable pi. Donc, faire attention à ne jamais utiliser de variable ayant comme nom pi. Pour les opérations mathématiques, voir la page Arithmetic de l’aide. Pour les comparaisons, voir la page Comparison. On peut aussi appliquer aux données numériques nombre de transformations numériques comme le logarithme… Voir la documentation. Parmi les fonctions utilitaires, signalons la fonction round qui permet d’arrondir un nombre réel à un nombre décimal ayant un nombre donné de chiffre après la virgule. round( a, 0) permet d’arrondir la variable a à l’entier de plus proche. Il existe aussi des fonctions de troncature (ceiling, floor, trunc et signif) ; voir la documentation. Chaînes de caractères Les chaînes de caractères s’écrivent entre double guillemets (") ou entre simple quote (') : a <- "c’est une chaine de caracteres" Pour plus d’informations sur les fonctions de manipulation des chaînes de caractères, voir la fenêtre ?character. Parmi ces fonctions, on peut citer : paste qui permet de concaténer plusieurs chaînes de caractères. À noter que si une des variables n’est pas une chaîne de caractères, cette variable est d’abord transformée en chaîne de caractères. Ainsi, paste( "la longueur de la variable", substitute( a), "est", length( a)) est équivalent à paste( " la longueur de la variable", as.character( substitute( a)), "est", as.character( length( a))). paste a deux arguments spéciaux à coder par nom qui son sep et collapse. nchar qui donne le nombre de caractères composant la chaîne. grep et grepl permettent de trouver un motif particulier dans la chaîne. sub et gsub permettent de remplacer un motif par un autre. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 14 R propose un certain nombre de commandes pour formater un titre, un commentaire ou une légende d’un graphique (cf. text, title, legend). Voir la page d’aide plotmath et la démonstration demo( plotmath). Supposons qu’on veut représenter sur un graphique une sensation en fonction de l’intensité d’un stimulus dans une échelle linéaire et la courbe d’ajustement à la loi de Fechner. On veut ajouter la légende suivante : 1) les points bleus correspondent aux données mesurées, 2) la courbe en trait continu rouge à la courbe d’ajustement de type s = Iα avec α=0,5. On écrira alors le texte de la légende ainsi : texte.legende <- c( "points mesurés", paste( "ajustement à la loi de Fechner", expression( "s = I"^alpha), "avec", expression( alpha), "= 0,5")) On remarquera que la deuxième ligne de la légende utilise la fonction paste pour concaténer les différentes composantes de la légende et que les expressions mathématiques sont arguments de la fonction expression ce qui permet d’interpréter les noms des lettres grecques. Si on avait écrit alpha à la place de expression( alpha), R aurait cherché une variable de ce nom. Les logiques ou booléens Les variables logiques prennent deux valeurs FALSE pour faux et TRUE pour vrai. Ces deux valeurs spéciales doivent être écrites en majuscule. Une valeur logique dont la valeur n’est pas connue prendra comme valeur NA. Elle pourra être manipulée dans des expressions logiques. Selon l’opération, le résultat pourra être FALSE, TRUE ou NA. Par exemple, pour l’opération logique ‘ou’ notée |, on a : FALSE | NA -> NA TRUE | NA -> TRUE NA | NA -> NA tandis que pour le ‘et’ logique noté &, on a : FALSE & NA -> FALSE TRUE & NA -> NA NA & NA -> NA. Exercice : Expliquez ces résultats et essayer avec d’autres opérateurs logiques comme le ‘ou’ exclusif (xor). Si une variable prend une valeur numérique (au sens de la fonction mode) non nulle, elle est assimilée à la valeur logique TRUE. Si elle vaut zéro, elle est assimilée à la fonction logique FALSE. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 15 Notons la fonction ifelse( test, yes, no) qui permet de créer de même dimension que l’objet testé test avec comme valeurs celles de yes si l’élément testé obéit à la condition, celles de no dans le cas contraire. yes et no peuvent être de longueur supérieure à 1, les valeurs seront alors prises les unes après les autres. Exemple : i <- 0 ifelse( i, "yes", "no") -> "no" >m # c’est une matrice de 3 lignes et de 2 colonnes [,1] [,2] [1,] 0 1 [2,] 0 1 [3,] 0 1 > ifelse( m, c( "a", "b", "c", "d"), "no")9 [,1] [,2] [1,] "no" "d" [2,] "no" "a" [3,] "no" "b" L’expression i == NULL n’a aucun sens et retourne toujours une variable logique de longueur nulle. Il faut utiliser is.null( i). L’expression i == NA retourne toujours la valeur NA. Il faut utiliser is.na( i). Vecteurs Constructeur Le vecteur est une suite de valeurs de même mode. Il se construit par vector( modeDesElements, longueur) où modeDesElements est une chaîne de caractères représentant n’importe quel type atomique. Il existe aussi une autre écriture ModeDesElements( longueur). Par exemple, vector( "logical", 12) est équivalent à logical( 12). On peut aussi construire un vecteur par concaténation d’éléments du même mode. La fonction réalisant la concaténation est notée c. Si un des éléments est une chaîne de caractères, alors on crée un vecteur de chaînes de caractères, les autres éléments étant convertis. Une variable atomique est assimilable à un vecteur de longueur 1. 9 En anticipant sur la suite du document, on peut traduire l’opération comme suit : mat.yes <- matrix( c("a", "b", "c", "d"), ncol=2, nrow=3) mat.no <- matrix( "no", ncol=2, nrow=3) mat.result <- matrix( NA, ncol=2, nrow=3) for( iRow in 1:3) { for( jCol in 1:2) { mat.result[ iRow, jCol] <- ifelse( m[ iRow, jCol], mat.yes[ iRow, jCol], mat.no[ iRow, jCol]) }} Programmer R - Version 1.2 (mars 2013) F. Aubry p. 16 Si les éléments sont des chaînes de caractères, elles peuvent être de longueurs différentes. Notons qu’il existe deux autres façons de construire des vecteurs de nombres. 1) par la fonction seq qui permet des pas fractionnaires positifs ou négatifs ; cette fonction a d’autres options, voir la documentation ; 2) si le pas est unitaire, par le constructeur from:to qui est équivalent à seq( from, to, by=sign( to - from)). ATTENTION a + from:to + b est équivalent à a + b + seq( from, to) (a + from):(to + b) est équivalent à seq( from + a, to + b). Accès aux éléments Les différents éléments du vecteur sont par défaut accessibles par position, le premier élément ayant la position 1. Les positions sont donc un vecteur de chiffres mis entre crochet droit : vec[1] accès au premier élément du vecteur vec ; vec[c( 1, 3, 6)] accès aux éléments en position 1, 3 et 6 : c’est un vecteur de longueur 3. On peut aussi donner des noms aux différents éléments et accéder à ces éléments par leur nom. Il y a deux manières pour cela : 1) on crée le vecteur vec puis on donne les noms aux éléments grâce à la fonction names : names( vec) <- c( …) # vecteur de chaîne de caractères de longueur length( vec) 2) on donne explicitement les noms en construisant le vecteur par la fonction de concaténation c : vec <- c( a=1, b=2, …) # la première valeur à comme nom a, la second b… L’accès aux éléments peut alors se faire par nom. On peut aussi accéder aux éléments par une condition logique ou par un vecteur de logiques. Par exemple, vec[vec > 6] renvoie un vecteur contenant tous les éléments de vec supérieurs à 6. R propose une méthode commode pour supprimer des éléments d’un vecteur : on fait précéder la liste des numéros des éléments à supprimer du signe -. Par exemple : vec[-c(10,12)] retourne un vecteur ne contenant pas les éléments de vec qui étaient en position 10 et 12. which est une fonction qui renvoie les indices des éléments répondant à une certaine condition logique. Par exemple : which( c( 2, 3, 1, 2) == 2) -> c( 1, 4) alors que c( 2, 3, 1, 2) == 2 -> c( TRUE, FALSE, FALSE, TRUE) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 17 match est une fonction qui renvoie les positions des éléments du premier vecteur dans le second et NA quand l’élément n’existe pas. %in% est un opérateur binaire qui renvoie TRUE quand il a trouvé l’élément du premier vecteur dans le second et FALSE dans le cas contraire. Les résultats de match et %in% sont des vecteurs de longueur égale à celle du premier vecteur. Opérations sur les vecteurs Toutes les opérations possibles sur les éléments d’un vecteur, donc compatibles avec le mode du vecteur, sont possibles sur les vecteurs. Les opérations sont faites élément par élément : vec.1 * vec.2 avec length( vec.2) == length( vec.1) équivaut à : vec.res <- NULL for( n in 1:length( vec.1)) vec.res <- c( vec.res, vec.1[n] * vec.2[n]) Les deux vecteurs peuvent être de longueurs différentes. R complète alors le vecteur le plus court en le répétant autant de fois que nécessaire. Si les longueurs ne sont pas multiples les unes des autres, R génère un avertissement (warning) mais effectue l’opération. Exemple vec.1 <- c( 1, 3, 5) vec.2 <- c( 2, 4, 6, 8) alors vec.res[4] vaudra vec.2[4] * vec.1[1]. R appelle cette opération recyclage (recycling). Quelques fonctions utilitaires Si les éléments du vecteur sont d’un mode ordonné (nombre, chaîne de caractères), le vecteur peut être ordonné dans le sens ascendant ou descendant par la fonction sort. Pour les nombres, l’ordre est l’ordre naturel, pour les chaînes de caractères, c’est l’ordre alphabétique. La fonction order permet de renvoyer les positions des éléments d’un vecteur selon leur ordre naturel, les indices des valeurs identiques étant donnés dans un ordre quelconque. On peut ordonner selon plusieurs vecteurs. Par exemple order( vec.1, vec.2) ordonnera les indices selon les valeurs de vec.1 et les valeurs identiques de vec.1 selon celles de vec.2 (ordre dit lexicographique). On peut ainsi trier un vecteur en fonction des valeurs d’un autre vecteur bien que cette opération ait plus d’intérêt pour les data.frame (cf. infra). La fonction unique retourne la liste des éléments du vecteur en supprimant tous les éléments dupliqués dans l’ordre où ceux-ci sont rencontrés dans le vecteur : unique( c( "a", "a")) -> "a" rep permet de répéter plusieurs fois la même valeur. sum fait la somme des éléments tandis que cumsum renvoie un vecteur faisant la somme cumulé. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 18 prod renvoie le produit des éléments tandis que cumprod renvoie un vecteur faisant le produit cumulé. scale permet de centrer (par rapport à sa moyenne) et de réduire un vecteur (division des valeurs par la déviation standard donc nouveau vecteur de variance unité). rev renvoie le même vecteur dans l’ordre inverse : rev( vec) est identique à vec[length( vec):1]. cut transforme un vecteur numérique en classes de valeurs considérées comme un facteur. rank retourne le rang des valeurs du vecteur en gérant les répétitions de valeurs et la valeur NA de différentes manières (argument na.last) : rank( c( 2.8, 1.3, 5.45, 4, 3.28, 6.12)) donne 2, 1, 5, 4, 3, 6 range renvoie le minimum et le maximum du vecteur. Parmi les autres fonctions utilitaires on peut citer : mean moyenne des éléments median médiane des éléments quantile calcul des quantiles sd écart type des éléments au sens d’un échantillon, c’est-à-dire : sd ( vec) = var ... 1 length ( vec ) ∑ (vec[i] − mean( vec) )2 N − 1 i =1 variance (carré de sd) La fonction all renvoie TRUE si tous les éléments du vecteur sont vrais, c’est-à-dire obéissent à la même condition. Par exemple, si vec.1 <- seq( 2, 12, by=2) all( vec.1 %%2 == 0) -> TRUE tandis que all( vec.1 %%4 == 0) -> FALSE La fonction any renvoie TRUE si au moins un élément remplit la condition. Nombreuses de ces fonctions (et d’autres fonctions de R) ont un argument spécial commençant généralement par la chaîne na. qui spécifie le comportement de la fonction face aux valeurs NA. Exemple : mean( c( sample( 12, replace=TRUE), NA)) renvoie NA mean( c( sample( 12, replace=TRUE), NA), na.rm=TRUE) est équivalent à mean( sample( 12, replace=TRUE)). Facteurs Le facteur (factor) est l’objet qui représente les facteurs des analyses statistiques. C’est donc un vecteur spécial applicable aux données qualitatives. Dans R, un facteur peut être constitué d’un ensemble de chaînes de caractères ou de nombres qui seront donc les niveaux du facteur. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 19 Constructeur Le constructeur factor a 5 arguments dont un seul, le premier, est obligatoire. Sa signature est : factor( x=character(), levels, labels=levels, exclude=NA, ordered=is.ordered( x)) x est le vecteur de données servant de facteur, levels est un vecteur optionnel qui contient les niveaux du facteur, labels est un vecteur optionnel des étiquettes des nivaux, sa longueur doit être celle du nombre de niveaux (i.e., la longueur du vecteur levels), exclude est un vecteur des valeurs exclues comme niveaux, par défaut la valeur NA (niveau non disponible) est exclue, ordered est une booléen signalant si le facteur est ordonné au nom. levels indique le codage interne du facteur qui peut être sous forme de nombre (par exemple pour faciliter l’ordonnancement, tandis que labels indique l’étiquette qui sera affichée. Quelques exemples : instruction données levels factor( 1:3) 123 123 factor( 1:3, levels=c( 3:1)) 123 321 factor( 1:3, levels=c( 3:1), labels=c( "a", "b", "c")) cba abc factor( c( "1", "2", "3"), levels=c(3:1), labels=c( "a", "b", "c")) c b a abc factor( c( "c", "b", "a"), levels=c(3:1), labels=c( "a", "b", "c")) <NA> <NA> <NA> a b c Les exemples ci-dessus montrent qu’il faut être très prudent quand on convertit un vecteur en facteur, notamment pas l’instruction as.factor, et vice-versa. En effet : as.character( factor( c( "1", "2", "3"), levels=c(3:1), labels=c( "a", "b", "c"))) donnera c( "c", "b", "a") alors que unclass( factor( c( "1", "2", "3"), levels=c(3:1), labels=c( "a", "b", "c"))) donnera c( 3, 2, 1) avec is.integer( ) -> TRUE. Si on ordonne les niveaux (ordered=TRUE), l’ordonnancement sera celui donné par l’ordre des niveaux (argument levels). De plus, pour les facteurs ordonnés, la fonction order a un sens. De même il est possible d’utiliser les opérateurs de conversions : df$TPS <- factor( df$TPS, orderd=TRUE, levels=c( "m00", "m06", "m12", "m24", …)) L’instruction df$TPS[n.x] < df$TPS[n.y] a un sens. De même : limite <- "m18" df$TPS[n] >= limite. Notons que lors des analyses statistiques, le comportement de la procédure statistique face à des individus dont le niveau du facteur est inconnu (NA) est contrôlé par l’argument na.action de la procédure qui peut être mis à une valeur par défaut pour la session grâce à la commande options( na.action=...) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 20 ou qui peut être spécifié par l’argument, généralement noté na.action, de chacune des procédures d’analyse. Contrastes L’option options( contrasts=...) donne le comportement par défaut des contrastes des facteurs non ordonnés et ordonnés. Ce comportement peut être modifié localement dans la procédure statistique grâce à l’argument contrasts de la procédure. On peut aussi modifier le comportement du facteur lui-même en lui associant un contraste donné par la fonction C (ATTENTION à la majuscule qui différencie cette procédure de celle de concaténation des vecteurs c). La notion de contraste est très important dans les analyses statistiques. En effet, le nombre de paramètres à estimer est toujours supérieur au nombre de degrés de liberté du problème. Par exemple, si on fait une Anova à un facteur F1 ayant deux niveaux (F1.1 et F1.2), on peut vouloir estimer : y j = δ 1.1 ( j ) ϕ1.1 + δ 1.2 ( j ) ϕ1.2 + ε j avec δ 1.i ( j ) = 1 si le niveau du facteur associé à l’individu j est F1.i, 0 autrement ; ou y j =ϕ 0 +δ 1.1 ( j ) ϕ1.1 + δ 1.2 ( j ) ϕ1.2 + ε j La première écriture semble plus intéressante car elle conduit à n’estimer que deux paramètres alors que le problème à deux degrés de liberté (les deux niveaux du facteur) tandis que la seconde est sur-paramétrée puisqu’il faut estimer trois paramètres. On pose alors comme contrainte ϕ1.1 + ϕ1.2 = 0 ce qui ramène au problème de l’estimation de deux paramètres. De plus, cette paramétrisation appelée sigma-restreint dans la littérature statistique impose que : ϕ0 = mean( y). Supposons maintenant que le modèle comporte deux facteurs F1 et F2 à deux niveaux avec une interaction, la première écriture donnera : y j = δ 1.1 ( j ) ϕ1.1 + δ 1.2 ( j ) ϕ1.2 + δ 2.1 ( j ) ϕ 2.1 + δ 2.2 ( j ) ϕ 2.2 + ∑ ∑δ k∈F 1l∈F 2 1.k ( j ) δ 1.l ( j ) ϕ12.kl +ε j ce qui conduit à 8 paramètres à estimer (les deux liés au facteur F1, les deux liés au facteur F2 et les 4 liés aux interaction F1.1:F2.1, F1.1:F1.2, F1.2:F2.1 et F1.2:F1.2 alors qu’il n’y a que quatre degrés de liberté. On est donc dans un modèle sur-paramétré. Il faut donc faire un changement de variables β et imposer des contraintes pour revenir à quatre paramètres. La seule contrainte admissible est d’imposer qu’un des nouveaux paramètres lié à l’effet principal d’un des facteurs soit nul (par exemple β 2.1 ) et que trois des quatre paramètres d’interaction le soient aussi. De ce fait, cette écriture qui semblait la plus intéressante avec un seul facteur conduit à déséquilibrer l’estimation des paramètres au profit d’un facteur et à perdre la symétrie de l’écriture. Pour rétablir l’équilibre, le modèle est réécrit : yj = β 0 + δ 1.1 ( j ) β 1.1 + δ 1.2 ( j ) β1.2 + ε j avec comme contrainte qu’un des paramètres de chaque facteur soit nul. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 21 Ces contraintes de nullité conduisent donc à la construction d’une matrice du schéma d’analyse (design matrix) ayant des colonnes nulles. Ceci rend difficile certains tests, d’autant que les paramètres estimés, quand il y en plus de deux, ne sont pas indépendants. Cette représentation conduit à ce que R appelle les contrastes de type traitement qui sont implantés sous trois formes : contr.treatment( n, base=1) : les paramètres estimés seront alors nommés par la concaténation du nom du facteur et de celui du niveau, par exemple FN1. Le paramètre base est le numéro du niveau servant de référence. contr.SAS( n) qui est équivalent à contr.treatment avec base ayant pour valeur le nombre de niveaux. Par défaut, R utilise contr.treatment pour les facteurs non ordonnés sauf si on modifie options( contrasts=...). La seconde écriture conduit à : y j = β 0 + δ 1.1 ( j ) β1.1 + δ 1.2 ( j ) β 1.2 + δ 2.1 ( j ) β 2.1 + δ 2.2 ( j ) β 2.2 + ∑ ∑δ k∈F 1l∈F 2 1. k ( j ) δ 1.l ( j ) β 12.kl +ε j avec : β1.1 + β1.2 = 0 β 2.1 + β 2.2 = 0 β12.11 + β12.12 = β12.21 + β12.22 = β12.11 + β12.21 = β12.12 + β12.22 = 0 β 0 = mean( y ) ce qui définit les quatre paramètres, les deux premières contraintes signifiant que les deux niveaux des facteurs se situent à égale distance de la moyenne et la troisième, que la moyenne est le centre d’un carré dont les quatre coins représentent les interactions. De ce fait, la représentation reste identique quelque soit le nombre de niveaux, de facteurs et les interactions et conserve les symétries. R propose trois implantations de ce type de paramétrisation sigma restreint10 : contr.sum qui calcule la différence au premier niveau de chaque facteur, tout en conservant les autres contraintes énoncés ci-dessus ; le problème principal est la non indépendance des estimations même pour les groupes équilibrés ; contr.helmert plus difficile à interpréter mais qui a l’avantage de l’indépendance des estimations pour les groupes équilibrés ; contr.poly qui n’a de sens que pour les facteurs ordonnés qui peuvent être interprétés comme des classes d’une variable continue, également distribuées ce qui conduit aux analyses de tendance ; en effet, ce contraste peut être assimilé à une discrétisation d’une régression polynomiale sur le variable continue sous-jacente au facteur ; ce contraste conduit à l’indépendance des estimations pour les groupes équilibrés. Notons qu’on peut aussi coder les contrastes treatment, sum et helmert, contr.Treatment, contr.Sum et contr.Helmert ce qui donnera dans des résultats des analyses statistiques plus faciles à lire. 10 Pour les facteurs ayant plus de deux niveaux et quelque soit le type de paramétrisation, les schémas des effets principaux et des interactions sont plus compliqués mais le principe reste le même, notamment la coordonnée à l’origine µ est la grande moyenne. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 22 Note sur le calcul des paramètres d’un modèle linéaire dans R Prenons l’exemple d’une analyse à un seul facteur. L’objectif est d’estimer les valeurs moyennes de la variable dépendante dans chacun des groupes formé par un niveau du facteur (ϕ = {ϕ {1} , L}) et de tester leur égalité. Estimation des coefficients R comme tous les autres logiciels statistiques ne calcule pas directement les coefficients ϕ mais des coefficients β qui se déduisent de ϕ par l’équation : ϕ =C β où C est la matrice des contrastes aussi bien pour les effets principaux que pour les interaction. On a toujours : β 0 = ϕ 0 , c’est-à-dire que le premier coefficient est toujours l’ordonnée à l’origine même quand celle-ci est forcé à zéro. Ainsi, l’équation à résoudre n’est plus : Y=Xϕ où X est la matrice du schéma (design matrix) mais Y=XCβ On peut revenir facilement aux coefficients d’intérêt ϕ par l’équation ci-dessus (ou utiliser la fonction dummy.coef) ou calculer les coefficients β ( 2) dérivés d’un contraste C(2) à partir des coefficients β (1) calculés à partir d’un contraste initial C (1) par l’équation suivante : β ( 2) = C ( 2) C (+1) β (1) C (+1) l’inverse généralisée de Moore-Penrose de C (1) (calculable par la avec fonction ginv de R, cf. infra). L’équation ci-dessus est plus générale car il suffit de remplacer C ( 2) par la matrice d’intérêt pour calculer n’importe quel paramètre à condition que la matrice ait autant de colonne que de paramètres ϕ du modèle. Exemple : mettons nous dans le cas d’une Anova à deux facteurs F1 et F2 à deux niveaux chacun. On veut estimer la moyenne des différents niveaux de chacun des facteurs hors interaction, c’est-à-dire par exemple pour le niveau 1 du facteur 1, on veut calculer : υ1.1 = ϕ 0 + ϕ1.1 donc en ne prenant pas en compte les termes d’interactions ϕ12.11 et ϕ12.12 On voit alors facilement que : υ 1 1 0 υ1 = 1.1 = υ1.2 1 0 1 Programmer R - Version 1.2 (mars 2013) F. Aubry β [0] 0 C β [0] 1. M p. 23 Statistique de Wald Quand on a estimé de nouveaux coefficients par l’équation ci-dessus, on désire connaître leur variance comme l’analyse en elle-même donne la variance des coefficients β initiaux (fonction summary). On calcule alors les statistiques dites statistiques de Wald par la formule suivante : ( var( ϑ ) = (Qβ ) Q var( β ) Q T T avec ) (Qβ ) −1 ( A)T , la transposée de la matrice A (cf. infra, fonction t), A −1 , l’inverse de la matrice A (cf. infra, fonction ginv puisque l’inverse généralisée et l’inverse d’une matrice carrée inversible sont identiques). Sous l’hypothèse Qβ = 0 , var( ϑ ) tend vers un χ2 à n degrés de liberté où n est le rang de la matrice Q (composante rank du résultat de la fonction qr appliquée à la matrice). Contrastes des interactions Sans entrer dans les détails du calcul, le contraste associé à l’interaction entre 2 facteurs F1 et F2 (dans cet ordre, donc F1:F2 pour R) se calcule par : C12 = C 2 ⊗ C1 avec ⊗ le produit de Kronecker. Plus généralement, l’interaction d’ordre n entre les facteurs F1 à Fn vaut : C1...n = C n ⊗ K ⊗ C1 Dans R, la fonction qui calcule ce type de produit s’appelle kronecker. Matrice des contrastes en cas d’interactions Pour exemple, je donnerai le cas de 2 facteurs, l’extension à plus de deux facteurs et à des interactions d’ordres supérieurs étant évident : effets principaux dans l’ordre des facteurs, interactions d’ordre 2 dans l’ordre lexicographique (i.e., F1:F2, F1:F3, ..., F2:F3, F2:F4, ... F3:F4...) puis la même stratégie pour les interactions d’ordres supérieurs. β0 β1. β2. β12. | Origine |1 |0 |0 |0 | F1 |0 | c1 <- C11( F1) |0 |0 | F2 |0 |0 | c2 <- C(F2) |0 | Interaction |0 |0 |0 | c2 ⊗ c1 Dans le document intitulé ‘Utiliser R et Statistica - notes pratiques’, je détaille comment construire effectivement la matrice des contrastes avec R. 11 C( F) dénote ici le contraste associé au facteur F. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 24 Utilitaires nlevels levels reorder relevel nombre de niveaux du facteur liste des niveaux modification de l’ordre des niveaux d’un facteur modifie le niveau de référence (utile pur contr.treatment et contr.sum) Matrices Constructeur Il existe plusieurs moyens pour construire des matrices. La plus simple est l’utilisation de la fonction matrix qui permet de donner une valeur initiale ainsi que les dimensions. La valeur initiale peut être une valeur atomique. Dans ce cas, toutes les cellules de la matrice auront cette valeur. Ce peut aussi être un vecteur. S’il est de longueur insuffisante, R recycle le vecteur. Par défaut, R remplit la matrice colonne par colonne sauf si l’argument byrow vaut TRUE ce qui conduit à un remplissage ligne par ligne. On peut aussi restructurer un vecteur en lui donnant le nombre de lignes et le nombre de colonnes de la matrice résultante par la fonction dim qui sert aussi à récupérer le nombre de lignes et de colonnes d’une matrice. Il faut alors que la longueur du vecteur soit égale au nombre de cellules de la matrice, sinon on a une erreur : vec <- 1:6 dim( vec) <- c( 2, 3). ncol revoie le nombre de colonnes tandis que nrow renvoie le nombre de lignes. On peut rajouter des colonnes à une matrice sous forme d’un vecteur de longueur le nombre de lignes de la matrice ou d’une matrice ayant le même nombre de lignes, grâce à la fonction cbind ou des lignes (vecteur de longueur le nombre de colonnes ou matrice ayant le même nombre de colonnes) grâce à rbind. Les lignes et les colonnes d’une matrice peuvent recevoir des noms : 1) par l’argument dimnames du constructeur matrix ; 2) par les fonctions rownames et colnames qui permettent aussi de récupérer ces noms. Exemple : rownames( vec) <- c( "ligne.1", "ligne.2") Sélectionner des éléments d’une matrice Les cellules de la matrice peuvent être accédées par position ou par nom. Les règles sont les mêmes que pour un vecteur, y compris celles pour supprimer des éléments qui seront ici soit des lignes entières, soit des colonnes entières. Pour accéder à différentes colonnes d’une ligne complète, on écrira : mat[,colonnesALire] # pas de valeurs pour la ligne et pour une colonne complète mat[lignesALire,]. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 25 colonnesALire (resp. lignesALire) peut être un vecteur de numéros ou de noms de colonnes (resp. lignes). mat[lignesALire,colonnesALire] permet d’accéder à une sous matrice. On peut spécifier les lignes et colonnes auxquelles on veut accéder comme celle que l’on désire supprimer. Dans ce cas, on fait précéder ces numéros du signe de soustraction (-). Si les lignes et les colonnes ont reçu des noms, on peut accéder par nom à ces lignes ou colonnes, au lieu d’y accéder par position. On peut aussi indicer logiquement les éléments d’une matrice : m > 0 donnera une matrice logique de même dimension que m ; m[m > 0] donnera un vecteur contenant la liste des éléments positifs de m. which donnera la position des éléments de la matrice répondant à la condition logique. Si l’argument arr.ind est FALSE (défaut), which retourne les positions de la matrice linéarisée (équivalent à which( as.vector( m)…), sinon, il retourne les véritables indices. On peut aussi donner un tableau d’indices dont le nombre de colonnes est 2. Ainsi identical( m[which( m > 0, arr.ind=TRUE)], m[m > 0]) est TRUE. Opérations Les opérations +, -, *, / et autres opérations mathématiques comme le modulo (%%) s’appliquent aux matrices. Les opérations sont cellule par cellule et les deux matrices doivent être de mêmes dimensions. Par exemple : mat.result <- mat.1 * mat.2 donne une matrice dont les cellules valent mat.result[i,j] <- mat.1[i,j] * mat.2[i,j]. La multiplication de deux matrices est codée par l’opérateur %*%. determinant et sa version simplifiée det calculent le déterminant de la matrice. La transposition d’une matrice se fait grâce à la fonction t. Le calcul de l’inverse d’une matrice se fait par la fonction ginv. Cette fonction permet de calculer l’inverse d’une matrice carrée inversible aussi bien que l’inverse généralisée d’une matrice rectangulaire. Notons que R propose une fonction particulière solve pour résoudre l’équation linéaire Ax = y où A est une matrice carrée : x <- solve( A, y) solve( A) calcule l’inverse de A. Si A est inversible, le résultat est identique à celui obtenu par ginv, sinon solve donne une erreur. svd calcule la décomposition en valeurs singulières d’une matrice quelconque. Soit B une matrice, on a donc : B <- svd( B)$u %*% diag( svd( B)$d) %*% t( svd( B)$v) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 26 eigen calcule les vecteurs propres et les valeurs propres d’une matrice carrée. Utilitaires Pour calculer la somme ou la moyenne de colonnes ou de lignes : colSums, colMeans, rowSums, rowMeans. Ce sont des implantations particulières de la fonction apply qui permet de retourner un tableau ou une liste de valeurs en appliquant une fonction donnée aux marges d’une matrice (argument FUN). Par exemple, si on désire calculer la déviation standard des colonnes de la matrice mat, on écrira : apply( mat, 2, sd) On peut aussi passer des arguments à la fonction. Supposons que mat soit le résultat d’un bootstrap d’une Anova, les lignes sont alors les paramètres de l’Anova (effets principaux, interactions…) et les colonnes les résultats pour les 10000 passages du bootstrap. On veut alors obtenir les quantiles à 2,5%, 25%, 50% (médiane), 75% et 97,5% et utiliser l’algorithme de type 8 de la fonction quantile. On utilisera alors les … pour passer les arguments à la fonction quantile : apply( mat, 1, quantile, probs=c( 0.025, 0.25, 0.50, 0.75, 0.975), type=8). Si on appliquait directement les fonctions applicables aux vecteurs aux matrices, les calculs comme la somme, le produit, la moyenne... ne se feraient pas ligne à ligne ou colonne à colonne mais sur la matrice entière considérée comme un vecteur. Notons que apply( mat, c( 1, 2), … renvoie une matrice de même dimension que mat en appliquant la fonction FUN à chaque élément de la matrice initiale. sweep permet d’appliquer aux éléments d’une matrice une fonction mettant en jeu des statistiques récapitulatives. Exercice : On désire centrer les colonnes d’une matrice (par exemple mat <- matrix( runif( 200), ncol=10)) sur leur médiane. 1) écrire le code sans utiliser ni apply ni sweep ; 2) écrire le code en utilisant apply ; 3) écrire le code en utilisant sweep. rowsum permet de calculer des sommes dans des colonnes en fonction d’un facteur de groupement. rowsum est une implantation particulière d’une fonction plus générale aggregate qui peut prendre comme premier argument une matrice et comme facteur d’agrégation un ensemble de facteurs de groupement. dim permet d’obtenir les dimension de la matrice. Il ne faut pas confondre cette fonction avec la fonction length. En effet : mat <- matrix( 0, ncol=10, nrow=20) dim( mat) donnera 20, 10 Programmer R - Version 1.2 (mars 2013) F. Aubry p. 27 length( mat) donnera 200, le nombre d’éléments de la matrice Tableaux Les tableaux (constructeur array) sont une extension aux dimensions supérieures à deux des matrices. Ce qui a été dit des matrices est applicable aux tableaux. La sélection des éléments se fait de la même manière et apply est aussi applicable, l’argument MARGIN prenant alors le numéro (ou les numéros) des dimensions à considérer. Lire la documentation pour avoir des détails. À noter que nrow donne la première dimension et ncol la seconde. L’extension de la transposition t aux tableaux s’appelle aperm. Séries temporelles Pour mémoire, je citerai la classe des séries temporelles construites par le constructeur ts et qui possède des méthodes propres, par exemple pour aligner les axes, changer l’échantillonnage… Tables Les tables (constructeur table) sont des matrices particulières pour représenter les tableaux de contingences à deux ou plus de deux entrées. Elles ont des méthodes spécifiques. Voir la documentation. Listes Les vecteurs permettent de regrouper des éléments de même mode dans une structure ordonnée. Les listes au sens de R font la même chose sauf que la structure peut contenir des éléments de classes différentes qui peuvent être eux-mêmes des listes. Constructeur R propose un constructeur peu utilisé à partir de la fonction vector : vector( "list", nbElements) qui montre la parenté entre le notion de liste de R et celle de vecteur. Le constructeur le plus utilisé est list qui a un nombre quelconque d’argument qui seront les éléments de cette liste rangé dans leur ordre d’apparition. De plus ce constructeur permet de nommer les éléments comme pour les vecteurs, en utilisant la construction nom=valeur. Les éléments d’une liste peuvent être nommés a posteriori par la fonction names : names( ma.list) <- c( "nomElement.1", "nomElement.2", ... Symétriquement, ils peuvent être récupérés sous forme d’un vecteur de chaînes de caractères par la fonction names. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 28 Accès aux éléments de la liste On peut vouloir extraire une sous-liste, c’est-à-dire créer une nouvelle liste ne contenant pas tous les éléments de la liste initiale ou accéder à un élément particulier de la liste. Sous liste La sous-liste d’extrait par l’opérateur noté par les parenthèses droites [ ]. L’argument entre ces parenthèses peut être un vecteur de nombre, les positions des éléments de la liste initiale composant la nouvelle liste, ou de noms des éléments si ceux-ci ont été nommés : li <- list( x="z", y=2+3, q="e") li[c(1, 3)] est alors équivalent à li[c( "x", "q")] et donc a list(x="z", q="e") Élément d’une liste On ne peut extraire qu’un élément à la fois puisque les composants peuvent être de différentes classes. Il existe deux façons d’accéder à l’élément : 1) par utilisation de l’opérateur [[ ]] qui contient aussi bien le nom que la position : li[[1]] ou li[["x"]] -> "z" 2) par utilisation de l’opérateur $ concaténé au nom de la liste quand les éléments sont nommés. Par exemple, on peut écrire li$x pour accéder à l’élément de nom x de la liste li cidessus. Extension d’une liste Il existe de nombreuses façons pour rajouter des éléments à une liste - par ajout de liste : . concaténation de listes par la fonction c . ajout de sous-listes position <- c( ) # un vecteur li[position] <- list( …)# liste de longueur celle du vecteur position - par ajout d’élément . par adressage li[[nouvellePosition]] <- valeur . par nom li$nom <- valeur. Utilitaires La fonction unlist permet d’aplatir la liste et retourne un vecteur. Si un élément est une liste, elle travaille récursivement jusqu’à arriver à des valeurs atomiques. Si un des éléments est une chaîne de caractères, le résultat est un vecteur de chaînes de caractères. Il existe une fonction utilitaire appelée lapply permettant d’appliquer itérativement une fonction aux éléments d’une liste. Voir la documentation pour la fonction et ses dérivées. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 29 Date R permet de manipuler des dates et des heures et de transformer des chaînes de caractères en objets de classe Date ou date POSIX et vice-versa. Voir la documentation à partir de la page d’aide de as.Date. Data frame Construction et accès aux éléments Le data.frame est au cœur de R puisque c’est ainsi qu’est codé tout tableau de valeurs sur lequel on effectue des analyses statistiques. Il est basé sur les listes mais avec la contrainte que tous les éléments de la liste soient des vecteurs de valeurs atomiques de même longueur. Ces éléments constituent les colonnes du data.frame. De plus, l’accès aux éléments utilise aussi les parenthèses droites ([ ]) mais avec la signification qu’elles ont pour les matrices. Le constructeur standard est la fonction data.frame avec la liste des colonnes qui peuvent être nommées par la construction nomColonne=valeur. Les colonnes peuvent être nommées a posteriori par la fonction names qui reçoit un vecteur de chaîne de caractères. Les lignes peuvent être nommées explicitement par l’argument row.names du constructeur ou a posteriori en utilisant la fonction row.names (noter la présence du point dans le nom au contraire de la fonction ayant le même objectif pour les matrices). L’accès aux éléments du data.frame par l’opérateur [ ] suit les mêmes règles que pour les matrices. On peut aussi accéder à une colonne entière par l’opérateur $. On peut retirer des lignes ou des colonnes d’un data.frame en faisant précédent leur numéro de position du signe moins. On peut rajouter des lignes grâce à la méthode rbind tandis que l’ajout de colonnes se fait par cbind. transform permet de rajouter une colonne à un data.frame dont la valeur résulte d’un calcul sur les autres colonnes. Facteurs L’option stringsAsFactors ou l’argument stringsAsFactors du constructeur permettent d’automatiser la conversion des chaînes de caractères en facteur. Si stringsAsFactors est TRUE et qu’on veut inhiber ce comportement pour une colonne, il faut alors utiliser la fonction I : t <- data.frame( g=c( "t"..., y=c( "z"..., stringsAsFactors=TRUE) alors les colonnes g et y seront des facteurs tandis que si on écrit : t <- data.frame( g=c( "t"..., y=I( c( "z", ...), stringsAsFactors=TRUE) seule la colonne g contiendra un facteur. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 30 Quand on a retiré un certain nombre de lignes d’un data.frame, il est possible que certains niveaux d’un facteur donné ne soient plus utilisés. Cette absence pourra conduire à une erreur puisque la procédure trouvera des cellules vides, celles correspondant aux niveaux qui ne sont plus utilisés. En effet, les procédures d’analyse se basent non pas sur les niveaux effectifs mais sur l’attribut levels de la colonne (i.e., du facteur). Il est donc nécessaire de remettre à jour cet attribut avant l’analyse. R propose deux méthodes pour cela 1) mesDonnees$F <- factor( mesDonnees$F) 2) mesDonnées <- droplevels( mesDonnees). La fonction drop quant à elle supprime les dimensions d’un tableau (array) qui n’a qu’un niveau. Utilitaires Il peut être intéressant de réordonner les lignes d’un data.frame en fonction d’autres données, par exemple de certaines colonnes du data.frame afin de regrouper sur des lignes adjacentes toutes les informations du même patient (colonne ID) et le trier dans l’ordre de leur date d’acquisition (colonne ACQ_DATE). Cela se fait simplement : df <- df[order( df, df$ID, df$ACQ_DATE),] On peut aussi vouloir ajouter une ou plusieurs colonnes dérivées des autres colonnes. L’utilisation de cbind est possible est possible mais demande de référencer exactement les arguments qui interviennent. On peut aussi utiliser la fonction transform qui permet de rajouter en une seule opération plusieurs colonnes en ne référençant que les noms des colonnes impliquées. Par exemple, on dispose dans le data.frame df d’une colonne DATE_NAISSANCE sous la forme "jj/mm/yyyy" avec mm le numéro du mois et d’une colonne DATE_EXAMEN selon le même format. On veut alors ajouter une colonne AGE qui contiendra l’âge à la date de l’examen. Les deux solutions12 sont : 1) df$AGE <- as.numeric( format( df$DATE_EXAMEN, "%Y")) as.numeric( format( df$DATE_NAISSANCE, "%Y")) 2) df <- transform( df, AGE= as.numeric( format( DATE_EXAMEN, "%Y")) as.numeric( format( DATE_NAISSANCE, "%Y")) Certaines procédures d’analyse ne savent pas gérer les valeurs inconnues (codées NA par R), d’autres ont un comportement tel que le résultat de l’analyse dépend de la présence ou non de ces valeurs. Il est donc nécessaire de contrôler les individus ayant de telles valeurs. Souvent, les procédures d’analyse possèdent un argument na.action pour cela. On peut aussi créer des data.frame dans lesquels on a contrôlé l’existence de telles valeurs. R propose quatre fonctions à ce propos : na.fail( object, …) retourne une erreur si l’objet contient des valeurs NA na.pass( object, …) retourne l’objet inchangé 12 Il faut d’abord extraire l’année de la chaîne de caractères représentant la date (fonction format) puis convertir la chaîne de caractères représentant l’année en nombre. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 31 na.omit( object, …) supprime toutes les lignes d’object contenant au moins une valeur NA et retourne cet objet nettoyé na.exclude( object, …) se comporte comme na.omit sauf lors de l’extraction des résidus d’une analyse ou la prédiction de nouvelles valeurs quand on veut tenir compte de l’existence de valeurs NA. On peut vouloir appliquer systématiquement à certaines colonnes une fonction particulière. Pour cela on dispose de la fonction sapply (ou lapply) qui travaille alors colonne par colonne en considérant le data.frame comme une liste, c’est-à-dire que sapply passe à la fonction référencée par l’argument FUN, la colonne entière. Par exemple, on veut centrer et réduire toutes les colonnes contenant des valeurs numériques : col.num <- sapply( df, FUN=is.numeric) sapply( df[,col.num], FUN=function( x){ ( x - mean( x)) / sd( x)})13 La première instruction retourne TRUE pour les colonnes numériques et FALSE pour les autres tandis que la seconde applique l’opération de centrage et de réduction sur les colonnes numériques en utilisant une fonction anonyme14. Sous certaines conditions, apply et sweep sont applicables aux data.frame. Exercice : Soit un data.frame d.f <- data.frame( RID=paste( "sujet", 1:12, sep="."), matrix( rbeta( 240, 3, 5), nrow=12, ncol=20, dimnames=list( NULL, paste( "test", 1:20, sep=".")))) A) on veut centrer les résultats des tests sujet par sujet, sur leur moyenne ; - écrire le code sans utiliser sweep ni boucles ; - écrire le code en utilisant sweep et vérifier qu’on obtient les mêmes résultats dans les deux cas toujours sans boucles for. B) faire la même chose mais colonne par colonne. Supposons que le tableau de données contient une colonne TEST qui référence le type de test effectué et deux colonnes TC et ERREUR qui recense le temps pris par le patient pour effectuer le test et le nombre d’erreurs. On voudrait connaître le temps moyen et le nombre d’erreurs faites en moyenne par test et tranche d’âge. On dispose alors de la fonction aggregate dont l’argument de regroupement by doit être de type list : df <- transform( df, CLASSE_AGE=cut( AGE, seq( 0, 119, by=10)) aggregate( df[,c( "TC", "ERREUR”)], by=list( df$TEST, df$CLASS_AGE), FUN=mean) La première instruction crée les classes d’âge allant de 10 en 10 tandis que la seconde calcule les moyenne en ordonnant le data.frame résultant d’abord par TEST (la colonne sera étiquetée Group.1) puis par classe d’âge (colonne étiquetée Group.2). La colonne x recevra le tableau ou le data.frame des valeurs calculées, si la fonction FUN retourne un vecteur. Si l’objet sur lequel porte l’agrégation (1er argument) comprend plusieurs colonnes, par exemple sélectionnées à partir d’un data.frame (cf. supra), d’une matrice ou construit par 13 Il serait plus rapide d’utiliser la fonction scale (sapply( df[,col.num], FUN=scale) ). J’ai codé ainsi pour illustration de l’utilisation des fonctions anonymes. 14 Cf. infra, le codage des fonctions Programmer R - Version 1.2 (mars 2013) F. Aubry p. 32 cbind, alors le data.frame de retour comprendra autant de colonnes que l’objet initial a de colonnes, en plus des colonnes Group.n des facteurs de groupement. Les noms seront donnés soit par les noms de colonnes, soit par défaut V1, V2… Il peut aussi être nécessaire de restructurer un tableau de données car son format n’est pas compatible avec celui utilisé par la procédure d’analyse. Le premier cas de figure est de mettre sur une seule ligne les données pour un sujet à un âge donné (du tableau df vers le tableau df.ligne) : ID AGE TC.TEST_1 ERREUR.TEST_1 TC.TEST_2 ERREUR.TEST_2... Le second cas de figure consiste à partir d’un tableau en ligne comme celui présenté précédemment (tableau df.ligne) pour en faire un tableau de type le tableau df. On peut utiliser la fonction reshape pour gérer les deux cas de figure. Cas 1 : df.ligne <- reshape( df, timevar="TEST", idvar=c( "ID", "AGE", "CLASSE_AGE"), direction="wide", sep=".") timevar nom de la colonne à mettre en ligne idvar nom des colonnes de regroupement, donc ne variant pas et à conserver direction direction de la restructuration, ici en format "wide" sep le séparateur entre le nom de la variable et celui du niveau de la variable timevar (par défaut, c’est ".") Cas 2 : df <- reshape( df.ligne, direction="long", varying=3:..., sep=".") direction format "long" varying numéros ou noms des colonnes à éclater, les autres seront répétées pour chaque occurrence, sep séparateur pour déterminer d’une part le nom de la variable résultat et d’autre par celui de la variable d’indiçage. La convention est nomVariableResultat (cf. supra TC ou ERREUR) suivi du séparateur suivi de la variable d’indiçage (ou temporelle). S’il y a plusieurs variables à restructurer (ici 2 : TC et ERREUR), on doit utiliser les mêmes noms pour l’indiçage (e.g. TEST_1, TEST_2...) Pour le premier cas on peut aussi utiliser la fonction melt du package reshape. Jointure de deux data.frame J’emploie ici la terminologie des bases de données relationnelles pour qualifier cette opération de fusion entre deux data.frame. Le problème que résout la fonction merge est assez courant quand on dispose au cours d’une expérience de données sous forme de tableaux venant de systèmes d’acquisition différents ou acquis à des moments différents. Supposons deux tableaux, le premier d.psycho recense des résultats de tests neuropsychologiques et possède les colonnes suivantes : ID identifiant du sujet, VISITE identifiant de la visite (pour le suivi) numérotée de 1 à n, ... résultats des tests Programmer R - Version 1.2 (mars 2013) F. Aubry p. 33 Le second d.bio recense des résultats d’analyses biologiques et possède les colonnes suivantes : BID identifiant du sujet VISITE identifiant de la visite (pour le suivi) numérotée de 1 à n, ... résultats des analyses. On suppose que les identifiants des sujets et des visites sont codées identiquement. Le problème à résoudre est de fusionner les deux tableaux afin d’avoir pour un sujet donné et une visite donnée, à la fois les résultats des tests neuropsychologiques et des analyses biologiques. L’utilisation de la fonction merge pour réaliser cette opération est aisée, on lui donne comme premier argument la référence au premier data.frame, comme second argument, la référence au second data.frame puis on lui donne les noms des colonnes qui serviront à la jointure, d’abord celles du premier data.frame (argument by.x) et leur correspondant, position par position, dans le second data.frame (argument by.y). On obtient alors l’appel : merge( d.psycho, d.bio, by.x=c( "ID", "VISITE"), by.y=c( "BID", "VISITE")) Si les colonnes servant à la fusion avaient les mêmes noms, l’appel aurait été plus simple en utilisant l’argument by : merge( d.psycho, d.bio, by=c( "ID", "VISITE")) On peut aussi vouloir changer les noms des colonnes afin de donner les mêmes noms aux colonnes servant à la jointure : col.bio <- match( c("BID", "VISITE"), names( d.bio)) names( d.bio)[col.bio] <- c( "ID", "VISITE") On peut se trouver confronté au fait que les colonnes ne sont pas codées de la même manière, par exemple dans d.psycho, les visites seront numérotées de 1 à n et dans d.bio, elles seront identifiées par un code alphanumérique exprimant le nombre de mois après la 1ère visite : "0m", "6m" ... Il faut donc transcoder une des deux colonnes avant de fusionner les tableaux. Le problème sera simple si aucune des colonnes n’est un facteur, il peut être plus complexe dans le cas de facteurs. Dans ce dernier cas et pour éviter tout problème, on commence par transformer la colonne du facteur en chaîne de caractères par l’instruction suivante : as.character( d.x$facteur) puis on rétablit le facteur après l’opération de fusion. On commence donc d’abord par tester si les colonnes sont des facteurs : fac.psycho <- sapply( d.psych, FUN=is.factor) fac.bio <- sapply( d.bio, FUN=is.factor) col.facteurs <- any( c( fac.psycho[c( "ID", "VISITE")], fac.bio[c("BID", "VISITE")])) if( col.facteurs) # au moins une colonne est un facteur Ensuite on transforme les valeurs dans les nouvelles valeurs. Une méthode efficace est de baser la transformation sur l’utilisation de la fonction switch qui prend comme premier argument la variable de longueur 1 à transformer puis une série d’arguments de type ancienneValeur=nouvelleValeur. À noter le l’argument nouvelleValeur sera la valeur par défaut : switch( a, 0, a=1, b=2) donnera 1 si a == "a", 2 si a == "b" et zéro dans tous les autres cas. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 34 Dans le problème actuel, il ne s’agit pas d’une variable de longueur 1 mais d’un vecteur. Pour le parcourir on utilise alors la fonction sapply. Mais quel est la fonction à donner à l’argument FUN ? Dans notre cas, on utilisera une fonction anonyme qui s’écrira : function( x) { switch( x, lesAlternatives)} Le fonctionnement de sapply fera qu’il passe à chaque fois un élément du vecteur. Nous avons un second problème dû aux valeurs que prend la colonne qu’on veut transformer, soit c’est d.psycho$VISITE et ce sont des valeurs numériques, soit c’est d.bio$VISITE et ce sont des valeurs qui commencent par un chiffre. Ces codes ne peuvent pas être utilisés à gauche de symbole d’égalité dans les arguments de switch puisque R n’acceptent aucun nom de variable commençant par un chiffre ou incluant un blanc ou un caractère spécial. Si ce ne sont que des chiffres, une des possibilités est de préfixer la valeur par un caractère grâce à la fonction paste : d.psycho$VISITE <- paste( "i.", d.psycho$VISITE, sep="") # il ne doit pas y avoir de blanc. L’autre possibilité est d’entourer la valeur de la paire de caractères d’échappement ` ` (apostrophe inversée). Ainsi, on pourra par exemple écrire l’instruction de transcodage ainsi : d.bio$VISITE <- sapply( d.bio$VISITE, FUN=function( x)switch( x, `0m`=0, `6m`=1, …)) Enfin, il est possible que certains résultats de tests neurophysiologiques soient indisponibles alors que les analyses biologiques le sont, ou l’inverse. Dans ce cas, il n’y aura donc pas correspondance parfaite entre les deux tableaux. On peut alors vouloir ne conserver que les couples (sujet, visite) complet, ce qui est le comportement par défaut de merge, soit conserver tous les résultats de tests neurophysiologiques même si les résultats des analyses biologiques ne sont pas disponibles, ou l’inverse, ou avoir tous les résultats. Ce comportement est contrôlé par les arguments all.x, all.y et all (raccourci pour all.x et all.y). Si all.x, all.y ou all est TRUE, les valeurs absentes seront remplacées par la valeur NA. Formule La dernière classe d’objets de R que je présenterai est rarement présentée comme tel dans la littérature bien qu’elle joue un rôle majeur dans les analyses. Cette classe est celle nommée formula (les modèles en langage statistique) qui explicite les relations entre différents vecteurs de données. Une formule est facilement reconnaissable par le fait qu’elle ne comporte aucune chaîne de caractères mais qu’elle contient le caractère ~ (tilde) quelquefois traduit par l’expression ‘est expliqué par’. En effet, la partie gauche avant le ~ référence les données à expliquer ou dépendantes et la partie droite la structure des données indépendantes ou explicatives. Dans certaines utilisations, la partie gauche de la formule peut être absente. Si la formule est utilisée dans la cadre de la manipulation d’un data.frame, comme c’est souvent le cas, la formule ne référence que les noms des colonnes, sinon elle doit référencer des vecteurs de données de même longueur. Je donnerai ici les principales constructions de formule. Il est nécessaire de se reporter à la description de la procédure l’utilisant pour les détails. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 35 Construction de la partie gauche Y une variable dépendante, vecteur de données cbind( Y.1, Y.2...) ou une matrice pour une analyse multi-variées sur les variables Y.1, Y.2, ... Surv( ...) pour les analyses de survie I( …) les opérateurs +, -, * et ^ à l’intérieur des parenthèses ne seront pas interprétés comme des opérateurs associés aux formules mais des opérateurs numériques ; les variables doivent donc être des variables numériques ; f( Y) avec f une fonction quelconque, par exemple log. Construction générale de la partie droite S’il n’y a qu’une variable, on la notera X : Y~X Y est expliqué par X Quand il y a plusieurs variables dépendantes, on note par + le fait qu’on s’intéresse à leurs effets séparément : le fait qu’on s’intéresse à leur interaction. Ceci donnera : Y ~ X.1 + X.2 Y est expliqué par les effets de X.1 et de X.2 qui n’interagissent pas entre eux ; Y ~ X.1 + X.2 + X.1:X.2 Y est expliqué par les effets de X.1 et de X.2 et par leur interaction, le : dénotant l’interaction. Le dernier schéma peut être simplifié en utilisant l’opérateur * : Y ~ X.1 * X.2 est équivalent à Y ~ X.1 + X.2 + X.1:X.2 À noter aussi que Y ~ (X.1 + X.2)^2 est équivalent aux écritures ci-dessus puisque X.1:X.1 = X.1 et X.1 + X.1 = X.1 Autres opérateurs utilisables : I( …) les opérateurs +, -, * et ^ à l’intérieur des parenthèses ne seront pas interprétés comme des opérateurs associés aux formules mais des opérateurs numériques ; les variables doivent donc être des variables numériques ; f( Y) avec f une fonction quelconque, par exemple log offset( …) terme à ajouter à une prédicteur linéaire avec un coefficient connu valant 1 au lieu d’estimer le coefficient. Quand le modèle sous-jacent est hiérarchique, il existe un effet principal du facteur de plus haut niveau dans la hiérarchie, puis une interaction entre le facteur immédiatement inférieur et le facteur, puis une interaction triple au niveau suivant. Supposons donc qu’on ait trois niveaux dans la hiérarchie : PATHOLOGIE, CENTRE, MACHINE telles qu’on s’intéresse aux différences entre images entre différentes pathologies, que ces images sont faites dans des centres différents mais qu’un centre ne fait des images que d’une et une seule pathologie et que chaque centre dispose de plusieurs machines. Pour modéliser ce cas de figure on pourra utiliser une construction à partir de l’opérateur / : PATHOLOGIE/CENTRE/MACHINE Programmer R - Version 1.2 (mars 2013) F. Aubry p. 36 qui est formellement équivalente à PATHOLOGIE + PATHOLOGIE:CENTRE + PATHOLOGIE:CENTRE:MACHINE Dans les analyses statistiques, on suppose par défaut qu’il existe une ordonnée par origine. Il est donc inutile de la noter. Si on veut forcer cette ordonnée à zéro, on peut utiliser l’une des constructions suivantes : i) Y ~ 0 + X.1 * X.2 ii) Y ~ X.1 * X.2 - 1 Note importante sur la notion d’ordonnée à l’origine nulle Quand on reprend ce qui a été dit sur les contrastes, forcer l’ordonnée à l’origine comme étant nulle va conduire à des résultats différents en fonction des contrastes utilisés. Si on utilise des véritables contrastes, c’est-à-dire ceux dont la somme des coefficients est nulle, cette contrainte est équivalente au centrage des variables dépendantes, c’est-à-dire au fait que leur grande moyenne est nulle. Cette contrainte n’a de sens que dans ce cas. Dans le cas d’un contraste de type cont.Treatment, cela revient à poser que le coefficient ϕ d’un des niveaux du facteur est imposé comme nul. En termes de significativité des coefficients β estimés, la non significativité de l’effet principal du facteur ou des interactions conduira à des coefficients β non significatifs (fonction summary) dans le cas des contrastes dont la somme des coefficients est nulle. Par contre, ce ne sera pas obligatoirement le cas pour contr.Treatment qui peut conduire à des coefficients β très significatifs. Pour les régressions, l’interprétation est plus simple mais peut conduire à des résultats erronés. Pour améliorer les estimations, on peut utiliser cette contrainte avec le centrage des variables dépendantes et indépendantes. On peut réaliser les mêmes centrages pour les analyses de type Ancova et utiliser la contrainte de l’ordonnée à l’origine nulle à condition de s’assurer : 1) que les variables dépendantes sont centrées sur la même valeur dans tous les groupes ; 2) que les variables indépendantes servant de régresseurs le sont aussi. Si ces contraintes ne sont pas respectées, les résultats peuvent être aberrants. Par contre, si ces contraintes sont respectées, cela conduira à une estimation plus puissante des différences entre pentes associées aux différents groupes. Dans les régressions, on peut vouloir utiliser des transformées des variables ou forcer que les coefficients de deux (ou plus de deux) variables soient identiques. On utilise alors la fonction I qui inhibe l’interprétation des opérateurs au sens des formules. Exemples : 1) régression polynomiale cubique Y ~ X + I(X^2) + I(X^3) 2) X.1 et X.2 doivent avoir le même coefficient I( X.1 + X.2) 3) emploi de fonctions arithmétiques Y ~ I( log( X)) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 37 On peut aussi vouloir ajouter une variable qui décalera les données et dont on force le coefficient à 1 par défaut. Cette variable est signalé par la fonction offset : Y ~ X + offset( Z) dans le contexte de df est équivalent à l’analyse Y~X dans le contexte de transform( df, Y=Y-Z) Si le coefficient de Z est une valeur quelconque k, on écrira : Y ~ X + offset( k * Z) Exercice : # On construit un tableau de données d.f <- data.frame( X=runif( 20, min=0, max=5)) d.f <- transform( d.f, Y=X + rnorm( 20, sd=0.2)) d.f <- transform( d.f, MEAN.Y=mean( Y)) # On compare les résultats des régressions suivantes lm.std <- lm( Y ~ X, d.f) lm.centre <- lm( Y - MEAN.Y ~ X, d.f) lm.offset <- lm( Y ~ X + offset( MEAN.Y), d.f) lm.zero <- lm(Y ~ 0 + X + MEAN.Y, d.f) Utiliser la fonction summary pour récupérer les résultats et interprétez-les. Autres arguments Je ne présenterai que deux arguments qu’on rencontre souvent dans les formules. | est un séparateur dont la partie droite représente le (ou les facteurs) de regroupement et la partie gauche ce qui est regroupé. Par exemple, dans le package lattice qui permet des visualisations graphiques sophistiquées, on voudra représenter séparément Y ~ X en fonction d’un troisième facteur Z. On écrira alors : xyplot( Y ~ X | Z, df) Ce séparateur est aussi utilisé dans la spécification des modèles mixtes analysé par les fonctions lme du package nlme et lmer du package lme4. Error permet dans la procédure aov15 de donner le modèle des erreurs et donc d’indiquer les corrélations entre elles. Cette fonction prend comme arguments le schéma de regroupement des erreurs. Par défaut, toutes les procédures supposent que d’une ligne à une autre du tableau à analyser, les erreurs sont indépendantes et identiquement distribuées (iid) : yj = β jxj +ε j avec : Exi x j = σ 2δ ij Qui conduit par exemple au modèle : TC ~ TEST * VISITE 15 aov ne peut être utilisé que pour les analyses sur des groupes équilibrés. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 38 Dans le tableau d.psycho, l’erreur sur la mesure du temps de réaction à chaque visite se compose d’une erreur de mesure indépendante du sujet et de la visite et d’une erreur spécifique de chaque sujet. La formulation précédente est fausse et conduira à des résultats biaisés. Si on veut prendre en compte la relation entre les erreurs via le sujet, on ajoutera donc au modèle un modèle d’erreur identique pour tous les sujets sous la forme : TC ~ TEST * VISITE + Error( ID) Ce modèle gère alors des résultats de types multi-variés mais pas de type mesures répétées car on ne modélise pas la relation entre les visites qui est le facteur intra-sujet. La corrélation entre les visites dépend du sujet et le schéma de corrélation entre sujet et visite est le même pour tous les sujets. Le modèle d’erreur conduit donc à poser : TC ~ TEST * VISITE + Error( ID/VISITE) On peut aussi considérer qu’il existe une corrélation entre les tests chez le même individu (par exemple, via un effet d’ordre parce que les tests sont effectués dans le même ordre chez tous les individus) sans qu’il y ait un effet d’habituation aux tests au travers des visites et que le schéma de corrélation est le même pour tous les sujets. Dans ce cas, le modèle devient : TC ~ TEST * VISITE + Error( ID/(VISITE + TEST)) Pour modéliser cet effet longitudinal entre les tests, on pourra écrire: TC ~ TEST * VISITE + Error( ID/(VISITE + VISITE:TEST)) # pas d’effet d’ordre entre les tests ou TC ~ TEST * VISITE + Error( ID/(VISITE * TEST)) # tous les effets possibles. Dans les trois exemples ci-dessus, VISITE et TEST sont des facteurs intra-sujet et les différentes écritures permettent de poser des modèles intra-sujet différents. Par contre cette écriture basé sur l’utilisation d’Error ne permet pas de prendre en compte des corrélations entre les erreurs dues indépendamment à des effets sujet et test (par exemple, lié à la difficulté du test ou à la charge cognitive comme dans des tests de type n-back), c’est-à-dire des modèles d’erreurs qui pourraient répondre à un modèle du type : Error( ID + TEST) ATTENTION : Cette construction avec Error n’est pas valable pour toutes les analyses. Voir la documentation de la procédure. De plus, elle est rarement la plus efficace et généralement peut être avantageusement remplacée par l’utilisation de modèles mixtes16. Utilitaires Quand la formule est complexe, il est souvent commode de la construire d’abord sous forme d’une chaîne de caractères puis de la transformer en formule. C’est le rôle du constructeur formula. Pour tester si une variable contient une formule, on utilisera la fonction inherits : inherits( f, "formula") 16 L’utilisation de ce type de modèle et le codage des formules qui lui sont associées vont au-delà de ce présent document (cf. les packages nlme et lme4 et les documentations associées).. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 39 Les procédures d’analyse comme lm, aov ou glm utilisent les formules. Elles sont aussi utilisées par des fonctions donnant des statistiques récapitulatives (summary statistics) comme la fonction aggregate. Elles peuvent aussi être utilisées par les fonctions graphiques. Il existe un certain nombre d’utilitaires permettant la manipulation des formules : terms décrit les composants de la formule all.vars liste toutes les variables utilisées par la formule model.frame construit un data.frame spécifique de la formule model.matrix construit la design matrix associée à la formule. Pour les détails, voir la documentation. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 40 Scripts Charger du code R est un langage de programmation. On peut donc écrire des scripts ou des fonctions qui pourront être réutilisés. Ces scripts et fonctions peuvent être regroupés au sein d’un package mais la construction d’un package pour qu’il soit reconnu comme une library demande une stratégie spéciale qui va au-delà de ce document. On peut cependant créer des fichiers contenant ses propres scripts et ses fonctions utilitaires. Ces fichiers peuvent être chargés par la commande source ou l’item sourcer du code du menu Fichiers. Quand on charge ainsi son code, les fonctions seront mises en attente d’utilisation tandis que les autres scripts seront exécutés au chargement. Pour connaître les objets présents en mémoire, on peut appeler la fonction ls. Affectation d’une valeur à une variable Ce paragraphe ne traite que les stratégies d’affectation les plus couramment utilisées. L’opérateur le plus souvent utilisé pour assigner une valeur à une variable est <-, composé du caractère inférieur et moins. Il affecte la valeur placée à sa droite à la variable placée à sa gauche. Il peut être utilisé plusieurs fois sur la même ligne : a <- b <- c <- 0 affecte 0 à la variable c puis affecte le contenu de c à b... Il faut éviter ce genre d’écriture qui devient facilement illisible, par exemple : a <- ( (c <- 0) + 1) commence déjà être difficile d’interprétation. On peut utiliser cet opérateur quand on passe un argument à une fonction : View( x <- maFonction( ...)) C’est particulièrement utile lors de la mise au point du programme. Il est à noter que R accepte la notation inverse -> où c’est la valeur à gauche qui est affectée à la variable de droite. R accepte aussi le caractère égal (=) comme opérateur d’affectation. Il n’est cependant pas conseillé car il est utilisé dans certains contextes avec des significations différentes. Par exemple y <- f2( x=z) et y <- f2( x <- z) ne signifie pas la même chose. Dans le premier cas, on affecte la valeur z au paramètre x de la fonction f2, dans le second cas, on crée (ou utilise) une variable x dans l’environnement appelant à qui on affecte la valeur z qui est aussi passée comme premier argument de la fonction f2. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 41 Structures de contrôle On appelle structure de contrôle toutes les constructions qui permettent de structurer le programme notamment de programmer des tests ou des boucles. Tests Nous avons vu précédemment la construction conditionnelle avec la fonction ifelse pour affecter une valeur de longueur 1 à une variable conditionnellement à une condition logique. La construction peut être plus complexe et on peut vouloir exécuter une suite d'instructions ou calculer une valeur dont la longueur est supérieure à 1. ifelse est alors insuffisant pour cela et pour exécuter une suite d’instructions conditionnellement à une condition logique, on utilise la construction if( condition) { à exécuter si la condition est vraie } S’il n’y a qu’une instruction on peut ne pas mettre les accolades mais je les recommande pour la lisibilité du code. S’il y a une suite d’instructions alternatives, on écrira : if( condition) { à exécuter si la condition est vraie } else { à exécuter si la condition est fausse } S’il y a plusieurs conditions à tester, on utilise la construction if( condition1) { ... } else if( condition2) { ... } ... { # autres else if éventuels } else { … } Il ne faut jamais avoir de fin de ligne entre } et else sinon R peut générer une erreur de syntaxe. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 42 Cette construction if/else if/else peut retourner toujours la dernière valeur évaluée. Ainsi, elle est une alternative à la fonction ifelse, surtout s'il y a plus d'une condition. En effet les écritures suivantes sont équivalentes : if( condition1) { var <- if( condition1) { ... ... var <- val1 val1 } else if ( condition2) { } else if ( condition2) { .... .... var <- val2 val2 } ... else { } ... else { ... ... var <- val.def val.def } } Boucles La principale structure de construction de boucle est basée sur l’instruction for et s’écrit for( variableDeBoucle in vect) { instructions pour la valeur courante de variableDeBoucle } La variableDeBoucle parcourt les éléments du vecteur vect dans l’ordre où ils sont rangés. Si vect est vide, R n’entre pas dans la boucle. while( condition) { ... } répète les instructions entre accolades tant que condition est vrai. repeat { ... } répète indéfiniment les instructions entre accolades. On peut dans les boucles vouloir sortir avant la fin de boucle (ou sortir tout court pour repeat) sur une condition donnée. Cela se fait par l’instruction break et donne une construction du type : while( condition) { ... if( conditionSortieAnticipée) { break } ... } Programmer R - Version 1.2 (mars 2013) F. Aubry p. 43 De façon similaire, on peut vouloir ne pas exécuter certaines instructions si une condition est réalisée. On peut le faire de deux manières différentes : while( condition) { while( condition) { ... ... if( !conditionDEchappement) { if( conditionDEchappement) { instructionsAExécuterSiFaux next } } } instructionsAExécuterSiFaux } Fonctions d’itérations Il convient d’éviter l’utilisation de boucles de type for qui sont peu efficaces, tout comme cela est le cas pour MALAB®. R offre des fonctions permettant de remplacer efficacement les boucles. La première de ces fonctions est la fonction apply applicable à une matrice ou à un tableau (cf. supra). Elle comporte trois arguments : X : le tableau à traiter, MARGIN : la ou les dimensions du tableau sur laquelle ou lesquelles s’applique, FUN : la fonction, : arguments supplémentaires à passer à FUN. … Cette fonction peut être une fonction déjà existante ou être une fonction anonyme ayant au moins un argument. De plus, elle peut retourner une seule valeur (exemple, la fonction mean) ou un vecteur de valeurs. Dans ce dernier cas, apply retourne une matrice ayant un nombre de lignes égal à la dimension du vecteur retourné et un nombre de colonnes égal au nombre de fois où FUN est appliquée. Il existe aussi une fonction dérivée sweep. Il existe aussi une série de fonctions s’appliquant à des listes (list) ou des vecteurs (vector). Je ne présenterai ici que deux de ces fonctions : sapply et replicate qui sont les plus utilisées. sapply est une fonction très générale qui prend 5 arguments : X : le vecteur ou la liste à traiter, FUN : la fonction à appliquer (cf. supra la description de FUN dans apply), … : arguments supplémentaires à passer à FUN, simplify : si FALSE, renvoie une liste. Si TRUE (défaut) essaie de simplifier si possible, généralement sous forme de vecteur ou de matrice. USE.NAMES : si TRUE et X est de type caractères, alors utiliser X pour identifier le résultat. Puisque simplify et USE.NAMES sont après …, il faut obligatoirement les nommer. Exemples : sapply( X, FUN=f, FALSE) # FALSE est considéré comme étant l’un pas paramètres remplaçant …, c’est-à-dire qu’il est considéré comme un paramètre anonyme passé à la fonction f Programmer R - Version 1.2 (mars 2013) F. Aubry p. 44 sapply( X, FUN=f, simplify=FALSE) # on ne simplifie pas le résultat retourné car simplify est un paramètre nommé de la fonction sapply. Il faut noter que la valeur de l’argument simplify n’est jamais passée à la fonction f. De ce fait, si f possède un argument de nom simplify sans valeur par défaut, celui-ci ne recevra jamais de valeurs, ce qui provoquera un erreur. S’il a une valeur par défaut, c’est elle qui sera toujours prise dans le corps de f. replicate permet de répliquer n fois l’expression qui est en second arguments. ATTENTION : il s’agit ici d’une expression et nom de la référence à une fonction. replicate a donc un intérêt quand il s’agit de répliquer n fois une expression qui contient un aléa. Si l’expression retourne un vecteur, replicate retourne une matrice ayant la même structure que celle retournée par apply. Ci-dessous quelques exemples d’utilisation de ces fonctions : 1) Calcul de statistiques sur les lignes d’une matrice mat <- matrix( rnorm( 10000), ncol=1000) apply( mat, 1, FUN=function( x, probs) c( MEAN=mean( x), SD=sd( x), QUANT=quantile( x, probs)), probs=c( 0.025, 0.50, 0.975)) apply( mat, 1, median) # Calcul de la médiane 2) Colonnes d’un data.frame qui sont des facteurs df <- data.frame( …) sapply( df, FUN=as.factor) 3) Ajout d’une colonne à un data.frame dont la valeur est calculée à partir de la valeur d’autres colonnes df <- data.frame( VAL=rnorm( 100), F=rep( c( "A", "B"), each=50)) df$S <- sapply( seq_len( nrow( df)), FUN=function( n.row) switch( df$F[n.row], A=sign( df$VAL[n.row]), B=-sign( df$VAL[n.row]))) 4) Bootstrapper la moyenne d’un vecteur replicate( 1000000, apply( mat[,sample.int( ncol( mat), replace=TRUE)], 1, mean)) Exercice : A) Soit un data.frame comportant deux colonnes, l’une la date de naissance, l’autre la date d’examen sous forme de chaîne de caractères jj/mm/aa où aa est l’année sur deux chiffres. Calculer l’âge en année de chaque sujet lors de l’examen (cf. as.Date). Comment gérer le changement de siècle et le fait que certains sujets sont nés avant 1970 ? Comment gérer le fait que certains sujets n’ont pas encore subi d’examen et donc que la date est codée par NA ? B) Soit un tableau (array) à 3 dimensions mesurant plusieurs fois dans le temps divers grandeurs (lignes), dans différentes conditions (colonnes), la répétition des mesures étant la 3ème dimension (temps) : mes.array <- array( 0, dim=c( nb.variables, nb.conditions, nb.temps) … vect.temps <- # Temps des mesures i) On veut vérifier que les mesures sont stables dans le temps. On va donc faire une régression individuelle sur la variable temps de chacune des variables dans chacune des conditions et construire la matrice variablesXconditions de p.value de la pente de régression. Utiliser la fonction apply pour cela. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 45 ii) On suppose que la stabilité est vérifiée, on veut alors vérifier l’effet des conditions en considérant les mesures dans le temps comme des occurrences indépendantes de la même mesure. On va donc construire un vecteur des p.value, un élément par variable. Utiliser aussi apply. N.B. : la fonction à utiliser est lm et on retourne la p.value associée à la table anova obtenue par la fonction anova. C) Soit une matrice de 3 colonnes et 256 lignes dont les valeurs sont comprises entre 0 et 1 correspondant à une LUT (look-up table), la première colonne correspond donc à l’intensité du rouge, la seconde, du vert et la troisième du bleu. R code les couleur sous forme de chaines de caractères en hexadécimal commençant par le caractère dièse ("#"), les deux caractères suivant étant le niveau de rouge, codé de 0 à FF (équivalent du 255 décimal), ensuite on a le niveau du vert puis celui du bleu. Écrire les lignes de code permettant de transformer la matrice en couleurs pour R (ne pas utiliser de boucles for). Gestion des erreurs L’exécution d’un programme peut provoquer des messages d’avertissement (fonction warning) ou des messages d’erreur (fonction stop). En général, dans le premier cas, il affiche l’avertissement à la console et continue ; dans le second cas, le programme s’arrête et sort. On peut modifier ces comportements par défaut, soit pour toute la session en modifiant les paramètres warn et error par la fonction options (c’est-à-dire options( warn=…) ou options( error=…)), soit localement en utilisant la construction tryCatch qui permet de définir le comportement à suivre pour l’instruction courante et comment terminer. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 46 Visualiser les valeurs Généralités On peut visualiser le contenu d’une variable atomique, d’un vecteur, d’une matrice ou d’un data.frame dans une fenêtre par la procédure View. Tous les objets possèdent une méthode print par défaut qui est appelée quand on tape le nom de la variable à la console. Cette méthode formate le contenu de la variable sous la forme d’une chaîne de caractères selon un certain format, cette chaîne sera alors envoyée au périphérique de sortie (par défaut, la console). Le format peut être différent du contenu de l’objet tel qu’il est donné par la fonction str (structure de l’objet). Par exemple, comparer : lm.objet <- lm( …) str( anova( lm.objet)) et anova( lm.objet) ou str( summary( lm.objet)) et summary( lm.objet). La méthode print associée à un objet de la classe des fonctions donne le code source de la fonction si celui-ci est disponible. cat est une simplification de print. Il existe d’autres fonctions plus souples permettant l’impression de valeurs : format, sprintf… Elles permettent des formatages plus sophistiqués au prix d’une plus grande difficulté dans la spécification. Lors de la mise au point d’un script, on peut utiliser ces fonctions d’affichage sur la console, notamment pour suivre l’évolution d’une boucle. Il faut cependant prendre en compte que ces fonctions n’écrivent pas directement sur la console mais dans un buffer, une zone spéciale de mémoire, qui est vidé (écrit) sur la console de manière asynchrone. De ce fait, dans une boucle, l’affichage ne se fera pas à chaque fois que la boucle trouve la fonction d’écriture mais par rafales. Par exemple, les valeurs à afficher pour les 10 premiers passages de la boucle pourront être affichés en une seule fois alors que la boucle en est à son quinzième passage. Visualiser à la console La fonction de visualisation standard d’une donnée View, visualise celle-ci sous forme d’un tableau. L’objet à visualiser (argument x) doit donc avoir une méthode as.data.frame qui lui est attachée. Moyennant l’écriture de cette méthode, on peut visualiser à la console n’importe quel objet d’une classe qu’on a créée. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 47 Exemple : class( x) # retour c( "MA.CLASSE", "list") if( exists( "as.data.frame.MA.CLASSE")) { View( x) } else { warning( "Visualisation par la méthode par défaut associée aux listes") View( x) } Imprimer sur la console Quand on appelle une fonction sans renvoyer son résultat dans une variable, le retour peut être silencieux si la valeur retournée est déclarée invisible dans la fonction, sinon ce retour sera formaté par la méthode print associée à la classe de l’objet. S’il n’existe pas de méthode print spécifique, R utilisera la méthode par défaut. Pour forcer l’impression sur la console d’un retour invisible, mettre l’appel de la fonction comme argument de print. Pour formater de manière plus générale l’objet, par exemple, pour le stocker dans un fichier texte, on peut aussi utiliser la méthode format et créer son propre format pour une classe donnée d’objet. Gestion des fichiers, lire et écrire des données sur disque17 Nous avons vu précédemment comment R pouvait charger et sauver du code source (fonction source), l’historique des commandes (loadhistory, savehistory) ou de l’environnement (load, save, save.image). Par défaut, c’est-à-dire si on ne donne pas les chemins d’accès complets, R utilise le répertoire de travail. La fonction getwd permet de le connaître et la fonction setwd d’en changer. Voir le document spécifique pour connaître comment définir un répertoire par défaut pour une session. Système de fichiers list.files permet de connaître la liste des fichiers dans un répertoire donné et, éventuellement, dans ses sous-répertoires. D’autres fonctions d’accès bas niveau au système de fichier natif de l’ordinateur sont listées dans la page files de la documentation. file.path permet de construire un nom de fichier dont la syntaxe suit celle du système d’exploitation. Il faut cependant noter que l’utilisation du / en tant que séparateur est reconnu par R sous Windows. La page files2 présente les fonctions de manipulation des répertoires et des permissions d’accès aux fichiers. 17 Voir le manuel R Data import / export pour une description complète. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 48 Accès aux fichiers de données On retrouve généralement des paires de fonction commençant par read (par exemple, read.table) pour la lecture et par write (par exemple, write.table) pour l’écriture. On peut se baser sur les codes générer par l’option Importer des données du menu Données de Rcmdr pour coder ses propres accès. Il est souvent avantageux de créer des fichiers texte (avec comme séparateur la tabulation : extension .txt), ou des virgules ou des points virgules (extension .csv) qui peuvent être facilement contrôlés grâce à n’importe quel éditeur de texte et qui sont lus par la grande majorité des tableurs et autres logiciels statistiques et qui, de plus, sont indépendants de la plateforme. De plus, la fonction read.table permet de contrôler la lecture (le type de séparateur décimal, point ou virgule ; le séparateur entre les champs ; les entêtes...) Exemple : lecture d’un fichier avec entêtes (noms) des colonnes, séparateur décimal codé par la virgule et séparateur de champ, la tabulation read.table( "mon_fichier.txt", header=TRUE, sep="\t", dec=",") ATTENTION : Si des valeurs contiennent des # ou des simples ou doubles apostrophes (par exemple, numero d’immatriculation), read.table lira mal les données ou générera des erreurs car par défaut # est la marque du début d’un commentaire et les simples ou doubles apostrophes, celles de chaînes de caractères. Il faut alors modifier les arguments comment.car et/ou quote. Pour écrire les données dans des fichiers texte, on peut utiliser la fonction write.table. Je conseille de toujours mettre l’argument row.names à FALSE alors qu’il est TRUE par défaut. Description des principaux arguments de read.table file : nom du fichier . par rapport au répertoire de travail (cf. getwd, setwd) . chemin absolu. On peut le donner en clair (par exemple, sous Unix, /usr/maZone/dataDirectory/mesDonnees.txt), soit lui demander de le construire en lui donnant tous les sous répertoires et le nom du fichier, grâce à la fonction file.path. A noter : Bien que le séparateur sous Windows soit le backslash, R reconnaît sous Windows un chemin d’accès qui utilise comme séparateur le slash. Pour être indépendant de la plateforme, je conseille de toujours utiliser le slash comme séparateur. header : variable logique indiquant si la première ligne du fichier contient les noms des colonnes. row.names : permet d’indiquer les noms des lignes. Par défaut, pas défini donc pas de noms de ligne stockés dans le fichier. . vecteur de chaînes de caractères : noms à donner aux lignes. Attention : s’il y a moins de noms que de lignes dans le fichier. Donc option déconseillée. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 49 . numéro de la colonne contenant les noms des lignes. Si header=TRUE, cette colonne doit avoir un nom ou être un emplacement vide. . nom de la colonne contenant les noms des lignes. quote : caractères servant de délimiteurs aux chaînes de caractères. A noter : i) par défaut, toute colonne ne comportant pas des nombres, des valeurs logiques (TRUE, FALSE) ou les chaînes définies dans l’argument na.strings sont considérées comme des chaînes de caractères. De ce fait, il est inutile de coder ces séparateurs dans le fichier de données. Par exemple : "Alzheimer" et Alzheimer sont tous les deux traités comme des chaînes de caractères contenant le mot ‘Alzheimer’. ii) à chaque fois que R rencontre un des caractères indiqués dans cet argument, il considère comme indiquant le début d’une chaîne si c’est une occurrence impaire, la fin d’une chaîne pour une occurrence paire. Par exemple, si quote="\"'", la chaîne ‘numero d'immatriculation’ va provoquer une erreur. na.strings : vecteur de chaînes de caractères qui seront traduites par R comme dénotant l’absence de valeur. Par défaut, vaut NA. i) une colonne vide, c’est-à-dire sans valeur entre deux séparateurs de colonnes est considéré comme ayant la valeur NA ; ii) si une colonne contient des nombres, quand R rencontre l’une des chaînes, il la traduit comme NA ; si la chaîne rencontrée n’est pas dans la liste des na.strings, la colonne sera considérée comme contenant des chaînes de caractères. comment.char : caractères servant de à signaler que la fin de la ligne est un commentaire. Quand R rencontre ce caractère, il considère que la fin de la ligne est un commentaire et arrête sa traduction, ce qui peut générer une erreur si la chaîne de caractères contient ce caractère. Si on ne veut pas prendre en compte ce délimiteur de commentaire, on peut écrire comment.char="". sep : caractère servant de délimiteur entre les colonnes, souvent la tabulation ("\t"). Dans le format csv, c’est la virgule. dec : pour les nombres, délimiteur décimal, le point chez les anglo-saxons, la virgule en France. stringsAsFactors : indique si R doit considérer que les colonnes contenant des chaînes de caractères contiennent en réalité les niveaux d’un facteur ; et donc que la variable est un facteur. Par défaut, cet argument prend la valeur de l’option globale stringsAsFactors (cf. la page d’aide de option pour voir comment configurer les options par défaut de R). A noter : i) la fonction data.frame possède aussi cet argument avec la même valeur par défaut. ii) Si toutes les colonnes contenant des chaînes caractères représentent des facteurs et qu’on est sûr du codage des noms des niveaux, alors stringsAsFactors=TRUE est le choix à faire. Si par contre, on doit ensuite grouper des tableaux de données provenant de différentes sources où le même facteur est codé différemment ou si on veut recoder les niveaux, il est préférable de mettre cet argument à FALSE et d’appeler ensuite explicitement la fonction factor. Il en va de même si on veut ordonner les niveaux d’un facteur. En effet, par défaut, R ordonne les niveaux selon l’ordre lexicographique. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 50 Exemple : dans un fichier les visites sont codées bs pour la première visite puis nombre de mois suivi de m pour les visites suivantes ; dans un second fichier, elles ne sont codées que par la lettre v suivie d’un chiffre, 1, pour la première visite, 2 pour la seconde... alors, il est préférable d’écrire : tab.1 <- read.table( "fic.1.txt", …, stringsAsFactors=FALSE) tab.2 <- read.table( "fic.2.txt", …, stringsAsFactors=FALSE) tab.1$VISITE <- sapply( tab.1$VISITE, switch, bs="m00", `6m`="m06", …) tab.1$VISITE <- factor( tab.1$VISITE, levels=c( "m00", "m06", …), labels=c( "m00", "m06", …), ordered=TRUE) tab.2$VISITE <- sapply( tab.2$VISITE, switch, v1="m00", v2="m06", …) tab.2$VISITE <- factor( tab.2$VISITE, levels=c( "m00", "m06", …), labels=c( "m00", "m06", …), ordered=TRUE) tab.total <- merge( tab.1, tab.2, by=c( "RID", "VISITE"...), ...) Note : 6m n’est pas un nom valide pour R. Le fait de mettre ce nom entre deux apostrophes inversées (cf. fonction quote) a pour but de prévenir d’une tentative d’interprétation du nom par R. Ce serait aussi vrai si le nom était un caractère représentant un chiffre ou s’il contenait certains caractères spéciaux (blanc, moins, plus...). Écriture des données par write.table La fonction qui permet d’écrire depuis R des data.frame dans des fichiers texte s’appelle write.table. Elle a des arguments qui ressemblent à ceux de read.table, notamment dec et sep. Elle possède deux arguments spéciaux : row.names : indique si on doit écrire dans le fichier les noms des lignes. col.names : indique si on doit écrire le nom des colonnes (relu dans read.table par header=TRUE) Note: si on écrit row.names=TRUE et col.names=TRUE, la première colonne du fichier sera les noms des lignes, par contre cette colonne prendra le nom de la première colonne du data.frame... Il y aura donc un décalage dans les noms de colonne si on oublie d’indiquer row.names=1 dans read.table. Si col.names=NA, la première colonne contiendra les noms de lignes et n’aura pas de nom. À la lecture, elle prendra le nom X et si une autre colonne à aussi comme nom X, R lui donnera le nom X.1 (cf. fonctions make.names et make.unique) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 51 Fonctions : manipuler et contrôler les arguments Définition de la fonction Une fonction est un objet de la classe function ayant un constructeur dénommé function. Il a particularité d'être suivi d'une paire d'accolades équilibrées { } contenant la suite des instructions composant la fonction. La liste des arguments formels peut contenir à n'importe quel endroit, l'argument spécial ... permettant de passer un nombre non déterminé par avance de valeurs. Les arguments formels peuvent aussi avoir des valeurs par défaut: function( x, n=length( x)) { } signifie que la fonction possède deux arguments formels x obligatoire n qui peut avoir n'importe quelle valeur mais qui aura comme valeur length( x) si on ne lui en donne pas à l'appel. Une fonction renvoie la dernière valeur évaluée comme le fait la structure if/else if/else. Cela signifie que : function( x, n=length( x)) { var <- x } ne renvoie pas de valeur visible à la console, tandis que le retour de function( x, n=length( x)) { x } sera visible à la console. Cependant, pour plus de clarté dans la lecture du code, je conseille d'utiliser l'instruction spéciale return qui permet d'indiquer explicitement le retour : function( x, n=length( x)) { ...; return( val) } Si on désire que la valeur de retour ne soit jamais visible à la console, on peut utiliser la fonction invisible. Sauf utilisation particulière comme argument d'une fonction (cf. supra, les exemples notamment avec sapply), une fonction est utilisée en plusieurs endroits d'un programme. Il faut donc pouvoir la réutiliser. Pour cela, on affecte à une variable une valeur qui est la définition formelle de la fonction et par abus de langage, on donnera comme nom à la fonction le nom de la variable. Par exemple, on parlera de la fonction var définie comme suit : var <- function( x) { return( sum( (x - mean( x))^2) / (length( x) - 1))} dont l'appel se fera par var( x). Étant donné que le nom de la fonction est une variable, celle-ci peut se voir attribuer une autre valeur. Il convient donc d’être prudent afin de ne pas écraser des définitions de fonctions. Note importante Tout au long du document j’ai utilisé le symbole <- pour définir l’affection d’une valeur (à droite du symbole) à une variable à gauche du symbole. R admet aussi l’utilisation du signe = comme beaucoup d’autres langages. Cependant, ce signe est aussi utilisé dans la définition des fonctions, pour définir les valeurs par défaut Programmer R - Version 1.2 (mars 2013) F. Aubry p. 52 ou admissibles des arguments, et dans l’appel des fonctions pour passer les arguments par nom et non plus par position. Exemple : appel de la fonction cut cut( x, 3, c( "a", "b", "c")) est équivalent à cut( x, labels= c( "a", "b", "c"), breaks=3) La seconde écriture est plus explicite. De plus, si on veut ordonner le résultat, il faut mentionner ordered_result=TRUE. Un passage d’argument par position impose de passer les valeurs des autres arguments avant ordered_result tandis qu’un passage par nom permet d’écrire : cut( x, labels= c( "a", "b", "c"), breaks=3, ordered_result=TRUE). L’utilisation du signe = comme symbole d’affectation peut générer des ambiguïtés et des comportements inattendus. Exemple : partons de l’environnement x <- 2 exists( "v") # retourne FALSE (v n’existe pas dans l’environnement) fu <- function( u, v) { …} et comparons les deux codes res <- fu( x, v=f1( …)) res <- fu( x, v <- f1( …)) v res <- fu( x, v <- f1( …)) v Dans le premier cas, la variable v n’existe toujours pas après l’appel de fu puisque le code signifie que l’on donne la valeur f1( …) à l’argument v de la fonction fu ; dans le second elle a été créée lors de l’appel car le code équivaut à : v <- f1( …) res <- fu( x, v) # ou res <- fu( v=v, u=x) ou … L’argument spécial ... La définition des fonctions peut contenir un argument spécial ... en n’importe quelle position pour signifier que la fonction accepte un nombre indéterminé d’arguments non spécifiés a priori. Cet argument est utilisé dans deux cas. Le premier cas est quand la fonction appelle elle-même une fonction à qui on veut passer des paramètres sans préjuger des quels comme pour la fonction aggregate. En effet, les paramètres à passer dépendront de la fonction d’agrégation : aggregate( Y ~ Groupe, donnees, quantile, probs=seq( 0, 0, 0.1)) ou aggregate( donnees$Y, by=list( donnees$Groupe), mean, na.rm=TRUE) Dans cette utilisation, les paramètres à passer doivent être de la forme : nomDuParametre=valeur. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 53 La seconde utilisation est quand le nombre de valeur à passer à la fonction n’est pas connu à l’avance comme par exemple pour les fonctions min et max qui acceptent soit un nombre quelconque de vecteurs, éventuellement de longueur un, comme arguments. Il faut respecter certaines règles dans le codage sinon le résultat est imprévisible : i) l’argument ... est en première position ; à l’appel de la fonction quand on veut donner des valeurs aux autres arguments, il faut donner explicitement leur nom et la valeur qui y est associée comme pour l’argument na.rm de min ou max : max( listeDesValeurs, na.rm=TRUE) De plus, il est préférable de donner des valeurs par défaut pour ces arguments. ii) l’argument ... est en dernière position, on passe alors les autres arguments par position. iii) l’argument ... est au milieu, cas qu’il faut éviter ; pour éviter les problèmes, il faut mieux procéder comme dans le cas i). On récupère dans une liste les arguments réels passés à la fonction qui correspondent à l’argument ... : f <- function( ...) { f.li <- list( ...) Ils sont dans l’ordre dans lequel ils sont cités. Les éléments de la liste sont numérotés sauf si les éléments réels sont nommés : appel de la fonction : f( 1, z=2, 3) donne f.li [[1]] [1] 1 $z [1] 2 [[3]] [1] 3 Si tous les éléments sont atomiques, on peut passer d’une liste à un vecteur par la fonction unlist. Contrôler la valeur des arguments Récupérer la liste des noms des arguments formels à qui on a attribué une valeur Certaines procédures et notamment celles d’analyse, comportent une liste d’arguments formels sans valeur par défaut mais à qui on peut ne pas donner de valeur. Par exemple, la signature de la procédure lm est : lm( formula, data, subset, weights, # + d’autres arguments Or, la plupart du temps, on ne donne pas de valeur à subset pour signifier qu’on analyse toutes les données, ni à weights car on donne le même poids à toutes les observations. Cependant, il n’y aucune valeur par défaut déclarée pour ces deux arguments. On peut cependant savoir quels sont les arguments qui ont reçu une valeur par l’instruction suivante : form.arg <- names( match.call()) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 54 qui renvoie un vecteur de chaînes de caractères vides pour les arguments sans nom, le premier argument étant toujours la chaîne vide "" puisqu’il correspond au constructeur de fonction. Donc, si la liste des arguments comporte l’argument spécial ..., on aura autant de chaînes vides qu’il y a d’arguments, à moins que ceux-ci soient nommés : f( 1, 2) renvoie "", "", "" tandis que f( 1, z=2) renvoie "", "", "z" Si on veut éviter ce problème avec l’expansion de ..., on peut écrire : form.arg <- names( match.call( expand.dots=FALSE)) qui retournera la chaîne "..." si l’appel comporte les arguments correspondants. On peut alors tester les arguments présents en utilisant la fonction match avec comme premier argument le vecteur des noms des arguments formels et comme second argument, celui des noms des arguments réels, qui renverra la position dans la liste ou NA si l’argument n’existe pas. Pour la fonction lm, on a donc : mf <- match.call( expand.dots = FALSE) m <- match( c( "formula", "data", "subset", "weights", "na.action", "offset"), names( mf), 0L) La fonction missing permet de vérifier si un argument a été passé. Par exemple, la signature de la fonction sample est : sample( x, size, replace=FALSE, prob=NULL) Elle ne définit donc pas de taille par défaut de l’échantillon à générer à partir du vecteur x. De ce fait, si on veut générer une permutation aléatoire du vecteur x, on devrait écrire : sample( x, length( x)). En réalité, on peut écrire : sample( x) parce que le corps de la fonction contient le code : if( missing( size)) size <- length( x) Une autre solution (plus élégante) aurait été d’écrire : sample( x, size=length( x), replace=FALSE, prob=NULL). Compléter une chaîne de caractères Un certain nombre de procédures reçoivent un argument de type de chaîne de caractères représentant une option possible. Pour être lisible, la signature comporte généralement les noms en entiers, par exemple pour les tests de normalité : "shapiro-wilks", "kolmogorovsmirnov", "lillienfors", "kramer-von-mises"... Si cela facilite la lecture de la documentation, cela devient aussi rapidement fastidieux de taper le nom entier surtout quand on travaille à la console. R propose donc une procédure pmatch permettant de compléter les chaînes non ambigües. En reprenant l’exemple ci-dessus, on écrira : pmatch( typeTest, c( "shapiro-wilks", "kolmogorov-smirnov", "lillienfors", "kramervon-mises")) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 55 qui renverra NA si typeTest n’est pas dans la liste ou est ambigu et la position dans la liste dans le cas contraire. Tester la valeur des arguments de type chaîne de caractères Dans de nombreuses applications, on passe des arguments sous forme de chaîne de caractères exprimant une option. Par exemple, la fonction cor permet de calculer la corrélation au sens de Pearson, de Kendall ou de Spearman. Le type de corrélation est donné par l’argument method. Si on donne une valeur autre que l’une des trois valeurs citées, la fonction retourne une erreur. Ce test est réalisé par la fonction match.arg basée sur pmatch et qui comporte de plus un argument several.ok qui permet les choix multiples s’il vaut TRUE. Si l’argument formel est déclaré dans la signature de la méthode avec la liste des valeurs possibles, il suffit d’appeler match.arg de la façon suivant : method <- match.arg( method) # cf. fonction cor L’affectation du résultat de la fonction à la variable méthode permet de récupérer la chaîne (ou les chaînes) complètes puisque match.arg est basé sur pmatch (cf. supra). Sinon, il faut écrire method <- match.arg( method, vecteurListeDesOptions) Le vecteur des valeurs possibles peut contenir la valeur spéciale NA puisqu’elle signifie que la valeur existe mais n’est pas (encore) disponible. Par contre, si elle contient la valeur NULL, celle-ci est ignorée. Par défaut, match.arg prend la première valeur qu’il rencontre comme valeur par défaut. Gérer des fonctions comme argument Certaines procédures comme apply, sapply ou aggregate possèdent des arguments à qui on donne comme valeur une fonction soit anonyme, soit nommée. Dans la procédure elle-même, la variable correspondant à l’argument formel prend comme valeur la définition de la fonction. Cela signifie que de passer des fonctions en tant qu’argument d’une autre fonction ne pose aucun problème. Donc, si on écrira : f <- function( x, fun) { ... res <- fun( x) ... } et res vaudra sin( x) si fun=sin. Généralement, les procédures qui ont des arguments de type fonction, accepte comme valeur soit le nom de la fonction (cf. supra) soit une chaine de caractères codant le nom. Par exemple, apply( mat, 1, FUN=mean) et apply( mat, 1, FUN="mean") sont équivalents. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 56 Une fonction spéciale, match.fun, de R permet de faire coïncider la chaîne de caractères avec le nom de la fonction. On codera donc : fun <- match.fun( fun) res <- fun( x) Liste d’arguments de longueur indéfinie Supposons une procédure qui doit à un moment donné ordonner les lignes d’un data.frame selon certaines colonnes de celui-ci, le nombre et les noms des colonnes servant à l’ordonnancement variant d’un problème à l’autre, donc de l’appel d’une procédure à un autre appel. La procédure sera donc définie ainsi : f <- function( df.donnees, noms.col, # noms.cols : vecteur des noms des colonnes servant à ordonner les lignes de df.donnees. Si on écrit df.ordonne <- df.donnees[order( df.donnees[noms.cols]),] on n’obtient pas ce qu’on veut car order linéarise ses arguments. Il faudrait écrire : df.ordonne <- df.donnees[order( df.donnees[noms.cols[1]], df.donnees[noms.cols[2]], ...),] ce qui pose le problème de dénombrer les colonnes et de les passer comme arguments différenciés. La fonction do.call permet de résoudre ce problème. Elle comprend en premier argument une chaîne de caractères dénotant le nom de la fonction puis une liste dont chacun des membres est un argument de la fonction. La solution au problème ci-dessus est donc : df.ordonne <- df.ordonne[do.call( "order", as.list( df.donnees[noms.cols])),] Créer de nouveaux opérateurs binaires Il est parfois avantageux de créer un nouvel opérateur binaire au lieu d’une fonction ce qui permet d’écrire : a <- x %operateur% y à la place de a <- f( x, y). L’exemple d’opérateur binaire est celui de la multiplication de matrices %*%. Pour définir un nouvel opérateur binaire par exemple opB, on utilise la construction suivant : "%opB%" <- function( x, y) { corps de la fonction } Et l’appel se fera alors comme suit : a <- x %opB% y On affecte donc la fonction à une chaîne de caractères commençant et finissant par le symbole %. Exercices : A) Expliquez le code suivant # rot : est la rotation en degré de l’image dans le sens trigonométrique visuImage <- function( im, type=c( "rgb", "grey"), rot) { nb.dims <- dim( im) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 57 rot <- ifelse( missing( rot), 0, rot) type <- match.arg( type) stopifnot( nb.dims >= 2, nb.dims <= 3, is.numeric( rot)) if( type == "grey" & nb.dims == 3) { im <- image.rgb2hsv( im)[,"value"] imageView( im, rot, visu=TRUE, type="grey") } else if( nb.dims == 2) { imageView( im, rot, visu=TRUE, type="grey") } else { imageView( im, rot, visu=TRUE, type="rgb") } } B) Soit la fonction rgb2col permettant de transformer les triplets en chaines de caractères codant les couleurs (cf. exercices précédents), expliquez le code suivant stopifnot( dim( im) == 3, min( im) >= 0, max( im) <= 1) z <- rgb2col( im) c <- sort( unique( z)) z <- match( z, c) dim( z) <- dim( im)[c( 1, 2)] C) En vous basant sur l’exercice de codage des couleurs RGB en chaînes de caractères et l’exercice B, programmer les deux fonctions de l’exercice A, image.rgb2hsv et imageView. Tenir compte du fait que les valeurs de l’image sont quelconques et non comprises entre 0 et 1. Vous pouvez pour cette partie utiliser des utilitaires de R. La façon dont R évalue les arguments d’une fonction diffère de beaucoup d’autres langages comme le C ou le C++. R fait de l’évaluation tardive (lazzy evaluation), c’est-à-dire qu’il évalue la valeur d’un argument au moment où il en a besoin et non dans la procédure appelant la fonction juste avant d’appeler la fonction. Ce comportement permet de coder de manière simple les opérations à effectuer ou les conditions logiques dans des fonctions comme transform, with, by, subset, ou des arguments comme le subset de nombreuses fonctions statistiques. Il faut se souvenir que quand un argument possède une valeur par défaut résultat d’un calcul sur les autres arguments, le résultat peut être différent de celui qu’on attend quand on raisonne de manière classique18 au sujet des arguments. 18 Classique est pris ici dans son sens littéral, c’est-à-dire ‘appris en classe’ dans les cours dits de programmation qui ne sont en général que des cours d’utilisation d’un langage donné, par exemple C ou C++. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 58 Par exemple, C Corps de la fonction double exemple( x, y) { x = x^2; return( x + y); } Appel u = 2; exemple( u, sqrt( u)); Retour 5.414 R exemple <- function( x, y=sqrt( x)) { x <- x^2 x+y } u <- 2 exemple( u) 6 Explication : À l’entrée de la fonction (ligne 2), y vaut sqrt( 2) en C tandis qu’en R, y n’étant pas encore utilisé, cette variable n’est pas évaluée. Quand elle est utilisée pour le retour, x vaut 4, donc y (qui vaut sqrt( x)) vaut 2. Options Lors de l’exécution de scripts ou l’exécution pas à pas, ou lors de la visualisation, R suit des règles implicites de comportement qui ont été définies par des options globales qui peuvent être modifiées soit dans le script, soit dans les fichiers d’initialisation. Les lister toutes serait trop long. N’hésitez pas à consulter la page d’aide de la procédure options. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 59 Mise au point du script Quand on programme ligne par ligne, il est facile de mettre au point le code. Quand on programme des scripts ou des fonctions, c’est en général plus délicat. R propose une série de fonctions relativement simples pour suivre le déroulement d’un programme. Ce système est assez basique et de bas niveau et ne propose pas toutes les fonctionnalités des IDE d’autres langages. L’utilisation d’éditeurs de type Tinn-R ou de RStudio facilite grandement cette mise au point. Voir la documentation des éditeurs. Gestion générale history permet de lister les commandes précédentes. ls permet de lister les objets en mémoire. Des arguments permettent de ne lister les objets dont les noms correspondent à un certain motif (pattern). rm permet de supprimer des objets de la mémoire. On peut aussi filtrer les objets à supprimer. gc permet de restructurer et d’optimiser la mémoire après de nombreux ajouts et suppressions d’objets. Cette fonction est surtout utile quand on dispose de peu de mémoire. Diagnostic de sortie en erreur traceback permet de connaître approximativement l’endroit du code ayant généré l’erreur. Cependant, l’information fournie est souvent très imprécise. Des fonctions plus sophistiquées permettent d’analyser l’erreur. Pour y avoir accès, il faut configurer l’option error : options( error=recover) # accès à la fonction recover pour parcourir la fonction en erreur options( error=quote( dump.frames( dumpto, to.file))) # pour examiner les variables et l’environnement en erreur La seconde option est plus puissante que la première d’autant qu’elle permet de sauver les informations sur disque et de les examiner en différé. Pour examiner le résultat de la première option, on utilise la fonction recover, tandis que pour la seconde option, on utilise la fonction debugger. Se reporter à la documentation. Diagnostic en ligne debug( nomDeLaFonction) permet d’entrer dans un mode de mise au point ligne à ligne. On peut aussi insérer à n’importe quel endroit de la fonction la commande browser qui permettra d’entrer conditionnellement ou inconditionnellement dans ce mode de mise au point ligne par ligne. On peut aussi insérer des points d’arrêt grâce à la fonction trace. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 60 Quand on est dans le mode d’instruction ligne à ligne, on peut examiner les variables, tester des instructions, etc. n permet de passer à l’instruction suivante, c de continuer soit jusqu’à la rencontre d’une nouvelle instruction browser ou jusqu’à la fin de la fonction et Q de sortir. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 61 Les distributions R dispose d’un ensemble de fonctions permettant de calculer les fonctions de densité, les distributions cumulées ou les quantiles des principales distributions aléatoires, ainsi que de générer des échantillons des variables aléatoires associées. Il utilise une nomenclature standardisée pour les différents cas : dXXX pour les fonctions de masse (v.a. discrètes) ou de densité (v.a. continues) pXXX pour les distributions cumulées qXXX pour les quantiles rXXX pour les échantillons avec XXX le nom de la distribution. Les différentes distributions sont (valeur de XXX) : beta cauchy chisq exp f gamma lnorm norm signrank t tukey unif weibull wilcox binom geom hyper multinom nbinom pois 19 Distributions continues Distribution béta Distribution de Cauchy Distribution du khi-deux Distribution exponentielle Distribution du F de Fisher-Snédecor Distribution gamma Distribution log-normale Distribution normale (gaussienne) Distribution du rang signé de Wilcoxon Distribution de Student Distribution de l’amplitude studentisé (Tukey)19 Distribution uniforme Distribution de Weibull Distribution de la somme des rangs de Wilcoxon Distributions discrètes Distribution binomiale incluant la distribution de Bernouilli Distribution géométrique Distribution hypergéométrique Distribution multinomiale Distribution binomiale négative Distribution de Poisson Cette distribution est utilisé pour les tests post-hoc. R propose que les fonctions ptukey et qtukey. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 62 Les graphiques Les fenêtres graphiques R permet de visualiser les données sous forme de graphique dans une fenêtre qui est par défaut recyclée (c’est-à-dire, réinitialisée) à chaque appel sauf si on appelle les fonctions permettant d’ajouter du texte ou des graphiques à la fenêtre courante. Cependant, R permet aussi d’utiliser plusieurs fenêtres et de naviguer parmi elles. S’il existe des fonctions particulières pour chaque système d’exploitation, il existe aussi un ensemble de fonctions génériques qui sont regroupées dans la page dev. Il est possible de contrôler finement l’apparence du graphique par un ensemble de paramètres accessibles par la fonction par. De nombreux paramètres peuvent être localement contrôlés pour une fonction donnée par des paramètres spécifiques de cette fonction. Si par défaut, on visualise un seul graphique (qui peut être complexe, cf. les fonctions du package lattice) par fenêtre, on peut aussi définir des sous fenêtres individualisables (fonctions split.screen ou layout). Il existe d’autres packages classiques. On peut consulter avec profit le site http://gallery.r-enthusiasts.com/. Les graphiques de base Il existe une méthode de base, la fonction plot, qui permet de représenter une courbe sous deux formes : explicite plot( df$x, df$y, ...) # df.x peut être absent formule plot( y ~ x, data=df, ...) Cette fonction contrôle un certain nombre de paramètres comme l’apparence de la courbe, son épaisseur, sa couleur... Cette méthode de type S3 plot est aussi applicable à d’autres objets, comme les arbres issus d’une procédure de regroupement, par exemple agnes du package cluster, les résultats d’une analyse linéaire comme lm... On appelle plot( objet, ...) qui exécute en fait la méthode plot.<class( objet)>( objet, ... Voir la documentation des différents objets. On peut selon les besoins ajouter de nouveaux objets au graphique (fonction points), du texte (text, mtext), des légendes (legend). Il existe d’autres fonctions. Par exemple, la fonction hist permet de calculer des histogrammes et, éventuellement, de les visualiser. Les autres principales fonctions sont : boxplot boîtes à moustaches abline pour ajouter des lignes à un graphique ; à noter abline.lm existe rug pour ajouter des sous divisions à un axe. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 63 Le package lattice Le package lattice permet de tracer des graphes plus complexes notamment des graphiques par groupe. La principale fonction est xyplot. Supposons que df est data.frame comportant quatre colonnes poids, age, sexe, regions et qu’il est ordonné comme suit : df <- df[order( df$age),] et qu’on veuille tracer le poids en fonction de l’âge et du sexe. On pourra le faire en une seule fois par : xyplot( poids ~ age | sexe, data=df, ...) et on obtiendra un graphique par sexe. Et si on veut comparer les régions par sexe : xyplot( poids ~ age | sexe, data=df, groups=regions, ...) Attention : Quand on utilise les fonctions de lattice dans un script ou une fonction, il faut écrire : print( nomDeLaFonction( ...)) pour que l’affichage de fasse. Les autres fonctions principales de lattice sont20 : # Scatterplot matrix splom( ~ data.frame) bwplot( factor ~ numeric , . .) # Box and whisker plot qqnorm( numeric , . .) # normal probability plots dotplot( factor ~ numeric , . .) # 1-dim. Display stripplot( factor ~ numeric , . .) # 1-dim. Display barchart( character ~ numeric , . .) histogram( ~ numeric , . .) densityplot( ~ numeric , . .) # Smoothed version of histogram qqmath( numeric ~ numeric , . .) # QQ plot splom( ~ dataframe, . .) # Scatterplot matrix # Parallel coordinate plots parallel( ~ dataframe, . .) cloud( numeric ~ numeric * numeric, . .) # 3-D plot contourplot( numeric ~ numeric * numeric, . .) # Contour plot levelplot( numeric ~ numeric * numeric, . .) # Variation on a contour plot 20 Cf. "Using R for Data Analysis and Graphics, Introduction, Code and Commentary", J H Maindonald (http://cran.r-project.org/doc/contrib/usingR.pdf) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 64 Exercices récapitulatifs Exercice récapitulatif I Soit le tableau de données dans le fichier texte "exercice_test.txt". Lire ce fichier ; les séparateurs de colonnes sont la tabulation et la virgule est le séparateur décimal. Tous les sujets sont nés au XXème siècle. 1) Manipulation de la variable 'Age' Calculer l'âge moyen, l'âge minimum et l'âge maximum par groupe Transformer la variable quantitative 'Age' en tranches d'âge de 10 ans, centrées sur le milieu des dizaines, c'est-à-dire [30, 40[, [40, 50[ ... Ce facteur, qu'on nommera CLASSE.AGE sera ordonné. Vérifier que les sujets sont uniformément répartis dans les classes d'âge, indépendamment de leur groupe. 2) Sujets dont les données sont incomplètes Chercher les sujets pour lesquels on n'a pas les résultats du TEST.1 ; faire de même pour le TEST.2. Donner les nombre de sujets par groupe sous forme de décompte puis de tableau de contingence (réponse/non réponse). Calculer la moyenne et l'écart type du TEST.2 par groupe. Chercher les sujets pour lesquels on n'a pas les résultats d'au moins un des tests. 3) Imputation simplifiée des valeurs manquantes On fait l’hypothèse d’un effet âge (au sens des classes d’âge). On remplacera alors les valeurs manquantes par la moyenne des valeurs pour la classe d’âge du sujet dans son groupe. ATTENTION : il s’agit d’un exercice, la méthode d’imputation des valeurs manquantes peut être plus complexe que cela. 4) Travail sur un sous-ensemble, celui des 50 ans et plus. Le sélectionner à partir de la variable 'Age' puis à partir du facteur CLASSE.AGE. 5) Trouver en quelle année les examens ont été effectués. 6) On suppose que le groupe "G.1" est le groupe des patients ayant développé une certaine pathologie, que le groupe "G.3" est celui des sujets de référence (groupe de référence) et le groupe "G.2" celui de sujets susceptible de développer la pathologie. On veut estimer dans quelle mesure la variable SC peut prédire le risque de développer cette pathologie pour les patients G.2. La première étape est de vérifier que cette variable permet de discriminer correctement les patients G.1 des sujets sains G.3. Pour cela on utilisera la régression logistique sur le sous-ensemble des sujets G.1 et G.3 et plus particulièrement la fonction glm. On testera les performances de la discrimination par la méthode leave-one-out de validation croisée. Cette méthode consiste à calculer la paramètres du modèle sur N-1 sujets et d’appliquer ce modèle (cf. predict) sur le sujet non utilisé dans l’estimation du modèle en retirant le 1er sujet, puis le second… i) Programmer la séquence et donner le résultat sous forme d’un tableau de contingence. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 65 ii) Tracer la courbe ROC correspondante et calculer l’aire sous la courbe et l’intervalle de confiance (rechercher le package qui permet cela et le charger). Exercice récapitulatif II : Opérations sur des matrices creuses Les matrices creuses (sparse matrix) sont des matrices de grandes dimensions contenant une majorité de valeurs nulles. Pour réduire leur taille en mémoire, on utilise généralement un codage spécial sous forme d’un tableau à trois colonnes, la première contient les numéros de lignes, la seconde, les numéros de colonnes et la troisième, la valeur. R propose des outils pour traiter ces matrices. Il faut donc prendre cet exercice comme des exemples de manipulation avancée par R et non comme des outils de manipulation des matrices creuses. On représentera les matrices creuses par un data.frame à trois colonnes nommées respectivement LIGNE, COLONNE, VALEUR. Les deux dernières lignes du data.frame auront NA comme valeurs de LIGNE et COLONNE ; les VALEURs associées seront le nombre de lignes puis de colonnes. LIGNE COLONNE VALEUR 1 6 23,7 3 8 12 NA NA nb.lignes NA NA nb.colonnes ... Écrire les fonctions permettant 1) de récupérer les dimensions, 2) de transformer un data.frame représentant une matrice creuse en une véritable matrice, 3) de transformer une matrice en data.frame représentant une matrice creuse, 4) de calculer la trace de la matrice creuse (somme des éléments diagonaux), 5) de calculer la somme d’au moins deux matrices creuses et retournant une matrice creuse, 6) de calculer le produit de deux matrices creuses et retournant une matrice creuse. Éviter d’utiliser des boucles for. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 66 Solution des exercices Exercice page 14 : Opérations logiques avec la valeur NA La valeur NA signifie qu'il existe une valeur inconnue, c'est-à-dire qui peut être TRUE ou FALSE. Ainsi, pour l'opérateur | (‘ou’), on a : FALSE | NA ⇔ { FALSE | TRUE = TRUE ; FALSE | FALSE = FALSE} = NA TRUE | NA ⇔ { TRUE | TRUE = TRUE ; TRUE | FALSE = TRUE } = TRUE NA | NA ⇔ { FALSE | NA, TRUE | NA} = NA On peut appliquer les mêmes règles pour tous les opérateurs logiques. Exercice page 26 : Exercice : On désire centrer les colonnes d’une matrice (par exemple mat <- matrix( runif( 200), ncol=10)) sur leur médiane. 1) écrire le code sans utiliser ni apply ni sweep ; 2) écrire le code en utilisant apply ; 3) écrire le code en utilisant sweep. Faire la même chose avec les lignes et comparer. # # Traitement des colonnes # med.col <- apply( mat, 2, median) # # Pas d'utilisation de sweep ni apply #A: # => Creation d'une matrice de meme dimension que mat et dont les lignes sont les valeurs mat.med <- matrix( med.col, nrow=nrow( mat), ncol=ncol( mat), byrow=TRUE) mat.res.1A <- mat - mat.med #B: # => Boucles mat.res.1B <- mat for( n.row in 1:nrow( mat)) { for( n.col in 1:ncol( mat)) { mat.res.1B[n.row,n.col] <- mat.res.1B[n.row,n.col] - med.col[n.col] } } Programmer R - Version 1.2 (mars 2013) F. Aubry p. 67 # # Utilisation d'apply mat.res.2 <- t( apply( mat, 1, FUN=function( x) x - med.col)) # apply renvoie un tableau de dim( mat)[1] colonnes d'ou la transposition # # Utilisation de sweep mat.res.3 <- sweep( mat, 2, med.col) # # Verifications - print( identical( ... si a partir d'un script identical( mat.res.1A, mat.res.1B) identical( mat.res.1A, mat.res.2) identical( mat.res.1A, mat.res.3) # # Traitement des lignes # med.row <- apply( mat, 1, median) mat.res.4 <- mat - med.row # # Utilisation d'une boucle a fin de verifications mat.res.4B <- mat for( n.col in 1:ncol( mat)) { for( n.row in 1:nrow( mat)) { mat.res.4B[n.row,n.col] <- mat.res.4B[n.row,n.col] - med.row[n.row] } } # Verification identical( mat.res.4, mat.res.4B) Explication du code pour les lignes : med.row est un vecteur de longueur le nombre de lignes de mat. R retire donc les valeurs contenues dans med.row de la première colonne de mat puisque R parcourt les matrices colonnes par colonnes. Plus généralement, R parcourt un tableau en parcourant sa première dimension, puis sa seconde... Quand R parcourt la seconde colonne de mat, il n'a plus de valeurs de med.row. Il effectue alors le recyclage (recycling) des valeurs de med.row. De ce fait : mat.res.4 <- mat - med.row est équivalent à : mat.res.4 <- mat - matrix( rep( med.row, ncol( mat)), nrow=nrow( mat)) ou, en utilisant le recyclage : : mat.res.4 <- mat - matrix( med.row, nrow=nrow( mat), ncol=ncol( mat)) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 68 Donc, dans le cas des lignes, il est inutile d'utiliser apply ou sweep. Exercice page 31 : Soit un data.frame d.f <- data.frame( RID=paste( "sujet", 1:12, sep="."), matrix( rbeta( 240, 3, 5), nrow=12, ncol=20, dimnames=list( NULL, paste( "test", 1:20, sep=".")))) A) on veut centrer les résultats des tests sujet par sujet, sur leur moyenne ; - écrire le code sans utiliser sweep ni boucles ; - écrire le code en utilisant sweep et vérifier qu’on obtient les mêmes résultats dans les deux cas toujours sans boucles for. B) faire la même chose mais colonne par colonne. Ce qui a été dit dans l’exercice précédent pour les matrices est applicable aux data.frame. Exercice page 36 : # On construit un tableau de données d.f <- data.frame( X=runif( 20, min=0, max=5)) d.f <- transform( d.f, Y=X + rnorm( 20, sd=0.2)) d.f <- transform( d.f, MEAN.Y=mean( Y)) # On compare les résultats des régressions suivantes lm.std <- lm( Y ~ X, d.f) lm.centre <- lm( Y - MEAN.Y ~ X, d.f) lm.offset <- lm( Y ~ X + offset( MEAN.Y), d.f) lm.zero <- lm(Y ~ 0 + X + MEAN.Y, d.f) Utiliser la fonction summary pour récupérer les résultats et interprétez-les. lm.std : on estime l’ordonnée à l’origine et la pente. lm.centre : on estime l’ordonnée à l’origine et la pente des données dépendantes centrées. On peut d’ailleurs constater que l’ordonnée à l’origine est égale à celle de lm.std moins la moyenne de Y. lm.offset : donne les mêmes résultats que lm.centre. lm.zero : on fait une régression multiple avec deux régresseurs, X et MEAN.X en forçant l’ordonnée à l’origine à zéro. On obtient donc les pentes pour X et pour MEAN.X Exercice pages 43 et 44 : A) Soit un data.frame comportant deux colonnes, l’une la date de naissance, l’autre la date d’examen sous forme de chaîne de caractères jj/mm/aa où aa est l’année sur deux chiffres. Calculer l’âge en année de chaque sujet lors de l’examen (cf. as.Date). Comment gérer le changement de siècle et le fait que certains sujets sont nés avant 1970 ? Comment gérer le fait que certains sujets n’ont pas encore subi d’examen et donc que la date est codée par NA ? Programmer R - Version 1.2 (mars 2013) F. Aubry p. 69 B) Soit un tableau (array) à 3 dimensions mesurant plusieurs fois dans le temps divers grandeurs (lignes), dans différentes conditions (colonnes), la répétition des mesures étant la 3ème dimension (temps) : mes.array <- array( 0, dim=c( nb.variables, nb.conditions, nb.temps) … vect.temps <- # Temps des mesures i) On veut vérifier que les mesures sont stables dans le temps. On va donc faire une régression individuelle sur la variable temps de chacune des variables dans chacune des conditions et construire la matrice variablesXconditions de p.value de la pente de régression. Utiliser la fonction apply pour cela. ii) On suppose que la stabilité est vérifiée, on veut alors vérifier l’effet des conditions en considérant les mesures dans le temps comme des occurrences indépendantes de la même mesure. On va donc construire un vecteur des p.value, un élément par variable. Utiliser aussi apply. N.B. : la fonction à utiliser est lm et on retourne la p.value associée à la table anova obtenue par la fonction anova. C) Soit une matrice de 3 colonnes et 256 lignes dont les valeurs sont comprises entre 0 et 1 correspondant à une LUT (look-up table), la première colonne correspond donc à l’intensité du rouge, la seconde, du vert et la troisième du bleu. R code les couleur sous forme de chaines de caractères en hexadécimal commençant par le caractère dièse ("#"), les deux caractères suivant étant le niveau de rouge, codé de 0 à FF (équivalent du 255 décimal), ensuite on a le niveau du vert puis celui du bleu. Écrire les lignes de code permettant de transformer la matrice en couleurs pour R (ne pas utiliser de boucles for). A) Pour la première partie de la question, voir l’exercice récapitulatif. Il suffit ensuite de transformer les deux colonnes de chaînes de caractères en date par la fonction as.Date et de faire la différence par la fonction difftime. On obtiendra la différence en jours. Pour avoir l’âge approximatif, on divisera cette différence par 365,25 durée approximative des années. Gestion des NA : date.exam.NA <- which( is.na( date.exam), arr.ind=TRUE) date.exam[!date.exam.NA] … # cf. supra B) i) Stabilité des mesures dans le temps. stabilite.mes <- apply( mes.array, c( 1, 2), FUN=function( x.t) { lm.mes <- lm( x.t ~ vect.temps) anova( lm.mes)["vect.temps","Pr(>F)"] }) Commentaires : 1) anova renvoie un objet héritant de data.frame dont les colonnes sont nommées : Df : nombre de degrés de liberté Sum Sq : somme des carrés Mean Sq : moyenne des carrés F: valeur du F Pr(>F) : p.value associée Programmer R - Version 1.2 (mars 2013) F. Aubry p. 70 et les ont pour nom les variables (sous la forme X), les interactions (par exemple, X:Y pour l’interaction entre X et Y) et la dernière ligne "Residuals", les résidus. 2) Quand on consulte la page d’aide de lm, l’appel est : lm( formula, data… or dans la solution ci-dessus, aucun valeur n’est donnée à l’argument data. Cela provient du fonctionnement de R pour l’évaluation de la formule. En effet, R évalue la formule dans l’environnement courant de l’utilisation de la formule, c’est-à-dire à l’intérieur de la fonction lm pour notre cas. Si l’argument data n’est pas spécifié, R va tenter de trouver les objets impliqués dans la formule dans la fonction appelant lm puis, s’il ne les trouve pas, la fonction d’un niveau supérieur. Donc dans notre cas, il trouve l’objet x.t dans la fonction appelant lm tandis que vect.temps dans celle qui utilise apply. Les objets sont donc parfaitement définis pour lm. Si data a une valeur, lm va commencer par essayer d’évaluer les objets dans l’environnement de cette valeur. Exemple : vect.temps <- c( …) d.f <- data.frame( Y=…) # Une seule colonne Y lm.mes <- lm( Y ~ vect.temps, data=d.f) L’objet Y existe dans l’environnement de d.f, c’est le vecteur colonne associé à la colonne Y du data.frame, tandis que vect.temps n’existe pas. Par contre vect.temps existe dans l’environnement d’appel. L’analyse peut donc être faite. Il est à noter que R fonctionne exactement de la même manière pour l’argument subset qu’on retrouve dans de nombreuses fonctions, ainsi que l’évaluation des conditions dans les fonctions which et subset ou d’expression dans transform pour ne citer que ces fonctions. 3) L’argument MARGIN de apply est c( 1, 2), ce qui signifie qu’on passe à FUN le vecteur mes.array[i,j,]. Cf. documentation de apply. ii) effet des conditions de mesures # On crée un facteur dont le nombre de niveaux est le nombre de conditions de mesures cdn.mes <- factor( … # avec nlevels( cdn.mes == nb.conditions) cdn.mes <- rep( cd.mes, nb.temps) # Utilisation de apply effet.cdn <- apply( mes.array, 1, FUN=function( mes) { mes.vect <- as.vector( mes) lm.mes <- lm( mes.vect ~ cdn.mes) … Commentaire : apply passe à la fonction une matrice de nb.conditions lignes et nb.temps colonnes. as.vector transforme cette matrice en un vecteur colonne par colonne. Du point de vue des conditions, on a donc les premières mesures des nb.conditions, puis les secondes… C’est la raison pour laquelle on crée un vecteur (facteur) de conditions en répétant nb.temps fois les conditions. C) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 71 i) L’un des codes possibles est le suivant (pour en faciliter la lecture, j’ai numéroté les lignes) 1 hexadecimal.code <- c( 0:9, "A", "B", "C", "D", "E", "F") 2 hexadecimal.table <- paste( rep( hexadecimal.code, each=16), rep( hexadecimal.code, 16), sep="") 3 im <- floor( im * 256) 4 im.col <- hexadecimal.table[im + 1] 5 dim( im.col) <- dim( im) 6 im.col <- apply( im.col, c( 1, 2), FUN=paste, collapse="") 7 im.col <- paste( "#", im.col, sep="") 8 dim( im.col) <- dim( im)[1:2] 1 et 2 : construction du vecteur des 256 codes hexadécimaux des valeurs 0 à 255. 3 : les plans couleurs sont normalisés entre 0 et 255 4 : transformation de l’image RGB en 1 vecteur de codes hexadécimaux ; le + 1 est nécessaire puisqu’en R, les indices commencent à 1 donc le code du zéro est le premier élément de hexadecimal.table. 5 : transformation du vecteur en un tableau de la même dimension que l’image initiale. En général, il faut éviter ce type de codage explicite mais ici, il se justifie car on sait que im.col est la linéarisation de im. Cependant, on aurait pu écrire : im.col <- array( im.col, dim=dim( im)) mais cela aurait créé un nouvel objet. 6 : on crée un matrice de dimension dim( im)[c( 1, 2)] concaténant les codes des trois couleurs. 7 : on ajoute le symbole "#", symbole préfixant les couleurs. 8 : l’utilisation de paste a de nouveau linéarisé la matrice, il faut donc recréer l’image. ii) On peut aussi tirer partie de la linéarisation sachant que R parcourt d’abord la 1ère dimension, puis la seconde… De ce fait, les éléments de im.col correspondant au plan rouge vont des indices 1 à prod( dim( im)[1:2]), le plan vert va de prod( dim( im)[1:2]) + 1 à 2 * prod( dim( im)[1:2]) et le plan bleu de 2 * prod( dim( im)[1:2]) + 1 à prod( dim( im)). Le code remplaçant les lignes 5 à 7 ci-dessus sera alors : pr.dim <- dim( im)[1:2]) im.col <- paste( "#", col.im[seq( from=1, length.out=pr.dim], col.im[seq( from=pr.dim + 1, length.out=pr.dim], col.im[seq( from=2 * pr.dim) + 1, length.out=pr.dim], sep=""). Commentaire: L’utilisation de l’argument length.out sans argument to ni by provoque la création d’une séquence d’entiers partant de from et de longueur length.out. Cet appel est plus simple que si on avait explicitement donné les valeurs de from et to. seq peut aussi générer des séquences de nombres complexes. Cependant, certaines combinaisons d’arguments ne sont pas valides. Note : Il s’agit d’un exercice. R fournit les utilitaires nécessaires notamment dans le package grDevices. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 72 Exercices pages 53 et 54 : A) Expliquez le code suivant # rot : est la rotation en degré de l’image dans le sens trigonométrique 1 visuImage <- function( im, type=c( "rgb", "grey"), rot) { 2 nb.dims <- dim( im) 3 rot <- ifelse( missing( rot), 0, rot) 4 type <- match.arg( type) 5 stopifnot( nb.dims >= 2, nb.dims <= 3, is.numeric( rot)) 6 if( type == "grey" & nb.dims == 3) { 7 im <- image.rgb2hsv( im)[,"value"] 8 imageView( im, rot, visu=TRUE, type="grey") 9 } else if( nb.dims == 2) { 10 imageView( im, rot, visu=TRUE, type="grey") 11 } else { 12 imageView( im, rot, visu=TRUE, type="rgb") 13 } 14 } B) Soit la fonction rgb2col permettant de transformer les triplets en chaines de caractères codant les couleurs (cf. exercices précédents), expliquez le code suivant 1 stopifnot( dim( im) == 3, min( im) >= 0, max( im) <= 1) 2 z <- rgb2col( im) 3 c <- sort( unique( z)) 4 z <- match( z, c) 5 dim( z) <- dim( im)[c( 1, 2)] C) En vous basant sur l’exercice de codage des couleurs RGB en chaînes de caractères et l’exercice B, programmer les deux fonctions de l’exercice A, image.rgb2hsv et imageView. Tenir compte du fait que les valeurs de l’image sont quelconques et non comprises entre 0 et 1. Vous pouvez pour cette partie utiliser des utilitaires de R. A) Pour faciliter la lecture, j’ai numéroté les lignes. 1 : appel de la fonction. L’argument type peut prendre deux valeurs, les autres sont libres. 2 : on récupère les dimensions de l’argument im qui doit donc être un tableau de valeurs. 3 : si l’argument rot n’est pas défini (missing), on lui donne la valeur 0 sinon on conserve sa valeur 4 : on teste la valeur de l’argument type (match.arg) qui dans notre cas ne peut prendre qu’une des deux valeurs prévues. Si type n’est pas défini dans l’appel, il prend la première valeur (grey) par défaut. Si on voulait qu’il puisse prendre plusieurs valeurs, il aurait fallu donner la valeur TRUE à l’argument several.ok de match.arg. Sans ce test, type peut prendre n’importe quelle valeur et vaudra c( "rgb", "grey") si on ne donne pas de valeurs à type lors l’appel. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 73 5 : la fonction sort en erreur si rot n’est pas numérique ou si le nombre de dimension du tableau n’est pas 2 ou 3. 6 à 8 : si le tableau est 3D et que le type est grey, on transforme l’image de type RGB et image de type HSV et on ne garde que la composante brillance (en anglais value) qui donne les niveaux de gris et on visualise l’image. 9 et 10 : si le nombre de dimension est 2, on a une image en niveaux de gris et on force l’affichage selon ce mode. 12 : image 3D donc RGB. On force l’affichage dans ce mode. B) On suppose que la fonction rgb2col est celle codée dans l’exercice précédent. 1 : on teste qu’il s’agit d’une image 3D codée entre 0 et 1. 2 : on la transforme en image 2D des couleurs 3 : on cherche les différentes couleurs utilisées de manière non ambigüe (unique) et on les trie ; le tri est celui de l’ordre alphanumérique mais comme les chiffres sont codés avant les lettres, c’est aussi l’ordre de valeurs. On construit ainsi la table des couleurs à utiliser (LUT ou look-up table). 4 : on cherche la position de chaque pixel dans la LUT et on crée ainsi une table des index. 5 : la table des index est un vecteur résultant de la linéarisation de l’image, on rétablit alors l’image. C) i) Trouver les packages et les fonctions qui pourront servir. Localement : ??mot.cle ou ??"mot cle" si le mot clé contient des blancs ou des caractères pouvant être interprétés autrement (par exemple, des opérateurs ou des caractères spéciaux). Sur le site de R : RSiteSearch( "mot cle") ii) Code possible de image.rgb2hsv : image.rgb2hsv <- function( r, g, b, i.min, i.max) { require( grDevices) stopifnot( length( dim( r)) %in% c( 2, 3), switch( length( dim( r)), NA, !missing( g) & !missing( b), missing( g) & missing( b))) if( length( dim( r) == 2)) { stopifnot( length( dim( g)) == 2, identical( dim( r), dim( g)), length( dim( b)) == 2, identical( dim( r), dim( b))) im<- array( 0, c( dim( r), 3)) im[,,1] <- r; im[,,2] <- g; im[,,3] <- b } else { im <- r Programmer R - Version 1.2 (mars 2013) F. Aubry p. 74 } if( missing( i.min)) i.min <- min( im) if( missing( i.max)) i.max <- max( im) i.min <- rep( i.min, 3)[1:3]; i.max <- rep( i.max, 3)[1:3] # Valeurs normalisées : floor( 256 * (x - min) / (max -min)) im <- sweep( im, c( 1, 2), i.min) im <- floor( 256 * sweep( im, c( 1, 2), i.max - i.min, "/") res <- rgb2hsv( im) names( res) <- c( "hue", "saturation", "value") res } Pour imageView, on peut le coder comme suit : - Attention, il ne s’agit que d’une proposition imageView <- function( im, rot, visu=TRUE, type=c( "rgb", "grey"), fill=0) { # Ce code est un exemple. Il n’est pas optimum et ne doit pas être utilisé tel quel # dans les applications réelles .simple.rot <- function( image, rot, fill=0) { mat.rot <- matrix( c( cos( rot), sin( rot), -sin( rot), cos( rot)), ncol=2, nrow=2) dim.im <- dim( image) x.centre <- (dim.im[1] - 1) / 2 y.centre <- (dim.im[2] - 1) / 2 im.rot <- array( fill, dim=dim.im) if( length( dim.im) == 1) image <- array( image, c( dim.im, 1)) for( y in 1:dim.im[2]) { for( x in 1:dim.im[1]) { new.coord <- round( mat.rot %*% c( x - 1 -x.centre, y - 1 y.centre)) + 1 if( any( new.coord < 1) || any( new.coord > dim.im[1:2])) next for( z in 1:dim.im[3]) { im.rot[new.coor[1], new.coord[2], z] <- image[x, y, z] } } } dim( im.rot) <- dim.im } Programmer R - Version 1.2 (mars 2013) F. Aubry p. 75 type <- match.arg( type) if( !missing( rot) && rot != 0) im <- .simple.rot( im, rot, fill) im <- im / max( im) if( type == "rgb") { if( length( dim( im)) == 2) { im <- rep( im, 3) dim( im) <- c( dim( im), 3) } } else { if( dim( im) == 3) { im <- rgb2hsv( im)[,,"v"] } } im <- rgb2col( im) if( visu) { lut.c <- sort( unique( im)) z.im <- match( im, lut.c) dim( z.im) <- dim( im)[c( 1, 2)] image( z.im, col=lut.c) } invisible( im) } Exercice récapitulatif I Soit le tableau de données dans le fichier texte "exercice_test.txt". Lire ce fichier ; les séparateurs de colonnes sont la tabulation et la virgule est le séparateur décimal. Tous les sujets sont nés au XXème siècle. 1) Manipulation de la variable 'Age' Calculer l'âge moyen, l'âge minimum et l'âge maximum par groupe Transformer la variable quantitative 'Age' en tranches d'âge de 10 ans, centrées sur le milieu des dizaines, c'est-à-dire [30, 40[, [40, 50[ ... Ce facteur, qu'on nommera CLASSE.AGE sera ordonné. Vérifier que les sujets sont uniformément répartis dans les classes d'âge, indépendamment de leur groupe. 2) Sujets dont les données sont incomplètes Programmer R - Version 1.2 (mars 2013) F. Aubry p. 76 Chercher les sujets pour lesquels on n'a pas les résultats du TEST.1 ; faire de même pour le TEST.2. Donner les nombre de sujets par groupe sous forme de décompte puis de tableau de contingence (réponse/non réponse). Calculer la moyenne et l'écart type du TEST.2 par groupe. Chercher les sujets pour lesquels on n'a pas les résultats d'au moins un des tests. 3) Imputation simplifiée des valeurs manquantes On fait l’hypothèse d’un effet âge (au sens des classes d’âge). On remplacera alors les valeurs manquantes par la moyenne des valeurs pour la classes d’âge du sujet dans son groupe. ATTENTION : il s’agit d’un exercice, la méthode d’imputation des valeurs manquantes peut être plus complexe que cela. 4) Travail sur un sous-ensemble, celui des 50 ans et plus. Le sélectionner à partir de la variable 'Age' puis à partir du facteur CLASSE.AGE. 5) Trouver en quelle année les examens ont été effectués. 6) On suppose que le groupe "G.1" est le groupe des patients ayant développé une certaine pathologie, que le groupe "G.3" est celui des sujets de référence (groupe de référence) et le groupe "G.2" celui de sujets susceptible de développer la pathologie. On veut estimer dans quelle mesure la variable SC peut prédire le risque de développer cette pathologie pour les patients G.2. La première étape est de vérifier que cette variable permet de discriminer correctement les patients G.1 des sujets sains G.3. Pour cela on utilisera la régression logistique sur le sous-ensemble des sujets G.1 et G.3 et plus particulièrement la fonction glm. On testera les performances de la discrimination par la méthode leave-one-out de validation croisée. Cette méthode consiste à calculer la paramètres du modèle sur N-1 sujets et d’appliquer ce modèle (cf. predict) sur le sujet non utilisé dans l’estimation du modèle en retirant le 1er sujet, puis le second… i) Programmer la séquence et donner le résultat sous forme d’un tableau de contingence. ii) Tracer la courbe ROC correspondante et calculer l’aire sous la courbe et l’intervalle de confiance (rechercher le package qui permet cela et le charger). Lecture du tableau : tab <- read.table( file="exercice_test.txt", sep="\t", dec=",", header=TRUE, quote='"', comment.char="") En effet, il est nécessaire de modifier l’argument quote puisque les commentaires contiennent des apostrophes, comme il faut aussi modifier l’argument comment.char puisqu’un des commentaires contient un dièse. Il faut noter qu’on aurait pu écrire quote="\"", le backslash étant un caractère d’échappement. # On peut s’assurer que les colonnes sont du type correct sapply( tab, class) # On peut formater correctement chaque colonne : tab$Groupe <- as.factor( tab$Groupe) tab$COMMENTAIRE <- as.character( tab$COMMENTAIRE) tab$DDN <- as.character( tab$DDN) # cf. infra pour convertir en Date Programmer R - Version 1.2 (mars 2013) F. Aubry p. 77 N.B. : Personnellement, je conseille de toujours mettre dans read.table, l’argument stringsAsFactor à FALSE pour éviter la conversion des chaînes de caractères en facteur, à moins qu’on soit sûr que toutes les chaînes de caractères du fichier correspondent à des niveaux de facteur. Dans le cas de notre, exemple, ceci est faux pour les colonnes COMMENTAIRE et DDN. 1) Manipulation de la variable ‘Age’ Calcul de la moyenne, du minimum et du maximum par groupe . Solution 1 : age.moyen <- aggregate( tab$Age, by=list( tab$Groupe), mean) # ecriture equivalent utilisant une formule age.moyen <- aggregate( Age ~ Groupe, tab, mean) # On fait alors de même pour l’$age minimum avec la fonction min et l’âge maximum avec max . Solution 2 # calcul en une seule fois age.desc <- aggregate( tab$Age, by=list( tab$Groupe), FUN=function( x) c( MOYEN=mean( x), R=range( x))) Notons alors que le résultat nous donne : > str( age.desc) 'data.frame': 3 obs. of 2 variables: $ Group.1: chr "G.1" "G.2" "G.3" $x : num [1:3, 1:3] 49.5 48.6 51 37 20 ... ..- attr(*, "dimnames")=List of 2 .. ..$ : NULL .. ..$ : chr "MOYEN" "R1" "R2" C’est-à-dire qu’on obtient in data.frame ayant deux colonnes, l’une Group.1 (et plus généralement n + 1, dont les premières sont nommées de Group.1 à Group.n quand il y a n facteurs de groupement) et la dernière nommée x contenant le résultat. Il est donc préférable de reformater la sorte et de renommer les colonnes des facteurs de groupement : age.desc <- cbind( age.desc[,"Group.1"], as.data.frame( age.desc$x)) names( age.desc)[1] <- "Groupe" Transformation en facteurs de classes d’âge dizaine.min <- signif( min( tab$Age), 1) dizaine.max <- 10 * ceiling( max( tab$Age) / 10) cut.points <- seq( dizaine.min, dizaine.max, by=10) labels.age <- paste( "CA", (tail( cut.points, -1) + head( cut.points, -1)) / 2, sep=".") tab <- transform( tab, CLASSE.AGE=cut( Age, breaks=cut.points, labels=labels.age, include.lowest=TRUE, right=FALSE, ordered=TRUE)) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 78 Répartition uniforme par classe d’âge nb.classes.age <- nlevels( tab$CLASSE.AGE) prop.test( as.vector( table( tab$CLASSE.AGE)), rep( nrow( tab), nb.classes.age), correct=TRUE) 2) Données manquantes . Quels sujets n’ont pas de résultat pour TEST.1 # # Méthode 1 # # Indicateur logique na.test.1 <- which( is.na( tab$TEST.1)) # Numéro ligne na.test.1 <- which( is.na( tab$TEST.1), arr.ind=TRUE) # Rid coorespondants rid.na.1 <- tab$RID[na.test.1] # # Méthode 2 # rid.na.1 <- tab$RID[which( is.na( tab$TEST.1))] On fait de même pour TEST.2. À noter qu’on ne peut pas utiliser le test logique tab$TEST.1 == NA mais qu’on emploie une fonction spéciale is.na car l’égalité n’a pas de sens avec NA puisque NA signifie n’importe quelle valeur. De même, il est impossible d’écrire x == NULL mais qu’il faut utiliser une fonction spéciale is.null ; il existe aussi des fonctions spéciale pour tester si un nombre est fini (is.finite), infini (is.infinite) ou si une variable n’est pas un nombre (is.nan). . Décompte du nombre de valeurs manquantes par groupement nb.na.1 <- aggregate( tab$TEST.1, by=list( tab$Groupe), FUN=function( x) sum( is.na( x))) Note : on peut utiliser la formulation d’aggregate avec une formule mais dans ce cas, il faut lui indiquer que faire avec les valeurs NA qui sont supprimées par défaut. Il faut donc écrire : nb.na.1 <- aggregate( TEST.1 ~ Groupe, tab, FUN=function( x) sum( is.na( x)), na.action=na.pass) # décompte par table de contingence table( tab$Groupe, is.na( tab$TEST.1)) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 79 . Moyenne et écart type par groupe pour le TEST.2 Il faut tenir compte des éventuelles valeurs manquantes. # Utilisation d’aggregate avec formule des.test.2 <- aggregate( TEST.2 ~ Groupe, tab, FUN=function( x) c( MOYENNE=mean( x), ET=sd( x))) # Utilisation d’aggregate par liste de facteurs de groupement des.test.2 <- aggregate( tab$TEST.2, by=list( tab$Groupe), FUN=function( x) c( MOYENNE=mean( x, na.rm=TRUE), ET=sd( x, na.rm=TRUE))) Dans le cas de l’utilisation de la formule, on utilise le comportement par défaut d’aggregate face aux valeurs manquantes (na.omit). Sur la structure du résultat, cf. ce qui a été dit précédemment, à la différence que les colonnes des facteurs de groupement ont le nom du facteur et non un nom par défaut. Dans l’agrégation par liste, aggregate passe à la fonction toutes les valeurs dont les NA, il faut donc dire aux fonctions de traitement (mean et sd) quoi faire de ces valeurs. En effet, logiquement la moyenne, la variance, la médiane... d’un vecteur contenant des NA n’est pas définie et vaut NA. # . Sujets dont au moyen un des tests n’a pas de valeur Conditon logique sujets.no.val <- is.na( tab$TEST.1) | is.na( tab$TEST.2) # sujets.val <- complete.cases( tab$TEST.1, tab$TEST.2) # # Vérification identical( sujets.no.val, !sujets.val) Note : R propose une fonction complete.cases qui permet de rechercher les lignes d’un data.frame qui n’ont aucune valeur manquante. 3) Imputation des valeurs manquantes # # # Calcul des moyennes mean.TEST.1 <- aggregate( TEST.1 ~ Groupe + CLASSE.AGE, tab, mean) # Calcul de la table réelle # Création d’un data.frame contenant toutes les combinaisons tab.mean <- expand.grid( Groupe=levels( tab$Groupe), CLASSE.AGE=levels( tab$CLASSE.AGE)) # On ajoute les lignes (combinaisons de facteurs) manquantes # On fusionne les deux data.frame en utilisant les groupes et les classes d’âge comme pivot Programmer R - Version 1.2 (mars 2013) F. Aubry p. 80 # On indique de conserver les lignes de tab.mean (suffixe .y dans les arguments de merge) si elles n’existent pas dans le premier data.frame mean.TEST.1 <- merge( mean.TEST.1, tab.mean, by=c( "Groupe", "CLASSE.AGE"), all.y=TRUE) # Construction de la matrice mean.TEST.1 <- matrix( mean.TEST.1$TEST.1, ncol=nlevels( tab$Groupe), dimnames=list( levels( tab$CLASSE.AGE), levels( tab$Groupe))) # # Imputation des valeurs # # Indices des sujets dont la valeur de TEST.1 est NA id.NA <- which( is.na( tab$TEST.1), arr.ind=TRUE) # Imputation des valeurs tab$TEST.1[id.NA] <- sapply( id.NA, FUN=function( n.sujet) with( tab, mean.TEST.1[CLASSE.AGE[n.sujet],Groupe[n.sujet]])) Identique pour TEST.2 4) Sélection des sujets de 50 ans et plus tab.suj.p50 <- subset( tab, CLASSE.AGE >= "C.55") Le facteur CLASSE.AGE étant ordonnée, les opérateurs de comparaison d’ordre (<, >, <= et >=) ont un sens. Si le facteur n’était pas ordonné, seuls les opérateurs d’égalité (==) ou de différence (!=) des valeurs pourraient être utilisés. 5) Année des examens La solution qui semble la plus simple pour traduire les chaînes de caractères exprimant la date de naissance en objet Date serait d’utiliser la fonction de conversion as.Date et donnant comme format %d/%m/%y. Malheureusement, celle-ci se base sur les normes POSIX 2004 et 2008 qui stipulent que pour les années comprises entre 00 et 68, l’année est préfixée par 20 et au-delà, elle est préfixée par 19. Donc, 59 donne l’année 2059 tandis que 88 donne l’année 1988. Il faut donc commencer par corriger les dates de naissance. # Ajout du prefixe 19 à l’année tab$DDN <- sapply( strsplit( tab$DDN, "/"), FUN=function( ddn) { ddn[3] <- paste( "19", ddn[3] , sep="") paste( ddn, collapse="/") }) # Transformation en objet de type Date tab$DDN <- as.Date( tab$DDN) # Nombre de jours approximatifs entre naissance et examen Programmer R - Version 1.2 (mars 2013) F. Aubry p. 81 nb.jours <- tab$Age * 365 + floor( tab$Age / 4) # Ajout des jours à la date de naissance # on ajoute le nombre de jours à la date de naissance date.exam <- tab$DDN + as.difftime( nb.jours, units="days") # Récupération de l’année et transformation en entier tab$DATE.EXAM <- as.integer( as.character( date.exam, "%Y")) 6) i) Prédiction # Selection du sous ensemble à analyse tab.11 <- droplevels( subset( tab, Groupe %in% c( "G.1", "G.3"))) # On veut s’assurer que le niveau de référence sera G.3 tab.11$Groupe <- relevel( tab.11$Groupe, ref="G.3") # leave-one-out + prediction perf.SC <- sapply( 1:nrow( tab.11), FUN=function( n.row) { res.glm <- glm( Groupe ~ SC, family=binomial, data=tab.11[-n.row,]) c( LOGIT=predict( res.glm, newdata=tab.11[n.row,], type="link"), RESPONSE=predict(res.glm, newdata=tab.11[n.row,], type="r")) }) perf.SC <- data.frame( tab.11[,c( "RID", "Groupe")], as.data.frame( t( perf.SC))) # La colonne LOGIT.1 contient la valeur de la fonction de lien (logit) # Elle est positive si le sujet est estimé appartenir à G.1 (groupe d’intérêt) et négative autrement # La colonne RESPONSE.1 donne la probabilité estimée d’appartenir au groupe G.1 perf.SC <- transform( perf.SC, Groupe.Estime=ifelse( perf.SC$LOGIT.1 > 0, "G.1", "G.3")) # On dit de Groupe.estime est un facteur dont les niveaux sont dans le même sens de Groupe perf.SC$Groupe.Estime <- factor(perf.SC$Groupe.Estime, levels=c( "G.3", "G.1"), labels=c("G.3", "G.1")) # # Construction de la table de contingence # with( perf.SC, table( Groupe, Groupe.Estime)) ii) courbe ROC require( pROC) # 3 manières équivalentes de construire la courbe roc.1 <- roc( Groupe ~ LOGIT.1, perf.SC) roc.2 <- roc( perf.SC$Groupe, perf.SC$LOGIT.1) roc.3 <- roc( controls=subset( perf.SC, Groupe == "G.3")$LOGIT.1, cases= subset( perf.SC, Groupe == "G.1")$LOGIT.1) # # Calcul de l’aire sous la courbe Programmer R - Version 1.2 (mars 2013) F. Aubry p. 82 # Puisque l’argument auc de roc est par défaut TRUE auc.perf <- as.numeric( roc.1$auc) # # Intervalle de confiance # # On peut écrire ci=TRUE dans l’appel de roc et éventuellement donner la méthode en argument supplémentaire ou écrire ci.perf <- ci.auc( roc.1) # donner éventuellement une méthode Exercice récapitulatif II : Opérations sur des matrices creuses Les matrices creuses (sparse matrix) sont des matrices de grandes dimensions contenant une majorité de valeurs nulles. Pour réduire leur taille en mémoire, on utilise généralement un codage spécial sous forme d’un tableau à trois colonnes, la première contient les numéros de lignes, la seconde, les numéros de colonnes et la troisième, la valeur. R propose des outils pour traiter ces matrices. Il faut donc prendre cet exercice comme des exemples de manipulation avancée par R et non comme des outils de manipulation des matrices creuses. On représentera les matrices creuses par un data.frame à trois colonnes nommées respectivement LIGNE, COLONNE, VALEUR. Les deux dernières lignes du data.frame auront NA comme valeurs de LIGNE et COLONNE ; les VALEURs associées seront le nombre de lignes puis de colonnes. LIGNE COLONNE VALEUR 1 6 23,7 3 8 12 NA NA nb.lignes NA NA nb.colonnes ... Écrire les fonctions permettant 1) de récupérer les dimensions, 2) de transformer un data.frame représentant une matrice creuse en une véritable matrice, 3) de transformer une matrice en data.frame représentant une matrice creuse, 4) de calculer la trace de la matrice creuse (somme des éléments diagonaux), 5) de calculer la somme d’au moins deux matrices creuses et retournant une matrice creuse, 6) de calculer le produit de deux matrices creuses et retournant une matrice creuse. Éviter d’utiliser des boucles for. # # Récupération des dimensions # Programmer R - Version 1.2 (mars 2013) F. Aubry p. 83 dim.mc <- function( mc) { tail( mc, 2)$VALEUR } # # Transformation d’une matrice creuse en matrice # mc2matrix <- function( mc) { dim.mat <- dim.mc( mc) mat <- matrix( 0, nrow=dim.mat[1], ncol=dim.mat[2]) idx.mat <- as.matrix( head( mc, -2))[,c( 1, 2)] mat[idx.mat] <- head( mc, -2)$VALEUR mat } # # Transformation inverse # matrix2mc <- function( mat) { dim.mat <- dim( mat) idx.mat <- which( mat != 0, arr.ind=TRUE) mc <- cbind.data.frame( as.data.frame( idx.mat), as.vector( mat[idx.mat])) mc <- rbind( mc, as.data.frame( matrix( c( NA, NA, NA, NA, dim.mat), ncol=3, dimnames=list( NULL, colnames( mc))))) names( mc) <- c( "LIGNE", "COLONNE", "VALEUR") mc } # # Calcul de la trace de la matrice # trace.mc <- function( mc) { tr.mc <- subset( head( mc, -2), LIGNE == COLONNE) ifelse( nrow( tr.mc) == 0, 0, sum( tr.mc$VALEUR)) } # # Somme d’au moins deux matrices creuses # somme.mc <- function( mc1, mc2, ...) { liste.mc <- c( list( mc2), list( ...)) dim.list <- sapply( liste.mc, FUN=dim.mc) dim.mc1 <- dim.mc( mc1) id.dim <- all( dim.list[1,] == dim.mc1[1]) & all( dim.list[2,] == dim.mc1[2]) if( !id.dim) { stop( "Les matrices ne sont pas toutes de même dimension") Programmer R - Version 1.2 (mars 2013) F. Aubry p. 84 } mc.res <- head( mc1, -2) for( n.mc in seq.int( length( liste.mc))) { mc.res <- merge( mc.res, head( liste.mc[[n.mc]], -2), by=c( "LIGNE", "COLONNE"), all=TRUE) } mc.res[is.na( mc.res)] <- 0 mc.res$VALEUR <- apply( mc.res, 1, FUN=function( x) sum( x[3:ncol( mc.res)])) rbind( mc.res[,c( "LIGNE", "COLONNE", "VALEUR")], data.frame( LIGNE=c( NA, NA), COLONNE=c( NA, NA), VALEUR=dim.mc1)) } # # Multiplication de deux matrices creuses # mult.mc <- function( mc1, mc2) { dim.mc1 <- dim.mc( mc1) dim.mc2 <- dim.mc( mc2) stopifnot( dim.mc2[1] == dim.mc1[2]) names( mc1) <- c( "LIGNE", "PIVOT", "VALEUR.x") names( mc2) <- c( "PIVOT", "COLONNE", "VALEUR.y") mc.res <- merge( head( mc1, -2), head( mc2, -2), by="PIVOT") # Il est inutile de passer les valeurs NA qui correspondent à des zéros puisque le résultat de la multiplication donnera zéro mc.res <- aggregate( I( VALEUR.x * VALEUR.y) ~ LIGNE + COLONNE, mc.res, sum) names( mc.res) <- c( "LIGNE", "COLONNE", "VALEUR") rbind( mc.res, data.frame( LIGNE=c( NA, NA), COLONNE=c( NA, NA), VALEUR=c( dim.mc1[1], dim.mc2[2]))) } # Vérifications df.1 <- data.frame( LIGNE=c( sample( 1:30, size=12), NA, NA), COLONNE=c( sample( 1:20, size=12), NA, NA), VALEUR= c( rnorm( 12), 30, 20)) df.2 <- data.frame( LIGNE=c( sample( 1:20, size=8), NA, NA), COLONNE=c( sample( 1:20, size=8), NA, NA), VALEUR= c( rnorm( 8), 20, 20)) mat.1 <- mc2matrix( df.1) arr.1 <- which( mc2matrix( df.1) != 0, arr.ind=TRUE) pos.1 <- as.matrix( head( df.1[,1:2], -2)) pos.1 <- pos.1[order( pos.1[,2], pos.1[,1]),] Programmer R - Version 1.2 (mars 2013) F. Aubry p. 85 all( arr.1 == pos.1) df.1.inv <- matrix2mc( mat.1) all( dim.mc( df.1) == dim.mc( df.1.inv)) df.m.1 <- merge( df.1, df.1.inv, by=c( "LIGNE", "COLONNE")) identical( df.m.1$VALEUR.X, df.m.1$VALEUR.Y) df.3 <- data.frame( LIGNE=c( 1, 2, 2, NA, NA), COLONNE=c( 1, 1, 2, NA, NA), VALEUR=c( 1:3, 3, 3)) trace.mc( df.3) df.4 <- data.frame( LIGNE=c( 1, 1, 3, NA, NA), COLONNE=c( 1, 2, 2, NA, NA), VALEUR=c( 2, 2, 2, 3, 3)) somme( df.1, df.3) s23 <- somme.mc( df.3, df.4) all( mc2matrix( s23) == mc2matrix( df.3) + mc2matrix( df.4)) mult.mc( df.1, df.4) m14 <- mult.mc( df.1, df.2) all( mc2matrix( m14) == mc2matrix( df.1) %*% mc2matrix( df.2)) Programmer R - Version 1.2 (mars 2013) F. Aubry p. 86 Les packages essentiels R propose par défaut un certains nombres de packages qui constituent le cœur : Fonctions de base. base compiler Compilateur. Jeux de données de base. datasets grDevices Gestion des dispositifs graphiques. graphics Fonctions pour les graphiques de base. Présentation des graphiques et interactions. grid methods Outils de base pour la programmation objet dans R. Support pour la programmation parallèle (multicore). parallel Fonctions spline et régression spline. splines Fonctions statistiques de base. stats Fonctions statistiques de base. stats4 Interface avec le langage Tcl/Tk pour la programmation des interfaces utilisateur. tcltk Outils pour le développement de package et leur administration. tools Functions utilitaires. utils Si ces packages permettent de pratiquer l’essentiel des analyses, les packages suivants21 sont un complément utiles aux packages de base mais ce ne sont pas les seuls : FactoMineR22 MASS Matrix R.matlab Rcmdr boot car class cluster Analyse des données ‘à la française’ (ACP, AFC, AFM…) Fonctions et jeux de données développés par Venables et Ripley, “Modern Applied Statistics with S”. Manipulations avancées de matrices Interface avec Malab ; lecture et écriture de fichier .mat. Interface R Commander Fonctions et jeux de données pour réaliser des analyses bootstrap (“Bootstrap Methods and Their Applications” by A. C. Davison and D. V. Hinkley, 1997, Cambridge University Press.) Fonctions supplémentaires pour la régression Fonctions de classification (k-nearest neighboor et LVQ). Fonctions pour l’analyse des cluster. 21 Le chargements de ces packages peuvent conduire au chargement d’autres packages dont ils dépendent. Par exemple, si on charge car, il est inutile de charger MASS puisque car en dépend et le charge automatiquement. 22 Ce package est développé par l’équipe de J. Pagès à Rennes, équipe à l’origine de beaucoup de ces méthodes ; il existe un autre package à peu près équivalent ade4 développé par une équipe lyonnaise. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 87 coin foreign klaR igraph lattice moments multcomp nlme23 nnet oro.nifti oro.dicom pROC rpart survival Zelig 23 Inférences conditionnelles dans le cadre des tests de permulation Fonctions pour écrire et lire des données aux format d’autres logiciels statistiques comme Minitab, S, SAS, SPSS, Stata, Systat, etc. Classification et visualisation de la classification Analyse et visualisation de graphes Fonctions graphiques avancées. Calculs et tests des moments d’une distribution Tests post-hoc et comparaisons multiples. Modèles mixtes linéaires et non-linéaires. Perceptrons mono-couche et modèle loglinéaires multinomiaux. Accès aux fichiers images en format Nifti, Analyze et Dicom. Courbes ROC Partitions et arbres de régression / de décision. Analyse de survie. Interface console standardisée pour un certain nombre de fonctions d’analyse statistique Le package lme4 reprend et étend les fonctions développées dans ce package. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 88 Quelques références Site de R : http://cran.r-project.org/ Documents accessibles à partir du site de R Manuels édités par R : http://cran.r-project.org/manuals.html Documents tiers : http://cran.r-project.org/other-docs.html (documents en différentes langues dont certains en français) http://www.r-project.org/other-docs.html (documents différents des précédents) Questions : http://cran.r-project.org/faqs.html Journal d’information de R : journal.r-project.org Autres documents, blogs, FAQ… Série de présentation en français : http://rug.mnhn.fr/semin-r/ Tutoriels : http://pairach.com/2012/02/26/r-tutorials-from-universities-around-the-world/ Généralités http://rdatamining.wordpress.com/2011/05/29/which-r-documents-to-read-and-whichr-packages-to-use/ http://www.ats.ucla.edu/stat/r/ R project mailing list : http://web.archiveorange.com/archive/project/r-project.org/ R bloggers: http://www.r-bloggers.com/ R graphs gallery: http://gallery.r-enthusiasts.com/ R statistics blog : http://www.r-statistics.com/all-articles/ Programmer R - Version 1.2 (mars 2013) F. Aubry p. 89 Groupe francophone des utilisateurs de R: http://forums.cirad.fr/logiciel-R/index.php?sid=634ad0a67ce9f1d128ffa000eb49844c Et n’hésitez surtout pas d’utiliser les moteurs de recherche. Pour vous assurer que vous filtrez bien les documents parlant de R, utiliser les mots-clés CRAN et R et non simplement R. Programmer R - Version 1.2 (mars 2013) F. Aubry p. 90