Pressupostos e Estatística Robusta

Modelos Lineares
Pressupostos
Robust Statistics
Diagnóstico
Um guia prático para diagnosticar modelos e usar alternativas robustas no R.
Autores
Afiliações

João O. Santos

ISPA

Cristina Mendonça

ISPA; WJCR

Vitória Melita

FP-UL

Mariona Pascual Peñas

UIB

João Raposo

ISPA

Marta Barros

FP-UL

0 Introdução

Nos tutoriais anteriores, ajustámos modelos como se fossem sempre perfeitos. Spoiler: não são. Os modelos lineares (lm(), aov(), mixed()) têm pressupostos, ou seja, assumem coisas sobre os dados. Quando os pressupostos falham, os valores-p (que são computados a partir da distribuição da estatística de teste, em H0, assumindo os pressupostos do modelo) começam a não ser tão fiáveis.

Neste tutorial vamos aprender:

  1. Como testar os principais pressupostos (de forma visual e formal).

  2. Como usar estatística robusta quando os pressupostos falham.

DicaFilosofia Moderna

Em vez de testar pressupostos e depois decidir, uma abordagem mais moderna consiste em:

  1. Ajustar o modelo OLS normal.

  2. Ajustar a versão robusta.

  3. Se os resultados forem semelhantes → reportar o OLS (mais familiar).

  4. Se diferirem → reportar o robusto (e mencionar que pressupostos foram violados e de que forma).

Field e Wilcox (2017) vão mais longe e sugerem mesmo que os modelos robustos deveriam ser o default na psicologia.

Vamos começar carregando as ferramentas:

library(palmerpenguins)  # Dados dos pinguins
library(ggplot2)         # Gráficos
library(performance)     # check_model()
library(see)             # Gráficos bonitos para o performance
library(car)             # leveneTest()
library(WRS2)            # Testes robustos
library(robustbase)      # lmrob() - regressão robusta
library(afex)            # Para ANOVAs

options(contrasts = c("contr.sum", "contr.poly"))

1 Os Pressupostos Principais

Para modelos lineares, precisamos de verificar:

  1. Linearidade: A relação entre X e Y é linear (só faz sentido para VIs contínuas).

  2. Normalidade dos Resíduos: Os erros seguem a distribuição normal.

  3. Independência: As observações não estão correlacionadas (já falámos disto no contexto das medidas repetidas) e/ou os erros são independentes (saber o erro para a observação x não me ajuda a prever o erro da observação x+1; o que é violado em algumas progressões temporais e será melhor modelado por séries temporais.

  4. Homocedasticidade: A variância dos erros é constante (não se aplica a ANOVAs de medidas repetidas que não tenham fatores entre-pp)

  5. Esferecidade: A variância das diferenças entre todos os momentos de medição (condições intra-pp) é constante (não se aplica a ANOVAs sem medições repetidas nem a ANOVAs em que o/s fator/es intra-pp só tenham dois níveis, porque aí só há uma diferença e a variância dessa diferença tem de ser igual a ela própria…como os jogadores de futebol).

  6. Ausência de Multicolinearidade: Os preditores/VIs não estão correlacionados entre si. Acaba por ser garantido pelo delineamento experimental, mas pode facilmente ser violado em dados observacionais (e.g., se quero ver se as percepções subjectivas de ansiedade e auto-confiança preveêm a percepção subjectiva de bem-estar, havendo uma correlação forte entre ansiedade e auto-confiança, os declives de cada uma destas variáveis não serão bem estimados porque uma variável “rouba” variância à outra).

  7. Ausência de Outliers: Nenhum ponto “sequestra” o modelo. Este não é necessariamente um pressuposto formal, dado que a existência de linearidade, normalidade e homocedasticidade perfeitas (enquanto conceito matemático formal) impediriam a existência de outliers.


2 Atalho: check_model()

A forma mais rápida de inspeccionar TUDO é usar performance::check_model():

library(palmerpenguins)
library(performance)

# Modelo: Prever massa corporal com comprimento do bico
modelo <- lm(body_mass_g ~ bill_length_mm, penguins)

check_model(modelo)

Este dashboard mostra 6 painéis: - Posterior Predictive Check: Os dados simulados pelo modelo parecem-se com os reais? - Linearity: A linha vermelha deve ser horizontal. - Homogeneity of Variance: A linha deve ser horizontal (sem funil). - Influential Observations: Pontos fora das linhas tracejadas são problemáticos. - Normality of Residuals: Os pontos devem seguir a linha diagonal. - Collinearity: VIF < 5 é bom (só relevante com múltiplas VIs, observadas).

Se tudo parecer “razoável”, pode avançar. Se algo parecer muito errado, convém tentar perceber o que se passa e testar a robustez das conclusões.


3 Linearidade (Só para IVs Contínuas)

Verificar Visualmente

ggplot(penguins, aes(x = bill_length_mm, y = body_mass_g)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", color = "blue") +
  geom_smooth(method = "loess", color = "red", linetype = "dashed") +
  theme_classic() +
  labs(title = "Linearidade: Azul = Linear, Vermelho = Loess")

  • Se as duas linhas forem semelhantes→ relação é linear.
  • Se a linha vermelha (loess) com curvas descrever muito melhor os dados→ não-linearidade.
Aviso

A linha azul será sempre linear, mesmo que os dados não sejam e a linha do loess terá quase sempre algum tipo de curva mesmo que a relação seja bastante linear. Foquem-se no padrão dos pontos.

Soluções

Se a violação da linearidade sugerir um padrão que pode ser bem descrito matematicamente (e.g., quadrático, exponencial, etc…) então o que teremos de fazer é modelar esse padrão. Se o padrão não parecer propriamente linear, mas também não for fácil descrevê-lo com outra função matemática, assumir linearidade pode ser o que implica menos pressupostos teóricos (mas devemos ter cuidado para não interpretar o modelo com o melhor modelo à face da Terra).

4 Normalidade dos Resíduos

Visual: Q-Q Plot (Preferido!)

residuos <- data.frame(resid = rstandard(modelo))

ggplot(residuos, aes(sample = resid)) +
  stat_qq() +
  stat_qq_line(color = "red") +
  theme_classic() +
  labs(title = "Q-Q Plot: Normalidade dos Resíduos")

Como interpretar: - Pontos perto da linha vermelha → Normal. - Caudas que se afastam → Desvios nas extremidades (comum e geralmente OK). - “S” shape → Assimetria forte.

Testes Formais

AvisoCuidado com Testes de Normalidade!
  • N pequeno → O teste tem pouco poder (não detecta violações).
  • N grande → O teste é demasiado sensível (detecta desvios irrelevantes).

Prefira sempre a inspeção visual.

Shapiro-Wilk


    Shapiro-Wilk normality test

data:  resid(modelo)
W = 0.99144, p-value = 0.04502

Kormogorov-Smirnov com Correcção de Lilliefors

library(nortest)

lillie.test(resid(modelo))

    Lilliefors (Kolmogorov-Smirnov) normality test

data:  resid(modelo)
D = 0.040585, p-value = 0.1859

Soluções

Se a normalidade falhar:

  • Transformar Y (e.g., log(Y) para skewness positiva).

  • Usar estatística robusta (ver abaixo).

  • Não fazer nada e confiar no teorema do limite central (TLC; tendo um N grande) e o facto dos modelos tenderem a ser robustos a violações da normalidade (mas isto só se a violação não for muito grave e soubermos mesmo o que TLC quer dize, nomeadamente ter em conta que a convergência para a normal pode necessitar de Ns maiores/ou demorar mais que o esperado em alguns casos).

5 Homoscedasticidade

Visual: Resíduos vs Previsões

residuos$fitted <- fitted(modelo)

ggplot(residuos, aes(x = fitted, y = resid)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red") +
  geom_smooth(color = "blue") +
  theme_classic() +
  labs(title = "Homoscedasticidade: Procurar Padrão de Funil",
       x = "Valores Ajustados", y = "Resíduos")

# ou com o performance e o see
plot(check_heteroscedasticity(modelo))

Como interpretar:

  • Dispersão constante ao longo do eixo X → Bom.

  • Funil (aumenta ou diminui) → Heteroscedasticidade.

Teste Formal: Teste de Levene e Brown-Forsythe

# Modelo com IV categórica
modelo_cat <- lm(body_mass_g ~ species, penguins)

library(car)

# Levene (SPSS chama Levene baseado na média)
leveneTest(modelo_cat, center = "mean")
Df F value Pr(>F)
group 2 5.335495 0.0052305
339 NA NA
# Brown-Forsythe (SPSS chama Levene baseado na mediana)
leveneTest(modelo_cat, center = "median")
Df F value Pr(>F)
group 2 5.120251 0.0064451
339 NA NA
# Ou sem argumento center, porque median é o default
leveneTest(modelo_cat)
Df F value Pr(>F)
group 2 5.120251 0.0064451
339 NA NA
# ou com o performance
# (o `[1]` é preciso para previnir um bug que o `check_homogeneity()`
#  tem com o método "levene")
check_homogeneity(modelo_cat, method = "levene", center = "mean")[1]
[1] 0.005230535
check_homogeneity(modelo_cat, method = "levene", center = "median")[1]
[1] 0.006445083
check_homogeneity(modelo_cat, method = "levene")[1]
[1] 0.006445083

Se p < α → Variâncias desiguais.

Soluções

  • Transformar Y (e.g., log(Y) ou sqrt(Y)).

  • Para ANOVA: Usar correção de Welch (oneway.test(Y ~ grupo, var.equal = FALSE)).

  • Usar testes robustos (ver abaixo).

6 Esfericidade

Usar o check_sphericity() do performance e aplicar a correcção de Greenhouse-Geisser (ou outra)—o afex::aov_4()/aov_car() já faz isso por nós.

7 Outliers

A palavra outlier parece simples, mas na prática há várias definições.

Na psicologia (e em modelos lineares), as definições baseadas no modelo costumam ser mais úteis do que métodos univariados (tipo “pontos fora do IQR” numa variável isolada), porque aquilo que nos interessa é: este caso está a distorcer a conclusão do modelo?

Nota
  • Há várias definições: resíduos padronizados, leverage, Cook’s distance, etc. Cada uma responde a uma pergunta diferente.

  • Outliers podem ser multivariados: um ponto pode não ser “extremo” em nenhuma variável isolada, mas ser estranho na combinação (pensem em “perfil raro”).

  • Os critérios podem ser enviesados pela presença de outliers: se os próprios outliers influenciam médias/variâncias/ajustes, eles também podem influenciar a régua que estamos a usar para os detectar.

DicaO melhor guia prático (mesmo)

Verificar com check_outliers()

out <- check_outliers(modelo)
out
OK: No outliers detected.
- Based on the following method and threshold: cook (0.7).
- For variable: (Whole model)

Se quiser ver os casos (linhas) que foram sinalizados, a forma mais segura é pegar nos dados que o modelo realmente usou:

# Dados usados pelo modelo (já sem NAs, etc.)
dados_modelo <- model.frame(modelo)

# Ver as linhas marcadas como influentes/outliers
# (Nota: 'out' funciona como índice)
#View(dados_modelo[out, ])

Visual: Cook’s Distance

residuos$cooksd <- cooks.distance(modelo)
residuos$id <- 1:nrow(residuos)

ggplot(residuos, aes(x = id, y = cooksd)) +
  geom_col() +
  geom_hline(yintercept = 4/nrow(penguins), color = "red", linetype = "dashed") +
  theme_classic() +
  labs(title = "Cook's Distance: Outliers Influentes",
       subtitle = "Acima da linha vermelha = Problemático")

# ou com performance e see
plot(check_outliers(modelo))

Nota

Mas eu não queria remover outliers…

Tudo bem.

Só não confundam duas coisas:

  • Detecção de casos influentes (diagnóstico) vs.
  • Remoção de casos (decisão).

E lembrem-se: muitos métodos robustos acabam por “aparar”/reduzir o peso dos outliers quase por definição, sem precisarmos de apagar linhas à mão.

Recomendação prática (sem cair em loops)

Um workflow razoável (e muito comum) é:

  1. Ajustar o modelo no dataset completo.
  2. Identificar casos influentes.
  3. Re-ajustar o modelo sem esses casos.
  4. Ver se a história muda.
# 1) Modelo no dataset completo
modelo <- lm(body_mass_g ~ bill_length_mm, penguins)

# 2) Detectar casos influentes/outliers (índices no dataset usado pelo modelo)
out <- check_outliers(modelo)

# 3) Reajustar sem esses casos (uma vez)
dados_sem_out <- model.frame(modelo)[-out, ]

modelo_sem_out <- lm(body_mass_g ~ bill_length_mm, penguins)

# 4) Comparar (o que muda?)
summary(modelo)

Call:
lm(formula = body_mass_g ~ bill_length_mm, data = penguins)

Residuals:
     Min       1Q   Median       3Q      Max 
-1762.08  -446.98    32.59   462.31  1636.86 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)     362.307    283.345   1.279    0.202    
bill_length_mm   87.415      6.402  13.654   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 645.4 on 340 degrees of freedom
  (2 observations deleted due to missingness)
Multiple R-squared:  0.3542,    Adjusted R-squared:  0.3523 
F-statistic: 186.4 on 1 and 340 DF,  p-value: < 2.2e-16
summary(modelo_sem_out)

Call:
lm(formula = body_mass_g ~ bill_length_mm, data = penguins)

Residuals:
     Min       1Q   Median       3Q      Max 
-1762.08  -446.98    32.59   462.31  1636.86 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)     362.307    283.345   1.279    0.202    
bill_length_mm   87.415      6.402  13.654   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 645.4 on 340 degrees of freedom
  (2 observations deleted due to missingness)
Multiple R-squared:  0.3542,    Adjusted R-squared:  0.3523 
F-statistic: 186.4 on 1 and 340 DF,  p-value: < 2.2e-16
Aviso“Recursive hell” (isto acontece mesmo)

Se removermos outliers e voltarmos a calcular os critérios, pode acontecer que:

  • casos que não eram outliers antes passem a ser agora;
  • removemos esses também;
  • e isto repete-se…

Por isso, neste tutorial prático, a recomendação é simples: façam no máximo uma ronda (modelo completo → modelo sem outliers) e usem estatística robusta quando o problema é estrutural.

Soluções

  • Investigar os casos (erro de medição? entrada mal feita? caso genuíno?).

  • Comparar modelo completo vs. modelo sem outliers (uma ronda, sem loops).

  • Usar modelos robustos (dão menos peso aos outliers e evitam “apagar pessoas”).

8 Estatística Robusta: As Ferramentas

Quando as assunções falham, usamos métodos que são menos sensíveis a violações.

Regressão -> robustbase::lmrob()

library(robustbase)

# Modelo OLS normal
modelo_ols <- lm(body_mass_g ~ bill_length_mm, penguins)

# Modelo robusto (estimador MM)
modelo_robusto <- lmrob(body_mass_g ~ bill_length_mm, penguins)

# Comparar
summary(modelo_ols)

Call:
lm(formula = body_mass_g ~ bill_length_mm, data = penguins)

Residuals:
     Min       1Q   Median       3Q      Max 
-1762.08  -446.98    32.59   462.31  1636.86 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)     362.307    283.345   1.279    0.202    
bill_length_mm   87.415      6.402  13.654   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 645.4 on 340 degrees of freedom
  (2 observations deleted due to missingness)
Multiple R-squared:  0.3542,    Adjusted R-squared:  0.3523 
F-statistic: 186.4 on 1 and 340 DF,  p-value: < 2.2e-16
summary(modelo_robusto)

Call:
lmrob(formula = body_mass_g ~ bill_length_mm, data = penguins)
 \--> method = "MM"
Residuals:
     Min       1Q   Median       3Q      Max 
-1892.83  -445.17    29.46   416.78  1593.07 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)      -7.327    382.138  -0.019    0.985    
bill_length_mm   96.554      9.555  10.105   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Robust residual standard error: 563.7 
  (2 observations deleted due to missingness)
Multiple R-squared:  0.4028,    Adjusted R-squared:  0.4011 
Convergence in 21 IRWLS iterations

Robustness weights: 
 28 weights are ~= 1. The remaining 314 ones are summarized as
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.2366  0.8229  0.9368  0.8806  0.9813  0.9990 
Algorithmic parameters: 
       tuning.chi                bb        tuning.psi        refine.tol 
        1.548e+00         5.000e-01         4.685e+00         1.000e-07 
          rel.tol         scale.tol         solve.tol          zero.tol 
        1.000e-07         1.000e-10         1.000e-07         1.000e-10 
      eps.outlier             eps.x warn.limit.reject warn.limit.meanrw 
        2.924e-04         1.084e-10         5.000e-01         5.000e-01 
     nResample         max.it       best.r.s       k.fast.s          k.max 
           500             50              2              1            200 
   maxit.scale      trace.lev            mts     compute.rd fast.s.large.n 
           200              0           1000              0           2000 
                  psi           subsampling                   cov 
           "bisquare"         "nonsingular"         ".vcov.avar1" 
compute.outlier.stats 
                 "SM" 
seed : int(0) 

O que muda:

  • Os coeficientes podem ser ligeiramente diferentes.

  • Os erros-padrão (e valores-p) são mais fiáveis com outliers.

ANOVA -> WRS2::t1way(); WRS2::t2way() (etc…)

library(WRS2)

# Fixar a semente de entropia (resultados replicáveis caso se use bootstrapping)
set.seed(123)

# ANOVA tradicional
aov_tradicional <- aov(body_mass_g ~ species, penguins)
summary(aov_tradicional)
             Df    Sum Sq  Mean Sq F value Pr(>F)    
species       2 146864214 73432107   343.6 <2e-16 ***
Residuals   339  72443483   213698                   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
2 observations deleted due to missingness
# ANOVA robusta (trimmed means)
t1way(body_mass_g ~ species, penguins)
Call:
t1way(formula = body_mass_g ~ species, data = penguins)

Test statistic: F = 247.5234 
Degrees of freedom 1: 2 
Degrees of freedom 2: 118.49 
p-value: 0 

Explanatory measure of effect size: 0.91 
Bootstrap CI: [0.85; 0.98]
t1way(body_mass_g ~ species, penguins, nboot = 5000)
Call:
t1way(formula = body_mass_g ~ species, data = penguins, nboot = 5000)

Test statistic: F = 247.5234 
Degrees of freedom 1: 2 
Degrees of freedom 2: 118.49 
p-value: 0 

Explanatory measure of effect size: 0.91 
Bootstrap CI: [0.86; 0.98]

O que faz:

  • Usa médias aparadas (ignora os 20% extremos).

  • Não assume homoscedasticidade.

  • Pode usar bootstrapping.

Medidas Repetidas -> robustlmm::rlmer()

library(robustlmm)

# Modelo misto robusto
modelo_rm_robusto <- rlmer(VD ~ condicao + (1 | participante), dados)

9 Fluxo de Trabalho Recomendado

Passo 1: Ajustar Modelo OLS

modelo <- lm(VD ~ IV, dados)

Passo 2: Diagnosticar

check_model(modelo)

Passo 3: Decidir

  • Tudo OK? → Reportar o modelo OLS.

  • Violações menores? → Reportar OLS + mencionar (ex: “pequenos desvios na normalidade foram observados”).

  • Violações graves? → Ajustar versão robusta.

Passo 4: Comparar

modelo_robusto <- lmrob(VD ~ IV, dados)

# Comparar padrão de resultados
summary(modelo)
summary(modelo_robusto)

Se os valores-p mudarem substancialmente (e.g: de 0.03 para 0.45, ou vice-versa), considerar reportar o robusto e citar Field & Wilcox (2017).

10 Exemplo Completo

Vamos prever a massa corporal dos pinguins com comprimento do bico E espécie.

library(palmerpenguins)
library(performance)
library(robustbase)

# 1. Ajustar modelo OLS
modelo_completo <- lm(body_mass_g ~ bill_length_mm * species, penguins)

# 2. Diagnosticar
check_model(modelo_completo)

# 3. Verificar outliers
check_outliers(modelo_completo)
OK: No outliers detected.
- Based on the following method and threshold: cook (0.7).
- For variable: (Whole model)
# 4. Ajustar versão robusta
modelo_robusto <- lmrob(body_mass_g ~ bill_length_mm * species, 
                        penguins)

# 5. Comparar coeficientes, estatísticas de teste e valores-p
summary(modelo_completo)

Call:
lm(formula = body_mass_g ~ bill_length_mm * species, data = penguins)

Residuals:
    Min      1Q  Median      3Q     Max 
-918.76 -245.89   -8.65  238.44 1126.27 

Coefficients:
                        Estimate Std. Error t value Pr(>|t|)    
(Intercept)              252.399    317.986   0.794  0.42790    
bill_length_mm            87.692      6.946  12.625  < 2e-16 ***
species1                -217.516    408.146  -0.533  0.59443    
species2                 593.744    498.875   1.190  0.23482    
bill_length_mm:species1    6.808      9.568   0.711  0.47729    
bill_length_mm:species2  -28.575     10.485  -2.725  0.00676 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 371.8 on 336 degrees of freedom
  (2 observations deleted due to missingness)
Multiple R-squared:  0.7882,    Adjusted R-squared:  0.7851 
F-statistic: 250.1 on 5 and 336 DF,  p-value: < 2.2e-16
summary(modelo_robusto)

Call:
lmrob(formula = body_mass_g ~ bill_length_mm * species, data = penguins)
 \--> method = "MM"
Residuals:
      Min        1Q    Median        3Q       Max 
-921.2416 -234.0475    0.8195  241.9822 1130.1167 

Coefficients:
                        Estimate Std. Error t value Pr(>|t|)    
(Intercept)              273.129    286.068   0.955   0.3404    
bill_length_mm            87.293      6.338  13.774   <2e-16 ***
species1                -356.282    371.500  -0.959   0.3382    
species2                 677.378    446.428   1.517   0.1301    
bill_length_mm:species1    9.945      8.806   1.129   0.2596    
bill_length_mm:species2  -30.348      9.590  -3.164   0.0017 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Robust residual standard error: 368.3 
  (2 observations deleted due to missingness)
Multiple R-squared:  0.7894,    Adjusted R-squared:  0.7863 
Convergence in 10 IRWLS iterations

Robustness weights: 
 39 weights are ~= 1. The remaining 303 ones are summarized as
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.3261  0.8615  0.9500  0.9041  0.9835  0.9989 
Algorithmic parameters: 
       tuning.chi                bb        tuning.psi        refine.tol 
        1.548e+00         5.000e-01         4.685e+00         1.000e-07 
          rel.tol         scale.tol         solve.tol          zero.tol 
        1.000e-07         1.000e-10         1.000e-07         1.000e-10 
      eps.outlier             eps.x warn.limit.reject warn.limit.meanrw 
        2.924e-04         1.084e-10         5.000e-01         5.000e-01 
     nResample         max.it       best.r.s       k.fast.s          k.max 
           500             50              2              1            200 
   maxit.scale      trace.lev            mts     compute.rd fast.s.large.n 
           200              0           1000              0           2000 
                  psi           subsampling                   cov 
           "bisquare"         "nonsingular"         ".vcov.avar1" 
compute.outlier.stats 
                 "SM" 
seed : int(0) 

Interpretação:

  • Se os coeficientes forem semelhantes → reportar OLS.

  • Se diferirem → reportar robusto e mencionar: “Devido a violações de homoscedasticidade, reportamos estimativas robustas (Field & Wilcox, 2017).”

  • Neste caso, as conclusões são muito semelhantes (a decisão em relação aos efeitos significativos vs não-significativos não muda; não há inversões de sinais nos declives; o decive de bill_length_mm é semelhante; as maiores diferenças estão nos efeitos não significativos da espécie, os declives da interacção até aumentam a magnitude). Logo, não parece que as conclusões que retirámos estejam a ser enviesadas por outliers.

11 Tabela Resumo: Soluções Rápidas

Violação Sintoma Visual Teste Formal Solução
Não-Linearidade Loess ≠ Linear NA GLM ou transformação
Não-Normalidade Q-Q Plot desviado Shapiro-Wilk lmrob(), WRS2::t1way() (ou equivalente)
Heteroscedasticidade Funil nos resíduos Levene’s Test lmrob(), Welch ANOVA, correcção de Brown-Forsythe, WRS2::t1way() (ou equivalente)
Outliers Cook’s D alto check_outliers() lmrob(), rlmer()

12 Recursos Adicionais

13 Conclusão

Testar pressupostos não é uma “checklist” burocrática. É uma forma de entender os nossos dados. Quando as assunções falham, não entrem em pânico. A estatística robusta existe para isso.

Recomendações

  1. Olhar para check_model().

  2. Se algo parecer errado, ajustar a versão robusta (ou ajustá-la anyway…).

  3. Comparar resultados.

  4. Reportar o mais apropriado (e justificar).

E lembrem-se desvios consideráveis aos pressupostos dos modelos podem ter relevância teórica. Não pensem neles apenas como “burocracia” matemática.