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.

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)
## [1] TRUE
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)
## [1] 3
x
## [1] 1 2 3 4 5
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)
## [1] 3
x
## Error in eval(expr, envir, enclos): objeto 'x' não encontrado
rm(x)
## Warning in rm(x): objeto 'x' não encontrado

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))
## 
## Call:
## lm(formula = mpg ~ wt, data = mtcars)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.5432 -2.3647 -0.1252  1.4096  6.8727 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  37.2851     1.8776  19.858  < 2e-16 ***
## wt           -5.3445     0.5591  -9.559 1.29e-10 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 3.046 on 30 degrees of freedom
## Multiple R-squared:  0.7528, Adjusted R-squared:  0.7446 
## F-statistic: 91.38 on 1 and 30 DF,  p-value: 1.294e-10
# Verifica que é, de fato, um objeto lm
class(fit)
## [1] "lm"
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))
## Error in summary.lm(fit = lm(mpg ~ wt, data = mtcars)): argumento "object" ausente, sem padrão
# É preciso reescrever o código em duas linhas
fit = lm(mpg ~ wt, data = mtcars)
summary(fit)
## 
## Call:
## lm(formula = mpg ~ wt, data = mtcars)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.5432 -2.3647 -0.1252  1.4096  6.8727 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  37.2851     1.8776  19.858  < 2e-16 ***
## wt           -5.3445     0.5591  -9.559 1.29e-10 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 3.046 on 30 degrees of freedom
## Multiple R-squared:  0.7528, Adjusted R-squared:  0.7446 
## F-statistic: 91.38 on 1 and 30 DF,  p-value: 1.294e-10
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? 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 é 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.

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.

fit <- lm(formula <- mpg ~ wt, data <- mtcars)
fit
## 
## Call:
## lm(formula = formula <- mpg ~ wt, data = data <- mtcars)
## 
## Coefficients:
## (Intercept)           wt  
##      37.285       -5.344
rm(fit)
# Exemplo utilizando nomes diferentes
fit <- lm(a <- "mpg ~ wt", b <- mtcars)
fit
## 
## Call:
## lm(formula = a <- "mpg ~ wt", data = b <- mtcars)
## 
## Coefficients:
## (Intercept)           wt  
##      37.285       -5.344
print(a)
## [1] "mpg ~ wt"
head(b)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
rm(list = ls())

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

fit = lm(a = "mpg ~ wt", b = mtcars)
## Error in terms.formula(formula, data = data): argumento não é um modelo válido

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)
## [1] 5
(x <- y = 5)
## Error in x <- y = 5: não foi possível encontrar a função "<-<-"

No geral, o operador <- é a forma mais “segura” de se definir objetos. De fato, atualmente, este operador é considerado mais apropriado. O livro Advanced R, do influente autor Hadley Wickham, por exemplo, recomenda que se use o operador <- exclusivamente para definir objetos. A inconviência de escrevê-lo num teclado moderno é contornada, como comentado acima, por atalhos como o Alt + - no RStudio. Em editores de texto como o Sublime Text ou Notepad++ também é possível criar atalhos personalizados para o <-.

É 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"
## [1] "ótimo"
# Verifica o valor de x
x
## [1] -2
# Mesma linha com uma diferença sutil
if (x <-1 ) "ótimo" else "errado"
## [1] "ótimo"
# Agora acabamos de mudar o valor de x (e não há aviso algum!)
x
## [1] 1

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. Atualmente, acho 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. É 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.

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

Os posts deste site vão continuar a usar o <- já que ele é mais comumente aceito, mas, pessoalmente eu prefiro =. Isto é, os posts serão todos escritos com = e depois serão convertidos para <-. Eventualmente, eu talvez pare de fazer isto e passe a escrever diretamente com =.