Aprender a falar R

Programação
Introdução
Aprender a escrever os primeiros comandos e a ler os erros do R.

0 Aprender a Falar R

Neste capítulo vamos acompanhar o leitor—sim, tu!—no seu primeiro encontro com o R. É um encontro de negócios, portanto é bom que o leitor não tente aproveitar-se do R. Se já usou o R, mas nunca o usou regularmente, este será um reencontro; se já tiver bastante experiência, pode saltar para o capítulo seguinte.

Como Usar Este Capítulo

Este capítulo pressupõe que o R já está a funcionar. Se ainda não instalou o R, ou nunca o usou e precisa de ajuda para escolher um programa onde escrever código, comece por Instalar o R. Aí explicamos a diferença entre o R e um editor ou IDE, antes de avançarmos para a sintaxe.

  1. Abra o R numa janela, lado a lado com esta página.

  2. Execute primeiro este pequeno exemplo. Quando vir blocos de código:

# Um comentário é ignorado pelo R.
cat("Hello World!\n")
Hello World!

escreva no R, sem copiar e colar, exactamente o que está no bloco.

  1. Evite copiar e colar o código para o R: escrever os comandos ajuda a praticar a sintaxe. Copie apenas quando um erro persistir.

  2. Se o código que escreveu der erro, e o capítulo não disser que esse erro era esperado, volte a escrevê-lo e tente perceber onde se enganou.

  3. Se o erro persistir, copie e cole apenas esse bloco. Se mesmo assim voltar a dar erro, pode enviar-me a mensagem de erro para joao.santos@ispa.pt.

Nota: este capítulo foi inspirado pela metodologia de ensino da série Learn Code The Hard Way de Zed A. Shaw.

1 As Primeiras Palavras

Ao longo deste capítulo encontrará sucessivos blocos de código.

Podemos começar pelo exemplo anterior:

# Um comentário é ignorado pelo R.
cat("Hello World!\n")
Hello World!

Na primeira linha temos um comentário. Um comentário é uma linha começada por # que o R ignora, isto é, que não tenta executar. Podemos portanto escrever nas linhas começadas por # o que quisermos sem ter de falar em código. A segunda linha invoca uma função chamada cat(). A sequência de caracteres "Hello World!\n" é o seu primeiro e único argumento. Se nada disto estiver a fazer muito sentido, não faz mal. Pode ser a primeira vez que encontra estes termos e vamos voltar a eles várias vezes.

O objectivo dos comentários é explicar o código. Neste tutorial, o primeiro comentário tentará descrever o que o código faz através duma analogia culinária. O segundo dará informação específica sobre aquele comando. O último traduzirá a ideia para uma linguagem mais técnica/computacional.

# Peça ao chef R a receita chamada mean.
# Queremos perceber como funciona mean().
# help() mostra a página do manual de uma função.
help(mean)

Quando escrever os seus próprios scripts de análise, deixe também comentários que expliquem os passos importantes. Assuma que o código vai ser lido por outra pessoa, nem que essa pessoa seja apenas o seu eu do futuro.

Nestes dois primeiros tutoriais, os comentários de código estão em português para que a própria sintaxe do R seja a principal novidade. A partir dos tutoriais seguintes, os comentários passam para inglês. É uma escolha deliberada: grande parte da documentação, dos exemplos de código e da colaboração entre equipas usa inglês, e convém ganhar esse hábito aos poucos.

You mostly collaborate with yourself, and me-from-two-months-ago never responds to email.

(attributed to Mark T. Holder)

Pronto para descobrir o R?

Vamos a isso!

Dizemos muitas vezes que o R é uma linguagem interpretada: escrevemos uma expressão e o interpretador avalia-a. Num compilador tradicional, o programa é traduzido antes de ser executado. Na prática, o R combina estas técnicas: compila bytecode e usa compilação JIT (just-in-time) em certas partes do código. O próprio R e muitas das suas bibliotecas também usam linguagens como C e Fortran.

código R
   ↓
interpretador / bytecode / JIT
   ↓
código nativo e bibliotecas compiladas
   ↓
instruções de máquina
   ↓
CPU

O assembly é uma representação textual, legível por humanos, de instruções muito próximas da arquitectura do processador. Um compilador pode gerar código de máquina directamente, sem produzir um ficheiro de assembly.

O processador executa instruções codificadas em padrões de bits, representados habitualmente por zeros e uns. Entre mean(x) e esses bits há várias camadas de software. Isso permite-nos programar sem pensar continuamente na electrónica do processador.

# Peça ao chef R a receita help.
# Queremos consultar a página do manual de help().
# Chame help() e indique help como tópico.
help(help)

# O atalho ? abre a mesma página de ajuda.
?help

2 Variáveis e Objectos

Uma variável pode assumir valores diferentes; se mantivesse sempre o mesmo valor, seria uma constante. Em matemática e em programação, a palavra tem sentidos próprios, e nos estudos também usamos variável para características que medimos ou manipulamos.

Em estatística, uma variável costuma ser uma característica medida, manipulada ou usada no delineamento do estudo, como idade, temperatura ou condição experimental. Os seus valores podem variar entre unidades ou observações e o seu papel depende da pergunta e do delineamento.

Em programação, variável costuma designar um nome associado a um valor que pode mudar durante a execução. No R, esse valor é um objecto. Para clarificar de que estamos a falar, neste site preferimos objecto ou nome de objecto para o lado da programação. Outros textos de programação usam variável com o mesmo sentido.

Às vezes o objecto guarda uma variável do estudo, como a coluna da idade. Outras vezes guarda um resultado ou um modelo: modelo, resultado e media_idade são nomes de objectos do programa, não características medidas nos participantes.

# Guarde o resultado de 3 + 2 num objecto chamado variavel.
variavel <- 3 + 2

# O R aceita este nome; quem ler o código mais tarde pode não agradecer.
fadfadsfasdf <- 4

# Este identificador com acento é válido nesta sessão.
média <- 2
Aviso

Os nomes têm regras: um nome comum não pode começar por um algarismo e muitos sinais de pontuação têm significado próprio no R. NA, por exemplo, é um nome reservado. O R moderno pode aceitar letras Unicode, como as de média, mas o conjunto exacto de letras reconhecidas num identificador depende da configuração regional (locale).

No NRB, recomendamos nomes com letras do alfabeto inglês, sem cedilhas nem acentos (ASCII), em snake_case, como media_idade. Esta convenção facilita a portabilidade, a pesquisa e a colaboração.

Um registo de 1969 do ASCII apresenta-o como um código de 7 bits para intercâmbio entre sistemas de informação e comunicação. A sua tabela limitada servia esse propósito de intercâmbio. Já existiam outras codificações, pelo que este registo cobre apenas uma parte da história da computação.

O Unicode foi desenvolvido mais tarde para dar uma codificação comum a sistemas de escrita muito mais numerosos e reduzir incompatibilidades entre codificações. No R actual, a definição dos identificadores continua a depender do locale, embora inclua letras acentuadas em muitas configurações. As operações com ficheiros também tratam caminhos com codificações marcadas, mas a conversão depende do sistema operativo e da codificação disponível.

Assim, média e dados_açores.csv podem funcionar. Preferimos media e dados_acores.csv quando os nomes terão de atravessar sistemas de ficheiros, terminais, editores e computadores diferentes. O texto guardado nos dados deve conservar os acentos de que precisa.

Por convenção, usamos <- para fazer atribuições. O operador = também pode atribuir em alguns contextos, mas aparece frequentemente a separar argumentos nomeados numa função. Enquanto aprendemos, <- torna mais fácil distinguir as duas situações.

Devemos também evitar nomes que já tenham um propósito no R. Por exemplo, um dado em falta no R é representado por NA.

# O R não permite atribuir desta forma a este nome reservado.
NA <- 2
Error in `NA <- 2`:
! invalid (do_set) left-hand side to assignment
# Combine vários valores num vector.
dados <- c(3, 2, 4, 6, 3, 2, 4, 1)

# Combine um vector que já existe com mais um valor.
dados <- c(dados, variavel)

# Compare um nome existente com um nome mal escrito, que dá erro.
fadfadsfasdf
[1] 4
fadfasdfasdf
Error:
! object 'fadfasdfasdf' not found
# Voltar a atribuir ao mesmo objecto substitui o valor anterior.
dados <- "teste"

# dados contém agora apenas a palavra teste.
dados
[1] "teste"

É como fazer uma mistura numa taça onde já estava outra coisa. Se quisermos usar a mesma taça, temos de deitar fora o que lá estava.

Se quisermos guardar o conteúdo dessa taça, temos de ir buscar outra. Da mesma forma, se não queremos alterar o valor dum objecto, podemos criar um novo.

# Mantenha dados inalterado e crie outro objecto.
novos_dados <- c(100, 100, 200, 154, 300)

# Guarde a média de novos_dados.
resultados <- mean(novos_dados)

O que faz a função/receita mean()? Usemos a função help() para descobrir.

help(mean)

Vemos que mean() calcula a média do objecto que lhe dermos.

# resultados contém agora a média de novos_dados.
resultados
[1] 170.8

Funções e Argumentos

Mas afinal o que são estes “argumentos” que pomos nas funções?

As funções são como receitas e normalmente levam vários ingredientes. Os argumentos das funções são esses ingredientes.

Muitas vezes só temos de especificar um ou dois. Para os restantes, a função já tem valores por defeito.

É como pedirmos a um chef para fazer um bitoque de vaca. Temos de dizer, por exemplo, o ponto da carne. Tirando isso, ele fará o resto do bitoque como costuma fazer. Se quisermos outro molho ou um ovo a cavalo, temos de pedir.

Se o R fizesse bitoques, seria algo assim:

# Esta função é deliberadamente inventada.
# Para pedir a carne mal passada, indicaríamos esse valor no argumento cooked_to.
bitoque(cooked_to = "medium-rare")
Error in `bitoque()`:
! could not find function "bitoque"
# egg = TRUE pediria um ovo a cavalo.
bitoque(cooked_to = "medium-rare", egg = TRUE)
Error in `bitoque()`:
! could not find function "bitoque"

Como viu, o R deu erro porque não tem essa função. Mesmo que reescreva o código ou copie e cole, continuará sem conseguir fazer um bitoque. Concluímos portanto que o R, infelizmente, não faz bitoques.

Vamos então ver outros exemplos do que ele faz.

# Calcule uma média aparada.
resultados_robustas <- mean(x = novos_dados, trim = 0.2)

Podemos usar o output de uma função como argumento de outra. Por exemplo, c() cria o vector que mean() recebe:

outra_media <- mean(c(500, 300, 600, 300, 400))

Chamadas encastradas curtas são fáceis de ler. Quando a sequência cresce, podemos criar objectos intermédios para inspeccionar, reutilizar ou depurar cada passo.

3 Ler uma Sequência com o Pipe Nativo

O pipe nativo permite escrever uma sequência de transformações de cima para baixo:

c("1", "2", "6", "9") |>
    as.numeric() |>
    mean() |>
    round(digits = 1)
[1] 4.5

Em inglês, pipe significa «tubo»: |> transporta o valor produzido à esquerda para a função seguinte. Podemos lê-lo como «e depois». Por defeito, esse valor ocupa o primeiro argumento da função à direita.

A cadeia anterior corresponde à chamada encastrada abaixo:

round(mean(as.numeric(c("1", "2", "6", "9"))), digits = 1)
[1] 4.5

A documentação oficial de |> descreve o caso simples como uma transformação sintáctica: ao analisar x |> f(y), o R constrói f(x, y). Para já, basta conseguir comparar a cadeia com a chamada encastrada acima.

Usamos o pipe nativo do R, disponível desde a versão 4.1 e sem necessidade de pacotes. Poderá encontrar %>% em código baseado no tidyverse, mas é outro operador. O guia de estilo do tidyverse para pipes também recomenda |> para código novo e desaconselha cadeias quando há objectos intermédios a que vale a pena dar nomes.

Escolhemos a organização que torna o cálculo mais claro:

  • encastramos expressões curtas;
  • criamos objectos intermédios para inspeccionar ou reutilizar resultados;
  • usamos |> em sequências com uma direcção clara.

O estilo de código reúne escolhas sobre espaços, indentação, nomes e mudanças de linha. Um linter também pode assinalar problemas prováveis, como nomes usados sem definição; um formatador reorganiza o código segundo regras consistentes. Cabe-nos interpretar os resultados e avaliar se a análise responde à pergunta do estudo.

  • Num projecto colaborativo, seguimos primeiro as convenções que já existem.
  • O RStudio inclui diagnósticos de código configuráveis; consulte a documentação sobre diagnósticos.
  • Em R, jarl verifica o código, e air formata-o. Use estas ferramentas quando o projecto as adoptar.

A referência sobre funções desenvolve estas ideias. Aí veremos com mais calma como usar outputs como argumentos e como passar funções como argumentos de outras funções.

Erros de Sintaxe e de Nome

O R é uma linguagem formal.

Uma linguagem formal define símbolos e regras para construir expressões válidas. Ao contrário do que fazemos numa conversa, o R não tenta adivinhar que expressão queríamos escrever.

Podemos cometer um erro de sintaxe: escrever uma expressão que não respeita as regras gramaticais do R. No exemplo seguinte fechamos parêntesis com um parêntesis recto.

# O R não consegue interpretar esta expressão.
mean(novos_dados]
Error in parse(text = input): <text>:2:17: unexpected ']'
1: # O R não consegue interpretar esta expressão.
2: mean(novos_dados]
                   ^

Também podemos escrever uma expressão sintacticamente válida que tenta usar um nome inexistente.

# A chamada é sintacticamente válida, mas meam() não existe.
meam(novos_dados)
Error in `meam()`:
! could not find function "meam"

O R tem uma função para calcular a média, mas chama-se mean() e não meam(). O interpretador, o chef que o R traz consigo, não corrigiu o nosso lapso: tentou encontrar a receita meam() e informou-nos de que esse objecto não existia. Este é um erro de nome ou de avaliação, não um erro de sintaxe.

Como se diz na tropa, o material tem sempre razão: o R executa as instruções que lhe damos, não as que tínhamos em mente. Por isso, temos de escrever o código com cuidado. Quando nos enganamos, o R costuma deixar pistas na mensagem de erro. Num script com muitas linhas, essa mensagem também pode indicar a linha onde o problema foi encontrado. Depois ainda temos de a ler, claro.

Como um pequeno erro pode impedir a execução dum script, é boa ideia testar o código à medida que o escrevemos. O carácter interactivo do R torna isto particularmente fácil: podemos experimentar uma expressão na consola, ver o resultado imediatamente e só depois acrescentar ao script a versão que queremos conservar.

Quando surge um erro, lemos a mensagem, tentamos perceber o que está mal, fazemos uma alteração e voltamos a executar. Se continuar a falhar, repetimos. Uma alteração de cada vez. Assim conseguimos perceber o que corrigiu o problema, em vez de mudar várias coisas ao mesmo tempo até o erro desaparecer sem sabermos porquê.

Se ficarmos presos, podemos procurar a mensagem de erro e ver como outras pessoas diagnosticaram problemas semelhantes. O processo de detectar e corrigir erros no código chama-se debugging.

Da Consola ao Script

Na consola, escrevemos uma expressão e vemos logo o resultado. No editor, guardamos num script as instruções que queremos voltar a executar. Na documentação dos painéis do RStudio, localize o editor (Source) e a consola (Console):

Experimente na consola e conserve no script os passos necessários. No fim, reinicie o R sem restaurar a sessão anterior e execute o ficheiro de cima para baixo. No tutorial de limpeza veremos um script que só funciona porque ficou um objecto na memória.

Erros Semânticos

Há um tipo de erro mais difícil. O R avisa-nos quando não consegue analisar uma expressão ou encontrar uma função. Pode, porém, executar perfeitamente aquilo que escrevemos e ainda assim não fazer o que queríamos. A estes podemos chamar erros semânticos.

# Um erro semântico óbvio.
media_novos_dados <- sum(novos_dados) / novos_dados

Neste caso, o R não dá erro, mas o resultado não é a média que pretendíamos.

# Compare o nosso cálculo com mean().
media_novos_dados == mean(novos_dados)
[1] FALSE FALSE FALSE FALSE FALSE

O resultado é FALSE: o nosso cálculo não coincide com mean().

Qual será o problema?

A média é a soma das observações dividida pelo número de observações. Nós dividimos a soma pelas próprias observações. Agora que encontrámos o erro, podemos corrigi-lo.

# length() conta os elementos do vector.
# Use-a para corrigir o cálculo.
media_novos_dados <- sum(novos_dados) / length(novos_dados)

# Compare o cálculo corrigido com a média calculada pelo R.
media_novos_dados == mean(novos_dados)
[1] TRUE

Agora o R devolve TRUE: o nosso resultado coincide com a média calculada por mean().

Nem todos os casos serão tão óbvios, nem teremos sempre uma função do R com a qual comparar o resultado. Às vezes podemos até usar correctamente uma função para responder à pergunta errada.

É por isso que os erros semânticos precisam de nós.

Então o R é pior do que o SPSS e os outros programas?

Não. Nesses programas também podemos escolher um teste quando queríamos outro, seleccionar a variável errada ou alterar um valor sem querer. Como no R escrevemos os passos em vez de apenas clicar em menus, ficamos com um registo que podemos voltar a ler e testar. Isso pode até tornar alguns erros mais fáceis de encontrar.

Se está a começar, uma boa estratégia é tentar primeiro replicar no R análises simples que já fez noutros programas. Assim aprende a linguagem sem ter de aprender ao mesmo tempo uma análise completamente nova.

Agora que já percebemos o funcionamento básico do R, resta praticar: escrever receitas, ler as que outras pessoas escreveram e, aos poucos, aprender a falar R.