R nasceu para estatística, e isso molda tudo na linguagem: o vetor é o cidadão de primeira classe, a indexação começa em 1, e operações são vetorizadas por padrão. Se você vem de Python, vai encontrar decisões de design que parecem estranhas no início e brilhantes depois. Este complemento cobre o R "puro" — a base sobre a qual o tidyverse e o tidymodels de Fundamentos de R para ML são construídos.

Dominar os fundamentos da linguagem R: atribuição, tipos, vetores, recycling, fatores, listas, data.frames, controle de fluxo, funções e a família apply — o suficiente para ler e escrever código R idiomático em projetos de ML.

Atribuição e o operador <-

Em R, a atribuição idiomática usa <- (embora = funcione na maioria dos contextos).

x <- 10            # forma idiomática
y = 5              # funciona, mas evite em código de nível superior
z <- x + y

print(z)           # 15
z                  # no console, digitar o nome já imprime

# Atribuição para a direita também existe (raro)
42 -> resposta

Por que <- e não =? Historicamente, = é reservado para argumentos de função (mean(x, na.rm = TRUE)), e separar os dois evita ambiguidades. Use <- para variáveis, = para argumentos.

Tipos atômicos

R tem seis tipos básicos; os que você usará o tempo todo são quatro:

inteiro   <- 42L           # o "L" força integer
numerico  <- 3.14          # double (padrão para números)
texto     <- "machine learning"
logico    <- TRUE          # ou FALSE (também T / F, mas prefira por extenso)

class(inteiro)   # "integer"
class(numerico)  # "numeric"
class(texto)     # "character"
class(logico)    # "logical"

# Conversões explícitas
as.numeric("3.5")   # 3.5
as.integer(3.9)     # 3 (trunca)
as.character(100)   # "100"
as.logical(0)       # FALSE

O vetor é tudo

Em R não existe escalar — o que parece um número é um vetor de comprimento 1. Isso torna a vetorização natural.

v <- c(10, 20, 30, 40, 50)   # c() = "combine"
length(v)                     # 5
v[1]                          # 10  (indexação começa em 1!)
v[length(v)]                  # 50  (não existe v[-1] para "último")

# Operações são vetorizadas por padrão
v * 2                         # 20 40 60 80 100
v + 100                       # 110 120 130 140 150
sqrt(v)                       # raiz de cada elemento

# Vetores são homogêneos: um só tipo. R "promove" se misturar:
misto <- c(1, "dois", TRUE)   # tudo vira character
class(misto)                  # "character"

Indexação — o ponto que mais confunde quem vem do Python

v <- c(10, 20, 30, 40, 50)

v[1]           # 10  — começa em 1, não em 0
v[c(1, 3, 5)]  # 10 30 50 — indexa por vetor de posições
v[2:4]         # 20 30 40 — intervalo (inclusivo nas duas pontas)
v[-1]          # 20 30 40 50 — índice NEGATIVO REMOVE o elemento!
v[-c(1, 2)]    # 30 40 50 — remove os dois primeiros
v[v > 25]      # 30 40 50 — indexação lógica (máscara)

A armadilha clássica: em Python v[-1] é o último elemento; em R v[-1] remove o primeiro. Para o último, use tail(v, 1) ou v[length(v)].

Recycling (reciclagem)

Se dois vetores têm comprimentos diferentes, R "recicla" o menor. Poderoso e perigoso:

c(1, 2, 3, 4) + c(10, 20)   # 11 22 13 24  (10,20 é reciclado)
c(1, 2, 3) + c(10, 20)      # aviso: comprimento não é múltiplo

Valores faltantes: NA, NULL, NaN

x <- c(1, 2, NA, 4)

mean(x)                # NA — qualquer conta com NA vira NA
mean(x, na.rm = TRUE)  # 2.333 — remove NAs antes

is.na(x)               # FALSE FALSE  TRUE FALSE
sum(is.na(x))          # 1 — conta faltantes

# NA (faltante) ≠ NULL (ausência de objeto) ≠ NaN (0/0)
NULL                   # objeto vazio, comprimento 0
0 / 0                  # NaN

na.rm = TRUE é um argumento que você vai digitar milhares de vezes. Faltantes se propagam silenciosamente — sempre verifique com is.na.

Fatores — categorias com nível

O fator é o tipo de R para variáveis categóricas. Fundamental em modelagem, porque muitos modelos tratam fatores automaticamente.

regiao <- factor(c("sul", "norte", "sul", "leste", "norte"))
regiao
levels(regiao)          # "leste" "norte" "sul"  (ordem alfabética)
as.integer(regiao)      # 3 2 3 1 2  (codificação interna)

# Fator ordenado (para variáveis ordinais)
escolaridade <- factor(
  c("médio", "superior", "fundamental"),
  levels = c("fundamental", "médio", "superior"),
  ordered = TRUE
)
escolaridade < "superior"   # TRUE TRUE FALSE — comparação faz sentido

Cuidado: converter fator para número com as.numeric direto retorna os códigos internos, não os valores. Para fatores que representam números, use as.numeric(as.character(f)).

Listas — o contêiner heterogêneo

Vetores são homogêneos; listas guardam qualquer coisa, inclusive outras listas. Modelos treinados em R são, por baixo, listas.

pessoa <- list(
  nome   = "Ana",
  idade  = 30,
  scores = c(0.82, 0.85, 0.79),
  ativo  = TRUE
)

pessoa$nome         # "Ana" — acesso por nome com $
pessoa[["idade"]]   # 30   — acesso por nome com [[ ]]
pessoa[["scores"]][2]  # 0.85

str(pessoa)         # inspeciona a estrutura (muito útil)
names(pessoa)       # "nome" "idade" "scores" "ativo"

[ ] retorna uma sub-lista; [[ ]] retorna o elemento em si. Essa distinção confunde no começo — [[ ]] "entra" no elemento, [ ] "fatia" a lista.

data.frame — a tabela

O data.frame é a estrutura tabular base (colunas podem ter tipos diferentes; cada coluna é um vetor). O tibble, visto em Fundamentos de R para ML, é uma versão modernizada dele.

df <- data.frame(
  idade  = c(25, 42, 31),
  renda  = c(3200, 8500, 4100),
  regiao = c("sul", "sul", "norte")
)

df$idade            # coluna como vetor
df[1, ]             # primeira linha (todas as colunas)
df[, "renda"]       # coluna renda
df[df$idade > 30, ] # filtro por condição

nrow(df); ncol(df)  # 3   3
names(df)           # "idade" "renda" "regiao"
summary(df)         # resumo estatístico por coluna
head(df, 2)         # primeiras 2 linhas

Controle de fluxo

# if / else
nota <- 7.5
if (nota >= 7) {
  print("aprovado")
} else if (nota >= 5) {
  print("recuperação")
} else {
  print("reprovado")
}

# for — evite quando puder vetorizar
total <- 0
for (i in 1:5) {
  total <- total + i
}
total  # 15

# while
n <- 10
while (n > 0) {
  n <- n - 2
}

# ifelse VETORIZADO — opera sobre vetores inteiros
idades <- c(15, 22, 17, 40)
ifelse(idades >= 18, "adulto", "menor")
# "menor" "adulto" "menor" "adulto"

Em R, if só aceita uma condição de comprimento 1. Para decisões sobre vetores inteiros, use ifelse() (vetorizado) ou, em versões recentes, dplyr::case_when().

Funções

# Definição
media_ponderada <- function(valores, pesos) {
  sum(valores * pesos) / sum(pesos)
}

media_ponderada(c(8, 6, 9), c(0.5, 0.3, 0.2))  # 7.6

# A última expressão é o valor de retorno (return é opcional)
quadrado <- function(x) x^2      # função de uma linha

# Argumentos com valor padrão
normalizar <- function(x, centro = TRUE, escala = TRUE) {
  if (centro) x <- x - mean(x)
  if (escala) x <- x / sd(x)
  x
}

normalizar(c(1, 2, 3, 4, 5))

R é uma linguagem funcional: funções são objetos de primeira classe, podem ser passadas como argumento e retornadas — a base da família apply.

A família apply — vetorização de alto nível

Em vez de loops, R aplica funções sobre estruturas. É mais idiomático e legível.

m <- matrix(1:12, nrow = 3)

apply(m, 1, sum)    # soma por LINHA (margin=1): 22 26 30
apply(m, 2, sum)    # soma por COLUNA (margin=2): 6 15 24 33

# lapply: aplica função a cada elemento, retorna LISTA
lapply(1:3, function(x) x^2)      # list(1, 4, 9)

# sapply: como lapply, mas simplifica para vetor quando possível
sapply(1:3, function(x) x^2)      # 1 4 9

# vapply: sapply com tipo de retorno declarado (mais seguro)
vapply(1:3, function(x) x^2, numeric(1))

# map (purrr) é a versão moderna e consistente

Regra prática: sapply/vapply para resultados simples, lapply quando precisar de lista, apply sobre linhas/colunas de matrizes. No mundo tidyverse, purrr::map_* substitui tudo isso com uma API mais previsível.

Instalando e carregando pacotes

install.packages("tidyverse")   # instala (uma vez)
library(tidyverse)              # carrega (toda sessão)

# Usar função sem carregar o pacote inteiro
dplyr::filter(df, idade > 30)

# Verificar se está instalado
requireNamespace("xgboost", quietly = TRUE)

O <- é a atribuição idiomática; o = fica para argumentos de função. Não existem escalares em R: tudo é vetor, e a indexação começa em 1. O índice negativo não conta do fim como em Python — ele remove elementos, e essa é a primeira armadilha para quem chega. Vetores são homogêneos, enquanto listas e data.frames guardam tipos mistos; os fatores representam categorias, e convertê-los para número sem pensar é fonte clássica de erro silencioso. O NA se propaga por onde passa, daí na.rm = TRUE e is.na(). Prefira vetorização e a família apply ou o purrr::map a laços explícitos. E guarde a distinção que mais confunde: [[ ]] extrai o elemento, [ ] fatia mantendo a estrutura.

Exercícios

  1. Qual a diferença entre v[-1] em R e v[-1] em Python?

    ✓ Resposta:

    Em Python, v[-1] retorna o último elemento (indexação negativa a partir do fim). Em R, v[-1] remove o primeiro elemento e retorna todos os outros. Para obter o último elemento em R, use tail(v, 1) ou v[length(v)]. Essa é uma das confusões mais comuns na migração entre as linguagens.

  2. O que o código abaixo imprime e por quê? > ``r > c(1, 2, 3, 4, 5, 6) + c(10, 20, 30) > ``

    ✓ Resposta:

    Imprime 11 22 33 14 25 36. O vetor menor c(10, 20, 30) é reciclado (recycling): como seu comprimento (3) é múltiplo do comprimento do maior (6), R o repete duas vezes — c(10, 20, 30, 10, 20, 30) — e soma elemento a elemento. Se os comprimentos não fossem múltiplos, R ainda faria a operação, mas emitiria um aviso.

  3. Por que as.numeric(fator) pode dar um resultado errado quando o fator representa números, e como corrigir?

    ✓ Resposta:

    Um fator armazena internamente códigos inteiros (1, 2, 3...) que apontam para os níveis (levels), ordenados alfabeticamente por padrão. as.numeric(fator) retorna esses códigos, não os valores originais. Por exemplo, factor(c("10","20","5")) tem níveis "10","20","5" → códigos 1,2,3 ≠ valores. A correção é as.numeric(as.character(fator)), que primeiro recupera o texto original e depois o converte para número.

  4. Escreva uma função resumo_vetor(x) que retorne uma lista com média, desvio-padrão e número de valores faltantes de x, ignorando NA nos cálculos.

    ✓ Resposta:
    resumo_vetor <- function(x) {
      list(
        media  = mean(x, na.rm = TRUE),
        desvio = sd(x, na.rm = TRUE),
        n_na   = sum(is.na(x))
      )
    }
    resumo_vetor(c(1, 2, NA, 4, 5))
    # $media 3   $desvio 1.826   $n_na 1

    O na.rm = TRUE faz média e desvio ignorarem faltantes; sum(is.na(x)) conta quantos NA existem. Retornar uma lista permite valores de tipos/estruturas diferentes.

  5. Explique a diferença entre df[1, ], df[, 1] e df[1] para um data.frame.

    ✓ Resposta:

    df[1, ] seleciona a primeira linha (todas as colunas), retornando um data.frame de uma linha. df[, 1] seleciona a primeira coluna (todas as linhas) e, por padrão, retorna um vetor (com drop = TRUE). df[1] usa a sintaxe de lista: seleciona o primeiro elemento da lista de colunas, retornando um data.frame de uma coluna (mantém a estrutura). Ou seja, a vírgula distingue seleção linha/coluna; sem vírgula, o data.frame se comporta como lista de colunas.

Fontes e leituras recomendadas