Organizar Dados em Tabelas

Desenho Experimental e Análise Avançada de Dados Ecológicos

João O. Santos

ISPA

Que Peixes Conservamos?

id tank species length_cm
1 A seabream 12.1
2 A seabream 14.3
3 A seabass 9.8

Exemplo didáctico: peixes medidos em dois tanques.

Queremos os peixes do tanque A com mais de 12 cm.

Uma Linha, Um Registo

Registo Marca do peixe Visita
1 P17 1
2 P23 1
3 P17 2
4 P23 2

4 observações · 2 peixes

O número do registo não identifica uma nova unidade independente.

Colunas e Variáveis

species · length_cm · tank

Cada coluna guarda uma variável; cada linha guarda um registo.

Peixes distintos podem partilhar a espécie. Partilhar um tanque também pode criar dependência.

Preparar o Script

library(dplyr)

Carregamos o pacote uma vez, no início.

Se faltar, instalem-no na consola, fora do script.

Criar Uma Tabela

fish <- data.frame(
  id = 1:6,
  tank = c("A", "A", "A", "B", "B", "B"),
  species = c("seabream", "seabream", "seabass",
              "seabass", "seabream", "seabass"),
  length_cm = c(12.1, 14.3, 9.8, 16.0, 11.5, 13.2)
)

Quatro vectores, cada um com seis elementos: os mesmos seis registos.

Primeiro: Inspeccionar

dim(fish)
[1] 6 4
names(fish)
[1] "id"        "tank"      "species"   "length_cm"
head(fish, 3)
  id tank  species length_cm
1  1    A seabream      12.1
2  2    A seabream      14.3
3  3    A  seabass       9.8

linhas × colunas → nomes → primeiras linhas

Ler a Estrutura

str(fish)
'data.frame':   6 obs. of  4 variables:
 $ id       : int  1 2 3 4 5 6
 $ tank     : chr  "A" "A" "A" "B" ...
 $ species  : chr  "seabream" "seabream" "seabass" "seabass" ...
 $ length_cm: num  12.1 14.3 9.8 16 11.5 13.2

Confirmem: length_cm é numérica?

Linhas ou Colunas?

Pergunta Operação O que muda?
Que peixes? filter() Linhas
Que variáveis? select() Colunas

Começamos por escolher os peixes.

Uma Condição

fish$length_cm > 12
[1]  TRUE  TRUE FALSE  TRUE FALSE  TRUE

TRUE · FALSE · um valor por linha

Quantos TRUE? Que identificadores esperamos conservar?

filter(): Escolher Linhas

filter(fish, length_cm > 12)
  id tank  species length_cm
1  1    A seabream      12.1
2  2    A seabream      14.3
3  4    B  seabass      16.0
4  6    B  seabass      13.2

4 linhas · as mesmas 4 colunas

Dentro de filter(), escrevemos length_cm sem repetir fish$.

Duas Condições

filter(fish, tank == "A", length_cm > 12)
  id tank  species length_cm
1  1    A seabream      12.1
2  2    A seabream      14.3

Vírgula: ambas têm de ser verdadeiras. Ficamos com os peixes 1 e 2.

== compara valores; = nomeia argumentos.

select(): Escolher Colunas

select(fish, id, length_cm)
  id length_cm
1  1      12.1
2  2      14.3
3  3       9.8
4  4      16.0
5  5      11.5
6  6      13.2

6 linhas · 2 colunas

Escolher variáveis não exclui peixes.

Encadear com |>

selected <- fish |>
  filter(tank == "A", length_cm > 12) |>
  select(id, length_cm)
selected
  id length_cm
1  1      12.1
2  2      14.3

partir de fish → escolher peixes → escolher variáveis

|> passa o resultado ao primeiro argumento da função seguinte.

mutate(): Criar Uma Coluna

prepared <- fish |>
  mutate(length_m = length_cm / 100)
select(prepared, id, length_cm, length_m)
  id length_cm length_m
1  1      12.1    0.121
2  2      14.3    0.143
3  3       9.8    0.098
4  4      16.0    0.160
5  5      11.5    0.115
6  6      13.2    0.132

Mesmos seis registos. Agora, duas unidades de comprimento.

Verificar o Resultado

dim(fish)
[1] 6 4
dim(prepared)
[1] 6 5
summary(prepared$length_m)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0980  0.1165  0.1265  0.1282  0.1403  0.1600 

6 × 4 → 6 × 5 · Os metros correspondem aos centímetros / 100?

fish conserva os dados de partida.

Uma Pequena Adaptação

3 minutos · em pares

Adaptem o encadeamento para obter:

  • peixes do tanque B, com menos de 14 cm;
  • apenas id e o comprimento em metros.

Antes de executar: quantas linhas e colunas esperam?

Também Há Tibbles

fish_tbl <- as_tibble(fish)
class(fish_tbl)
[1] "tbl_df"     "tbl"        "data.frame"

Um tibble é uma forma de data.frame.

filter(), select() e mutate() aceitam ambos.

Tabela ou Vector?

class(fish[, "length_cm"])
[1] "numeric"
class(fish_tbl[, "length_cm"])
[1] "tbl_df"     "tbl"        "data.frame"
fish$length_cm
[1] 12.1 14.3  9.8 16.0 11.5 13.2

Uma coluna com [ pode simplificar o data.frame para vector.

$ pede o vector em ambos; select() conserva uma tabela.

Reconhecer Base R

fish[1:3, c("id", "length_cm")]
  id length_cm
1  1      12.1
2  2      14.3
3  3       9.8

[linhas, colunas] · A vírgula separa as duas decisões.

Útil para ler código existente e localizar células.

Reconhecer Um Filtro

fish[fish$tank == "A", c("id", "length_cm")]

O mesmo pedido em dplyr:

fish |> filter(tank == "A") |> select(id, length_cm)
  id length_cm
1  1      12.1
2  2      14.3
3  3       9.8

Contar Antes de Concluir

table(fish$tank, fish$species)
   
    seabass seabream
  A       1        2
  B       2        1

Um número por célula. Não um número por réplica, a menos que cada linha seja uma réplica.

Juntar por Chave

tanks <- data.frame(
  tank = c("A", "B"),
  temperature_C = c(18.5, 21.0)
)
combined <- merge(fish, tanks, by = "tank")
select(combined, id, tank, temperature_C)
  id tank temperature_C
1  1    A          18.5
2  2    A          18.5
3  3    A          18.5
4  4    B          21.0
5  5    B          21.0
6  6    B          21.0

A chave tank associa cada peixe à temperatura do seu tanque.

Posição Não É Chave

mesma ordem ≠ mesma unidade

nrow(fish)
[1] 6
nrow(combined)
[1] 6
table(tanks$tank)

A B 
1 1 

Seis linhas antes e depois; uma correspondência por tanque em tanks.

Escolher Um Ficheiro

getwd()
file.choose()

No Windows, escolham o CSV na janela. Copiem o caminho devolvido para o script.

Caminhos relativos são interpretados a partir de getwd().

Exercício: Qualidade da Água

20 minutos · pares · Descarregar o CSV

path <- "C:/.../WaterQualityTesting.csv"
file.exists(path)
water <- read.csv(path)
names(water) <- c("Sample", "pH", "Temp", "NTU",
                  "Oxygen", "Conductivity")

Inspeccionem com dim(), names() e str().

Adaptar o Workflow

Com water, criem uma nova tabela usando |>:

  1. Conservem os registos com Oxygen > 8.
  2. Criem oxygen_ref_pct = 100 * Oxygen / 8.
  3. Conservem Sample, Oxygen e oxygen_ref_pct.
  4. Verifiquem dimensões e valores. water mudou?

8 mg/L é uma referência didáctica, não saturação nem limiar ecológico.

O Que Representa Uma Linha?

Sample identifica um registo. E a unidade ecológica?

Que metadados faltam para reconhecer o local, o momento e eventuais revisitas?

Exercício: Tanques

10 minutos · individual, depois comparem

  1. Contem os peixes por tanque e espécie.
  2. Juntem fish e tanks pela chave tank.
  3. Verifiquem as seis linhas e a temperatura dos peixes 1 e 4.

Se o número de linhas mudar, parem e encontrem a razão.

Quando a Tabela Engana

valores em falta · espécies escritas de duas maneiras · linhas duplicadas

Estes problemas ficam para a limpeza de dados. Hoje basta reconhecê-los.

Guardar Uma Cópia

file.choose(new = TRUE)
output_path <- "C:/.../water_selected.csv"
write.csv(water_selected, output_path, row.names = FALSE)
file.exists(output_path)

No Windows, escolham o destino na consola e copiem o caminho para o script.

Antes de Continuar

  • A tabela tem a forma que esperávamos?
  • Cada linha continua a guardar o mesmo registo?
  • Os tipos e as unidades estão certos?

Para Voltar a Consultar

Introdução aos data.frames

Exercícios de Adaptação

Próximo passo: Importação e Exportação de Dados

Voltar às aulas DEAADE