4 Subsetting
este capítulo está em construção. O que segue abaixo é apenas um rascunho.
4.1 Introdução
Referência básica: Advanced R (2e) cap. 4 and cap. 5.
Na aula de hoje, aprenderemos operações mais concretas, mas muito essenciais.
Haverão três aprendizados principais:
(i) como funciona o sistema de subsetting, e sua relação com os tipos de valores;
(ii) as ferramentas de control flow no R;
(iii) o sistema de condições e suas ferramentas.
Para os exemplos, considere:
4.2 Subset - Operador [
4.2.1 Operador [
O operador [
é utilizado após um vetor x[...]
, para selecionar um subconjunto de seus elementos.
O conteúdo em ...
pode tomar seis formas.
4.2.2 Operador [
Com Vetores Atômicos
Seis ‘insumos’ para selecionar com [
:
A1. Inteiros positivos: retornam elementos nas posições:
-
x[1] #> 1.3
. -
x[1:3] #> 1.3 7.4 6.0
. -
x[c(1,3)] #> 1.3 6.0
.
A2. Inteiros negativos: retornam todos os elementos menos os das posições.
x[-c(1,3)] #> 7.4 5.9
Obs: decimais são truncados x[1.7] == x[1]
, e factors ‘viram’ inteiros.
A3. Vetores lógicos: retornam os elementos nas posições onde tem-se TRUE
’s.
-
x[c(TRUE, TRUE, FALSE, FALSE)] #> 1.3 7.4
.
A4. Vetores de texto: retornam os elementos com os nomes escolhidos.
-
c(a = 1, b = 2, c = 3)[c("a", "c")] #> 1 3
.
Reciclagem: para relacionar vetores de tamanhos diferentes, o R multiplica o tamanho do menor, se for múltiplo do maior: x[c(TRUE, FALSE)]
.
A5. Nada: x[]
retorna o vetor original.
- Será relevante para matrizes, e para substituir o conteúdo, deixando a estrutura intacta.
A6. Zero: retorna um vetor de tamanho 0.
Note que 1. e 4. podem reordear um vetor x[c(1, 3, 2)]
, bem como retornar um único elemento.
4.2.3 Operador [
Com Matrizes e Arrays
Com matrizes (vetores com dimensão), existem três ‘métodos’:
M1. Múltiplos vetores: um vetor para cada dimensão:
-
m[c(TRUE, FALSE, TRUE), c("B", "A")]
. - Um vetor omisso retorna todos os ‘elementos da dimensão’:
a[, c(1, 3)]
. - Para arrays, use um argumento para cada dimensão
a[1:2, 2, , 4]
.
M2. Vetor único: como matrizes são vetores, podemos tratá-las como tal:
-
m[5:7]
do elemento 5 até o 7 (a ordem é “column-major”). - Podemos usar um vetor lógico aqui. E, como dimensão é um mero meta-dado, uma matriz lógica.
M3. Matrizes seletoras: onde cada coluna é uma dimensão, e cada linha um elemento quisto:
-
m[rbind(c(1,1), c(3,1), c(2,4))]
. - Para arrays, precisamos de três ou mais colunas.
4.2.4 Operador [
Com Listas e Dataframes
Com listas, não temos nenhuma diferença. [
sempre retorna uma lista, mesmo ao selecionar um único elemento.
E para data frames? O que df[1:2]
deve retornar?
E df[1:2, ]
? É “natural” que esses métodos coexistam?
Com dataframes, que são uma ‘lista com cara de matriz’, ambos os métodos existem.
4.2.5 Operador [
e Simplificação
O operador [
simplifica a dimensão do resultado.
- Em matrizes/arrays, dimensões de length 1 são desfeitas:
-
m[1,]
, em[,1]
. Ou tambémm[1:9]
.
-
- Ou ao selecionar uma única coluna de um dataframe no ‘modo matriz’
df[..., 2]
.- Mas selecionar linhas, mais de uma coluna, ou pelo ‘modo elemento’, mantêm a dimensão.
Isso é controlado pelo argumento drop = TRUE
do operador. Podemos alterá-lo: df[1, , drop = FALSE]
.
Tibbles tem drop = FALSE
por padrão, sempre retornando outra tibble/nunca simplificando (alá a preguiça).
4.3 Subset - Operador [[
4.3.1 Operador [[
Lembre que, em listas, [
sempre retorna uma lista – retorna o(s) elemento(s) e a ‘estrutura em volta’.
Para retornar apenas um (único) elemento, usamos os operadores [[...]]
ou $...
.
Qual deve ser o resultado de x[1] == x[[1]]
? Porque?
Pode ser útil usar [[
mesmo assim?
4.3.2 Operador [[
Com Listas
Dois ‘insumos’ para selecionar com [[
:
L1. Um escalar inteiro: selecionando o elemento pelo índide.
L2. Um escalar string: selecionando pelo nome.
No segundo caso, podemos usar a abreviação l$a
, no lugar de l[["a"]]
.
Obs:
- Selecionar com um vetor seleciona recursivamente na lista,
x[[c(1, 2)]]
é similar àx[[1]][[2]]
. -
$
faz matching parcial nos nomes, useoptions(warnPartialMatchDollar = TRUE)
.
4.4 Subset - Seleções Erradas e Assignment
4.4.1 Seleções Erradas
E se selecionarmos um elemento que não existe? A resposta é suco de R:
x | fun | -Int OOB | +Int OOB | Chr OOB | NA |
0-length |
---|---|---|---|---|---|---|
Atomic | [ |
x |
NA |
NA |
N-length NA
|
0-length |
List | [ |
x |
NULL |
NULL |
N-length NULL
|
0-length |
Atomic | [[ |
Error | Error | Error | Error | Error |
List | [[ |
Error | Error | NULL |
NULL |
Error |
NULL |
[[ /[
|
NULL |
NULL |
NULL |
NULL |
NULL |
Obs: o comportamento é o mesmo com ’x’s de tamanho zero.
Não é importante decorar essa matriz, mas note:
-
[
é mais permissivo, retorna “nada” em vez de um erro.- Com -Int, retorna
x
, faz sentido. - O que pode representar “nada” em listas? E em atômicos?
- Com -Int, retorna
-
[[
é menos permissivo, retorna erro em vez de nada.- Mas com listas, temos duas exceções.
- O que significa selecionar com
NA
? Em qual dos tipos de conteúdo ele se encaixa?
Para lidar com essas inconsistências, existem as seguintes funções:
- Para
[
:purrr::keep_at()
,vctrs::vec_slice
. - Para
[
:purrr::pluck()
,purrr::chuck()
.
4.4.2 Assingment
Todos os operadores de subset podem ser combinados com <-
: x[i] <- ...
.
x[[i]] <- NULL
remove o item de uma lista, x[i] <- list(NULL)
adiciona um elemento NULL
.
Subsetar com nada seleciona o ‘conteúdo’ de um objeto, e pode ser combinado com assignment para manter a estrutura inalterada:
4.5 Control Flow - Condicionais
4.5.1 Control Flow
As ferramentas de control flow são aquelas que alteram a ordem de execução de um dado código.
No R, temos dois grupos principais:
4.5.2 If e Else
Podemos criar uma expressão que apenas é avaliada se uma certa condição for verdade. Se não for, podemos complementar com expressões auxiliares:
x <- 60
if (x > 90) {
"A"
} else if (x > 80) {
"B"
} else if (x > 50) {
"C"
} else {
"F"
}
4.5.3 If e Else
Características gerais:
A condição deve gerar
TRUE
ouFALSE
(os escalares). Outras coisas geram erros.Podemos escrever essas expressões em uma linha, sem “{”.
-
Podemos utilizá-las para assinalar valores:
x <- if (TRUE) 1 else 2
.- Se não houver nenhum
else
,if
retorna umNULL
.
- Se não houver nenhum
Mais pra fente, veremos que if é uma função!
`if(cond, expr1, expr2)`
.
4.5.4 If e Else Vetorizado
A função ifelse()
funciona para um vetor de condições, diferentemente de if
, que exigia um único valor booleano.
Para cada elemento, ela checa a condição, e avalia uma ou outra expressão, salvando os resultados em um vetor.
O que o código abaixo vai retornar?
x <- 1:10
ifelse(x > 5, NA, x)
Outra opção é dplyr::case_when()
, que permite vários pares condição-resultado:
4.5.5 And And e Or Or
Existem os operadores “and and” &&
e “or or” ||
que garantem sempre um único valor booleano (dão erros com vetores longos), além de serem “short circuit”.
Por isso, use &&
e ||
em if
, &
e |
em ifelse()
.
4.5.6 Switch
Existe uma maneira mais simples de escrever uma expressão if
de “escolha de opções”, com a função switch()
:
4.5.7 Infix Operator
Muitas vezes, queremos substituir o valor de uma variável caso ela seja NULL
. Muitas vezes, funções usam isso para definir argumentos padrão.
Para isso, temos o infix operator:
k <- NULL
k <- k %||% 10
4.6 Control Flow - Loops
4.6.1 Loops For
Loops for são usados para iterar em elementos de um vetor:
for (item in vector) expr
item
é literalmente um objeto, cujo valor vai sendo atualizado a cada iteração, e fica armazenado no ambiente global.
Dicas gerais:
- O R é uma lingua vetorizada, use operações vetorizadas no lugar de loops!
- Ao usar um loop para criar uma variável, crie o “contêiner” de antemão, pois é bem mais rápido:
x <- NULL; for(i in 1:5) x <- c(x, i)
x <- NULL; for(i in 1:5) x[[i]] <- i
Prefira usar
seq_along(x)
no lugar de1:length(x)
.Cuidado que loops costuma remover atributos. Use
seq_along()
e acesse os itens você mesmo:
4.6.2 Map
Em muitas linguagens, um map é uma higher-order-function que aplica uma função aos elementos de uma coleção.
Este é um padrão funcional, e será discutido na aula 5, mas tem sua semelhança com loops.
No R, existem vários maps:
E existem muitas variações:
4.6.3 Outros Loops
Se você não tem um vetor de opções de antemão, não sabe sobre quais valores quer iterar, você pode:
Usar um loop
while (condition) {...}
, que repete uma ação enquanto uma condição valer.Ou, ainda mais flexível,
repeat(...)
, que repete uma ação para sempre até encontrar umbreak
.
É uma boa prática usar a opção menos flexível possível, para dar menos brechas para erros. Prefira usar o for
, ou lapply
/purrr::map
.
4.8 Condition System
4.8.1 Conditions
O sistema de condições serve para funções indicarem ao usuário alguma informação, como a existência de erros.
Existem três tipos de informação:
- Erros: muito severos, indicam que nem foi possível terminar a execução da função
- Avisos: médiamente severos, indicam alga provávelmente errado, mas que não impediu a execução.
- Mensagens: pouco severos, normalmente indicam que a função realizou alguma correção para o usuário.
4.8.2 Condition Functions
Dentro da função, chamadas à stop()
, warning()
, ou message()
criam a condição. stop()
, inclusive, para a execução da função.
Também existe rlang::abort()
, rlang::warn()
, rlang::inform()
.
O usuário pode encapsular uma call em funções como try()
e tryCatch()
para ignorar ou lidar com condições.
4.8.3 Signaling
Autores de funções devem aprender quando e como indicar cada tipo de condição. Se tiver curiosidade, veja a seção 8.2 do AdvR.
Considere as função de teste do pacote varr.
test$category <- function(arg, options, env) {
arg_name <- rlang::ensym(arg)
if (!all(arg %in% options)) {
cli_abort(
"{.var {arg_name}} must contain only {.or {options}}",
call = env
)
}
}
test$unused <- function(arg, x, class, env) {
arg_name <- ensym(arg)
if (class(x) == class) {
cli_inform(
"{.val {arg_name}} is unused if `x` is of class {class}.",
call = env
)
}
}
4.8.4 Ignoring
No R, podemos ignorar condições:
- Ignore erros com
try()
. - Ignore avisos com
suppressWarnings()
. - Ignore mensagens com
suppressMessages()
.
Considere o padrão abaixo.
x <- NULL
try(x <- call_que_pode_dar_errado())
4.8.5 Handling
Podemos lidar com condições usando tryCatch()
e withCallingHandlers()
, que especificam funções para se executar no caso de cada tipo de condição:
tryCatch(
error = function(cnd) {
# código a ser executado quando ocorrer um erro
},
warning = function(cnd) {
# código a ser executado quando um aviso for sinalizado
},
message = function(cnd) {
# código a ser executado quando uma mensagem for sinalizada
},
call_rodada_com_handlers
)
Qual a diferença das duas?
tryCatch()
define exiting handlers, após a condição aparecer, a função handler relevante é ativada, e seu valor é retornado para ondetryCatch()
foi chamado.withCallingHandlers()
define calling handlers, após a condição, a função handler relevante é ativada, e seu valor é retornado para onde a call que gerou a condição estava.
Isto é, uma sai da call, outra não. A primeira é mais intuitiva para erros, a segunda para avisos ou mensagens.
Complemento
Recapitulando
4.8.5.1 Subsetting
Seis ‘insumos’ para selecionar com [
:
A1. Inteiros positivos: retornam elementos nas posições.
A2. Inteiros negativos: retornam todos os elementos menos os das posições.
A3. Vetores lógicos: retornam os elementos nas posições onde tem-se TRUE
’s.
A4. Vetores de texto: retornam os elementos com os nomes escolhidos.
A5. Nada: x[]
retorna o vetor original.
A6. Zero: retorna um vetor de tamanho 0.
Reciclagem: para relacionar vetores de tamanhos diferentes, o R multiplica o tamanho do menor, se for múltiplo do maior: x[c(TRUE, FALSE)]
.
Três ‘métodos’ para selecionar com dimensões:
M1. Múltiplos vetores: um vetor para cada dimensão. M2. Vetor único: como matrizes são vetores, podemos tratá-las como tal. M3. Matrizes seletoras: onde cada coluna é uma dimensão, e cada linha um elemento quisto.
Com listas e dataframes:
- Com listas, não temos nenhuma diferença.
[
sempre retorna uma lista, mesmo ao selecionar um único elemento. - Com dataframes, que são uma ‘lista com cara de matriz’, temos o método normal e o de matriz.
O operador [
simplifica a dimensão do resultado. Veja o argumento drop = TRUE
.
Para retornar apenas um (único) elemento, usamos os operadores [[...]]
ou $...
.
Dois ‘insumos’ para selecionar com [[
:
L1. Um escalar inteiro: selecionando o elemento pelo índide. L2. Um escalar string: selecionando pelo nome.
Selecionar elementos que não existem está relacionado a comportamentos inconsistentes, as vezes retornando erros, NULL
, ou NA
.
-
[
é mais permissivo, retorna “nada” em vez de um erro.- Considere usar
purrr::keep_at()
ouvctrs::vec_slice
.
- Considere usar
-
[[
é menos permissivo, retorna erro em vez de nada.- Considere usar
purrr::pluck()
epurrr::chuck()
.
- Considere usar
Todos os operadores de subset podem ser combinados com <-
: x[i] <- ...
.
4.8.5.2 Control Flow
As ferramentas de control flow são aquelas que alteram a ordem de execução de um dado código.
-
Choices, com
if
,else
,switch()
.- E suas versões vetorizadas,
ifelse()
edplyr::case_when()
.
- E suas versões vetorizadas,
-
Loops, com
repeat
,while
, efor
.- Similares ao conceito de map, funções como
lapply()
,purrr::map()
, eapply()
.
- Similares ao conceito de map, funções como
Também vimos:
4.8.5.3 Condições
O sistema de condições serve para funções indicarem ao usuário alguma informação, como a existência de erros.
Existem três tipos de informação: erros (stop()
), avisos (warning()
), e mensagens (message()
).
Podemos ignorar condições:
- Ignore erros com
try()
. - Ignore avisos com
suppressWarnings()
. - Ignore mensagens com
suppressMessages()
.
E podemos lidar com condições usando tryCatch()
e withCallingHandlers()
, que especificam funções para se executar no caso de cada tipo de condição.
-
tryCatch()
define exiting handlers, após a condição aparecer, a função handler relevante é ativada, e seu valor é retornado para ondetryCatch()
foi chamado. -
withCallingHandlers()
define calling handlers, após a condição, a função handler relevante é ativada, e seu valor é retornado para onde a call que gerou a condição estava.
Dicionário de Funções
Categoria | Função | Descrição |
---|---|---|
subset |
[ , [[ , $
|
extração de elementos |
control flow |
if , switch() , ifelse() , %||%
|
if else, ifelse vetorizado, e infix operator |
control flow |
for , while , repeat
|
loops |
control flow |
break , next
|
keywords |
operadores |
&& , ||
|
lógica |
outros |
seq() , seq.int() , seq_along() , seq_len()
|
geração de sequências |
maps |
lapply() , apply() , purrr::map()
|
maps |
maps |
sapply() , replicate() , Map()
|
maps (variações) |
conditions |
stop() , warning() , message()
|
signaling conditions |
conditions |
rlang::abort() , rlang::warn() , rlang::inform()
|
signaling conditions (rlang) |
conditions |
try() , supressWarnings() , supressErrors()
|
ignoring conditions |
conditions |
tryCatch() , withCallingHandlers
|
handling conditions |