Nesta aula vamos construir um fluxo completo de trabalho para dados experimentais salvos em múltiplos arquivos. Em uma situação real de pesquisa, é comum receber bases produzidas por pessoas diferentes, em anos diferentes e com pequenas inconsistências de nomes, categorias e formatos. Por isso, antes de ajustar modelos ou produzir gráficos, precisamos organizar a base de dados de forma coerente.
Ao longo desta aula, vamos:
importar arquivos de anos distintos;
inspecionar a estrutura de cada tabela;
padronizar nomes de colunas;
harmonizar categorias;
unir tabelas por linhas;
adicionar informações auxiliares com join;
comparar médias de produtividade entre tratamentos com o teste t;
visualizar o valor da estatística t e o p-valor;
construir um gráfico final anotado com o resultado do teste.
Objetivo
importar dados de múltiplos arquivos `.csv`;
reconhecer inconsistências entre tabelas;
padronizar variáveis com `rename()`, `mutate()` e `select()`;
empilhar tabelas com `bind_rows()`;
enriquecer tabelas com `left_join()`;
executar e interpretar um teste *t* para comparar duas médias;
relacionar o teste *t* com sua representação gráfica;
construir gráficos informativos com `ggplot2`.
Arquivos utilizados
Nesta aula serão usados os seguintes arquivos:
experiment_2022.csv
experiment_2023.csv
experiment_2024.csv
location_info.csv
Todos os arquivos devem estar salvos na mesma pasta do documento Quarto, ou em uma pasta cujo caminho seja informado corretamente no código.
1. Carregar os pacotes
Começamos carregando os pacotes que serão usados ao longo da análise. O tidyverse reúne funções importantes para importação, transformação e visualização de dados. O pacote broom será útil para organizar a saída do teste estatístico em formato de tabela.
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.0 ✔ readr 2.2.0
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.2 ✔ tibble 3.3.1
✔ lubridate 1.9.5 ✔ tidyr 1.3.2
✔ purrr 1.2.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(broom)library(gsheet) # Para importar dados direto da web
2. Importar os arquivos anuais
Cada arquivo representa um ano de experimento. A primeira etapa é importar cada um separadamente e armazená-los em objetos com nomes claros e sequenciais.
# Caso não tenha os arquivos locais, você pode adaptar para leitura online se necessáriodados_2022_bruto <-read_csv("experiment_2022.csv")
Rows: 8 Columns: 7
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (3): location, cultivar, treatment
dbl (4): year, block, severity, yield_kg_ha
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 8 Columns: 7
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (3): site, cult, trt
dbl (4): Year, rep, sev, yield
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 8 Columns: 6
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (3): location, cultivar, treatment
dbl (3): block, severity_pct, yield_kg_ha
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
3. Inspecionar a estrutura das tabelas
Antes de unir arquivos, precisamos entender como eles estão organizados. A função glimpse() mostra os nomes das colunas, os tipos de variáveis e uma prévia dos valores.
Ao observar a estrutura das tabelas, note que algumas colunas equivalentes têm nomes diferentes entre anos. Esse é um dos problemas mais comuns em dados reais. Por exemplo, a mesma informação pode aparecer como:
year em um arquivo e Year em outro;
location em um arquivo e site em outro;
yield_kg_ha em um arquivo e yield em outro.
Além disso, as categorias de tratamento também podem variar. Em um ano, o tratamento pode aparecer como "Control" e "Fungicide", enquanto em outro aparece abreviado como "ctrl" e "fung".
4. Padronizar o arquivo de 2022
O arquivo de 2022 já está próximo do formato final desejado, mas vamos manter uma convenção única de nomes para que os três anos possam ser combinados sem dificuldade.
No arquivo de 2023, alguns nomes já estão próximos do padrão que queremos, mas ainda precisamos harmonizar as categorias de tratamento e os nomes dos locais.
dados_2023 <- dados_2023_bruto |>rename(yld = yield ) |>mutate(trt =case_match( trt,"ctrl"~"Control","fung"~"Fungicide",.default = trt ),site =case_match( site,"Uberlândia"~"Uberlandia",.default = site ) )
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `trt = case_match(trt, "ctrl" ~ "Control", "fung" ~ "Fungicide",
.default = trt)`.
Caused by warning:
! `case_match()` was deprecated in dplyr 1.2.0.
ℹ Please use `recode_values()` instead.
O arquivo de 2024 apresenta dois problemas adicionais: o ano precisa ser criado como coluna, e a variável de severidade aparece com o nome severity_pct. Também existem espaços extras em algumas categorias de tratamento, o que pode causar problemas posteriores.
7. Conferir se os nomes das colunas agora coincidem
Antes de empilhar os arquivos, é importante verificar se os três objetos têm exatamente a mesma estrutura.
names(dados_2022)
[1] "Year" "site" "cult" "rep" "trt" "sev" "yld"
names(dados_2023)
[1] "Year" "site" "cult" "rep" "trt" "sev" "yld"
names(dados_2024)
[1] "Year" "site" "cult" "rep" "trt" "sev" "yld"
Se os nomes coincidirem e estiverem na mesma ordem, podemos unir as tabelas com segurança.
8. Unir os três anos em uma única tabela
A função bind_rows() empilha tabelas por linhas. Ela é usada quando queremos juntar observações de anos, locais ou experimentos diferentes, desde que as colunas representem as mesmas variáveis.
9. Padronizar a tabela empilhada em nomes mais descritivos
Até aqui usamos nomes curtos para facilitar a padronização entre anos. Agora que os dados já estão unidos, podemos renomear as colunas para uma forma mais clara e didática.
10. Adicionar coordenadas geográficas com left_join()
Em muitas análises, precisamos enriquecer uma base principal com informações auxiliares. Isso é feito com a família join. Neste exemplo, vamos acrescentar coordenadas geográficas aos locais do experimento.
Rows: 3 Columns: 3
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): location, state
dbl (1): altitude_m
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
coordenadas_locais
Agora usamos left_join(). Essa função mantém todas as linhas da tabela principal e acrescenta colunas da tabela auxiliar quando existe correspondência entre as chaves informadas.
dados_experimento_geo <- dados_experimento |>left_join(coordenadas_locais, by =c("location"="location"))
Após unir tabelas, é sempre bom verificar se ainda existem valores ausentes nas variáveis principais. Isso ajuda a decidir como resumir e analisar os dados.
Agora vamos comparar as médias de produtividade entre os dois tratamentos: Control e Fungicide.
O teste t para duas amostras independentes é um procedimento inferencial usado para avaliar se a diferença entre duas médias amostrais é grande o suficiente para sugerir que as médias populacionais também diferem.
13.1 Hipóteses do teste
Ao aplicar o teste t, formulamos duas hipóteses:
Hipótese nula (\(H_0\)): as médias populacionais são iguais \[H_0 : \mu_1 = \mu_2\]
Hipótese alternativa (\(H_1\)): as médias populacionais são diferentes \[H_1 : \mu_1 \neq \mu_2\]
No nosso exemplo, queremos comparar a produtividade média entre os tratamentos Control e Fungicide.
13.2 Estatística do teste
A estatística t compara a diferença observada entre médias com a variabilidade dessa diferença.
Na forma clássica do teste com variâncias assumidas como iguais, a estatística é dada por:
\[
t = \frac{\bar{x}_1 - \bar{x}_2}{s_p\sqrt{\frac{1}{n_1} + \frac{1}{n_2}}}
\]
em que:
\(\bar{x}_1\) e \(\bar{x}_2\) são as médias amostrais;
\(n_1\) e \(n_2\) são os tamanhos das amostras;
\(s_p\) é o desvio-padrão combinado (pooled standard deviation), calculado por:
\(s_1^2\) e \(s_2^2\) são as variâncias amostrais dos dois grupos.
Na prática, o R calcula automaticamente a estatística t e o p-valor quando usamos t.test(). O mais importante neste momento é entender a lógica: quanto maior a diferença entre médias em relação à variabilidade dos dados, mais extremo tende a ser o valor de t.
13.3 Ajustar o teste no R
Vamos aplicar o teste t à produtividade.
teste_t_yield <-t.test(yield_kg_ha ~ treatment, data = dados_experimento_geo)teste_t_yield
Welch Two Sample t-test
data: yield_kg_ha by treatment
t = -15.737, df = 20.762, p-value = 5.186e-13
alternative hypothesis: true difference in means between group Control and group Fungicide is not equal to 0
95 percent confidence interval:
-816.4113 -625.7099
sample estimates:
mean in group Control mean in group Fungicide
3341.667 4062.727
A saída mostra, entre outras informações:
o valor da estatística t;
os graus de liberdade;
o p-valor;
as médias estimadas dos dois grupos.
Também podemos organizar essa saída em formato de tabela com broom::tidy().
O valor de t indica quantos “erros-padrão” separam a diferença observada entre médias do valor esperado sob a hipótese nula. Quanto mais distante de zero estiver esse valor, maior a evidência contra \(H_0\).
O p-valor representa a probabilidade de observar um valor de t tão extremo quanto o obtido, ou mais extremo, caso a hipótese nula seja verdadeira.
Se o p-valor for pequeno, consideramos que os dados fornecem evidência para rejeitar a hipótese nula.
Vamos extrair os valores de interesse para usar nos gráficos seguintes.
16. Construir um gráfico de produtividade anotado com o teste t
Agora que já ajustamos e interpretamos o teste, podemos construir um gráfico de produtividade por tratamento e acrescentar o resultado estatístico ao painel.
A ordem importa: primeiro fazemos a análise, depois usamos seu resultado para enriquecer a visualização.
Esta aula mostrou um fluxo muito comum em análise de dados experimentais:
importar múltiplos arquivos;
inspecionar a estrutura das tabelas;
padronizar nomes e categorias;
unir arquivos anuais;
adicionar informações auxiliares com join;
resumir os dados de forma descritiva;
comparar tratamentos com um teste inferencial;
representar a análise em um gráfico final.
Esse fluxo reforça uma ideia importante: uma boa análise não começa no teste estatístico. Ela começa na organização da base e no entendimento claro do que cada variável representa.
18. Exercícios sugeridos
Calcule a severidade média por local e por tratamento.
Construa um gráfico de severidade por tratamento.
Aplique um teste t para comparar a severidade média entre os tratamentos.
Verifique se o resultado do teste para severidade é consistente com o gráfico.
Modifique o código do gráfico final para anotar o teste da severidade em vez do teste da produtividade.
19. Complemento: Exploração Visual Extra
Uma das grandes vantagens do pacote ggplot2 é a facilidade em criar gráficos que revelam a relação entre as variáveis da nossa base.
19.1. Dispersão entre variáveis
Podemos, por exemplo, visualizar a relação direta entre a Severidade e a Produtividade. Geralmente, esperamos que quanto maior a severidade de uma doença na planta, menor seja a sua produtividade (correlação negativa).
dados_experimento_geo |>ggplot(aes(x = severity, y = yield_kg_ha)) +geom_point(alpha =0.6) +theme_minimal() +labs(x ="Severidade (%)",y ="Produtividade (kg/ha)",title ="Relação entre Severidade e Produtividade" )
Warning: Removed 3 rows containing missing values or values outside the scale range
(`geom_point()`).
19.2. Painéis múltiplos (facet_grid)
Para análises de múltiplos anos e locais, podemos dividir nossos gráficos em facetas (painéis). O código abaixo permite visualizar se o efeito dos tratamentos na severidade muda a depender do Ano e da Localidade.
dados_experimento_geo |>ggplot(aes(x = treatment, y = severity, color = treatment)) +geom_jitter(width =0.1, alpha =0.7) +facet_grid(year ~ location) +# Separa as linhas por Ano e colunas por Localtheme_bw() +labs(x ="Tratamento",y ="Severidade (%)",title ="Severidade sob Tratamentos (Por Ano e Local)" ) +theme(legend.position ="none")
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
20. Bônus: Trabalhando com Dados Espaciais e Mapas
Como importamos dados geográficos no Passo 10 (location_info.csv), podemos produzir mapas diretamente no R. Isso é excelente para publicações científicas e apresentações de projeto, indicando exatamente onde as amostras ou parcelas estão localizadas.
Para trabalhar com mapas no R, utilizamos o formato padrão sf (Simple Features) e pacotes auxiliares de dados como o rnaturalearth.
20.1. Carregando pacotes essenciais para mapas
library(sf) # O pacote chave para trabalhar com objetos espaciaislibrary(rnaturalearth) # Disponibiliza mapas mundiais prontos (shapes)library(rnaturalearthdata) library(ggthemes) # Para temas específicos de mapaslibrary(ggrepel) # Para evitar que as legendas de texto fiquem sobrepostaslibrary(ggspatial) # Adiciona itens cartográficos (ex: flecha de norte e escala)
20.2. Construindo um Mapa da Etiópia (Exemplo didático)
Vamos criar o mapa de uma localização específica. Para isso, importamos uma base global que contém a localização de diversas fazendas de café acometidas por ferrugem.
dados_cafe <-gsheet2tbl("[https://docs.google.com/spreadsheets/d/1bq2N19DcZdtax2fQW9OHSGMR0X2__Z9T/edit?gid=1871397229#gid=1871397229](https://docs.google.com/spreadsheets/d/1bq2N19DcZdtax2fQW9OHSGMR0X2__Z9T/edit?gid=1871397229#gid=1871397229)")# 1. Faz o download do formato vetorial ('sf') do país desejado.ethiopia_map <-ne_countries(scale ="medium", country ="ethiopia", returnclass ="sf")# 2. Plota o shape do país e os pontos de coordenadas acima dele.ggplot() +geom_sf(data = ethiopia_map, fill ="antiquewhite", color ="darkgrey") +geom_point(data = dados_cafe, aes(x = lon, y = lat), color ="brown", size =2, alpha =0.7) +theme_minimal() +labs(title ="Localização das Fazendas de Café - Etiópia",subtitle ="Dados de Coffee Rust",x ="Longitude",y ="Latitude" ) +coord_sf() # Exclusivo para garantir que a proporção geográfica do mapa seja correta
20.3. Desenhando o Mapa do Brasil com Capitais
Também podemos construir mapas estaduais usando o R! Para o Brasil, precisaremos baixar detalhes da subunidade (estados). Criaremos um data frame manual com as coordenadas das capitais e traçaremos o país inteiro.
Dica: Caso dê erro de pacote ao renderizar, rode install.packages("rnaturalearthhires", repos = "https://ropensci.r-universe.dev") no console antes de continuar.
# Criando o dataframe com todas as capitais brasileirascoordenadas <-data.frame(site =c("Aracaju", "Belém", "Belo Horizonte", "Boa Vista", "Brasília", "Campo Grande", "Cuiabá", "Curitiba", "Florianópolis", "Fortaleza", "Goiânia", "João Pessoa", "Macapá", "Maceió", "Manaus", "Natal", "Palmas", "Porto Alegre", "Porto Velho", "Recife", "Rio Branco", "Rio de Janeiro", "Salvador", "São Luís", "São Paulo", "Teresina", "Vitória" ),lat =c(-10.9472, -1.4558, -19.9167, 2.8235, -15.7942, -20.4427, -15.6010, -25.4284, -27.5969, -3.7319, -16.6869, -7.1153, 0.0347, -9.6662, -3.1190, -5.7944, -10.2128, -30.0346, -8.7612, -8.0578, -9.9747, -22.9068, -12.9714, -2.5307, -23.5505, -5.0892, -20.3155 ),lon =c(-37.0731, -48.4902, -43.9345, -60.6758, -47.8822, -54.6464, -56.0974, -49.2733, -48.5495, -38.5267, -49.2648, -34.8611, -51.0666, -35.7353, -60.0217, -35.2110, -48.3243, -51.2177, -63.9039, -34.8778, -67.8101, -43.1729, -38.5014, -44.3068, -46.6333, -42.8016, -40.3128 ))# 1. Faz download da divisão estadual brasileirabrazil_map <-ne_states(country ="brazil", returnclass ="sf")# 2. Plotagem do mapa e customizaçãoggplot() +geom_sf(data = brazil_map, fill ="#f4f4f4", color ="darkgray") +# Adiciona as coordenadas no mapa (geom_point mapeia os pontos x/y)geom_point(data = coordenadas, aes(x = lon, y = lat), color ="darkred", size =1.5) +# Adiciona o nome das capitais sem que os textos se cruzem ou engulam os pontosgeom_text_repel(data = coordenadas, aes(x = lon, y = lat, label = site), size =2.5, fontface ="bold") +# Adiciona estilo de mapa, flecha do norteannotation_north_arrow(location ="bl", which_north ="true", style = north_arrow_fancy_orienteering) +theme_bw() +labs(title ="Mapa das Capitais do Brasil",x ="Longitude",y ="Latitude" )