Curso de pós-graduação: Introdução ao ambiente R (módulo 3, parte I)

Universidade Federal de Pelotas. Departamento de Ecologia, Biologia e Genética

Modified

Friday, June 21, 2024

Nota 1: este documento contém o material do módulo 3 (parte I) do curso ministrado aos alunos do Programa de Pós-Graduação em Biodiversidade Animal (junho de 2024). O curso foi dirigido pelo Dr. Sebastián Sendoya e apoiado por mim (especificamente fui responsável por este módulo).

Nota 2: até a data em que escrevi o documento, meu português, com alguma sorte, era suficiente para comprar comida em um restaurante sem falhar na tentativa (na maioria das vezes). Todos os erros gramaticais e outras aberrações são culpa minha e do Google Translate.

1 Objetivos

  1. Conhecer as principais estruturas de dados em R e a sintaxe da linguagem para indexar seus elementos (Parte I).

  2. Relacionar o desenho amostral com os principais formatos para tabulação dos dados e sua exploração (Parte II).

Após concluir o módulo você poderá: (i) reconhecer e manipular os principais tipos de objetos que R usa para armazenar dados, (ii) tabular os dados do seu trabalho de acordo com o design do seu experimento, e ( iii) ) combinar i e ii para realizar uma exploração deles.

2 Estruturas de dados e indexação

Com base na minha própria experiência e na da maioria dos meus colegas —- biólogos/ecologistas com pouco ou nenhum treinamento formal em programação, uma compreensão superficial da forma como vetores, matrizes, arrays, data frames (tibbles) ou listas são indexados, retarda a curva de aprendizado e o uso de estruturas de programação mais complexas. É por isso que neste módulo decidi enfatizar a indexação como base fundamental para a compreensão da linguagem.

Indexação refere-se às regras (ou sintaxe, se você quiser ser mais técnico) que R usa para extrair ou isolar informações de cada estrutura de dados. Essas regras são baseadas nos atributos do objeto, além da posição e/ou condições dos elementos que ele contém. R usa três símbolos fundamentais e combinações deles para indexar qualquer estrutura de dados: [], [[]] e $. Vamos contextualizar o que foi dito acima, começando com a estrutura básica que R usa para armazenar dados, vetores.

3 Vetores

Vetores, e realmente qualquer estrutura de dados, têm três atributos: nomes, tamanho (ou seja, uma ou n dimensões) e classe (ou tipo). R possui funções para explorar cada uma delas:

  • Nomes: names(), colnames(), rownames(), dimnames()

  • Tamanho: length(), ncol(), nrow(), dim()

  • Classe: class(), str() –na verdade fornece informações sobre todos os atributos–, is.* (funções como is.numeric(), is.logical(), is.array()…)

Vamos criar o vetor v1 contendo os números de 1 a 10 e ver como algumas dessas funções se aplicam

set.seed(5)
v1 <- sample(1:10)
v1
 [1]  2  9  7  3  1  6  5 10  4  8

Sabemos que é um objeto com 10 elementos, nós o criamos, mas podemos confirmar:

length(v1)
[1] 10

e seus nomes?

names(v1)
NULL

R retorna NULL –pode ser entendido como nada, um conjunto vazio– já que v1 ainda não tem nomes.

Vamos atribuir nomes a cada elemento v1

names(v1) <- c('a', 'b', 'c', 'd', 'e', 
               'f', 'g', 'h', 'i', 'j')
names(v1)
 [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"

Agora vamos dar uma olhada nos atributos de v1

str(v1)
 Named int [1:10] 2 9 7 3 1 6 5 10 4 8
 - attr(*, "names")= chr [1:10] "a" "b" "c" "d" ...

A primeira linha indica que v1 é um vetor de inteiros (int), com comprimento 10 ([1:10]) e nomes (attr(*, "names")), os quais são de tipo chr.

vamos ver:

class(names(v1))
[1] "character"

Como mencionei anteriormente, podemos usar esses atributos para indexar os itens em v1.

Vamos indexar por posição primeiro

. Neste caso estamos armazenado no quinto elemento do vetor v1

v1[5]
e 
1 

Observe que usamos [] para nos referir aos elementos contidos em v1 e 5 se refere à posição. Além disso, observe que v1[5] retorna não apenas o elemento da quinta posição, mas também seu nome. Ou seja, quando indexamos um objeto com [] o resultado é um subconjunto que herda atributos do objeto do qual foi extraído, neste caso o nome do quinto elemento (e). Agora vamos indexar o mesmo elemento, mas usando [[]].

v1[[5]]
[1] 1

R retorna o elemento sem quaisquer outros atributos. Isso ocorre porque [[]] é usado para enfatizar o objeto que você deseja indexar, sem considerar seus atributos no objeto (v1 neste caso). Parece uma diferença trivial entre as duas notações, porém quando estamos automatizando processos ou programando funções, [[]] é uma opção mais segura para indexar um único elemento. Voltaremos a este tópico quando examinarmos a indexação de data frames (tibbles) e listas.

Agora vamos indexar mais de um elemento pela sua posição, vejamos três opções:

elementos consecutivos:

v1[3:7]
c d e f g 
7 3 1 6 5 

elementos não consecutivos:

v1[c(1, 3:5, 8)]
 a  c  d  e  h 
 2  7  3  1 10 

Neste caso devemos utilizar a função concatenar (c()) para agrupar as posições dos elementos que precisamos indexar. Fazemos isso porque o “,” na notação de indexação é um caracter reservado para denotar mais de uma dimensão e, lembre-se, os vetores possuem apenas uma. Vamos ver:

v1[1, 3:5, 8]
Error in v1[1, 3:5, 8]: incorrect number of dimensions

A mensagem Error in v1[1, 3:5, 8] : incorrect number of dimensions é autoexplicativa.

Agora vamos indexar v1 usando os nomes de seus elementos:

v1['a']
a 
2 
v1[c('a', 'f')]
a f 
2 6 

Observe que, em essência, estamos usando um vetor do tipo caractere (os nomes de v1) para indexar os valores hospedados em v1. Então também poderíamos salvar os nomes de v1 em um novo objeto e usá-lo para indexar v1. Vamos ver:

nomes_v1 <- names(v1)

v1[nomes_v1[c(3:5, 9)]]
c d e i 
7 3 1 4 

Este exemplo nos ajuda a ilustrar dois pontos-chave para entender como o código R funciona:

  1. Podemos usar objetos para indexar outros objetos.

  2. R executa o código de maneira aninhada. Ou seja, no código v1[nomes_v1[c(3:5, 9)]] a ordem de execução foi: (i) a função c() agrupou os números 3 a 5 e 9 (ou seja, c (3:5, 9)):

c(3:5, 9)
[1] 3 4 5 9
  1. R usou esta sequência de números para indexar o vetor nomes_v1
nomes_v1[c(3:5, 9)]
[1] "c" "d" "e" "i"
  1. posteriormente, usou o vetor ['c', 'd', 'e', 'i'] (ou c('c', 'd', 'e', 'i' ) na sintaxe de R) para o índice v1:
v1[nomes_v1[c(3:5, 9)]]
c d e i 
7 3 1 4 

Esta é uma das várias diferenças entre R e outras linguagens de programação (por exemplo, Python, JavaScript), onde o código é lido e executado da esquerda para a direita. Teremos a oportunidade de destacar outra diferença fundamental.

O último método de indexação é baseado no uso de operadores booleanos

fazer “perguntas” com respostas binárias. Ou seja, usamos a sintaxe R para consultar se os elementos contidos em um objeto atendem ou não a uma determinada condição.

Primeiro vamos dar uma olhada nos principais operadores booleanos:

  • X == Y: igual
  • X > Y: maior que
  • X < Y: menor que
  • X >= Y: maior ou igual
  • X <= Y: menor ou igual
  • X | Y: X ou Y
  • X & Y: X e Y
  • !X: negação (ou seja, NÃO X)

Podemos usá-los para fazer perguntas lógicas (ou seja, SIM ou NÃO, TRUE ou FALSE), por exemplo:

5 > v1
    a     b     c     d     e     f     g     h     i     j 
 TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE  TRUE FALSE 

O resultado é um vetor booleano onde cada elemento corresponde às questões 5 > V1[1], …, 5 > V1[10]. Ou seja, R itera sobre cada elemento de v1 para realizar este ou qualquer tipo de operação. Isso é conhecido como vetorização e, novamente, diferencia R de linguagens como Python.

Vamos usar esse vetor booleano para indexar v1, temos duas alternativas:

b_v1 <- 5 > v1
v1[b_v1]
a d e i 
2 3 1 4 

o

v1[5 > v1]
a d e i 
2 3 1 4 

O resultado é um subconjunto contendo os elementos de v1 maiores que 5 (ou seja, aquele que é TRUE). Podemos realizar operações mais complexas, combinando diferentes tipos de operadores booleanos

v1[!(v1 <= 10 & v1 > 4 | v1 >= 3)]
a e 
2 1 

O que foi dito acima é a base para a compreensão da indexação de vetores e estabelece as bases para uma indexação de objetos mais complexa. Isto é vantajoso, pois as demais estruturas são, em essência, vetores com duas ou mais dimensões (matrizes e arrays) ou coleções de vetores agrupados em um único objeto (data frames, tibbles e lists `).

4 Matrizes

Matrizes são vetores com duas dimensões, linhas e colunas. Convenientemente, R possui a função matrix() para gerar as matrizes, seus argumentos são explicados no código a seguir:

v2 <- rpois(6*5, 10)
m <- matrix(data = v2, # dados a incluir en la matriz
            ncol = 5, # número de colunas
            nrow = 6, # número de linhas
            byrow = F) # como os elementos em v2 serão organizados na matriz

Vamos explorar o vetor v2 e ver como ele foi organizado em formato matriz

v2
 [1]  9 10  7 10 13  7  9 15 15 11 12 12 14 12 12  9 14 14  7  6 13 17 13 14 13
[26] 15  8 12 16  8
m
     [,1] [,2] [,3] [,4] [,5]
[1,]    9    9   14    7   13
[2,]   10   15   12    6   15
[3,]    7   15   12   13    8
[4,]   10   11    9   17   12
[5,]   13   12   14   13   16
[6,]    7   12   14   14    8

Bem, m contém um matriz de \(6 \times 5\). Podemos explorar seus atributos com as funções length(), ncol(), nrow(), dim(), colnames(), rownames(), dimnames() e str(). Primeiro, vamos atribuir nomes às linhas e colunas e depois continuar com a indexação.

rownames(m) <- paste('F', 1:nrow(m), sep = '')
colnames(m) <- paste('C', 1:ncol(m), sep = '')
m
   C1 C2 C3 C4 C5
F1  9  9 14  7 13
F2 10 15 12  6 15
F3  7 15 12 13  8
F4 10 11  9 17 12
F5 13 12 14 13 16
F6  7 12 14 14  8

Como você pode imaginar, atribuir nomes a linhas e colunas em uma matriz segue o mesmo procedimento que nomear vetores. As funções para fazer isso são diferentes, mas possuem um nome suficientemente explicativo para entender como utilizá-las.

Já mencionei que na notação de indexação R usa o caractere , para denotar diferentes dimensões. No caso de objetos com estrutura tabular, o , é utilizado para separar os índices de linhas e colunas: matriz[linha, coluna]. Essa é a única diferença com a indexação vetorial, o resto é basicamente igual ao que apresentei para vetores.

Vejamos alguns exemplos:

# indexação por posição

m[1:4, ]
   C1 C2 C3 C4 C5
F1  9  9 14  7 13
F2 10 15 12  6 15
F3  7 15 12 13  8
F4 10 11  9 17 12
m[3:nrow(m), c(1:3, 5)]
   C1 C2 C3 C5
F3  7 15 12  8
F4 10 11  9 12
F5 13 12 14 16
F6  7 12 14  8
# indexação por nomes

nom_fila <- rownames(m)
nom_col <- colnames(m)

m[nom_fila[c(1:3, nrow(m))], nom_col[ncol(m)]]
F1 F2 F3 F6 
13 15  8  8 
# por condição

m[m[, 1] > 10, 3:5]
C3 C4 C5 
14 13 16 
# combinando diferentes métodos

m[nom_fila[1:3], m[1, ] < 10]
   C1 C2 C4
F1  9  9  7
F2 10 15  6
F3  7 15 13

Indexei m de uma forma deliberadamente confusa. Se não deu para entender, por favor, volte um passo atrás e releia a seção de vetores. Se conseguiu entender, podemos continuar e observar várias coisas: (i) Não indicar nenhum índice no espaço de linhas ou colunas significa, obviamente, que nada está indexado naquela dimensão. Por exemplo, m[1:3, ] pode ser lido como um subconjunto de m que preserva as linhas 1 a 3 e todas colunas de m. O mesmo se aplicaria a um espaço em branco à esquerda de ,. (ii) Quando a indexação resulta em elementos de uma única dimensão (linha ou coluna), R por defeito reduz a matriz em um vetor. Vamos ver:

Se indexarmos a primeira linha da matriz, teremos:

is.vector(m[1, ])
[1] TRUE
is.matrix(m[1, ])
[1] FALSE

Para evitar essa redução na dimensionalidade da matriz, usamos o argumento drop = FALSE entre colchetes da indexação. Vamos ver e comparar o resultado no console:

m[1, ] # vetor (1 dimensão)
C1 C2 C3 C4 C5 
 9  9 14  7 13 
m[1, , drop = FALSE] # matriz de 1 x 5
   C1 C2 C3 C4 C5
F1  9  9 14  7 13

drop = FALSE controla que o objeto indexado não é reduzido a uma única dimensão (ou seja, a um vetor), drop = TRUE indica o oposto. O argumento drop opera da mesma maneira em objetos tabulares como data frames e tibbles, e suas implicações não são triviais quando programamos funções e automatizamos processos.

É necessário lembrar que matrizes são vetores bidimensionais. Logo, seu comprimento (length(m)) é igual ao número de células na matriz:

length(m)
[1] 30

o

m[1:length(m)]
 [1]  9 10  7 10 13  7  9 15 15 11 12 12 14 12 12  9 14 14  7  6 13 17 13 14 13
[26] 15  8 12 16  8

Vamos continuar com outros tipos de matrizes.

5 Array

Arrays são matrizes de \(i \times j\) organizadas em N dimensões. As dimensões implicam basicamente uma série de matrizes aninhadas hierarquicamente, onde R usa , para denotar cada aninhamento na indexação. O resto da sintaxe é a mesma das matrizes: array[linha, coluna, aninhamento 1, ... , aninhamento N]

Vamos criar o vetor v3 e ver como agrupá-lo com a função array().

set.seed(5)
v3 <- rnbinom(6*5*2*2*3, mu = 20, size = 3) 
array1 <- 
  array(data = v3, 
        dim = c(6, 5, 2, 2, 3), 
        dimnames = 
          list(
            paste('F', 1:6, sep = ''), 
            paste('C', 1:5, sep = ''), 
            c('ani_1a', 'ani_1b'),
            c('ani_2a', 'ani_2b'), 
            c('ani_3a', 'ani_3b', 'ani_3c')))

array1
, , ani_1a, ani_2a, ani_3a

   C1 C2 C3 C4 C5
F1  7 21 15 13 13
F2  8 32 26 16 15
F3 35 35 37 25 11
F4 14 29  9 14 18
F5 23 21  3 10  9
F6  9 10 17 10 16

, , ani_1b, ani_2a, ani_3a

   C1 C2 C3 C4 C5
F1 15 19 17 20  0
F2 24 12 23  4 24
F3 25 16  6  6  5
F4 10 25 16 32 15
F5  4 18 15 30 13
F6 32 13 15 17 21

, , ani_1a, ani_2b, ani_3a

   C1 C2 C3 C4 C5
F1 15 26 17 15 17
F2 18 28 19 15 21
F3 41 14  7 18 13
F4 33 16 12 14 35
F5 46  9 53  6 22
F6  7 18 27 15  8

, , ani_1b, ani_2b, ani_3a

   C1 C2 C3 C4 C5
F1 26 31 29  9  8
F2 26 34 25 14 22
F3  6  8 27 45  6
F4 25 14 11 15  5
F5 28 17 11  8 14
F6 43 11 45 17 28

, , ani_1a, ani_2a, ani_3b

   C1 C2 C3 C4 C5
F1 19 34 21 47 26
F2 32 22 14 13  8
F3 28  3 15 15 20
F4 59 45 39 43 42
F5 61  8 15 11 43
F6 18 20 21  9 32

, , ani_1b, ani_2a, ani_3b

   C1 C2 C3 C4 C5
F1 11  8  3 16 16
F2 15 16  9  7 33
F3 26  7 17 63 27
F4 17 16 23 32 11
F5  2 43 13  8 26
F6 28 13 25 46 14

, , ani_1a, ani_2b, ani_3b

   C1 C2 C3 C4 C5
F1 19  9 13 12 12
F2 18 11  7 34 20
F3 20 11 15 10 28
F4 10 27 16 10 21
F5 11 10 29 28 19
F6 28 37 23 13 25

, , ani_1b, ani_2b, ani_3b

   C1 C2 C3 C4 C5
F1 20 16 33 31 43
F2 13 29 13 33  1
F3  9 40 21 33 18
F4  7 25 21 30 14
F5  7 18  9 24 21
F6 22 39 18 48  6

, , ani_1a, ani_2a, ani_3c

   C1 C2 C3 C4 C5
F1  8  6 27 14 10
F2 27  9 14 25 12
F3 14  8 12  9  9
F4 28 11  6 30 22
F5  3 13 15 13  7
F6 29  7  3 20 33

, , ani_1b, ani_2a, ani_3c

   C1 C2 C3 C4 C5
F1 35 33 19  1 16
F2  8 39 45 11 18
F3  2 39 17 34 33
F4 17 42 34 19 28
F5  6  4 24 13 11
F6 14 28 37 25 22

, , ani_1a, ani_2b, ani_3c

   C1 C2 C3 C4 C5
F1 35 12 16 17  5
F2 16 17 70 19 26
F3  7 20  7 15 40
F4 35 21 53 13 15
F5 21 22  8  4 16
F6  9 38  8 20 10

, , ani_1b, ani_2b, ani_3c

   C1 C2 C3 C4 C5
F1 52 21  6  6 33
F2 22 24 10 23 10
F3 34 10 17 54 36
F4 11 11 22 15  7
F5 30 22  9  6 14
F6 10 35 34  6 33

A indexação de array1 é fundamentalmente a mesma de uma matriz. Só precisamos considerar as dimensões (ou aninhamento extra). Por exemplo, vamos indexar as matrizes contidas em nesting 1a (ani_1a):

array1[, , 'ani_1a', ,]
, , ani_2a, ani_3a

   C1 C2 C3 C4 C5
F1  7 21 15 13 13
F2  8 32 26 16 15
F3 35 35 37 25 11
F4 14 29  9 14 18
F5 23 21  3 10  9
F6  9 10 17 10 16

, , ani_2b, ani_3a

   C1 C2 C3 C4 C5
F1 15 26 17 15 17
F2 18 28 19 15 21
F3 41 14  7 18 13
F4 33 16 12 14 35
F5 46  9 53  6 22
F6  7 18 27 15  8

, , ani_2a, ani_3b

   C1 C2 C3 C4 C5
F1 19 34 21 47 26
F2 32 22 14 13  8
F3 28  3 15 15 20
F4 59 45 39 43 42
F5 61  8 15 11 43
F6 18 20 21  9 32

, , ani_2b, ani_3b

   C1 C2 C3 C4 C5
F1 19  9 13 12 12
F2 18 11  7 34 20
F3 20 11 15 10 28
F4 10 27 16 10 21
F5 11 10 29 28 19
F6 28 37 23 13 25

, , ani_2a, ani_3c

   C1 C2 C3 C4 C5
F1  8  6 27 14 10
F2 27  9 14 25 12
F3 14  8 12  9  9
F4 28 11  6 30 22
F5  3 13 15 13  7
F6 29  7  3 20 33

, , ani_2b, ani_3c

   C1 C2 C3 C4 C5
F1 35 12 16 17  5
F2 16 17 70 19 26
F3  7 20  7 15 40
F4 35 21 53 13 15
F5 21 22  8  4 16
F6  9 38  8 20 10

array1[, , 'ani_1a', ] implica uma indexação das matrizes a e b contidas no aninhamento ani_2, além das a, b e c contidas em ani_3. Observe que como ani_3 é o aninhamento superior, ambas as matrizes em ani_2 se repetem nos níveis de ani_3. Enrolado? Provavelmente. Vejamos um esquema do array completo e depois do indexado (array1[, , 'ani_1a', ]):

Array sem indexação

array1[, , 'ani_1a', ] (sombreamento amarelo indica indexação)

Podemos usar a sintaxe do R para continuar indexando diferentes elementos no array. Por exemplo:

array1[4:5, # linhas 4 e 5 
       2:3, # colunas 2 e 3
       1, # aninhamento 1 
       2, # matriz b
       2:3] # matriz b e c
, , ani_3b

   C2 C3
F4 27 16
F5 10 29

, , ani_3c

   C2 C3
F4 21 53
F5 22  8

O esquema de array1[4:5, 2:3, 1, 2, 2:3]

No início indexar arrays é sempre confuso, foi para mim e para a maioria dos colegas com quem trabalhei. A boa notícia é que não é uma estrutura de dados usada com frequência em análises mais básicas. Certamente você ouvirá isso e no momento não o utilizará em suas análises, mas é uma boa ideia conhecê-lo e entender como manipulá-lo. Eu, por exemplo, uso arrays apenas como saída de modelos bayesianos com dezenas de parâmetros. Também conheço pacotes R especializados em análise morfométrica que utilizam esse tipo de estrutura de dados. É útil quando tratamos dados do mesmo tipo com uma estrutura aninhada, por exemplo: imagine um conjunto de dados em que cada linha corresponde a um indivíduo, cada coluna a uma característica funcional, o primeiro aninhamento corresponde a fragmentos florestais, o segundo às paisagens e o terceiro às ecorregiões.

Como mensagem principal, arrays são estruturas para armazenamento de dados tabulares estruturados de forma aninhada. São indexados da mesma forma que as matrizes e , é usado para denotar cada dimensão.

6 Data frames e tibbles

Os data frames e tibbles são estruturas de dados tabulares, cujas colunas podem ser de diferentes tipos. Portanto, são um dos objetos que mais utilizamos em nossas análises. Em geral, são utilizados para armazenar conjuntos de dados relacionais onde as colunas correspondem a variáveis e as linhas correspondem a casos ou observações. Em essência, podemos usar data frames e tibbles de forma intercambiável, porém existem algumas diferenças que valem a pena mencionar:

  • Os data frames vêm por defeito com a instalação do R. Os Tibbles, por outro lado, fazem parte do pacote tibble.

  • A execução de um tibble no console retorna as dez primeiras linhas da tabela e um cabeçalho com o nome e o tipo de variável.

  • Em termos de exibição do console, os tibbles são melhores para aninhar listas em colunas. Isso é particularmente útil quando precisamos iterar seções do conjunto de dados.

  • Lembra do argumento drop? Por defeito, ele está definido como FALSE nos tibbles. Portanto, enquanto data_frame[, 1] resulta em um vetor da coluna 1, tibble[, 1] gera um tibble (ou tabela) de \(N~rows \times 1\).

Nota 1: Pessoalmente prefiro usar tibbles, a visualização é mais confortável. Mas selecionar um ou outro é trivial para o que geralmente fazemos em biologia.

Nota 2: Os exemplos abaixo são generalizáveis para ambos os tipos de objetos, no código apresentarei as duas versões.

A sintaxe para criar os dois tipos de objetos é a mesma, variando apenas no nome da função a ser utilizada. Lembre-se, a única condição é que os vetores (ou seja, colunas) tenham o mesmo comprimento.

tibbles:

tibble(
  var1 = vetor,
  var2 = vetor,
  ...
  var_n = vetor
)

data frames:

data.frame(
  var1 = vetor,
  var2 = vetor,
  ...
  var_n = vetor
)

Ok, vamos gerar nosso primeiro data frame/tibble:

library(tibble)

set.seed(123)
df <- 
  data.frame(
    site = rep(c('bosque1', 'bosque2', 'bosque3'), 
            each = 10),
    trap = rep(c(1, 2, 3, 4, 5), each = 2, length.out = 30), 
    formigas = rnbinom(30, mu = 10, size = 3)
  )

set.seed(123)
tib <- 
  tibble(
    site = rep(c('bosque1', 'bosque2', 'bosque3'), 
            each = 10),
    trap = rep(c(1, 2, 3, 4, 5), each = 2, length.out = 30), 
    formigas = rnbinom(30, mu = 10, size = 3)
  )

Agora vamos ver as diferenças do output no console:

data frame:

df
      site trap formigas
1  bosque1    1        9
2  bosque1    1       18
3  bosque1    2       14
4  bosque1    2        4
5  bosque1    3        9
6  bosque1    3       17
7  bosque1    4       10
8  bosque1    4        9
9  bosque1    5        4
10 bosque1    5        2
11 bosque2    1        4
12 bosque2    1        2
13 bosque2    2       14
14 bosque2    2        1
15 bosque2    3       11
16 bosque2    3        4
17 bosque2    4       10
18 bosque2    4        9
19 bosque2    5       22
20 bosque2    5       17
21 bosque3    1        1
22 bosque3    1       14
23 bosque3    2       11
24 bosque3    2        5
25 bosque3    3       17
26 bosque3    3       11
27 bosque3    4       11
28 bosque3    4       12
29 bosque3    5       10
30 bosque3    5        3

tibble:

tib
# A tibble: 30 × 3
   site     trap formigas
   <chr>   <dbl>    <dbl>
 1 bosque1     1        9
 2 bosque1     1       18
 3 bosque1     2       14
 4 bosque1     2        4
 5 bosque1     3        9
 6 bosque1     3       17
 7 bosque1     4       10
 8 bosque1     4        9
 9 bosque1     5        4
10 bosque1     5        2
# … with 20 more rows

A partir de agora continuarei com os tibbles, tudo que mencionei se aplica exatamente aos data frames.

A indexação dos tibbles é quase igual às das matrizes, quase! A novidade está nas novas formas de indexar as colunas do conjunto de dados. Vejamos seis maneiras de extrair a mesma coluna do objeto tib:

tib[, 1]
# A tibble: 30 × 1
   site   
   <chr>  
 1 bosque1
 2 bosque1
 3 bosque1
 4 bosque1
 5 bosque1
 6 bosque1
 7 bosque1
 8 bosque1
 9 bosque1
10 bosque1
# … with 20 more rows
tib[, 'site']
# A tibble: 30 × 1
   site   
   <chr>  
 1 bosque1
 2 bosque1
 3 bosque1
 4 bosque1
 5 bosque1
 6 bosque1
 7 bosque1
 8 bosque1
 9 bosque1
10 bosque1
# … with 20 more rows
tib[['site']]
 [1] "bosque1" "bosque1" "bosque1" "bosque1" "bosque1" "bosque1" "bosque1"
 [8] "bosque1" "bosque1" "bosque1" "bosque2" "bosque2" "bosque2" "bosque2"
[15] "bosque2" "bosque2" "bosque2" "bosque2" "bosque2" "bosque2" "bosque3"
[22] "bosque3" "bosque3" "bosque3" "bosque3" "bosque3" "bosque3" "bosque3"
[29] "bosque3" "bosque3"
tib[[1]]
 [1] "bosque1" "bosque1" "bosque1" "bosque1" "bosque1" "bosque1" "bosque1"
 [8] "bosque1" "bosque1" "bosque1" "bosque2" "bosque2" "bosque2" "bosque2"
[15] "bosque2" "bosque2" "bosque2" "bosque2" "bosque2" "bosque2" "bosque3"
[22] "bosque3" "bosque3" "bosque3" "bosque3" "bosque3" "bosque3" "bosque3"
[29] "bosque3" "bosque3"
tib[, 1][[1]]
 [1] "bosque1" "bosque1" "bosque1" "bosque1" "bosque1" "bosque1" "bosque1"
 [8] "bosque1" "bosque1" "bosque1" "bosque2" "bosque2" "bosque2" "bosque2"
[15] "bosque2" "bosque2" "bosque2" "bosque2" "bosque2" "bosque2" "bosque3"
[22] "bosque3" "bosque3" "bosque3" "bosque3" "bosque3" "bosque3" "bosque3"
[29] "bosque3" "bosque3"
tib$site
 [1] "bosque1" "bosque1" "bosque1" "bosque1" "bosque1" "bosque1" "bosque1"
 [8] "bosque1" "bosque1" "bosque1" "bosque2" "bosque2" "bosque2" "bosque2"
[15] "bosque2" "bosque2" "bosque2" "bosque2" "bosque2" "bosque2" "bosque3"
[22] "bosque3" "bosque3" "bosque3" "bosque3" "bosque3" "bosque3" "bosque3"
[29] "bosque3" "bosque3"

As formas 2 e 3 são exatamente o que faríamos para extrair as colunas se o tib fosse uma matriz. Na indexação do tibbles o símbolo [[]] é usado para extrair uma coluna por seu nome (formulário 3) ou sua posição (formulários 4 e 5). Vamos fazer uma pausa em tib[, 1][[1]]. Neste caso o código é lido da esquerda para a direita: (i) indexamos a primeira coluna do tib (tib[, 1]), (ii) depois extraímos a primeira coluna em formato de vetor ([[1]]). Esta forma de indexação é semelhante à das listas porque tibbles e data frames são listas de vetores. A sexta maneira de indexar colunas usa o símbolo $ e o nome da coluna.

Veremos a filtragem de data frames ou tibbles e sua exploração na segunda parte do módulo.

7 Listas

As listas são a estrutura de dados mais flexiveis no R. Você pode imaginá-los como caixas onde qualquer coisa pode ser armazenada: vetores, matrizes, arrays, data frames, funções, outras listas… Qualquer objeto. Vamos criar uma lista e partir do básico para entender como indexá-las.

Criamos listas com a função list() -existem outras maneiras, mas por enquanto vamos simplificar.

l1 <- list()

O objeto l1 é uma lista vazia. Agora vamos armazenar diferentes objetos usando o símbolo $:

l1$vector <- v1
l1$matriz <- m
l1$array <- array1
l1$data_frame <- df
l1$tibble <- tib

l1
$vector
 a  b  c  d  e  f  g  h  i  j 
 2  9  7  3  1  6  5 10  4  8 

$matriz
   C1 C2 C3 C4 C5
F1  9  9 14  7 13
F2 10 15 12  6 15
F3  7 15 12 13  8
F4 10 11  9 17 12
F5 13 12 14 13 16
F6  7 12 14 14  8

$array
, , ani_1a, ani_2a, ani_3a

   C1 C2 C3 C4 C5
F1  7 21 15 13 13
F2  8 32 26 16 15
F3 35 35 37 25 11
F4 14 29  9 14 18
F5 23 21  3 10  9
F6  9 10 17 10 16

, , ani_1b, ani_2a, ani_3a

   C1 C2 C3 C4 C5
F1 15 19 17 20  0
F2 24 12 23  4 24
F3 25 16  6  6  5
F4 10 25 16 32 15
F5  4 18 15 30 13
F6 32 13 15 17 21

, , ani_1a, ani_2b, ani_3a

   C1 C2 C3 C4 C5
F1 15 26 17 15 17
F2 18 28 19 15 21
F3 41 14  7 18 13
F4 33 16 12 14 35
F5 46  9 53  6 22
F6  7 18 27 15  8

, , ani_1b, ani_2b, ani_3a

   C1 C2 C3 C4 C5
F1 26 31 29  9  8
F2 26 34 25 14 22
F3  6  8 27 45  6
F4 25 14 11 15  5
F5 28 17 11  8 14
F6 43 11 45 17 28

, , ani_1a, ani_2a, ani_3b

   C1 C2 C3 C4 C5
F1 19 34 21 47 26
F2 32 22 14 13  8
F3 28  3 15 15 20
F4 59 45 39 43 42
F5 61  8 15 11 43
F6 18 20 21  9 32

, , ani_1b, ani_2a, ani_3b

   C1 C2 C3 C4 C5
F1 11  8  3 16 16
F2 15 16  9  7 33
F3 26  7 17 63 27
F4 17 16 23 32 11
F5  2 43 13  8 26
F6 28 13 25 46 14

, , ani_1a, ani_2b, ani_3b

   C1 C2 C3 C4 C5
F1 19  9 13 12 12
F2 18 11  7 34 20
F3 20 11 15 10 28
F4 10 27 16 10 21
F5 11 10 29 28 19
F6 28 37 23 13 25

, , ani_1b, ani_2b, ani_3b

   C1 C2 C3 C4 C5
F1 20 16 33 31 43
F2 13 29 13 33  1
F3  9 40 21 33 18
F4  7 25 21 30 14
F5  7 18  9 24 21
F6 22 39 18 48  6

, , ani_1a, ani_2a, ani_3c

   C1 C2 C3 C4 C5
F1  8  6 27 14 10
F2 27  9 14 25 12
F3 14  8 12  9  9
F4 28 11  6 30 22
F5  3 13 15 13  7
F6 29  7  3 20 33

, , ani_1b, ani_2a, ani_3c

   C1 C2 C3 C4 C5
F1 35 33 19  1 16
F2  8 39 45 11 18
F3  2 39 17 34 33
F4 17 42 34 19 28
F5  6  4 24 13 11
F6 14 28 37 25 22

, , ani_1a, ani_2b, ani_3c

   C1 C2 C3 C4 C5
F1 35 12 16 17  5
F2 16 17 70 19 26
F3  7 20  7 15 40
F4 35 21 53 13 15
F5 21 22  8  4 16
F6  9 38  8 20 10

, , ani_1b, ani_2b, ani_3c

   C1 C2 C3 C4 C5
F1 52 21  6  6 33
F2 22 24 10 23 10
F3 34 10 17 54 36
F4 11 11 22 15  7
F5 30 22  9  6 14
F6 10 35 34  6 33


$data_frame
      site trap formigas
1  bosque1    1        9
2  bosque1    1       18
3  bosque1    2       14
4  bosque1    2        4
5  bosque1    3        9
6  bosque1    3       17
7  bosque1    4       10
8  bosque1    4        9
9  bosque1    5        4
10 bosque1    5        2
11 bosque2    1        4
12 bosque2    1        2
13 bosque2    2       14
14 bosque2    2        1
15 bosque2    3       11
16 bosque2    3        4
17 bosque2    4       10
18 bosque2    4        9
19 bosque2    5       22
20 bosque2    5       17
21 bosque3    1        1
22 bosque3    1       14
23 bosque3    2       11
24 bosque3    2        5
25 bosque3    3       17
26 bosque3    3       11
27 bosque3    4       11
28 bosque3    4       12
29 bosque3    5       10
30 bosque3    5        3

$tibble
# A tibble: 30 × 3
   site     trap formigas
   <chr>   <dbl>    <dbl>
 1 bosque1     1        9
 2 bosque1     1       18
 3 bosque1     2       14
 4 bosque1     2        4
 5 bosque1     3        9
 6 bosque1     3       17
 7 bosque1     4       10
 8 bosque1     4        9
 9 bosque1     5        4
10 bosque1     5        2
# … with 20 more rows

Observe que com o uso de $ armazenamos objetos e seus nomes:

names(l1)
[1] "vector"     "matriz"     "array"      "data_frame" "tibble"    

As listas são uma estrutura de dados com uma única dimensão:

length(l1)
[1] 5

Sua indexação ocorre em duas etapas e segue a mesma sintaxe que utilizamos para vetor:

  1. Usamos nomes ou posições para indexar cada elemento da lista: por ex. lista['nome 1'] - lista[1], lista[c('nome 1', 'nome 2')] - lista[1:2]. Neste ponto devemos prestar especial atenção ao uso de [] ou [[]], vejamos:

indexando um objeto

l1['vector']
$vector
 a  b  c  d  e  f  g  h  i  j 
 2  9  7  3  1  6  5 10  4  8 
l1[1]
$vector
 a  b  c  d  e  f  g  h  i  j 
 2  9  7  3  1  6  5 10  4  8 

indexando dois ou mais objetos

l1[names(l1)[c(1:2, 4)]]
$vector
 a  b  c  d  e  f  g  h  i  j 
 2  9  7  3  1  6  5 10  4  8 

$matriz
   C1 C2 C3 C4 C5
F1  9  9 14  7 13
F2 10 15 12  6 15
F3  7 15 12 13  8
F4 10 11  9 17 12
F5 13 12 14 13 16
F6  7 12 14 14  8

$data_frame
      site trap formigas
1  bosque1    1        9
2  bosque1    1       18
3  bosque1    2       14
4  bosque1    2        4
5  bosque1    3        9
6  bosque1    3       17
7  bosque1    4       10
8  bosque1    4        9
9  bosque1    5        4
10 bosque1    5        2
11 bosque2    1        4
12 bosque2    1        2
13 bosque2    2       14
14 bosque2    2        1
15 bosque2    3       11
16 bosque2    3        4
17 bosque2    4       10
18 bosque2    4        9
19 bosque2    5       22
20 bosque2    5       17
21 bosque3    1        1
22 bosque3    1       14
23 bosque3    2       11
24 bosque3    2        5
25 bosque3    3       17
26 bosque3    3       11
27 bosque3    4       11
28 bosque3    4       12
29 bosque3    5       10
30 bosque3    5        3
l1[c(1:2, 4)]
$vector
 a  b  c  d  e  f  g  h  i  j 
 2  9  7  3  1  6  5 10  4  8 

$matriz
   C1 C2 C3 C4 C5
F1  9  9 14  7 13
F2 10 15 12  6 15
F3  7 15 12 13  8
F4 10 11  9 17 12
F5 13 12 14 13 16
F6  7 12 14 14  8

$data_frame
      site trap formigas
1  bosque1    1        9
2  bosque1    1       18
3  bosque1    2       14
4  bosque1    2        4
5  bosque1    3        9
6  bosque1    3       17
7  bosque1    4       10
8  bosque1    4        9
9  bosque1    5        4
10 bosque1    5        2
11 bosque2    1        4
12 bosque2    1        2
13 bosque2    2       14
14 bosque2    2        1
15 bosque2    3       11
16 bosque2    3        4
17 bosque2    4       10
18 bosque2    4        9
19 bosque2    5       22
20 bosque2    5       17
21 bosque3    1        1
22 bosque3    1       14
23 bosque3    2       11
24 bosque3    2        5
25 bosque3    3       17
26 bosque3    3       11
27 bosque3    4       11
28 bosque3    4       12
29 bosque3    5       10
30 bosque3    5        3

Sabemos que o segundo objeto de l1 é uma matriz Se indexarmos, deveremos ter um objeto com classe matrix, certo? Bem, isto depende:

class(l1[2])
[1] "list"
class(l1[[2]])
[1] "matrix" "array" 

l1[2] resulta em uma lista porque este tipo de indexação envolve um “corte” da lista, não extração do objeto em questão. Em vez disso, l1[[2]] enfatiza a extração do objeto.

A matriz l1[[2]] poderia ser atribuído a um novo objeto e indexado da maneira que já vimos. Mas, se você planeja indexar conjuntamente a lista e o objeto que ela contém, passamos para a segunda etapa.

  1. Indexamos o objeto de lista (list[[object]]) e, em seguida, usamos a indexação correspondente para extrair os elementos do objeto (list[[object]][indexing object]):
Indexando o primeiro objeto (vetor)
l1[['vector']][c(1, 3:5)]
a c d e 
2 7 3 1 
Indexando o segundo (array)
l1[['matriz']][4:5, , drop = T]
   C1 C2 C3 C4 C5
F4 10 11  9 17 12
F5 13 12 14 13 16
Indexando o terceiro (array)
l1[['array']][c(1, 4:4), 3:5, 1, 1:2, ]
, , ani_2a, ani_3a

   C3 C4 C5
F1 15 13 13
F4  9 14 18

, , ani_2b, ani_3a

   C3 C4 C5
F1 17 15 17
F4 12 14 35

, , ani_2a, ani_3b

   C3 C4 C5
F1 21 47 26
F4 39 43 42

, , ani_2b, ani_3b

   C3 C4 C5
F1 13 12 12
F4 16 10 21

, , ani_2a, ani_3c

   C3 C4 C5
F1 27 14 10
F4  6 30 22

, , ani_2b, ani_3c

   C3 C4 C5
F1 16 17  5
F4 53 13 15
Indexando o elemento na quarta posição (data frame)
l1[['data_frame']][1, 2:3, drop = F]
  trap formigas
1    1        9

Lembre-se que as listas podem armazenar outras listas. Nestes, e basicamente em qualquer caso, as regras de indexação são as mesmas. Só temos que prestar atenção na sequência do aninhamento, vejamos um exemplo:

l1$sub_lista <- l1

l1[['sub_lista']][['array']][c(1, 4:4), 3:5, 1, 1:2, 2:3]
, , ani_2a, ani_3b

   C3 C4 C5
F1 21 47 26
F4 39 43 42

, , ani_2b, ani_3b

   C3 C4 C5
F1 13 12 12
F4 16 10 21

, , ani_2a, ani_3c

   C3 C4 C5
F1 27 14 10
F4  6 30 22

, , ani_2b, ani_3c

   C3 C4 C5
F1 16 17  5
F4 53 13 15

Os itens acima são os elementos básicos que você precisa para manipular qualquer estrutura de dados em R. A compreensão dessas regras lhe dará uma base razoavelmente sólida para entender erros comuns, programar funções, executar tarefas com iterações e, em geral, aproveitar ao máximo os recursos da linguagem.