C. Huber
Transcription
C. Huber
Université Paris V, René Descartes UFR Biomédicale 45, rue des Saints-Pères 75 006 Paris Cours de Modélisation Biostatistique en Splus. C. Huber 1 Modélisation Statistique en Splus. Chapitre 1 : Introduction 1 Les objets en Splus 2 Les quatre modes de données en Splus 3 Comment obtenir des données dans l'environnement Splus a Ordre d'affectation b Fonction scan() Exemple 1 c Importation d'un fichier existant Exemple 2 (mantel.rat) d Génération systématique de nombres Génération de nombres au hasard 4 Organisation des données en Splus a Vecteurs b Matrices Exemple 3 c Listes Exemple 4 d Data frames 5 Opérateurs en Splus a Opérateurs arithmétiques Exemple 5 b Autres opérateurs Exemple 6 c Extraction de sous-ensembles Exemples 7 6 Travailler avec les data frames Exemple 8 (coagulation du sang) Exemple 9 (dialyse : fichier kidney) Exemple 10 (le même, avec factor) Création automatique d'une variable de type catégoriel Création manuelle d'une variable de type catégoriel 7 Ordonner une variable ou tout un tableau 8 Graphes : la fonction plot 9 Remarques importantes 1 2 3 4 5 6 7 8 9 7 10 11 12 13 11 14 15 16 17 18 Chapitre 2 : Probabilités, Statistique Descriptive et Tests Elementaires 1 Lois de probabilité usuelles 2 Statistiques descriptives : summary, hist, stem 3 Quelques tests élémentaires : (t.test, var.test, wilcox.test) Chapitre 3 : Tests d'adéquation 1 Evaluation graphique : cdf.compare 2 Le test du chi-deux : chisq.gof et chisq.test 2 Le test de Kolmogorov-Smirnov : ks.gof C. Huber 19 21 26 32 34 43 2 Chapitre 4 : Régression linéaire 1 Régression multiple Exemple 1 : données hospitalières 2 Procédure de détermination du modèle 3 Comment tester la validité du modèle Exemple 2 : ANOVA1 (coagulation) Exemple 3 : ANOVA2 (dialyse) Chapitre 5 : Courbes ROC. Bootstrap 1 Définitions : sensibilité, spécificité, courbe ROC 2 Un exemple simple : insuffisance respiratoire 3 Un exemple fondé sur la régression logistique 4. Estimation de l'erreur grâce au bootstrap a Définition de la technique bootstrap b Exemple 1 : la médiane c Exemple 2 : écart-type de l'aire sous la courbe ROC Chapitre 6 : Modèles pour les durées de survie censurées 1 Notions fondamentales 2 Estimation non-paramétrique de la fonction de survie 3 Modèle de Cox à hasards proportionnels 4 Examen des résidus 5 Exemple d'utilisation du modèle de Cox (dialyse) Chapitre 7 : Régression logistique 1 Les lois exponentielles 2 Construction du modèle linéaire généralisé 3 La régression logistique 4 Comment tester si le modèle est convenable ? 5 Le modèle de régression logistique général 6 Exemple 1 (données sur la grippe) 7 Exemple 2 (maladies coronariennes) C. Huber 46 47 49 65 69 70 72 75 76 77 79 80 81 82 85 86 87 88 92 Introduction 1 Chapitre 1 Introduction : Mise en route de SPLUS. Notions de base. SPLUS n'est pas un simple logiciel de statistique comme MINITAB, SPSS ou SAS. C'est plutôt un environnement de programmation, intermédiaire entre un langage et un logiciel : il a la puissance d'un langage de bas niveau en même temps que la facilité d'emploi d'un logiciel de statistique de haut niveau. Il a aussi des capacités exploratoires et graphiques de très bonne qualité. Possibilités d'extensions : on peut ajouter à Splus des fonctions en Splus et adapter le langage exactement à ce qu'on désire faire. On peut aussi écrire du code en Fortran et en C et l'incorporer comme une fonction dans Splus. SPLUS dispose aussi d'une excellente documentation en ligne : utiliser l'ordre ? nom_de_la_fonction On obtient alors l'information complète au sujet de la syntaxe de cette fonction et de ce qu'on peut en attendre. Attention : Splus fait la distinction entre les majuscules et les minuscules. 1. Les Objets en splus : Tout en SPLUS est un "objet" : "object" en anglais : scalaires (scalar), vecteurs (vector), tableaux (matrix, data.frame, list, array) et fonctions (function). Les opérateurs en Splus sont performants : ils peuvent s'appliquer à différents types d'objets. Les objets peuvent être créés et stockés de façon permanente sur le disque, ou bien créés temporairement à l'intérieur de fonctions. Dans le second cas, ils disparaissent une fois que la fonction a été exécutée. Le répertoire dans lequel les objets permanents sont stockés s'appelle “_Data”. Pour supprimer des objets : remove("nom_de_l'objet"). Faire ?remove pour connaître les autres options. Pour supprimer tous les objets d'un répertoire: remove(objects(patt="*")) l'astérisque représentant n'importe quelle suite de caractères. Chaque fois qu'on se réfère à un objet de Splus, Splus le recherche dans l'ordre : 1. Dans l'environnement local (désigné par "frame"), en général à l'intérieur d'une fonction ou d'une base de données (un data.frame), 2. dans le répertoire _Data, 3. dans les répertoires du système et dans les bibliothèques (libraries). Pour connaître les divers niveaux de cette recherche, taper la commande: C. Huber Introduction 2 search() Attention : Si vous créez un objet qui a le même nom qu'un objet déjà défini dans Splus, c'est VOTRE objet qui se substitue à l'objet Splus. Si par exemple vous utilisez c, vous perdrez la fonction c de Splus (combiner). De même pour t qui en Splus désigne le test de Student. Les commentaires sont précédés du signe #. Ils ne sont pas reconnus comme des commandes par Splus. 2. Les quatre modes de données en Splus : Il y a quatre modes de base pour les données : numeric (numérique) is.numeric(x) teste si x est numérique (T ou F : 1=True, 0=False). character (alpha) is.character(x) teste si x est alpha. logical (logique) is.logical(x) teste si x est logique. complex (complexe) as.numeric(x) convertit x en numérique. 3. Comment saisir des données dans l'environnement splus? a Ordre d'affectation <- et combinaison c : x <- 5 # affecte à x la valeur 5. (en abrégé : x _5). Le signe # précède les commentaires, non lus par Splus. Conséquence de la notation abrégée : on ne peut pas utiliser le signe "_" à l'intérieur d'un nom. Seulement des points. Par exemple "fonction.base" est une nom valable. y <- c(5,7,9,3) # affecte à y le vecteur (5,7,9,3). c veut dire "combiner" . b fonction scan() : Si les données sont en plus grand nombre, on a intérêt à utiliser la fonction scan(). Voici un exemple : z<-scan( ) 5 7 9 3 # passer à la ligne après scan() # taper la suite des valeurs séparées par des blancs # passer à la ligne après la dernière entrée: # une entrée vide signale à Splus la fin de scan(). Exemple 1 : 20 rats ont reçu un produit censé induire des tumeurs cancéreuses au bout d'un certain temps. 10 ont été tirés au sort parmi les 20 et ont reçu un traitement chimiothérapique, les 10 autres non. Les durées observées, en jours, sont les suivantes: Traités par la chimiothérapie x 101 Non traités y 74 91 92 98 77 89 88 79 96 81 84 66 32 73 80 18 72 82 69 On utilisera c() pour définir x et scan() pour définir y. C. Huber Introduction 3 c Importation d'un fichier existant : Si on dispose déjà des données dans un fichier WORD ou WORDPAD , on peut utiliser l'option IMPORT du menu fichier en SPLUS. Exemple 2 : On veut créer un objet de type data.frame en SPLUS à partir de données qui sont dans un fichier texte mantel.txt sur la disquette a:. Il s'agit, pour chaque ligne, des durées jusqu'à l'apparition de tumeurs chez trois rats d'une même portée, un traité et deux témoins. Les 50 premières portées sont constituées uniquement de femelles et les cinquante suivantes uniquement de mâles. Chez certains rats, la tumeur n'est jamais apparue : ils ont alors un 0 dans la colonne "cens". Le fichier a 100 lignes : id 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 dur1 101 91 104 91 104 98 77 91 89 104 88 96 104 79 96 61 82 63 70 104 89 104 91 104 39 104 103 65 93 86 85 104 104 95 104 104 81 104 67 92 104 104 104 63 104 104 87 cens1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 dur2 49 104 102 104 104 62 97 98 104 104 96 71 94 104 104 88 77 104 104 104 91 80 70 104 45 53 69 104 104 104 72 100 63 104 104 104 104 93 104 98 104 89 104 32 83 98 104 cens2 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 C. Huber dur3 104 102 104 102 104 77 79 76 104 98 104 91 77 99 104 85 104 102 77 102 90 92 92 101 50 102 91 91 103 75 104 102 104 95 74 102 69 80 68 83 104 89 102 51 40 78 104 cens3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 Introduction 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 104 104 87 89 104 78 104 104 90 86 91 34 104 76 23 103 104 102 87 80 104 45 104 94 67 104 104 104 104 76 104 80 51 72 102 73 88 92 67 104 104 55 81 49 94 89 104 88 104 103 104 104 92 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 104 104 104 104 104 104 91 81 104 55 104 104 104 87 104 73 71 104 51 104 83 79 104 104 84 104 104 101 94 84 103 81 104 95 98 104 54 104 84 98 104 104 82 83 104 104 89 79 69 91 75 104 104 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 4 102 104 94 104 102 104 102 64 55 94 102 54 102 74 102 84 90 80 102 73 102 104 96 104 94 104 99 94 102 78 102 76 91 104 102 66 39 102 54 73 87 104 102 77 102 104 77 99 102 104 64 79 102 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 Cliquer sur le menu file de SPLUS, choisir Import data from file et, dans la fenêtre de dialogue qui apparaît, aller d’abord dans l’onglet option pour y choisir : en face de name row : 1, en face de Delimiters : un blanc, si le fichier a pour première ligne les noms des colonnes et pour séparateurs des blancs comme dans l’exemple ci-dessus mantel.txt. Aller ensuite dans l’onglet Specs et mettre le nom qu’on veut donner au fichier que l'on veut importer (from a:\) dans la case intitulée File name. Puis cliquer sur ouvrir. On a ainsi créé un data.frame qu'on peut observer grâce à l'object.browser qui s'ouvre automatiquement. Si on veut changer le nom d'une variable, double-cliquer sur ce nom et taper le nouveau nom. d Génération systématique de nombres : rep (répétition) et seq (séquences) C. Huber Introduction 5 rep : La fonction rep est très utile : son premier argument est le contenu et le second le nombre des répétitions. On vérifiera que rep(0,10) # répète 0, 10 fois. rep(c(1,2,3),4) # répète 4 fois la séquence 1,2,3. rep(c(1,2,3),c(4,2,7)) # répète 1, 4 fois, 2, 2fois et 3, 7 fois. seq : La fonction seq a pour arguments : le début, la fin et le pas de la séquence. s1 <- seq(from=-4,to=5, by=.3) s1 # pour afficher s1. [1] -4.0 -3.7 -3.4 -3.1 -2.8 -2.5 -2.2 -1.9 -1.6 -1.3 -1.0 -0.7 -0.4 -0.1 [15] 0.2 0.5 0.8 1.1 1.4 1.7 2.0 2.3 2.6 2.9 3.2 3.5 3.8 4.1 [29] 4.4 4.7 5.0 Quelques autres façons d'obtenir une séquence : 1:15 # donne tous les entiers de 1 à 15 letters[1:3] # donne les minuscules a b c. LETTERS[3:6] # donne les majuscules C D E. e Génération de nombres au hasard : La génération de nombres au hasard est utile dans les simulations. r (comme random) suivi du nom d'une loi engendre des variables aléatoires indépendantes qui suivent cette loi : normale, béta, Poisson, etc.. . Pour en avoir la liste, taper ? probability. Exemple : x<- rnorm(100) # génère 100 variables normales N(0,1) indépendantes # et les stocke dans le vecteur x rnorm(100) # affiche 100 nombres aléatoires à l'écran, # mais ne les sauvegarde pas si on a oublié de les affecter à # une variable x. Cependant, on peut rattraper ça en faisant x<-.Last.value # .Last.value contient le vecteur des 100 valeurs : # elles seront stockées dans x. Autres nombres aléatoires : runif(50, min=2, max=10) # engendre 50 valeurs distribuées uniformément sur (2,10) rexp(10, rate=2) # engendre 10 variables exponentielles de moyenne ½. 4 Organisation des données en Splus : a vecteurs (vector) (déjà introduits ci-dessus : exemple c(1,5,6)). C. Huber Introduction 6 b matrices (matrix) : matrix(data, nrows= , ncol= , byrow = ). Exemples 3 : y <- matrix(1:20,nrow=5) # 1:20 désigne la suite des nombres entiers # de 1 à 20, # dont l'autre écriture est # c(1,2,3,…,20) (sans pointillés). y # affichage de y [,1] [,2] [,3] [,4] [1,] 1 6 11 16 [2,] 2 7 12 17 [3,] 3 8 13 18 [4,] 4 9 14 19 [5,] 5 10 15 20 Remarque : SPLUS stocke les matrices en colonnes. Pour le forcer à les stocker par ligne, utiliser byrow=T. y <- matrix(1:20,nrow=5,byrow=T) y [,1] [,2] [,3] [,4] [1,] 1 2 3 4 [2,] 5 6 7 8 [3,] 9 10 11 12 [4,] 13 14 15 16 [5,] 17 18 19 20 Pour créer des matrices on peut utiliser des abréviations comme suit : # Nous avons donné une seule valeur numérique. # Elle est répétée autant de fois qu'il le faut pour # remplir la matrice. [,1] [,2] [,3] [,4] [1,] 1 1 1 1 [2,] 1 1 1 1 [3,] 1 1 1 1 [4,] 1 1 1 1 [5,] 1 1 1 1 matrix(1,5,4) Si on donne moins de valeurs qu'il n'en faut, SPLUS utilisera les mêmes en les répétant jusqu'à ce que la matrice soit remplie. matrix(c(1,2,3),5,4,byrow=T) [,1] [,2] [,3] [,4] C. Huber Introduction 7 [1,] 1 2 3 1 [2,] 2 3 1 2 [3,] 3 1 2 3 [4,] 1 2 3 1 [5,] 2 3 1 2 Warning messages: Replacement length not a multiple of number of elements to replace in: data[1:ll] <- old On peut utiliser des nombres aléatoires pour remplir une matrice : y<-matrix(rnorm(10),ncol=5) y [,1] [,2] [,3] [,4] [,5] [1,] -0.6349455 0.5476715 -0.2105945 -0.5090833 -0.02591333 [2,] -0.2517527 -0.2930510 1.4006490 0.9879103 -1.04293702 Pour accéder aux éléments d'une matrice ou d'une sous-matrice : y [,1] [,2] [,3] [,4] [,5] [1,] -0.6349455 0.5476715 -0.2105945 -0.5090833 -0.02591333 [2,] -0.2517527 -0.2930510 1.4006490 0.9879103 -1.04293702 y[1,3] [1] -0.2105945 # accès à l'élément y[1:2,c(1,3,5)] # accès à une sous-matrice. [,1] [,2] [,3] [1,] -0.6349455 -0.2105945 -0.02591333 [2,] -0.2517527 1.4006490 -1.04293702 x<-y[2,] # accès à la ligne 2 x [1] -0.2517527 -0.2930510 1.4006490 0.9879103 -1.0429370 x<-y[,4] # accès à la colonne 4 x [1] -0.5090833 0.9879103 c Listes (list) Les matrices sont limitées par le fait qu'elles ne contiennent que des entrées du même type: soit numériques, soit alpha. De plus la longueur de toutes les colonnes et de toutes les lignes sont fixées. Lorsqu'on a des données de longueur variable et de différents types, il faut utiliser C. Huber Introduction 8 une list. En utilisant une liste (list), on peut transférer des données d'un objet d'une fonction à une autre. En règle générale, les résultats de fonctions en Splus sont des lists. Exemple 4 : mylist<-list(a=c(1,2,3),b=c("a","b")) mylist$a # accès à la 1re entrée. [1] 1 2 3 mylist$b # accès à la 2ème entrée. [1] "a" "b" d data.frame : C'est un hybride entre une matrice et une liste: toutes les colonnes doivent être de la même longueur, mais elles peuvent être de différents modes. Comme pour les matrices, on peut utiliser les indices. Une matrice peut être convertie en un data.frame en utilisant la fonction data.frame : d.frame<-data.frame(y) # où y est une matrice. Pour définir un data.frame, on peut aussi importer un fichier dans Splus comme expliqué plus haut. On peut pour cela employer la fonction read.table. Exemple : Si le fichier fich.txt se trouve dans le répertoire z de la disquette a:, et a 3 colonnes nommées A, B et C, on fera: fichier<-read.table("a:\\z\\fich.txt",col.names=c("A","B","C")) Quand on lit un fichier texte pour en faire un objet de type data.frame en Splus, on peut avoir une ligne qui contient les noms des colonnes comme on l'a fait dans l'exemple 2 mantel.rat, devenu mantel en Splus. Cependant, pour pouvoir être capable de se référer aux variables par leurs noms, on doit utiliser: attach(mantel) On aura pu auparavant s'assurer du nom donné au nouveau data.frame par SPLUS en tapant objects() et utiliser ce nom dans la fonction ‘attach’. Ne pas attacher un data.frame avant de lui avoir ajouté toutes les variables désirées. Si par exemple on veut rajouter le sexe à mantel.txt, on crée la colonne sex, qui donne 0 pour une femelle et 1 pour un mâle, et on appelle mantels le data.frame mantel augmenté de cette information: sex<-c(rep(0,50),rep(1,50)) mantels<-data.frame(mantel,sex) C. Huber Introduction 9 Pour revenir à l'état précédent, annuler attach et ne plus avoir le fichier mante.txt au premier plan, on doit taper: detach("mantel") Attention aux guillemets, qu'il n'y a pas dans attach(). 5 Opérateurs en SPLUS : a Opérateurs arithmétiques: Les opérations usuelles : + - * / ^ (addition, soustraction, multiplication, division, exponentiation). Quand on applique ces opérateurs à des matrices a and b de même dimension, ces opérations ont lieu élément par élément. Exemple 5 : a<-matrix(1:12,3,4,byrow=T) a [,1] [,2] [,3] [,4] [1,] 1 2 3 4 [2,] 5 6 7 8 [3,] 9 10 11 12 b<-matrix(2,3,4) b [,1] [,2] [,3] [,4] [1,] 2 2 2 2 [2,] 2 2 2 2 [3,] 2 2 2 2 a^b [,1] [,2] [,3] [,4] [1,] 1 4 9 16 [2,] 25 36 49 64 [3,] 81 100 121 144 a*b [,1] [,2] [,3] [,4] [1,] 2 4 6 8 [2,] 10 12 14 16 [3,] 18 20 22 24 b Autres opérateurs : division entière : division modulo : 5%/%3 [1] 1 %/%: %% : C. Huber Introduction 10 5/3 [1] 1.666667 5%%3 [1] 2 # 5:3 = 1 reste 2 Multiplication des matrices : % * % Reprenons les matrices a et b. Transposons b. Nous pouvons maintenant multiplier a et b en tant que matrices : # affiche la transposée de b t(b) [,1] [,2] [,3] [1,] 2 2 2 [2,] 2 2 2 [3,] 2 2 2 [4,] 2 2 2 a % * % t(b) # effectue le produit matriciel de a et de t(b) [,1] [,2] [,3] [1,] 20 20 20 [2,] 52 52 52 [3,] 84 84 84 Opérateurs logiques : >< >= <= == Différent de : >< >= <= égal : == (deux signes =) Notons qu'en SPLUS ‘=’ n'existe pas. L'affectation se fait par ‘<-‘ , et la condition d'égalité par double égale.. Quand ces opérateurs sont appliqués à des vecteurs ou à des matrices, la comparaison a lieu élément par élément. Exemple 6 : x<-runif(5) x [1] 0.3860282 0.7095107 0.2220855 0.8128106 0.2053301 y<-runif(5) x>y [1] T T T T F Extraction de sous-ensembles des données (indices) : L'opérateur subscript est une parenthèse carrée : []. C'est la différence entre l'appel à une fonction, gérée par des parenthèses (), et les tableaux. Pour une matrice ou un data.frame l'indexation est : x[e1,e2] C. Huber Introduction où e1 et e2 sont des expressions numériques ou logiques. Exemples 7: 1. x<-matrix(floor(runif(15,0,10)),3,5) # floor(x) = le plus grand entier <= x. x # runif(n,a,b) donne n nombres aléatoires de (a,b). [,1] [,2] [,3] [,4] [,5] [1,] 2 0 5 7 5 [2,] 3 7 6 8 6 [3,] 6 4 8 6 3 x[3,5] [1] 3 x[1:3,5] # sélectionne un élément d'une matrice. # sélectionne une sous-matrice.La sous-matrice a une dimension # nulle (NULL). [1] 5 6 3 x[c(1,3),5] # sélectionne une sous-matrice de dimension nulle. [1] 5 3 x[1,] # sélectionne la ligne1. Pas d'attribut de dimension. [1] 2 0 5 7 5 x[,3] # sélectionne la colonne 3. Pas d'attribut de dimension. [1] 5 6 8 x[c(1,3),3:5] # sélectionne une sous-matrice. La dimension de la sous-matrice # est c(2,3) [,1] [,2] [,3] [1,] 5 7 5 [2,] 8 6 3 x[,2]==4 # pour savoir quelles sont les lignes qui ont un 4 en colonne 2 : [1] F F T # la seule ligne qui est dans ce cas est la ligne 3. y<-x[x[,2]==4,] # sélectionne la ligne 3 et la met dans y. y [1] 6 4 8 6 3 2. x<-2*(1:10) x # on veut choisir les éléments n° 1,5,6,10 du vecteur x: [1] 2 4 6 8 10 12 14 16 18 20 choix<-c(1,0,0,0,1,1,0,0,0,1) x[choix] [1] 1 1 1 1 # ne marche pas ! C. Huber 11 Introduction > x[as.logical(choix)] # marche ! [1] 2 10 12 20 Essayer choix<-c(1,5,6,10) : obtient on le résultat voulu? 6 Travailler avec les data.frame : Exemple 8 : Considérons l'expérience suivante (Box et al (1978)): Les durées de coagulation sanguine pour quatre régimes différents A, B, C, and D ont été observés. Les résultats sont les suivants : A 62 60 63 59 Régime B C 63 68 67 66 71 71 64 67 65 68 66 68 D 56 62 60 61 63 64 63 59 Comment construire le data.frame correspondant ? On a besoin d'autant de lignes qu'il y a de sujets dans l'expérience, et dans chaque ligne, du régime et de la durée de coagulation. Cela donne ici : coag <- scan() # début de l'entrée des données. 62 60 63 59 63 67 71 64 65 66 68 66 71 67 68 68 56 62 60 61 63 64 63 59 # une ligne vide prévient Splus de la fin des entrées. # pour créer les noms des traitements (régime) : regime<- factor(rep(LETTERS[1:4],c(4,6,6,8))) regime # AAAABBBBBBCCCCCCDDDDDDDD coag.df <- data.frame(regime,coag) # crée un data.frame coag.df coag.df # affiche le data.frame à l'écran. regime coag 1 2 3 4 5 A A A A B 62 60 63 59 63 C. Huber 12 Introduction 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 B B B B B C C C C C C D D D D D D D D 13 67 71 64 65 66 68 66 71 67 68 68 56 62 60 61 63 64 63 59 On a ainsi obtenu les données sous la forme dont on a besoin pour une analyse ultérieure. Exemple 9 : Supposons que vous ayez un fichier texte nommé kidney.txt dans le répertoire a:\data. Il s'agit de données sur la récurrence d'infections au point d'insertion d'un cathéter, pour des patients ayant une affection rénale et qui utilisent un équipement de dialyse portable. Les cathéters peuvent être ôtés pour des raisons autres qu'une infection, et, dans ce cas, la durée est dite censurée. Chaque patient a exactement deux observations. (McGilchrist and Aisbett, Biometrics 47, 461-66, 1991). Les variables sont : patient, infectime (durée jusqu'à l'infection), cens (censuré (0) ou pas (1)), age, sex (1=masculin, 2=féminin), disease(le type de la maladie) (0=Glomerulo Nephritis, 1=Acute Nephritis, 2=Polycystic Kidney Disease, 3=Other), cens : 1 = l'infection a eu lieu ; 0 = retiré de l'étude ou mort avant l'infection. Le voici : patient 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 infectime 8 16 23 13 22 28 447 318 30 12 24 245 7 9 511 30 53 196 15 154 7 cens 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 age 28 28 48 48 32 32 31 32 10 10 16 17 51 51 55 56 69 69 51 52 44 C. Huber sex 1 1 2 2 1 1 2 2 1 1 2 2 1 1 2 2 2 2 1 1 2 disease 3 3 0 0 3 3 3 3 3 3 3 3 0 0 0 0 1 1 0 0 1 frailty 2.3 2.3 1.9 1.9 1.2 1.2 0.5 0.5 1.5 1.5 1.1 1.1 3.0 3.0 0.5 0.5 0.7 0.7 0.4 0.4 0.6 Introduction 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20 20 21 21 22 22 23 23 24 24 25 25 26 26 27 27 28 28 29 29 30 30 31 31 32 32 33 33 34 34 35 35 36 36 37 37 38 38 333 141 8 96 38 149 70 536 25 17 4 185 177 292 114 22 159 15 108 152 562 402 24 13 66 39 46 12 40 113 201 132 156 34 30 2 25 130 26 27 58 5 43 152 30 190 5 119 8 54 16 6 78 63 8 1 1 0 1 1 0 0 1 0 1 0 1 1 1 1 0 0 1 0 1 1 1 0 1 1 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 1 1 0 0 0 1 1 0 44 34 34 35 35 42 42 17 17 60 60 60 60 43 44 53 53 44 44 46 47 30 30 62 63 42 43 43 43 57 58 10 10 52 52 53 53 54 54 56 56 50 51 57 57 44 45 22 22 42 42 52 52 60 60 14 2 2 2 2 2 2 2 2 2 1 1 2 2 2 2 2 2 2 2 1 1 2 2 2 2 2 2 1 1 2 2 2 2 2 2 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 3 3 1 1 1 1 3 3 1 1 3 3 3 3 0 0 3 3 2 2 3 3 1 1 1 1 1 1 1 1 0 0 1 1 0 0 0 0 1 1 1 1 2 2 0 0 3 3 3 3 2 2 2 2 0.6 1.2 1.2 1.4 1.4 0.4 0.4 0.4 0.4 1.1 1.1 0.8 0.8 0.8 0.8 0.5 0.5 1.3 1.3 0.2 0.2 0.6 0.6 1.7 1.7 1.0 1.0 0.7 0.7 0.5 0.5 1.1 1.1 1.8 1.8 1.5 1.5 1.5 1.5 1.7 1.7 1.3 1.3 2.9 2.9 0.7 0.7 2.2 2.2 0.7 0.7 2.1 2.1 1.2 1.2 En utilisant “import” comme indiqué auparavant, on crée un data.frame appelé “kidney”. On peut vouloir créer d'abord de nouvelles variables à ajouter au tableau, dont on pourrait avoir besoin plus tard pour l'analyse. Vous pouvez avoir l'intention d'ajuster aux données un modèle de Cox à hasards proportionnels. Dans ce modèle, la durée de survie est la durée jusqu'à l'infection, l'indicateur de censure est cens, et les variables explicatives sont le sexe, l'âge et le type de la maladie. Cependant, le type de la maladie est une variable catégorielle. Aussi est il nécessaire, soit d'utiliser la commande as.factor (première méthode ci-dessous), soit de définir quatre variables indicatrices (deuxième méthode, manuelle et plus longue). C. Huber Introduction 15 a. Création automatique d'une variable de type catégoriel : la fonction as.factor : Quand une variable catégorielle est introduite comme variable explicative dans une régression, ses valeurs ne signifient rien. Si elle prend k valeurs distinctes, on doit faire entrer dans l'équation de régression k-1 variables. Dans l'exemple 9 ci-dessus, la variable disease prend 4 valeurs : 0-3, et dis1,dis2,dis3 pourrait être un choix de variables binaires pour remplacer dis. Cela peut être fait de manière automatique en SPLUS par la commande as.factor : attach(kidney) dis.fac<-as.factor(disease) puis en l'ajoutant au data.frame kidney : kidney1<-cbind(kidney,dis.fac) attach(kidney1) La nouvelle variable s'est vu attribuer le nom X2 dans le fichier étendu kidney1. On doit changer le nom de cette colonne pour le rendre plus informatif : dimnames(kidney1)[[2]] # donne les noms des variables de kidney1 dimnames(kidney1)[[2]][8]<-"maladie" b. Création manuelle d'une variable de type catégoriel (factor) : Comme exemple de manipulation de data.frame, nous allons procéder de la deuxième manière, qui explique ce que fait la fonction as.factor. Les nouvelles variables que nous créons sont les suivantes : Exemple 10 : dis0 = 1 si disease = 0 et 0 sinon dis1 = 1 si disease = 1 et 0 sinon dis2 = 1 si disease = 2 et 0 sinon dis3 = 1 si disease = 3 et 0 sinon En fait on n'a besoin que de trois variables, puisque disease prend seulement 4 valeurs en tout : 0,1,2,3. Mais nous voulons trouver lesquelles il vaut mieux garder, aussi commençons nous malgré tout avec les quatre. Pour créer ces variables comme des colonnes dans une matrice 76x4 , faire : nn<-nrow(kidney) # nrow est la fonction qui compte le nombre des lignes # d'une matrice ou d'un data.frame. nn C. Huber Introduction 16 [1] 76 kidney[1,] # affiche la première ligne de kidney patient infectime cens age sex disease frailty 1 1 8 1 28 1 3 2.3 dis <- matrix(0,nn,4) # crée une matrice pleine de zeros pour recevoir # les variables dis0-dis4, ayant nn lignes. for(i in 0:3){ # boucle iterative dis[,i+1]<-kidney[,6]==i} dis[1:4,] # affiche les lignes 1 à 4 de la nouvelle matrice # [,1] [,2] [,3] [,4] # [1,] 0 0 0 1 # [2,] 0 0 0 1 # [3,] 1 0 0 0 # [4,] 1 0 0 0 kidney[1:4,6] # pour vérifier que nous avons construit # les bonnes variables. # [1] 3 3 0 0 dis <- as.data.frame(dis) # transforme la matrice dis en un data.frame dis[1:4,] # V1 V2 V3 V4 # 1 0 0 0 1 # 2 0 0 0 1 Il reste maintenant à changer les noms V1-V4 en dis0-dis3. On peut le faire en utilisant l'object browser dont l'icône apparaît au haut de la fenêtre de commande. Dans le browser , cliquer sur data.frame pour trouver dis, cliquer dessus, puis cliquer deux fois sur chaque en-tête de colonne tour à tour pour le changer. Nous sommes maintenant prêts pour fusionner le data.frame kidney avec le data.frame dis pour en faire un nouveau data.frame qu'on sauvegardera sous le nom kidney1. Pour ce faire, on utilise la commande cbind, pour “column bind” : kidney1<-cbind(kidney,dis) kidney1[1:4,] 1 2 patient infectime cens age sex disease frailty dis0 dis1 dis2 dis3 1 8 1 28 1 3 2.3 0 0 0 1 1 16 1 28 1 3 2.3 0 0 0 1 Attachons maintenant le data.frame kidney1 de façon à avoir à disposition toutes les colonnes avec leurs noms : attach(kidney1) 7. Ordonner une variable ou tout un tableau (data.frame) : sort, order, rank Pour ordonner une variable par ordre croissant, employer la commande sort: Exemple: C. Huber Introduction 17 Quels sont les âges, par ordre croissant des patients atteints de maladie du rein du fichier "kidney1" ? attach(kidney1) age.ord<-sort(age) Essayer la commande order : ord<-order(age) ord On vérifiera que order(age) donne la suite des identificateurs des sujets par ordre croissant de leur âge, ce que l'on appelle : les anti-rangs. Pour ordonner l'ensemble du tableau (data.frame) kidney1 par ordre croissant des âges, on va donc les réorganiser en choisissant les lignes selon la suite ord = order(age) en faisant : kidney1.ord<-kidney1[order(age),] Vérifier le résultat en affichant kidney1.ord. La commande rank donne les rangs des valeurs de la variable. Le vérifier sur rg<-rank(age) rg 8. Graphes : la fonction plot : Supposons maintenant que nous voulions faire un dessin représentant une fonction, par exemple f(x) = 1/((2()exp(-x2/2), entre –3 et +3. On fera x<-seq(-3,+3,by=0.01) y<-(1/sqrt(2*pi))*exp(-(x^2/2)) plot(x,y,type="l",xlab="valeurs de x",ylab="densite") title(main="Densite de la loi normale standard") win.graph() # pour ouvrir la fenêtre graphique # si elle ne s'est pas ouverte automatiquement L'argument type de la fonction plot permet de choisir : - un nuage de points : type = "p" , - une ligne joignant les points (x,y) : type = "l" . Le type de la ligne, se choisit par l'argument lty; on peut choisir - une ligne pleine par lty=1, - pointillée par lty = 2, - etc... L'argument col de plot définit la couleur. On peut choisir - la couleur noire par col = 1, - bleue par col = 2, - etc.. L'argument lwd donne l'épaisseur du trait: - lwd = 4 donne un trait plus épais que lwd = 2 ou 3. Essayer de changer les valeurs de tous ces arguments sur le dessin précédent. C. Huber Introduction 18 Nous verrons au chapitre suivant que la densité normale est incorporée dans Splus et qu'on peut donc obtenir directement le dessin de cette fonction (par dnorm()), ainsi que la fonction de répartition correspondante (par pnorm()). 9. Remarques importantes : Ces deux remarques concernent l'environnement Splus que l'on peut créer pour séparer les dossiers relatifs aux différents travaux que l'on traite en utilisant ce logiciel. Remarque 1 : Pour faire une icône de Splus spécifique d'un projet : 0. Créer le répertoire où on veut que soit stocké le travail effectué avec Splus. Par exemple : C:\data\essai. 1. Créer un raccourci. du fichier exécutable de Splus , nommé Splus.exe, et situé dans le répertoire C:\Program Files\splus45\cmd, et le mettre sur le bureau. 2. Le sélectionner, appuyer sur le bouton droit de la souris et choisir : propriétés. 3. Dans l'onglet raccourci il doit y avoir : dans démarrer en : "C:\Program Files\splus45\cmd". dans cible : "C:\Program Files\splus45\cmd\SPLUS.EXE" Y ajouter S_PROJ="chemin_du_répertoire_de_travail" (qui est dans notre exemple "C:\data\essai") (Selon les cas il faut encadrer ou non ce chemin par des guillemets) 4. 5. Aller dans changer d'icône pour changer la physionomie de l'icône. Remarque 2 : Pour créer son propre répertoire de données : Pour créer son propre répertoire de données (_data file) qui contiendra TOUS LES OBJETS de Splus que l'on est amené à créer lors d'une session par des commandes dans la fenêtre de commandes, on peut : 1. Ouvrir Window Explorer. 2. Dans le répertoire C:\ créer un nouveau dossier et lui donner un nom (le vôtre par exemple). 3. Supposons que votre nom soit durand. Créer alors un nouveau dossier dans durand, qu'on appellera _data. 4. Ouvrir SPLUS. 5. Taper les lignes suivantes qui créent la fonction .First puis l'appliquent : .First <- function() { attach("c:\\durand\\_data", 1) cat("On attache le repertoire durand \n en position 1") } .First( ) Noter que l'ordre cat " " signifie imprimer à l'écran ce qui est entre guillemets, et que \n est l'ordre de passage à la ligne. C. Huber Introduction 19 Comme plusieurs personnes utilisent le même ordinateur que vous, il faudra répéter l'étape 5 chaque fois que vous ouvrirez une session de SPLUS. Si quelqu'un a enlevé ou changé votre fonction .First, il faudra repartir de l'étape 1. C. Huber Probabilités et Statistique Descriptive 20 Chapitre 2. Lois de Probabilité, Statistiques Descriptives et Tests Elémentaires. 1. Lois de probabilité usuelles : Il est très facile d'obtenir la fonction de répartition (cumulative probabilities), la densité et les quantiles pour les lois usuelles discrètes et continues. Pour savoir lesquelles sont disponibles, taper à l'invite > : ?probability et SPLUS donnera une liste des distributions pour lesquelles la fonction de répartition et la génération de variables sont disponibles. Cliquer sur le nom de celle pour laquelle vous voulez obtenir de l'information. Si vous le faîtes sur la loi normale (gaussienne) vous verrez, parmi d'autres explications : dnorm(x, mean=0, sd=1) pnorm(q, mean=0, sd=1) qnorm(p, mean=0, sd=1) rnorm(n, mean=0, sd=1) densité en x de la loi normale N(0,1). f. r. ( cumulative probability) de la loi normale N(0,1) : P(X ≤ x). quantile de N(0,1) pour la probabilité p. génère n variables aléatoires (random) normales N(0,1) indépendantes. Traçons maintenant la courbe de la densité normale N(0,1) entre –4 et +4: x<-c(-(.01)*(400:1),.01*(0:400)) length(x) # donne le nombre des éléments du vecteur : 801 par(mfrow=c(2,1)) # dit à SPLUS de créer deux dessins par page # sur 2 lignes.(c(1,2): sur 2 colonnes, l'essayer). plot(x,dnorm(x,0,1),type="l",lty=1,lwd=3,col=2) # trace la densité de la normale standard. # Pour donner un titre en haut du graphe : title(" densite normale standard ") win.graph() # pour ouvrir la fenêtre graphique Traçons maintenant la densité normale N(2,3) entre –10 et +10: # pour ajouter des noms (labels) aux axes : plot(3*x+2,dnorm(3*x+2,2,3),type="l",lty=1,lwd=3,col= 3, xlab="x", ylab="f(x)") title("densite normale de moyenne 2 et ecart-type 3") C. Huber Probabilités et Statistique Descriptive 21 0.3 0.2 0.1 0.0 dnorm(x, 0, 1) 0.4 densite normale standard -4 -2 0 2 4 x 0.08 0.0 0.04 f(x) 0.12 densite normale de moyenne 2 et ecart-type 3 -10 -5 0 5 10 x En fait, si on veut comparer ces deux densités, il faudrait les tracer sur le même graphique, ce qu'on peut faire par la commande: x<- seq(-4,+8,0.01) y<-dnorm(x,0,1) y 0.0 0.1 0.2 0.3 0.4 plot(x, y,type= "l",lty=1,lwd=4,col=2) lines(x, dnorm(x,2,3),type= "l",lty=1,lwd=4,col=3) # lines pour ajouter une courbe au même graphique. -4 -2 0 2 4 6 8 x # Pour obtenir la valeur de P[N(1,1) ≤ 4] faire pnorm(4,1,1) # [1] 0.9986501 # Pour obtenir le 95 percentile de N(m=2, σ=3) faire qnorm(.95, 2,3) #[1] 6.934561 Génération d'échantillons aléatoires : On peut engendrer 100 variables normales N(1,2) (remarquer qu'ici 2 est l'écarttype (sd = standard deviation)) en faisant : y <- rnorm(100,mean=1,sd=2) C. Huber Probabilités et Statistique Descriptive 22 2. Statistiques descriptives : summary, hist, stem : Pour un échantillon y : 0 5 10 15 20 summary(y) # donne le minimum, maximum, les quantiles de y,.. hist(y) # donne l'histogramme de y, tel quel ou selon un nombre # fixé de classes : nclass = 3, ou selon des coupures # fixées : breaks = c(min(y)-0.01, -2,1,0,1,2, max(y) stem(y) # donne le diagramme en tiges et feuilles. -2 0 2 4 6 y # pour obtenir un histogramme # pour le vecteur y # des 100 variables N(1,2), en 8 classes. # Essayer avec les points de coupure : -3, -1, 1, 3. hist(y,nclass=8) Tiges et feuilles : stem stem(y) # pour obtenir un affichage en tige et feuilles. Pour vérifier la signification, faire sort(y) N = 100 Median = 1.216908 Quartiles = -0.1948993, 2.517407 Decimal point is at the colon -4 : 8 -3 : 631 -2 : 44430 -1 : 6543211 -0 : 87655322222210 0 : 012222244566778 1 : 012222455555567789 2 : 00111124444468889999 3 : 012355669 4 : 122369 5 : 79 L'histogramme ne révèle pas d'asymétrie (skewness). Le dessin de "tige et feuilles" non plus. Les deux montrent cependant des queues plus lourdes que celles de la normale standard. C. Huber Probabilités et Statistique Descriptive 23 Comparaison des quantiles de l'échantillon à ceux de la normale N(0,1) : qqnorm 2 0 -2 -4 quantiles d'un echantillon de 100 N(1,2) 4 On utilise ensuite un QQ plot pour vérifier si elles ressemblent à un échantillon issu d'une distribution normale standard N(0,1). qqnorm(y,ylab="quantiles d'un echantillon de 100 N(1,2)") qqline(y) -2 -1 0 1 2 Quantiles of Standard Normal Le graphe créé par qqnorm montre que les queues de la distribution empirique des y engendrés sont un peu plus lourdes que celles de la normale. Remarque : on peut faire un qqplot pour comparer les quantiles de deux échantillons: x<-rnorm(100,1,4) y<-rnorm(100,1,2) win.graph() qqplot(x,y) Exemple : le fichier kidney (ou kidney1) des infections de catheters: Nous allons maintenant donner quelques statistiques descriptives pour certaines des variables de kidney1 : attach(kidney1) kidney1[1,] patient infectime cens age sex disease frailty dis0 dis1 dis2 dis3 dis.fac 1 1 8 1 28 1 3 2.3 0 0 0 1 3 summary(infectime) Min. 1st Qu. Median Mean 3rd Qu. Max. 2 16 39.5 101.6 149.8 562 Attention : Comparer ce que l'on obtient en faisant summary d'une variable numérique, disease, et de sa transformée en "factor", dis.fac: summary(disease) Min. 1st Qu. Median Mean 3rd Qu. Max. 0 1 1 1.553 3 3 C. Huber Probabilités et Statistique Descriptive 24 summary(dis.fac) 0 1 2 3 18 24 8 26 Bien que disease et dis.fac aient exactement les mêmes valeurs, leurs "résumés" (summary) sont très différents. dans le cas de dis.fac, ce sont les fréquences des modalités qui sont données. stem(infectime) # On ne perd pas les valeurs de la variable. N = 76 Median = 39.5 Quartiles = 16, 150.5 Decimal point is 2 places to the right of the colon 0 : 0000111111111111112222222222333333334444 0 : 55566778 1 : 01112334 1 : 555566889 2 : 004 2:9 3 : 23 3: 4:0 High: 447 511 536 562. 0 10 20 30 40 50 Ce dessin montre une durée d'infection qui est extrêmement asymétrique (skewed). Cette asymétrie est visible aussi sur l'histogramme: hist(infectime) 0 100 200 300 400 500 600 infectime Si nous devions utiliser cette variable dans une régression linéaire, on pourrait souhaiter la transformer pour la rendre plus proche d'une normale. On essaye la transformation log: stem(log(infectime)) C. Huber Probabilités et Statistique Descriptive 25 N = 76 Median = 3.67622 Quartiles = 2.77259, 5.01391 Decimal point is at the colon 0:7 1:4 1 : 66899 2 : 11112 2 : 556677888 3 : 11122223334444 3 : 567788 4 : 0011224 4 : 67778999 5 : 00000122233 5 : 5788 6 : 01233 0 5 10 15 20 log(infectime) n'est pas normal, mais paraît moins asymétrique, comme on le voit aussi sur l'histogramme: hist(log(infectime) 0 2 4 6 log(infectime) Boîte à moustaches : boxplot Faisons maintenant un "boxplot" pour log(infectime) : Un boxplot, aussi appelée "boîte à moustaches" (box-and-whiskers plot), requiert seulement 5 paramètres décrivant l'échantillon : le minimum, le premier quartile Q1, la médiane Q2, le troisième quartile Q3, et le maximum. La boîte est bornée par Q1 et Q3, et les moustaches par min et max. C'est très utile pour une appréciation rapide de la symétrie d'un échantillont et en particulier pour la comparaison de plusieurs sous-échantillons. z <- log(infectime) boxplot(z, sub = "log(infection time)") # donne un seul box plot pour l'échantillon C. Huber 26 1 2 3 4 5 6 Probabilités et Statistique Descriptive log(infection time) Comparaison de sous-populations: plot.factor: Si nous voulons comparer la forme du log-infection-time pour plusieurs souspopulations, par maladie, on utilise le facteur maladie dis.fac (voir l'exemple 10 au chapître1) dans plot.factor. Le premier argument doit appartenir à la classe factor et c'est lui qui définit les sous-populations pour lesquelles sont dessinés les boxplot. plot.factor(dis.fac, log(infectime),data= kidney1,xlab="Maladie") title("Boxplots de log(infect-time) par type de maladie du rein") 4 3 1 2 log(infectime) 5 6 Boxplots de log(infect-time) par type de maladie du rein 0 1 2 3 Maladie On remarque une différence importante entre les médianes log(infectime) pour les différents types de maladie. La variabilité de log(infectime) n'est pas la même non plus pour les différents types de maladie. Finalement, utilisons un QQ plot normal pour tester la normalité de z : qqnorm(z) # title("QQ normal plot for log(infection time)") qqline(log(infectime)) C. Huber Probabilités et Statistique Descriptive 27 1 2 3 z 4 5 6 QQ normal plot for log(infection time) -2 -1 0 1 2 Quantiles of Standard Normal summary(log(infectime)) Min. 1st Qu. Median Mean 3rd Qu. Max. 0.6931 2.773 3.676 3.821 5.009 6.332 En fait la courbe est très non-linéaire, et z n'est pas du tout normal. Les quantiles et le boxplot montrent cela très bien. 3. Quelques tests élémentaires en SPLUS : binom.test, prop.test, t.test, var.test,wilcox.test a. Inférence statistique pour des proportions et des effectifs : 1. Un seul échantillon d’échecs (0) et succès (1) :* Supposons qu’ayant tiré au sort n = 100 patients atteints d’une même maladie, on ait dans l’échantillon ainsi constitué, 42 femmes et 58 hommes. On saisit ces valeurs en notant 0 pour femme et 1 pour homme. sex<-c(rep(0,42),rep(1,58)) On se demande si cet échantillon suffit à prouver que cette maladie est plus répandue chez les hommes que chez les femmes. Si p est la probabilité qu’un patient tiré au sort soit un homme, on teste donc l’hypothèse : Ho : p = 0.5 (il y a autant d’hommes que de femmes) contre l’alternative : H1 : p > 0.5 (Il y a plus d’hommes que de femmes) z<-binom.test(58,100,p=0.5,alt = "g") L'argument alt ="g" précise que l'alternative est p est plus grand que 0.5, "g" pour "greater than". Si l'alternative est bilatérale, H'1 : p ≠ 0.5, on ne précise pas l'hypothèse alternative: C. Huber Probabilités et Statistique Descriptive 28 z1<- binom.test(58,100,p=0.5) Si l'alternative est unilatérale dans l'autre sens, H"1 : p <0.5, on précise alt="l", dans binom.test, pour "less than" : z2<- binom.test(58,100,p=0.5,alt = "l") z$p.value donne le degré de signification du test. Si on ne précise pas p, p vaut 0.5 par défaut. Intervalle de confiance pour une proportion : prop.test Pour obtenir un intervalle de confiance, de coefficient de confiance 0.98 pour la probabilité p d'être un homme pour un patient tiré au sort, on utilise l'ordre suivant: z3<-prop.test(58,100,conf.level = 0.98) Par défaut, si on ne précise pas le niveau de confiance par conf.level, celui-ci vaut 0.95. 2. Deux échantillons d'échecs succès : prop.test Exemple: Le vaccin Salk contre la poliomyélite avait fait l'objet, avant sa mise sur le marché, d'un essai clinique sur deux très grands groupes de personnes : Parmi n1 = 200 745 personnes vaccinées, il y a eu x1 = 57 cas de polio, et, parmi n2 = 201 229 personnes non vaccinées (atant reçu un placebo), x2 = 142 cas. Saisie des données: n<-c(200 745, 201 229) x<-c(57,142) Le premier vecteur, n, contient les effectifs de chacun des deux groupes, et le second, x, les nombres de cas correspondants. On veut tester l'efficacité du vaccin, donc, en appelant p 1 la probabilité d'être atteint quand on est vacciné et p 2 quand on ne l'est pas, on veut prouver l'alternative à Ho (p1 = p 2) : H1: p1 < p 2. z<-prop.test(x,n,alt="l") z$p.value donne le degré de signification du test qui vaut ici : 1.433 10 -9. Le test est donc extrêmement significatif. Remarques: 1. On peut tester simultanément : Ho : p1 = p 01, p2 = p02, contre H1 :non H0. Exemple : Salk pour p01 = 0.0002 et p02 = 0.0006: p01 <-0.0002 p02 <- 0.0006: z<-prop.test(x,n,p=c(p01,p02)) C. Huber Probabilités et Statistique Descriptive 29 On trouve zz$p.value = 0.00599. Quelle est la conclusion ? 2. z$conf.int donne un intervalle de confiance pour la différence p1-p2 à 95%, si z <-prop.test(x,n) est un test bilatéral, sans indication sur le coefficient de confiance. 3. Plus de deux échantillons : prop.test Exemple: Quatre études différentes au sujet de patients atteints de cancer du poumon ont donné les résultats suivants (Fleiss): Etude Nombre de patients 86 93 136 82 1 2 3 4 Nombre de fumeurs 83 90 129 70 Question : peut on considérer qu'il y a la même proportion de fumeurs dans les quatre études ? Soit Ho : p1=p2=p3=p4. x<-c(83,90,129,70) n<-c(86,93,136,82) prop.test(x,n)$p.value vaut 0.00558. Quelle est la conclusion ? (On conclut, avec un très bon degré de signification qu'il n'y a pas d'homogénéité entre les quatre études. Remarque: Lorsqu'il y a plus de deux échantillons, prop.test ne calcule pas d'intervalles de confiance. 4. Tableaux de contingence et tests d'indépendance : chisq.test Exemple: vaccin Salk Reprenons l'exemple du vaccin Salk, mais avec une précision supplémentaire sur le type de poliomyélite: paralysante ou non. Pas de polio Vaccinés Placebo Total 200 688 201 087 401 775 polio non paralytique 24 27 51 polio paralytique 33 115 148 Total 200 745 201 229 401 974 Ce tableau de contingence croise deux variables catégorielles, la vaccination, à deux états, et la maladie, à trois états. Nous avons donc 2x3 = 6 effectifs. a. Saisie des données: salk.mat<-rbind(c(200 688, 24, 33), c(201 087, 27, 115)) C. Huber Probabilités et Statistique Descriptive 30 Remarque: On peut obtenir le même résultat par: salk.mat<-matrix(c(200 688, 24, 33), c(201 087, 27, 115),2, 3, byrow=T) Si on ne met pas byrow=T, la matrice se remplit par colonnes, ce qui ne donne pas du tout le même tableau. L'essayer. b. Test de l'hypothèse d'indépendance: z<-chisq.test(salk.mat) donne χ2 =45.4224 pour 2 degrés de liberté (ddl, ou df en anglais), avec un degré de signification très proche de 0. Remarque: Si les données ne sont pas fournies sous la forme d'un tableau de contingence, mais comme un fichier relatif à des patients, soit un par ligne, voici comment on procède sur un exemple: Exemple : essai clinique sur le propanolol et survie à 28 jours: med survie prop oui control non control oui prop oui med<-c("prop","control","control","prop") survie<-c(1,0,1,1) Pour obtenir le tableau de contingence correspondant: tableau<-table(med,survie) Exemple:Etude de l'effet de la vitamine C sur une néphropathie. Pour mesurer l'effet de la vitamine C sur la Cytose néphropathique on a obtenu les résultats suivants sur 64 patients : Amélioration clinique Oui Non Vitamine C 24 8 Pas de Vitamine C 29 3 Est il clair que la Vitamine C est un traitement efficace contre la Cytose néphropathique ? On commence par tester qu'il y a indépendance entre vitamine C et Cytose néphropathique. La manière la plus simple de saisir les données est de faire : NC <- matrix(c(24,29,8,3),2,2) # les données entrent par colonne! NC [,1] [,2] [1,] 24 8 [2,] 29 3 chisq.test(NC) Pearson's chi-square test with Yates' continuity correction data: NC C. Huber Probabilités et Statistique Descriptive 31 X-square = 1.7564, df = 1, p-value = 0.1851 Au niveau 5% on ne peut pas conclure que la vitamine C ait un effet quelconque. c. Tests de comparaison de deux échantillons d'une variable continue : Student et Wilcoxon Dans le data frame kidney1 nous avons la variable infectime qui est en fait le temps jusqu'à l'infection ou le temps jusqu'à ce qu'on quitte l'étude, selon celui qui arrive en premier. Mais on a aussi le sexe des patients. Pour tester l'hypothèse nulle : Ho : La durée moyenne chez les hommes = la durée moyenne chez les femmes. Avec le test classique t, en supposant que les variances des deux populations sont égales, on fait un test t : t.test : X1 − X 2 t= (n1 − 1) S12 + (n2 − 1) S 22 (n1 + n2 − 2) t.test(infectime[sex==1],infectime[sex==2]) (Standard t-Test ) la sortie commence ici : data: infectime[sex == 1] and infectime[sex == 2] t = -1.706, df = 74, p-value = 0.0922 do not reject the null hypothesis. alternative hypothesis: true difference in means is not equal to 0 95 percent confidence interval: for mean(male) – mean(female) -124.551228 9.651228 sample estimates: mean of x mean of y 59.3 116.75 Remarque Nous devrions nous restreindre aux cas 'cens=1'. Le faire. Et nous devrions aussi tester tout d'abord l'égalité des variances. Pour tester l'hypothèse nulle H0 : variance(infectime (homme) )= variance (infectime (femme)) on emploie la fonction var.test pour faire un test F d'égalité de variances : var.test(infectime[sex==1],infectime[sex==2]) C'est le test F d'égalité des variances : ( X i − X ) 2 / (n1 − 1) ∑ F= ∑ (Yi − Y ) 2 / (n2 −1) data: infectime[sex == 1] and infectime[sex == 2] C. Huber Probabilités et Statistique Descriptive 32 F = 0.9354, num df = 19, denom df = 55, p-value = 0.9093 alternative hypothesis: true ratio of variances is not equal to 1 95 percent confidence interval: 0.4710643 2.1338385 sample estimates: variance of x variance of y 15896.43 16994.85 Il n'y a aucune preuve contre l'égalité des variances. Aussi était il convenable de faire un test t avec égalité des variances. Mais nous savons que la durée jusqu'à l'infection (infectime) n'est pas normale. Aussi devrions nous plutôt faire un test de Wilcoxon, qui ne fait aucune hypothèse de ce genre, pour tester l'égalité des distributions pour les hommes et les femmes, (ce qui implique l'égalité des moyennes). Pour cela employer la fonction wilcox.test : wilcox.test(infectime[sex==1],infectime[sex==2]) # c'est le test de Wilcoxon fondé sur la somme des rangs. data: infectime[sex == 1] and infectime[sex == 2] rank-sum normal statistic with correction Z = -2.9614, p-value = 0.0031 alternative hypothesis: true mu is not equal to 0 reject the null hypothesis Warning messages: 1: cannot compute exact p-value for n larger than 50 in: wil.rank.sum(x, y, al ternative, exact, correct) Les deux tests , t et Wilcoxon, donnent des résultats apparemment contradictoires. Mais comme la normalité n'est pas vérifiée, c'est la conclusion du test de Wilcoxon que nous conserverons. Remarquons d'ailleurs que t teste l'égalité des moyennes et Wilcoxon l'égalité des lois à partir de celle des médianes. Or l'égalité des lois entraîne celle des moyennes, mais pas l'inverse. Il n'est donc pas impossible d'avoir l'égalité des moyennes sans avoir pour cela l'égalité des lois. Remarquons que aucun de ces deux tests ne fera la différence entre deux lois normales de même moyenne et de variances différentes : par contre, le test de Kolmogorov-Smirnov, qui viendra au chapitre 3, lui, fait la différence. C. Huber Tests d'adéquation 33 Chapitre 3. Tests d'adéquation (fit) : Le test du Chi-deux et le test de Kolmogorov-Smirnov. (degré de signification (p-value) calculé par Bootstrap) Lorsqu'on se demande si la loi de probabilité d'un échantillon appartient à une certaine famille de lois: les lois normales, ou les lois exponentielles, ou les lois gammas, bétas, ou les lois de Poisson, etc... on peut commencer par avoir une approche graphique en comparant les quantiles des observations aux quantiles de la loi qui dans la famille est la plus proche par la fonction cdf.compare Cette commande compare les quantiles de l'échantillon x aux quantiles d'une loi de la famille. Le premier argument est l'échantillon x , le second le type de la loi, les suivants les paramètres qui définissent la loi à l'intérieur de sa famille. Le dessin correspondant donne une idée de la proximité des deux lois mais ensuite, si elles ne sont pas trop éloignées, il faut faire un test pour pouvoir conclure valablement. les deux tests de fit les plus courants sont le test du Chi-deux correspondant aux commandes chisq.test chisq.gof et le test de Kolmogorov-Smirnov dont la commande est ks.gof Nous allons générer des échantillons aléatoires à partir de lois standards connues et utiliser ensuite ces tests de fit pour voir s'ils rejettent ou non l'hypothèse nulle H0 selon laquelle l'échantillon provient de la loi qui à servi à le générer. Les tests ne devraient pas rejeter H0. 1. Evaluation graphique : cdf.compare Avant d'effectuer un test d'ajustement, on commence en général par faire un graphe permettant de se rendre compte si la loi pressentie pour l'échantillon a des chances d'être acceptée. Voici une des possibilité que donne Splus : La fonction cdf.compare. C. Huber 34 Tests d'adéquation x <- rnorm(100) cdf.compare(x, distribution = "normal") 0.0 0.2 0.4 0.6 0.8 1.0 Empirical and Hypothesized normal CDFs -4 -3 -2 -1 0 1 2 solid line is the empirical d.f. cdf.compare(x, distribution = "exponential") 0.0 0.2 0.4 0.6 0.8 1.0 Empirical and Hypothesized exponential CDFs -4 -3 -2 -1 0 1 2 solid line is the empirical d.f. x <- rexp(100) cdf.compare(x, distribution = "exponential") x<-rbinom(100,3,.2) cdf.compare(x, distribution = "binomial",size=3,p=.2) x<-rpois(100,3) cdf.compare(x, distribution = "poisson",lambda=5) Exemple: Pour la durée jusqu'à l'infection (infectime) du fichier sur les maladies du rein (kidney1), faire cdf.compare, C. Huber 35 Tests d'adéquation - d'abord pour la loi normale standard: cdf.compare(infectime,dist="normal") 0.0 0.2 0.4 0.6 0.8 1.0 Empirical and Hypothesized normal CDFs 0 100 200 300 400 500 solid line is the empirical d.f. - puis pour la loi normale qui a la même moyenne que infectime et la même variance qu'elle: cdf.compare(infectime,dist="normal",mean=mean(infectime),sd=sq rt(var(infectime))) 0.0 0.2 0.4 0.6 0.8 1.0 Empirical and Hypothesized normal CDFs 0 100 200 300 400 500 solid line is the empirical d.f. Le deuxième dessin montre évidemment une bien meilleure adéquation. 2. Le test du Chi-deux : les fonctions chisq.gof et chisq.test Définition du test: Supposons que X est le vecteur des observations, de longueur n. Pour mener à bien le test du chi-deux, on doit d'abord diviser la droite réelle en un nombre fini k d'intervalles disjoints, et calculer pour chaque intervalle I : C. Huber Tests d'adéquation 36 L'effectif Observé[i] = #{X[j] : X[j] ∈ intervalle I} noté Obs[i] L'effectif Attendu[i] = n P F[X ∈ intervalle I] noté Exp[i] L'effectif attendu est l'espérance (expectation en anglais)de l'effectif sous la loi F. La statistique du chi-deux pour l'hypothèse nulle H0 : X~F est donnée par T = ∑ {Obs[i] –Exp[i]}2 / Exp[i] Le test rejette H0 si T > χ2 (k-1) où la valeur critique est déterminée en utilisant une table du chi-deux à k-1 degrés de liberté. Si la distribution F à laquelle on se réfère n'est précisée qu'à m paramètres près, on doit estimer ces paramètres par maximum de vraisemblance (sur les classes), et le nombre des degrés de liberté est réduit d'autant : il vaut k-m-l. Cela à condition que m désigne le nombre des paramètres indépendants. La fonction chisq.gof: a. Sans argument: x<-rexp(100) z<-chisq.gof(x,distribution="normal") z Chi-square Goodness of Fit Test data: x Chi-square = 167.28, df = 12, p-value = 0 alternative hypothesis: True cdf does not equal the normal Distn. for at least one sample point. Exemple: l'appliquer à la variable infectime et log(infectime) x<-infectime z<-chisq.gof(x,distribution="normal",mean=mean(x), sd=sqrt(var(x))) z data: x Chi-square = 89.4737, df = 11, p-value = 0 xx<-log(infectime) zz<-chisq.gof(xx,distribution="normal",mean=mean(xx), sd=sqrt(var(xx))) zz data: xx Chi-square = 9.8947, df = 11, p-value = 0.5399 La conclusion est que la durée jusqu'à l'infectioj n'est pas normale du tout, le degré de signification étant "extrêmement nul", alors que lson logarithme peut être considéré comme tel. C. Huber Tests d'adéquation 37 Attention: En effectuant ce test, nous avons estimé à partir de l'échantillon deux paramètres: il faut le signaler par l'argument n.param.est = 2 dans la fonction cuisq.gof. Que devienent alors les résultats ci-dessus? xx<-log(infectime) zzz<-chisq.gof(xx,distribution="normal",mean=mean(xx), sd=sqrt(var(xx)),n.param.est=2) zzz data: xx Chi-square = 9.8947, df = 9, p-value = 0.3591 La conclusion est la même: le logarithme de la durée jusqu'à l'infection peut être considéré comme normal (ce qui n'est pas le cas de la durée proprement dite, infectime). b. Avec un nombre de classes spécifié : n.classes zc<- chisq.gof(x,distribution="normal", n.classes=5) zc Chi-square Goodness of Fit Test data: x Chi-square = 79.9, df = 4, p-value = 0 alternative hypothesis: True cdf does not equal the normal Distn. for at least one sample point. c. Avec des points de coupure spécifiés : cut.points Essayons le test du chi-deux avec des intervalles qui dépendent des données et qui sont définis par leurs points frontières obtenus par la fonction cut.points : x<-rpois(100,3) x 4 3 0 4 0 0 3 5 3 2 3 4 1 2 4 3 1 1 2 3 4 5 2 2 0 1 5 1 3 2 2 1 3 3 7 4 2 1 2 4 2 5 2 2 1 0 5 1 2 4 6 3 4 4 1 0 1 4 5 8 1 3 3 1 3 1 4 2 3 4 3 1 4 1 2 0 2 2 3 2 0 3 7 0 4 1 3 1 2 5 5 5 6 5 2 8 5 1 0 3 coupures<-c(min(x)-1,quantile(x)) coupures 0% 25% 50% 75% 100% -1 0 1 3 4 8 Il y a 5 intervalles et 4 degrés de liberté. Les intervalles sont (-1 0], (0 1], (1, 3], C. Huber Tests d'adéquation 38 (3 4], (4,8]. Notons que le premier intervalle d'entiers est (-1,0] qui est simplement l'entier 0. Chisq<-chisq.gof(x,dist="poisson",lambda=3, cut.points=coupures) Warning messages: Expected counts < 5. Chi-squared approximation may not be appropriate. in: chisq.gof(x, dist = "poisson", lambda = 3, cut.points = c(min(x) - 1, ... Chisq # Affiche la valeur de la statistique chi deux:: 4.987597 Il résulte du "warning" de SPLUS pour le test de Poisson(3) qu'il est douteux que la statistique T du test suive effectivement une loi du chi-deux. Aussi allons nous utiliser le bootstrap pour obtenir la valeur critique bootstrap de la loi de T. Bien que Splus ait une fonction bootstrap que nous avons essayé d'utiliser, le programme s'est bloqué. Il semble en effet que lorsque la statistique utilisée dans le bootstrap est un peu complexe, Splus sature sa mémoire. Aussi avons nous programmé une routine de bootstrap directe qui calcule la loi de la statistique de test sous l'hypothèse nulle. Comme ici elle est complétement connue (c'est la loi de Poisson de paramètre 3), cela revient à une simple simulation. On a besoin de Bb= nombre de tirages bootstrap ; Bb<-1000 n <- length(x) # simulation de même taille que l'échantillon x Boot.chi<- matrix(0,Bb,1) Lambda.theo<-3 for (bb in 1:Bb) { y <- rpois( n, Lambda.theo) Bins <- c(min(y)-1,quantile(y)) # quantile : "les 5 nombres"min, Q1, Q2, Q3, max # fondements de la boîte à moustaches (Tukey). y.max <-Bins[6] Bins<-unique(floor(Bins)) # unique(y) enlève les répétitions dans y. # print(Bins) k <- length(Bins) if(k<=1){print(bb);print(Bins);next} # si k<=1, on n'a pas d'intervalle valable : on va par next à la # prochaîne itération de la boucle. Si on avait voulu sortir # de la boucle, on aurait mis stop. Obs <- matrix(0,k,1) # fréquences observées Exp <- matrix(0,k,1) C. Huber Tests d'adéquation 39 # fréquences attendues sous Poisson(Lambda.theo) for (jj in 1:(k-1)) { Obs[jj] <- sum(y<=Bins[jj+1] & y > Bins[jj]) Exp[jj] <-n * (ppois(Bins[jj+1], Lambda.theo) – ppois(Bins[jj],Lambda.theo)) } Exp[k] <- (1 – ppois(y.max,Lambda.theo))*n Obs[k] <- 0 Chisq <- sum((Obs-Exp)^2 / Exp) Boot.chi[bb] <- Chisq } Mean <- mean(Boot.chi) Var <- var(Boot.chi) Critical.value.95 <- sort(Boot.chi)[1000*.95] Critical.value.95 # Affiche : [1] 10.4315 Mean [1] 4.740271 Var [1,] 8.960227 Comme la valeur observée de la statistique du chi-deux vaut 4.987597 << 10.4315, le test Bootstrap ne rejette pas H0. Le degré de signification bootstrap vaut : Bootstrap.p.value <- sum(Boot.chi > 4.987597)/1000 Bootstrap.p.value # Affiche : 0.372 Le degré de signification Bootstrap est de 0.372 au lieu de 0.2885725 comme le donne l'approximation du chi deux : 1-pchisq(4.987597, df = 4, ncp=0) # affiche [1] 0.2885725 valeur obtenue par approximation asymptotique. Notons aussi que la moyenne de la distribution bootstrap est 4.740271 et sa variance 8.960227. Pour la distribution chi-deux, la variance est toujours égale à deux fois la moyenne, qui est à son tour égale au nombre de C. Huber Tests d'adéquation 40 degrés de liberté. La distribution bootstrap de la statistique T du chi-deux paraît donc assez voisine, au moins sur ce plan-là, d'une distribution du chi-deux à 4 degrés de liberté. Mais, pour examiner de plus près la distribution bootstrap de T faisons : stem(Boot.chi) # Affiche : N = 1000 Median = 4.156777 Quartiles = 2.650716, 6.15224 Decimal point is at the colon 0 : 23344 0 : 5555555566667777778888888888899999 1 : 000000000111111111222223333333334444444444 1 : 555555555555555555566666666666677777777788888888888888888999999x 2 : 0000000011111111122222222222333333333333333444444444444444 2 : 555555555555555555555666666666666667777777777777777777888888888x 3 : 000000000000000111111111111111111222222222223333333333333333444x 3 : 555555555555555556666666666666666667777777788888888888888888888x 4 : 000000000000000000001111111111111111112222222222222222223333333x 4 : 555555555555556666666666666777777788888888888888888999999999999x 5 : 000000000000001111111111222222222233333333333333344444444444444x 5 : 555555566666666677777777788888888888889999999 6 : 0000000011111111122222233333334444444 6 : 5555555555555566666666777777777777888899999999999999 7 : 0000001111111111222223333444 7 : 5555555666666666667777778888999 8 : 0000001111112222333344 8 : 56667889 9 : 000011233444444 9 : 56677788889 10 : 0022333333344 10 : 567779 11 : 012334444 11 : 9999 12 : 00234 12 : 889 13 : 01 High: 13.20846 13.23913 13.23933 13.40341 13.67238 13.72838 13.94078 High: 13.98775 14.04006 14.14536 14.18454 14.99237 15.09345 15.22190 High: 15.35661 15.45370 15.65704 18.14057 19.50034 19.83742 23.28470 C. Huber Tests d'adéquation 41 Ce dessin montre une distribution étalée vers la droite, mais qui ne ressemble pas à une distribution de chi-deux. Estimons maintenant lambda en utilisant la moyenne de l'échantillon, et refaisons le test du chideux : Chisq<-chisq.gof(x,dist="poisson",lambda=mean(x), cut.points=c(min(x)-1,quantile(x))) Chisq # Affiche : Chi-square Goodness of Fit Test data: x Chi-square = 3.7871, df = 4, p-value = 0.4356 alternative hypothesis: True cdf does not equal the poisson Distn. for at least one sample point. Nous savons bien sûr que le nombre de degrés de liberté est maintenant égal à 3, ce que nous aurions dû en fait lui dire par l'argument n.param.est =1. Cependant, même avec 3 degrés de liberté, le test ne rejette pas l'hypothèse que l'échantillon provient d'une loi de Poisson de moyenne non spécifiée. Nous pouvons à nouveau essayer un bootstrap, appelé bootstrap paramétrique car les paramètres de la loi de tirage sont estimés sur l'échantillon. Nous devons seulement remplacer dans le programme précédent lambda =3 par lambda = mean(x). Voici le programme : On a besoin de Bb= nombre de tirages bootstrap ; x le vecteur des valeurs observées Bb<-1000 n <- length(x) # simulation de même taille que l'échantillon x lambda.theo<-3 Boot.chi<- matrix(0,Bb,1) for (bb in 1:Bb) { y <- rpois( n,lambda.theo) lambda.emp<-mean(y) Bins <- c(min(y)-1,quantile(y)) # quantile : "les 5 nombres"min, Q1, Q2, Q3, max # fondements de la boîte à moustaches (Tukey). y.max <-Bins[6] Bins<-unique(floor(Bins)) # unique(y) enlève les répétitions dans y. # print(Bins) k <- length(Bins) if(k<=1){print(bb);print(Bins);next} # si k<=1, on n'a pas d'intervalle valable : on va par next à la # prochaîne itération de la boucle. Si on avait voulu sortir C. Huber Tests d'adéquation 42 # de la boucle, on aurait mis stop. Obs <- matrix(0,k,1) # fréquences observées Exp <- matrix(0,k,1) # fréquences attendues sous Poisson(lambda.theo) for (jj in 1:(k-1)) { Obs[jj] <- sum(y<=Bins[jj+1] & y > Bins[jj]) Exp[jj] <-n * (ppois(Bins[jj+1], lambda.theo) – ppois(Bins[jj],lambda.emp)) } Exp[k] <- (1 – ppois(y.max,lambda.emp))*n Obs[k] <- 0 Chisq <- sum((Obs-Exp)^2 / Exp) Boot.chi[bb] <- Chisq } Mean <- mean(Boot.chi) Var <- var(Boot.chi) Critical.value.95 <- sort(Boot.chi)[1000*.95] Critical.value.95 # Affichage : 13.02983 Mean 5.519774 Var 17.69588 mean(x) 2.73 Critical.value.95 13.02983 Boo.p.value <- sum(Boot.chi > 2.0909)/1000 Boo.p.value 0.85 Il est clair qu'on ne rejette pas l'hypothèse nulle selon laquelle l'échantillon provient d'une loi de Poisson. Le degré de signification (p-value) bootstrap vaut .85 plutôt que la valeur asymptotique 0.791. Test de fit du Chi-deux pour une variable catégorielle : direct et par chisq.test Exemple: La loi d'un certain indicateur de la teneur en plomb du cheveu humain normal est donnée par : C. Huber Tests d'adéquation 43 x 1 2 3 4 5 P(x) .1 .2 .4 .2 .1 Un échantillon de 20 nouveau-nés dans un quartier pauvre a été testé et on a trouvé que, parmi les 20, 4 valaient 1, 8 valaient 2, 6 valaient 3, et 2 valaient 4 pour l'indicateur x . x frequence 1 2 3 4 5 4 8 6 2 0 On peut utiliser le test du chi-deux pour vérifier si cet échantillon obéit bien à la distribution spécifiée. Cependant, nous ne pouvons pas utiliser chisq.gof parce que la distribution spécifiée n'est aucune de celles qui sont traitées par chisq.gof. Aussi devons nous écrire directement un programme en Splus pour faire le test ou bien la commande chisq.test. Programmation directe du test du chi-deux pour une variable catégorielle: Obs <- matrix(c(4,8,6,2,0),5,1) Exp <- matrix (20*c(.1, .2, .4, .2, .1),5,1) Chi <- sum((Obs-Exp)^2/Exp) Df <- 4 P.value <- 1 – pchisq(Chi,df=Df) # pchisq=f.r. du chi-deux Critical.value <- qchisq(.95,df=Df) # qchisq= fonction des quantiles # de la loi du chi-deux # Affichage Chi [1] 9.5 P.value [1] 0.04974725 Critical.value [1] 9.487729 Le test du chi-deux est à peine significatif au niveau 5%. Il y a une faible suspicion que les nouveau-nés ont une loi de la teneur en plomb dans les cheveux légèrement différente de celle de la population. La fonction chisq.test: Elle peut être utilisée pour tester qu'un échantillon d'une variable catégorielle, c'est à dire de type "factor", suit une distribution donnée. Elle a pour argument une matrice qui a pour première ligne les effectifs de l'échantillon et pour deuxième ligne leurs espérances si la loi que l'on veut tester est vraie. C. Huber 44 Tests d'adéquation Exemple: Dans le fichier kidney, on se demande si la maladie (dis.fac) est équirépartie sur les quatre maladies possibles. Faire un test du chi-deux: attach(kidney1) x<-dis.fac eff<-as.vector(summary(dis.fac)) eff.tot<-sum(eff) esp<-eff.tot*rep(0.25,4) eff # 18 24 8 26 esp # 19 19 19 19 tab<-matrix(c(eff,esp),nrow=2,byrow=T) res<-chisq.test(tab) res Pearson's chi-square correction test without Yates' continuity data: tab X-square = 6.1788, df = 3, p-value = 0.1032 En conclusion, on ne peut donc pas rejeter l'hypothèse que les quatre types de maladie sont aussi bien représentées l'une que l'autre dans l'échantillon. Exercice : On pourra appliquer ce test aux données sur la teneur en plomb des cheveux et comparer au programme direct. 2. Le test d'ajustement de Kolmogorov-Smirnov : fonction ks.gof Le test de Kolmogorov-Smirnov ne demande pas de créer des intervalles particulier. Sa statistique de test est donnée par : S = sup {| F n (x) – F(x) | : -∞ ≤ x ≤ ∞ } = max { max(|Fn(Xi-1) – F(Xi)| , |Fn(Xi) – F(Xi)| ) : i=1,…n} où Fn est la f.r. empirique de l'échantillon et est définie pour tout x comme Fn = (le nombre des ( Xi <= x) )/ n Le test rejette H0 quand S est plus grand qu'une valeur critique déterminée à partir de la distribution asymptotique (c'est à dire pour n grand) de la statistique de Kolmogorov. C. Huber Tests d'adéquation 45 Nous allons utiliser à nouveau un échantillon x de taille n = 1000 d'une loi de Poisson (lambda =3) , et tester qu'il provient d'une loi de Poisson. Malheureusement, le test de K-S n'accepte en Splus que des échantillons de taille inférieure ou égale à 50. Aussi utiliserons nous seulement les 50 premières observations : ks.gof(x[1:50],dist="poisson",lambda=3) Test de Kolmogorov-Smirnov pour un échantillon Distribution supposée = Poisson data: x[1:50] ks = 0.2672, p-value = 0.3911 alternative hypothesis: True cdf is not the poisson distn. with the specified parameters A nouveau, en général, on ne connaît pas lambda, et on utilise à la place lambda=mean(x), et faisons ensuite un bootstrap pour trouver la p-value. Le test de Kolmogorov-Smirnov pour une loi non standard : Poursuivant avec le même exemple, on pourrait vouloir utiliser le test unilatéral de K-S pour tester : H0 : F >= F0 contre H1 : F < F0 (c'est à dire : (F0(x) >= F(x) pour tout x) où F0 est la fonction de répartition de la teneur en plomb dans la population et F celle de la population des nouveau-nés. Nous devons à nouveau programmer le test directement : F0 <- matrix (c(.1, .3, .7, .9, 1.0),5,1) # f.r; de la population Fn <- matrix(c(4,12,18,20,20)/20,5,1) # f.r. empirique Fn # affiche la f.r. empirique de la teneur en plomb. [,1] [1,] 0.2 [2,] 0.6 [3,] 0.9 [4,] 1.0 [5,] 1.0 F0 # affiche la f.r. de la teneur en plomb dans la population. [,1] [1,] 0.1 [2,] 0.3 C. Huber Tests d'adéquation 46 [3,] 0.7 [4,] 0.9 [5,] 1.0 KS <- max(Fn - F0) # le test unilatéral de Kolmogorov Smirnov KS [1] 0.3 Pour obtenir le degré de signification bootstrap : Bb <- 1000 Boot.KS <- matrix(0,Bb,1) n <- 20 for(bb in 1:Bb) { y <- sample(x, size = n, replace = T) Fn <- matrix(0,5,1) for(ii in 1:5) {Fn[ii] <- sum(y<=ii)/n} Boot.KS[bb] <- max(Fn – F0) # le test unilatéral de Kolmogorov Smirnov } Boot.KS <- sort(Boot.KS) # Affichage : Boot.KS[Bb*.95] # valeur critique bootstrap du test K-S. [1] 0.45 sum(Boot.KS > .3)/Bb # degré de signification bootstrap. [1] 0.489 On voit que l'hypothèse nulle que les nouveau-nés ont une teneur en plomb stochastiquement plus grande que la population générale n'est pas rejetée à 5%, ni même à 40% (niveau). C. Huber Régression linéaire 47 Chapitre 4. REGRESSION LINEAIRE MULTIPLE. 1. Définition du modèle de Régression Linéaire Multiple : C'est un modèle qui consiste à supposer qu'une variable réponse y est une combinaison linéaire de plusieurs variables explicatives x, avec des poids β qu'il faut évaluer, à une erreur aléatoire près ε qui suit une loi normale. Ce que l'on espère du modèle c'est que la part aléatoire qui demeure dans l'erreur ε est faible. Autement dit, on espère que la variance σ de l'erreur est faible par rapport à la variabilité générale de y y = Σ β i xi + ε y x1, x2,…, xp ε = = = (4.1) la variable réponse, les variables explicatives, l'erreur : normale N(0,σ σ2) On a n observations : n équations : y1 = Σ β i xi1 + ε 1 y2 = Σ β i xi2 + ε 2 ………………… yn = Σ β i xin + ε ν 1re observation 2ème observation …………………. ème n observation (dernière observation) Exemple 1 : données HOSPITALIERES : n = 113 hôpitaux. p = 10. On cherche à savoir quelles sont les caractéristiques de l'hôpital qui permettent de prédire le mieux possible le nombre moyen de jours (y = ave.hosp.days ) qu'un patient reste dans cet hôpital. Id ave.hosp.days ave.age.pat infect.risk : : : : numéro d'identification de l'hôpital. nombre moyen de jours passés à l'hôpital par patient. âge moyen des patients. score de risque d'infection. C. Huber Régression linéaire cultrat xrayrat nbeds Medschl region patday nurses pctservice : : : : : : : : 48 taux de patients (en % ) pour qui une culture est requise. taux de patients (en % ) pour qui une radio est requise. Nombre de lits hospitaliers. L'hôpital est il associé à un CHU ? (oui=1, non=2). Région du pays: 1=N-E, 2=N-O, 3 =Sud , 4= Ouest . nombre moyen de patients présents par jour. nombre moyen d'infirmières dans l'hôpital. pourcentage de services offerts par l'hôpital. Ecriture matricielle : Y = Xβ + ε Y nx1 Xnxp β px1 ε nx1 σ Estimateurs sans biais des β : observée observée à estimer non observée à estimer b = (XT X)-1XTY L (b) = N( β , Σ(b)) Σ( )) : E(b) = β Σ(b) Σ( ) = σ2(X TX)-1 (4.2) (4.3) Estimateur sans biais de σ2 : Estimateur(σ σ2) = s2 = ErrSS / (n-p) (4.4) ErrSS = (y - X b)T (y - X b). Résidus : e estime l'erreur ε . e=y-Xb (4.5) Valeurs prédites (fitted values) : ^ y = Hy = X( X T X ) −1 X T y PROCEDURE DE DETERMINATION DU MODELE. 1) Commencer par le modèle qui contient toutes les covariables (p), C. Huber (4.6) Régression linéaire 49 2) Eliminer à chaque étape la covariable qui a la plus grande p-value 3) jusqu'à ce que les covariables restantes aient une p-value inférieure à une limite donnée, par exemple: .05. Traitement de l'exemple 1 : 113 hôpitaux 1) Importer le fichier texte des données (hosp) avec les en-tête de colonnes, pour en faire un data.frame en SPLUS appelé regex. 2) Attacher le data.frame pour avoir à disposition toutes les variables : attach(regex) regex[1,] # affiche la première ligne du data.frame. id ave.hosp.days ave.age.pat infect.risk cultrat xrayrat nbeds medschl region 1 7.13 55.7 4.1 9 39.6 279 2 4 patday nurses pctservice 207 241 60 3) Ajuster une régression linéaire multiple grâce à la fonction lm (pour linear model) : reg.lm1 <- lm(ave.hosp.days ~ ave.age.pat + infect.risk + cultrat + xrayrat + nbeds + medschl + as.factor(region) + patday + nurses + pctservice) Remarque importante : La région est une variable catégorielle, même si elle est codée de 1 à 4. Pour cette raison, on l'inclut comme facteur (as.factor(region)) plutôt que region, dans le modèle. Alors, lm (linear model) sait qu'il faut la traiter comme une variable catégorielle, ce qui est équivalent à générer 3 variables binaires. reg.lm1 est un objet SPLUS qui est une liste (list) de tous les résultats de l'analyse. La fonction summary en extrait un résumé : summary(reg.lm1) Call: lm(formula = ave.hosp.days ~ ave.age.pat + infect.risk + cultrat + xrayrat + nbeds + medschl + as.factor(region) + patday + nurses + pctservice) Residuals : (les ei) Min 1Q Median 3Q Max -2.305 -0.6608 -0.0272 0.5862 6.3 C. Huber Régression linéaire 50 Comme les résidus ne sont pas standardisés, il est difficile de juger si 6.3 est grand, mais, comparé au plus petit, qui vaut –2.3, il paraît grand. Coefficients: (les coefficients b pour le modèle complet lm1) Value Std. Error t value (Intercept) 2.6260 1.8579 1.4134 ave.age.pat 0.0799 0.0283 2.8275 infect.risk 0.4397 0.1273 3.4538 cultrat 0.0055 0.0160 0.3470 xrayrat 0.0127 0.0071 1.7753 nbeds -0.0049 0.0036 -1.3464 medschl -0.2666 0.4411 -0.6045 as.factor(region)1 -0.4065 0.1757 -2.3135 as.factor(region)2 -0.2506 0.0944 -2.6560 as.factor(region)3 -0.3059 0.0911 -3.3572 patday 0.0152 0.0044 3.4321 nurses -0.0059 0.0022 -2.6560 pctservice -0.0122 0.0138 -0.8842 Pr(>|t|) 0.1606 0.0057 0.0008 0.7293 0.0789 0.1812 0.5469 0.0227 0.0092 0.0011 0.0009 0.0092 0.3787 Pr(>|t|) est la p-value (dds) pour le test des hypothèses nulles successives : H0 : toutes les autres variables, excepté celle-ci, sont dans le modèle. H1: toutes les variables sont dans le modèle. Les variables pour lesquelles l'hypothèse nulle n'est pas rejetée sont cultrat, nbeds, medschl, et pctsrvice. Elles seront supprimées dans le modèle suivant. Comment est testée la validité du modèle ? Ho : β = 0 H1 : β ≠ 0 Le test est fondé sur LA PROPORTION DE VARIABILITE DE Y EXPLIQUEE par la régression. Statistique du test : le rapport F : F = (RegSS/p) / (ErrSS/(n-p)) ErrSS : RegSS : somme des carrés des erreurs, donnée par (4.4) la somme des carrés des écarts expliquée par la régression C. Huber (4.7) Régression linéaire 51 RegSS = TotSS –ErrSS TotSS = Σ (yi – y-bar)2 (4.8) (4.9) TotSS = somme des carrés des erreurs pour le modèle nul (sans covariables), Sous H0 : F ∼ Fisherp,(n-p) dds := p-value := P[Fp,n-p > F observé | H0 est vraie] (4.10) calculé directement à partir de la table de F de SPLUS. Pour le modèle lm1 : Residual standard error: 1.231 on 100 degrees of freedom Multiple R-Squared: 0.6299 F-statistic: 14.18 on 12 and 100 degrees of freedom, the p-value is 1.11e-016 Le test F rejette l'hypothèse que tous les beta sont nuls. La valeur du R2 multiple est assez élevée. Des modèles plus parcimonieux, avec moins de variables, auront une valeur de R 2 probablement plus faible. Corrélation des coefficients: (pour le modèle complet lm1) (Intercept) ave.age.pat infect.risk cultrat xrayrat nbeds ave.age.pat infect.risk cultrat xrayrat nbeds medschl as.factor(region)1 as.factor(region)2 as.factor(region)3 patday nurses pctservice -0.7828 0.1574 -0.3202 -0.2200 0.1412 -0.4777 -0.3125 -0.0942 -0.1332 -0.1698 -0.0313 -0.2289 -0.1643 0.2918 -0.0169 -0.0630 -0.0377 0.2503 -0.0010 0.0512 0.0526 0.0553 -0.0005 medschl as.factor(region)1 as.factor(region)2 as.factor(region)1 0.1226 as.factor(region)2 -0.0621 as.factor(region)3 0.1657 patday 0.2887 nurses 0.0528 -0.4977 -0.2916 0.1845 -0.1926 -0.0926 0.0142 -0.2302 -0.2430 -0.0231 -0.1822 0.1277 0.1163 0.2486 0.0594 C. Huber -0.1549 -0.1561 0.0032 0.2426 0.0229 -0.1790 0.2394 0.0731 -0.2745 0.1039 0.1752 -0.1295 0.2381 0.1099 -0.1874 0.2316 0.0141 -0.8861 -0.0913 0.0137 -0.2388 0.0997 0.0246 -0.2643 0.0806 0.0651 0.0112 Régression linéaire pctservice 0.1095 0.1098 0.2352 as.factor(region)3 patday patday nurses pctservice 52 0.2968 -0.1464 0.0870 nurses -0.1186 0.1384 -0.1909 Il y a quelques corrélations élevées parmi les variables. Or ce n'est pas souhaitable car cela conduit à des estimateurs instables des coefficients beta. Mais comme nous avons déjà décidé d'éliminer du modèle certaines variables, on peut espérer que cela a fait disparaître certaines des corrélations les plus élevées. Voici le second modèle qui ne contient ni cultrat, ni nbeds, ni medschl, ni pctsrvices. reg.lm2<-lm(ave.hosp.days ~ ave.age.pat + infect.risk + xrayrat + as.factor(region) + patday + nurses) summary(reg.lm2) Residuals: Min -2.368 1Q Median -0.7176 -0.02696 3Q 0.5994 Max 6.268 Coefficients: Value (Intercept) 2.0039 ave.age.pat 0.0724 infect.risk 0.4559 xrayrat 0.0136 as.factor(region)1 -0.4658 as.factor(region)2 -0.2527 as.factor(region)3 -0.3216 patday 0.0097 nurses -0.0072 Std. Error 1.5574 0.0268 0.1086 0.0070 0.1653 0.0906 0.0871 0.0019 0.0021 t value 1.2867 2.6961 4.1992 1.9231 -2.8173 -2.7890 -3.6935 5.1522 -3.5009 Pr(>|t|) 0.2011 0.0082 0.0001 0.0572 0.0058 0.0063 0.0004 0.0000 0.0007 Tous les tests t partiels (partiels parce que toutes les autres variables sont laissées dans le modèle quand on teste la nullité d'un coefficient particulier), sont significatifs à près de 5%. C'est donc un modèle qui mérite d'être considéré. Les signes des coefficients de la régression s'interprètent bien. La variable réponse est le nombre de jours passés à l'hôpital. L'âge moyen des patients, le risque d'infection, le taux de rayons X, le nombre moyen de patients par jour à l'hôpital, sont toutes corrélées positivement avec la variable réponse. Pour tester un modèle M0 à p0 < p covariables (plus parcimonieux), C. Huber Régression linéaire 53 contre le modèle M1 à p covariables : F = [RegSS(M 0) – RegSS(M 1)]/(p-p0) / (ErrSS(M1)/(n-p)) (4.11) Sous M0 F ∼ Fisherp-p0,n-p . nombre des covariables ↓ : ErrSS ↑ mais stabilité ↑ Au lieu de comparer les sommes des carrés des erreurs ErrSS, on compare les coefficients de Mallow Cp : Cp = {ErrSS(p)/ s2(k)} – (n – 2p) (4.12) ErrSS(p) = somme des carrés résiduels de (4.4) pour un modèle à p variables, s2(k) = estimateur de la variance de l'erreur issu de (4.4) pour le modèle complet comprenant les k>p variables. Si le modèle à p covariables est correct, on a deux estimateurs de σ2 : s2(k) =ErrSS(k)/(n-k) s2(p)=ErrSS(p)/(n-p) Donc Cp est à peu près égal à (n-p)-(n-2p) = p. On peut donc comparer différents modèles en utilisant Cp, et chercher le modèle qui a la valeur de Cp/p la plus proche de 1. Une fois le modèle choisi Regarder de près les résidus : ⇒ on doit le valider. certains sont ils anormalement grands ? SPLUS a une fonction qui donne plusieurs graphes : • Les résidus e i en fonction des valeurs prédites (yi-hat)=Hyi • Un Q-Q plot des résidus ordonnés en fonction des quantiles correspondants de la loi normale standard. • La distance de Cook pour chacune des observations. Si le Q-Q plot est presque linéaire, les résidus suivent approximativement une loi normale (comme le modèle le suppose). La distance de Cook pour une observation i est donnée par C. Huber Régression linéaire n Di = ^ 54 ^ ∑ ( y j − y j (i ) ) j =1 ( p + 1) s 2 2 2 ei hii = . s. e.(ei ) ( p + 1)(1 − hii ) (4.13) La distance de Cook, Di , est une mesure standardisée de l'écart de l'observation i par rapport à sa prédiction et de la variation dans les données prédites due à la suppression de l'observation i. Premier terme dans Di = le résidu standardisé pour l'observation i. Second terme = une mesure de l'influence de cette observation sur l'estimation du modèle. hii = le ième élément diagonal de la matrice H-chapeau. S'il est grand, cette observation exerce une grande influence sur le modèle et change toute la régression. yj(i) – chapeau = les valeurs prédites pour les observations yj lorsqu'on a enlevé l'observation i. La distance de Cook est une distance globale qui mesure à la fois, pour une observation donnée, le fait d'être à l'écart du gros des observations, et celui d'avoir un effet exceptionnellement important sur le modèle. Les résidus standardisés sont préférables aux résidus : résidus standards = ei / s.e.( ei) non donnés par SPLUS, mais calculés en utilisant les éléments diagonaux de la matrice H chapeau et les résidus ordinaires e i : s.e.( ei) = s *sqrt(1- hii) (4.14) s = l'écart-type du modèle calculé par SPLUS. La statistique R 2 multiple est une mesure du pouvoir prédictif du modèle. Pour un modèle à p covariables et une seule réponse, il est défini comme : R2 = Correlation(y, y-hat)2 Les deux statistiques R2 et F sont reliées par C. Huber (4.15) Régression linéaire 55 F = ((R2 / (1- R2))((n-p)/p) où F est le rapport F de (4.7). Les statistiques F et R2 sont toutes les deux des mesures de l'adéquation du modèle. Pour le modèle lm2 : Il y a quelques corrélations élevées parmi les variables. Or ce n'est pas souhaitable car cela conduit à des estimateurs instables des coefficients beta. Mais comme nous avons déjà décidé d'éliminer du modèle certaines variables, on peut espérer que cela a fait disparaître certaines des corrélations les plus élevées. Voici le second modèle qui ne contient ni cultrat, ni nbeds, ni medschl, ni pctsrvices. reg.lm2<-lm(ave.hosp.days ~ ave.age.pat + infect.risk + xrayrat + as.factor(region) + patday + nurses) summary(reg.lm2) Residuals: (modèle lm2) Min 1Q Median 3Q -2.368 -0.7176 -0.02696 0.5994 Coefficients: (modèle lm2) Value (Intercept) 2.0039 ave.age.pat 0.0724 infect.risk 0.4559 xrayrat 0.0136 as.factor(region)1 -0.4658 as.factor(region)2 -0.2527 as.factor(region)3 -0.3216 patday 0.0097 nurses -0.0072 Max 6.268 Std. Error 1.5574 0.0268 0.1086 0.0070 0.1653 0.0906 0.0871 0.0019 0.0021 t value 1.2867 2.6961 4.1992 1.9231 -2.8173 -2.7890 -3.6935 5.1522 -3.5009 Pr(>|t|) 0.2011 0.0082 0.0001 0.0572 0.0058 0.0063 0.0004 0.0000 0.0007 Tous les tests t partiels (partiels parce que toutes les autres variables sont laissées dans le modèle quand on teste la nullité d'un coefficient particulier), sont significatifs à près de 5%. C'est donc un modèle qui mérite d'être considéré. Les signes des coefficients de la régression s'interprètent bien. La variable réponse est le nombre de jours passés à l'hôpital. L'âge moyen des patients, le risque d'infection, le taux de rayons X, le nombre moyen de patients par jour à l'hôpital, sont toutes corrélées positivement avec la variable réponse. Surprenant : la corrélation négative avec le nombre des infirmières. Il arrive parfois qu'un signe surprenant soit dû à une colinéarité, c'est à dire à une corrélation très élevée parmi les variables. C'est le cas ici entre patday et nurses. C. Huber Régression linéaire 56 Pour le modèle lm2 : Residual standard error: 1.232 on 104 degrees of freedom Multiple R-Squared: 0.6143 F-statistic: 20.7 on 8 and 104 degrees of freedom, the p-value is 0 Remarquons que cet R2 n'est pas beaucoup plus petit que celui du modèle complet.. Correlation of Coefficients: (Intercept) ave.age.pat infect.risk xrayrat as.factor(region)1 ave.age.pat -0.9302 infect.risk -0.0737 -0.0335 xrayrat -0.2888 0.0383 -0.4282 as.factor(region)1 -0.2503 0.2024 0.0712 0.1105 as.factor(region)2 -0.0753 -0.0437 0.1154 0.1994 0.0815 as.factor(region)3 -0.0041 -0.0110 -0.1079 0.1461 0.0217 patday 0.0342 -0.0501 -0.1101 0.0680 -0.0357 nurses -0.0797 0.0847 -0.0836 0.0002 0.0286 as.factor(region)2 as.factor(region)3 patday as.factor(region)3 0.0476 patday -0.0539 0.2537 nurses 0.0560 -0.1829 -0.8930 Quelques graphes de diagnostic pour ce modèle : par(mfrow=c(2,2)) plot(reg.lm2) 0.4 0.6 0.8 1.0 0.0 0.20 Cook's Distance 0.0 43 -2 0.2 112 47 0.10 6 4 0 2 ave.hosp.days 0 2 4 -2 0.0 0.30 Residuals 6 Fitted Values # crée 4 graphes sur la même page. 0.2 0.4 0.6 0.8 1.0 0 20 40 60 80 100 Index f-value Le graphe des résidus en fonction des valeurs ajustées montre que plusieurs observations, identifiées par SPLUS comme étant la 47 et, peut être la 101 et la 43, pourraient être des outliers (valeurs aberrantes). Le graphe des coefficients C. Huber Régression linéaire 57 de Cook montre que certaines des observations ont des coefficients de Cook très importants. Quand on choisit le meilleur modèle, on est amené à ôter une observation ou même davantage. Pour l'instant, essayons de voir si le fait de laisser tomber une autre variable du modèle 2 est justifié, en utilisant la fonction drop : drop1(reg.lm2) Single term deletions (Suppression de facteurs un par un) On part du modèle lm2 : ave.hosp.days ~ ave.age.pat + infect.risk + xrayrat + + as.factor(region) + patday + nurses Df Sum of Sq RSS Cp <none> 157.8524 185.1730 ave.age.pat 1 11.03305 168.8854 193.1704 infect.risk 1 26.76431 184.6167 208.9017 xrayrat 1 5.61331 163.4657 187.7507 as.factor(region) 3 40.84912 198.7015 216.9152 patday 1 40.29053 198.1429 222.4279 nurses 1 18.60275 176.4551 200.7401 Aucun des modèles ayant une variable de moins que le modèle 2 n'a une valeur de Cp inférieure à celle du modèle courant lm2. On conserve donc le modèle courant lm2. En SPLUS Cp n'est pas exactement le coefficient de Mallows, mais le critère AIC (Akaike Information Criterion) : AIC = 2 (-maximized log likelihood + # paramètres) = deviance + 2p . qui, pour le modèle linéaire normal, est relié au coefficient de Mallows par la formule : AIC = (Cp + n) s2(modèle complet) Cela explique pourquoi les valeurs de Cp ci-dessus sont si grandes et éloignées de p qui vaut 5 : Ici s 2(modèle complet) = 1.2322 pour 104 degrés de liberté pour le modèle lm2. Aussi les valeurs de AIC se traduisent en Cp = (AIC/1.2322) – 113 = 9.0. Ce n'est pas une très bonne valeur. Aucun des modèles ci-dessus n'a une valeur de C. Huber Régression linéaire 58 Cp proche de 1, mais notre modèle a un Cp plus petit que n'importe quel modèle plus petit (ayant moins de paramètres); aussi retenons nous ce modèle. Mais nous pouvons encore essayer de chercher le plus petit modèle possible, en comparant tous les modèles grâce aux critères AIC ou Cp. Pour cela, commençons par le modèle nul (sans covariable) et effectuons une addition pas à pas des covariables grâce à la fonction step : reg.lm0 <- lm(ave.hosp.days~1, data=regex) step(reg.lm0,~ave.age.pat + infect.risk + cultrat + xrayrat + nbeds + medschl + as.factor(region) + patday + nurses + pctservice) Start: AIC= 416.5177 ave.hosp.days ~ 1 Single term additions (addition de facteurs un par un) Model: ave.hosp.days ~ 1 scale: 3.653664 Df <none> ave.age.pat infect.risk cultrat xrayrat nbeds medschl as.factor(region) patday nurses pctservice 1 1 1 1 1 1 3 1 1 1 Sum of Sq 14.6041 116.4459 43.6719 59.8644 68.5419 36.0841 103.5542 91.8953 47.4069 51.7271 Step: AIC= 307.3792 ave.hosp.days ~ infect.risk Single term deletions Model: ave.hosp.days ~ infect.risk C. Huber RSS 409.2104 394.6063 292.7645 365.5385 349.3460 340.6684 373.1263 305.6562 317.3150 361.8035 357.4832 Cp 416.5177 409.2209 307.3792 AIC min. 380.1532 363.9607 355.2831 387.7409 334.8855 331.9297 376.4181 372.0979 Régression linéaire 59 scale: 3.653664 <none> infect.risk Df Sum of Sq 1 116.4459 RSS 292.7645 409.2104 Cp 307.3792 416.5177 ***ne pas supprimer infect.risk Single term additions Model: ave.hosp.days ~ infect.risk scale: 3.653664 Df <none> ave.age.pat cultrat xrayrat nbeds medschl as.factor(region) patday nurses pctservice 1 1 1 1 1 3 1 1 1 Sum of Sq 14.51410 0.48032 10.18592 22.20532 12.89706 71.96682 35.01970 8.21158 9.04647 RSS 292.7645 278.2504 292.2842 282.5786 270.5592 279.8675 20.7977 257.7448 284.5529 283.7181 Cp 307.3792 300.1724 314.2062 304.5006 292.4812 301.7894 257.3343 *** 279.6668 306.4749 305.6400 add as.factor(region). Step: AIC= 257.3343 ave.hosp.days ~ infect.risk + as.factor(region) On regarde maintenant si des termes peuvent être supprimés ou pas : Single term deletions Model: ave.hosp.days ~ infect.risk + as.factor(region) scale: 3.653664 Df Sum of Sq <none> RSS 220.7977 C. Huber Cp 257.3343 Régression linéaire infect.risk as.factor(region) 1 84.85849 3 71.96682 305.6562 292.7645 60 334.8855 ***ne pas enlever 307.3792 ***ne pas enlever Essayons d'ajouter des termes au modèle : Addition d'un seul terme au modèle : Model: ave.hosp.days ~ infect.risk + as.factor(region) scale: 3.653664 <none> ave.age.pat cultrat xrayrat nbeds medschl patday nurses pctservice Df Sum of Sq 1 1 1 1 1 1 1 1 11.18836 2.02117 2.08909 18.51643 11.47893 25.67973 6.16604 3.90313 RSS 220.7977 209.6093 218.7765 218.7086 202.2813 209.3188 195.1180 214.6317 216.8946 Cp 257.3343 253.4533 262.6205 262.5526 246.1252 253.1627 238.9619 *** AIC le plus bas: 258.4756 ajouter 260.7385 Step: AIC= 238.9619 ave.hosp.days ~ infect.risk + as.factor(region) + patday Suppression d'un seul terme : Model: ave.hosp.days ~ infect.risk + as.factor(region) + patday scale: 3.653664 Df Sum of Sq RSS <none> 195.1180 infect.risk 1 42.47691 237.5949 as.factor(region) 3 62.62685 257.7448 patday 1 25.67973 220.7977 Cp 238.9619***rien à enlever 274.1315 ***pour réduire l'AIC 279.6668 257.3343 Addition d'un seul terme Model: ave.hosp.days ~ infect.risk + as.factor(region) + patday scale: 3.653664 Df Sum of Sq RSS C. Huber Cp Régression linéaire <none> ave.age.pat cultrat xrayrat nbeds medschl nurses pctservice 1 1 1 1 1 1 1 13.04477 0.71858 4.97092 10.60844 0.21356 21.19919 7.21208 195.1180 182.0732 194.3994 190.1471 184.5095 194.9044 173.9188 187.9059 61 238.9619 233.2245 245.5507 241.2984 235.6608 246.0557 225.0701***AIC le plus bas: 239.0572 ajouter nurses. Step: AIC= 225.0701 ave.hosp.days ~ infect.risk + as.factor(region) + patday + nurses Suppression d'un seul terme : ave.hosp.days ~ infect.risk + as.factor(region) + patday + nurses Df <none> infect.risk as.factor(region) patday nurses 1 3 1 1 Sum of Sq RSS 173.9188 47.747 221.6662 55.443 229.3624 40.712 214.6317 21.199 195.1180 Cp 225.0701***aucun terme à 265.5102 supprimer 258.5917 258.4756 238.9619 Addition d'un seul terme ave.hosp.days ~ infect.risk + as.factor(region) + patday + nurses <none> ave.age.pat cultrat xrayrat nbeds medschl pctservice Df Sum of Sq RSS 173.9188 1 10.4530 163.4657 1 0.02582 173.8930 1 5.03334 168.8854 1 3.72763 170.1912 1 0.60214 173.3166 1 2.28498 171.6338 Cp 225.0701 221.9243 ***ajouter 232.3516 227.3441 228.6498 231.7753 230.0924 Step: AIC= 221.9243 ave.hosp.days ~ infect.risk + as.factor(region) + patday + nurses + ave.age.pat Suppression d'un seul terme : ave.hosp.days ~ infect.risk + as.factor(region) + patday + nurses + ave.age.pat <none> infect.risk Df Sum of Sq 1 46.88 RSS 163.46 210.35 C. Huber Cp 221.9243 261.5040 Régression linéaire as.factor(region) patday nurses ave.age.pat 3 1 1 1 51.89 38.44 18.60 10.45 215.36 201.91 182.07 173.91 62 251.9000 ***rien à supprimer 253.0655 233.2245 225.0701 Addition d'un seul terme Model: ave.hosp.days ~ infect.risk + as.factor(region) + patday + nurses + ave.age.pat Df Sum of Sq RSS Cp <none> 163.4657 221.9243 cultrat 1 0.769685 162.6960 228.4620 xrayrat 1 5.613306 157.8524 223.6183 nbeds 1 4.286508 159.1792 224.9451 medschl 1 1.385230 162.0805 227.8464 pctservice 1 2.612122 160.8536 226.6195 Conclusion : Rien à ajouter On voit que lors de ce processus pas à pas, le modèle finalement choisi est celui-ci : lm(formula = ave.hosp.days ~ infect.risk + as.factor(region) + patday + nurses + ave.age.pat, data = regex) Coefficients: (Intercept) infect.risk as.factor(region)1 as.factor(region)2 as.factor(region)3 2.868829 0.5452886 -0.5009072 -0.2874727 -0.3461238 patday nurses ave.age.pat 0.009444597 -0.007200022 0.07040985 Degrees of freedom: 113 total; 105 residual Residual standard error (on weighted scale): 1.247724 Le modèle suggéré a 5 variables (dont une catégorielle à 3 degrés de liberté). Notre modèle 2 (lm2) avait 6 variables : ave.age.pat + infect.risk + xrayrat + as.factor(region) + patday + nurses AIC = 185.17 Le "meilleur" modèle (lm5) obtenu par la procédure pas à pas a 5 variables: infect.risk + as.factor(region) + patday + nurses + ave.age.pat AIC = 221.92. hosp.lm5<-lm(ave.hosp.days ~ infect.risk + as.factor(region) + patday + nurses + ave.age.pat) C. Huber Régression linéaire 63 summary(hosp.lm5) Call: lm(formula = ave.hosp.days ~ infect.risk + as.factor(region) + patday + nurses + ave.age.pat, data = regex) Residuals: Min 1Q Median 3Q Max -2.645 -0.7749 -0.1307 0.7114 6.455 Coefficients: (modèle hosp.lm5) Value Std. Error t value (Intercept) 2.8688 1.5101 1.8998 infect.risk 0.5453 0.0994 5.4879 as.factor(region)1 -0.5009 0.1664 -3.0101 as.factor(region)2 -0.2875 0.0899 -3.1965 as.factor(region)3 -0.3461 0.0872 -3.9671 patday 0.0094 0.0019 4.9696 nurses -0.0072 0.0021 -3.4572 ave.age.pat 0.0704 0.0272 2.5912 Residual standard error: 1.248 on 105 degrees of freedom Multiple R-Squared: 0.6005 F-statistic: 22.55 on 7 and 105 degrees of freedom, the p-value is 0 Pr(>|t|) 0.0602 0.0000 0.0033 0.0018 0.0001 0.0000 0.0008 0.0109 hats <- lm.influence(hosp.lm5)$hat std.resid <- resid(hosp.lm5)/((1.066)*(1-hats)^.5) std.resid[abs(std.resid)>2] n°obs résidu std 10 -2.194446 43 3.003277 65 -2.118423 101 2.547981 112 3.475738 Nous avons calculé les résidus standardisés (std.resid) selon la formule (4.14) cidessus. Le plus grand résidu (non standardisé) dépasse 6, mais après standardisation, le plus grand des résidus devient beaucoup plus raisonnable : 3.48. On a maintenant l'impression qu'on ne doit exclure aucune observation. Traçons les résidus standardisés en fonction des valeurs prédites dans le modèle sélectionné par la procédure pas à pas. plot(fitted(hosp.lm5),std.resid,xlab="fitted", ylab="standardized residuals") title("Donnees hospitalieres : Meilleur Modele pas a pas") C. Huber Régression linéaire 64 Bien qu'il y ait quelques grand résidus, pour les observations 10, 43, 65, 101, et 112, le graphe ci-dessous montre qu'on ne devrait pas rejeter le modèle simplement à cause de 5 grands résidus standardisés. Les résidus standardisés sont approximativement des variables normales standard. La probabilité que l'une d'elles excède 2 en valeur absolue est environ .05. Aussi, sur 113 résidus il faudrait s'attendre à en avoir 113*.05 = 5.65 plus grands que 2 en valeur absolue. Modèle ml5 : 2 0 -2 standardized residuals 4 6 Donnees hospitalieres : Meilleur Modele pas a pas 8 10 12 fitted Faisons la même analyse pour le modèle ml2 : (voir les commandes ci-dessous) C. Huber 14 16 Régression linéaire 65 2 -2 0 residus standardises 4 6 Donnees hospitalieres : Meilleur Modele obtenu directement 8 10 12 14 16 valeurs predites 1) résidus standardisés pour le modèle lm2 hats.2 <- lm.influence(reg.lm2)$hat std.resid.2 <- resid(reg.lm2)/((1.066)*(1-hats.2)^.5) 2) résidus standardisés supérieurs à 2 pour lm2 std.resid.2[abs(std.resid.2)>2] 10 43 -2.320521 3.086785 47 6.148208 87 -2.120281 101 2.491783 112 2.541829 Pour obtenir le graphe : plot(fitted(reg.lm2),std.resid.2,xlab="valeurs predites",ylab="residus standardises") title("Donnees hospitalieres : Meilleur Modele obtenu directement") Il est difficile de choisir entre les deux modèles : Modèle reg.lm2 : ave.hosp.days ~ ave.age.pat + infect.risk + as.factor(region) + patday + nurses + xrayrat Model hosp.lm5 : C. Huber Régression linéaire 66 ave.hosp.days ~ ave.age.pat + infect.risk + as.factor(region) + patday + nurses La différence entre les deux modèles consiste en l'addition de xrayrat dans le modèle lm2. Cela abaisse la valeur du AIC , mais donne des valeurs plus grandes aux résidus standardisés. Aussi l'un et l'autre choix peuvent se justifier. Une autre façon de choisir entre ces deux modèles est de les valider en estimant le modèle sur les 57 premières observations et ensuite en prédisant les valeurs des 56 observations restantes grâce aux coefficients estimés. Cette méthode est appelée la validation croisée (cross validation, en anglais). Exemple 2. ANALYSE de VARIANCE à UN FACTEUR : Durée de coagulation du sang pour 4 régimes A B C D . Objectif : déterminer s'il existe une différence significative entre les distributions de la durée de coagulation pour les différents traitements (diet, régime). Le data.frame coag.df contient les variables : coag (coagulation time) et regime (diet) pour chaque sujet expérimental. L'ANOVA à un facteur est une généralisation directe du test t pour deux échantillons. Ici, on compare les moyennes de k populations au lieu de seulement 2, et nos données consistent en k échantillons indépendants qui n'ont pas forcément la même taille. Les populations sont supposées normales, de même variance, mais avec des moyennes qui ne sont pas forcément les mêmes. On teste H0 :µ 1 = µ 2 =...= µ κ contre H1 : au moins deux des k moyennes sont différentes. On suppose que σ1 = σ2 =...= σκ Ce n'est en fait rien d'autre qu'un problème de régression linéaire avec une variable explicative catégorielle. Par conséquent, tous les outils développés pour la régression linéaire vont pouvoir être utilisés ici aussi. Commençons pas tracer le graphe représentant les données : par(mfrow=c(1,2)) # crée deux graphes sur une seule page. plot.design(coag.df) # trace les moyennes des facteurs: # les moyennes pour chaque sousC. Huber Régression linéaire 67 # échantillon correspondant à un # régime particulier. plot.design(coag.df,fun=median) # trace les médianes des facteurs. title("Moyennes et medianes du temps de coagulation pour 4 regimes") # titre du graphe design. par(mfrow=c(1,1)) # crée un graphe par page plot.factor(coag.df) # boxplot pour la coagulation # pour chaque régime. diet subpopulation title("BOX PLOTS FOR COAGULATION TIMES IN 4 DIETS") On note des différences très importantes entre les médianes pour les différents régimes, ainsi que quelques différences entre les distributions du temps de coagulation. Cela entraîne que le régime va être un facteur prédictif très important pour la durée de coagulation. Pour confirmer cette hypothèse, faisons une analyse de variance : coag.aov <- aov(coag ~ diet, coag.df) # effectue l'analyse de variance. summary(coag.aov) Df Sum of Sq diet 3 228 Residuals 20 112 Mean Sq 76.0 5.6 F Value 13.57143 Pr(F) 0.00004658471 Ce degré de signification (0.00004658471) très faible confirme notre hypothèse selon laquelle le régime est un excellent prédicteur de la durée coagulation. hist(resid(coag.aov)) # trace l'histogramme des résidus. Les résidus ne sont pas standardisés. Les résidus standardisés (ou studentisés), qui ne sont pas fournis par SPLUS, sont beaucoup plus utiles pour détecter les outliers, les points leviers et la non normalité des résidus. Il faut créer une fonction : on l'appellera stdres Elle peut être utilisée avec n'importe quel modèle linéaire créé par la fonction C. Huber Régression linéaire 68 lm ou aov. Notre fonction stdres a comme arguments : l'écart-type du modèle (standard error of the model), le nombre des observations n, le nombre des covariables p (count (k-1) pour un factor à k catégories), le vecteur des résidus, et le vecteur des points leviers qui contient les éléments diagonaux de la matrice chapeau H . Initialisons les arguments pour notre exemple. stderror <- 2.366432 n<-24 p<-3 residuals <- resid(coag.aov) lev<-lm.influence(coag.aov)$hat # levier(leverage). La fonction "résidus studentisés" : stdres # Voici la fonction stdres qui donne les résidus studentisés. stdres<-function(n, p, residuals, lev, stderror) { standres <- residuals/(stderror * (1 - lev)^0.5) stdresid <- standres/((n - p - standres^2) /(n - p - 1))^0.5 return(stdresid) } Appelons la fonction stdres : stdres.aov <- stdres(n, p, residuals, lev, stderror) stdres.aov # affiche les résidus studentisés. 1 2 3 4 5 6 7 0.4789131 -0.4789131 0.9747403 -0.9747403 -1.422136 0.4540766 2.617119 8 9 10 11 12 13 -0.9225312 -0.4540766 6.269345e-017 1.379256e-016 -0.9225312 1.422136 14 15 16 17 18 19 -0.4540766 1.379256e-016 1.379256e-016 -2.533473 0.4430246 -0.4430246 20 21 22 23 24 -1.223651e-017 0.8993875 1.384533 0.8993875 -0.8993875 Utilisons les résidus studentisés pour faire quelques graphes de diagnostic : C. Huber Régression linéaire 69 plot(fitted(coag.aov),stdres.aov,xlab="valeurs predites pour la duree de coagulation", ylab="residus standardises de coagulation") title(" RESIDUS STANDARDISES VS VALEURS PREDITES POUR LA COAGULATION") 1 0 -1 -2 residus standardises de coagulation 2 RESIDUS STANDARDISES VS VALEURS PREDITES POUR LA COAGULATION 62 64 66 68 valeurs predites pour la duree de coagulation plot(coag,stdres.aov,xlab=" coagulation", ylab="residus studentises de la coagulation") title("RESIDUS STANDARDISES VS VALEURS OBSERVEES POUR LA COAGULATION ") C. Huber Régression linéaire 70 1 0 -1 -2 residus studentises de la coagulation 2 RESIDUS STANDARDISES VS VALEURS OBSERVEES POUR LA COAGULATION 60 65 70 coagulation Aucun de ces tracés ne suggère de problème avec notre analyse de variance. Exemple 3 : ANALYSE DE VARIANCE A DEUX FACTEURS kidney1 Données sur la récurrence d'infections au point d'insertion d'un cathéter, pour des patients ayant une affection rénale et qui utilisent un équipement de dialyse portable. On cherche à faire une analyse de variance à deux facteurs pour la durée d'infection par rapport au sexe et au type de la maladie. options(contrasts="contr.treatment") # pour interpréter les niveaux du facteur # comme des différences par rapport au niveau 1. infect.lm <- lm(infectime~sex + dis.fac, data=kidney1) summary(infect.lm) Call: lm(formula = infectime ~ sex + dis.fac, data = kidney1) Residuals: Min 1Q -158.9 -75.1 Median -35.21 3Q 31.48 C. Huber Max 464.2 Régression linéaire 71 Coefficients: Value Std. Error t value (Intercept) -22.4631 64.7526 -0.3469 sex 67.1112 34.3540 1.9535 dis.fac1 -30.1574 40.4138 -0.7462 dis.fac2 53.1713 54.8191 0.9699 dis.fac3 33.4971 39.4987 0.8481 Pr(>|t|) 0.7297 0.0547 0.4580 0.3354 dis.fac peut être jeté. 0.3993 Residual standard error: 128.3 on 71 degrees of freedom Multiple R-Squared: 0.09069 F-statistic: 1.77 on 4 and 71 degrees of freedom, the p-value is 0.1444 Correlation of Coefficients: (Intercept) sex dis.fac1 dis.fac2 sex -0.8842 dis.fac1 -0.2242 -0.1417 dis.fac2 -0.3500 0.1044 0.3980 dis.fac3 -0.2787 -0.0892 0.5856 0.4131 Laisser dis.fac : infect.aov.1 <- aov(infectime~ as.factor(sex) , data=kidney1) summary(infect.aov.1) Df S um of Sq Mean Sq F Value Pr(F) as.factor(sex) 1 48639 48638.98 2.91028 0.09221072 Residuals 74 1236749 16712.82 C'est équivalent au test t pour deux échantillons. Residual standard error: 129.3 on 74 degrees of freedom Multiple R-Squared: 0.03784 F-statistic: 2.91 on 1 and 74 degrees of freedom, the p-value is 0.09221 Pour faire une analyse de variance avec interaction entre sexe et factor(disease)=dis.fac faire infect.aov <- aov(infectime~ sex*dis.fac , data=kidney1) infect.aov Call: aov(formula = infectime ~ sex * dis.fac, data = kidney1) C. Huber Régression linéaire 72 Terms: sex Sum of Squares 48639 Deg. of Freedom 1 dis.fac 67930 3 sex:dis.fac Residuals 105336 1063482 3 68 Residual standard error: 125.0578 summary(infect.aov) Df sex 1 dis.fac 3 sex:dis.fac 3 Residuals 68 Sum of Sq Mean Sq 48639 48638.98 67930 22643.31 105336 35112.11 1063482 15639.45 F Value 3.110019 1.447833 2.245099 Pr(F) 0.0823042 0.2365862 0.0909007 On ne peut pas dire grand-chose car ni les effets principaux du sexe et du type de la maladie, ni leur interaction ne sont significatifs. Mais nous pouvons essayer le sexe et l'interaction sex : dis.fac : infect.aov.2 <- aov(infectime~ sex + sex:dis.fac , data=kidney1) summary(infect.aov.2) Df sex 1 sex:dis.fac 3 Residuals 71 Sum of Sq 48639 62101 1174647 Mean Sq 48638.98 20700.46 16544.33 F Value Pr(F) 2.939919 0.0907773 1.251212 0.2977584 Mais on ne voit rien là non plus. Aucune combinaison du sexe et du type de la maladie n'explique la variation de la durée d'infection. Bien sûr, rappelons nous que ces durées sont censurées : c'est le minimum entre la durée d'infection et la durée jusqu'au retrait de l'étude par départ de l'hôpital par exemple. Nous étudierons plus à fond cet exemple dans le chapitre sur les durées de survie censurées. Ici, nous regardons seulement le mécanisme de l'ANOVA. Essayons pour finir la transformation en log de l'infectime pour le rendre plus normal, et voyons ce qui arrive : . loginfect <-log(infectime) infect.aov.3 <- aov(loginfect~ sex*dis.fac , data=kidney1) summary(infect.aov.3) Df Sum of Sq C. Huber Mean Sq F Value Pr(F) Régression linéaire sex 1 dis.fac 3 sex:dis.fac 3 Residuals 68 73 16.2654 16.26542 10.15409 0.0021754 4.4036 1.46787 0.91635 0.4377560 8.2796 2.75985 1.72291 0.1704922 108.9264 1.60186 Cette transformation fait peu de différence, aussi essayerons nous le test non paramétrique de Kruskal-Wallis, kruskal.test , qui ne suppose pas la normalité. Faisons d'abord deux analyses de variance à un facteur : pour le factor maladie (dis.fac), puis pour le factor sex. kruskal.test(infectime,dis.fac) Kruskal-Wallis rank sum test data: infectime and dis.fac # dis.fac est disease factor Kruskal-Wallis chi-square = 1.1862, df = 3, p-value = 0.7563 alternative hypothesis: two.sided La maladie par elle-même n'est pas un élément déterminant pour expliquer la variation de la durée jusqu'à l'infection. kruskal.test(infectime,as.factor(sex)) Kruskal-Wallis rank sum test data: infectime and as.factor(sex) Kruskal-Wallis chi-square = 8.8049, df = 1, p-value = 0.003 alternative hypothesis: two.sided Par contre le sexe est significatif : il contribue à expliquer la variabilité de la durée d'infection. Malheureusement, le test de Kruskal-Wallis ne fait pas d'analyse de variance à deux facteurs. On crée donc un nouveau facteur dd à 8 catégories, une pour chacune des combinaisons de sex (1-2) et de maladie (0-3). dd<-matrix(0,76,1) ind1<-(sex==1)*(1:76) # ind1 contient les indices des patients mâles. ind<-(sex==2)*(1:76) # ind contient les indices des patients # féminins. C. Huber Régression linéaire 74 dis<-matrix(disease,76,1) # fait une matrice du vecteur des maladies. dd[ind,1]<-dis[ind,1] # dd sera égal aux maladies pour les femmes. dd[ind1,1]<-dis[ind1,1]+4 # dd sera égal à 4 + maladie pour les mâles. kruskal.test(infectime,as.factor(dd)) # kruskal.test requiert un facteur (factor) # comme second argument Kruskal-Wallis rank sum test data: infectime and as.factor(dd) Kruskal-Wallis chi-square = 14.7834, df = 7, p-value = 0.0389 alternative hypothesis: two.sided On conclut que le sexe, la maladie et leur interaction sont significatifs pour expliquer la durée d'infection. Cette procédure n'est cependant pas satisfaisante car nous ne savons pas comment faire une analyse de variance pour les deux facteurs et leur interaction. C'est cependant instructif de voir comment manipuler les matrices et leurs indices. Remarque importante à propos des transformations : On est parfois amené à transformer des variables pour se rapprocher des hypothèses de normalité et avoir une variance constante pour la variable réponse. Or même si les variables binomiales et Poisson sont presque normales asymptotiquement, elles ont une variance qui est liée à la moyenne, qui, elle, est variable d'une observation à l'autre ; elle est modélisée comme une fonction linéaire des variables explicatives. En particulier, si la variable réponse Y est un effectif qui suit une loi de Poisson, on la transforme en prenant √ Y car cela stabilise la variance. En effet, la "delta-méthode" nous dit que si n ( X n − µ ) → N(0, σ 2 ) , alors si g est une fonction dérivable, 2 n (g( X n ) − g(µ)) → N(0, σ 2 (g' (µ)) 2 ) . Or pour une variable de Poisson, σ = λ, ce qui donne g'(λ) = 1/√λ. De même pour la binomiale, g est telle que sin(g(x)) = √x, soit g(x) = Arcsin(√ √ x). C. Huber Courbes ROC. Bootstrap. 75 Chapitre 5 . Sensibilité et spécificité d'une procédure de discrimination. La courbe ROC. Utilisation du Bootstrap. 1 Définitions: Supposons qu'une population soit répartie en deux classes notées respectivement 1 et 0. Mais, au lieu d'observer directement la classe à laquelle appartient un sujet, on dispose de renseignements sur chaque sujet, permettant d'en déduire la classe à laquelle il appartient avec une certaine probabilité d'erreur. C'est par exemple la situation dans laquelle on se trouve en général lorsqu'on établit un diagnostic, ou, lorsqu'en épidémiologie on cherche à classer les sujets en sujets "à risque" ou non par rapport à une maladie donnée. On a donc recours à une procédure de classification. La qualité de la méthode de classification est mesurée par deux indicateurs, sa sensibilité et sa spécificité, définies cidessous : 1 Sensibilité se : c'est la proportion des cas, parmi les sujet de type 1, qui sont bien classés par la procédure. Autrement dit, si on note X la vraie classe du sujet et Y la classe du sujet prédite par la méthode de classification, se = P(Y=1| X=1) . 2 Spécificité sp : c'est la proportion des cas, parmi les sujets de type 0, qui sont bien classés par la procédure, c'est à dire sp = P(Y=0| X=0) . 3 Courbe ROC : c'est la courbe représentative de la sensibilité en fonction de (1 – la spécificité), c'est à dire se = f(1 – sp) . On utilise la courbe ROC (de l'anglais : Receiver Operating Characteristic curve) d'une procédure de discrimination pour mesurer globalement sa capacité à affecter correctement les sujets à leur classe respective. 2. Un exemple simple: INSUFFISANCE RESPIRATOIRE : SENSIBILITE, SPECIFICITE ET COURBE ROC : La quantité d'air, en litres, rejetée par un sujet sain lors d'une expiration forcée est une variable aléatoire X qui est supposée normale N(µ = 2.65 ; σ2 =0.5) et la capacité respiratoire C. Huber Courbes ROC. Bootstrap. Y est la somme de deux expirations forcées successives séparées par un intervalle de deux minutes et supposées indépendantes. a) Quelle est la loi de la capacité respiratoire d'un sujet sain ? b) Quelle est la probabilité pour qu'un sujet sain ait une capacité respiratoire inférieure à 3.3 ? Une maladie M entraîne une insuffisance respiratoire chez les sujets qui en sont atteints. La loi de leur capacité respiratoire Y' est chez eux normale N(µ = 2.8 ; σ2 =1). c) Quelle est la probabilité p' pour qu'un sujet atteint de M ait une capacité respiratoire inférieure à 3.3 ? d) Si l'on se fonde sur l'observation de Y pour diagnostiquer M, quelle règle de diagnostic proposez vous ? e) Avec cette règle quels sont les risques d'erreur que vous prenez ? f) Si on appelle D le diagnostic, qui vaut 1 si on diagnostique M et 0 sinon, on appelle sensibilité (se) la probabilité d'un bon diagnostic de M et spécificité (sp) la probabilité d'un bon diagnostic de non M. Quand on change de seuil, comment évolue se en fonction de 1-sp ? @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ a) La loi de la somme de deux v.a. normales indépendantes est normale de moyenne la somme des moyennes et de variance la somme des variances: L(Y) = N((µ µ = 5.3 ; σ2 = 1) b) p = P(Y < 1.3) = P(Z+5.3 < 3.3) = P(Z < -2) = 1 - P(Z < 2) = 1 – 0.9772 = 0.0228 c) p' = P(Y' < 3.3) = P(Z+2.8 < 3.3) = P(Z < 0.5) = 1 - P(Z < 0.5) = 1 – 0.6915 = 0.3085 d) D = 1 si y < 3.3 et D = 0 si y > 3.3. Risques d'erreur : P(D=0| M=1) = P(Y'>3.3) = 1- p' = 0.6915 P(D=1| M=0) = P(Y<3.3) = p = 0.0228 Bonne spécificité, mais très mauvaise sensibilité. Choisir plutôt un seuil plus élevé, par exemple celui c qui égalise les deux erreurs : P(Z+5.3 < c) = P(Z+2.8 > c) ⇒ c-2.8 = -(c-5.3) ⇒ c = (5.3+2.8)/2 = 4.05 Ordres en Splus : # Valeurs utiles de x étant données les deux lois normales : seuil<-seq(0,8,0.05) win.graph() plot(seuil,dnorm(seuil,5.3,1),type="l",lty=1,lwd=3, col=1,xlab = "y",ylab="densite de y et y'") lines(seuil,dnorm(seuil,2.8,1),lty=1,lwd=3,col=2) lines(c(4.05,4.05),c(-0.2,dnorm(4.05,2.8,1)), C. Huber 76 Courbes ROC. Bootstrap. 77 0.2 0.0 0.1 densite de y et y' 0.3 0.4 lwd=2,col=1,lty=1) lines(c(3.3,3.3),c(-0.2,dnorm(3.3,2.8,1)), lwd=2,col=3,lty=1) 0 2 4 6 8 y sens<-pnorm(seuil-2.8) un.moins.spec<-pnorm(seuil-5.3) seuil sens spec win.graph() plot(un.moins.spec,sens,type="l",xlab="1 specificite",ylab="sensibilite") title(main="courbe ROC") 0.6 0.4 0.0 0.2 sensibilite 0.8 1.0 courbe ROC 0.0 0.2 0.4 0.6 0.8 1.0 1 - specificite 3. Un exemple fondé sur la régression logistique : Au chapître sur la régression logistique, nous verrons que ce type de régression permet d'estimer la probabilité qu'une variable réponse soit égale à 1 quand on connaît les valeurs de plusieurs paramètres du sujet. Dans l'exemple des maladies coronariennes (fichier coronary, p. 79), la réponse est l'existence ou non d'une maladie coronarienne à partir de deux indicateurs : l'âge et le poids. La classification est entre (coron=1) et (coron = 0 ) étant donnés l'âge et le poids. Sur la base de ces probabilités, on peut définir une règle de classification de la façon suivante : C. Huber Courbes ROC. Bootstrap. 78 Si la probabilité est supérieure à a, classer la personne comme 1 (coron =1). Si au contraire la probabilité est inférieure à a, la classer comme 0 (coron =0). Bien que le seuil a = 0.5 paraisse a priori une valeur raisonnable, il n'est pas du tout évident que ce soit exact. Pour chaque valeur de a on peut calculer la spécificité et la sensibilité, et, quand a varie, la courbe ROC visualise les qualités de la règle de classification correspondante. La sensibilité est la proportion des déclarés positifs (type 1: coron =1) parmi les positifs, et la spécificité la proportion des déclarés négatifs (type 0 : coron = 0) parmi les négatifs. Si on choisit a = 0, on aura se = 1 et sp = 0. Cela donne le point (1,1) de la courbe ROC. En revanche, si on choisit a = 1, on aura le point (0,0) de la courbe ROC. La courbe ROC est croissante de (0,0) jusqu'à (1,1). Plus elle est éloignée et au-dessus de la diagonale, meilleure est la règle de classification. Utilisons pour cela la prédiction qui est faite de la probabilité π d'avoir une maladie coronarienne à partir de l'âge et du poids: log(π/(1-π))= β0 + β 1 âge + β 2 poids On obtient les trois coefficients de la régression logistique en faisant: coron.glm.AWT<-glm(coron ~ age+wt , family = binomial, data=coronary) summary( coron.glm.AWT) Et on obtient les valeurs prédites pour π en faisant : Fit<-fitted.values(coron.glm.AWT) mmin<-min(Fit) mmax<-max(Fit) mmin # [1] 0.02003988 mmax # [1] 0.480566 rocc<-matrix(0,24,2) for(ii in (1:24)) { # sensibilité rocc[ii,1]<-sum(coron*(Fit > ii*.02))/sum(coron) # 1 - spécificité rocc[ii,2]<-1-sum((1-coron)*(Fit<=ii*.02))/sum(1coron) } rocc [,1] [,2] [1,] 1.00000000 1.000000000 [2,] 1.00000000 0.850574713 [3,] 0.96153846 0.706896552 [4,] 0.92307692 0.568965517 [5,] 0.73076923 0.454022989 [6,] 0.69230769 0.339080460 C. Huber Courbes ROC. Bootstrap. [7,] 0.61538462 0.270114943 [8,] 0.57692308 0.241379310 [9,] 0.46153846 0.189655172 Remarquons que les deux quantités sensibilité = rocc[,1] et 1 - spécificité = rocc[,2] sont décroissantes. Pour obtenir un tableau croissant, on doit réordonner par : ord <- order(rocc[,2], rocc[,1]) # ce qui ordonne la deuxième colonne # et sépare les ex-aequo en utilisant l'autre colonne. ord # ord contient les indices des éléments du tableau ordonné. [1] 23 24 22 21 20 19 17 18 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 rocc1 <- rocc[ord,] rocc1 [,1] [,2] [1,] 0.00000000 0.005747126 [2,] 0.00000000 0.005747126 [3,] 0.07692308 0.011494253 [4,] 0.11538462 0.011494253 [5,] 0.11538462 0.017241379 [6,] 0.11538462 0.034482759 [7,] 0.11538462 0.040229885 [8,] 0.11538462 0.040229885 [9,] 0.19230769 0.051724138 [10,] 0.19230769 0.068965517 [11,] 0.26923077 0.074712644 [12,] 0.34615385 0.097701149 # Calcul de l'aire au-dessous de la courbe ROC roca<-0 for(ii in 2:24) { delta <- rocc1[ii,2] - rocc1[ii-1,2] roca <- roca + delta * ((rocc1[ii,1]-rocc1[ii-1])/2 + rocc1[ii-1,1]) } # Aire au-dessous de la courbe : roca [1] 0.7390584 L'aire au-dessous de la courbe ROC vaut .74, ce qui indique un pouvoir de prédiction assez élevé pour ce modèle logistique. Traçons maintenant la courbe ROC elle-même : plot(rocc1[,2],rocc1[,1], xlab="1-Specificite",ylab="Sensibilite") C. Huber 79 Courbes ROC. Bootstrap. 80 title("COURBE ROC POUR LE MODELE LOGISTIQUE : coron ~ age + weight") Maintenant que nous disposons de la courbe ROC, il devient clair que le choix de a pour classer de futurs patients dont on connaît l'âge et le poids n'est pas évident. Les deux indicateurs que sont la sensibilité et la spécificité sont importants tous les deux, et le choix du seuil a doit ménager un bon équilibre entre les deux. Bien sûr, l'aire au-dessous de la courbe, roca, ainsi que la courbe elle-même, sont aléatoires. Il est donc intéressant d'avoir un estimateur de leur écart-type. Comme nous ne disposons pas de formule (asymptotique) pour estimer cette erreur, nous allons utiliser des techniques de rééchantillonnage, le BOOTSTRAP et le JACKNIFE pour ce faire. Le BOOTSTRAP Pour comprendre le bootstrap, nous allons commencer avec un exemple simple. Supposons que nous ayons un vecteur y de n = 15 observations : y<-c(5,7,3,3,2,4,0,8,0,1,7,3,5,4,6) y median(y) # [1] 4 Nous avons déjà calculé la médiane de y. Si maintenant nous voulons estimer la variance de cet estimateur de la médiane, nous ne disposons pas comme pour la moyenne d'un estimateur simple de cette variance. Nous allons donc construire un estimateur bootstrap de cette variance comme suit : 1. Tirer un échantillon de taille n=15 de y avec remise. 2. Calculer la médiane de l'échantillon ainsi obtenu. 3. Stocker la médiane de l'échantillon Bootstrap dans un vecteur appelé boot. 4. Retourner en 1. On répète ces étapes un grand nombre de fois, soit B = 1 000 fois par exemple. On obtient ainsi un échantillon bootstrap de médianes de taille 1 000. Ce que l'on réalise ainsi est essentiellement une reconstruction de la manière dont a été obtenu l'échantillon et sa médiane en rééchantillonnant y à partir de lui-même. POPULATION → ECHANTILLON ECHANTILLON → ECHANTILLON BOOTSTRAP La variabilité d'échantillonnage dans la population se reflète dans la variabilité d'échantillonnage dans la sous-population y : mais, alors que nous n'avons pas accès à la variabilité de median(y), dans la population totale, le vecteur boot illustre la variabilité de median(y bootstrap) quand on échantillonne dans la sous-population y. Aussi utiliserons nous la variance du vecteur boot pour estimer la variance de median(y). Il est clair que ce que nous venons de faire pour la médiane, nous pouvons le répéter pour estimer la variance d'autres statistiques. La suite des opérations est toujours la même : C. Huber Courbes ROC. Bootstrap. 1 2. 3. 4. 81 Faire un tirage au hasard avec remise de taille 15, dans l'échantillon y. Calculer la statistique pour le nouvel échantillon bootstrap obtenu en 1. Stocker la statistique ainsi calculée dans un vecteur nommé boot. Retourner en 1. Répéter ces étapes B=1000 fois. Cependant, nous pouvons faire beaucoup d'autres choses grâce au bootstrap. Par exemple, nous pouvons obtenir un intervalle de confiance pour le paramètre d'intérêt sans faire aucune supposition sur la loi de son estimateur. Tout ce dont nous avons besoin, c'est d'ordonner le vecteur boot par ordre croissant. Alors, un intervalle de confiance de coefficient de confiance (1-a)*100% , pour le paramètre estimé par la statistique considérée, qui dans notre cas est la médiane est donné par l'intervalle : ( boot [ B*(a/2)] , boot[B*(1-a/2)] ) Tout cela est obtenu par la fonction bootstrap en SPLUS. Exemple 1 : boot.obj1 <- bootstrap(y, statistic = median, B=1000, seed = 0, trace = F) summary(boot.obj1) # Affichage des résultats : Call: bootstrap(data = y, statistic = median, B = 1000, seed = 0, trace = F) Number of Replications: 1000 Summary Statistics: Observed Bias median 4 -0.133 Mean SE 3.867 0.9199 Empirical Percentiles: 2.5% 5% 95% 97.5% median 2 3 5 6 BCa Percentiles: 2.5% 5% 95% 97.5% median 2 2 5 5 On déduit de ce résumé que : La médiane observée valait 4. La moyenne du vecteur boot, qui devrait être un bon estimateur de la médiane de l'échantillon vaut 3.867. Donc, un estimateur du biais de la médiane de l'échantillon comme estimateur de la médiane de la population est : moyenne(médianes bootstrap) - médiane(y) C. Huber Courbes ROC. Bootstrap. 82 c'est à dire ici -.133. D'autre part, l'estimateur bootstrap de l'écart-type de la médiane vaut .9199. On remarquera que ce n'est pas cet estimateur qui est utilisé pour obtenir l'intervalle de confiance pour la médiane. La première méthode, appelée celle des percentiles, et décrite ci-dessus, donne pour intervalle de confiance bootstrap à 95% pour la médiane de la population [2 ; 6]. La deuxième méthode, appelée BCa (corrected percentile method, l'intervalle de confiance bootstrap à 95% vaut [2 ; 5]. Le vecteur boot de médianes bootstrap est donné par SPLUS et peut être utilisé pour tracer un histogramme bootstrap de la médiane pour la population : Plot(boot.obj1) Exemple 2 : Cherchons maintenant un estimateur bootstrap de l'écart-type de l'aire sous la courbe ROC, et un intervalle de confiance bootstrap à 95% pour cette aire. Voici le programme Splus correspondant : # rappelons que Fit<-fitted.values(coron.glm.AWT) (cf regression logistique) mmin <- min(Fit) mmax <- max(Fit) delta <- .01 # nn = nombre des points en lesquels # la sensibilité et la spécificité seront calculées. nn <- floor((mmax - mmin) / delta) boot.frame <- data.frame(coron, Fit) attach(boot.frame) ROCarea <- function(coron, Fit,nn) { rocc<-matrix(0,nn,2) for(ii in (1:nn)) { rocc[ii,1]<-sum(coron* (Fit >ii*.02))/sum(coron) # sensitivite rocc[ii,2]<-1-sum ((1-coron)*(Fit<=ii*.02))/sum(1-coron) #1-specificite } ord <- order(rocc[,2], rocc[,1]) rocc1 <- rocc[ord,] # coron et Fit ordonnes roca<-0 for(ii in 2:nn) { delta <- rocc1[ii,2] - rocc1[ii-1,2] roca <- roca + delta * ((rocc1[ii,1]-rocc1[ii1])/2 + rocc1[ii-1,1]) } C. Huber Courbes ROC. Bootstrap. 83 c(roca) # roca = area under the ROC curve is # returned by the function ROCarea } # fin de la fonction ROCarea boot.obj1 <- bootstrap(data = boot.frame, statistic = ROCarea(coron,Fit,nn), B=1000, seed=0) summary(boot.obj1) # Affichage des résultats : Call: bootstrap(data = boot.frame, statistic = ROCarea(coron, Fit, nn), B = 1000, seed = 0) Number of Replications: 1000 Summary Statistics: Observed Bias Mean SE Param 0.7391 -0.00477 0.7343 0.04811 Empirical Percentiles: 2.5% 5% 95% 97.5% Param 0.634 0.6532 0.8101 0.826 BCa Percentiles: 2.5% 5% 95% 97.5% Param 0.6364 0.6572 0.8134 0.8267 plot(boot.obj1) A partir de l'histogramme de la distribution bootstrap de l'aire au-dessous de la courbe ROC, on constate que cette distribution est remarquablement normale. Pour vérifier cette impression, on peut faire : qqnorm(boot.obj1) qui donne un graphe des quantiles de la loi normale contre les quantiles des données (QQ plot) pour l'échantillon bootstrapé des aires sous la courbe ROC. La linéarité de la courbe obtenue suggère bien qu'en effet la loi de l'aire sous la courbe ROC est approximativement normale. L'intervalle de confiance à 95%, obtenu grâce aux percentiles corrigé, pour l'aire sous la courbe ROC, vaut [.6364 ; .8267]. Par ailleurs, l'estimateur bootstrap de l'écart-type de l'estimateur de l'aire vaut .0481. Remarquons que si on utilisait l'intervalle de confiance à 95% fondé sur l'approximation normale, on aurait .7391 +/- 1.96*.0481 , soit [.645 ; .833]. Cet intervalle est très proche de celui donné par les percentiles bootstrap, ce qui était prévisible après le tracé du QQ plot. C. Huber Courbes ROC. Bootstrap. C. Huber 84 Modèles pour des durées de survie. 85 Chapitre 6 . MODELES POUR DES DUREES DE SURVIE AVEC CENSURE . 1 Quelques notions fondamentales Par définition, la fonction de survie S(t) est la probabilité qu'une personne survive au-delà du temps t. Cela s'écrit formellement S(t) = 1 – F(t) = P[T >t] Et le taux de hasard, ou taux d'incidence (hazard rate en anglais) h(t) est h(t) = f(t) / S(t). où f(t) est la densité, f(t) = F'(t) = -S'(t). L'interprétation du taux de hasard est h(t) dt = P[le patient meure durant un bref intervalle de temps dt autour de l'instant t, sachant qu'il a survécu jusqu'à cet instant t]. Un taux de hasard constant indique une survie exponentielle. Cela arrive dans la désagrégation des particules et dans certaines maladies mortelles comme les cancers métastatiques. Le taux de hasard cumulé H(t) est défini comme l'intégrale of h(u) for 0 to t : H(t) = t ∫ h(u) du 0 Cette fonction en elle-même n'a pas une signification particulièrement intuitive, mais elle est utile en analyse de survie car elle peut être facilement estimée à partir des données, même quand celles-ci sont censurées. Rappelons qu'on a une censure droite en t si la personne a été retirée vivante de l'étude à ce moment là. Les durées de survie avec censure se présentent comme des couples (T i, δi), i=1,2,…,n, où δi =1 s'il s'agit d'une mort et 0 autrement. Soit t1* < t 2* < t 3* . . . < t m* les temps de mort (failure times) distincts et soit Yi (t) les fonctions indicatrices des personnes à risque à l'instant t : Yi (t) = 1 si une mort est observée et 0 sinon. Notons le r( t ) = n ∑ Y (t ) i=1 i nombre des personnes à risque à l'instant t. Si d(t) compte le nombre des morts qui ont lieu en t, alors l'estimateur le plus courant de la fonction de survie S(t), l'estimateur de Kaplan-Meier, est donné par ^ d (t j ) S (t ) = ∏ (1 − ) r (t j ) j :t j < t C. Huber Modèles pour des durées de survie. 86 où r(t) = Σ Yi (t), le nombre des personnes présentes, ou "à risque" à l'instant t. L'estimateur de sa variance, appelé estimateur de Greenwood, est ^ 2 S (t ) ∑ j :t j < t d (t j ) r (t j )(r (t j ) − d (t j )) Les intervalles de confiance (à 95%), pour S(t) sont calculés comme S(t) +/- 1.96 se(S). 2 Estimation non paramétrique de la fonction de survie : Kaplan-Meier : Exemple : kidney.Kaplan.Meier<-survfit(Surv(infectime,cense)~ sex,data= kidney) plot(kidney.Kaplan.Meier,lty=1:2,col=c(1,3),xlab ="durees de survie", ylab="Survie de Kaplan-Meier selon le sexe") title(main="Comparaison des durees jusqu'à l'infection selon le sexe",cex=0.8) legend(400,0.8,c("Hommes","Femmes"),cex=1,lty=c(1,2), col=c(1,3)) 0.8 0.2 0.4 0.6 Hommes Femmes 0.0 Survie de Kaplan-Meier selon le sexe 1.0 Comparaison des durees jusqu'à l'infection selon le sexe 0 100 200 300 400 500 durees de survie 3 Le modèle à hasards proportionnels de Cox : Le modèle de régression le plus utilisé pour les durées de survie est le modèle de Cox, ou modèle à hasard proportionnel. Si Zi(t) est le vecteur des covariables du sujet i à l'instant t, le modèle suppose que le taux de hasard pour cette personne est C. Huber Modèles pour des durées de survie. 87 λ(t | Z i(t)) = λ 0(t)r i(β,t) où le score de risque r i(β,t) est donné par r i(β,t) = exp(βΤ Zi(t)), et λ0(t) est un taux de hasard de base, non spécifié, commun à tous les sujets de l'étude. Le vecteur β des coefficients de la régression n'inclut pas la constante, qui est incluse dans le taux de hasard de base. Supposons qu'une mort ait lieu à l'instant t*. Conditionnellement à cette mort, la probabilité que cela soit arrivé au sujet i qui est à risque à l'instant t* est égale à Li (β ) = λ 0 (t *)ri ( β , t *) = ∑ Yj (t*) λ 0 (t *)rj ( β , t*) j ri ( β , t *) ∑ Yj (t *)rj ( β , t*) j Le produit de ces termes sur tous les temps de mort, au nombre de m, vaut donc L( β ) = ∏ Li ( β ) i C'est ce que l'on appelle la vraisemblance partielle de Cox. La maximisation du log de cette vraisemblance partielle donne un estimateur pour β. Un estimateur de sa matrice de variance-covariance est donné par l'inverse de la matrice des dérivées secondes du log likelihood, qui est la matrice d'information. L'estimateur du maximum de vraisemblance partielle des coefficients de régression st presque aussi efficace que l'estimateur du maximum de vraisemblance quand un modèle paramétrique est vrai et connu, comme par exemple le modèle de Weibull. La méthode suppose que les durées de survie sont continues, ce qui exclut la possibilité d'exaequo. Cependant, ls données réelles sont souvent données en semaines, en mois ou en années de telle sorte qu'il y a en fait des ex-aequo. Quelle devrait donc être la contribution à la vraisemblance Lj(β) de tous les sujets j morts au même moment ? Il y a plusieurs solutions : “exacte”, “d'Efron”, et "de Bleslau”; la solution exacte est celle qui prend le plus de temps, et celle de Breslau celle qui en prend le moins, mais qui est aussi la moins bonne. Stratification : quand les données sont stratifiées sur une variable catégorielle, une façon d'en tenir compte consiste à autoriser un taux de hasard de base différent pour chaque strate. En SPLUS on peut le faire en utilisant strata(var-name) dans la spécification du modèle. En SPLUS le modèle à hasards proportionnels, avec les estimateurs par maximum de vraisemblance parielle et leurs variances estimées sont donnés par la fonction coxph. 4 Examen des RESIDUS : Comme dans la régression linéaire, les résidus offrent plusieurs possibilités pour tester l'adéquation du modèle et l'influence des diverses observations sur les estimateurs. Pour des covariables fixées dans le temps, les résidus de martingale sont C. Huber Modèles pour des durées de survie. 88 ) Yi (t j )ri ( β , t j ) resid i = δ i − ∑ ) j ≤ n ∑ Yl ( t j ) rl ( β , t j ) l ≤n Ces résidus représentent essentiellement les indicateurs observés de survie moins l'espérance des indicateurs de survie sous le modèle estimé. Un autre type de résidus sont les résidus de la déviance : deviance. resid i = sigm(resid i ) 2[ − resid i − δ i log(δ i − resid i )] Les résidus de la déviance réduisent (?) l'asymétrie (?), et quand on en fait la somme des carrés, on obtient la déviance. Mais, bien que les résidus de martingale puissent être très asymétriques, ils sont préférables à ceux de la déviance pour identifier les sujets qui sont mal prédits par le modèle. La fonction qui donne les résidus en SPLUS est resid . On obtient les résidus de la déviance en choisissant l'option type = ”deviance”. Par défaut, on obtient les résidus de martingale. Pour identifier les points (les sujets) leviers, c'est à dire ceux dont la suppression aurait un fort impact sur le changement du modèle estimé, on calcule les résidus dfbeta. Le changement de chacune des composantes du vecteur beta des paramètres dû à la suppression de chacun des sujets tour à tour est donné par Splus dans la fonction des résidus en demandant type=”dfbeta”. En SPLUS les résidus sont obtenus après avoir appliqué le modèle de Cox estimé par coxph, en utilisant resid comme nous le faisons dans l'exemple ci-dessous. TESTS pour les HASARDS PROPORTIONNELS : Les tests d'adéquation sont fondés sur les résidus de Schoenfeld changés d'échelle à partir d'un modèle ayant des betas dépendant du temps λ(t;Z i(t)) = λ 0(t)r i(β(t),t). c'est à dire un modèle de type Cox, avec toutes les covariables dans le modèle, mais où chaque composante de beta peut être une fonction du temps. Si en ajustant ce modèle on trouve graphiquement que les composantes de β(t) sont à peu près constantes dans le temps, alors cela plaide en faveur du modèle à hasards proportionnels. Un test global de l'hypothèse nulle H0 : β(t) = β est donné en SPLUS par la fonction cox.zph. Cette fonction teste aussi individuellement chaque composante de beta. 5 EXEMPLE d'UTILISATION du MODELE de COX : Pour faire l'analyse d'un modèle de Cox, faire : options(contrasts='contr.treatment') # pour s'assurer que les facteurs sont # bien traités comme des facteurs. kfit<-coxph(Surv(infectime,cense) ~ age + sex + X2, C. Huber Modèles pour des durées de survie. kfit 89 data = kidney1) # affiche le résultat de l'ajustement (fit. Call: coxph(formula = Surv(infectime, cense) ~ age + sex + X2, data = kidney1) coef exp(coef) se(coef) z p age 0.00318 1.003 0.0111 0.285 0.780000 # age may be removed sex -1.48319 0.227 0.3582 -4.140 0.000035 # huge difference between the sexes X21 0.26282 1.301 0.3779 0.695 0.490000 X22 -1.51907 0.219 0.5822 -2.609 0.009100 #only significant disease variable X23 -0.08796 0.916 0.4064 -0.216 0.830000 Likelihood ratio test=17.6 on 5 df, p=0.00342 n= 76 Quand on utilise options(contrasts='contr.treatment') SPLUS crée trois variables : X21 = dis1 X22 = dis2 X23 = dis3. Les betas correspondants mettent en évidence les différences entre les divers niveaux et le premier d'entre eux. Par exemple le taux d'incidence (taux de hasard) de la maladie 2 n'est que .219 fois celui de la maladie 0 à tout instant. kfit suggère que l'âge n'a pas d'effet significatif. Aussi essayons nous le modèle : kfit1<-coxph(Surv(infectime,cense) ~ sex+X2 , data = kidney1) kfit1 Call: coxph(formula = Surv(infectime, cens) ~ sex + X2, data = kidney1) coef exp(coef) se(coef) z p sex -1.477 0.228 0.357 -4.140 0.000035 X21 0.274 1.315 0.375 0.730 0.470000 X22 -1.506 0.222 0.581 -2.591 0.009600 X23 -0.139 0.870 0.364 -0.383 0.700000 Likelihood ratio test=17.6 on 4 df, p=0.0015 n= 76 La différence considérable entre les hommes et les femmes (la valeur de Z est de – 4.14, c'est à dire que les femmes ont un taux d'infection qui est seulement .228 fois celui des hommes quel que soit le temps, puisque les femmes sont codées 2 et les hommes 1) permet de soupçonner que le taux de base est peut être différent pour les deux sexes. Aussi allons nous essayer d'autoriser un taux de base différent pour chacune des deux strates par sexe. kfit2<-coxph(Surv(infectime,cense) ~ data = kidney1) C. Huber strata(sex) + X2, Modèles pour des durées de survie. 90 kfit2 Call: coxph(formula = Surv(infectime, cense) ~ strata(sex) + X2, data = kidney1) coef exp(coef) se(coef) z p X21 0.270 1.309 0.376 0.718 0.47 X22 -0.676 0.509 0.558 -1.212 0.23 X23 -0.265 0.767 0.367 -0.723 0.47 Likelihood ratio test=4.27 on 3 df, p=0.234 n= 76 Pour choisir entre les deux modèles kfit1 and kfit2, on trace les graphes de leurs résidus de déviance. plot(resid(kfit1, type="deviance")) plot(resid(kfit2, type="deviance")) Un examen attentif des deux graphes ne permet pas de décider clairement entre les deux modèles. On continue donc avec kfit1 car il permet de quantifier la différence entre les sexes. Pour le modèle kfit1, regardons l'influence de chacune des observations sur les coefficients beta. Aucune observation ne semble particulièrement influente. Finalement, on peut tracer la fonction de survie pour la valeur moyenne des covariables pour le modèle stratifié kfit2 : par(mfrow=c(1,1)) plot(survfit(kfit2), lty=2:3) # lty est le type de ligne (line type). legend(500, .9, c("homme", "femme"), lty=2:3) title("Courbes de survie pour les hommes et les femmes \n dialysés ayant les covariables moyennes") # \n signifie :passage à la ligne La différence des fonctions de survie pour les durées d'infection entre les hommes et les femmes est évidente. Pour être complet, traçons aussi la courbe de survie de la durée d'infection avec son intervalle de confiance à 95% pour le modèle kfit1. plot(survfit(kfit1), lty=2:3) title("Courbe de survie pour des patients sous dialyse avec valeurs moyennes des covariables") Résumé des ordres utilisés pour le modèle de Cox: res<-coxph(Surv(duree,censure)~facteur1+facteur2,data=fichier) res donne les coefficients estimés de facteur1 et facteur2 summary(res) donne les trois tests : le test du rapport de vraisemblance: -2 [log(V(βο)−log(V(β_hat)] le test de Wald ((β_hat - βο)/ se(β_hat) le test du score : UtIU summary(survfit(res)) donne la fonction de répartition, avec intervalle de confiance, C. Huber Modèles pour des durées de survie. 91 pour les facteurs 1 et 2 égaux à leurs valeurs moyennes. plot(survfit(res)) donne un dessin de cette fonction de répartition. Pour comparer deux modèles de Cox, notés res1 et res2: a) Calculer : L<-2*(res1$loglik[2]-res2$loglik[2]) car c'est la deuxième composante qui est la log vraisemblance du modèle, la première étant celle du modèle nul, c'est à dire sans covariable. b) Regarder quel est le nombre de degrés de liberté entre les deux modèles et calculer: pchisq(L,df=difference) Pour faire des strates: res<-coxph(Surv(duree,censure)~facteur1+strata(facteur2), data=fichier) C. Huber 92 Chapitre 7 . Le BOOTSTRAP. Pour comprendre le bootstrap, nous allons commencer avec un exemple simple. Supposons que nous ayons un vecteur y de n = 15 observations : y<-c(5,7,3,3,2,4,0,8,0,1,7,3,5,4,6) y median(y) # [1] 4 Nous avons déjà calculé la médiane de y. Si maintenant nous voulons estimer la variance de cet estimateur de la médiane, nous ne disposons pas comme pour la moyenne d'un estimateur simple de cette varance. Nous allons donc construire un estimateur bootstrap de cette variance comme suit : 1. Tirer un échantillon de taille n=15 de y avec remise. 2. Calculer la médiane de l'échantillon ainsi obtenu. 3. Stocker la médiane de l'échantillon Bootstrap dans un vecteur appelé boot. 4. Retourner en 1. On répète ces étapes un grand nombre de fois, soit B = 1 000 fois par exemple. On obtient ainsi un échantillon bootstrap de médianes de taille 1 000. Ce que l'on réalise ainsi est essentiellement une reconstruction de la manière dont a été obtenu l'échantillon et sa médiane en rééchantillonnant y à partir de lui-même. POPULATION → ECHANTILLON ECHANTILLON → ECHANTILLON BOOTSTRAP La variabilité d'échantillonnage dans la population se reflète dans la variabilité d'échantillonnage dans la sous-population y : mais, alors que nous n'avons pas accès à la variabilité de median(y), dans la population totale, le vecteur boot illustre la variabilité de median(y bootstrap) quand on échantillonne dans la sous-population y. Aussi utiliserons nous la variance du vecteur boot pour estimer la variance de median(y). Il est clair que ce que nous venons de faire pour la médiane, nous pouvons le répéter pour estimer la variance d'autres statistiques. La suite des opérations est toujours la même : 1 2. 3. 4. Faire un tirage au hasard avec remise de taille 15, dans l'échantillon y. Calculer la statistique pour le nouvel échantillon bootstrap obtenu en 1. Stocker la statistique ainsi calculée dans un vecteur nommé boot. Retourner en 1. Répéter ces étapes B=1000 fois. C. Huber 93 Cependant, nous pouvons faire beaucoup d'autres choses grçace au bootstrap. Par exemple, nous pouvons obtenir un intervalle de confiance pour le paramètre d'intérêt sans faire aucune supposition sur la loi de son estimateur. Tout ce dont nous avons besoin, c'est d'ordonner le vecteur boot par ordre croissant. Alors, un intervalle de confiance de coefficent de confiance (1-a)*100% , pour le paramètre estmé par la statistique considérée, qui dans notre cas est la médiane est donné par l'intervalle : ( boot [ B*(a/2)] , boot[B*(1-a/2)] ) Tout cela est obtenu par la fonction bootstrap en SPLUS. Exemple 1 : boot.obj1 <- bootstrap(y, statistic = median, B=1000, seed = 0, trace = F) summary(boot.obj1) # Affichage des résultats : Call: bootstrap(data = y, statistic = median, B = 1000, seed = 0, trace = F) Number of Replications: 1000 Summary Statistics: median Observed Bias 4 -0.133 Mean SE 3.867 0.9199 Empirical Percentiles: 2.5% 5% 95% 97.5% median 2 3 5 6 BCa Percentiles: 2.5% 5% 95% 97.5% median 2 2 5 5 On déduit de ce résumé que : La médiane observée valait 4. La moyenne du vecteur boot, qui devrait être un bon estimateur de la médiane de l'échantillon vaut 3.867. Donc, un estimateur du biais de la médiane de l'échantillon comme estimateur de la médiane de la population est : moyenne(médianes bootstrap) -médiane(y) c'est à dire ici -.133. D'autre part, l'estimateur bootstrap de l'écart-type de la médiane vaut .9199. On remarquera que ce n'est pas cet estimateur qui est utilisé pour obtenir l'intervalle de confiance pour la médiane. La première méthode, appelée celle des percentiles, et décrite ci-dessus, donne pour intervalle de confiance bootstrap à 95% pour la médiane de la population [2 ; 6]. C. Huber 94 La deuxième méthode, appelée BCa (corrected percentile method, l'intervalle de confiance bootstrap à 95% vaut [2 ; 5]. Le vecteur boot de médianes bootstrap est donné par SPLUS et peut être utilisé pour tracer un histogramme bootstrap de la médiane pour la population : Plot(boot.obj1) Exemple 2 : Cherchons maintenant un estimateur bootstrap de l'écart-type de l'aire sous la courbe ROC, et un intervalle de confiance bootstrap à 95% pour cette aire. Voici le programme Splus correspondant : # rappelons que Fit<-fitted.values(coron.glm.AWT) (cf regression logistique) mmin <- min(Fit) mmax <- max(Fit) delta <- .01 # nn = nombre des points en lesquels # la sensibilité et la spécificité seront calculées. nn <- floor((mmax - mmin) / delta) boot.frame <- data.frame(coron, Fit) attach(boot.frame) ROCarea <- function(coron, Fit,nn) { rocc<-matrix(0,nn,2) for(ii in (1:nn)) { rocc[ii,1]<-sum(coron* (Fit >ii*.02))/sum(coron) # sensitivite rocc[ii,2]<-1-sum ((1-coron)*(Fit<=ii*.02))/sum(1-coron) #1-specificite } ord <- order(rocc[,2], rocc[,1]) rocc1 <- rocc[ord,] # coron et Fit ordonnes roca<-0 for(ii in 2:nn) { delta <- rocc1[ii,2] - rocc1[ii-1,2] roca <- roca + delta * ((rocc1[ii,1]-rocc1[ii1])/2 + rocc1[ii-1,1]) } c(roca) # roca = area under the ROC curve is # returned by the function ROCarea } # fin de la fonction ROCarea boot.obj1 <- bootstrap(data = boot.frame, statistic = C. Huber 95 ROCarea(coron,Fit,nn), B=1000, seed=0) summary(boot.obj1) # Affichage des résultats : Call: bootstrap(data = boot.frame, statistic = ROCarea(coron, Fit, nn), B = 1000, seed = 0) Number of Replications: 1000 Summary Statistics: Observed Bias Mean SE Param 0.7391 -0.00477 0.7343 0.04811 Empirical Percentiles: 2.5% 5% 95% 97.5% Param 0.634 0.6532 0.8101 0.826 BCa Percentiles: 2.5% 5% 95% 97.5% Param 0.6364 0.6572 0.8134 0.8267 plot(boot.obj1) A partir de l'histogramme de la distribution bootstrap de l'aire au-dessous de la courbe ROC, on constate que cette distribution est remarquablement normale. Pour vérifier cette impression, on peut faire : qqnorm(boot.obj1) qui donne un graphe des quantiles de la loi normale contre les quantiles des données (QQ plot) pour l'échantillon bootstrapé des aires sous la courbe ROC. La linéarité de la courbe obtenue suggère bien qu'en effet la loi de l'aire sous la courbe ROC est approximativement normale. L'intervalle de confiance à 95%, obtenu grâce aux percentiles corrigé, pour l'aire sous la courbe ROC, vaut [.6364 ; .8267]. Par ailleurs, l'estimateur bootstrap de l'écart-type de l'estimateur de l'aire vaut .0481. Remarquons que si on utilisait l'intervalle de confiance à 95% fondé sur l'approximation normale, on aurait .7391 +/- 1.96*.0481 , soit [.645 ; .833]. Cet intervalle est très proche de celui donné par les percentiles bootstrap, ce qui était prévisible après le tracé du QQ plot. C. Huber