8.1 Lista

Listas são o segundo tipo de vetor. O primeiro tipo nós já vimos, são os vetores atômicos, nos quais todos os elementos devem ser dados do mesmo tipo. Listas são uma estrutura de dados mais versátil por pelo menos 3 motivos:

  1. Os elementos podem ser de diferentes classes de objetos (p.ex.: um elemento numeric, outro character);

  2. Cada elemento pode ter um tamanho diferente;

  3. Os elementos podem conter diferentes estrutura de dados (p.ex.: um elemento matrix, outro vector);

Dentro da lista o conjunto de objetos são ordenados e cada elemento pode conter sub-elementos.

8.1.1 Criação

As vezes precisamos de um container para armazenar diferentes tipos de dados do R e com diferente tamanhos. As listas servem para isso e permitem armazenar qualquer número de itens de qualquer tipo. Uma lista pode conter números, caracteres ou uma mistura de dataframes, sub-listas, matrizes e vetores.

Listas podem ser criadas com a função list(). A especificação do conteúdo de uma lista é muito similar a da função c() vista anteriormente. Nós simplesmente listamos os dados que queremos como elementos da lista separados por vírgula dentro da função list().

# lista de dados heterogêneos
lst <- list(
  # 4 vetores atômicos
  c(1L, 6L, 10L, NA),
  c(-1, 0, 1, 2, NA),
  c(FALSE, NA, FALSE, TRUE),
  c('ae', NA, "ou"),
  # uma lista com 2 elementos 
  list(0, 1)
)
lst
#> [[1]]
#> [1]  1  6 10 NA
#> 
#> [[2]]
#> [1] -1  0  1  2 NA
#> 
#> [[3]]
#> [1] FALSE    NA FALSE  TRUE
#> 
#> [[4]]
#> [1] "ae" NA   "ou"
#> 
#> [[5]]
#> [[5]][[1]]
#> [1] 0
#> 
#> [[5]][[2]]
#> [1] 1

Os índices em colchetes duplos [[]] identificam o elemento ou a componente da lista. Os índices em colchete simples [] indicam qual sub-elemento da lista está sendo mostrado. Por exemplo -1 é o primeiro elemento do vetor armazenado no segundo elemento da lista lst. Desse aninhamento de elementos surge o sistema de indexação de listas. A estrutura de uma lista pode se tornar complicada com o aumento do grau de sub-elementos. Mas essa flexibilidade, faz das listas uma ferramenta de armazenamento de dados para diversos propósitos.

Uma forma rápida de visualizar a estrutura de uma lista com a função str():

str(lst)
#> List of 5
#>  $ : int [1:4] 1 6 10 NA
#>  $ : num [1:5] -1 0 1 2 NA
#>  $ : logi [1:4] FALSE NA FALSE TRUE
#>  $ : chr [1:3] "ae" NA "ou"
#>  $ :List of 2
#>   ..$ : num 0
#>   ..$ : num 1

A saída mostra que a lista tem 5 elementos, para cada elemento é indicado seu tipo de dado. Uma lista pode armazenar uma lista dentro de suas componentes. Neste caso a lista é dita ser uma lista aninhada. Em algumas circunstâncias você pode precisar verificar se uma lista é aninhada e para isso usamos a função is.recursive().

is.recursive(lst)
#> [1] TRUE

Listas, por serem vetores, podem ter nomes:

names(lst)
#> NULL
names(lst) <- c("vetor_int", "vetor_dbl", "vetor_log", "vetor_char", "lista")

Note a diferença de como é mostrada no console a lista após ter sido nomeado seus componentes:

lst
#> $vetor_int
#> [1]  1  6 10 NA
#> 
#> $vetor_dbl
#> [1] -1  0  1  2 NA
#> 
#> $vetor_log
#> [1] FALSE    NA FALSE  TRUE
#> 
#> $vetor_char
#> [1] "ae" NA   "ou"
#> 
#> $lista
#> $lista[[1]]
#> [1] 0
#> 
#> $lista[[2]]
#> [1] 1

Ao invés do duplo colchetes, agora aparecem os nomes das componentes da lista precedidas do símbolo $. A lista interna da lst (5° elemento) segue sendo impressa com duplo colchetes uma vez que seus elementos não tem nomes.

Veremos que listas são frequentemente usadas para armazenar a saída de funções com diversos resultados. Como por exemplo a saída das funções rle(). Esta função é bastante útil para identificar sequência de dados repetidos.

8.1.2 Exemplo de lista com dados de estações meteorológicas

Vamos ver um exemplo onde criamos uma lista com dados e informações (metadados) de duas estações meteorológicas.

# matriz de dados meteorológicos da estação de Santa Maria
dados_sm <- cbind(
  tar = c(31, 35, 21, 23, 33, 17),
  prec = c(300, 200, 150, 120, 210, 110)
)
dados_sm
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110
# lista com informações da estação de santa maria
sm_l <- list(
  c(-45, -23),
  "Santa Maria",
  dados_sm
)
sm_l
#> [[1]]
#> [1] -45 -23
#> 
#> [[2]]
#> [1] "Santa Maria"
#> 
#> [[3]]
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110
# adicionar nomes aos elementos
names(sm_l) <- c("coords", "cidade", "dados")
sm_l
#> $coords
#> [1] -45 -23
#> 
#> $cidade
#> [1] "Santa Maria"
#> 
#> $dados
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110
# matriz de dados meteorológicos da estação de Júlio de Castilhos
dados_jc <- cbind(
  tar = c(22.5, 20, 18.75, 18, 20.25, 17.75),
  prec = c(360, 310, 285, 270, 315, 265)
)
# criando lista de JC, mas nomeando de forma diferente
jc_l <- list(
  coords = c(-45.1, -23.2),
  cidade = "Júlio de Castilhos",
  dados = dados_jc
)
# adicionar nomes as componentes
names(jc_l) <- names(sm_l)
jc_l
#> $coords
#> [1] -45.1 -23.2
#> 
#> $cidade
#> [1] "Júlio de Castilhos"
#> 
#> $dados
#>        tar prec
#> [1,] 22.50  360
#> [2,] 20.00  310
#> [3,] 18.75  285
#> [4,] 18.00  270
#> [5,] 20.25  315
#> [6,] 17.75  265

As informações de cada estação estão armazenadas em 2 listas. Mas é mais prático termos todas estações em um única lista:

# combinando listas mantendo os elementos separadamente
dados_l <- list(sm_l, jc_l)
dados_l
#> [[1]]
#> [[1]]$coords
#> [1] -45 -23
#> 
#> [[1]]$cidade
#> [1] "Santa Maria"
#> 
#> [[1]]$dados
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110
#> 
#> 
#> [[2]]
#> [[2]]$coords
#> [1] -45.1 -23.2
#> 
#> [[2]]$cidade
#> [1] "Júlio de Castilhos"
#> 
#> [[2]]$dados
#>        tar prec
#> [1,] 22.50  360
#> [2,] 20.00  310
#> [3,] 18.75  285
#> [4,] 18.00  270
#> [5,] 20.25  315
#> [6,] 17.75  265
names(dados_l)
#> NULL
# nomes para cada estação
names(dados_l) <- c("sm", "jc")
dados_l
#> $sm
#> $sm$coords
#> [1] -45 -23
#> 
#> $sm$cidade
#> [1] "Santa Maria"
#> 
#> $sm$dados
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110
#> 
#> 
#> $jc
#> $jc$coords
#> [1] -45.1 -23.2
#> 
#> $jc$cidade
#> [1] "Júlio de Castilhos"
#> 
#> $jc$dados
#>        tar prec
#> [1,] 22.50  360
#> [2,] 20.00  310
#> [3,] 18.75  285
#> [4,] 18.00  270
#> [5,] 20.25  315
#> [6,] 17.75  265
# tamanho da lista ou número de elementos da lista
length(dados_l)
#> [1] 2

Vamos ver como ficou a estrutura da lista dados_l:

str(dados_l)
#> List of 2
#>  $ sm:List of 3
#>   ..$ coords: num [1:2] -45 -23
#>   ..$ cidade: chr "Santa Maria"
#>   ..$ dados : num [1:6, 1:2] 31 35 21 23 33 17 300 200 150 120 ...
#>   .. ..- attr(*, "dimnames")=List of 2
#>   .. .. ..$ : NULL
#>   .. .. ..$ : chr [1:2] "tar" "prec"
#>  $ jc:List of 3
#>   ..$ coords: num [1:2] -45.1 -23.2
#>   ..$ cidade: chr "Júlio de Castilhos"
#>   ..$ dados : num [1:6, 1:2] 22.5 20 18.8 18 20.2 ...
#>   .. ..- attr(*, "dimnames")=List of 2
#>   .. .. ..$ : NULL
#>   .. .. ..$ : chr [1:2] "tar" "prec"

As listas também poderiam ser combinadas com função concatena ou combina c().

dados_l2 <- c(sm_l, jc_l)
str(dados_l2)
#> List of 6
#>  $ coords: num [1:2] -45 -23
#>  $ cidade: chr "Santa Maria"
#>  $ dados : num [1:6, 1:2] 31 35 21 23 33 17 300 200 150 120 ...
#>   ..- attr(*, "dimnames")=List of 2
#>   .. ..$ : NULL
#>   .. ..$ : chr [1:2] "tar" "prec"
#>  $ coords: num [1:2] -45.1 -23.2
#>  $ cidade: chr "Júlio de Castilhos"
#>  $ dados : num [1:6, 1:2] 22.5 20 18.8 18 20.2 ...
#>   ..- attr(*, "dimnames")=List of 2
#>   .. ..$ : NULL
#>   .. ..$ : chr [1:2] "tar" "prec"

Perceba a diferença entre a lista combinada com list() (dados_l) e com c() (dados_l2). Esta última possui 6 elementos e com nomes repetidos, o que pode gerar confusão quando for selecionar os dados.

8.1.3 Indexação

8.1.3.1 Operador [

Assim como na indexação de vetores atômicos (7.5), podemos acessar os elementos de uma lista usando os colchetes [ com índices numéricos positivos, negativos, caracteres (nomes dos elementos) e lógicos. As expressões abaixo, ilustram o uso dessas diferentes formas de seleção de elementos e produzem o mesmo resultado.

# seleção dos dois primeiros elelemntos da lista
# por números
sm_l[1:2]
#> $coords
#> [1] -45 -23
#> 
#> $cidade
#> [1] "Santa Maria"
# por nomes
sm_l[c("coords", "alt")]
#> $coords
#> [1] -45 -23
#> 
#> $<NA>
#> NULL

O resultado da seleção do 1º e 2º elemento é uma lista menor que a original. Isso não é muito útil, uma vez que muitas funções do R não lidam com listas. Por exemplo, se quiséssemos calcular a soma do vetor contido do primeiro elemento da lista lst obtém-se um erro.

# seleção do 1º elemento da lst
lst[1]
#> $vetor_int
#> [1]  1  6 10 NA
# o resultado da seleção é uma lista
mode(lst[1])
#> [1] "list"
# a função sum() espera como entrada um vetor
sum(lst[1])
#> Error in sum(lst[1]): invalid 'type' (list) of argument
# acessando elemento inexistente
lst[6]
#> $<NA>
#> NULL

Então ao selecionar elementos de uma lista com o operador [ o resultado preserva a estrutura original do objeto. lst é uma lista e o resultado da seleção lst[1] também é uma lista.

Portanto, a seleção de elementos com o operador [ preserva a estrutura de dados original.

8.1.3.2 Operador [[ e $

Na maioria das vezes estamos interessados no conteúdo dos elementos de uma lista. Para fazer isso há dois operadores:

  • duplo colchetes [[

  • sifrão $

Para acessar elementos individuais de uma lista usamos o duplo colchetes [[ especificando o número do elemento ou o nome. Essa forma de seleção de dados permite o acesso a um elemento por vez.

# 1º elemento de sm_l
sm_l[[1]]
#> [1] -45 -23
sm_l[["coords"]]
#> [1] -45 -23

# ultimo elemento de sm_l
sm_l[[length(sm_l)]]
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110
sm_l[["dados"]]
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110

A indexação pode ser combinada, por exemplo: para extrair da estação sm (1° elemento da dados_l) o nome da cidade (2° sub-elemento), fazemos:

dados_l[["sm"]][["cidade"]]
#> [1] "Santa Maria"

Para acessar o conteúdo de elementos de uma lista que possui nomes podemos também usar o operador $. Ele funciona de forma similar ao duplo colchetes usado com o nome do elemento da lista. Mas esse operador tem duas vantagens: a IDE RStudio autocompleta o nome do elemento (usando a tecla <tab>) e o aceita o nome parcial dos nomes dos elementos.

# seleção de dados por nomes usando o símbolo $
dados_l$s
#> $coords
#> [1] -45 -23
#> 
#> $cidade
#> [1] "Santa Maria"
#> 
#> $dados
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110
dados_l$j
#> $coords
#> [1] -45.1 -23.2
#> 
#> $cidade
#> [1] "Júlio de Castilhos"
#> 
#> $dados
#>        tar prec
#> [1,] 22.50  360
#> [2,] 20.00  310
#> [3,] 18.75  285
#> [4,] 18.00  270
#> [5,] 20.25  315
#> [6,] 17.75  265

Com o operador $, para extrair da estação sm (1° elemento da dados_l) o nome da cidade (2° sub-elemento), fazemos:

dados_l$sm$cidade
#> [1] "Santa Maria"

8.1.3.3 Lista de condimentos

É fácil de confundir quando usar ] e ]]. A tabela abaixo ajuda lembrar da diferença entre eles.

descrição código resultado
frasco de pimenta frasco
frasco de pimenta com apenas 1 pacote de pimenta frasco[1]
1 pacote de pimenta frasco[[1]]
conteúdo de um pacote de pimenta frasco[[1]][[1]]

8.1.4 Conversão de lista para vetor e vice-versa.

Um vetor é convertido para listacom a função as.list().

va <- 1:10
va
#>  [1]  1  2  3  4  5  6  7  8  9 10
va_list <- as.list(va)
va_list
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 2
#> 
#> [[3]]
#> [1] 3
#> 
#> [[4]]
#> [1] 4
#> 
#> [[5]]
#> [1] 5
#> 
#> [[6]]
#> [1] 6
#> 
#> [[7]]
#> [1] 7
#> 
#> [[8]]
#> [1] 8
#> 
#> [[9]]
#> [1] 9
#> 
#> [[10]]
#> [1] 10

Você pode "desmanchar" uma lista com a função unlist():

# desmanchando a lista
unlist(va_list)
#>  [1]  1  2  3  4  5  6  7  8  9 10

Ou seja a lista é convertida em vetor atômico.

A remoção de um elemento da lista pode ser feita usando a indexação e o valor NULL.

str(va_list)
#> List of 10
#>  $ : int 1
#>  $ : int 2
#>  $ : int 3
#>  $ : int 4
#>  $ : int 5
#>  $ : int 6
#>  $ : int 7
#>  $ : int 8
#>  $ : int 9
#>  $ : int 10
va_list[8] <- NULL
str(va_list)
#> List of 9
#>  $ : int 1
#>  $ : int 2
#>  $ : int 3
#>  $ : int 4
#>  $ : int 5
#>  $ : int 6
#>  $ : int 7
#>  $ : int 9
#>  $ : int 10

8.1.5 Conversão de list para data.frame

Vamos modificar a lista sm_l para convertê-la em um dataframe.

sm_l
#> $coords
#> [1] -45 -23
#> 
#> $cidade
#> [1] "Santa Maria"
#> 
#> $dados
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110
# ao invés da componente coords, criamos uma lon e lat
sm_l$lon <- sm_l$coords[1]
sm_l$lat <- sm_l$coords[2]
sm_l$coords <- NULL
sm_l
#> $cidade
#> [1] "Santa Maria"
#> 
#> $dados
#>      tar prec
#> [1,]  31  300
#> [2,]  35  200
#> [3,]  21  150
#> [4,]  23  120
#> [5,]  33  210
#> [6,]  17  110
#> 
#> $lon
#> [1] -45
#> 
#> $lat
#> [1] -23
# converter para dataframe
sm_df <- data.frame(sm_l)
sm_df
#>        cidade dados.tar dados.prec lon lat
#> 1 Santa Maria        31        300 -45 -23
#> 2 Santa Maria        35        200 -45 -23
#> 3 Santa Maria        21        150 -45 -23
#> 4 Santa Maria        23        120 -45 -23
#> 5 Santa Maria        33        210 -45 -23
#> 6 Santa Maria        17        110 -45 -23