# Instale uma única vez, se necessário:
# install.packages("tidyverse")
# install.packages("visdat")
# install.packages("GGally")
library(tidyverse)Explore antes de modelar
Tutorial prático de análise exploratória de dados em ovinocaprinocultura
Execute os blocos na ordem, copiando-os pelo botão de cópia disponível no HTML renderizado. Este material acompanha a aula Explorar os dados. Os dados são simulados para fins didáticos e não representam resultados reais da Embrapa.
1 Objetivos de aprendizagem
Ao final, você deverá conseguir reconhecer a estrutura dos dados, identificar ausências e valores discrepantes, resumir tendência central e dispersão, visualizar distribuições, comparar grupos, investigar associações e formular hipóteses para a modelagem.
O roteiro é: conferir → resumir → visualizar → comparar → relacionar → interpretar.
2 1. Preparar o ambiente
O tidyverse reúne ferramentas para organizar, resumir e visualizar dados. Instale os pacotes uma única vez, se necessário.
No RStudio, execute cada bloco com Run. Para gerar o tutorial, salve este arquivo como .qmd e clique em Render. O botão de cópia de código estará disponível na versão HTML.
3 2. Simular dados de um lote de animais
Cada linha representa um animal. Vamos criar dados de grupo genético, dieta, peso, ECC, FAMACHA, OPG e ganho médio diário (GMD).
set.seed(123) # torna a simulação reproduzível
n <- 80 # número de animais simulados
dados <- tibble(
animal = sprintf("C%03d", 1:n),
grupo_genetico = sample(c("Santa Inês", "Cruzado"), n, replace = TRUE),
dieta = sample(c("Dieta A", "Dieta B"), n, replace = TRUE),
peso_inicial = round(rnorm(n, mean = 24, sd = 3), 1),
ecc_inicial = sample(seq(2, 4.5, by = 0.5), n, replace = TRUE),
famacha = sample(1:5, n, replace = TRUE),
opg = round(rlnorm(n, meanlog = log(500), sdlog = 0.8))
) |>
mutate(
# A diferença média entre dietas é programada na simulação.
gmd_simulado = rnorm(
n,
mean = if_else(dieta == "Dieta B", 0.230, 0.200),
sd = 0.045
),
dias = 60,
peso_final = round(peso_inicial + gmd_simulado * dias, 1),
gmd = round((peso_final - peso_inicial) / dias, 3),
ecc_final = pmin(
5,
pmax(1, ecc_inicial +
sample(seq(-0.5, 1.5, by = 0.5), n, replace = TRUE))
)
) |>
select(-gmd_simulado)
head(dados)# A tibble: 6 × 11
animal grupo_genetico dieta peso_inicial ecc_inicial famacha opg dias
<chr> <chr> <chr> <dbl> <dbl> <int> <dbl> <dbl>
1 C001 Santa Inês Dieta B 24 2 1 983 60
2 C002 Santa Inês Dieta B 25.2 4 3 1080 60
3 C003 Santa Inês Dieta A 22.9 2 4 864 60
4 C004 Cruzado Dieta B 25.9 3.5 2 164 60
5 C005 Santa Inês Dieta B 23.3 3 5 987 60
6 C006 Cruzado Dieta B 25 2 1 350 60
# ℹ 3 more variables: peso_final <dbl>, gmd <dbl>, ecc_final <dbl>
Funções usadas: tibble() cria uma tabela; sample() sorteia categorias; rnorm() e rlnorm() geram dados simulados; mutate() cria colunas; round() arredonda; pmin() e pmax() limitam os valores; select() mantém ou remove colunas.
A base é artificial. A dieta B recebeu uma média de GMD um pouco maior por construção, portanto isso não é uma descoberta científica. Também não foi definida uma estrutura de baias ou unidades experimentais coletivas; não use a simulação para inferir efeitos reais.
3.1 ECC inicial e final
ggplot(dados, aes(x = ecc_inicial, y = ecc_final)) +
geom_jitter(width = 0.10, height = 0.10, alpha = 0.55,
color = "#28785B") +
geom_smooth(method = "lm", se = FALSE, color = "#005A9C") +
labs(
title = "ECC inicial e final dos animais simulados",
x = "ECC inicial", y = "ECC final"
) +
theme_minimal(base_size = 12)geom_jitter() desloca levemente pontos sobrepostos. Como o ECC é registrado em incrementos de 0,5 e tem limites, trate-o como um escore ordinal; a reta é uma síntese exploratória, não uma prova causal.
Reflita: há concentração nos limites da escala? Os pontos se distribuem de modo semelhante em todos os valores?
4 3. Conhecer a estrutura da base
glimpse(dados) # nomes, tipos e exemplosRows: 80
Columns: 11
$ animal <chr> "C001", "C002", "C003", "C004", "C005", "C006", "C007",…
$ grupo_genetico <chr> "Santa Inês", "Santa Inês", "Santa Inês", "Cruzado", "S…
$ dieta <chr> "Dieta B", "Dieta B", "Dieta A", "Dieta B", "Dieta B", …
$ peso_inicial <dbl> 24.0, 25.2, 22.9, 25.9, 23.3, 25.0, 27.3, 25.3, 23.0, 2…
$ ecc_inicial <dbl> 2.0, 4.0, 2.0, 3.5, 3.0, 2.0, 4.5, 2.0, 2.5, 3.5, 2.5, …
$ famacha <int> 1, 3, 4, 2, 5, 1, 4, 3, 1, 2, 3, 3, 1, 2, 1, 2, 2, 2, 5…
$ opg <dbl> 983, 1080, 864, 164, 987, 350, 575, 531, 704, 510, 132,…
$ dias <dbl> 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,…
$ peso_final <dbl> 40.5, 33.6, 33.7, 40.0, 34.7, 39.7, 40.4, 37.2, 30.1, 4…
$ gmd <dbl> 0.275, 0.140, 0.180, 0.235, 0.190, 0.245, 0.218, 0.198,…
$ ecc_final <dbl> 1.5, 3.5, 1.5, 5.0, 4.5, 3.0, 4.5, 2.0, 3.5, 3.5, 2.0, …
dim(dados) # número de linhas e colunas[1] 80 11
summary(dados) # resumo básico de cada variável animal grupo_genetico dieta peso_inicial
Length:80 Length:80 Length:80 Min. :17.80
Class :character Class :character Class :character 1st Qu.:22.07
Mode :character Mode :character Mode :character Median :23.70
Mean :23.85
3rd Qu.:25.73
Max. :30.60
ecc_inicial famacha opg dias peso_final
Min. :2.00 Min. :1.00 Min. : 97.0 Min. :60 Min. :29.10
1st Qu.:2.50 1st Qu.:2.00 1st Qu.: 325.8 1st Qu.:60 1st Qu.:33.55
Median :3.00 Median :3.00 Median : 553.5 Median :60 Median :36.35
Mean :3.15 Mean :2.85 Mean : 736.1 Mean :60 Mean :36.73
3rd Qu.:4.00 3rd Qu.:4.00 3rd Qu.: 990.0 3rd Qu.:60 3rd Qu.:40.00
Max. :4.50 Max. :5.00 Max. :2552.0 Max. :60 Max. :46.30
gmd ecc_final
Min. :0.1120 Min. :1.500
1st Qu.:0.1800 1st Qu.:2.500
Median :0.2135 Median :3.500
Mean :0.2147 Mean :3.525
3rd Qu.:0.2450 3rd Qu.:4.500
Max. :0.3150 Max. :5.000
Cada linha representa um animal neste exemplo; cada coluna representa uma variável. A unidade observacional pode ser diferente da unidade experimental. Se uma dieta for aplicada à baia inteira, por exemplo, a baia pode ser a unidade experimental e os animais, unidades observacionais.
5 4. Investigar dados ausentes
Vamos introduzir ausências artificiais para praticar sua identificação.
set.seed(321)
dados <- dados |>
mutate(
ecc_final = replace(ecc_final, sample(1:n, 5), NA_real_),
opg = replace(opg, sample(1:n, 8), NA_real_)
)NA indica informação ausente. is.na() identifica ausências; sum() conta; across() aplica a mesma operação a várias colunas.
dados |>
summarise(
across(
c(peso_inicial, peso_final, gmd, ecc_final, famacha, opg),
~ sum(is.na(.x))
)
)# A tibble: 1 × 6
peso_inicial peso_final gmd ecc_final famacha opg
<int> <int> <int> <int> <int> <int>
1 0 0 0 5 0 8
5.1 Visualizar as ausências
# Instale uma única vez, se necessário:
# install.packages("visdat")
library(visdat)
dados |>
select(peso_inicial, peso_final, gmd, ecc_final, famacha, opg) |>
visdat::vis_miss()Pergunte: em quais variáveis faltam dados? Em dados longitudinais reais, as ausências se concentram em determinada avaliação, lote ou condição sanitária? O gráfico localiza ausências, mas não explica sozinho por que ocorreram.
Não substitua automaticamente NA por zero. Zero pode ser uma medida válida e não equivale a “não medido”. A decisão depende da causa da ausência e da pergunta científica.
6 5. Resumir tendência central e dispersão
A média e a mediana descrevem o centro. O desvio-padrão (DP) e o intervalo interquartil (IIQ) ajudam a descrever a variabilidade.
dados |>
summarise(
n = sum(!is.na(gmd)),
media = mean(gmd, na.rm = TRUE),
mediana = median(gmd, na.rm = TRUE),
desvio_padrao = sd(gmd, na.rm = TRUE),
IIQ = IQR(gmd, na.rm = TRUE),
minimo = min(gmd, na.rm = TRUE),
maximo = max(gmd, na.rm = TRUE)
)# A tibble: 1 × 7
n media mediana desvio_padrao IIQ minimo maximo
<int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 80 0.215 0.214 0.0467 0.065 0.112 0.315
- Média: soma dividida pelo número de observações válidas.
- Mediana: valor central dos dados ordenados.
- DP: descreve o espalhamento dos valores em torno da média.
- IIQ: diferença entre o terceiro e o primeiro quartis.
- Mínimo e máximo: extremos observados.
na.rm = TRUE manda a função ignorar valores ausentes naquele cálculo.
6.1 Comparar o GMD entre dietas
dados |>
group_by(dieta) |>
summarise(
n = sum(!is.na(gmd)),
media_gmd = mean(gmd, na.rm = TRUE),
dp_gmd = sd(gmd, na.rm = TRUE),
mediana_gmd = median(gmd, na.rm = TRUE),
IIQ_gmd = IQR(gmd, na.rm = TRUE),
.groups = "drop"
)# A tibble: 2 × 6
dieta n media_gmd dp_gmd mediana_gmd IIQ_gmd
<chr> <int> <dbl> <dbl> <dbl> <dbl>
1 Dieta A 36 0.207 0.0462 0.2 0.047
2 Dieta B 44 0.221 0.0466 0.23 0.0527
group_by() separa os dados por grupo; summarise() calcula estatísticas dentro de cada grupo.
Reflita: as médias são diferentes? As medianas contam história semelhante? A diferença entre médias é grande em relação à dispersão?
O DP descreve a variabilidade entre observações; não mede diretamente a precisão da média. A precisão também depende do número de unidades independentes e da estrutura amostral. O erro-padrão descreve a incerteza amostral de um estimador; o intervalo de confiança depende do estimador, do método e do desenho.
7 6. Visualizar a distribuição do GMD
Um resumo numérico não revela toda a estrutura dos dados. O histograma ajuda a identificar assimetria, concentrações e possíveis subgrupos.
ggplot(dados, aes(x = gmd)) +
geom_histogram(bins = 15, fill = "#80B89A", color = "white") +
labs(
title = "Como se distribui o GMD?",
x = "GMD (kg/animal/dia)", y = "Número de animais"
) +
theme_classic(base_size = 12)O argumento bins controla o número aproximado de intervalos. Observe: a distribuição parece simétrica? Há caudas longas, lacunas ou mais de um pico? Um padrão bimodal pode sugerir subgrupos, mas é preciso investigar outras variáveis antes de propor uma explicação biológica.
7.1 Boxplot e possíveis valores discrepantes
ggplot(dados, aes(y = gmd)) +
geom_boxplot(fill = "#B9D8C5", color = "#16543F") +
labs(
title = "Há valores que merecem investigação?",
x = NULL, y = "GMD (kg/animal/dia)"
) +
theme_classic(base_size = 12)O boxplot resume mediana, quartis e dispersão. Valores fora dos limites usuais são possíveis discrepantes, não necessariamente erros.
7.2 Selecionar registros para inspeção
O critério comum sinaliza valores abaixo de (Q_1 - 1{,}5 IIQ) ou acima de (Q_3 + 1{,}5 IIQ).
q1 <- quantile(dados$gmd, 0.25, na.rm = TRUE)
q3 <- quantile(dados$gmd, 0.75, na.rm = TRUE)
iiq <- IQR(dados$gmd, na.rm = TRUE)
limite_inferior <- q1 - 1.5 * iiq
limite_superior <- q3 + 1.5 * iiq
dados |>
filter(gmd < limite_inferior | gmd > limite_superior) |>
select(animal, dieta, grupo_genetico, peso_inicial, peso_final, gmd)# A tibble: 0 × 6
# ℹ 6 variables: animal <chr>, dieta <chr>, grupo_genetico <chr>,
# peso_inicial <dbl>, peso_final <dbl>, gmd <dbl>
filter() seleciona linhas que atendem a uma condição. O código apenas lista registros para inspeção; não os exclui. Confira unidade, transcrição, registro original e plausibilidade biológica antes de decidir o que fazer.
8 7. Comparar grupos sem esconder os animais
ggplot(dados, aes(x = dieta, y = gmd, color = dieta)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter(width = 0.12, alpha = 0.7, size = 2) +
labs(
title = "Como varia o GMD entre dietas?",
x = "Dieta", y = "GMD (kg/animal/dia)"
) +
theme_classic(base_size = 12) +
theme(legend.position = "none")O boxplot resume a distribuição; geom_jitter() mostra as observações individuais. outlier.shape = NA evita desenhar novamente os valores extremos, já visíveis nos pontos.
A comparação gráfica é exploratória: não substitui inferência nem corrige falhas de delineamento. Se o tratamento foi aplicado às baias, os animais de uma mesma baia não devem ser tratados automaticamente como réplicas independentes.
9 8. Investigar relações entre variáveis
Cada ponto do gráfico abaixo representa um animal. A cor distingue grupos genéticos.
ggplot(
dados,
aes(x = peso_inicial, y = gmd, color = grupo_genetico)
) +
geom_point(size = 2.5, alpha = 0.8) +
geom_smooth(method = "lm", se = FALSE) +
labs(
title = "Peso inicial e GMD estão associados?",
x = "Peso inicial (kg)",
y = "GMD (kg/animal/dia)",
color = "Grupo genético"
) +
theme_classic(base_size = 12)geom_smooth(method = "lm") adiciona uma reta linear exploratória por grupo. Ela resume uma tendência linear, mas não demonstra causalidade nem garante que a relação linear seja adequada.
9.1 Correlação geral
cor(
dados$peso_inicial,
dados$gmd,
use = "complete.obs"
)[1] 0.07312221
cor() calcula, por padrão, a correlação de Pearson. use = "complete.obs" considera apenas pares sem valores ausentes.
O coeficiente (r) varia de -1 a 1: valores próximos de 1 indicam associação linear positiva; próximos de -1, associação linear negativa; próximos de zero indicam pouca associação linear, mas ainda pode haver relação não linear.
9.2 Correlação e teste por grupo genético
resumo_cor <- dados |>
group_by(grupo_genetico) |>
group_modify(~ {
df <- .x |>
select(peso_inicial, gmd) |>
drop_na()
teste <- cor.test(
df$peso_inicial,
df$gmd,
method = "pearson"
)
tibble(
n = nrow(df),
correlacao = unname(teste$estimate),
p_valor = teste$p.value
)
}) |>
ungroup()
resumo_cor# A tibble: 2 × 4
grupo_genetico n correlacao p_valor
<chr> <int> <dbl> <dbl>
1 Cruzado 32 0.199 0.275
2 Santa Inês 48 -0.000347 0.998
cor.test() estima a correlação e testa a hipótese nula de correlação populacional igual a zero, sob as condições do método. O valor de p não mede a força da associação nem sua relevância biológica.
Cuidado com este exemplo: o GMD foi calculado usando peso final e peso inicial. Logo, o peso inicial participa matematicamente da construção do GMD. A associação observada pode refletir essa dependência matemática e não deve ser interpretada automaticamente como efeito biológico independente.
Uma correlação significativa em um grupo e não significativa em outro não prova que as correlações sejam diferentes. Para comparar correlações, é necessário um teste apropriado para essa diferença, além de considerar os tamanhos amostrais e os padrões gráficos.
10 9. Mesma média, diferentes dispersões
Dois grupos podem ter a mesma média e heterogeneidade muito diferente. Esta segunda base é independente da simulação anterior.
set.seed(456)
exemplo <- tibble(
grupo = rep(c("Menor dispersão", "Maior dispersão"), each = 30),
gmd = c(
rnorm(30, mean = 0.200, sd = 0.015),
rnorm(30, mean = 0.200, sd = 0.050)
)
) |>
group_by(grupo) |>
mutate(
# Ajuste didático para que ambas as médias sejam exatamente 0,200.
gmd = gmd - mean(gmd) + 0.200
) |>
ungroup()exemplo |>
group_by(grupo) |>
summarise(
n = n(),
media = mean(gmd),
desvio_padrao = sd(gmd),
IIQ = IQR(gmd),
.groups = "drop"
)# A tibble: 2 × 5
grupo n media desvio_padrao IIQ
<chr> <int> <dbl> <dbl> <dbl>
1 Maior dispersão 30 0.2 0.0421 0.0491
2 Menor dispersão 30 0.2 0.0173 0.0264
ggplot(exemplo, aes(x = grupo, y = gmd, color = grupo)) +
geom_jitter(width = 0.10, size = 2, alpha = 0.7) +
stat_summary(
fun = mean, geom = "point", shape = 23,
size = 4, fill = "white", color = "black"
) +
labs(
title = "Mesma média, diferentes distribuições",
subtitle = "Dados simulados para fins didáticos",
x = NULL, y = "GMD (kg/animal/dia)"
) +
theme_classic(base_size = 12) +
theme(legend.position = "none")O losango branco representa a média; cada ponto representa uma observação. A média isolada não descreve a uniformidade dos animais.
Discussão: se dois grupos apresentam o mesmo GMD médio, mas um é mais heterogêneo, isso pode importar para o manejo e a padronização dos lotes. A relevância depende do objetivo produtivo e do contexto biológico.
11 10. Atividade complementar: matriz de gráficos
Esta atividade é opcional e um pouco mais avançada. Primeiro, use a matriz padrão de GGally::ggpairs().
# Instale uma única vez, se necessário:
# install.packages("GGally")
library(GGally)
dados_grafico <- dados |>
select(grupo_genetico, peso_inicial, peso_final, gmd) |>
drop_na()
GGally::ggpairs(
dados_grafico,
columns = 2:4,
mapping = aes(color = grupo_genetico, alpha = 0.7),
lower = list(continuous = wrap("points", size = 1.8)),
diag = list(continuous = wrap("densityDiag", alpha = 0.4))
) +
theme_classic(base_size = 11)- Diagonal: distribuições de cada variável.
- Triângulo inferior: diagramas de dispersão entre pares.
- Triângulo superior: resumos das relações, conforme a configuração padrão.
Desafio: substitua peso_final por ecc_inicial. Compare as distribuições e identifique um padrão que mereça investigação. Escreva uma frase sobre o que o gráfico mostra e outra sobre o que ainda precisaria ser testado.
Lembre-se: peso_final e gmd compartilham informação matemática, pois o GMD foi calculado usando os pesos inicial e final.
12 11. Da exploração à hipótese científica
Para interpretar os resultados, responda:
- Padrão observado: o que aparece no resumo ou gráfico?
- Qualidade dos dados: há ausências, inconsistências ou valores a conferir?
- Explicações plausíveis: que mecanismos biológicos ou de manejo poderiam explicar o padrão?
- Alternativas: lote, idade, sexo, grupo genético ou condição sanitária poderiam estar relacionados?
- Próximo passo: que análise é coerente com a pergunta, a variável resposta, o delineamento e a dependência entre observações?
- Resumir não é compreender toda a estrutura dos dados.
- Gráficos são instrumentos de diagnóstico e interpretação, não decoração.
- Observação extrema não é sinônimo de erro.
- Correlação não demonstra causalidade.
- Significância estatística não é sinônimo de relevância biológica.
- O modelo deve respeitar a pergunta científica, o delineamento e a estrutura dos dados.
13 12. Referências para aprofundamento
- WICKHAM, Hadley; ÇETINKAYA-RUNDEL, Mine; GROLEMUND, Garrett. R para Ciência de Dados. 2. ed. 2024.
- MONTGOMERY, Douglas C. Design and Analysis of Experiments. 8. ed. 2013.
- ÇETINKAYA-RUNDEL, Mine; HARDIN, Johanna. Introduction to Modern Statistics. 2. ed. 2024.
- KNAFLIC, Cole Nussbaumer. Storytelling with Data: A Data Visualization Guide for Business Professionals. 2015.
Curso de Atualização: Estatística Experimental e Ciência de Dados para Pesquisa em Ovinocaprinocultura — Embrapa Caprinos e Ovinos.
Dados simulados para fins didáticos; não representam resultados de rebanhos reais.