solutions
Transcription
solutions
Introduction à R pour la recherche biomédicale Exercices corrigés > a <- data.frame(x1=rnorm(n, 10), x2=rnorm(n, 12), + x3=gl(2, n/2)) > write.csv(a, file="a.csv") 5. On peut utiliser l'indexation, par exemple > d$class[which(d$height==max(d$height))] Le langage R 1. Comme il n'est pas précisé pas si le facteur doit être équilibré et/ou préalablement trié, on peut utiliser sample. Autres possibilités : > x <- gl(2, 10, labels=c("toto","titi")) > relevel(x, ref="titi") [1] B Levels: A B [1] toto toto toto toto toto toto toto toto toto [10] toto titi titi titi titi titi titi titi titi [19] titi titi Levels: titi toto Ou alors trier les données : > d[do.call(order, d),"class"][40] [1] B Levels: A B 2. Le plus simple est d'utiliser matrix et apply : > m <- matrix(c(6,7,2,1,5,9,8,3,4), nr=3, byrow=TRUE) > apply(m, 1, sum) # lignes [1] 15 15 15 > apply(m, 2, sum) # colonnes [1] 15 15 15 3. On peut utiliser rbinom ou sample. Dans le premier cas, une réalisation de l'événement βtirer 20 fois une pièceβ s'écrit > rbinom(20, 1, p=0.5) [1] 0 0 1 1 0 0 1 0 1 1 1 1 1 1 0 1 1 0 0 0 Dans le second cas, > sample(0:1, 20, replace=TRUE) [1] 1 1 0 1 1 1 0 0 0 1 0 0 0 0 1 1 0 1 0 1 On peut insérer ces commandes dans une boucle, par exemple > k <- seq(10, 10000, by=100) > res <- numeric(length(k)) > for (i in seq_along(k)) + res[i] <- sum(rbinom(k[i], 1, p=0.5))/k[i] ou alors, en plus élégant : > tails <- function(n=5, p=0.5) sum(rbinom(n, 1, p))/n > res <- sapply(k, tails) Ci-dessous figure l'évolution du βnombre de pilesβ lorsque k augmente, avec un intervalle de confiance à 95% asymptotique basé sur la loi normale, π ± τΊ½.τ» τ»βπ(τΊ½ β π)/π. Proportion de piles 0.60 0.55 β β β β β β β ββ 0.50 β β ββ β β β β β β β β β β β β β β βββ β β ββ β ββ β β β ββ β β ββ ββ ββ β β ββ ββ β ββ β ββ β ββ β β β β ββ β β βββ β β β β β β ββ β β β β ββ β β 0 2000 4000 6000 8000 [1] old old new old new old Levels: new old 7. Quelques suggestions : vérifier le format du fichier à l'aide d'un éditeur de texte (en particulier : ligne d'en-tête, séparateur de champ, séparateur décimal) afin de définir la commande à utiliser pour charger le fichier ; vérifier le type des variables que R a lu ; afficher un résumé numérique (summary) et examiner si les distributions univariées semblent cohérentes. Ici, on ne se souciera pas d'imputation : les valeurs suspectes seront converties en valeurs manquantes. > WD <- "../pub" > lung <- read.table(paste(WD, "lungcancer.txt", sep="/"), + header=TRUE, na.strings=".") > summary(lung) # str(lung) time Min. : -2.0 1st Qu.: 73.0 Median :167.0 Mean :186.1 3rd Qu.:275.0 Max. :558.0 NA's : 2.0 β β β β β ββ β On peut ajouter as.character pour supprimer l'affichage des attributs du facteur. 6. La solution simple consiste à créer le schéma d'allocation de base des niveaux du facteur et à le dupliquer pour obtenir la taille voulue. Par exemple, > tx <- factor(rep(rep(c("std","new"), each=3), 10)) Comme le motif est régulier (3 std suivi de 3 new), on peut utiliser gl directement : > tx <- gl(2, 3, 60, labels=c("std","new")) Pour la suite, il s'agit de manipulation des niveaux du facteur. > levels(tx)[1] <- "old" > tx <- relevel(tx, ref="new") > head(tx <- sample(tx)) 10000 Nombre de lancers 4. Une solution de base : > n <- 10 1 age Min. : 5.00 1st Qu.:53.00 Median :59.00 Mean :59.02 3rd Qu.:66.00 Max. :78.00 NA's : 2.00 cens Min. :0.0000 1st Qu.:0.0000 Median :1.0000 Mean :0.5191 3rd Qu.:1.0000 Max. :2.0000 + > names(anorex) vital.capac high:95 low :35 low : 1 [1] [6] [11] [16] [21] > head(sort(lung$time)) [1] -2 0 0 1 1 2 > head(sort(lung$age)) "weight" "purge" "school" "body" "diag2" to.data.frame=TRUE) "mens" "hyper" "satt" "time" "time2" "fast" "fami" "sbeh" "diag" "binge" "eman" "mood" "tidi" > table(lung$cens) > anorex <- anorex[,-c(21,22)] > table(anorex$time) 0 1 64 66 1 2 3 4 55 53 54 55 [1] 5 35 36 38 39 40 2 1 > table(lung$vital.capac) > class(anorex$time) high 95 [1] "numeric" low low 35 1 > anorex$time <- factor(anorex$time, ordered=TRUE) Les données sont stockées au format βlongβ, c'est-à-dire qu'on dispose d'une variable codant la période d'acquisition des données pour chaque cas (ligne). Sous réserve qu'il n'y ait pas de valeurs manquantes isolées, un tableau de fréquence de l'identifiant patient (number) doit nous permettre de savoir de combien de cas complets nous disposons. > any(apply(anorex, 1, function(x) sum(is.na(x))) != 0) > lung <- within(lung, { + time[time<0] <- NA + age[age==5] <- NA + cens[cens==2] <- NA + cens <- factor(cens) + levels(vital.capac)[2:3] <- "low" + }) Après ces modifications, on peut vérifier que tout semble normal : > summary(lung) time Min. : 0.0 1st Qu.: 74.5 Median :167.5 Mean :187.6 3rd Qu.:277.0 Max. :558.0 NA's : 3.0 vital.capac high:95 low :36 age Min. :35.00 1st Qu.:53.00 Median :59.50 Mean :59.44 3rd Qu.:66.00 Max. :78.00 NA's : 3.00 [1] FALSE > unique(table(anorex$number)) cens 0 :64 1 :66 NA's: 1 [1] 4 3 > table(table(anorex$number)==4) FALSE 3 "id" "los" "priorstr" "cooking" TRUE 52 > names(which(table(anorex$number) < 4)) [1] "47" "71" "76" Pour connaître la répartition par groupe clinique, il faut bien sûr filtrer sur la période (time). > with(subset(anorex, time=="1"), table(diag)) diag Anorexia Nervosa 25 Anorexia with Bulimia Nervosa 9 Bullimia Nervosa after Anorexia 14 Atypical Eating Disorder 7 8. Pour charger des fichiers de données SPSS (*.sav), on a besoin du package foreign. Voir aussi R Data Import/Export, § 3.1 (http://bit.ly/yRnVit). > library(foreign) > adl <- read.spss(paste(WD, "adl.sav", sep="/"), + to.data.frame=TRUE) > names(adl) [1] [5] [9] [13] "vomit" "frie" "preo" "number" Enfin, pour le score mood, comme rien n'est précisé on va calculer le score moyen aux quatre dates : > with(subset(anorex, diag=="Anorexia Nervosa"), + tapply(mood, time, mean)) "group" "gender" "age" "diabetic" "hypertns" "afib" "smoker" "psd" "travel" "housekpg" > apply(adl, 2, function(x) sum(is.na(x))) id group 0 0 diabetic hypertns 0 0 psd travel 0 0 gender age 0 0 afib priorstr 0 0 cooking housekpg 0 0 1 2 3 4 2.360000 2.652174 2.791667 2.840000 los 0 smoker 0 10. Ici, il s'agit d'un jeu de fichier propre à R, que l'on peut charger avec la commande data, éventuellement en précisant le package dans lequel se trouve le jeu de données. > data(birthwt, package="MASS") > summary(birthwt) > table(adl$group) Control Treatment 46 54 9. Même principe que ci-dessus. On charge d'abord les données : > anorex <- read.spss(paste(WD, "anorectic.sav", sep="/"), 2 low Min. :0.0000 1st Qu.:0.0000 Median :0.0000 Mean :0.3122 age Min. :14.00 1st Qu.:19.00 Median :23.00 Mean :23.24 lwt Min. : 80.0 1st Qu.:110.0 Median :121.0 Mean :129.8 3rd Qu.:1.0000 Max. :1.0000 race Min. :1.000 1st Qu.:1.000 Median :1.000 Mean :1.847 3rd Qu.:3.000 Max. :3.000 ptl Min. :0.0000 1st Qu.:0.0000 Median :0.0000 Mean :0.1958 3rd Qu.:0.0000 Max. :3.0000 ui Min. :0.0000 1st Qu.:0.0000 Median :0.0000 Mean :0.1481 3rd Qu.:0.0000 Max. :1.0000 3rd Qu.:26.00 3rd Qu.:140.0 Max. :45.00 Max. :250.0 smoke Min. :0.0000 1st Qu.:0.0000 Median :0.0000 Mean :0.3915 3rd Qu.:1.0000 Max. :1.0000 ht Min. :0.00000 1st Qu.:0.00000 Median :0.00000 Mean :0.06349 3rd Qu.:0.00000 Max. :1.00000 ftv bwt Min. :0.0000 Min. : 709 1st Qu.:0.0000 1st Qu.:2414 Median :0.0000 Median :2977 Mean :0.7937 Mean :2945 3rd Qu.:1.0000 3rd Qu.:3487 Max. :6.0000 Max. :4990 Descriptive Statistics by low +------------+--------------+--------------+ | |No |Yes | | |(N=130) |(N=59) | +------------+--------------+--------------+ |race : White| 56% (73) | 39% (23) | +------------+--------------+--------------+ | Black | 12% (15) | 19% (11) | +------------+--------------+--------------+ | Other | 32% (42) | 42% (25) | +------------+--------------+--------------+ |smoke : Yes | 34% (44) | 51% (30) | +------------+--------------+--------------+ |ui : Yes | 11% ( 14) | 24% ( 14) | +------------+--------------+--------------+ |ht : Yes | 4% ( 5) | 12% ( 7) | +------------+--------------+--------------+ |age |19.0/23.0/28.0|19.5/22.0/25.0| +------------+--------------+--------------+ Analyse exploratoire 11. Le fichier étant au format csv, on le charge avec read.csv : > WD <- "../pub" # à adapter > cereal <- read.csv(paste(WD, "cereal.csv", sep="/")) Plusieurs solutions pour afficher les distributions : histogrammes, matrice de dispersion. La dernière à l'avantage de combiner les deux approches. Considérons par exemple les 6 premières variables numériques : > p <- splom(~ cereal[,3:8], type=c("p","smooth"), + diag.panel=panel.hist.splom, xlab="", + pscale=0, varname.cex=0.7, cex=.8, + col="grey60") On peut décider de recoder les variables low, race, smoke, ui et ht en facteurs. > yesno <- c("No","Yes") > ethn <- c("White","Black","Other") > birthwt <- within(birthwt, { + low <- factor(low, labels=yesno) + race <- factor(race, labels=ethn) + smoke <- factor(smoke, labels=yesno) + ui <- factor(ui, labels=yesno) + ht <- factor(ht, labels=yesno) + }) Pour les tableaux croisés, on peut utiliser table ou xtabs (parfois plus commode car on peut utiliser des notations par formule à partir du data.frame). > xtabs(~ low + race, data=birthwt) β β β β β β β ββ ββ β β β ββ β β β ββ β β β βββ β β ββ β β β β β β ββ β β ββ β ββ β β β race low White Black Other No 73 15 42 Yes 23 11 25 ββ β ββ β β β β β β β ββ βββ ββ β β β β β > with(birthwt, table(low, smoke)) smoke low No Yes No 86 44 Yes 29 30 β β β On peut également profiter des commandes très pratiques dans le package Hmisc,1 par exemple la commande summary.formula qui permet d'effectuer des résumés de variables quantitative ou qualitative conditionnellement aux niveaux d'un facteur. Surtout, ne pas oublier de lire l'aide en ligne : help(summary.formula). > library(Hmisc) > summary(low ~ race + smoke + ui + ht + age, + data=birthwt, method="reverse") β β β ββ β β β β ββ ββ β β ββ ββ ββ β β β β βββ β ββ β β β β β β β β β β ββ β β β β β β β β β β β β β β β β β β β ββ β β β β β β β β βββ ββ β β ββ β β ββ β β β β β β β ββ βββ β ββ β β ββ β β β ββ β ββ β ββ β ββ βββ β β β β β β β β β β β β β β β β β ββ β β ββ ββ βββ β β ββ β β ββ β β β β β β β β βββ ββ ββ β β βββ β βββ ββ β β β β β β β β β β β calories β β β β β β β β β β β ββ ββ β ββ βββ β β β ββ ββ β ββ β β β ββ ββ ββ β β β β β β β β β β β β β β β β ββ β β ββ β β β ββββ β β β β β ββ β β β β β β ββββ β β β β β β ββ β ββ β β β β β β β β β β β β β β β β β β ββ ββ β ββ βββ β β β β β β β β β β β β β β β ββ β β β ββ β ββ β β ββ β β ββ β β β βββ ββ β β β β ββ βββ β β ββ β β β β β β β β β β βββ β ββ β ββ β β βββ β β ββββββββ β β ββ ββ β β βββ β β β β β β β β β β β β β β β β ββ β ββββ ββ β β βββ β β ββ β ββ β ββ ββ β β β β β β β β β ββ ββ β β β ββ β ββ β ββ β ββββ ββ β β β βββ ββ β β β β β β βββ β β β β β β β β β β β β β β βββ β ββ β β ββ β ββ β β β β β β K. β β ββ ββ β β β β ββ β βββ β β β β β ββ ββ βββ β fat X.fat β Na. β β β ββ β β ββ ββ ββ β β β ββ ββ β β β ββ β β β β β β ββ ββ β β β β β ββ β β β ββ β β β β β β β ββ β β β β β β ββ ββ β β ββ β β ββ β β βββ β β size β β β β β ββ ββ ββ β β β β β βββ β β β β Notons que l'on a supprimé l'affichage des unités (pscale=0), par souci de lisibilité et parce que dans une optique βexploratoire visuelleβ on n'en a pas vraiment besoin. Lorsque le nombre d'observations est beaucoup plus important, on peut avantageusement remplacer le nuage de points par un nuage en densité, en ajoutant panel=panel.hexbinplot (après avoir chargé le package hexbin). Enfin, on peut avoir une approche plus interactive et étudier les distributions bivariées 1 C Alzola and F Harrell. An Introduction to S and The Hmisc and Design Libraries. 2006. URL: http://biostat.mc.vanderbilt.edu/wiki/Main/RS. 3 une à une. Le code suivant permet de sélectionner interactivement les points et afficher le nom de la compagnie.2 > xyplot(cost ~ calories, cereal) > while (!is.null(fp <- trellis.focus())) { + if (fp$col > 0 & fp$row > 0) + panel.identify(labels=cereal$company) + } 12. On s'intéresse aux données à l'inclusion (time=1), donc dans un premier temps il est intéressant de réduire le tableau à ces seules données. Voir l'exercice 9 pour le chargement du fichier et le recodage de time en facteur. Notons que la commande summary ne renvoit pas l'écart-type. Elle reste toutefois utile pour résumer les distributions univariées, vérifier leur symétrie et la présence d'observations atypiques ! > anorex.scores.t1 <- subset(anorex, time=="1", + select=weight:body) > sapply(anorex.scores.t1, + function(x) c(mean=mean(x), sd=sd(x))) 3 4 6 20 22 36 47 54 56 70 72 88 121 138 142 172 189 240 X1 fast binge purge binge purge binge preo purge fami purge fami fami eman frie mood satt sbeh body X2 weight weight weight mens mens fast fast binge binge vomit vomit purge fami eman eman school satt preo value 0.3340665 -0.5726599 -0.4260873 -0.3201155 -0.4378354 -0.3103808 0.3106862 0.7043708 -0.3792735 0.4230144 -0.3332859 -0.4151892 0.3269283 0.4245090 -0.3887455 0.7405494 0.4015018 0.4195842 14. Il faut dans un premier temps calculer les scores moyens par période. Il existe plusieurs façons d'aborder le problème : weight mens fast binge procéder colonne par colonne, et utiliser tapply pour calmean 1.6545455 1.218182 1.4363636 3.254545 culer les scores moyens conditionnellement à time ; passer sd 0.9854155 0.533712 0.8336363 1.294120 du format βwideβ au format βlongβ (en utilisant le package vomit purge hyper fami reshape). mean 3.6727273 2.763636 1.9636364 1.8727273 sd 0.9438798 1.477782 0.6074731 0.8400738 Pour la première approache, voici une solution possible : eman frie school satt > items <- 1:16 mean 1.7818182 2.4727273 2.0181818 2.1454545 > res <- matrix(nr=16, nc=4, sd 0.6580248 0.8131909 0.7325983 0.7556744 + dimnames=list(colnames(anorex[,items]), sbeh mood preo body + levels(anorex$time))) mean 1.818182 2.2727273 1.4363636 1.890909 > for (i in items) sd 0.434226 0.7806584 0.5362295 0.785817 + res[i,] <- tapply(anorex[,i], anorex[,"time"], mean) 13. Ici, l'idée est de construire la matrice de corrélation des 16 Dans ce cas, on effectue les opérations item par item, soit items, en utilisant le coefficient de Spearman. Cette matrice, 16 opérations au total. On pourrait également calculer les π , est symétrique, c'est-dire que πππ = πππ , et sa diagonale ne moyennes pour chaque niveau du facteur time, soit 4 opéracontient que des 1 (puisqu'il s'agit de la corrélation de l'item tions seulement : avec lui-même). Si l'on veut filtrer les coefficients, il faut > for (i in levels(anorex$time)) considérer soit la partie inférieure, soit la partie supérieure + res[,i] <- apply(subset(anorex, time==i, items), (hors diagonale). + 2, mean) > correl.t1 <- cor(anorex.scores.t1, method="spearman") Quant à la second approche, on utilise une combinaison de > correl.t1[upper.tri(correl.t1, diag=TRUE)] <- NA melt et cast pour passer du format βwideβ au format βlongβ À partir de là, une instruction comme apply(correl.t1, 1, funcet revenir avec les données moyennées au format βwideβ. tion(x) x > 0.3) indiquerait précisément quelles paires de vari> anorex.scores <- melt(anorex[,1:17], id.vars=17) ables ont un coefficient de corrélation > τΊΌ.τΊΏ. Pour extraire > res <- with(anorex.scores, directement les paires de variables qui nous intéressent, on + aggregate(value, peut faire : + list(item=variable, time=time), > names(unlist(apply(correl.t1, 1, + mean)) + function(x) which(abs(x) > .3)))) > cast(res, item ~ time) [1] [4] [7] [10] [13] [16] "fast.weight" "binge.fast" "purge.binge" "fami.vomit" "frie.eman" "mood.eman" "binge.weight" "purge.weight" "purge.vomit" "fami.purge" "satt.school" "preo.fast" "binge.mens" "purge.mens" "fami.binge" "eman.fami" "sbeh.satt" "body.preo" item 1 2 3 4 1 weight 1.654545 2.641509 2.759259 3.036364 2 mens 1.218182 1.773585 2.222222 2.381818 3 fast 1.436364 2.660377 2.833333 2.963636 4 binge 3.254545 3.584906 3.500000 3.600000 5 vomit 3.672727 3.735849 3.777778 3.709091 6 purge 2.763636 3.452830 3.444444 3.581818 7 hyper 1.963636 2.339623 2.537037 2.709091 8 fami 1.872727 2.301887 2.500000 2.709091 9 eman 1.781818 2.283019 2.462963 2.672727 10 frie 2.472727 2.509434 2.537037 2.600000 mais ce n'est pas très élégant. Une autre solution est de convertir la matrice de corrélation en data.frame : > library(reshape) > subset(melt(correl.t1), abs(value) > .3) 2 D Sarkar. Lattice, Multivariate Data Visualization with R. Springer, 2008, p. 219. 4 L'avantage de cette approche est que l'on peut utiliser plot sur le résultat de la commande ci-dessus pour obtenir un graphique de type dotplot, comme illustré ci-dessous : > adl.num <- subset(adl, select=c(group, travel:housekpg)) > plot(summary(group ~ ., data=adl.num, method="reverse")) 11 school 2.018182 2.188679 2.462963 2.454545 12 satt 2.145455 2.245283 2.314815 2.400000 13 sbeh 1.818182 2.433962 2.351852 2.490909 14 mood 2.272727 2.528302 2.814815 2.727273 15 preo 1.436364 2.188679 2.333333 2.400000 16 body 1.890909 2.188679 2.407407 2.363636 Proportions Stratified by group En fait cette dernière approche est intéressante lorsque l'on utilise lattice car elle permet de travailler directement à partir de formule. Quoi qu'il en soit, il est toujours plus intéressant d'avoir des données au format βlongβ pour faire des graphiques avec lattice. Avec les deux premières solutions, on peut convertir la matrice res en data.frame en tapant as.data.frame(as.table(res)). > p <- xyplot(value ~ time, data=anorex.scores, + groups=variable, type="a", + ylab="Score moyen", + auto.key=list(space="right", + lines=TRUE, points=FALSE)) travel Same as before illness Gets out if someone else drives Gets out in wheelchair Home or hospital bound Bedβridden β β β β ββ β β ββ cooking Plans and prepares meals Some cooking but less than normal Gets food out if prepared by other Does nothing for meals Never did any β β β β ββ β β β β housekpg As before Does at least 1/2 usual Occasional dusting of small jobs No longer keeps house Never did any β β β β β β β β β β 4.0 0.0 0.2 0.6 0.8 1.0 Proportion 3.5 weight mens fast binge vomit purge hyper fami eman frie school satt sbeh mood preo body 3.0 Score moyen 0.4 2.5 2.0 16. Voir l'exercice 11 pour le chargement des données. On procède dans un premier au recodage des compagnies. > company2 <- factor(cereal$company) > levels(company2)[c(2,4:8)] <- "other" On pourrait remplacer la dernière instruction par > levels(company2)[!(levels(company2) %in% + c("g mills","kellogs"))] <- "other" qui paraît plus compliquée mais qui présente l'avantage de ne pas avoir à se soucier du nom des variables à regrouper. Ensuite, le graphique est assez simple à réaliser : > p <- parallel(~ cereal[,3:8], groups=company2, + horizontal.axis=FALSE, auto.key=TRUE) 1.5 1.0 1 2 3 4 time 15. Voir l'exercice 8 pour le chargement des données. On a toujours deux options : procéder variable par variable en utilisant table, ou utiliser des fonctions spécifiques (reshape ou Hmisc). > adl.bin <- subset(adl, select=c(group, diabetic:psd)) > library(Hmisc) > summary(group ~ ., data=adl.bin, method="reverse") Descriptive Statistics by group +--------------+---------+----------+ | |Control |Treatment | | |(N=46) |(N=54) | +--------------+---------+----------+ |diabetic : Yes| 17% ( 8)| 13% ( 7) | +--------------+---------+----------+ |hypertns : Yes| 28% (13)| 37% (20) | +--------------+---------+----------+ |afib : Yes | 7% ( 3)| 2% ( 1) | +--------------+---------+----------+ |priorstr : Yes| 0% ( 0)| 2% ( 1) | +--------------+---------+----------+ |smoker : Yes | 2% ( 1)| 0% ( 0) | +--------------+---------+----------+ |psd : Yes | 33% (15)| 35% (19) | +--------------+---------+----------+ g mills other kellogs Max Min size calories X.fat fat Na. K. À la lecture de ce graphique, il apparaît clairement que les deux marques g mills et kellogs diffèrent systématiquement dans la teneur en graisses (staurées ou non), et contrairement aux autres marques, celles-ci ont des teneurs en Na. supérieures aux autres marques. 5 > p <- qqmath(~ age | group, data=adl, aspect=1, + prepanel=prepanel.qqmathline, + panel=function(x, ...) { + panel.qqmathline(x, ...) + panel.qqmath(x, ...) + }) 17. On part avec les données pré-traitées à l'exercice 14 ; les données aggrégées par temps se trouve dans la variable res. > p <- dotplot(reorder(item, x, min) ~ x, data=res, + group=time, auto.key=list(column=2), + xlab="Score moyen", aspect=.6) 1 2 3 4 β β2 β1 Control β 85 β β β β 80 β 75 β β 70 β β β ββ β β βββ ββββββββββ β β β β β β β β β β β βββ β β ββββββ ββββ β2 β ββ β β 65 β β1 0 1 ββ β β β βββ ββββ β β β β β β β β β β β β β β β β β β β β β β β β β ββββββββ β β βββ 2 qnorm β 2.0 2.5 3.0 3.5 On notera que, par défault, lattice propose un arrangement des sous-figures (βpanelsβ) utilisant des axes verticaux communs. Cela fait souvent sens, mais on peut s'en affranchir en contrôlant l'argument scale=, comme dans l'exemple suivant : > update(p, scale=list(relation="free")) 19. Ici, on peut envisager deux solutions. la première consiste à afficher en parallèle les distributions avant et après traitement, pour chaque groupe de traitement. La seconde consiste à montrer plus explicitement la covariation des scores au niveau individuel, et elle est plus avantageurse pour détecter des patterns d'association spécifiques ou des individus atypiques. > data(anorexia, package="MASS") > p <- bwplot(value/2.2 ~ variable | Treat, + data=melt(anorexia), aspect=1.2, + layout=c(3,1), ylab="Weight (kg)") On peut choisir de trier les scores par n'importe quelle fonction. Ici on a choisi le score minimum, qui est apparemment toujours observé à l'inclusion. 18. Considérant que les données sont déjà chargées sous R (exercice 8), pour afficher la distribution des effectifs, on utilise type="count", autrement on a par défaut des fréquences relatives. > p <- histogram(~ age | group, data=adl, + type="count", aspect=1) 65 70 Control 75 80 85 90 Treatment 25 20 Count β β β Score moyen 15 10 5 CBT 0 Cont FT 70 75 80 85 Weight (kg) 45 65 90 age Notons qu'il est souvent avantageux de remplacer les histogrammes par des densités (voir densityplot). Pour la fonction de répartition, > p <- qqmath(~ age, data=adl, group=group, + distribution=qunif, aspect=.5, + auto.key=list(x=.05, y=.95)) Control Treatment β β βββ ββββββββββ ββββββββ βββ ββββ β ββββββ ββββ βββ 65 0.0 0.2 0.4 0.6 0.8 β β β β β β β Postwt Prewt Postwt Prewt Postwt Voici la seconde solution, qui utilise simplement un diagramme de dispersion, auquel on ajoute la βdroite identitéβ (π¦ = π₯) pour faciliter la lecture : les points au dessus de celle-ci dénotent des patients dont le poids a augmenté après traitement. > anorexia <- transform(anorexia, Prewt=Prewt/2.2, + Postwt=Postwt/2.2) > p <- xyplot(Postwt ~ Prewt | Treat, data=anorexia, + layout=c(3,1), aspect="iso") > p <- update(p, + panel=function(...) { + panel.xyplot(...) + panel.abline(0, 1, col="lightgrey") + }) β 70 β Prewt β 75 β β β 80 β 40 35 85 age 2 β 1.5 90 1 90 β age vomit binge purge frie mood satt school hyper body fami sbeh eman weight preo fast mens 0 Treatment 1.0 qunif Pour les QQ plots, 6 15 16 17 18 19 β CBT Cont β FT β β 20 β β β Postwt β β β β β ββ β β β β ββ β ββ 16 β β ββ β β β 18 β β β β β β β β 16 β β β β ββ 17 18 19 Mean 3rd Qu. 0.62 0.81 Max. 4.30 Median -0.072 Mean 3rd Qu. -0.093 0.740 Max. 3.300 Median 1.90 Mean 3rd Qu. 1.50 2.40 Max. 4.40 21. Puisque les mesures sont collectées sur les 10 mêmes sujets, le test approprié est un test pour données appariées : > with(sleep, tapply(extra, group, mean)) 1 2 0.75 2.33 > t.test(extra ~ group, data=sleep, paired=TRUE) Paired t-test data: extra by group t = -4.0621, df = 9, p-value = 0.002833 alternative hypothesis: true difference in means is not equal to 0 95 percent confidence interval: -2.4598858 -0.7001142 sample estimates: mean of the differences -1.58 20. Ici, il s'agit globalement de la seconde option de visualisation évoquée à l'exercice précédent, mais en superposant les graphiques dans la même figure, soit > p <- xyplot(Postwt ~ Prewt, data=anorexia, + group=Treat, span=.8, + type=c("p","g","smooth"), + auto.key=list(space="right", points=TRUE, + lines=TRUE)) β β β β β β β β 18 CBT Cont FT β β β β β β β β β β β β β β Le résultat du test indique qu'il y a bien une différence significative (π = τΊΌ.τΊΌτΊΌτΊΏ) en faveur du deuxième traitement (augmentation de une heure et demie de sommeil environ). Si les groupes étaient considérés comme indépendants (soit 20 sujets au total), on obtient une p-valeur > 0.05, d'où l'importance de prendre en considération l'appariement lorsqu'il est présent ! > t.test(extra ~ group, data=sleep)$p.value [1] 0.07939414 22. L'idée générale est d'estimer la précision d'un estimateur, ici une moyenne de différences, en se servant de l'échantillon disponible par une méthode que l'on appelle le bootstrap. > di <- with(sleep, abs(diff(tapply(extra, + group, mean)))) > sleep2 <- with(sleep, cbind(extra[group==1], + extra[group==2])) > B <- 499 > res <- numeric(B) > for (i in 1:B) + res[i] <- mean(apply(sleep2[sample(10, rep=TRUE),], + 1, diff)) > quantile(res, c(.025, .975)) β β 16 β β β 2.5% 97.5% 1.0090 2.3765 β β 16 β 16 Mesures et tests d'association Median 0.29 20 β 15 16 17 18 19 $FT Min. 1st Qu. -1.10 0.81 β β Prewt On calcule les scores de différence assez simplement, comme suit : > anorexia$an.diff <- with(anorexia, Postwt-Prewt) et on les résume aussi simplement : > with(anorexia, tapply(an.diff, Treat, summary, + digits=2)) $CBT Min. 1st Qu. -1.90 -0.14 $Cont Min. 1st Qu. -2.500 -1.400 CBT Cont FT β β β β 15 Prewt Postwt β ββ β β ββ β β β β ββ β β 15 16 17 18 19 15 18 β ββ ββ β β β β ββ β β β β ββ β β β β β ββ β ββ β Postwt β β 20 17 18 19 Prewt 100 Le paramètre span permet de contrôler le degré de lissage des courbes βloessβ. En utilisant des droites de régression, soit en supposant linéarité de la relation fonctionnelle entre les deux séries de mesure (typiquement, dans le cadre d'une analyse de covariance), on a les résultats suivants : > p <- update(p, type=c("p","g","r"), aspect=.5) Count 80 60 40 20 0 1.0 7 1.5 2.0 2.5 On peut vérifier que l'on obtient sensiblement les mêmes résultats en utilisant les fonctions R dédiées. > library(boot) > d <- function(d, x) mean(apply(d[x,], 1, diff)) > boot.ci(boot(sleep2, d, R=499), type="perc") purge π y a βπ = BOOTSTRAP CONFIDENCE INTERVAL CALCULATIONS Based on 499 bootstrap replicates CALL : boot.ci(boot.out = boot(sleep2, d, R = 499), type = "perc") Intervals : Level Percentile 95% ( 0.985, 2.450 ) Calculations and Intervals on Original Scale 23. Dans un premier temps, on filtre les données qui nous intéressent, à savoir mood à π‘τΊ½ et π‘τ» . On a également besoin de l'identifiant patient pour s'assurer du bon appariement des scores. > anorex0 <- subset(anorex, time %in% c(1,4) & + diag=="Anorexia Nervosa", + select=c(number,time,mood)) > all(table(anorex0$number)==2) [1] TRUE > xtabs(~ time + mood, data=anorex0) time 1 2 3 4 mood 1 5 0 0 1 2 3 6 14 0 0 0 0 2 22 > wilcox.test(mood ~ time, data=anorex0, paired=TRUE, + exact=FALSE) Wilcoxon signed rank test with continuity correction data: mood by time V = 9, p-value = 0.01628 alternative hypothesis: true location shift is not equal to 0 Le résultat suggère que les scores ont tendance à évoluer dans le sens de valeurs plus élevées entre l'inclusion et la fin de l'étude. Ceci étant, en regardant les données brutes, il apparaît clairement que les scores se concentrent autour de 3 à π‘τ» (88 % des observations). β β β 1 β ββ ββ 1 ββ β β β β 2 0.704 0.423 1.000 τ»! τΊΎ!(τ»βτΊΎ)! = τ» manières de combiner deux éléments pris parmi n). En voici une, selon laquelle on sélectionne les colonnes de anorex1 à tester à partir d'un tableau résumant les combinaisons possibles : > cb <- combn(4, 2) > res.p <- numeric(ncol(cb)) > for (i in 1:ncol(cb)) + res.p[i] <- cor.test(anorex1[,cb[1,i]], + anorex1[,cb[2,i]], + method="spearman", + exact=FALSE)$p.value Enfin, voici une façon possible pour regrouper les résultats dans un data.frame, qui repose globalement sur ce qui a été vu à l'exercice 13 : > res.c[upper.tri(res.c, diag=TRUE)] <- NA > res.c <- subset(melt(res.c), !is.na(value)) > res.c$p <- res.p > res.c$p.adj <- ifelse(res.c$p*6 < 1, res.c$p*6, 1) On pourra vérifier que la dernière colonne peut également être obtenue par la commande suivante : > p.adjust(res.c$p, "bonf") Le résultat ci-dessous indique que, à l'exception de weight/vomit et binge/vomit, toutes les associations testées sont significatives, même après une correction (très conservative) pour les comparaisons multiples. > format(res.c, digits=3, scientific=FALSE) 2 3 4 7 8 12 X1 X2 value p p.adj binge weight -0.573 0.00000491182 0.0000294709 vomit weight -0.120 0.38353694716 1.0000000000 purge weight -0.426 0.00118078100 0.0070846860 vomit binge 0.170 0.21490582189 1.0000000000 purge binge 0.704 0.00000000197 0.0000000118 purge vomit 0.423 0.00129263518 0.0077558111 La même procédure peut être exploitée pour refaire les calculs à π‘τ» : il suffit de changer le filtre initial, time==4. On obtiendrait les résultats suivants : > format(res.c, digits=3, scientific=FALSE) 2 3 4 7 8 12 ββ βββ β β β β βββ β β ββ β ββ time 4 -0.426 Ici, il existe plusieurs façons de procéder pour tester les corrélations des variables prises deux à deux (on rappelle qu'il β βββ β βββ ββ β βββ 3 X1 X2 value p p.adj binge weight 0.2910 0.03113317414 0.1867990449 vomit weight 0.0597 0.66515436534 1.0000000000 purge weight 0.2406 0.07684707553 0.4610824532 vomit binge 0.3536 0.00808064175 0.0484838505 purge binge 0.6880 0.00000000647 0.0000000388 purge vomit 0.4811 0.00020071774 0.0012043065 Cette fois-ci, toutes les associations sont positives ; en particulier, la corrélation entre binge et weight est passée de -0.573 à 0.291 alors que celle entre purge et weight est passée de -0.426 à 0.241, et seraient juger non-significatives à π‘τ» selon le critère de Bonferroni. Toutefois, on a un peu triché car on a appliqué les corrections sur 6 comparaisons, alors qu'en réalité on en a réalisé 12 ! 25. Les données ont déjà été résumées visuellement sous forme de boîtes à moustaches à l'exercice 19. Le modèle d'ANOVA est assez simple à réaliser : mood 24. Encore une fois, on sélectionne dans un premier temps les données qui nous intéressent. > anorex1 <- subset(anorex, time==1, + select=c(weight,binge:purge)) > round(res.c <- cor(anorex1, method="spearman"), 3) weight binge vomit purge weight 1.000 -0.573 -0.120 -0.426 binge -0.573 1.000 0.170 0.704 vomit -0.120 0.170 1.000 0.423 8 > with(anorexia, tapply(Prewt, Treat, var)) 70 CBT Cont FT 1.002272 1.390382 1.074346 Df Sum Sq Mean Sq F value Pr(>F) 2 1.39 0.6952 0.599 0.552 69 80.01 1.1596 β 50 PCV Min. :38.00 1st Qu.:44.00 Median :45.00 Mean :45.25 3rd Qu.:47.00 Max. :52.00 NEUTRO Min. : 9.00 1st Qu.:20.00 Median :24.00 Mean :24.89 3rd Qu.:29.00 Max. :42.00 WBC Min. :3100 1st Qu.:4500 Median :5100 Mean :5384 3rd Qu.:6000 Max. :9899 LEAD Min. :13.00 1st Qu.:17.00 Median :19.00 Mean :20.27 3rd Qu.:23.00 Max. :62.00 LYMPHO β À l'évidence, rien ne permet de rejeter l'hypothèse nulle d'absence de différence de poids lors de l'entrée dans l'étude chez ces 72 patientes. 26. Avant toute chose, il est utile de caractériser numériquement et visuellement les distributions uni- et bivariées. > paint <- read.table(paste(WD, "PAINT.DAT", sep="/"), + header=TRUE) > summary(paint) HAEMO Min. :13.20 1st Qu.:14.60 Median :15.00 Mean :15.17 3rd Qu.:15.85 Max. :17.40 LYMPHO Min. : 8.00 1st Qu.:17.00 Median :22.00 Mean :23.83 3rd Qu.:27.50 Max. :69.00 β 60 > summary(aov(Prewt ~ Treat, anorexia)) Treat Residuals β β β 40 β β β 30 β β β β ββ β β β β β β β β ββ β β β β β β β β β β β β β β β β β ββ β ββ β β β β ββ β β β ββ β β β β β 10 β β β β β β 4000 β β β β β β 20 β ββ β β ββ β β β β β β β β β β β β 6000 8000 10000 WBC Finalement, la corrélation entre LYMPHO et WBC, ajustée sur le volume moyen d'hémoglobine ne varie pas sensiblement des calculs ci-dessus. > library(ppcor) > with(paint, pcor.test(LYMPHO, WBC, HAEMO)) estimate p.value statistic n gp Method 1 0.8261682 1.10664e-48 14.66336 103 1 pearson 27. Le chargement des données ne pose pas de problème particulier puisque les données sont déjà stockées sous forme de data.frame, format manipulable avec la commande xtabs. > blood <- read.table(paste(WD, "blood.txt", sep="/"), + header=TRUE) > bg <- c("A","B","AB","O") > blood$mother <- factor(blood$mother, labels=bg) > blood$father <- factor(blood$father, labels=bg) > blood$sex <- factor(blood$sex, + labels=c("female","male")) > (chsq <- chisq.test(xtabs(count ~ mother + father, + data=blood))) > p <- splom(~ paint) Le test de corrélation ne pose pas de difficultés ; sans aucune hypothèse sur la direction de l'association (positive ou négative), un test bilatéral s'impose : > with(paint, cor.test(LYMPHO, WBC)) Pearson's product-moment correlation data: LYMPHO and WBC t = 14.7823, df = 101, p-value < 2.2e-16 alternative hypothesis: true correlation is not equal to 0 95 percent confidence interval: 0.7541505 0.8797144 sample estimates: cor 0.8269801 Pearson's Chi-squared test data: xtabs(count ~ mother + father, data = blood) X-squared = 20.8776, df = 9, p-value = 0.0132 Le résultat du test est significatif et suggère une association entre les groupes sanguins. L'inspection des résidus, residuals(chsq) indique des écarts particuliers à l'indépendance, avec une plus grande contribution au πτΊΎ des groupes A/A et B/A (mère/père). 28. Reconstruisons dans un premier temps le tableau de contingence et réalisons un test du πτΊΎ pour tester l'indépendance entre les deux variables. > snp <- matrix(c(30,30,246,130,380,184), ncol=3) > dimnames(snp) <- list(BMI=c("<= 25","> 25"), + Geno=c("AA","GA","GG")) > chisq.test(snp) Un simple diagramme de dispersion met en évidence une observation assez éloignée des autres (aux coordonnées, (τ» τ»τ» τ» , τ»τ» )). On peut recalculer la corrélation linéaire sans les points appartenant à l'enveloppe du nuage de dispersion comme suit : > chi <- with(paint, chull(LYMPHO, WBC)) > with(paint[-chi,], cor.test(LYMPHO, WBC)) Pearson's product-moment correlation data: LYMPHO and WBC t = 12.9676, df = 92, p-value < 2.2e-16 alternative hypothesis: true correlation is not equal to 0 95 percent confidence interval: 0.7183762 0.8655857 sample estimates: cor 0.803971 Pearson's Chi-squared test data: snp X-squared = 7.2638, df = 2, p-value = 0.02647 Le résultat du test est significatif (π = τΊΌ.τΊΌτΊΎτ») et suggère que le génotype est associé au BMI des sujets. Ce test utlise 9 (τΊΎ β τΊ½) × (τΊΏ β τΊ½) = τΊΎ degrés de libertés. On peut faire mieux Control Treatment 0.15 Density et surtout rendre compte de la nature même de la variable Geno sous un modèle de dosage allélique en recodant celleci comme une variable ordinale : AA=0, GA=1, GG=2. Il est nécessaire de passer au format βlongβ (en utilisant le package reshape) : > snp.df <- as.data.frame(as.table(snp)) > snp.df <- untable(snp.df, snp.df$Freq) > snp.df$Geno <- ordered(snp.df$Geno) > test.stats <- sum(snp) * with(snp.df, + cor(as.numeric(BMI), as.numeric(Geno))^2) > pchisq(test.stats, df=1, lower.tail=FALSE) 0.10 0.05 0.00 β β 10 β β β β β β β β β β β β β 15 20 β β 25 los Il est tout à fait possible d'utiliser un test π‘ pour comparer les durées moyennes de séjour. Toutefois, considérons la stratégie suivante : on permute aléatoirement les labels du facteur group et on recalcule la différence de moyennes ; cette procédure est répétée 999 fois. Il s'agit donc d'une approximation car le nombre total de permutations est de [1] 0.03396257 Le test est ici encore significatif. À la différence du πτΊΎ d'indépendance, ici on a tenu compte de la nature ordinale de Geno et gagner un degré de liberté. (τ»τ»+τ»τ»)! τ»τ»!τ»τ»! . > perm.diff <- replicate(999, + with(adl, + diff(tapply(los, + sample(group), + mean)))) > sum(perm.diff <= dm)/1000 BMI above 25 BMI less or equal to 25 [1] 0.027 AA GA GG 200 Genotype On peut comparer le résultat précédent avec un test de Cochran-Armitage (version conditionnelle) : > library(coin) > independence_test(BMI ~ Geno, data=snp.df, + teststat="quad", + scores=list(Geno=c(0,1,2))) Count 150 100 50 0 β2 Asymptotic General Independence Test data: BMI by Geno (AA < GA < GG) chi-squared = 4.4921, df = 1, p-value = 0.03405 β1 0 1 Ce résultat peut être comparé à ce que donneraient des fonctions plus sophistiquées de R pour les tests de permutation. Par exemple, > library(coin) > oneway_test(los ~ group, data=adl, + distribution=approximate(B=999)) 29. La différence de moyenne, même minime, mérite d'être testée puisque la durée de séjour est un facteur pronostic. On peut résumer les données de manière numérique avec tapply ou bien en utilisant le package Hmisc : > summary(los ~ group, data=adl, fun=mean) Approximative 2-Sample Permutation Test los by group (Control, Treatment) Z = 2.0488, p-value = 0.04605 alternative hypothesis: true mu is not equal to 0 data: los N=100 +-------+---------+---+--------+ | | |N |los | +-------+---------+---+--------+ |group |Control | 46|17.82609| | |Treatment| 54|16.75926| +-------+---------+---+--------+ |Overall| |100|17.25000| +-------+---------+---+--------+ > (dm <- as.numeric(diff(with(adl, tapply(los, group, + mean))))) [1] -1.066828 10 Un test exact donnerait une π-valeur de 0.04381. Notre test βmanuelβ, avec 99999 rééchantillonnages, donne 0.04546 (en bilatéral). Un test π‘ nous donnerait π = τΊΌ.τΊΌτΊΏτ» τ»τ», en supposant l'homogénéité des variances. 30. L'idée est de vérifier empiriquement le taux de faux positifs (risque de première espèce) que l'on observerait hypothétiquement en répétant le processus de décision associé au test d'hypothèse. La commande suivante permet d'appeler la fonction sim.data en fixant dm=0 et en variant n de 10 à 20 (par pas de 1). Elle renvoit les π-valeurs associées au test de Student. Cela ne suffit toutefois pas pour évaluer la fréquence de rejet de π»τΊΌ , car il faudrait répliquer l'expérience plusieurs fois, pour chaque paramètre manipulé. > sapply(10:20, sim.data, dm=0.5) 2369.623518 4.429108 [[2]] (Intercept) I(lwt/2.2) 2.369623518 0.009744037 [1] 0.22592951 0.06750175 0.04390103 0.86025844 [5] 0.19966900 0.36742708 0.20093824 0.23607682 [9] 0.24345003 0.26565522 0.06743805 Dans l'exemple ci-dessous, on simule 100 tests sur la différence de moyenne observée, alors qu'en réalité les deux échantillons proviennent de la même population (dm=0). > sum(replicate(100, sim.data(n=15, dm=0, + var.equal=TRUE))<.05)/100 [[3]] (Intercept) scale(lwt, scale = F) 2944.587302 4.429108 > summary(mod[[3]]) Call: lm(formula = bwt ~ scale(lwt, scale = F), data = birthwt) Residuals: Min 1Q Median 3Q Max -2192.12 -497.97 -3.84 508.32 2075.60 [1] 0.04 On rejetterait donc π»τΊΌ (à tort) dans 4 cas sur les 100, mais cela reste en deçà du risque de première espèce fixé à 5 %. Voici un autre exemple de simulation où l'on fait varier simultanément n et dm. Les fréquences de rejet de π»τΊΌ sur 100 simulations sont résumées dans la figure suivante. > k <- 100 > des.params <- expand.grid(n=seq(10, 100, by=10), + dm=seq(0, 1, by=.2)) > res <- replicate(k, apply(des.params, 1, + function(x) sim.data(n=x[1], dm=x[2], + var.equal=TRUE))) > res <- data.frame(des.params, + perc=apply(res, 1, + function(x) sum(x<.05))/k) β β β β Residual standard error: 718.4 on 187 degrees of freedom Multiple R-squared: 0.0345, Adjusted R-squared: 0.02933 F-statistic: 6.681 on 1 and 187 DF, p-value: 0.0105 L'examen des coefficients de régression et tests π‘ associés indiquent que dans les trois cas on arrive aux mêmes conclusions : le poids de la mère (lwt) est significatif à 5 %. En revanche, l'interprétation des coefficients sera différente : dans le premier modèle, l'ordonnée à l'origine est le poids moyen des enfants, en g, lorsque le poids de la mère (en livres) est nul (peu interprétable) ; dans le deuxième modèle, le terme d'intercept reflète la même chose en changeant les unités de mesure (kg) ; dans le troisième modèle, il s'agit du poids des enfants ayant des mères de poids moyen. La pente indique dans tous les cas l'augmentation du poids des bébés lorsque le poids de la mère, mesuré en livres ou en kg, varie d'une unité. 32. Considérons le premier modèle. Les résidus s'obtiennent ainsi : > mod1.resid <- resid(mod[[1]]) Une façon d'examiner leur distribution est d'afficher un histogramme, ou un Q-Q plot de leurs valeurs, ainsi qu'un graphique montrant l'évolution des résidus en fonction des valeurs prédites. > p <- qqmath(~ resid(mod[[1]])) > p <- xyplot(resid(mod[[1]]) ~ fitted(mod[[1]])) β β β % rejet H0 0.8 0.6 dm β 0 0.2 0.4 0.6 0.8 1 0.4 0.2 β β β β β β β β β β 0.0 20 40 60 80 100 n Modèle linéaire et applications 31. La formule R de base reste la même dans les trois cas, à l'unité de mesure près. On notera que l'opérateur / sert à désigner les relations d'emboîtement dans les formules, donc il faut utiliser I() pour empêcher une telle interprétation.3 > mod <- list() > mod[[1]] <- lm(bwt ~ lwt, data=birthwt) > mod[[2]] <- lm(I(bwt/1000) ~ I(lwt/2.2), data=birthwt) > mod[[3]] <- lm(bwt ~ scale(lwt, scale=F), data=birthwt) > lapply(mod, coef) [[1]] (Intercept) 2000 1000 0 β1000 β2000 β β βββ β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β ββ β ββ β3 β2 β1 1000 0 β1000 β2000 0 1 2 3 Standard normal quantiles lwt 2000 Residuals β β Estimate Std. Error t value (Intercept) 2944.587 52.259 56.346 scale(lwt, scale = F) 4.429 1.713 2.585 Pr(>|t|) (Intercept) <2e-16 scale(lwt, scale = F) 0.0105 Residuals 1.0 Coefficients: β β ββ β ββ β β ββ ββ β ββ ββ β ββ β β β ββ β ββ ββ β β β β β β ββ ββ ββββββ β ββ ββ β ββ ββ ββ β ββ β ββ β β ββ β βββ β β β βββ β β β β βββ β β β β ββ β β β β ββ β β β β ββ ββββ ββ β β ββ ββ βββ β ββ β β β β ββ ββ β ββ β β βββ ββ ββ ββ β β β β β β β 2800 3000 3200 3400 Fitted values Pour aller plus loin, on peut examiner les mesures d'influence. > mod1.inf <- influence.measures(mod[[1]]) 3 JM Chambers and TJ Hastie, eds. Statistical Models in S. Wadsworth & Brooks, 1992, pp. 28 et 31. 11 > head(round(mod1.inf$infmat, 3)) 85 86 87 88 89 91 > anova(mod1a) dfb.1_ dfb.lwt dffit cov.r cook.d hat 0.097 -0.115 -0.134 1.023 0.009 0.021 0.030 -0.043 -0.067 1.014 0.002 0.009 -0.029 0.023 -0.036 1.018 0.001 0.009 -0.024 0.018 -0.032 1.018 0.001 0.008 -0.024 0.019 -0.031 1.018 0.000 0.008 -0.012 0.006 -0.031 1.014 0.000 0.005 Analysis of Variance Table Response: bwt Df Sum Sq Mean Sq F value Pr(>F) race 2 5015725 2507863 4.9125 0.008336 Residuals 186 94953931 510505 Les contrastes utilisés ci-dessus sont appelés contrastes de traitement. > op <- options(contrasts=c("contr.sum", "contr.poly")) > mod1b <- lm(bwt ~ race, data=birthwt) > coef(mod1b) Si l'on se réfère aux indices DFFITs, les observations jugées influentes (supérieures en valeurs absolues à ±τΊΎβτΊΎ/π) sont données par > idx <- which(mod1.inf$is.inf[,"dffit"]) > c(-1,1)*2*sqrt(2/nrow(birthwt)) [1] -0.2057378 (Intercept) 2875.8982 0.2057378 dfb.1_ dfb.lwt dffit cov.r cook.d hat 0.349 -0.409 -0.464 0.944 0.103 0.024 0.282 -0.322 -0.351 1.006 0.061 0.033 Black Other -383.0264 -297.4352 Pour ré-estimer le modèle de régression sans ces individus, on pourrait utiliser update(mod[[1]], subset=!(rownames(birthwt) %in% idx). Ces deux points sont surlignés dans le diagramme de dispersion suivant. 5000 > grp.means[1:2] - mean(grp.means) Dans le premier modèle, l'intercept vaut grp.means[1], tandis que dans le second modèle il s'agit de la moyenne des moyennes de groupe (mean(grp.means)). Les deux coefficients associés aux pentes représentent dans le premier cas les déviations entre Black et Other par rapport à White, et dans le second cas entre White et Black et la moyenne des trois groupes. Pour plus d'informations sur le codage des contrastes sous R, http://bit.ly/LFkFBg. 34. Pour l'ANOVA à deux facteurs, on considèrera donc les termes race, smoke et race:smoke (interaction). > aov0 <- aov(bwt ~ race * smoke, data=birthwt) > summary(aov0) β bwt 3000 2000 β β β β β β ββ β ββ β ββ β β β β β β β β β β β β β β β β β β βββ ββ β β β ββ β ββ β β β β β β β β β β β β β ββ β ββ β β β β β β β β ββ ββ β β β β β β β ββ β β β β β β βββ β β β β ββ β β ββ β β β β β ββ β β ββ β β β β β β β βββ β β β ββ β β β β ββ β β β β β β β β β β β β β β ββ β β β β β β β β β ββ β β β β β β β β β 1000 β β 28 11 β 100 150 200 # mod1b White Black 226.8205 -156.2059 β 4000 race2 -156.2059 > options(op) On peut le vérifier là partir des moyennes de groupe : > grp.means <- with(birthwt, tapply(bwt, race, mean)) > grp.means[2:3] - grp.means[1] # mod1a > round(mod1.inf$infmat[idx,], 3) 11 28 race1 226.8205 250 lwt 33. Un modèle d'ANOVA à un facteur s'écrirait aov(bwt ~ race, data=birthwt). Avec la commande lm, on obtient les mêmes informations, ainsi que les déviations des moyennes de groupes par rapport à la moyenne des poids des bébés chez les mères dont le niveau est white. Après recodage du facteur (cf. exercice 10), on obtient > mod1a <- lm(bwt ~ race, data=birthwt) > summary(mod1a) race smoke race:smoke Residuals Df Sum Sq 2 5015725 1 7322575 2 2101808 183 85529548 Mean Sq F value Pr(>F) 2507863 5.366 0.005438 7322575 15.667 0.000108 1050904 2.249 0.108463 467375 L'interaction n'est pas significative. On peut réestimer le modèle sans elle comme suit : > aov1 <- update(aov0, . ~ . - race:smoke) > summary(aov1) Call: lm(formula = bwt ~ race, data = birthwt) Residuals: Min 1Q Median 3Q Max -2096.28 -502.72 -12.72 526.28 1887.28 race smoke Residuals Df Sum Sq Mean Sq F value Pr(>F) 2 5015725 2507863 5.294 0.005809 1 7322575 7322575 15.459 0.000119 185 87631356 473683 Un résumé des effets principaux peut être obtenu ainsi : > model.tables(aov1) Tables of effects race White Black Other 158.1 -224.9 -139.3 rep 96.0 26.0 67.0 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 3102.72 72.92 42.548 < 2e-16 raceBlack -383.03 157.96 -2.425 0.01627 raceOther -297.44 113.74 -2.615 0.00965 Residual standard error: 714.5 on 186 degrees of freedom Multiple R-squared: 0.05017, Adjusted R-squared: 0.03996 F-statistic: 4.913 on 2 and 186 DF, p-value: 0.008336 12 smoke No Yes 148.5 -230.8 rep 115.0 74.0 > summary(mod2) ce qui est essentiellement équivalent à calculer les différences entre les moyennes de groupe et la grande moyenne manuellement : > with(birthwt, tapply(bwt, race, mean) - mean(bwt)) Call: lm(formula = glucose ~ exercise, data = hers, subset = diabetes == 0) Residuals: Min 1Q Median 3Q Max -48.668 -6.668 -0.668 5.639 29.332 White Black Other 158.1314 -224.8950 -139.3037 35. Pour obtenir les résidus du modèle aov1 précédent, on utilise comme dans le cas de la régression l'extracteur resid() : > aov1.resid <- resid(aov1) > round(summary(aov1.resid), 1) Min. 1st Qu. -2314.0 -440.2 Median 15.8 Mean 3rd Qu. 0.0 492.1 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 97.3610 0.2815 345.848 < 2e-16 exercise -1.6928 0.4376 -3.868 0.000113 Max. 1655.0 > p <- qqmath(~ aov1.resid) Residual standard error: 9.715 on 2030 degrees of freedom Multiple R-squared: 0.007318, Adjusted R-squared: 0.006829 F-statistic: 14.97 on 1 and 2030 DF, p-value: 0.000113 β Residuals 1000 0 β1000 β β ββββ βββββ ββ β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β ββ ββ ββ ββ βββ β On constate que l'effet du facteur exercise est plus important sur le taux de glucose (1.7 mg/dL plus bas chez les patients qui font de l'exercice au moins 3 fois par semaine). Les deux modèles sont emboîtés (mod2 ne comprend que des termes présents dans mod1), ce qui justifierait l'emploi d'un test πΉ pour comparer les deux résiduelles. Toutefois, il faudrait s'assurer que les deux modèles ont bien été estimés sur le même nombre d'individus, ce qui n'est pas garantit en raison de la présence de valeurs manquantes (supposées MAR). L'examen des valeurs résiduelles en fonction des valeurs prédites fait apparaître quelques patients atypiques. On peut confirmer cette approche graphique par un résumé numérique des résidus par rapport aux critères habituels. > p <- xyplot(resid(mod1) ~ fitted(mod1), + type=c("p", "smooth")) > mod1.inf <- influence.measures(mod1) > which(mod1.inf$is.inf[,"dffit"]) β β β β2000 β β3 β2 β1 0 1 2 3 Standard normal quantiles Rien n'indique apparemment de déviations flagrante par rapport à l'hypothèse de normalité des résidus (on pourrait utiliser un test plus formel, comme shapiro.test, mais cela n'est pas vraiment utile dans ce cas). 36. Pour le chargement des données, il faut vérifier le codage des données manquantes (souvent présentes dans les études épidémiologiques). > hers <- read.table("../pub/hersdata.txt", + header=TRUE, na.strings=".") > fm <- glucose ~ exercise + age + drinkany + BMI > mod1 <- lm(fm, data=hers, subset=diabetes==0) > summary(mod1) 20 256 369 391 401 804 1187 1295 1352 1597 16 176 255 272 281 587 868 954 996 1186 1730 1747 1833 2470 2655 2709 2717 1282 1292 1353 1814 1947 1987 1993 Concernant les résidus studentisés les plus extrêmes, on retrouve une partie des individus listés ci-dessus : > which(abs(rstudent(mod1))>3) Call: lm(formula = fm, data = hers, subset = diabetes == 0) Residuals: Min 1Q Median 3Q Max -47.560 -6.400 -0.886 5.496 32.060 256 1187 1747 1833 2002 2221 2470 176 868 1292 1353 1479 1646 1814 β ββ βββ β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β βββ ββ β 20 Residuals Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 78.96239 2.59284 30.454 <2e-16 exercise -0.95044 0.42873 -2.217 0.0267 age 0.06355 0.03139 2.024 0.0431 drinkany 0.68026 0.42196 1.612 0.1071 BMI 0.48924 0.04155 11.774 <2e-16 0 β20 β β β 1833 β40 Residual standard error: 9.389 on 2023 degrees of freedom (4 observations deleted due to missingness) Multiple R-squared: 0.07197, Adjusted R-squared: 0.07013 F-statistic: 39.22 on 4 and 2023 DF, p-value: < 2.2e-16 β 2470 β2 0 2 Standard normal quantiles 37. Connaissant les coefficients de régression du modèle, > coef(mod1) Le modèle réduit s'écrit glucose ~ exercise : > mod2 <- update(mod1, . ~ . - age - drinkany - BMI) 13 (Intercept) exercise 78.96239394 -0.95044096 BMI 0.48924198 age 0.06354948 > > > > drinkany 0.68026413 il est tout à fait possible de calculer manuellement la prédiction recherchée, par exemple : > agem <- median(hers$age[hers$diabetes==0], + na.rm=TRUE) > t(coef(mod1)) %*% c(1, 1, agem, 0, 22) Call: lm(formula = fm, data = cystic) Residuals: Min 1Q Median 3Q -39.535 -11.904 4.259 15.614 [,1] [1,] 93.03309 1 93.03309 > predict(mod1, data.frame(exercise=0, age=agem, + drinkany=0, BMI=22)) Residual standard error: 22.73 on 20 degrees of freedom Multiple R-squared: 0.6148, Adjusted R-squared: 0.5378 F-statistic: 7.981 on 4 and 20 DF, p-value: 0.0005139 1 93.98353 Par rapport à un patient ne faisant pas d'exercice, on voit que la différence dans les taux de glucose prédit est de 0.950441, ce qui correspond bien à l'interprétation du coefficient de régression partiel pour le facteur exercise. Pour produire une surface de réponse, il est nécessaire de construire une grille de valeurs pour les variables d'intérêt, ici age et BMI. Pour les deux autres prédicteurs, on assumera des valeurs fixées. > pd <- expand.grid(age=seq(44, 79, length=100), + BMI=seq(15.21, 54.13, length=100)) > pd$exercise <- rep(1, nrow(pd)) > pd$drinkany <- rep(0, nrow(pd)) > pd$pred <- predict(mod1, pd) > head(pd) 39. age 44.00000 44.35354 44.70707 45.06061 45.41414 45.76768 Max 35.334 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 62.4448 53.2431 1.173 0.254647 Weight 1.7480 0.3797 4.603 0.000172 BMP -1.3644 0.5633 -2.422 0.025062 FEV 1.5480 0.5771 2.682 0.014317 RV 0.1275 0.0832 1.532 0.141135 Ou alors, et c'est plus simple lorsqu'on s'intéresse à plusieurs prédictions, on utilise la fonction predict() : > predict(mod1, data.frame(exercise=1, age=agem, + drinkany=0, BMI=22)) 1 2 3 4 5 6 cystic <- read.table("../pub/CYSTIC.DAT", header=TRUE) fm <- PEmax ~ Weight + BMP + FEV + RV mod1 <- lm(fm, data=cystic) summary(mod1) BMI exercise drinkany pred 15.21 1 0 88.24950 15.21 1 0 88.27197 15.21 1 0 88.29443 15.21 1 0 88.31690 15.21 1 0 88.33937 15.21 1 0 88.36184 Les résultats indiquent que le poids (Weight), ainsi que deux autres prédicteurs (BMP et FEV) semblent significativement associés aux variations de PEmax. Concernant l'identification des observations avec effet levier, on peut utiliser le critère βπ > τΊΎ(π + τΊ½)/π, avec π = τ» et π = τΊΎτ», soit > hcrit <- 2*(5+1)/25 > any(hatvalues(mod1) > hcrit) [1] FALSE A priori, aucune observation n'exerce d'effet levier. Concernant la qualité d'ajustement du modèle, le coefficient de détermination ajusté vaut 0.538 (soit environ 54 % de la variabilité de PEmax expliquée par les facteurs pris en compte dans le modèle). On augmente le modèle précédent avec les variables Age et Sex : > mod2 <- update(mod1, . ~ . + Age + Sex) > summary(mod2) Call: lm(formula = PEmax ~ Weight + BMP + FEV + RV + Age + Sex, data = cystic) Residuals: Min 1Q Median 3Q Max -37.976 -10.632 2.201 15.952 38.151 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 100.01652 89.98226 1.112 0.2810 Weight 2.37709 1.20397 1.974 0.0639 BMP -1.71963 0.86957 -1.978 0.0635 FEV 1.49478 0.71844 2.081 0.0520 RV 0.11140 0.09244 1.205 0.2438 Age -1.95096 3.54021 -0.551 0.5884 Sex 0.57570 11.63143 0.049 0.9611 pred age Residual standard error: 23.76 on 18 degrees of freedom Multiple R-squared: 0.6213, Adjusted R-squared: 0.4951 F-statistic: 4.922 on 6 and 18 DF, p-value: 0.003837 BMI 38. On charge les données de la manière habituelle pour les données texte avec séprateur de champ de type tabulation. Plus rien ne semble significatif ! Le problème vient essentiellement du nombre de paramètres dans le modèle : 6 14 prédicteurs (+ l'intercept) pour 25 observations. 40. Les données sont stockées au format texte, avec séparation de type tabulation. Les données manquantes sont codées avec un β.β. > babies <- read.table("../pub/babies.txt", + header=TRUE, na.string=".") > babies$baby <- factor(babies$baby) > babies$loginsp <- log(babies$inspirat) > range(table(babies$baby)) [1] 8 47 Le modèle d'ANOVA incorporant entre 8 et 47 mesures répétées par sujet s'écrit : > aov1 <- aov(loginsp ~ maxp + Error(baby), + data=babies[complete.cases(babies),]) > summary(aov1) Error: baby Df Sum Sq Mean Sq F value Pr(>F) maxp 1 1.829 1.829 1.559 0.223 Residuals 25 29.328 1.173 Error: Within Df Sum Sq Mean Sq F value Pr(>F) maxp 1 32.93 32.93 197 <2e-16 Residuals 523 87.44 0.17 L'effet de la pression maximale est largement significatif. Notons que l'analyse a été effectuée sur les cas complets. Comparons avec une approche par modèle à effet aléatoire : > library(nlme) > mod1 <- lme(loginsp ~ maxp, data=babies, + random= ~ 1 | baby, na.action=na.omit) > anova(mod1) (Intercept) maxp numDF denDF F-value p-value 1 523 726.9259 <.0001 1 523 190.2486 <.0001 On obtient donc sensiblement les mêmes résultats. Il est intéressant de regarder ce que ce dernier modèle fournit comme prédictions. Comme on peut le voir dans la figure suivante, les données individuelles sont assez βbruitéesβ et il est difficile d'estimer la pente commune à vue d'Εil. Cela devient par contre beaucoup plus évident lorsque l'on affiche les prédictions individuelles. On peut utiliser > qqmath(~ ranef(mod1)) pour visualiser la distribution des effets aléatoires (termes d'intercept propres à chaque sujet) sous forme de Q-Q plot. 3 Fitted loginsp 2.5 2 1 2.0 1.5 1.0 0 10 20 30 maxp 40 10 20 30 40 maxp 15