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!—ao seu primeiro encontro com o R. É um encontro de negócios, portanto é bom que o leitor não se tente aproveitar do R. Se o leitor já usou o R, mas nunca chegou a usá-lo regularmente, então iremos acompanhá-lo a um reencontro com o R (continuará a tratar-se duma reunião de negócios). Se o leitor já tiver muita experiência com o R então poderá saltar este capítulo, e só se reencontrar com o R no próximo. Assim o R poderá libertar a sua agenda neste capítulo, para se encontrar com os leitores mais inexperientes.

Como Usar Este Capítulo

  1. Abra o R numa janela e coloque-o lado a lado da janela do programa onde está a ler este livro.

  2. Leia este capítulo com atenção. Quando vir blocos de código destacados:

# Exemplo de comentário
cat("Hello World!\n")
Hello World!

Escreva, no R, sem fazer copy-paste, exactamente o que está no bloco.

  1. Por favor, evite mesmo fazer copy paste do código para o R. A ideia é praticar escrever correctamente os comandos no R sem cometer erros.

  2. Se o código que escreveu no R der algum erro e se este capítulo não indicar que esse erro era esperado, reescreva o código tentando perceber onde possa estar o erro.

  3. Caso o erro persista, pode fazer copy-paste apenas desse bloco de código. Se mesmo assim voltar a dar erro, por favor, reporte o erro para o email de apoio ao leitor (contact-project+rggd-moc-34811360-issue-@incoming.gitlab.com ou para joao.santos@ispa.pt).

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

As Primeiras Palavras

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

Podemos começar pelo exemplo anterior:

# Exemplo de comentário
cat("Hello World!\n")
Hello World!

Na primeira linha temos um exemplo dum comentário. Um comentário nada mais é que uma linha começada por um # que o R ignora (i.e., que não tenta executar). Assim sendo, podemos escrever nas linhas começadas por # o que quisermos sem ter de ser em código. A segunda linha contém a invocação de uma função, chamada cat() e a sequência de caracteres "Hello World!\n" é o seu primeiro e único argumento. Não se preocupe se nada disto estiver a fazer muito sentido, é normal que isso aconteça, até porque pode ser a primeira vez que se depara com estes termos.

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

# Usa a receita help para pedir ajuda sobre a receita mean.
# A função help mostra-nos o manual de cada função/comando.
# Usa a função help para mostrar o manual da função mean.
help(mean)

No futuro, quando escrever os seus scripts de análise de dados é recomendado que deixe também comentários a descrever os passos da análise. Assuma sempre que o seu código vai ser lido por outras pessoas nem que seja o seu eu do futuro.

You mostly collaborate with yourself, and me-from-two-months-ago never responds to email. (attributed to Mark Holder)

Pronto para descobrir o R?

Aqui vamos!

# Usa a receita help para pedir ajuda sobre como usar a receita help.
# A função help mostra-nos o manual de cada função/comando.
# Usa a receita help para mostrar o manual da função help.
help(help)

# Faz a receita help (abreviada para ?) com o igrediente help.
# No `R` podemos usar o '?' como atalho para a função help.
# O resultado é exactamente o mesmo.
?help

# Faz uma preparação chamada "variavel" com o resultado de 3 + 2
# No `R` um "<-" ou "=" atribuem o valor à direita à variável à esquerda.
# Cria uma variável chamada "variavel" para guardar o output de 3 + 2
variavel <- 3 + 2

# Podemos dar o nome que quisermos às variáveis exemplo:
fadfadsfasdf <- 4
Aviso

Contudo os nomes não devem ter acentos, espaços, cedilhas, números, ou caracteres especiais como “$ % [] ( ) & ^ # ? ! + - /  ’” *“

Nota

Porquê?

Porque muito dos avanços iniciais na computação ocorreram nos EUA. O Inglês não tem acentos e cedilhas e os computadores inicialmente também não os tinham. A adição de caracteres de outras línguas é posterior e existe mais que uma forma dos codificar, podendo causar problemas de compatibilidade entre sistemas. Os espaços são usados pelo R e outras linguagens para separar elementos/tokens, podendo também ser usados para separar argumentos (e.g., POSIX shells) ou definir identação que afecte o fluxo do código (e.g., python). Já os caracteres especiais (e.g., “$ % [] ( ) & ^ # ? ! + - /  ’” *“) são usados pelo R e outras linguagens de programação com significados específicos na linguagem.

Também devemos evitar usar acentos, espaços, cedilhas e caracteres especiais em nomes de ficheiros. Por exemplo devemos usar “base_de_dados.csv” (sem espaços) e não “base de dados.csv”.

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

# Se eu tentar dar este nome a uma variável o `R` vai dar um erro.
NA <- 2
Error in `NA <- 2`:
! invalid (do_set) left-hand side to assignment
# Faz uma preparação chamada dados onde junta o resultado da receita c
# feita com os ingredientes 3,2,4,6,3,2,4,1.
# A função c é muito útil porque junta tudo o que quisermos num vector
# Guarda o outupt da função c invocada com 3,2,4,... como argumentos.
dados <- c(3,2,4,6,3,2,4,1)

# Na preparação dados guardo o resultado da receita c feita com os
# ingredientes dados e variavel.
# A função c também cria vectores de outros vectores.
# Guarda o outupt da função c, invocada com dados e variavel como
# argumento na variável dados.
dados <- c(dados, variavel)

# Se me enganar a escrever o nome delas invocarei algo que não existe...
# Nesse caso o `R` dará erro.
# Exemplo: só uma das variáveis abaixo existe, tente descubrir qual.
fadfadsfasdf
[1] 4
fadfasdfasdf
Error:
! object 'fadfasdfasdf' not found
# Posso também atribuir outro valor a uma variável que já exista.
# Isto quer dizer que ela deixa de ter o valor que tinha anteriormente.
# Portanto perco toda a informação que estava na variável.
dados <- "teste"

# Neste caso a preparação dados já só tem a palavra teste.
dados
[1] "teste"

É o mesmo que fazer uma mistura numa taça que já tinha lá coisas. Se quiser usar a mesma taça tenho que pôr fora o que lá estava.

Se quiser guardar o que está nessa taça tenho que ir buscar uma nova. Se não quero alterar o valor duma variável posso sempre criar outra.

# Neste caso a taça dados continua a só ter a palavra "teste".
# Mas posso criar outra taça/variável, exemplo: novos_dados.
# Cria a variável novos_dados onde guarda o output da função c.
novos_dados <- c(100, 100, 200, 154, 300)

# Cria a preparação resultados onde junta o resultado da receita mean.
# Usa a receita mean com o igrediente novos_dados.
# Invoca a função mean com o argumento novos_dados.
resultados <- mean(novos_dados)

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

help(mean)

Vemos que a função mean computa a média do objecto que lá colocarmos.

# A variável resultados guarda agora a média dos novos_dados.
resultados
[1] 170.8

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

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

Normalmente só temos de especificar um ou dois desses ingredientes. As funções/receitas têm valores padrão para os restantes ingredientes.

É como pedirmos a um chefe para fazer um bitoque de vaca. Só temos de dizer o ponto da carne (bem, média, mal-passada). Tirando isso ele vai fazer o resto do bitoque como costuma fazer.

Contudo, se quisermos um outro molho ou um ovo a cavalo temos de pedir.

Se o R fizesse bitoques seria algo assim:

help(mean)
# Para um bitoque bem passado chamaríamos a função bitoque.
# Teríamos apenas que definir um valor para o argumento ponto, exemplo:
bitoque(cooked_to = "medium-rare")
Error in `bitoque()`:
! could not find function "bitoque"
# Para pedir um ovo a cavalo teríamos de acrescentar esse argumento.
bitoque(cooked_to = "medium-rare", egg = TRUE)
Error in `bitoque()`:
! could not find function "bitoque"

Como viram o R deu erro dizendo que não tem essa função. Mesmo que reescrevam o código ou até façam copy-paste continuará sem os conseguir fazer… Concluímos portanto que o R, infelizmente, não faz bitoques ;(

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

# Usa a receita mean com novos_dados como o ingrediente x.
# Usa 0.2 como o ingrediente trim.
# Guarda em resultados_robustas o output da função mean, invocada com
# os argumentos trim = 0.2
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
NotaTermo técnico: syntactic sugar

Em programação, syntactic sugar é uma sintaxe alternativa para exprimir uma operação que a linguagem já consegue representar. A página Syntactic sugar mostra outros exemplos do termo.

No caso simples, o R transforma x |> f(y) em f(x, y) quando analisa o código. quote() permite-nos ver a chamada construída sem a executar:

quote(c(1, 2, 3) |> mean())
mean(c(1, 2, 3))

A documentação oficial de |> descreve esta transformação sintáctica.

Usamos o pipe nativo do R, disponível desde a versão 4.1 e sem necessidade de pacotes. Poderão encontrar %>% em código baseado no tidyverse, mas os exemplos deste capítulo usam |>.

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.

O tutorial 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 sequer analisar esta expressão.
mean(novos_dados]
Error in parse(text = input): <text>:2:17: unexpected ']'
1: # O R não consegue sequer analisar 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 é válida, mas não existe uma função chamada meam().
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 mesmo 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 muito cuidado ao escrever receitas. A boa notícia é 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 em que o problema foi encontrado. Depois ainda temos de a ler, claro. O R ajuda, mas recusa-se teimosamente a fazer a cadeira por nós.

Como um pequeno erro pode impedir a execução da receita/script, é boa ideia testar o código à medida que o escrevemos. Se surgir um erro, lemos a mensagem e tentamos perceber o que está mal. Fazemos as alterações que acharmos necessárias e voltamos a tentar. Se voltar a dar erro, lemos a mensagem com mais atenção e tentamos pensar com mais calma no que possa estar mal. Fazemos mais alguma(s) correcção(ões) e voltamos a correr o código. Repetimos estes passos sucessivamente até deixarmos de ter erros. Se nada funcionar podemos procurar na internet se alguém teve um erro semelhante e que soluções foram propostas.

O processo de detectar e corrigir erros no código chama-se debugging.

Atenção: existe um tipo de erros pior do que os anteriores. O R avisa-nos quando não consegue analisar uma expressão ou encontrar uma função.

Contudo o R não detecta automaticamente os erros semânticos: código que é válido e executável, mas que diz correctamente ao computador para fazer algo diferente do que pretendíamos.

# Erro semântico óbvio
media_novos_dados <- sum(novos_dados) / novos_dados

Neste caso o R não dá erro nenhum mas o resultado não está certo.

# Compara o resultado da nossa receita com o da função mean.
media_novos_dados == mean(novos_dados)
[1] FALSE FALSE FALSE FALSE FALSE

Vemos que o R nos diz que dá FALSE. Ou seja o nosso resultado não é igual à média.

Qual será o problema?

A média é a divisão da soma das observações pelo número de observações mas nós dividimos a soma das observações pelas próprias observações. Sabendo qual foi o nosso erro podemos corrigi-lo.

# A função length diz-nos quantos objectos estão no nosso vector.
# Podemos usá-la para corrigir o nosso cálculo da média.
media_novos_dados <- sum(novos_dados) / length(novos_dados)

# Comparamos novamente o nosso resultado com o do `R`.
media_novos_dados == mean(novos_dados)
[1] TRUE

Agora o R diz que é verdade (TRUE) que o nosso resultado é igual à média.

Nem todos os casos serão tão óbvios como o anterior. Nem poderemos comparar os nossos resultados com os das funções do R, até porque às vezes nos podemos enganar com as próprias funções do R.

Só nós podemos descobrir os erros semânticos.

Então o R é mesmo pior que o SPSS e todos os outros softwares?

Não, nesses softwares também podemos ter erros semânticos. Podemos clicar num determinado teste quando queríamos um outro, seleccionar a variável errada para o teste ou alterar/apagar sem querer um valor. Como no R escrevemos em vez de clicarmos em menus e o R dá-nos mensagens de erro úteis, até pode ser mais fácil detectarmos os nossos erros.

Neste sentido, aconselho que comecem por tentar replicar no R análises simples que fizeram noutros programas, antes de tentarem usar o R para fazerem as análises que os outros programas não conseguem fazer.

Agora que já percebemos o funcionamento básico do R devemos tentar escrever o máximo de receitas possível, assim como devemos ler as receitas que outros escreveram para aprendermos a falar R!