Travailler sur des proportions - Les bonnes pratiques Marc Girondot July 2, 2013 Contents 1 Chargement des bibliothèques 2 2 Introduction 3 3 Intervalle de confiance d une fréquence 4 3.1 Pourcentage.............................. 7 3.2 Pour une distribution à plus de 2 états............... 9 4 Régression linéaire sur des proportions 10 4.1 Ce qu il ne faut pas faire......................... 10 4.2 Transformée angulaire d une proportion.............. 13 4.3 Poids des différentes mesures.................... 17 5 Un GLM pour analyser des proportions 19 5.1 Introduction sur les GLM...................... 19 5.2 Fonctions logit et probit....................... 19 5.3 La vraisemblance d une observation................. 21 5.4 Analyse par un modèle linéraire généralisé (GLM)........ 23 5.5 Pour aller plus loin............................ 26 6 Fonction predict après un GLM 27 1
1 Chargement des bibliothèques library("phenology") Loading required package: fields Loading required package: spam Spam version 0.29-3 (2013-04-23) is loaded. Type help( Spam) or demo( spam) for a short introduction and overview of this package. Help for individual functions is also obtained by adding the suffix.spam to the function name, e.g. help( chol.spam). Attaching package: spam L objet suivant est masqué from package:base : backsolve, forwardsolve Loading required package: maps Loading required package: coda Loading required package: lattice Loading required package: zoo Attaching package: zoo L objet suivant est masqué from package:base : as.date, as.date.numeric Welcome in package phenology! library("hmisc") Loading required package: survival Loading required package: splines Hmisc library by Frank E Harrell Jr Type library(help= Hmisc ),?Overview, or?hmisc.overview ) to see overall documentation. NOTE:Hmisc no longer redefines [.factor to drop unused levels when subsetting. To get the old behavior of Hmisc type dropunusedlevels(). Attaching package: Hmisc L objet suivant est masqué from package:survival : untangle.specials L objet suivant est masqué from package:fields : describe Les objets suivants sont masqués from package:base : format.pval, round.posixt, trunc.posixt, units 2
2 Introduction Une proportion est définie comme un rapport entre l occurence d un type d évènement particulier et l ensemble des possibilités. Soit un évènement dont le résultat peut se présenter sous deux états, A ou B et na et nb le nombre d occurence de A et B avec N=nA+nB alors les proportions de A et B sont: pa=na/n pb=nb/n On peut définir aussi des proportions lorsqu il y a plus de 2 états, par exemple 3 états: A, B et C pa=na/n pb=nb/n pc=nc/n avec N=nA+nB+nC Ces règles du calcul des proportions peuvent être utilisées pour calculer des fréquences à partir d observations ou bien d établir des probabilités qui sont des mesures d une incertitude sur un événement. Voyons comment l écrire sous R de différentes façons pour se familiariser avec le language. On peut définir les différents évènements sous la forme d un vecteur avec c() de 0 et 1: obs1 <- c(1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0) N1 <- length(obs1) obs1 == 1 [1] TRUE FALSE TRUE FALSE TRUE TRUE FALSE [8] TRUE FALSE TRUE FALSE TRUE TRUE TRUE [15] TRUE FALSE na1 <- sum(obs1 == 1) nb1 <- N1 - na1 pa1 <- na1/n1 pb1 <- nb1/n1 print(paste("il y a", na1, "'1' sur", N1, "soit une fréquence de", pa1)) [1] "Il y a 10 '1' sur 16 soit une fréquence de 0.625" print(paste("il y a", nb1, "'0' sur", N1, "soit une fréquence de", pb1)) [1] "Il y a 6 '0' sur 16 soit une fréquence de 0.375" On peut aussi définir les différents évènements sous la forme d un vecteur avec c() de A, B et C: 3
obs2 <- c("a", "C", "B", "C", "A", "A", "C", "C", "A", "A", "C", "B", "C", "A") n2 <- c(a = sum(obs2 == "A")) n2 <- c(n2, B = sum(obs2 == "B")) n2 <- c(n2, C = sum(obs2 == "C")) n2 A B C 6 2 6 N2 <- sum(n2) (p2 <- n2/n2) A B C 0.4286 0.1429 0.4286 Il est possible aussi bien sûr de le définir sous la forme aggrégée: n3 <- c(a = 20, B = 21, C = 3) N3 <- sum(n3) (p3 <- n3/n3) A B C 0.45455 0.47727 0.06818 3 Intervalle de confiance d une fréquence Le package Hmisc a une fonction très intéressante pour avoir l intervalle de confiance d une fréquence mais cela ne marche que lorsqu il y a deux états possibles car la méthode est basés sur une distribution binomiale. b1 <- binconf(na1, N1) errbar(1, b1[, 1], b1[, 3], b1[, 2], ylab = "Proportion", las = 1, ylim = c(0, 1)) Pour représenter un diagramme en point avec des barres d erreur, je trouve plus simple d utiliser mon package phenology car la formulation reprend exactement celle de la fonction plot(). plot_errbar(1, b1[, 1], y.plus = b1[, 3], y.minus = b1[, 2], ylab = "Proportion", las = 1, ylim = c(0, 1), bty = "n") Il est alors facile de représenter une série de fréquences avec les intervalles de confiance. Par défaut, alpha=0,05 c est à dire qu il y a 95% de chance que la véritable proportion se trouve bien dans l intervalle de confiance calculé: 4
1.0 0.8 Proportion 0.6 0.4 0.2 0.0 0.6 0.8 1.0 1.2 1.4 1 Figure 1: Fréquence et intervalle de confiance de la fréquence 5
1.0 0.8 Proportion 0.6 0.4 0.2 0.0 0.6 0.8 1.0 1.2 1.4 1 Figure 2: Fréquence et intervalle de confiance de la fréquence 6
1.0 0.8 Proportion 0.6 0.4 0.2 0.0 1 2 3 4 5 6 7 : 1 Figure 3: Proportion et intervalle de confiance d une série de proportions n4 <- c(10, 2, 5, 7, 9, 12, 5) N4 <- c(45, 10, 5, 19, 12, 24, 6) b4 <- binconf(n4, N4) errbar(1:7, b4[, 1], b4[, 3], b4[, 2], ylab = "Proportion", las = 1, ylim = c(0, 1)) 3.1 Pourcentage Pour travailler en pourcentage, il suffit de tout multiplier par 100: errbar(1, b1[, 1] * 100, b1[, 3] * 100, b1[, 2] * 100, ylab = "Pourcentage", las = 1, ylim = c(0, 100)) 7
100 80 Pourcentage 60 40 20 0 0.6 0.8 1.0 1.2 1.4 1 Figure 4: Pourcentage et intervalle de confiance d une série de pourcentages 8
3.2 Pour une distribution à plus de 2 états Pour établir l intervalle de confiance de proportions dans une distribution multinomiale (plus de 2 états), if faut utiliser la méthode de (Glaz and Sison, 1999) disponible dans le package MultinomialCI. Glaz, J. and C.P. Sison. Simultaneous confidence intervals for multinomial proportions. Journal of Statistical Planning and Inference 82:251-262 (1999). library("multinomialci") m = multinomialci(x = c(23, 12, 44), alpha = 0.05) print(paste("first class: [", m[1, 1], m[1, 2], "]")) [1] "First class: [ 0.189873417721519 0.410418258547599 ]" print(paste("second class: [", m[2, 1], m[2, 2], "]")) [1] "Second class: [ 0.0506329113924051 0.271177752218485 ]" print(paste("third class: [", m[3, 1], m[3, 2], "]")) [1] "Third class: [ 0.455696202531646 0.676241043357725 ]" print(paste("somme bornes hautes:", sum(m[, 2]))) [1] "Somme bornes hautes: 1.35783705412381" On peut remarquer que si on somme la borne haute de chacune des valeurs, on dépasse 1; c est logique puisque les données ne sont pas indépendantes. 9
4 Régression linéaire sur des proportions 4.1 Ce qu il ne faut pas faire... Imaginez que vous avez des données temporelles de proportions, par exemple: timeobs <- c(1, 3, 6, 8, 10, 12, 13, 14, 17, 19, 20, 22, 25) obs <- c(0, 1, 2, 5, 2, 8, 9, 2, 19, 23, 5, 12, 15) Nobs <- c(1, 3, 3, 12, 4, 10, 11, 2, 21, 26, 6, 15, 15) obs/nobs [1] 0.0000 0.3333 0.6667 0.4167 0.5000 0.8000 [7] 0.8182 1.0000 0.9048 0.8846 0.8333 0.8000 [13] 1.0000 Il est extrèmement courant de voir une régression linéaire effectuée sur de telles données. x <- timeobs y <- obs/nobs plot(x, y, ylim = c(0, 1), bty = "n", xlim = c(0, 25)) abline(lm(y ~ x)) text(22, 0.6, paste("r=", sprintf("%.3f", cor(x, y, method = "pearson")), sep = "")) (testcor <- cor.test(x, y, method = "pearson")) Pearson's product-moment correlation data: x and y t = 5.01, df = 11, p-value = 0.000396 alternative hypothesis: true correlation is not equal to 0 95 percent confidence interval: 0.5233 0.9489 sample estimates: cor 0.8339 text(22, 0.5, paste("p=", sprintf("%.3f", testcor$p.value), sep = "")) 10
y 0.0 0.2 0.4 0.6 0.8 1.0 r=0.834 p=0.000 0 5 10 15 20 25 x Figure 5: Régression linéaire sur des proportions 11
y 0.0 0.2 0.4 0.6 0.8 1.0 r=0.834 p=0.000 0 5 10 15 20 25 x Figure 6: Régression linéaire sur des proportions Une régression linéaire donne des valeurs qui ne sont pas limitées dans l intervalle [0; 1]. par(xpd = TRUE) plot(x, y, ylim = c(0, 1), bty = "n", xlim = c(0, 25)) abline(lm(y ~ x)) text(22, 0.6, paste("r=", sprintf("%.3f", cor(x, y, method = "pearson")), sep = "")) text(22, 0.5, paste("p=", sprintf("%.3f", testcor$p.value), sep = "")) segments(x0 = 0, x1 = 25, y0 = 1, y1 = 1, lty = 3) 12
4.2 Transformée angulaire d une proportion Un des nombreux problèmes de la régression linéaire sur des proportions vient du fait qu une proportion n est pas distribuée normalement (normalement signifiant une distribution de Gauss-Laplace). Pour s en convaincre il suffit de prendre en compte le fait qu une proportion est comprise entre 0 et 1 et qu une variable tirée dans une distribution normale est comprise entre ]-infini, +infini[. L ajustement de la droite de régression est fait par la méthode des moindres carrés qui a pour hypothèse que la variable dépendante (y) a une distribution marginale normale. C est donc clairement faux pour une proportion. Pour s en sortir, autrefois on effectuait une transformation angulaire de type: ( ) 2 arcsin proportion Cette formule qui peut sembler magique en premier abord trouve son origine directement dans l expression mathématique de la loi normale. vx <- seq(from = 0, to = 1, by = 0.01) vy <- 2 * asin(sqrt(vx)) plot(vx, vy, type = "l", bty = "n", xlab = "proportion", ylab = "transformée angulaire", las = 1) 13
3.0 2.5 transformée angulaire 2.0 1.5 1.0 0.5 0.0 0.0 0.2 0.4 0.6 0.8 1.0 proportion Figure 7: Transformée angulaire de proportions 14
par(xpd = FALSE) y_transform <- 2 * asin(sqrt(y)) plot(x, y_transform, bty = "n", xlim = c(0, 25)) abline(lm(y_transform ~ x)) text(22, 1.6, paste("r=", sprintf("%.3f", cor(x, y_transform, method = "pearson")), sep = "")) (testcor_transform <- cor.test(x, y_transform, method = "pearson")) Pearson's product-moment correlation data: x and y_transform t = 4.516, df = 11, p-value = 0.0008779 alternative hypothesis: true correlation is not equal to 0 95 percent confidence interval: 0.4587 0.9397 sample estimates: cor 0.806 text(22, 1.4, paste("p=", sprintf("%.3f", testcor_transform$p.value), sep = "")) 15
y_transform 0.0 0.5 1.0 1.5 2.0 2.5 3.0 r=0.806 p=0.001 0 5 10 15 20 25 x Figure 8: Régression linéaire sur la transformée angulaire des proportions 16
4.3 Poids des différentes mesures Mais un autre problème n est pas réglé. La régression linéaire basée sur des proportions ne prend pas en compte l effectif, or plus le nombre de mesures est important, plus la proportion est bien connue et donc devrait avoir un poids important. En conclusion, il ne faut pas travailler sur des proportions mais sur le nombre d observations. Voir par exemple WARTON, D.I. & F.K.C. HUI 2011. The arcsine is asinine: the analysis of proportions in ecology. Ecology 92:3-10. par(xpd = FALSE) bobs <- binconf(obs, Nobs) errbar(x, bobs[, 1], bobs[, 3], bobs[, 2], ylab = "Proportion", las = 1, ylim = c(0, 1), xlim = c(0, 25), bty = "n") abline(lm(y ~ x)) text(22, 0.5, paste("r=", sprintf("%.3f", cor(x, y, method = "pearson")), sep = "")) text(22, 0.4, paste("p=", sprintf("%.3f", testcor$p.value), sep = "")) 17
1.0 0.8 Proportion 0.6 0.4 r=0.834 p=0.000 0.2 0.0 0 5 10 15 20 25 x Figure 9: Régression linéaire sur des proportions 18
5 Un GLM pour analyser des proportions 5.1 Introduction sur les GLM La solution permettant de résoudre ces problèmes passe par deux changements de cadre de réflexion. D une part la régression linéaire n est clairement pas adaptée à des données limités dans un intervalle D autre part la méthode des moindres carrés utilisée pour ajuster les paramètres de la régression ne prend pas en compte la précision de la mesure 5.2 Fonctions logit et probit A la place d une équation linéaire, une fonction logit ou probit permet de limiter l intervalle à [0-1]. La fonction logit s écrit: 1 y = 1 + e ax+b La fonction probit est basée la fonction de répartition d une loi normale: Φ (z) = 1 2π z e 1 2 Z2 dz Le choix entre logit et probit est plus une question d habitude que de différence fondamentale. En biologie on utilise plutôt une logit alors qu en économie on utilise plutôt une probit. En pratique, il y a très peu de différence entre l utilisation d un logit et un probit. On peut dire que si on suppose que les résultats sont influencés par une variable à distribution gaussienne, le modèle probit serait plus adapté. Mais il faut garder à l esprit que les modèles logit et probit ne sont que des modèles : Essentially, all models are wrong, but some are useful, comme le disait George E. P. Box (in Box, G. E. P., and Draper, N. R., 1987, Empirical Model Building and Response Surfaces, John Wiley & Sons, New York, NY.)! xl <- -100:100 b <- 0 a <- 0.05 yl <- 1/(1 + exp(a * xl + b)) plot(xl, yl, bty = "n", type = "l", las = 1, col = "black", ylim = c(0, 1)) b <- 1 a <- -0.07 yl <- 1/(1 + exp(a * xl + b)) plot_add(xl, yl, bty = "n", type = "l", las = 1, col = "red") b <- -1 a <- 0.1 yl <- 1/(1 + exp(a * xl + b)) 19
1.0 0.8 a=0,05; b=1 a= 0,07; b= 1 a=0,1; b= 1 0.6 yl 0.4 0.2 0.0 100 50 0 50 100 xl Figure 10: Courbes logits plot_add(xl, yl, bty = "n", type = "l", las = 1, col = "blue") legend(x = 40, y = 0.8, legend = c("a=0,05; b=1", "a=-0,07; b=-1", "a=0,1; b=-1"), col = c("black", "red", "blue"), lty = 1, bty = "n") xl <- -100:100 b <- 5 a <- 10 yl <- pnorm(xl, mean = a, sd = b) plot(xl, yl, bty = "n", type = "l", las = 1, col = "black", ylim = c(0, 1)) b <- 20 a <- -10 yl <- pnorm(xl, mean = a, sd = b) 20
1.0 0.8 0.6 yl 0.4 0.2 0.0 a=10; b=5 a= 10; b=20 a=7; b=15 100 50 0 50 100 xl Figure 11: Courbes probits plot_add(xl, yl, bty = "n", type = "l", las = 1, col = "red") b <- 15 a <- 7 yl <- pnorm(xl, mean = a, sd = b) plot_add(xl, yl, bty = "n", type = "l", las = 1, col = "blue") legend(x = "bottomright", legend = c("a=10; b=5", "a=-10; b=20", "a=7; b=15"), col = c("black", "red", "blue"), lty = 1, bty = "n") 5.3 La vraisemblance d une observation La vraisemblance d une observation de x succès sur un ensemble size avec une probabilité de succès de prob est: dbinom(x, size, prob, log = FALSE) 21
La distribution binomiale avec une size = n et prob = p a une densité de: ( ) n d = p x (1 p) n x x d <- choose(size, x)*prob^x*(1-prob)^(size-x) 22
5.4 Analyse par un modèle linéraire généralisé (GLM) On crée un data.frame avec deux colonnes, les observations A et les observations B. On essaye de modéliser les observations en fonction de la variable timeobs. ydf <- cbind(na = obs, nb = Nobs - obs) model <- glm(ydf ~ timeobs, family = binomial(link = "logit")) anova(model, test = "Chisq") Analysis of Deviance Table Model: binomial, link: logit Response: ydf Terms added sequentially (first to last) Df Deviance Resid. Df Resid. Dev Pr(>Chi) NULL 12 27.51 timeobs 1 18.5 11 8.97 1.7e-05 NULL timeobs *** --- Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 summary(model) Call: glm(formula = ydf ~ timeobs, family = binomial(link = "logit")) Deviance Residuals: Min 1Q Median 3Q Max -1.6171-0.5268-0.0238 0.7149 1.1616 Coefficients: Estimate Std. Error z value Pr(> z ) (Intercept) -1.174 0.637-1.84 0.065 timeobs 0.170 0.043 3.96 7.5e-05 (Intercept). timeobs *** --- Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 (Dispersion parameter for binomial family taken to be 1) 23
Null deviance: 27.5092 on 12 degrees of freedom Residual deviance: 8.9745 on 11 degrees of freedom AIC: 35.92 Number of Fisher Scoring iterations: 4 bobs <- binconf(obs, Nobs) errbar(x, bobs[, 1], bobs[, 3], bobs[, 2], ylab = "Proportion", las = 1, ylim = c(0, 1), xlim = c(0, 25), bty = "n") newd <- data.frame(timeobs = seq(from = 1, to = 25, by = 0.1)) p <- predict(model, newdata = newd, type = "link", se = TRUE) plot_add(newd[, 1], with(p, exp(fit)/(1 + exp(fit))), type = "l", bty = "n") plot_add(newd[, 1], with(p, exp(fit + 1.96 * se.fit)/(1 + exp(fit + 1.96 * se.fit))), type = "l", bty = "n", lty = 2) plot_add(newd[, 1], with(p, exp(fit - 1.96 * se.fit)/(1 + exp(fit - 1.96 * se.fit))), type = "l", bty = "n", lty = 2) 24
1.0 0.8 Proportion 0.6 0.4 0.2 0.0 0 5 10 15 20 25 x Figure 12: Régression logistique par la méthode du maximum de vraisemblance 25
5.5 Pour aller plus loin... Imaginez qu à chacune des observations soit associée, en plus de la dimension temporelle, une autre co-variable. On peut alors se demander si les proportions observées dépendent aussi de ce co-facteur voire d une interaction entre le temps et ce cofacteur. timeobs [1] 1 3 6 8 10 12 13 14 17 19 20 22 25 ydf na nb [1,] 0 1 [2,] 1 2 [3,] 2 1 [4,] 5 7 [5,] 2 2 [6,] 8 2 [7,] 9 2 [8,] 2 0 [9,] 19 2 [10,] 23 3 [11,] 5 1 [12,] 12 3 [13,] 15 0 cofacteur <- c(9.2, 8.1, 2, 7.3, 9.5, 1.2, 10.8, 20.9, 11.9, 2.5, 2.3, 9.7, 10) model_c <- glm(ydf ~ cofacteur, family = binomial(link = "logit")) model_t_c <- glm(ydf ~ timeobs + cofacteur, family = binomial(link = "logit")) model_t_c_i <- glm(ydf ~ timeobs * cofacteur, family = binomial(link = "logit")) compare_aic(list(time = model, cofacteur = model_c, time_cofacteur = model_t_c, time_cofacteur_interaction = model_t_c_i)) [1] "The lowest AIC (35.922) is for series time with Akaike weight=0.640" AIC DeltaAIC time 35.92 0.000 cofacteur 54.21 18.289 time_cofacteur 37.88 1.962 time_cofacteur_interaction 39.27 3.350 Akaike_weight time 6.400e-01 cofacteur 6.836e-05 time_cofacteur 2.400e-01 time_cofacteur_interaction 1.199e-01 26
6 Fonction predict après un GLM La fonction predict permet de faire des prédictions à partir d un modèle ajusté. Cependant une erreur courante est d utiliser l approximation normale pour l intervalle de confiance des résultats prédits. Cela peut conduire à des grossières erreurs. Repartons du modèle précédemment ajusté: summary(model) Call: glm(formula = ydf ~ timeobs, family = binomial(link = "logit")) Deviance Residuals: Min 1Q Median 3Q Max -1.6171-0.5268-0.0238 0.7149 1.1616 Coefficients: Estimate Std. Error z value Pr(> z ) (Intercept) -1.174 0.637-1.84 0.065 timeobs 0.170 0.043 3.96 7.5e-05 (Intercept). timeobs *** --- Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 (Dispersion parameter for binomial family taken to be 1) Null deviance: 27.5092 on 12 degrees of freedom Residual deviance: 8.9745 on 11 degrees of freedom AIC: 35.92 Number of Fisher Scoring iterations: 4 On créé un dataframe avec les valeurs de timeobs à prédire. df <- data.frame(timeobs = 1:30) On visualise les résultats avec l option response. # make prediction and estimate CI using # 'response' option preddf2 <- predict(model, type = "response", newdata = df, se.fit = TRUE) plot_errbar(1:30, with(preddf2, fit), bty = "n", errbar.y.minus = with(preddf2, 1.96 * se.fit), errbar.y.plus = with(preddf2, 1.96 * se.fit), xlab = "timeobs covariable", ylab = "Response", type = "l", ylim = c(0, 1.1)) segments(0, 1, 30, 1, col = "red") 27
Response 0.0 0.2 0.4 0.6 0.8 1.0 0 5 10 15 20 25 30 timeobs covariable Figure 13: Notez que les barres d erreur dépassent 1! 28
Response 0.0 0.2 0.4 0.6 0.8 1.0 0 5 10 15 20 25 30 timeobs covariable Figure 14: Notez que les barres d erreur ne dépassent pas 1. On visualise les résultats maintenant avec l option link. # good practice: use the option 'link' preddf <- predict(model, type = "link", newdata = df, se.fit = TRUE) plot_errbar(1:30, with(preddf, exp(fit)/(1 + exp(fit))), bty = "n", errbar.y.minus = with(preddf, exp(fit)/(1 + exp(fit))) - with(preddf, exp(fit - 1.96 * se.fit)/(1 + exp(fit - 1.96 * se.fit))), errbar.y.plus = with(preddf, exp(fit + 1.96 * se.fit)/(1 + exp(fit + 1.96 * se.fit))) - with(preddf, exp(fit)/(1 + exp(fit))), xlab = "timeobs covariable", ylab = "Response", type = "l", ylim = c(0, 1)) segments(0, 1, 30, 1, col = "red") 29