Descritivas no R

Programação
Estatística
Descritivas
Como resumir dados no R e perceber o que cada resumo mostra e esconde.
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 Estatística Descritiva: Resumos Que Não Apagam os Dados

A estatística descritiva resume os dados que observámos. Por exemplo, a média da massa corporal de um grupo de pinguins dá-nos um centro numérico, mas não mostra se há dois grupos de tamanhos diferentes, quantos valores faltam ou se uma observação extrema a desloca. Cada resumo perde parte da informação que estava nas linhas originais. Neste tutorial começamos por cinco famílias de resumos: frequência, tendência central, localização, dispersão e forma. Em cada uma perguntamos: o que é que este resumo revela, e o que pode esconder? Os gráficos também são estatísticas descritivas. Mostram a forma, os agrupamentos e as relações que um número isolado pode esconder. A página teórica de Estatística Descritiva desenvolve os conceitos; o tutorial Gráficos no R desenvolve a parte visual do trabalho.

Vamos usar penguins, do pacote palmerpenguins. Instale o pacote uma vez, fora do script, se necessário, e depois execute:

install.packages("palmerpenguins")
library(palmerpenguins)
ds <- penguins

Há valores em falta nesta base e isso faz parte do exemplo. Uma estatística sem a sua regra para dados em falta é uma instrução incompleta.

Não é preciso chegar já a saber estatística ou R de cor. Quem procura uma função pode começar pelo bloco mais próximo da sua pergunta. Quem quer primeiro reconhecer os resumos encontra as categorias abaixo. Em ambos os casos, vale a pena voltar aos dados e, quando a forma importar, aos gráficos. A tabela é o início da conversa, não a última palavra.

Famílias de Resumos

Frequências: Contar Categorias

Frequência absoluta é o número de casos. Frequência relativa é a proporção (ou percentagem). Frequência acumulada acrescenta contagens pela ordem dos níveis, por isso faz sentido sobretudo para variáveis ordinais. Para uma variável nominal como species, conte sem inventar uma ordem.

# Count the absolute frequency for each species.
table(ds$species)

   Adelie Chinstrap    Gentoo 
      152        68       124 
# Calculate the relative frequency for each species.
prop.table(table(ds$species))

   Adelie Chinstrap    Gentoo 
0.4418605 0.1976744 0.3604651 
# Cross-tabulate two categorical variables.
table(ds$species, ds$sex, useNA = "ifany")
           
            female male <NA>
  Adelie        73   73    6
  Chinstrap     34   34    0
  Gentoo        58   61    5
# A small ordinal example: cumulative frequency follows the stated order.
ordem <- factor(c("baixo", "baixo", "medio", "alto", "alto", "alto"),
                levels = c("baixo", "medio", "alto"), ordered = TRUE)
frequencias_ordinais <- table(ordem)
cumsum(frequencias_ordinais)
baixo medio  alto 
    2     3     6 

Estas contagens dão-nos o tamanho e a composição da amostra. Para comparar a massa corporal entre espécies precisamos de resumir outra variável; para saber se a amostra representa uma população mais ampla precisamos de informação sobre como foi obtida.

Tendência Central: Onde Está o Centro?

A moda é o valor mais frequente e é especialmente natural para variáveis categóricas. Em português, a palavra também significa roupa que está em voga. Na estatística, porém, basta contar ocorrências. Se cinco pessoas escolherem «calças largas, calças justas, calças justas, saia, calças justas», a moda é «calças justas». A mediana é o valor central depois de ordenar os dados (o segundo quartil, Q2). A média é a soma dividida pelo número de observações e é sensível a valores extremos.

Para a massa corporal, calculamos mediana e média. Para mostrar que uma variável pode ter mais de uma moda, usamos um exemplo categórico inventado com um empate.

# Return every mode when categories tie for the highest frequency.
respostas <- c("concordo", "concordo", "discordo", "discordo", "neutro")
tab_respostas <- table(respostas)
names(tab_respostas)[tab_respostas == max(tab_respostas)]
[1] "concordo" "discordo"
# Median and mean body mass, explicitly ignoring NA.
median(ds$body_mass_g, na.rm = TRUE)
[1] 4050
mean(ds$body_mass_g, na.rm = TRUE)
[1] 4201.754

Neste exemplo, concordo e discordo são ambas modas. Devolver apenas a primeira esconderia a multimodalidade. Numa variável quantitativa, um gráfico é geralmente mais informativo para reconhecer vários picos.

Nota

Pode ter aprendido “média para quantitativas, mediana para ordinais e moda para nominais”. É uma boa primeira classificação, mas não uma lei sobre o que podemos aprender. Neste site, escolhemos o resumo pela pergunta, pela escala, pelos valores extremos e pelo delineamento; declaramos a escolha em vez de a esconder atrás de uma etiqueta.

Localização: Quartis e Quantis

Os quartis dividem os valores ordenados em quatro partes: Q1, Q2 e Q3. Outros quantis usam outros pontos de corte. A amplitude interquartil (AIQ, Q3 - Q1) resume a largura dos 50% centrais e é menos afectada por extremos do que a amplitude total.

quantile(ds$body_mass_g, probs = c(0, .25, .5, .75, 1), na.rm = TRUE)
  0%  25%  50%  75% 100% 
2700 3550 4050 4750 6300 
IQR(ds$body_mass_g, na.rm = TRUE)
[1] 1200
range(ds$body_mass_g, na.rm = TRUE)
[1] 2700 6300

Dispersão: Quão Diferentes São os Valores?

A amplitude total é max - min. A variância calcula desvios quadrados à média e o desvio-padrão (sd()) volta à unidade original. O desvio-padrão é útil quando a média e a escala quadrática fazem sentido; a AIQ pode ser uma escolha mais resistente. Nenhum deles descreve a forma completa da distribuição.

max(ds$body_mass_g, na.rm = TRUE) - min(ds$body_mass_g, na.rm = TRUE)
[1] 3600
var(ds$body_mass_g, na.rm = TRUE)
[1] 643131.1
sd(ds$body_mass_g, na.rm = TRUE)
[1] 801.9545

Forma: Assimetria, Caudas e Multimodalidade

Assimetria e curtose são termos que o leitor encontrará em manuais, mas não substituem a observação dos dados. A curtose não deve ser ensinada como um simples “achatamento”: diferentes definições usam diferentes referências e são sensíveis às caudas. Em vez de calcular um número por reflexo, olhe para um gráfico e descreva assimetria, caudas, lacunas ou vários picos. O tutorial Gráficos no R mostra como fazer isso.

Um Resumo Numérico e o Que Ele Omite

summary() é uma forma rápida de obter mínimo, quartis, mediana, média e máximo (e informação sobre NA, conforme o tipo). É excelente para uma primeira inspecção, mas não permite concluir que duas distribuições têm o mesmo formato. Por exemplo, duas listas podem ter os mesmos cinco números e, ainda assim, uma concentrar os valores no centro e outra juntar dois grupos.

summary(ds$body_mass_g)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs 
   2700    3550    4050    4202    4750    6300       2 

Dois conjuntos podem ter os mesmos cinco números e formas muito diferentes. Um gráfico de pontos, histograma, densidade ou boxplot pode revelar aglomeramentos, assimetria e observações isoladas que uma tabela esconde. Esse é o motivo para combinar pelo menos um centro com uma medida de dispersão e, quando a forma importa, também uma visualização.

Resumir por Grupos e Aproximar o Modelo

Uma média global pode ocultar diferenças entre grupos ou uma composição imprevista da amostra. aggregate() produz resumos por grupo; a função não faz um teste de hipóteses e não ajusta a incerteza de um modelo.

aggregate(body_mass_g ~ species, data = ds,
          FUN = mean, na.rm = TRUE)
species body_mass_g
Adelie 3700.662
Chinstrap 3733.088
Gentoo 5076.016
aggregate(body_mass_g ~ species, data = ds,
          FUN = sd, na.rm = TRUE)
species body_mass_g
Adelie 458.5661
Chinstrap 384.3351
Gentoo 504.1162

Para várias descritivas, podemos usar dplyr. Instale-o uma vez se for necessário, mas não misture estilos sem motivo dentro de um mesmo script.

library(dplyr)

resumo_especie <- ds |>
  group_by(species) |>
  summarise(
    N = n(),
    N_massa = sum(!is.na(body_mass_g)),
    media = mean(body_mass_g, na.rm = TRUE),
    mediana = median(body_mass_g, na.rm = TRUE),
    DP = sd(body_mass_g, na.rm = TRUE),
    AIQ = IQR(body_mass_g, na.rm = TRUE),
    .groups = "drop"
  )

resumo_especie
species N N_massa media mediana DP AIQ
Adelie 152 151 3700.662 3700 458.5661 650.0
Chinstrap 68 68 3733.088 3700 384.3351 462.5
Gentoo 124 123 5076.016 5000 504.1162 800.0

N e N_massa não são redundantes: documentam quantos casos formam cada resumo. Se resumirmos por species e sex, passamos a perguntar por combinações de grupos; grupos pequenos ou ausentes merecem atenção.

ds |>
  group_by(species, sex) |>
  summarise(
    N_massa = sum(!is.na(body_mass_g)),
    media = mean(body_mass_g, na.rm = TRUE),
    DP = sd(body_mass_g, na.rm = TRUE),
    .groups = "drop"
  )
species sex N_massa media DP
Adelie female 73 3368.836 269.3801
Adelie male 73 4043.493 346.8116
Adelie NA 5 3540.000 477.1661
Chinstrap female 34 3527.206 285.3339
Chinstrap male 34 3938.971 362.1376
Gentoo female 58 4679.741 281.5783
Gentoo male 61 5484.836 313.1586
Gentoo NA 4 4587.500 338.1937

Da Tabela para o Modelo

As descritivas ajudam a decidir o que investigar, mas não substituem o modelo. Um modelo linear pode estimar diferenças de massa entre espécies, ajustar covariáveis e quantificar incerteza:

modelo <- lm(body_mass_g ~ species, data = ds)
summary(modelo)

Call:
lm(formula = body_mass_g ~ species, data = ds)

Residuals:
     Min       1Q   Median       3Q      Max 
-1126.02  -333.09   -33.09   316.91  1223.98 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)       3700.66      37.62   98.37   <2e-16 ***
speciesChinstrap    32.43      67.51    0.48    0.631    
speciesGentoo     1375.35      56.15   24.50   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 462.3 on 339 degrees of freedom
  (2 observations deleted due to missingness)
Multiple R-squared:  0.6697,    Adjusted R-squared:  0.6677 
F-statistic: 343.6 on 2 and 339 DF,  p-value: < 2.2e-16

A tabela de médias resume diferenças observadas; o modelo explicita a comparação e depende do delineamento, da independência, da escala da resposta e de outras verificações. Consulte modelos lineares e pressupostos antes de interpretar coeficientes.

Exercícios

  1. Calcule uma tabela de frequência para island e explique por que não há frequência acumulada natural para essa variável nominal.
  2. Compare média, mediana, AIQ e desvio-padrão de bill_length_mm por espécie. Que aspecto poderia continuar escondido? Faça um gráfico para o investigar.
  3. Construa um resumo por species e sex que inclua N e N_massa. Explique por que são importantes antes de comparar médias.
  4. Ajuste um modelo para uma pergunta que o seu resumo sugeriu. Escreva uma frase que distinga claramente o que a tabela mostra do que o modelo estima.
  5. Tarefa mais exigente, mas delimitada. Leia data/manatees.csv, descrito na página dos datasets. Para ManateeDeaths e Powerboats, faça uma tabela com N, valores em falta, mínimo, mediana, média e desvio-padrão. Faça também um gráfico de dispersão por Year e escreva duas frases: uma sobre o padrão descritivo que observa e outra sobre o que estes resumos não permitem concluir acerca de causalidade. Não ajuste um modelo.

Soluções

Exercício 1
freq_ilhas <- table(ds$island, useNA = "ifany")
freq_ilhas

   Biscoe     Dream Torgersen 
      168       124        52 
island é nominal: os nomes das ilhas não têm uma ordem numérica ou natural que permita interpretar cumsum() como frequência acumulada. Poderíamos calcular uma soma cumulativa depois de impor uma ordem administrativa, mas essa seria uma escolha convencional, não uma propriedade da variável.
Exercício 2
a <- ds |>
  group_by(species) |>
  summarise(
    media = mean(bill_length_mm, na.rm = TRUE),
    mediana = median(bill_length_mm, na.rm = TRUE),
    AIQ = IQR(bill_length_mm, na.rm = TRUE),
    DP = sd(bill_length_mm, na.rm = TRUE),
    .groups = "drop"
  )
a
species media mediana AIQ DP
Adelie 38.79139 38.80 4.000 2.663405
Chinstrap 48.83382 49.55 4.725 3.339256
Gentoo 47.50488 47.30 4.250 3.081857
ggplot2::ggplot(ds, ggplot2::aes(species, bill_length_mm)) +
  ggplot2::geom_jitter(width = 0.12, alpha = 0.35) +
  ggplot2::geom_boxplot(width = 0.45, outlier.shape = NA, alpha = 0.25) +
  ggplot2::theme_classic() +
  ggplot2::labs(x = "Espécie", y = "Comprimento do bico (mm)")

A média e o desvio-padrão podem esconder assimetria, vários picos e valores extremos. Os pontos mostram as observações individuais e a caixa resume quartis; um histograma ou uma densidade por espécie poderia revelar aspectos diferentes. A comparação deve indicar também quantos valores contribuíram para cada resumo, sobretudo quando há dados em falta.
Exercício 3
resumo_especie_sexo <- ds |>
  group_by(species, sex) |>
  summarise(
    N = n(),
    N_massa = sum(!is.na(body_mass_g)),
    media = mean(body_mass_g, na.rm = TRUE),
    DP = sd(body_mass_g, na.rm = TRUE),
    .groups = "drop"
  )
resumo_especie_sexo
species sex N N_massa media DP
Adelie female 73 73 3368.836 269.3801
Adelie male 73 73 4043.493 346.8116
Adelie NA 6 5 3540.000 477.1661
Chinstrap female 34 34 3527.206 285.3339
Chinstrap male 34 34 3938.971 362.1376
Gentoo female 58 58 4679.741 281.5783
Gentoo male 61 61 5484.836 313.1586
Gentoo NA 5 4 4587.500 338.1937
N conta as linhas no grupo; N_massa conta as massas efectivamente usadas no resumo. Se forem diferentes, a média não se baseia em todos os casos. Os dois números também alertam para grupos pequenos ou para padrões de valores em falta antes de compararmos médias.
Exercício 4

Uma pergunta possível, sugerida pelas diferenças descritivas entre espécies, é se a massa corporal difere entre espécies depois de ter em conta o comprimento do bico:

modelo_bico <- lm(body_mass_g ~ species + bill_length_mm, data = ds)
summary(modelo_bico)

Call:
lm(formula = body_mass_g ~ species + bill_length_mm, data = ds)

Residuals:
    Min      1Q  Median      3Q     Max 
-871.21 -246.17    5.29  213.13 1084.73 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)       153.740    268.901   0.572    0.568    
speciesChinstrap -885.812     88.250 -10.038  < 2e-16 ***
speciesGentoo     578.629     75.362   7.678 1.76e-13 ***
bill_length_mm     91.436      6.887  13.276  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 375.3 on 338 degrees of freedom
  (2 observations deleted due to missingness)
Multiple R-squared:  0.7829,    Adjusted R-squared:  0.781 
F-statistic: 406.3 on 3 and 338 DF,  p-value: < 2.2e-16
A tabela mostra médias observadas de body_mass_g em cada espécie. O modelo estima diferenças condicionais entre espécies, segundo a forma funcional e as hipóteses desse modelo. Outra pergunta sugerida pelos dados poderia justificar um modelo diferente; não devemos escolher a pergunta apenas para obter um resultado mais conveniente.
Exercício 5
manatees <- read.csv("../data/manatees.csv")

resumo_manatees <- do.call(rbind, lapply(
  c("ManateeDeaths", "Powerboats"),
  function(nome) {
    x <- manatees[[nome]]
    data.frame(
      variavel = nome,
      N = length(x),
      em_falta = sum(is.na(x)),
      minimo = min(x, na.rm = TRUE),
      mediana = median(x, na.rm = TRUE),
      media = mean(x, na.rm = TRUE),
      DP = sd(x, na.rm = TRUE)
    )
  }
))
resumo_manatees
variavel N em_falta minimo mediana media DP
ManateeDeaths 35 0 13 53 58.05714 29.34274
Powerboats 35 0 447 735 755.95714 179.33613
ggplot2::ggplot(manatees, ggplot2::aes(x = Year, y = ManateeDeaths)) +
  ggplot2::geom_point() +
  ggplot2::theme_classic() +
  ggplot2::labs(x = "Year", y = "Manatee deaths")

ggplot2::ggplot(manatees, ggplot2::aes(x = Year, y = Powerboats)) +
  ggplot2::geom_point() +
  ggplot2::theme_classic() +
  ggplot2::labs(x = "Year", y = "Powerboats")

O gráfico mostra a evolução descritiva anual das mortes registadas e o resumo quantifica o centro e a dispersão das duas variáveis. Estes dados observacionais não permitem concluir, só por apresentarem padrões conjuntos, que o número de Powerboats causou as mortes; seria necessário justificar um delineamento e uma estratégia de análise adequados. Uma tabela ou um gráfico alternativo pode ser preferível, desde que preserve as mesmas verificações e limites de interpretação.