Aprender a falar R

Programação
Introdução
As primeiras palavras no R.
Autores
Afiliações

João O. Santos

ISPA

Cristina Mendonça

ISPA; WJCR

Vitória Melita

FP-UL

Mariona Pascual Peñas

UIB

João Raposo

ISPA

Marta Barros

FP-UL

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 o instalou, siga primeiro Instalar o R; as instruções para cada sistema operativo e a escolha dum editor ficam nessa página para não interrompermos esta primeira lição de 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.

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 Holder)

Pronto para descobrir o R?

Vamos a isso!

# 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

Variáveis e Objectos

A palavra variável parece mais técnica do que é. Uma variável é, literalmente, alguma coisa que pode variar: pode assumir mais do que um valor. Se só pudesse ter sempre o mesmo valor, seria uma constante. A definição matemática torna esta ideia mais precisa, mas não a torna mais misteriosa.

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, esses valores são objectos e os nomes ficam associados a esses objectos. Para não misturarmos os dois sentidos, neste site vamos preferir objecto e nome de objecto quando estivermos a falar de programação. Quando encontrar a palavra variável noutro texto de programação, porém, não há uma segunda coisa misteriosa escondida no nome. É apenas o uso informático da mesma ideia.

Às vezes os dois sentidos coincidem. Um objecto pode guardar uma coluna que representa uma variável estatística. Outras vezes não: modelo, resultado ou media_idade podem ser objectos úteis no script sem serem variáveis do estudo.

# 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 ASCII — as letras do alfabeto inglês sem cedilhas nem acentos — em snake_case, como media_idade. É uma convenção prática para facilitar a portabilidade, a pesquisa e a colaboração, não uma afirmação de que o R só aceita inglês ou caracteres ASCII.

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 deve ser entendida nesse contexto histórico e técnico; não resume a história mundial da computação nem todas as codificações que já existiam.

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 quiser usar a mesma taça, tenho de deitar fora o que lá estava.

Se quiser guardar o conteúdo dessa taça, tenho de ir buscar outra. Da mesma forma, se não quero alterar o valor dum objecto, posso 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

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.

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. Não precisamos de inspeccionar objectos da linguagem para compreender a transformação.

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.
NotaO que é o estilo de código?

O estilo de código reúne escolhas sobre espaços, indentação, nomes e mudanças de linha. Não altera o cálculo, mas pode poupar bastante tempo a quem tenta percebê-lo.

  • Num projecto colaborativo, respeitamos primeiro o estilo que já existe.
  • Uma equipa pode adoptar um guia, como o Tidyverse style guide.
  • Onde o projecto ou o guia nada disserem, desenvolvemos preferências com a prática.

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.

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.

Em conclusão, temos de estar atentos às receitas que escrevemos. Como se diz na tropa: o material tem sempre razão.

Temos então uma boa e uma má notícia. A má notícia é que temos de ter cuidado a escrever código. A boa é que, 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. 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 o processo. Isto é muito mais útil do que 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.

Há, no entanto, um tipo de erro mais difícil. O R avisa-nos quando não consegue analisar uma expressão ou encontrar uma função. Não consegue avisar-nos sempre que o código executou perfeitamente aquilo que escrevemos, mas aquilo que escrevemos não era o que queríamos fazer. 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.