Definindo objetos no R. = ou <- ?

tutorial-R
Author

Vinicius Oike

Published

July 11, 2023

Definindo objetos no R

Há dois operadores para definir um objeto no R: = e <-. A maior parte dos usuários parece preferir o último apesar dele parecer um tanto inconveniente. Em teclados antigos, havia uma tecla específica com o símbolo <-, mas em teclados ABNT modernos ele exige três teclas para ser escrito.

Para contornar este incômodo é comum criar um atalho no teclado para esse símbolo; o RStudio, por exemplo, tem um atalho usando a teclas Alt e - em conjunto. Mas ainda assim fica a questão: por que não utilizar o =? A resposta curta é que o símbolo <- é a melhor e mais consistente forma de definir objetos R. Na prática, contudo, há poucas diferenças entre as expressões e elas dificilmente vão fazer alguma diferença. Podemos começar com um exemplo bastante simples para entender estas diferenças.

Qual a diferença?

O código abaixo cria duas variáveis, x e y, cujos valores são a sequência \(1, 2, 3, 4, 5\). Até aí tudo igual. A função all.equal certifica que os objetos são iguais e a função rm “deleta” os objetos. Esta última vai ser conveniente para manter os exemplos organizados.

x = 1:5
y <- 1:5

all.equal(x, y)
rm(x, y)

Agora considere o código abaixo. A função median está sendo aplicada em x <- 1:5. O que acontece desta vez? O resultado é que é criada uma variável x com valor 1 2 3 4 5 e também é impresso a mediana deste vetor, i.e., 3.

median(x <- 1:5)
x
rm(x)

Poderíamos fazer o mesmo usando =, certo? Errado. Aí está uma das primeiras diferenças entre estes operadores. O código abaixo calcula a mediana do vetor, mas não cria um objeto chamado x com valor 1 2 3 4 5. Por quê? O problema é que o operador = tem duas finalidades distintas. Ele serve tanto para definir novos objetos, como em x = 2, como também para definir o valor dos argumentos de uma função, como em rnorm(n = 10, mean = 5, sd = 1). Coincidentemente, o nome do primeiro argumento da função median é x. Logo, o código abaixo é interpretado como: tire a mediana do vetor 1 2 3 4 5. O mesmo acontece com outras funções (ex: mean, var, etc.)

median(x = 1:5)
x
rm(x)

Outro exemplo em que há divergência entre os operadores é com o comando lm. Usando <- podemos escrever, numa única linha, um comando que define um objeto lm (resultado de uma regressão) ao mesmo tempo em que pedimos ao R para imprimir os resultados desta regressão. O código abaixo faz justamente isto.

# Imprime os resultados da regressão e salva as info num objeto chamado 'fit'
summary(fit <- lm(mpg ~ wt, data = mtcars))
# Verifica que é, de fato, um objeto lm
class(fit)
rm(fit)

Note que isto não é possível com o operador =. Isto acontece, novamente, porque o = é interpretado de maneira diferente quando aparece dentro de uma função. É necessário quebrar o código em duas linhas.

# Este exemplo não funciona
summary(fit = lm(mpg ~ wt, data = mtcars))
# É preciso reescrever o código em duas linhas
fit = lm(mpg ~ wt, data = mtcars)
summary(fit)
rm(fit)

Há também algumas pequenas divergências pontuais. Os primeiros dois argumentos da função lm são formula e data. Considere o código abaixo. Sem executar o código qual deve ser o resultado?

fit <- lm(formula <- mpg ~ wt, data <- mtcars)

Estamos aplicando a função lm em dois argumentos. O primeiro deles se chama formula e é definido como mpg ~ wt, o segundo é chamado data e é definido como os valores no data.frame mtcars. Ou seja, o resultado deve ser o mesmo do exemplo acima com median(x <- 1:5). A função é aplicada sobre os argumentos e os objetos formula e data são criados.

fit <- lm(formula <- mpg ~ wt, data <- mtcars)
fit

formula

head(data)

rm(fit, formula, data)

Note que usei os nomes dos argumentos apenas para exemplificar o caso. Pode-se colocar um nome diferente, pois não estamos “chamando” o argumento e sim especificando qual valor/objeto a função deve utilizar.

# Exemplo utilizando nomes diferentes
fit <- lm(a <- "mpg ~ wt", b <- mtcars)
fit
print(a)
head(b)
rm(list = ls())

O mesmo não é possível com =, por causa da duplicidade apontada acima.

fit = lm(a = "mpg ~ wt", b = mtcars)

Há ainda mais alguns exemplos estranhos, resultados da ordem que o R processa os comandos. O segundo código abaixo, por exemplo, não funciona. Isto acontece porque o <- tem “prioridade” e é lido primeiro.

(x = y <- 5)
(x <- y = 5)

É difícil encontrar desvatagens em usar o <- (além da dificuldade de escrevê-lo num teclado normal). Mas há pelo menos um caso em que ele pode levar a problemas. O código abaixo mostra como este operador pode ser sensível a espaços em branco. No caso, define-se o valor de x como -2. O primeiro teste verifica se o valor de x é menor que \(-1\). Logo, espera-se que o código imprima "ótimo" pois -2 é menor que -1. Já o segundo teste faz quase o mesmo. A única diferença é um espaço em branco, mas agora ao invés de um teste, a linha de código define o valor de x como 1 e imprime "ótimo", pois o valor do teste (por padrão) é TRUE.

Assim como muitos dos exemplos acima, é difícil imaginar que isto possa ser um problema real. Eventualmente, podemos apagar espaços em branco usando o ‘localizar e substituir’ e isto talvez leve a um erro como o abaixo.

# Define x como -2
x <- -2
# Se x for menor que -1 (menos um) então "ótimo"
if (x < -1) "ótimo" else "errado"
# Verifica o valor de x
x
# Mesma linha com uma diferença sutil
if (x <-1 ) "ótimo" else "errado"
# Agora acabamos de mudar o valor de x (e não há aviso algum!)
x

Mais um adendo

Eu citei apenas dois operadores: = e <-; mas na verdade há ainda outros: <<-, -> e ->> (veja help("assignOps")). Os operadores com “flecha dupla” são comumente utilizadas dentro de funções para usos específicos. Algumas pessoas acham que o operador -> é mais intuitivo quando usado com “pipes”. Pessoalmente, acho este tipo de código abominável.

AirPassengers |> 
  log() |> 
  window(start = c(1955, 1), end = c(1958, 12)) -> sub_air_passengers

sub_air_passengers

Além dos operadores base, o popular pacote magrittr também possui um novo tipo de operador. Muitos conhecem o %>% que ajuda a formar “pipes” e carregar objetos progressivamente em funções distintas. Menos conhecido, mas igualmente poderoso, o %<>% faz algo parecido.

library(magrittr)
x <- 1:5
print(x)
x %<>% mean() %<>% sin()
print(x)

Note que o código acima é equivalente ao abaixo. A vantagem do operador %<>% é de evitar a repetição do x <- x ... o que pode ser conveniente quando o nome do objeto é longo. Da mesma forma, também pode ser aplicado para transformar elementos em uma lista ou colunas num data.frame.

x <- 1:5
x <- x %>% mean() %>% sin()

Não recomendo o uso nem do -> e nem do %<>%.

Resumo

No geral, o operador <- é a forma mais “segura” de se definir objetos. De fato, atualmente, este operador é considerado o mais apropriado. O livro Advanced R, do influente autor Hadley Wickham, por exemplo, recomenda que se use o operador <- exclusivamente para definir objetos.

A inconveniência de escrevê-lo num teclado moderno é contornada, como comentado acima, por atalhos como o Alt + - no RStudio. Em editores de texto como o VSCode, Sublime Text ou Notepad++ também é possível criar atalhos personalizados para o <-. Pessoalmente, eu já me acostumei a digitar <- manualmente e não vejo grande problema nisso. Acho que o = tem seu valor por ser mais intutivo, especialmente para usuários que já tem algum conhecimento de programação, mas acabo usando mais o <-. Por fim, o <- fica bem bonito quando se usa uma fonte com ligaturas como o Fira Code.

É importante frisar que o <- continua sendo o operador de predileção da comunidade dos usuários de R e novas funções/pacotes devem ser escritas com este sinal. Por sorte há opções bastante simples que trocam os = para <- corretamente como o formatR apresentado abaixo. Veja também o addin do pacote styler. Ou seja, é possível escrever seu código usando = e trocá-los por <- de maneira automática se for necessário.

library(formatR)
tidy_source(text = "x = rnorm(n = 10, mean = 2)", arrow = TRUE)

O ponto central deste post, na verdade, é mostrar como os operadores <- e = são muito similares na prática e que a escolha entre um ou outro acaba caindo numa questão subjetiva. Há quem acredite ser mais cômodo usar o = não só porque ele é mais fácil de escrever, mas também porque ele é mais próximo de universal. Várias linguagens de programação comuns para a/o economista (Matlab, Python, Stata, etc.) usam o sinal de igualdade para definir objetos e parece estranho ter que usar o <- somente para o R.

Em livros de econometria ambos os operadores são utilizados. Os livros Introduction to Econometrics with R, Applied Econometrics with R, Arbia. Spatial Econometrics, entre outros, usam o <-. Já os livros Tsay. Financial Time Series e Shumway & Stoffer. Time Series Analysis usam =.

No fim, use o operador que melhor