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)
[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): object 'x' not found
rm(x)
Warning in rm(x): object 'x' not found

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)): argument "object" is missing, with no default
# É 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?

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

Call:
lm(formula = formula <- mpg ~ wt, data = data <- mtcars)

Coefficients:
(Intercept)           wt  
     37.285       -5.344  
formula
mpg ~ wt
head(data)
                   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(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

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): argument is not a valid model

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: could not find function "<-<-"

É 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

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
          Jan      Feb      Mar      Apr      May      Jun      Jul      Aug
1955 5.488938 5.451038 5.587249 5.594711 5.598422 5.752573 5.897154 5.849325
1956 5.648974 5.624018 5.758902 5.746203 5.762051 5.924256 6.023448 6.003887
1957 5.752573 5.707110 5.874931 5.852202 5.872118 6.045005 6.142037 6.146329
1958 5.828946 5.762051 5.891644 5.852202 5.894403 6.075346 6.196444 6.224558
          Sep      Oct      Nov      Dec
1955 5.743003 5.613128 5.468060 5.627621
1956 5.872118 5.723585 5.602119 5.723585
1957 6.001415 5.849325 5.720312 5.817111
1958 6.001415 5.883322 5.736572 5.820083

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)
[1] 1 2 3 4 5
x %<>% mean() %<>% sin()
print(x)
[1] 0.14112

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)
x <- rnorm(n = 10, mean = 2)

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