GLMs no R: modelos para proporções e contagens
0 Setup
Um GLM é o lm() quando a variável dependente não é bem descrita por erros normais (p.ex., 0/1, proporções, contagens).
1 GLM, Quando, Como e Porquê?
Se a VD é quantitativa (aprox. contínua) e os resíduos fazem sentido como “ruído normal”, começamos por lm().
Mas se a VD for:
Binária (0/1; sim/não; sucesso/insucesso) → GLM binomial (regressão logística).
Proporção (k sucessos em n tentativas) → GLM binomial (com
cbind(sucessos, falhas)), oubinom.test(), ouprop.test(), ou teste de independência do qui-quadrado comchisq.test().Contagem (0, 1, 2, 3, …) → GLM Poisson (ou alternativas se houver overdispersion, ver pacote
glmmTMB).
Binomial:
family = binomial(link = "logit")Poisson:
family = poisson(link = "log")
Na prática, quase nunca precisas de mudar o link por defeito (mas podes, se houver motivo).
2 Comparação de GLMs
A lógica que já vimos nos modelos lineares continua a funcionar:
Temos um modelo compacto (mais simples).
Temos um modelo aumentado (mais parâmetros)
Avalias se os ganhos no desempenho justificam o aumento de complexidade.
A diferença é que, em GLMs, o erro não é medido com SSE (soma de quadrados), mas com log-likelihood / deviance.
3 Binomial: Regressão Logística
Vamos prever a probabilidade de sobrevivência (Survived) em função da classe (Pclass) e do sexo (Sex).
| X | PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 1 | 0 | class3 | Braund, Mr. Owen Harris | male | 22 | 1 | 0 | A/5 21171 | 7.2500 | S | |
| 2 | 2 | 1 | class1 | Cumings, Mrs. John Bradley (Florence Briggs Thayer) | female | 38 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
| 3 | 3 | 1 | class3 | Heikkinen, Miss. Laina | female | 26 | 0 | 0 | STON/O2. 3101282 | 7.9250 | S | |
| 4 | 4 | 1 | class1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35 | 1 | 0 | 113803 | 53.1000 | C123 | S |
| 5 | 5 | 0 | class3 | Allen, Mr. William Henry | male | 35 | 0 | 0 | 373450 | 8.0500 | S | |
| 7 | 7 | 0 | class1 | McCarthy, Mr. Timothy J | male | 54 | 0 | 0 | 17463 | 51.8625 | E46 | S |
Ajustar o Modelo
Tabela da ANOVA
| LR Chisq | Df | Pr(>Chisq) | |
|---|---|---|---|
| Pclass | 92.34621 | 2 | 0e+00 |
| Sex | 223.13415 | 1 | 0e+00 |
| Pclass:Sex | 30.15567 | 2 | 3e-07 |
# Linha da interacção na tabela da ANOVA
anova(m_log0, m_log1, test = "Chisq")| Resid. Df | Resid. Dev | Df | Deviance | Pr(>Chi) |
|---|---|---|---|---|
| 710 | 672.4307 | NA | NA | NA |
| 708 | 642.2750 | 2 | 30.15567 | 3e-07 |
Pclass: diferenças entre classes em média.Sex: diferenças entre sexos em média.Pclass:Sex: se a diferença entre sexos muda com a classe (interacção).
Interpretar Coeficientes
Os coeficientes do modelo estão em log-odds.
coef(m_log1)→ log-odds.exp(coef(m_log1))→ odds ratios.
coef(m_log1) (Intercept) Pclass1 Pclass2 Sex1 Pclass1:Sex1 Pclass2:Sex1
0.28347600 1.15957992 0.06901481 1.57608030 0.28897003 0.49917712
(Intercept) Pclass1 Pclass2 Sex1 Pclass1:Sex1 Pclass2:Sex1
1.327737 3.188594 1.071452 4.835963 1.335052 1.647365
Se um coeficiente for 0.7, então exp(0.7) ≈ 2.01: as odds são ~2x maiores (não é o mesmo que “probabilidade 2x maior”).
emmeans: probabilidades e comparações
O grande truque é pedir type = "response", para obteres probabilidades (em vez de log-odds).
Pclass Sex prob SE df asymp.LCL asymp.UCL
class1 female 0.965 0.0200 Inf 0.8963 0.989
class2 female 0.919 0.0317 Inf 0.8310 0.963
class3 female 0.461 0.0494 Inf 0.3667 0.558
class1 male 0.396 0.0487 Inf 0.3056 0.494
class2 male 0.152 0.0360 Inf 0.0935 0.236
class3 male 0.150 0.0225 Inf 0.1113 0.200
Confidence level used: 0.95
Intervals are back-transformed from the logit scale
emm_pclass Pclass prob SE df asymp.LCL asymp.UCL
class1 0.809 0.0480 Inf 0.697 0.886
class2 0.587 0.0618 Inf 0.463 0.701
class3 0.280 0.0267 Inf 0.231 0.335
Results are averaged over the levels of: Sex
Confidence level used: 0.95
Intervals are back-transformed from the logit scale
emm_sex Sex prob SE df asymp.LCL asymp.UCL
female 0.865 0.0292 Inf 0.797 0.913
male 0.215 0.0219 Inf 0.176 0.261
Results are averaged over the levels of: Pclass
Confidence level used: 0.95
Intervals are back-transformed from the logit scale
Pairwise comparisons (ex.: classes, corrigindo para múltiplas comparações conforme o default do emmeans):
pairs(emm_pclass) contrast odds.ratio SE df null z.ratio p.value
class1 / class2 2.98 1.20 Inf 1 2.713 0.0183
class1 / class3 10.89 3.68 Inf 1 7.066 <0.0001
class2 / class3 3.66 1.05 Inf 1 4.515 <0.0001
Results are averaged over the levels of: Sex
P value adjustment: tukey method for comparing a family of 3 estimates
Tests are performed on the log odds ratio scale
Sex = female:
contrast odds.ratio SE df null z.ratio p.value
class1 / class2 2.41 1.750 Inf 1 1.213 0.4453
class1 / class3 31.99 19.800 Inf 1 5.588 <0.0001
class2 / class3 13.26 6.230 Inf 1 5.501 <0.0001
Sex = male:
contrast odds.ratio SE df null z.ratio p.value
class1 / class2 3.67 1.270 Inf 1 3.756 0.0005
class1 / class3 3.71 0.998 Inf 1 4.874 <0.0001
class2 / class3 1.01 0.334 Inf 1 0.031 0.9995
P value adjustment: tukey method for comparing a family of 3 estimates
Tests are performed on the log odds ratio scale
Pclass = class1:
contrast odds.ratio SE df null z.ratio p.value
female / male 41.68 25.90 Inf 1 6.000 <0.0001
Pclass = class2:
contrast odds.ratio SE df null z.ratio p.value
female / male 63.47 32.40 Inf 1 8.141 <0.0001
Pclass = class3:
contrast odds.ratio SE df null z.ratio p.value
female / male 4.83 1.28 Inf 1 5.938 <0.0001
Tests are performed on the log odds ratio scale
Um gráfico de interacção (sem drama)
emm_df <- as.data.frame(emm_cells)
# Nota: em GLMs binomiais, a coluna do emmeans com probabilidades chama-se tipicamente `prob`
# e os intervalos assimptóticos `asymp.LCL` / `asymp.UCL`.
ggplot(emm_df, aes(x = Pclass, y = prob, color = Sex, group = Sex)) +
geom_point(size = 2) +
geom_errorbar(aes(ymin = asymp.LCL, ymax = asymp.UCL), width = 0.15) +
geom_line() +
theme_classic() +
labs(x = "Classe", y = "P(Sobreviver)")Diagnósticos
Em logística, o que costuma matar análises não é “não-normalidade dos resíduos” (isso já estava assumido), mas sim:
- especificação errada (falta de preditores importantes / não-linearidades),
- separação (quase) completa,
- overdispersion em dados agregados,
- pontos influentes.
performance::check_model(m_log1)4 Binomial com Proporções
Se não tens uma VD 0/1 por linha, mas sim contagens de sucessos e falhas por condição, ajusta um binomial assim:
# Exemplo didáctico (simulado) com 3 grupos
dat_prop <- data.frame(grupo = factor(c("A", "B", "C")),
sucessos = c(12, 20, 28),
total = c(40, 40, 40))
dat_prop$falhas <- dat_prop$total - dat_prop$sucessos
dat_prop| grupo | sucessos | total | falhas |
|---|---|---|---|
| A | 12 | 40 | 28 |
| B | 20 | 40 | 20 |
| C | 28 | 40 | 12 |
| LR Chisq | Df | Pr(>Chisq) | |
|---|---|---|---|
| grupo | 13.16526 | 2 | 0.0013842 |
emmeans(m_prop, ~ grupo, type = "response") grupo prob SE df asymp.LCL asymp.UCL
A 0.3 0.0725 Inf 0.179 0.457
B 0.5 0.0791 Inf 0.350 0.650
C 0.7 0.0725 Inf 0.543 0.821
Confidence level used: 0.95
Intervals are back-transformed from the logit scale
contrast odds.ratio SE df null z.ratio p.value
A / B 0.429 0.2010 Inf 1 -1.810 0.1661
A / C 0.184 0.0896 Inf 1 -3.473 0.0015
B / C 0.429 0.2010 Inf 1 -1.810 0.1661
P value adjustment: tukey method for comparing a family of 3 estimates
Tests are performed on the log odds ratio scale
cbind(sucessos, falhas)?
Porque assim o modelo sabe quantas tentativas existem em cada linha, e não trata “0.70” e “7/10” como a mesma coisa.
5 Poisson: modelos para contagens
Quando a VD é uma contagem (número de eventos), um ponto de partida natural é Poisson com link log.
Vamos usar um exemplo real do curso: mortes de manatins (ManateeDeaths) em função do número de barcos (Powerboats).
| Year | ManateeDeaths | Powerboats |
|---|---|---|
| 1982 | 13 | 447 |
| 1983 | 21 | 460 |
| 1984 | 24 | 481 |
| 1985 | 16 | 498 |
| 1986 | 24 | 513 |
| 1987 | 20 | 512 |
Ajustar Poisson
| Resid. Df | Resid. Dev | Df | Deviance | Pr(>Chi) |
|---|---|---|---|---|
| 34 | 543.05050 | NA | NA | NA |
| 33 | 66.97316 | 1 | 476.0773 | 0 |
summary(m_pois1)
Call:
glm(formula = ManateeDeaths ~ Powerboats, family = poisson(link = "log"),
data = ds)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 1.6319121 0.1278815 12.76 <2e-16 ***
Powerboats 0.0030391 0.0001503 20.22 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for poisson family taken to be 1)
Null deviance: 543.051 on 34 degrees of freedom
Residual deviance: 66.973 on 33 degrees of freedom
AIC: 272
Number of Fisher Scoring iterations: 4
Interpretar: multiplicadores (IRR)
Em Poisson, exp(beta) é um multiplicador na taxa esperada.
Se exp(beta1) = 1.10, isso sugere ~10% mais eventos por cada unidade de X (dependendo da escala de X).
Diagnóstico-chave: overdispersion
Poisson assume (muito aproximadamente) que variância ≈ média. Se a variância for muito maior, os erros-padrão ficam optimistas e os p-values ficam “bons demais”.
performance::check_overdispersion(m_pois1)# Overdispersion test
dispersion ratio = 1.983
Pearson's Chi-Squared = 65.429
p-value = 0.001
Se houver overdispersion: 3 caminhos comuns
- Quasi-Poisson (ajusta os SE, mas não dá AIC “a sério”):
m_qpois <- glm(ManateeDeaths ~ Powerboats,
data = ds,
family = quasipoisson(link = "log"))
summary(m_qpois)
Call:
glm(formula = ManateeDeaths ~ Powerboats, family = quasipoisson(link = "log"),
data = ds)
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1.6319121 0.1800673 9.063 1.79e-10 ***
Powerboats 0.0030391 0.0002116 14.361 9.44e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for quasipoisson family taken to be 1.982688)
Null deviance: 543.051 on 34 degrees of freedom
Residual deviance: 66.973 on 33 degrees of freedom
AIC: NA
Number of Fisher Scoring iterations: 4
- Negative Binomial (muito comum em contagens):
Call:
glm.nb(formula = ManateeDeaths ~ Powerboats, data = ds, init.theta = 63.1074526,
link = log)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 1.5519755 0.1624194 9.555 <2e-16 ***
Powerboats 0.0031387 0.0001973 15.906 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for Negative Binomial(63.1075) family taken to be 1)
Null deviance: 298.374 on 34 degrees of freedom
Residual deviance: 32.973 on 33 degrees of freedom
AIC: 261.84
Number of Fisher Scoring iterations: 1
Theta: 63.1
Std. Err.: 30.2
2 x log-likelihood: -255.836
- Rever o modelo (muitas vezes, overdispersion é sinal de variável omitida ou estrutura de agrupamento → GLMM).
6 Offset: Taxas em Vez de Contagens
Às vezes a pergunta não é “quantos eventos”, mas sim “qual é a taxa por exposição” (tempo, população, km percorridos, etc.).
Um offset(log(exposicao)) diz ao modelo: “a contagem esperada deve crescer proporcionalmente à exposição”.
| exposicao | grupo | y |
|---|---|---|
| 10 | A | 1 |
| 10 | B | 7 |
| 20 | A | 6 |
| 20 | B | 8 |
Call:
glm(formula = y ~ grupo + offset(log(exposicao)), family = poisson(link = "log"),
data = dat_rate)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -1.0742 0.2289 -4.694 2.68e-06 ***
grupo1 -0.3811 0.2289 -1.665 0.0959 .
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for poisson family taken to be 1)
Null deviance: 5.4383 on 3 degrees of freedom
Residual deviance: 2.4615 on 2 degrees of freedom
AIC: 19.865
Number of Fisher Scoring iterations: 4
emmeans(m_rate, ~ grupo, type = "response") grupo rate SE df asymp.LCL asymp.UCL
A 3.30 1.25 Inf 1.57 6.92
B 7.07 1.83 Inf 4.26 11.73
Results are averaged over the levels of: exposicao
Confidence level used: 0.95
Intervals are back-transformed from the log scale
7 Mini-checklist antes de escrever resultados
A VD é mesmo binária/proporção/contagem? (Se for 0–100 contínuo, talvez não.)
As variáveis categóricas estão como
factor()?Fizeste pelo menos uma comparação de modelos (compacto vs aumentado)?
Usaste
emmeans(..., type = "response")para comunicar probabilidades/taxas?Verificaste overdispersion em Poisson?
Fizeste um
check_model()para apanhar problemas óbvios?
8 Exercícios
Titanic: acrescentar idade
No Titanic, refaz o modelo com Age e compara:
Survived ~ Pclass * SexSurvived ~ Pclass * Sex + Age
Dica: remove NAs de Age.
Manatees: comparar Poisson vs quasi-Poisson
Ajusta:
glm(..., family = poisson)glm(..., family = quasipoisson)
Compara as conclusões (coeficientes semelhantes, SE/p-values diferentes).
emmeans em logística
No modelo Titanic, pede:
emmeans(m_log1, list(pairwise ~ Pclass | Sex), type = "response")
E resume em 2 frases o padrão de diferenças.