A Gramática do R

Programação
Introdução
As primeiras frases no R. Pode ser usado como referência sobre os básicos da linguagem.
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 A Gramática do R

No tutorial anterior estivemos a ver como podemos aprender a falar R. No fundo estivemos a aprender as nossas primeiras palavras.

O objectivo deste tutorial é ajudar a consolidar esses conhecimentos, tentando aprender a gramática do R e aprendendo a escrever frases simples.

Apesar deste tutorial e do anterior não estarem muito relacionados com estatística, estes tutoriais são fundamentais para que construam boas bases do R. Só assim conseguirão ler código R e isso será uma competência essencial para realizar análises estatísticas no R, independentemente da sua complexidade. Aliás, assim que conseguirem ler R conseguirão perceber o código das análises mais complexas, mesmo que não entendam logo à partida a teoria estatística subjacente. Portanto, todo o tempo investido nestes tutoriais e nos seus exercícios trará muitas recompensas no futuro.

Reparem que podem sempre voltar a reler estes tutoriais quando encontrarem “palavras” ou “frases” em scripts de R que tenham dificuldade em compreender.

Variáveis

No R como nas outras linguagens de programação podemos criar variáveis. As variáveis podem guardar vários tipos de informação, por exemplo, texto, números, ou listas.

Reparem que a definição de variável na estatística é diferente da definição de variável nas linguagens de programação. Em ambos os casos as variáveis contêm valores que podem variar. Contudo, em programação as variáveis são definidas pelos programadores e vão sendo alteradas ao longo dos programas. Por outro lado, na estatística as variáveis correspondem ao que foi observado. É fácil de confundirmos os dois conceitos quando usamos o R para análise estatísticas, dado que as nossas variáveis no R (variáveis da programação) podem corresponder a variáveis do nosso estudo (variáveis estatísticas), mas também podem conter outras informações. Para distinguir os dois conceitos, podem usar o termo “objectos” para se referirem a variáveis do ponto de vista da programação, reservando o termo “variável” para variáveis estatísticas. Dito, neste tutorial em concreto, usamos variável com a conotação das linguagens de programação para que se possam habituar a esse uso.

Criar Variáveis

Para criar uma variável escrevemos o nome que lhe queremos dar à esquerda do sinal <- (i.e., o caractere de < e o - sem espaço) e o valor que lhe queremos dar à direita.

# Criar ou alterar variáveis.
variavel <- "sou uma variável"
outra_variavel <- "afinal havia outra"
um_numero <- 10
Nota

Quem esteja familiarizado com outras linguagens de programação pode estranhar o sinal <- por estar habituado a usar o sinal de igual (=). No R ambos podem ser usados para a criação/redefinição de variáveis, mas é considerado mais idiomático escrever <-.

Podem ir buscar o que ficou guardado nas variáveis mais tarde, por exemplo, “imprimindo”/print() a variável.

print(variavel)
[1] "sou uma variável"
print(outra_variavel)
[1] "afinal havia outra"
print(um_numero)
[1] 10
Nota

O termo imprimir vem do tempo em que os computadores não tinham monitores/ecrãs estando ligados a impressoras e portanto os outputs eram impressos em vez de exibidos num ecrã.

Experimentem agora criar várias variáveis guardando números e palavras. Tomem nota dos erros que encontrem e das mensagens de erro que sejam geradas. Tentem perceber o que faz com que o código funcione e o que causa erro.

Alterar Variáveis

Como o nome indica, as variáveis, podem variar, ou seja, podem alterar o seu valor.

Aviso

Sempre que atribuírem um novo valor a uma variável, ela deixará de ter valor antigo para passar a ter o novo. Ou seja, assim que alterarem o valor duma variável, nunca mais terão acesso à informação que lá estava anteriormente. Por outro lado, passarão a ter acesso à nova informação que lá guardaram quando invocarem a variável no futuro. Portanto dependendo do objectivo pode ser muito útil ou muito perigoso alterar o valor duma variável.

# Altera o valor da variável `variavel` para 3.
variavel <- 3

# Imprime a variável `variavel`.
print(variavel)
[1] 3
# Altera o valor da variável `variavel` para 2000.
variavel <- 2000

# Imprime a variável `variavel`.
print(variavel)
[1] 2000

Experimentem alterar e criar variáveis, na vossa sessão do R. Tomem nota dos casos em que se deparem com erros e tentem corrigi-los após lerem com atenção as mensagens de erro.

Nomes das variáveis

Podem dar o nome quiserem às variáveis desde que:

  1. Não comecem com um número (e.g., var1 nunca 1var).

  2. Não coloquem espaços no nome da variável (e.g., base_de_dados nunca base de dados).

  3. Não usem caracteres especiais como: ", -, *, \, /

Devem também evitar usar letras não façam parte do alfabeto inglês para garantir que o vosso código é o mais compatível possível com diversos sistemas e configurações. Ou seja, evitem usar caracteres como: “ã, õ, á, à, é, etc…”

isto_e_um_nome_possivel <- "teste"

# Outro nome possível.
dfaldsfasdf <- 300

Reparem que quando as variáveis guardam informação textual (i.e., letras, palavras, frases, etc…) essa informação pode ter espaços e caracteres especiais.

nome_sem_espacos <- "Mas o texto pode ter espaços"

nome_sem_caracteres_especiais <- "O texto também pode ter acentos e caracteres especiais * - /"
Nota

Por motivos históricos os computadores começaram por só saber representar as letras do alfabeto inglês (os interessados podem ler sobre o sistema ASCI). Com o tempo foram criadas novas especificações e standards internacionais para passarem a armazenar caracteres dos vários alfabetos e até emojis. Apesar de ter havido muito progresso nesta área que se designa de internacionalização, a verdade é que por vezes os caracteres não ASCI podem ainda dar alguns problemas consoante as configurações do sistema operativo e dos programas.

Como Evitar Usar Espaços Nos Nomes das Variáveis

Há várias estratégias possíveis para escrever variáveis, sem espaços, e cujos nomes têm mais que uma palavra.

# Palavras separadas por underscores `_`.
variavel_snake_case <- "snake_case"

# Primeira palavra começa com minúscula todas as outras com maiúscula.
variavelCamelCase <- "camelCase"

# Todas as palavas começam com maiúscula, mesmo a primeira.
VariavelDromedaryCase <- "DromedaryCase"

# Palavras separadas por pontos `.`.
variavel.com.pontos <- "podem.usar.no.R"

Nota: a utilização de pontos nos nomes das variáveis pode ser confusa para programadores que venham doutras linguagens, onde o ponto tenha outra função (e.g., designar procedimentos dum objecto e/ou campos específicos duma estrutura com vários campos).

Tipos de Dados Simples

Assim como acontece nas outras linguagens de programação, o R tem vários tipos de dados. Vamos começar por ver alguns tipos simples de dados e depois passaremos a alguns compostos. Como o nome indica os tipos compostos podem ser criados juntando dados com tipos simples. Isto pode parecer confuso agora mas ficará claro mais à frente. Por agora vamos ver alguns tipos simples de dados.

É importante sabermos o tipo de dados das variáveis para percebermos que operações podemos e que operações não podemos fazer com elas. Vamos explorar melhor esta questão mais tarde, para já, podem pensar que se a minha variável é um número faz sentido que a eu a possa multiplicar por outro número (e.g., 10). Por outro lado se a minha variável for uma letra é normal que eu não a possa dividir por um número (e.g., 2).

Para sabermos a classe/tipo de dados de uma variável usamos a função class(). Por exemplo:

class(variavel)
[1] "numeric"
class(nome_sem_espacos)
[1] "character"

Experimentem descobrir a classe das variáveis que foram criando ao longo deste tutorial.

Nota: A função typeof() também permite descobrir o tipo de uma variável. Muitas vezes o seu output será igual ao de class() mas às vezes será diferente. A função typeof() tende a ser mais ùtil para uma programação de mais baixo nível (i.e., mais perto do hardware) porque nos diz como a variável é guardada na memória do computador. A função class() tende a dar uma resposta de mais alto nível referindo a classe mais geral a que a variável pertence. Se tudo isto parece chinês ignorem esta nota e usem a função class() nos vossos scripts.

numeric

Um tipo de informação simples que podemos guardar numa variável são números, sejam eles inteiros ou com valores decimais, negativos ou positivos. Por exemplo:

numero_inteiro <- 20
numero_negativo <- -3
numero_decimal <- 0.2

class(numero_inteiro)
[1] "numeric"
class(numero_negativo)
[1] "numeric"
class(numero_decimal)
[1] "numeric"

character

As variáveis também podem guardar caracteres ou sequências de caracteres (i.e., palavras, frases, parágrafos). Nas linguagens de programação, estas sequências de caracteres costumam chamar-se strings. Vejamos alguns exemplos.

# Caracteres isolados.
caracter <- "A"
caracter <- "B"

# Strings de uma só palavra.
palavra <- "palavra"
palavra <- "livro"

# Strings de várias palavras.
frase <- "Esta string contém várias palavras."
frases <- "Esta string contém mais que uma frase. Ah pois é!"

# Pertencem todas à classe character.
class(caracter)
[1] "character"
class(palavra)
[1] "character"
class(frase)
[1] "character"
class(frases)
[1] "character"

factor

No R também temos um tipo de dados muito úteis para a estatística—factor. Este tipo de dados é muito pouco comum nas linguagens de programação (pelo menos com este nome) mas permite-nos, por exemplo, distinguir variáveis estatísticas categóricas de strings/character.

Por exemplo, se no nosso estudo tivermos uma condição de controlo e outra experimental, podemos ter uma coluna com a condição em que cada participante ficou. Reparem que neste caso, podemos não querer que a informação dessa coluna seja interpretada como character em que todos os valores são possíveis. Em vez disso podemos querer que a variável seja interpretada como só podendo ter dois valores possíveis—“experimental” e “controlo”.

condicao1 <- factor("experimental")
condicao2 <- factor("controlo")

class(condicao1)
[1] "factor"
class(condicao2)
[1] "factor"

Os factores no R têm níveis—levels—e podem ter etiquetas/descrições—labels. Dado que ainda não vimos tipos de dados compostos, nem estudámos muito o uso de funções, é possível que os próximos exemplos ainda sejam difíceis de entender Façam um esforço para tentarem compreender agora, mas não se preocupem se ainda não conseguirem perceber bem os usos das variáveis factor. Assim que começarmos a fazer análises estatísticos, creio que o seu uso ficará mais claro.

# Variável factor com dois níveis sem labels.
condicao <- factor("experimental", levels = c("experimental", "controlo"))
# Variável factor com três níveis e sem labels.
stress <- factor("high", levels = c("low", "medium", "high"))
# Variável factor com dois níveis e com labels.
lab_v1 <- factor(1, levels = c(1, 2), labels = c("A", "B"))
# Variável factor com dois níveis e com labels.
lab_v2 <- factor("A", levels = c("A", "B"), labels = c("lab A", "lab B"))

print(condicao)
[1] experimental
Levels: experimental controlo
print(stress)
[1] high
Levels: low medium high
print(lab_v1)
[1] A
Levels: A B
print(lab_v2)
[1] lab A
Levels: lab A lab B

logical

Existe outro tipo de dados simples no R—logical, que será estranho para quem não tiver prática com outra linguagem de programação, mas que nos será muito útil. Assim como aconteceu nos factor é possível que ao início não seja claro o uso e utilidade deste tipo de dados. Tentem ler e executar os exemplos com atenção mas não tenham receio de avançar mesmo que continuem com algumas dúvidas.

Uma variável lógica/logical só pode ter um de dois valores, verdadeiro—TRUE—ou falso—FALSE.

TRUE
[1] TRUE
FALSE
[1] FALSE

Nota: Se sentirem que estão a ficar mesmo confusos e/ou que ler esta secção está a esgotar os vossos recursos cognitivos, saltem-na e voltem cá mais tarde quando começarem a ler ou escrever código com este tipo de operações.

Operações Lógicas

Os dados logical acabam por ser muito úteis, não tanto para guardar dados estatísticos, mas para fazermos operações lógicas ou executar alguma operação só em determinadas condições. Como disse isto será estranho para quem não tiver prática com linguagens de programação, mas creio que ficará mais claro com alguns exemplos.

Imaginem que querem saber se uma variável é maior que outra, podem usar o sinal de maior ou menor e o resultado dessa operação será ou verdadeiro/TRUE ou falso/FALSE.

v1 <- 10
v2 <- 30

# v1 é MAIOR que v2?
v1 > v2
[1] FALSE
# v2 é MAIOR que v1?
v2 > v1
[1] TRUE
# v1 é MENOR que v2?
v1 < v2
[1] TRUE
# v2 é MENOR que v1?
v2 < v1
[1] FALSE

Também podemos descobrir se duas variáveis são iguais ou diferentes uma da outra.

# v1 é IGUAL a v2?
v1 == v2
[1] FALSE
# v1 é DIFERENTE de v2?
v1 != v2
[1] TRUE

Para além disso podemos fazer outras operações com álgebra booleana.

Com o quê?!?!!?!

A álgebra booleana refere-se às operações, como as que já fomos fazendo, cujo resultado só pode ser verdadeiro ou falso. Vamos ver mais algumas operações importantes.

O caractere ! permite-nos negar um valor lógico/logical, incluindo o resultado duma operação lógica. Negar no fundo é inverter o seu valor, ou seja, a negação de verdadeiro/TRUE é falso/FALSE e vice versa.

# Dá falso.
!TRUE
[1] FALSE
# Dá verdadeiro.
!FALSE
[1] TRUE
# Dahn! Obviamente que 1 é maior que 0.
1 > 0
[1] TRUE
# Então a sua negação dá falso.
!(1 > 0)
[1] FALSE

Para além de podermos negar um valor lógico/logical podemos usá-los em operações lógicas de “E” e “OU”. Quando usamos um “E” estamos a dizer que tanto uma coisa como outra têm de ser verdadeiras ou a nossa afirmação é falsa. Por exemplo, a frase “a Terra é um planeta E uma estrela” é falsa porque a Terra é um planeta mas não é uma estrela. Quando usamos um “OU” estamos a dizer que para a afirmação ser verdadeira basta que uma das suas partes seja verdade. Claro que se ambas forem falsas a frase é falsa, mas desde que pelo menos uma seja verdadeira a frase será verdadeira. Por exemplo, a frase “eu vou ao cinema OU ao teatro” é verdadeira quer eu vá ao cinema, ao teatro, ou aos dois, sendo que é mentira se eu não for a nenhum dos dois. No R usamos o & para “E” e usamos | para “OU”.

# Verdadeiro E verdadeiro = verdadeiro.
TRUE & TRUE
[1] TRUE
# Verdadeiro E falso = falso.
TRUE & FALSE
[1] FALSE
# Falso E verdadeiro = falso. (A ordem nunca interessa)
FALSE & TRUE
[1] FALSE
# Falso E falso = falso. (Pois...não sei o que estavas à espera...)
FALSE & FALSE
[1] FALSE
# Verdadeiro OU verdadeiro = verdadeiro.
TRUE | TRUE
[1] TRUE
# Verdadeiro OU falso = verdadeiro.
TRUE | FALSE
[1] TRUE
# Falso OU verdadeiro = verdadeiro. (A ordem nunca interessa)
FALSE | TRUE
[1] TRUE
# Falso OU falso = falso. (Como não podia deixar de ser...)
FALSE | FALSE
[1] FALSE

Sabendo estas regras lógicas podemos começar a fazer perguntas mais interessantes sobre as nossas variáveis. Tentem adivinhar o resultado de cada operação e usem o R para confirmar a vossa resposta.

v1 <- 500
v2 <- -10

v1 > 100 & v2 > 0
v1 > 300 | v2 > 10
v1 < 1000 & v2 < 0
v1 > 1000 | v2 < -5

# Mais difícil
!(v1 > 1000) & v2 < 0
!(v1 > 1000) | !(v2 < 0)

Os valores lógicos/logical também são úteis porque nos vão permitir escrever código que faz coisas diferentes em condições diferentes.

v1 <- c(1, -1)
print(v1)
[1]  1 -1
# Muda os valores de v1 consoante eles são ou não maiores que 0.
v1 <- ifelse(v1 > 0, yes = "Positivo", no = "Negativo")
print(v1)
[1] "Positivo" "Negativo"

Para já ficam só com este exemplo mas vamos voltar a este tema das operações lógicas quando virmos os tipos de dados compostos.

Aviso

No R os valores lógicos/logical têm de ser escritos com letra maiúscula. O R também nos permite referir-nos a estes valores pelas suas abreviaturas, T e F respectivamente.

T
[1] TRUE
F
[1] FALSE

Contudo, recomendo que evitem usar as abreviaturas dos valores logical porque as abreviaturas, ao contrário da sua versão extensa, não são nomes reservados no R e podem ganhar novo significado no código. Recomendo também que, apesar de não serem nomes reservados, nunca usem as letras T e F como nomes de variáveis, para evitarem este tipo de problemas.

# Por defeitoo T é uma abreviatura para TRUE
T == TRUE
[1] TRUE
# Mas como não é reservado pode passar a ser outra coisa.
T <- "Outra coisa que não o valor original"

T == TRUE
[1] FALSE
# Aliás até pode passar a ser FALSE
T <- FALSE
# E F pode passar a ser TRUE
F <- TRUE

# Oh não agora fica tudo trocado!!!
T == TRUE
[1] FALSE
F == FALSE
[1] FALSE
T == FALSE
[1] TRUE
F == TRUE
[1] TRUE

Usando sempre os nomes de TRUE e FALSE por extenso no vosso código não têm de se preocupar com estes problemas. Nesse caso, podem zangar-se comigo por ter perdido tempo a explicar porque é que pode ser problemático usar as abreviaturas.

Tipos de Dados Compostos

Agora que já vimos vários tipos de dados simples vamos ver tipos de dados que são criados ao compor um ou mais tipos simples. Isto agora pode parecer confuso mas imaginem que têm vários tipos palavras e vários tipos de frases. Para fazer um tipo de frase podem usar mais que um tipo de palavras. Na secção anterior vimos alguns desses tipos de palavras, agora vamos ver alguns tipos de frases.

Vectores

Podem pensar nos vectores como uma lista de coisas. Essas coisas podem ser todas do mesmo tipo ou de tipos diferentes. A função c() combina vários valores num vector (é o que diz o manual… help(c)).

# Podemos usar a função c para combinar tudo e mais alguma coisa.
idade <- c(21, 32, 24, 46, 33, 20, 50, 39, 18, 20)

genero <- c("M", "F", "M", "F", "F", "F", "M", "M", "F", "M")

rts <- c(1435, 4352, 2232, 2820, 4552, 6454, 6500, 1005, 9001, 2003)

Experimentem criar algumas novas variáveis deste tipo, ou seja, criarem variáveis que guardam o output da função c() invocada com diferentes argumentos.

O R consegue aceder só a elementos ou a partes do vector. Para um vector v podemos descobrir um dado elemento i usando a sintaxe v[i]. Vejamos alguns exemplos:

# v[i] = o elemento número i do vector v.

# Primeiro elemento do vector idade.
idade[1]
[1] 21
# Terceiro elemento do vector rts.
rts[3]
[1] 2232

Também podemos aceder a uma parte maior do vector, ou seja, a mais que um valor. Por exemplo, se quisermos todas as posições de x a y do vector v, podemos usar a sintaxe v[x:y].

# var[i:j] - todos os elementos entre i e j do vector var.

# Todas os valores entre as posições 4 a 8 do vector idade.
idade[4:8]
[1] 46 33 20 50 39
# Todas os valores entre as posições 3 a 5 do vector rts.
rts[3:5]
[1] 2232 2820 4552

Podemos ainda especificar posições específicas usando a função c() para indicar um vector com as posições que queremos. Parece confuso mas é simples, reparem:

# As posições 3, 5 e 7 do vector rts.
rts[c(3, 5, 7)]
[1] 2232 4552 6500
# As posições 1, 3 e 6 do vector idade.
idade[c(1, 3, 6)]
[1] 21 24 20

Nota: O R começa a contagem das posições no 1 e não no 0, isto parece a escolha óbvia para a maioria das pessoas. Contudo, outras linguagens de programação começam a contar no 0 e isto pode causar confusão a quem venha dessas linguagens. Se vêm de outra linguagem de programação não se preocupem, desde que estejam atentos, com a experiência, vão-se habituar aos dois tipos de contagem. Se não têm experiência com nenhuma linguagem de programação escusavam de ter perdido tempo a ler esta nota…mas agora já sabem mais um facto curioso sobre linguagens de programação…

Data Frames

Os data frames são um tipo de dados que nos permite guardar tabelas, com colunas e linhas, e cujas colunas (e linhas também) podem ter nomes. As variáveis onde guardamos as nossas bases de dados costumam ser data frames. Portanto, para sermos eficazes a fazer análises de dados no R convém que percebamos muito bem o que são os data frames e como podemos trabalhar trabalha com eles.

Reparem que os data frames vão ser tão importantes para nós que temos um capítulo dedicado exclusivamente aos data frames. Nesta secção vamos ver apenas algumas das suas propriedades que depois exploraremos em detalhes no capítulo que lhes é dedicado.

Podemos criar data frames com a função data.frame().

dados <- data.frame(idade = idade, genero = genero, rts = rts)

Na prática será raro criarmos data frames de raiz, mas as variáveis onde vamos guardar as nossas bases de dados vão ser data frames. Portanto, não é muito importante que saibamos criar data frames mas é importante que saibamos trabalhar com eles.

Os data frames têm duas dimensões, linhas e colunas, podemos usar as estratégias que usámos para aceder a partes dos vectores, para aceder: (1) só a algumas linhas, (2) só a algumas colunas, (3) só a algumas células.

# Acede á primeira LINHA.
dados[1, ]
idade genero rts
21 M 1435
# Acede á terceira LINHA.
dados[3, ]
idade genero rts
3 24 M 2232
# Acede a todas as LINHAS entre 2 e 4.
dados[2:4, ]
idade genero rts
2 32 F 4352
3 24 M 2232
4 46 F 2820
# Acede á segunda COLUNA.
dados[ ,2]
 [1] "M" "F" "M" "F" "F" "F" "M" "M" "F" "M"
# Acede á primeira COLUNA.
dados[ ,1]
 [1] 21 32 24 46 33 20 50 39 18 20
# Acede a todas as COLUNAS entre 1 e 3.
dados[ ,1:3]
idade genero rts
21 M 1435
32 F 4352
24 M 2232
46 F 2820
33 F 4552
20 F 6454
50 M 6500
39 M 1005
18 F 9001
20 M 2003
# Acede à primeira célula (primeira linha da primeira coluna).
dados[1, 1]
[1] 21
# Acede à quarta linha da segunda coluna.
dados[4, 2]
[1] "F"
# Acede à primeira e segunda células da terceira coluna.
dados[1:2, 3]
[1] 1435 4352
# Acede à primeira linha da segunda e terceira colunas.
dados[1, 2:3]
genero rts
M 1435
# Acede a todas linhas de 4 a 5 da primeira à segunda colunas.
dados[4:5, 1:2]
idade genero
4 46 F
5 33 F

Sendo que os data frames têm colunas e linhas é provável que enquanto estamos a trabalhar com as bases de dados queiramos saber quantas linhas ou colunas tem. Nos casos em que sabemos que só temos uma linha na base de dados para cada participante saber o número de linhas será uma forma de confirmarmos o número de participantes no nosso estudo.

# Número de colunas.
ncol(dados)
[1] 3
# Número de linhas.
nrow(dados)
[1] 10
# Número de linhas seguido do número de colunas.
dim(dados)
[1] 10  3

Como disse antes, as colunas dos data frames podem ter nomes. Nesses casos, para além de podermos aceder às colunas especificando os seus números, também o podemos fazer usando os seus nomes.

# Podemos ter acesso às colunas com "$" seguido do nome da coluna.
dados$idade
 [1] 21 32 24 46 33 20 50 39 18 20
# Podemos também usar um vector com os nomes das colunas que queremos.
dados[ , c("idade", "genero", "rts")]
idade genero rts
21 M 1435
32 F 4352
24 M 2232
46 F 2820
33 F 4552
20 F 6454
50 M 6500
39 M 1005
18 F 9001
20 M 2003
# Como saber quais os nomes das colunas?
colnames(dados)
[1] "idade"  "genero" "rts"   

Quando estamos a explorar os dados muitas vezes será útil vermos a base de dados para percebermos como está estruturada.

# Para espreitarmos a parte de "cima" dos dados (as primeiras 6 linhas).
head(dados)
idade genero rts
21 M 1435
32 F 4352
24 M 2232
46 F 2820
33 F 4552
20 F 6454
# Podemos pedir para ver mais ou menos linhas.
head(dados, n = 4)
idade genero rts
21 M 1435
32 F 4352
24 M 2232
46 F 2820
head(dados, 8)
idade genero rts
21 M 1435
32 F 4352
24 M 2232
46 F 2820
33 F 4552
20 F 6454
50 M 6500
39 M 1005
# A função tail() mostra a parte de baixo (as últimas 6 linhas).
tail(dados)
idade genero rts
5 33 F 4552
6 20 F 6454
7 50 M 6500
8 39 M 1005
9 18 F 9001
10 20 M 2003
# Também podemos especificar o número de colunas que queremos ver.
tail(dados, n = 5)
idade genero rts
6 20 F 6454
7 50 M 6500
8 39 M 1005
9 18 F 9001
10 20 M 2003
tail(dados, 2)
idade genero rts
9 18 F 9001
10 20 M 2003

A função View() permite-nos ver a base de dados numa interface mais parecida com uma folha de cálculo (e.g., do Libreoffice Calc ou Microsoft Office Excel). Não dará para verem o output neste documento mas vejam o que obtêm na vossa sessão do R quando correm a linha abaixo.

View(dados)

Experimentem usar as setas do teclado para “navegar” na base de dados como costumam fazer nas folhas de cálculo normais.

Funções

No R, e noutras linguagens de programação, temos ainda outro tipo de dados, as funções. Resumidamente, as funções são receitas, que podem ou não levar um ou mais ingredientes, produzindo um resultado/output. Ao longo deste tutorial e do anterior já fomos usando várias funções mas nunca programámos nenhuma. Até nos tornarmos utilizadores mais avançados do R, é normal que sejamos ávidos consumidores de funções mas não sejamos produtores. Nesta secção vamos criar funções muito simples, para percebermos melhor o que as funções são e como funcionam, em vez de nos parecerem magia obscura.

Função Sem Argumentos

Comecemos por criar uma função super simples, chamada hello(), cuja única missão que tem na vida é devolver/retornar como output a frase “Hello world!”.

hello <- function() {
    return("Hello world!")
}

Sempre que invocarmos a função hello() ela vai-nos devolver essa frase. Não se esqueçam que só podem invocar a função, na vossa sessão do R, se já tiverem criado/definido, ou seja, se tiverem corrido as linhas de código anteriores.

hello()
[1] "Hello world!"
hello()
[1] "Hello world!"

Reparem que a função não leva argumentos porque quando a criamos não colocámos nada entre os parêntesis de function(). Quando tentamos invocar a função hello() com um ou mais argumentos, sejam eles quais forem, vamos obter erros.

hello("a")
Error in `hello()`:
! unused argument ("a")
hello(3)
Error in `hello()`:
! unused argument (3)

Função Com Um Argumento

Podemos experimentar criar uma função um pouco mais complexa e que já requer um argumento x. A única missão da vida desta função é subtrair 1 a x e devolver o resultado dessa subtracção.

x_menos_um <- function(x) {
    resultado <- x - 1

    return(resultado)
}

Podemos ver que a função funciona, quando lhe damos um valor como argumento o output que ela retorna é igual à subtracção de 1 a esse valor. Não se esqueçam que têm de ter correr as linhas de código anteriores, em que a função foi criada/definida, para a poderem executar as próximas linhas de código na vossa sessão do R.

x_menos_um(10)
[1] 9
x_menos_um(30)
[1] 29
x_menos_um(1)
[1] 0
x_menos_um(0)
[1] -1

Experimentem invocar, na vossa sessão do R, a função x_menos_um() com vários argumentos diferentes.

Como a função só leva um argumento—x—o valor que pusermos entre parêntesis será avaliado como sendo o argumento x mas nada nos impede de sermos explícitos e especificarmos que é isso que estamos a fazer.

x_menos_um(x = 8)
[1] 7
x_menos_um(x = 500)
[1] 499

Neste caso especificarmos que o argumento que lhe estamos a passar é o argumento x ou simplesmente colocarmos o valor de x é uma questão de preferência pessoal e estilo de escrita.

# Ambas as formas produzem o mesmo resultado.
x_menos_um(x = 8)
[1] 7
x_menos_um(8)
[1] 7

Reparem que a função que criamos precisa do argumento x, se for invocada sem nenhum argumento dá erro.

# Dá erro (não consegue subtrair 1 a algo que não existe).
x_menos_um()
Error in `x_menos_um()`:
! argument "x" is missing, with no default

A função também dará erro se for invocada com um argumento a que não possamos subtrair 1, nomeadamente, strings (e.g., ver secção character).

# Dá erro (não consegue subtrair 1 à letra "a").
x_menos_um("a")
Error in `x - 1`:
! non-numeric argument to binary operator

Funções Com Vários Argumentos

Podemos criar uma nova função x_menos_y em que em vez de subtrairmos 1 a x vamos subtrair y, sendo que o utilizador terá de especificar ambos.

x_menos_y <- function(x, y) {
    resultado <- x - y

    return(resultado)
}

Podemos testar a função invocando-a com diferentes valores para os argumentos de x e y. Experimentem invocá-la com mais alguns argumentos para além dos exemplos abaixo. Lembrem-se que têm de ter executado as linhas de código acima para que a função possa ter sido criada na vossa sessão do R.

x_menos_y(20, 30)
[1] -10
x_menos_y(5, 2)
[1] 3
x_menos_y(20, -10)
[1] 30

À semelhança do que vimos anteriormente, podemos invocar a função com ou sem especificar o nome dos argumentos.

x_menos_y(x = 20, y = 30)
[1] -10
x_menos_y(20, 30)
[1] -10
x_menos_y(40, 10)
[1] 30
x_menos_y(x = 40, y = 10)
[1] 30

No entanto, agora conseguimos perceber que, se indicarmos os nomes dos argumentos, não precisamos de os escrever pela ordem em que a função os define. Neste caso, isso parece pouco útil. Contudo, quando usarmos funções escritas por outras pessoas, podemos conhecer os argumentos sem nos lembrarmos da ordem em que foram definidos. Se indicarmos os nomes, o R sabe onde colocar cada valor.

# Obtemos o mesmo resultado de ambas as maneiras.
x_menos_y(30, 20)
[1] 10
x_menos_y(x = 30, y = 20)
[1] 10
# Mas especificando o nome dos argumentos podemos alterar a ordem.
x_menos_y(y = 20, x = 30)
[1] 10

Funções Com Argumentos Opcionais

Quando criamos uma função podemos especificar qual o valor que os argumentos assumem por defeito/default. Os argumentos que têm valores default acabam por ser opcionais. Ou seja, se não especificarmos qual o valor desse argumento, o R usará o default em vez de dar erro a dizer que o argumento não foi especificado.

Vamos experimentar reescrever a função x_menos_y fixando 1 como o valor default para y quando y não é especificado.

x_menos_y <- function(x, y = 1) {
    resultado <- x - y

    return(resultado)
}

Agora que há um default para y podemos invocar a função especificando apenas o valor de x.

x_menos_y(5)
[1] 4
x_menos_y(x = -10)
[1] -11

Dito isto, se quisermos atribuir um valor a y diferente do default é só especificarmos esse valor. Nesse caso a função x_menos_y vai usar o valor de y que especificarmos em detrimento do default.

x_menos_y(30, 20)
[1] 10
x_menos_y(x = 10, y = 5)
[1] 5

Contudo, sendo que x não tem um valor default teremos sempre de especificar x, se especificarmos só y dá erro.

x_menos_y(y = 2)
Error in `x_menos_y()`:
! argument "x" is missing, with no default
x_menos_y(y = 30)
Error in `x_menos_y()`:
! argument "x" is missing, with no default

Usar Variáveis Como Argumentos

Os valores que passamos aos argumentos das funções podem ser os próprios valores em si que lhes queremos passar ou variáveis que contêm esses valores. Estudem bem os exemplos abaixo e experimentem algumas variações na vossa sessão do R.

x_menos_y(10, 5)
[1] 5
v1 <- 10
v2 <- 5

x_menos_y(v1, v2)
[1] 5
v1 <- 5
v2 <- 30

x_menos_y(x = v1, y = v2)
[1] -25

Usar Outputs de Funções Como Argumentos

Também podemos usar o output de uma função como argumento de outra função. Sei que isto pode parecer altamente confuso mas na prática é algo que acabamos por fazer muitas vezes.

Já vimos que podemos criar uma variável para guardar o output duma função. Isto pode ser algo tão simples como guardar output da função c(), invocada com os argumentos 1 e 3, na variável v1.

v1 <- c(1, 3)

Também já sabemos que podemos usar a variável v1 que guarda o output de c(1, 3) como argumento para uma outra função, por exemplo, mean().

mean(v1)
[1] 2

Conseguimos perceber que no fundo invocamos a função mean() dando-lhe como input o output de c(1, 3). Ora se não formos reutilizar a variável v1 mais tarde, podemos evitar criá-la e invocar logo mean() com `c(1, 3) como argumento.

mean(c(1,3))
[1] 2

O que é importante é irmos pensando que as funções que estão «encastradas» noutras são executadas primeiro e os seus outputs são passados como argumentos às funções que estão de fora. Em inglês, estas chamadas são frequentemente descritas como nested function calls. No fundo isto só quer dizer que se resolve primeiro o que está dentro dos parêntesis duma função que esteja dentro dos parêntesis de outra. Na prática talvez seja bom evitar, sempre que possível, colocar muitas funções dentro de outras para que seja mais fácil os leitores conseguirem perceber o que está a acontecer. Para isso já sabemos que podemos criar variáveis que guardem o output doutras funções e depois usá-las como argumento.

A partir da versão 4.1 do R, podemos usar o pipe nativo |>. A palavra inglesa pipe significa «tubo». A analogia ajuda: o tubo recebe um valor de um lado, transporta-o e entrega-o à função que está do outro lado.

NotaTermo técnico: syntactic sugar

Em programação, syntactic sugar é uma sintaxe alternativa para exprimir uma operação que a linguagem já consegue representar. Essa alternativa costuma tornar o código mais fácil de escrever ou ler. A página Syntactic sugar apresenta exemplos noutras linguagens.

No caso simples, o próprio R transforma x |> f(y) em f(x, y) quando analisa o código. Podemos observar a chamada construída com quote(), que a mostra sem executar o cálculo:

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

A documentação oficial do operador |> descreve este mecanismo como uma transformação sintáctica. As regras habituais de avaliação do R continuam a aplicar-se. A transformação também explica por que razão algumas mensagens de erro ou avisos podem mostrar a chamada encastrada, apesar de termos escrito a versão com o pipe.

Vejamos novamente a mesma transformação em quatro formas. Primeiro, uma versão compacta e cheia de parêntesis:

print(mean(as.numeric(c("1", "2", "3"))))
[1] 2

Podemos tornar a estrutura dos parêntesis mais visível com indentação. Continuamos a ler de dentro para fora.

print(
    mean(
        as.numeric(
            c("1", "2", "3")
        )
    )
)
[1] 2

Podemos também guardar os resultados intermédios. Esta forma é útil se quisermos inspeccionar ou reutilizar algum deles.

v1 <- c("1", "2", "3")
v1_numerico <- as.numeric(v1)
media_v1 <- mean(v1_numerico)
print(media_v1)
[1] 2

Finalmente, podemos mostrar a direcção das transformações com o pipe nativo. Agora lemos de cima para baixo.

c("1", "2", "3") |>
    as.numeric() |>
    mean() |>
    print()
[1] 2

Para esta cadeia, podemos pensar que o R transforma progressivamente a expressão com pipes na expressão encastrada. Este modelo mental ajuda-nos a perceber de onde vem cada argumento e a confirmar que as quatro formas produzem o mesmo resultado.

Normalmente, o valor vindo do pipe ocupa o primeiro argumento. Podemos continuar a indicar os restantes argumentos pelo nome.

c(1, 2, 100) |>
    mean(trim = 0.2)
[1] 34.33333

Quando o valor tiver de ocupar outro argumento, as versões modernas do R permitem usar _ como marcador num argumento nomeado.

mtcars |>
    lm(mpg ~ wt, data = _)

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

Coefficients:
(Intercept)           wt  
     37.285       -5.344  

Importa ressalvar que o operador %>% já existia em pacotes associados ao tidyverse antes de |> fazer parte do R. É possível que encontrem %>% em código antigo ou em scripts de outras pessoas. Os dois operadores produzem resultados semelhantes em muitas cadeias simples, mas não têm regras idênticas. Neste tutorial usamos |>, que não requer qualquer pacote.

Usar Funções Para Simplificar Scripts

Já vimos algumas propriedades das funções. É provável que estejam a sentir que é muita informação e que ainda não vejam qual a utilidade das funções. Se estão a pensar isso não se preocupem, é mesmo normal que seja esse o caso (é sinal que o meu plano malvado está a funcionar…muahhahhah!!!).

Nesta subsecção vamos ver como as funções, tenham sido escritas por nós ou não, nos podem ajudar a simplificar os scripts. Mais concretamente, vamos ver como usar funções evita que o nosso código fique muito repetitivo e nos pode ajudar a que fique mais claro para quem lê.

As funções são uma forma de abstracção e de encapsular código.

Quê!??

Lá vem o chinês outra vez…

Calma…isto só quer dizer que as funções permitem que nos abstraiamos dos pormenores de como alguma coisa é feita, para nos focarmos no que é que está a ser feito. Ao mesmo tempo permitem que o mesmo código seja reutilizado sem ter de ser reescrito. Como isto continua a ser abstracto vejamos alguns exemplos.

Imaginemos que eu quero inverter os itens de uma escala numérica que mede o stress e que vai de 0 a 5.

stress <- c(1, 2, 1, 0, 3, 4, 5)

Para inverter os valores (e.g., 5 passar a 0, 0 passar a 5, etc…) posso subtrair cada valor ao valor máximo da escala.

5 - stress
[1] 4 3 4 5 2 1 0

Se tiver uma escala de bem estar que vai de 1 a 100, teria de repetir o processo, mas teria de somar o mínimo, que deixou de ser zero, ao máximo da escala e só depois só depois subtrair os dados.

bem_estar <- c(1, 100, 50, 30, 40, 20, 10)

101 - bem_estar
[1] 100   1  51  71  61  81  91

Ora eu posso criar uma função com um nome que represente bem o que ela faz, bem como o que deve ser especificado em cada argumento.

inverter <- function(dados, minimo_da_escala, maximo_da_escala) {
    dados_invertidos <- (maximo_da_escala + minimo_da_escala) - dados

    return(dados_invertidos)
}

De seguida sempre que eu quiser inverter uma escala posso usar a função que criei o que deve ajudar a tornar o código mais explícito.

inverter(stress, minimo_da_escala = 0, maximo_da_escala = 5)
[1] 4 3 4 5 2 1 0
inverter(bem_estar, minimo_da_escala = 1, maximo_da_escala = 100)
[1] 100   1  51  71  61  81  91

Reparem que neste caso os detalhes matemáticos de como é feita a inversão (i.e., subtrair os valores ao máximo da escala) ficam abstraídos e os leitores do código podem focar-se no que é feito (i.e., inversão da escala). Neste caso só precisamos duas opearações matemáticas para inverter a escala (a soma do máximo ao mínimo e a subtracção dos dados a esse valor), mas imaginem que tínhamos operações com muitos passos, todos esses passos seriam abstraídos para nos focarmos no que era feito. Por exemplo, para calcularmos uma média temos de somar todas as observações e dividir a soma pelo número de observações. A função mean() faz isso por nós. Quando usamos a função mean(), nem nós nem os leitores do nosso código, temos de nos preocupar com esses detalhes matemáticos.

mean(stress)
[1] 2.285714
mean(bem_estar)
[1] 35.85714

Usar Funções Escritas Por Outros

No início desta secção disse que quando começamos a usar o R não é comum termos de escrever funções mas apenas usar funções que outros escreveram. Depois perdi imenso tempo a mostrar como se escrevem funções… Como tinha avisado não fiz isso para que aprendessem a escrever funções mas para que as funções não parecessem coisas mágicas e obscuras.

Caso o meu plano tenha funcionado, neste momento já têm em mente que as funções executam uma série de passos (código) usando os valores que lhes passamos como ingredientes. Caso o meu plano não tenha funcionado isto ainda parece tudo chinês e estão prestes a fechar o separador onde estão a ler este livro. Por favor, não façam isso! Esta secção das funções é daquelas a que podem voltar mais tarde, quando já tiverem mais familiaridade com o R. Para além disso, esta subsecção vai focar-se em oferecer dicas para usar funções escritas por outros—aquilo que fazemos na maioria das vezes. Depois, a próxima subsecção vai fazer o sumário dos conceitos mais importantes a reter sobre as funções. Ou seja, não fechem este separador antes de lerem esta e a próxima subsecções.

Para usarmos funções escritas por outras pessoas só precisamos de saber os argumentos obrigatórios que levam, ou seja, os ingredientes necessários. Se soubermos a ordem com que devem ser fornecidos nem precisamos de saber o nome, se os indicarmos pelo nome não precisamos de saber ordem. Quando digo que temos de saber os argumentos que a função leva isso que dizer que temos de saber quais os valores possíveis para os argumentos obrigatórios. Por exemplo, uma dada função pode só aceitar valores numéricos/numeric para um argumento e só aceitar valores lógicos/logical (i.e., TRUE ou FALSE) para outro argumento.

Deves estar a achar que eu algum dia vou saber essas coisas!

Calma…não se preocupem…ironia ou não há uma função—help() que nos ajuda a saber o que faz uma função, que argumentas aceita, e muitas vezes até mostra exemplos de como pode ser usada.

Por exemplo, experimentem executar a linha abaixo no vosso interpretador.

help(mean)

Experimentem, agora usar a função help() para ver os manuais de outras funções. Sei que provavelmente já usaram a função, quanto mais não seja no capítulo anterior, mas tentem ler os manuais com mais atenção agora que já sabem mais sobre como funcionam as funções. Nomeadamente, tentem perceber quais os argumentos obrigatórios e quais os opcionais, vejam também qual a classe/tipo de dados de cada argumento.

# Exemplos.
# Ver manual da função sd e descobrir:
# - O que faz/para que serve?
# - Qual/quais o/s argumento/s obrigatório/s?
# - Quais são opcionais?
# - Qual o tipo de dados de cada argumento?
help(sd)

O R tem uma funcionalidade interessante e permite-nos usar ? como uma abreviatura para a função help(). Experimentem:

?mean # == help(mean)
?sd # == help(sd)

Quando estiverem a ler o código de outras pessoas, ou código vosso que já escreveram há muito tempo, é normal que se deparem com funções que não fazem ideia do que fazem nem que argumentos aceitem.

Imaginem que viam algo como o exemplo (parvo) abaixo.

# Este código não funciona, só serve de exemplo.
resultados <- weird_function(data = my_data,
                             weird_argument = TRUE,
                             weirder = FALSE,
                             launch_skynet = TRUE)

Era normal que lendo esse código pensassem:

Nunca ouvi falar da weird_function…é realmente estranha como o nome indica…

Os argumentos que aceita também parecem estranhos…

Mesmo assim, se não desistissem logo talvez conseguissem inferir algumas coisas…tais como:

O argumento data deve esperar bases de dados…talvez data frames

Quem escreveu o script quer guardar o output numa variável chamada resultados…a função deve fazer alguma/s análise/s estatística/s.

Os restantes argumentos são estranhos…como os nomes indicam…mas esperam valores do tipo logical() (i.e., TRUE ou FALSE).

Depois podiam ir ler o manual da função para tirar as dúvidas.

# Continua a ser um exemplo ilustrativo que não funciona.
help(weird_function)

Quando estivessem a ler o manual procuravam a secção que explica cada um dos argumentos que viram: data,weird_argument, weirder, e launch_skynet.

Independentemente do que o manual dissesse, se já viram o exterminador implacável iam usar sempre a função com launch_skynet = FALSE

Funções Como Argumentos de Funções

Já vimos que podemos usar o output de funções como argumento para outras funções. Na realidade o R permite-nos ir mais longe e usar funções propriamente ditas como argumento de funções.

Nota: Para quem quiser aprender mais sobre programação a possibilidade de usar funções como argumentos de funções é um dos requisitos do paradigma de programação funcional. O paradigma de programação funcional é um paradigma de programação, como o imperativo, ou orientado a objectos. Importa salientar que não é o facto de uma linguagem permitir o uso de funções que a torna funcional. Para uma linguagem ser funcional tem mesmo de permitir que as funções possam ser tratadas como um tipo de variável de pleno direito, o que implica que possam ser usadas como argumentos em funções. O paradigma de programação funcional está para além do âmbito deste livro. Quem quiser saber mais deve procurar aprender como limitar efeitos secundários, como escrever funções recursivas, estudar uma linguagem puramente funcional como Haskell ou uma família de linguagens historicamente associada a este paradigma, como LISP.

Vamos imaginar que eu escrevo uma função muito simples que a única coisa que faz é executar a função mean() com o argumento na.rm definido como TRUE (em vez de FALSE que é o valor por defeito).

media_sem_nas <- function(x) {
    resultado <- mean(x, na.rm = TRUE)

    return(resultado)
}

Eu poderia também escrever uma função equivalente para o desvio padrão.

desvio_sem_nas <- function(x) {
    resultado <- sd(x, na.rm = TRUE)

    return(resultado)
}

No entanto, sendo que posso usar funções como argumentos no R, eu posso escrever uma função mais genérica. Essa função pode então executar qualquer função que seja dada como argumento à minha função, com o argumento na.rm = TRUE.

sem_nas <- function(x, fun) {
    resultado <- fun(x, na.rm = TRUE)

    return(resultado)
}

Assim sendo eu posso usar essa nova função para substituir as anteriores.

v1 <- c(1, 2, 3, 4, 5, NA)

# Ambas as funções fazem a mesma coisa.
media_sem_nas(v1)
[1] 3
sem_nas(v1, mean)
[1] 3
# O mesmo é verdade para o desvio padrão.
desvio_sem_nas(v1)
[1] 1.581139
sem_nas(v1, sd)
[1] 1.581139

O pipe nativo combina naturalmente com esta função: encaminha v1 para o primeiro argumento, x, enquanto nós escolhemos a função que será aplicada no argumento fun.

v1 |> sem_nas(mean)
[1] 3
v1 |> sem_nas(sd)
[1] 1.581139

Podemos levar a mesma ideia a várias amostras. lapply() recebe uma lista de dados, uma função a aplicar—neste caso sem_nas()—e os argumentos adicionais de que essa função precisa.

amostras <- list(
    local_a = c(2, 4, 6, NA),
    local_b = c(10, 12, NA, 14),
    local_c = c(3, 5, 7, 9)
)

amostras |>
    lapply(sem_nas, fun = mean)
$local_a
[1] 4

$local_b
[1] 12

$local_c
[1] 6
amostras |>
    lapply(sem_nas, fun = sd)
$local_a
[1] 2

$local_b
[1] 2

$local_c
[1] 2.581989

Este padrão separa três decisões: quais são os dados, como percorremos os dados e qual é a estatística calculada. É mais fácil substituir uma dessas peças sem reescrever as restantes.

Sumário

O que é que aprendemos sobre funções nesta secção?

Sobre funções nada…só que tu não sabes escrever tutoriais de apoio, tu confundes tutorial com tortura…

Obrigado pelo feedback…é sempre bem vindo…

Nesse caso, diria que se eu soubesse escrever tutoriais de apoio podíamos ter aprendido que as funções:

  • Executam linhas de código e podem ajudar-nos a tornar o código mais claro e menos repetitivo.

  • Podem aceitar nenhum ou vários argumentos (ingredientes) e normalmente produzem um output (resultado).

  • Podem ter argumentos que são obrigatórios (sem os quais não funcionam) e outros que são opcionais (alteram valores default).

  • Normalmente têm manuais de ajuda que podemos ler com a função help(), que também pode ser invocada com ?.

Escreveste tanta coisa só para tirarmos estas quatro conclusões?!!!?!

Sim…o que conta não é o destino é a viagem…

Sem comentários!!!

Operações Com Variáveis

Agora que já sabemos os diferentes tipos de dados que podem ser guardados em variáveis, já vamos conseguir perceber as operações que vamos poder e as que não vamos poder fazer com cada variável.

Uma particularidade importante das operações com variáveis no R, é serem vectorisadas. Ou seja, se uma variável tiver mais que um valor, por exemplo quando a variável é um vector() ou um data.frame() o R vai automaticamente aplicar a operação a todos os elementos da variável. Isto é daquelas coisas que se tornam óbvias para quem se habitua ao R mas podem parecer magia para quem vier de outras linguagens de programação.

Matemáticas

Como já temos visto o R faz contas e pode ser usado como uma calculadora. Logo, um dos tipos de operações que podemos fazer com o R são as operações matemáticas.

Vejamos:

v1 <- 10

v1 + 1
[1] 11
v1 * 2
[1] 20
v1 / 100
[1] 0.1
v1**2
[1] 100
# Vejam se advinham o que cada uma destas faz...
sqrt(v1)
[1] 3.162278
log(v1)
[1] 2.302585
exp(v1)
[1] 22026.47

Agora que já conhecemos diferentes tipos de dados conseguimos perceber que este tipo de operações será mais adequado a variáveis numéricas/numeric().

v1 <- 2

# Posso fazer operações matemáticas porque v1 guarda um valor numeric() 
v1**2 * 3 + 2 / 3
[1] 12.66667
# v1 agora guarda um caractere/character()
v1 <- "a"

# Já não posso (nem faz sentido) fazer operações matemáticas
v1 * 3
Error in `v1 * 3`:
! non-numeric argument to binary operator

Dito isto convém estarmos atentos para o facto de algumas variáveis poderem conter dados que podem ser numéricos mas estão guardados como sendo de outro tipo.

Por exemplo:

# v1 guarda o caractere/character() "3".
v1 <- "3"

# Eu posso pensar que guarda o número 3 e tentar fazer uma conta.
# Nesse caso obtenho um erro
v1 * 3
Error in `v1 * 3`:
! non-numeric argument to binary operator

Mais à frente veremos que dá para pedir ao R para interpretar uma variável como sendo doutro tipo, o que em alguns casos pode solucionar estes problemas. Mas chega de spoilers

Também nos podemos deparar com situações estranhas, em que o R consegue fazer contas com variáveis que não são numéricas/numeric() seguindo uma lógica que talvez nos escape. Nomeadamente as variáveis lógicas/logical() podem ser tratadas como numéricas. Nesses casos, o R vai tratar o valor TRUE como sendo 1 e FALSE como sendo 0. Se tiverem experiência em programação, nomeadamente em linguagens como C ou C++ isto faz todo o sentido, caso contrário pode parecer muito estranho. Seja como for, fica o aviso para confirmarem se as variáveis com que estão a fazer operações matemáticas são mesmo numeric() ou se esse não é o caso.

v1 <- TRUE

# Pode não ser o que eu queria fazer mas o R vai aceitar.
v1 * 15
[1] 15
# Pelo sim pelo não confirmem a classe das variáveis...
class(v1)
[1] "logical"

Dado que as operações são vectorizadas quando faço uma operação matemática com uma variável com vários elementos, essa operação será aplicada a todos os elementos. Estudem com atenção os exemplos a baixo e façam algumas experiências semelhantes na vossa sessão do R.

v1 <- c(1, 500, 20, 1, -2, -50, 3)

# Todos os valores contidos em v1 são multiplicados por 2.
v1 * 2
[1]    2 1000   40    2   -4 -100    6
# Todos os valores contidos em v1 são divididos por 10.
v1 / 10
[1]  0.1 50.0  2.0  0.1 -0.2 -5.0  0.3

Para fazer contas com vectores têm que ter o mesmo tamanho. Caso contrário, o vector mais curto é reciclado/multiplicado até atingir o cumprimento do mais longo.

tmp <- c(3, 4, 2)

O R dá um aviso apenas caso o cumprimento do mais longo não seja um múltiplo do mais curto (e.g., n_curto x 2 = n_longo).

rts / tmp == rts / 3
Warning in rts/tmp: longer object length is not a multiple of shorter object
length
 [1]  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE

Lógicas

As mesmas operações lógicas que vimos anteriormente podem ser realizadas com tipos de dados compostos, nomeadamente, com vectores/vectors e data.frames. Dado que as operações são vectorizadas se a variável tiver mais que um elemento as operações lógicas serão aplicadas a todos os elementos. Por exemplo se eu perguntar se uma dada variável, com vários elementos, é igual a 1, a resposta será um TRUE ou FALSE para cada elemento. Isto torna-se mesmo muito útil quando descobrimos que ao passar um conjunto de TRUEs ou FALSEs para uma variável com vários elementos vamos obter só os valores das posições TRUE e nunca das FALSE. Ou seja, juntando estas duas funcionalidades vamos poder escolher apenas os elementos que cumprem as condições lógicas que especificamos. É altamente provável que não tenham percebido nada do que acabei de explicar. Não se preocupem a culpa é minha porque não faço ideia de como se explica isto em Português. Felizmente, acho que se escrever alguns exemplos R vão perceber logo.

Porque é que não escreveste logo os exemplos então?

Como isto é um tutorial achei que precisava de algum texto para parecer uma coisa séria…

Sem mais demoras, os exemplos:

# Operações lógicas
# Igual?
1 == 1
[1] TRUE
1 == 2
[1] FALSE
v1 <- 2
v1 == 3
[1] FALSE
v2 <- 2
v1 == v2
[1] TRUE
# Diferente?
2 != 3
[1] TRUE
3 != 10
[1] TRUE
v1 != v2
[1] FALSE
# Maior?
2 > 2
[1] FALSE
3 < 5
[1] TRUE
# Maior/Menor ou igual
2 >= 2
[1] TRUE
5 <= 5
[1] TRUE
v1 <= v2
[1] TRUE
# O que acontece se passar TRUE ou FALSE a variáveis?
v1 <- 2
v1[TRUE]
[1] 2
v1[FALSE]
numeric(0)
# Interessante...
v1 <- c(2, 3, 4)
# Algum dos valores desaparece? Qual?
v1[c(TRUE, FALSE, TRUE)]
[1] 2 4
# Operações lógicas em variáveis com vários elementos.
v1 == 2
[1]  TRUE FALSE FALSE
v1 == 3
[1] FALSE  TRUE FALSE
v1 == 4
[1] FALSE FALSE  TRUE
# Posso então usar o resultado para selecionar elementos.
v1[v1 == 4]
[1] 4

Coerção

O R tem vários comandos que permitem transformar as variáveis dum tipo noutro. Nem todos os tipos podem ser sempre transformados noutro, mas muitas operações são possíveis. Quando vierem funções que começam por as.qualquer.coisa() podem inferir que essa função fará as variáveis serem tratadas como se fossem do tipo qualquer.coisa e não do seu tipo original.

variavel <- 'a'
class(variavel)
[1] "character"
class(as.factor(variavel))
[1] "factor"
variavel <- 1
class(variavel)
[1] "numeric"

Redefinições Recursivas

Muitas vezes dizemos que uma variável é igual a ela própria após alguma operação. Ao início isto parece confuso mas depois será intuitivo. O que acontece nestes casos é que a variável passa a guardar o resultado da operação feita com o seu valor antigo. Perdemos o valor original porque redefinimos a variável mas evitamos ter de criar novas variáveis.

v1 <- v1 + 5

v1 <- v1 / 2

Pacotes

O R permite que instalemos pacotes que adicionam funcionalidades à linguagem e simplificam o nosso trabalho. Podemos pensar nesses pacotes como livros de receitas que ao serem descarregados dão ao R a possibilidade de fazer receitas de forma simples, quando antes seria preciso combinarmos muitas receitas, mais básicas, para o conseguirmos fazer. Em termos mais técnicos, os pacotes são bases de código, de R ou de outras linguagens, que tornam disponíveis no nosso ambiente funções e objectos que não estavam disponíveis antes.

Instalar Pacotes

Genericamente, um pacote X é instalado com install.packages("X").

# Exemplo: instalar o `ggplot2` um pacote para fazer gráficos bonitos.
install.packages("ggplot2")
Importante

Só precisam de instalar o pacote uma vez, depois para o usar basta carregarem o pacote com library(nome_do_pacote_sem_aspas).

Ou seja, não devem incluir a função install.packages(...) nos scripts de análise de dados, mas sim executá-la no interpretador se precisarem de instalar um pacote (i.e., se quiserem usar funções dum pacote que não têm isntalado). Isto porque o código que instala o/s pacote/s não é código que deva ser sempre executado, cada vez que se corre a análise, mas sim algo que faz parte da preparação manutenção da instalação do R. Quando falarmos de workflows mais avançados do R veremos como podemos incluir e partilhar um script que instale todos os pacotes necessários de forma a que outras pessoas possam correr a nossa análise.

Carregar Pacotes

Para usar as funcionalidades dum pacote ao longo do script só precisamos do importar uma vez, antes de usarmos qualquer uma das suas funcionalidades, com a função library(nome_do_pacote). Notem que neste caso, ao contrário da instalação, o nome do pacote já não deverá aparecer entre-aspas. Ou seja, para carregar o pacote ggplot2 escreveríamos no script, antes de invocar qualquer uma das suas funcionalidades, library(ggplot2).

Dica

Apesar de podermos importar o pacote a qualquer momento antes do utilizarmos, por uma questão de estilo e organização recomendo MUITO vivamente que todos os pacotes sejam importados numa única secção do script, idealmente nas primeiras linhas e por ordem alfabética.

# Exemplo de script maravilhoso de análise de dados

# Importação de pacotes logo nas primeiras linhas
library(afex)
library(effectsize)
library(emmeans)
library(parameters)

# resto do nosso script maravilhoso
# ONDE EM MAIS NENHUMA LINHA IMPORTAMOS PACOTES

Exemplo do que NÃO fazer:

# Exemplo de script manhoso

dados <- read.csv("../caminho/para/o/ficheiro/dados.csv")

modelo <- lm(VD ~ VI, dados)
# Instalar pacotes aleatoriamente a meio do script só porque sim
install.packages("pacote_desnecessario_so_para_irritar")

# Carregar um pacote fixe mas que não é usado no script só para confundir
library(ggplot2)

# Usar a função dum pacote que não carregámos no script
# Talvez tenhamos carregado o pacote no interpretador e funcionava lá
# Mas não vai funcionar nas próximas vezes tornando o script manhoso
parameters(modelo)
Nota

Isto são questões de estilo, de organização e boas práticas. A ideia não é que se tornem pedantes e arrogantes criticando quem, sem maldade, escreve código assim. Poderão até encontrar casos em se justifica quebrar as recomendações dos guias de estilo.

Actualizar Pacotes

Para actualizarmos todos os pacotes basta corrermos a função update.packages(). Contudo, recomendo adicionar alguns argumentos a essa função para simplificar e evitar problemas na actualização dos pacotes.

update.packages(ask = FALSE, checkBuilt = TRUE,
                NCpus = parallel::detectCores())

Mais concretamente, o argumento ask quando definido como FALSE faz com que o R actualize todos os pacotes sem estar constantemente a pedir a nossa autorização. O argumento checkBuilt quando TRUE faz com que o R confirme se os pacotes instalados foram compilados para versões mais antigas do R e nesse recompila-as na nossa nova versão para evitar incompatibilidades. A opção NCpus define o número de núcleos de processamento a utilizar na compilação de pacotes. Ora como a compilação dos pacotes pode demorar algum tempo, se o nosso computador tiver vários núcleos de processamento, se distribuirmos a tarefa por todos, a compilação pode ser mais rápida. A função detectCores() do pacote parallel é capaz de fazer o que o seu nome indica e detectar quantos núcleos de processamento tem a nossa máquina. Assim sendo, ao definirmos NCpus = parallel::detectCores() estamos a dizer à função update.packages() para usar todos os núcleos da nossa máquina para compilar os pacotes.

Notem que algumas destas opções podem não ter grande efeito em sistemas Windows ou outros quando o R está configurado para instalar versões pré-compiladas dos pacotes em vez dos compilar de raiz. Nesses casos a actualização espera-se mais rápida e as opções podem ter pouco ou nenhum efeito.

Remover Pacotes

Genericamente, um pacote X é removido com remove.packages("X").

# Exemplo: remover o `ggplot2`
remove.packages("ggplot2")

for Loops

Aviso

Esta subsecção será muito estranha para quem não tenha experiência noutras linguagens de programação. Se for o seu caso pode saltá-la agora e voltar mais tarde, até porque não deverá precisar de usar for loops na maioria dos scripts de análise de dados por razões explicadas no fim desta subsecção e na próxima subsecção.

Os for loops no R têm uma sintaxe semelhante à das linguagens que partilham herança histórica com C. Ou seja, usam parêntesis curvos () para definir a duração da iteração e chavetas (curly braces, {}) para encapsular o código a executar durante a iteração. Ao contrário do que acontece em C e noutras linguagens, a iteração é definida recorrendo à palavra chave in para iterar por todos os elementos duma variável/vector/lista/etc…

Vejamos alguns exemplos.

# `i` irá assumir os valores de 1 a 10 ao longo da iteração.
for (i in 1:10) {
    print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
# Criamos uma variável `v1` com as letras "A", "B" e "C".
v1 <- c("A", "B", "C")

# i vai assumir o valor de cada letra ao longo da iteração.
for (i in v1) {
    print(i)
}
[1] "A"
[1] "B"
[1] "C"

Na prática não precisamos de usar muitos for loops no R porque a linguagem está vectorizada. Ou seja, quando fazemos uma operação com uma variável com vários elementos ela é executada (com grande eficiência) em todos os seus elementos.

Vejamos um exemplo.

Criamos uma variável v1 com os números de 3 a 7.

v1 <- c(3, 4, 5, 6, 7)

Se quisermos que v1 passe a conter não os números de 3 a 7 mas os seus dobros (i.e., 6, 8, 10, 12, 14) podemos usar um for loop.

# i vai assumir os números de 1 até à última posição de `v1` (5)
for (i in 1:length(v1)) {
    # Redefinimos o valor na posição i de `v1` como o seu dobro.
    v1[i] <- v1[i] * 2
}

Só que o R não temos de o fazer e podemos escrever algo mais simples.

# Este código faz o mesmo que o for loop anterior e é mais simples.
v1 <- v1 * 2

A Família apply()

As funções da família apply() recebem outras funções como argumentos e organizam a sua aplicação a vários valores. Assim, em muitos problemas, podemos separar a operação que queremos executar da forma como percorremos uma matriz, lista ou vector.

apply()

A função apply() recebe três argumentos principais:

  1. X: a matriz ou array;
  2. MARGIN: a margem a percorrer (1 para linhas e 2 para colunas);
  3. FUN: a função a aplicar.

Se FUN precisar de argumentos adicionais, passamo-los depois destes três argumentos através de ....

# Cria uma base de dados apenas com colunas numéricas.
ds <- data.frame(A = c(1, 2, 3, 4, 5),
                 B = c(10, 20, 30, 40, 50))

# Aplica sum() a cada LINHA de ds.
apply(ds, MARGIN = 1, FUN = sum)
[1] 11 22 33 44 55
# O mesmo cálculo com o pipe nativo.
ds |>
    apply(MARGIN = 1, FUN = sum)
[1] 11 22 33 44 55
# Aplica sum() a cada COLUNA de ds.
apply(ds, MARGIN = 2, FUN = sum)
  A   B 
 15 150 

O exemplo seguinte mostra um argumento adicional destinado a mean(). apply() organiza a repetição e encaminha na.rm = TRUE para a função.

ds_com_nas <- data.frame(A = c(1, 2, NA),
                          B = c(10, NA, 30))

apply(ds_com_nas, MARGIN = 2, FUN = mean, na.rm = TRUE)
   A    B 
 1.5 20.0 

lapply()

A função lapply() aplica FUN a cada elemento de uma lista ou vector e devolve sempre uma lista. Não tem MARGIN, porque percorre directamente os elementos do objecto recebido.

# Calcula todos a raiz quadrada de cada elemento da linha 2.
lapply(ds[2, ], sqrt)
$A
[1] 1.414214

$B
[1] 4.472136
# Definimos a variável `v1` como uma lista de vectores.
v1 <- list(c(1, 2), c(5, 5, 5), c(1, 2, 3, 4))

# Somamos cada elemento da lista, ou seja, os elementos de cada vector.
lapply(v1, sum)
[[1]]
[1] 3

[[2]]
[1] 15

[[3]]
[1] 10

sapply

Semelhante a lapply() mas simplifica o output por exemplo para um array em vez de para uma lista de vectores.

# `v1` é a lista de vectores da secção anterior.
# Somamos cada elemento da lista, ou seja, os elementos de cada vector.
# Com `sapply` o output passa a ser um único vector (não uma lista).
sapply(v1, sum)
[1]  3 15 10
Nota

A função sapply() é uma função utilitária (utility function) para conveniência do utilizador, dado que invoca a função lapply().

mapply

Com a função mapply() entramos ainda mais no reino da programação funcional. A função mapply() vai aplicar uma função que terá como argumentos os elementos das listas/vectores que lhe forem passados. É difícil de explicar…mas fácil de exemplificar.

mapply(sum, c(1, 2, 3), c(10, 20, 30))
[1] 11 22 33

Notem que isto evita ainda mais a necessidade de for loops porque com o mapply() anterior escrevemos numa só linha o seguinte for loop.

arg1 <- c(1, 2, 3)
arg2 <- c(10, 20, 30)

for (i in 1:length(arg1)) {
    print(sum(c(arg1[i], arg2[i])))
}
[1] 11
[1] 22
[1] 33

Controlo de Fluxo/Execução

Mais uma vez a sintaxe do R é semelhante às linguagens com herança partilhada com C, no sentido em que a expressão a comparar está dentro de parêntesis e o código a executar em chavetas.

if (TRUE) {
    print("a")
} else {
    print("b")
}
[1] "a"
if (FALSE) {
    print("a")
} else {
    print("b")
}
[1] "b"

Exercícios

Tentem resolver todos os problemas sem consultar as soluções.

Exercício 1

  • Abra o R e faça algumas contas (no mínimo cinco contas).

  • Eleve um número ao quadrado, ao cubo e a Y.

    • dica: x**y ou x^y.
  • Descubra a ordem de operações do R.

    • dica: É praticamente a mesma que aprendemos na escola.

Soluções

Clique para ver as soluções
# Exemplos de contas.
3 * 3
[1] 9
6000 / 2
[1] 3000
10 + 50
[1] 60
500 - 250
[1] 250
30 * 2 - 5
[1] 55
(30 * 2) - 5
[1] 55
# Números ao quadrado.
6**2
[1] 36
6**3
[1] 216
6**50
[1] 8.082813e+38
# Ordem das operações = PEMDAS.
# (Parêntesis, Expoentes, Multiplicação, Divisão, Adição, Subtracção).
(3 + 2)**2 * 3 / 5 + 2 - 1
[1] 16
5**2 * 3 / 5 + 2 - 1
[1] 16
25 * 3 / 5 + 2 - 1
[1] 16
75 / 5 + 2 - 1
[1] 16
15 + 2 - 1
[1] 16
17 - 1
[1] 16
16
[1] 16

Exercício 2

  • Crie uma nova variável

  • Crie mais uma variável

  • Veja o valor da primeira e da segunda

  • Altere o valor da primeira

  • Confirme que foi alterado

Soluções

Clique para ver as soluções
v1 <- 3

v2 <- 9

print(v1)
[1] 3
print(v2)
[1] 9
v1 <- 5

print(v1)
[1] 5

Exercício 3

  • Crie uma variável numérica.

  • Multiplique-a pelo seu número favorito

  • Divida-a pela sua altura em centímetros.

Soluções

Clique para ver as soluções
v1 <- 7308

v1 * 10
[1] 73080
v1 / 174
[1] 42

Exercício 4

  • Crie uma variável numérica.

  • Crie uma segunda variável cujo valor é o dobro da primeira, sem escrever o valor da primeira.

  • Faça com que a segunda variável seja igual à primeira, sem escrever o valor da primeira e sem invocar a primeira variável.

    • Nota: Esta pergunta tem rasteira pense bem na resposta antes de desistir.

Soluções

Clique para ver as soluções
v1 <- 990

v2 <- v1 * 2

v2 <- v2 / 2

Exercício 5

  • Crie uma variável numérica.

  • Transforme-a num factor.

  • Multiplique-a por dois.

  • Explique porque é que o R deu erro?

  • Transforme-a outra vez em numérica.

  • Multiplique-a por dois.

  • Explique porque é que o R já não deu erro?

  • Explique porque é que o valor da variável mudou?

Soluções

Clique para ver as soluções
v1 <- 22

v1 <- as.factor(v1)

# v1 é agora um factor e não podemos multiplicar factores.
v1 * 2
[1] NA
v1 <- as.numeric(v1)

# v1 é agora novamente numérica e podemos multiplicar números.
v1 * 2
[1] 2

Na penúltima linha de código, v1 tornou-se novamente numérica mas com outro valor. Esse valor corresponde ao número do nível do factor. Por exemplo se eu tiver um vector com “male” e “female” para sexo. Ao passar para numérico poderia passar a ter os valores 1 e 2.

Exercício 6

Depois de executar a seguinte linha de código:

dados <- c(10000, 200, 10, 0.5, -1)
  • Teste quais as posições do vector dados que contêm valores inferiores a 1.

  • Teste quais as posições do vector dados que contêm valores iguais a 0.5.

  • Teste quais as posições do vector dados que contêm valores iguais a 0.5 OU a 200.

  • Teste quais as posições do vector dados que contêm valores superiores a 10 E inferiores a 10000.

Soluções

Clique para ver as soluções
dados < 1
[1] FALSE FALSE FALSE  TRUE  TRUE
dados == 0.5
[1] FALSE FALSE FALSE  TRUE FALSE
dados == 0.5 | dados == 200
[1] FALSE  TRUE FALSE  TRUE FALSE
dados > 10 & dados < 10000
[1] FALSE  TRUE FALSE FALSE FALSE

Exercício 7

  • Crie um vector com 10 números.

  • Crie um vector com 10 palavras.

  • Crie um vector com 5 palavras e 5 números

  • Descubra a classe de cada vector.

Soluções

Clique para ver as soluções
ns <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

ps <- c("branco", "preto", "verde", "vermelho", "amarelo",
        "cinzento", "laranja", "roza", "azul", "roxo")

np <- c(1, "Os", 3, "exercícios", 5, "são", 7, "muito", 8, "chatos", 10)

class(ns)
[1] "numeric"
class(ps)
[1] "character"
class(np)
[1] "character"

Exercício 8

  • Crie um vector com resultados numa escala de 0 a 100 (e.g., notas).

  • Crie um outro vector com esses resultados convertidos para uma escala de 0 a 10.

  • Altere o segundo vector para ter os resultados numa escala de 0 a 20 (e.g., notas).

Soluções

Clique para ver as soluções
notas <- c(56, 88, 90, 63)

notas_rcd <- notas / 10

notas_rcd <- notas_rcd * 2



Exercício 9

Depois de executar as seguintes linhas de código:

idade <- c(21, 32, 24, 46, 33, 20, 50, 39, 18, 20)

sexo <- c("M", "F", "M", "F", "F", "F", "M", "M", "F", "M")

rts <- c(1435, 4352, 2232, 2820, 4552, 6454, 6500, 1005, 9001, 2003)

dados <- data.frame(idade = idade, sexo = as.factor(sexo), rts = rts)
  • Quais são os rts dos participantes entre o participante 1 e o 5?

  • Quais são as idades dos participantes 3, 5 e 7?

  • Qual é a diferença entre idade * 2 e idade[2] * 2?

Soluções

Clique para ver as soluções
print(rts[1:5])
[1] 1435 4352 2232 2820 4552
print(idade[c(3,5,7)])
[1] 24 33 50
# Multiplica todas as idades por 2
idade * 2
 [1]  42  64  48  92  66  40 100  78  36  40
# Multiplica apenas a idade do segundo participante por 2
idade[2] * 2
[1] 64

Se não executou antes, execute agora as linhas de código deste exercício, nomeadamente:

idade <- c(21, 32, 24, 46, 33, 20, 50, 39, 18, 20)

sexo <- c("M", "F", "M", "F", "F", "F", "M", "M", "F", "M")

rts <- c(1435, 4352, 2232, 2820, 4552, 6454, 6500, 1005, 9001, 2003)

dados <- data.frame(idade = idade, sexo = as.factor(sexo), rts = rts)
  • Os exercícios que se seguem assumem que têm essas variáveis no seu ambiente.

Exercício 10

  • Compute a média dos tempos de resposta.
  • Compute o desvio padrão dos tempos de resposta.
  • Compute o t.test() dos tempos de resposta.

Soluções

Clique para ver as soluções
mean(rts)
[1] 4035.4
sd(rts)
[1] 2619.45
t.test(rts)

    One Sample t-test

data:  rts
t = 4.8717, df = 9, p-value = 0.0008815
alternative hypothesis: true mean is not equal to 0
95 percent confidence interval:
 2161.558 5909.242
sample estimates:
mean of x 
   4035.4 

Exercício 11

  • Obtenha apenas a primeira célula do data.frame dados.

  • Obtenha as primeiras três linhas da primeira coluna dos dados.

    • dica: i:j
  • Obtenha as primeira linha das duas primeiras colunas dos dados.

Soluções

Clique para ver as soluções
dados[1, 1]
[1] 21
dados[1:3, 1]
[1] 21 32 24
dados[1, 1:2]
idade sexo
21 M

Exercício 12

  • Compute a média das idades na coluna idade do data.frame dados.

  • Obtenha o sumário da coluna sexo do data.frame dados.

Soluções

Clique para ver as soluções
mean(dados$idade)
[1] 30.3
summary(dados$sexo)
F M 
5 5 



Exercício 13

  • Descubra o manual da função sd().

  • Descubra o manual da própria função help()

Soluções

Clique para ver as soluções
help(sd)
# ?nome_da_função = help(nome_da_função) <=> ? = abreviatura de help()
?sd

help(help)
?help
# ?? diferente de ?help
# ?? é usado para pedir outro tipo de ajuda.

Exercício 14

Nota: Se não executou antes, execute agora as linhas de código do Exercício 9.

Leia correctamente as seguintes linhas de código:

sd(rts)
[1] 2619.45
mean(x = rts)
[1] 4035.4
max(c(3, 5, 7))
[1] 7
menor <- min(c(96, 5, 6))

Soluções

Clique para ver as soluções
# invoca a função sd com a variável rts como argumento.
sd(rts)
[1] 2619.45
# invoca a função mean com a variável rts como o argumento x.
mean(x = rts)
[1] 4035.4
# invoca a função max com o output da função c() (invocada com 3,
# 5 e 7 como argumentos), como argumento.
max(c(3, 5, 7))
[1] 7
# cria a variavel menor onde guarda o output da função min,
# invocada com o output da função c()
# (invocada com 96, 5 e 6 como argumentos), como argumento.
menor <- min(c(96, 5, 6))

Exercício 15

O que consegue inferir do seguinte script:

modelo <- rts ~ sexo

fit <- lm(formula = modelo, data = dados)

resultados <- summary(fit)

Soluções

Clique para ver as soluções
modelo <- rts ~ sexo
# Não conheço o símbolo `~` sei que deve fazer parte dum modelo.
# Ainda não se usou nenhuma função porque ainda não vi `()`.


fit <- lm(formula = modelo, data = dados)
# Usou a função `lm()` com o argumento da `formula` com o valor do
# `modelo` e o argumento `data` com o valor dos `dados`.
# A função `lm()` deve aceitar fórmulas em modelos e dados como argumentos.
# O output talvez seja o fit do modelo
# Nada melhor do que ler o manual da função lm: help(lm)


resultados <- summary(fit)
# O `summary()` da variável `fit` (que guardava o output da função
# `lm()` com as variáveis `modelo` e `dados` como input) deve dar como
# output algum resultado estatístico relevante por ser guardado numa
# variável com o nome de `resultados`.
# Se os nomes das variáveis não fossem tão claros não tinha inferido
#  tanta coisa.
# Se o script tivesse sido escrito com mais comentários `# ` eu teria
# percebido melhor.
# Vou dar bons nomes às minhas variáveis e comentar os meus scripts!

Exercício 16

  • Dê um exemplo de um erro de sintaxe que tenha encontrado ao tentar resolver estes exercícios.

  • Clarifique porque se trata de um erro de sintaxe e não de semântica.

  • Dê um exemplo de um erro semântica que tenha cometido ao resolver estes exercícios.

  • Clarifique porque se trata de um erro semântico e não de sintaxe.

Soluções

Clique para ver as soluções
  • Qualquer erro em que o R se tenha recusado a executar o código por ter alguma letra ou conjunto de letras mal escritas (e.g., parêntesis a mais ou a menos).

  • Trata-se de um erro de sintaxe se após a sua correcção e execução o resultado obtido era o pretendido apesar da sintaxe inicial ter sido a errada.

  • Qualquer erro em que o R tenha executado o código sem problema mas o resultado não era o esperado.

  • Trata-se de um erro semântico porque apesar da sintaxe estar correcta o processo descrito não leva ao resultado que queríamos.