Prefácio

Este livro está a ser construído pelo RUGGED, um grupo de utilizadores do R. Veja o nosso site rggd.gitlab.io para saber mais sobre nós e para instruções de como fazer parte do grupo.

Se foi redireccionado para esta página a partir do site do RUGGED, pode voltar à página principal clicando aqui.

Primeira Parte

Nesta primeira parte introduzimos o livro, expondo a sua anatomia (os leitores mais sensíveis poderão ficar chocados). Sugerimos também várias formas de usar o livro, terminando com alguns avisos e notas.

1 Que Livro é Este?

Este livro pretende servir de apoio a quem queira aprender a usar o R para análises de dados da psicologia ou áreas semelhantes. Começamos por apresentar os básicos do R e da estatística, para depois apresentar de forma sintética como realizar as análises no R. Depois mostramos como o R pode ser usado para produzir gráficos e relatórios apelativos, bem como deixamos algumas dicas para aumentar a produtividade no uso do R. Ao longo do livro recorremos a demasiados trocadilhos, piadas secas e outras tentativas de humor com resultados não significativos.

2 Anatomia do Livro

Neste momento o livro é composto por sete partes:

  1. Introdução ao Livro (estamos aqui)
  2. Introdução ao R
  3. Teoria
  4. Análises no R
  5. Fazer Coisas Lindas no R
  6. Trabalhar Eficazmente no R
  7. Anexos

2.1 Dissecando o Livro

Esta introdução (parte I) pretende apenas dar a conhecer o livro, ajudando o leitor a embarcar nesta viagem à descoberta do R, da estatística, e do arrependimento de ter escolhido ler este livro, quando havia tantas alternativas melhores…

A segunda parte tem como objectivo apresentar o leitor ao R, dado que ele é tímido, tem poucas competências sociais e nem sempre sabe ser bem educado, apresentando-se a quem o está a conhecer pela primeira vez. Nesta parte tentamos traduzir os termos mais técnicos da programação para termos mais compreensíveis. Por essa razão é importante que leia com atenção esta secção para perceberem os termos usados no resto do livro.

Na terceira parte apresentamos/revemos, de forma crítica, os conceitos básicos da estatística, seguindo a abordagem de comparação de modelos (Judd et al., 2017). Note que estes conceitos são básicos na medida em que basicamente ninguém os percebe, estando por isso na base da maioria dos desentendimentos da estatística. Neste livro pretendemos separar a teoria da prática da estatística. Nas secções teóricas não nos preocupamos em explicar como fazer as análises. Nas secções práticas não nos preocupamos em explicar a interpretação das análises e a teoria subjacente. Por essa razão recomendamos que leia a parte teórica com atenção porque esses conceitos poderão ser necessários mais tarde.

A quarta parte do livro inclui as secções em que demonstramos como computar diferentes modelos e análises estatísticas no R. Nestas secções tentaremos ser sintéticos. Vamos focar-nos apenas nos passos necessários para realizar as análises no R, evitando perder tempo com explicações sobre a linguagem R, ou sobre estatística. Ou seja, nestas secções assumiremos que domina os básicos do R, apresentados na segunda parte, bem como os conceitos estatísticos, apresentados na terceira.

O objectivo da quinta parte é demonstrar como o R pode ser usado para produzir gráficos, relatórios e outros outputs com um visual profissional e apelativo. O Principezinho dizia que o “Essencial é invisível aos olhos” e tinha razão porque não interessa ter gráficos incríveis se estes não apresentarem informação relevante. Por outro lado, o Principezinho nunca deve ter tido de fazer uma apresentação para uma audiência que não soubesse interpretar tabelas de ANOVAs… Duvido que tenha tido de falar para quem quisesse ver gráficos com cores bonitas, que o ajudasse a perceber porque é que o Princepezinho perdeu tantas horas a fazer análise estatísticas e o que é que aprendeu com isso… Mais uma vez, nestas secções assumiremos que domina os básicos do R, apresentados na segunda parte, bem como os conceitos estatísticos, apresentados na terceira.

Na sexta parte, vamos propor várias formas de trabalhar mais eficazmente no R. Vamos propor ao leitor uma troca. Vamos pedir que abdique de algum tempo e conforto para aprender a usar o R de forma mais avançada. Em troca mostraremos formas de poupar tempo automatizando tarefas repetitivas e de usar ferramentas mais avançadas (e.g., git, Containers) para gerir o seu trabalho. Nestas secções voltaremos a ter de assumir que o leitor já domina os conceitos dos capítulos anteriores. Dado o seu intuito este capítulo poderá ser mais benéfico para quem já tenha alguma experiência a trabalhar com o R e queira aprender a ser mais eficiente.

A sétima parte é onde poderá encontrar alguns anexos que poderão servir de referências para consulta rápida no futuro. Dado que o objectivo desses anexos é servirem de ajudas à memória sobre as secções do livro, eles serão mais úteis para quem leu o livro e dominou os seus conceitos.

3 Como Usar Este Livro

Pode escolher ler este livro de diversas formas consoante for o seu objectivo. Isto se o seu objectivo não for ler um livro que lhe dê prazer. Dada a quantidade de tentativas de humor falhadas do livro, a vergonha alheia que vai sentir dos autores será sempre superior a qualquer prazer que pudesse tirar da leitura. Estando feita a ressalva podemos então listar as formas de ler o livro, com os objectivos e públicos alvo a que se adequam, bem como as suas potenciais limitações.

3.1 Ler do Princípio ao Fim

Esta é sempre uma forma de ler um livro para quem não é hipster que abomina o mainstream. Se estiver disposto a embarcar nesta viagem pela estatística com o R seguindo o itinerário que criámos esta será uma excelente forma de ler o livro.

Objectivo: Aprender estatística e R simultaneamente seguindo a nossa pedagogia.

Público alvo: Pessoas, sem bases de programação, que queiram aprender a usar o R e que estejam disponíveis a aprender (ou reaprender) estatística seguindo a abordagem de comparação de modelos.

Limitações: Implica algum investimento de tempo e paciência para seguir todo o itinerário.

3.2 Ler Apenas a Segunda e Quarta Partes

Para quem só queira aprender a usar o R para fazer as análises que antes fazia noutro programa, esta é uma forma de ler apenas as secções estritamente necessárias para isso.

Objectivo: Aprender a usar o R para substituir outro programa.

Público alvo: Pessoas com boas bases de estatística que estejam à vontade a usar outro programa e queiram aprender a usar o R para fazer as mesmas análises que fazem nesse outro programa.

Limitações: Para compreender a forma como se constroem e testam modelos no R, é importante entender os conceitos da abordagem de comparação de modelos (Judd et al., 2017), explicados na terceira parte.

3.3 Ler Apenas a Sexta Parte

Quem já trabalha no R há bastante tempo e domina os conceitos estatísticos e análises a que recorre pode estar só à procura de formas de trabalhar no R mais eficazmente, que é o tema da sexta parte.

Objectivo: Apenas aprender a usar o R mais eficazmente para fazer aquilo que já faz no R.

Público alvo: Pessoas com boas bases do R e de estatística que queiram começar a automatizar o seu trabalho recorrendo a ferramentas tipicamente usadas por programadores.

Limitações: Apesar de poder conhecer o R e dominar a estatística ao saltar as restantes partes corre o risco de não estar familiarizado com os termos e abordagens que usamos neste livro.

3.4 Ler Apenas os Anexos/Sétima Parte

Os membros veteranos do RUGGED, ou leitores que já tenham lido todo o livro, podem preferir usar apenas alguns anexos para avivarem rapidamente a memória.

Objectivo: Avivar a memória de alguma parte/técnica/ensinamento do livro.

Público alvo: Membros veteranos do RUGGED ou leitores que tenham lido o livro do fio a pavio.

Limitações: Os anexos só serão úteis a quem ainda tenha o livro relativamente fresco na memória.

3.5 Acrescentar o Livro a Uma Lista de Referências

Quando queremos aprender a usar o R muitas vezes o primeiro passo é começarmos a criar uma lista de referências de recursos (e.g., livros, tutoriais, vídeos, etc…) que poderemos usar para aprender. O problema é que muitas vezes esta primeira fase vai-se estendendo e acabamos por compilar uma lista tão grande que não sabemos por onde começar. Às vezes até decidimos pegar num livro/recurso, mas assim que nos deparamos com a primeira dificuldade mudamos automaticamente de recurso, porque sabemos que temos muitos a que podemos recorrer. Estando este livro disponível gratuitamente online é tentador adicioná-lo a essa longa e crescente lista de recursos.

Objectivo: Compilar uma lista de recursos para poder aprender a usar o R.

Público alvo: Pessoas que estejam a compilar uma lista de recursos sobre o R.

Limitações: Este poderá apenas ser mais um recurso (e pior que os outros) numa longa lista de opções. Ao continuar a compilar a lista poderá só estar a adiar o momento em que começa a pôr a mão na massa e começar a usar o R.

3.6 Fazer Copy-Paste de Código da Quarta Parte

Honestamente, quando começamos a usar o R, provavelmente, 90% do que fazemos é fazer copy-paste de código que faça algo muito parecido ao que nós queremos e adaptá-lo ao problema que queremos resolver. Mais tarde, quando já temos mais experiência a usar o R, apenas 89.99% do que fazemos é copy-paste de código de outras pessoas. O problema é que se não decidirmos aprender um pouco melhor os básicos do R e da estatística vamos sempre sentir que não percebemos o nosso código, apesar de ele até fazer mais ou menos o que queremos. Assim sendo nunca vamos chegar ao ponto em que sentimos orgulho por compreender bem o script que escrevemos de raiz. Nem nos vamos poder sentir completamente perdidos quando esse script incrível não fizer nada do que era suposto fazer.

Objectivo: Arranjar um bocado de código que faça o que queremos e que possamos adaptar para resolver o problema que temos em mãos.

Público alvo: Pessoas que estejam a usar o R para fazer as suas análises, que tenham um problema concreto a resolver e que não tenham tempo para investir numa aprendizagem aprofundada.

Limitações: Se sentimos que não temos tempo agora para aprender o R com mais calma, no futuro é provável que sintamos que ainda temos menos tempo. Corremos portanto o risco de nunca aprender os conceitos base e perdermos mais tempo à procura de código para fazer copy-paste do que a escrever código que faça o que queremos.

4 Avisos e Notas

AVISOS:

  • Seguem-se vários avisos e notas. Se for rebelde pode ignorar o resto desta secção mas não nos responsabilizamos pelos danos causados.

  • Estando o livro em construção é possível que os capítulos estejam muito incompletos, sejam alterados frequentemente, ou tenham informações erradas.

  • Garantimos que o livro não tem qualquer garantia.

  • Ao longo do livro faremos trocadilhos e outras tentativas de humor piores que a anterior. Se não tiver para aturar isso deverá procurar alternativas.

Notas:

  • Estamos a trabalhar numa tradução deste livro para Inglês mas neste momento, essa tradução ainda mal começou. Quando tivermos uma boa parte do livro traduzida começaremos a divulgar os links para essa versão.

  • Este livro é partilhado ao abrigo da licença de código aberto/open source GPL-v2.0. Isto quer dizer que pode, partilhar, alterar e partilhar as suas alterações de qualquer parte deste livro (incluindo código) desde que partilhe essas alterações com a mesma licença.

  • Este livro está aberto a contribuições, estando todos o seu código fonte/source code disponível em gitlab.com/rggd/moc. Ao contribuir está a certificar que tem direito de fazer a contribuição ao abrigo da licença (ver gitlab.com/rggd/moc/-/blob/main/DCO.md). Reservamo-nos o direito de aceitar/rejeitar arbitrariamente as contribuições feitas para o GitLab do projecto (sendo que poderá sempre partilhar as suas alterações noutro local e com quem quiser, nos termos da licença GPL-v2.0).

  • Estamos receptivos a quem se proponha a traduzir este livro para outra língua. Podemos partilhar essa tradução nas páginas oficiais do projecto caso esteja interessado.

Segunda Parte

Esta segunda parte do livro foca-se em explicar os básicos do R. O primeiro capítulo procura providenciar um primeiro contacto com o R. Vamos guiar o leitor—Sim, tu! Olá!—enquanto ele (continuas a ser tu) abre o R e começa a executar algum código. No segundo capítulo começamos a decompor de forma mais sistemática, os conceitos fundamentais da linguagem R. Passamos depois para questões mais sobre como trabalhar com bases de dados. Tomámos a opção de evitar ao máximo usar conceitos estatísticos nesta parte. Esta decisão foi tomada para evitar que aquilo que possa ser uma dificuldade na compreensão da estatística, se confunda como uma dificuldade em compreender o funcionamento do R. Por esta razão, o uso do R para a computação de análises estatísticas só será abordado na quarta parte deste livro. Esperamos que assim esta parte do livro possa servir como recurso a consultar quando surgem questões técnicas relacionadas com o uso e especificidades da linguagem R.

5 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.

5.1 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")

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.filipe.santos@campus.ul.pt).

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

5.2 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")

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, ou seja, não a tenta executar, por isso podemos lá escrever 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. 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

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

Porquê?

Porque muito dos avanços iniciais na computação ocorreram nos EUA. O Inglês não tem essas letras e os computadores também não os tinham.

Também devemos evitar usar esses caracteres em nomes de ficheiros. Ou seja, 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 variavel2.
# A função c também cria vectores de outros vectores.
# Guarda o outupt da função c, invocada com dados e variavel2 como
# argumento na variável dados.
dados <- c(dados, variavel2)
Error: object 'variavel2' not found

# 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
Error: object 'fadfadsfasdf' not found
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

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:

# 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(cooked_to = "medium-rare"): 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(cooked_to = "medium-rare", egg = TRUE): 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 funções como o argumento de outras funções. Ou seja, podemos usar o produto de algumas receitas como ingredientes noutras receitas. Por exemplo eu posso usar o resultado de uma receita de chantilly caseiro como ingrediente numa receita de um bolo.

# Cria a preparação outra_media onde junta o resultado da receita mean.
# Usa a receita mean com o resultado da receita c como ingrediente.
# Invoca a função mean com o output da função c como argumento.
outra_media <- mean(c(500,300,600,300,400))

# Se encadearmos muitas funções o código pode ficar difícil de ler.
ilegivel <- mean(c(c(500,300), c(600, 300), mean(c(300, c(400, 800)))))

# Podemos colocar diferentes argumentos em diferentes linhas.
# Se pusermos espaços antes dessas linhas clarificamos a ordem dos argumentos.
mais_legivel <- mean(c(c(500,300),
                       c(600, 300),
                       mean(c(300,
                              c(400, 800)))))

# Em muitos casos será melhor "partir" o código em vários passos.
# Depois conseguiremos encontrar mais fácilmente os nossos erros.
temp <- c(500, 300)
temp <- c(temp, c(600, 300))
temp2 <- c(300, c(400, 800))
legivel <- mean(c(temp, temp2))

O R é uma linguagem formal.

As linguagens formais são aquelas que têm só uma interpretação. Por exemplo, em notação química H2O só pode ser a molécula da água. Mas HdoisO NÃO é uma forma válida de descrever a molécula da água.

Neste sentido, basta que o nosso código tenha um erro de sintaxe (ortográfico) que o R já não o conseguirá executar. Ou seja, ao mínimo erro as receitas podem não funcionar. Mesmo que seja uma receita tão simples como calcular uma média…

# O `R` não consegue executar a próxima linha.
meam(novos_dados)
Error in meam(novos_dados): could not find function "meam"

O R diz que não tem a função? O R não tem a função para a média? Tem, só que se chama mean e não meam(). Sendo uma linguagem formal mean tem um só significado e meam() nenhum.

O interpretador (o chef que o R traz consigo) também não nos corrigiu. Como faz exactamente o que nós dizemos tentou fazer a receita meam(). Só que ele não tinha a receita meam() e foi isso que nos disse na mensagem de erro.

Conclusão, temos que 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 que ter muito cuidado a escrever receitas. A boa notícia é que quando nos enganamos o R diz-nos onde foi (em scripts com várias linhas de código o R indica em que linha de código está o erro). Ao lermos a mensagem de erro descobrimos o que temos de corrigir.

Dado que ao mínimo erro a nossa receita/script pode deixar de funcionar, é boa ideia, enquanto escrevemos receitas/scripts irmos testando se elas estão funcionar. Se der algum erro, lemos a mensagem e tentamos ver 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.

A este processo de detectar e corrigir erros no código chamamos de debugging.

Atenção: existe um tipo de erros pior do que o anterior. O R detecta os erros anteriores - os erros de sintaxe (ortográficos, dizermos o que queríamos de uma forma errada).

Contudo o R não detecta os erros semânticos (dizermos o que não queríamos duma forma correcta).

# Erro semântico óbvio
media_novos_dados <- 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)

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)

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!

6 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 fazer 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 ler.

6.1 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 estudado nas investigações. No R é fácil de confundirmos os dois conceitos porque quando o usamos para análises estatísticas as nossas variáveis no R podem corresponder a variáveis do nosso estudo, 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.

6.1.1 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<U+00E1>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.

6.1.2 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 o 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.

6.1.3 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.

6.1.3.1 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).

6.2 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.

6.2.1 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"

6.2.2 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"

6.2.3 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

6.2.4 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.

6.2.4.1 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.

6.2.4.2 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.

6.3 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.

6.3.1 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.
rts[c(1, 3, 6)]
[1] 1435 2232 6454

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…

6.3.2 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
1    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
1     21      M 1435
2     32      F 4352
3     24      M 2232
4     46      F 2820
5     33      F 4552
6     20      F 6454
7     50      M 6500
8     39      M 1005
9     18      F 9001
10    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
1      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
1     21      M 1435
2     32      F 4352
3     24      M 2232
4     46      F 2820
5     33      F 4552
6     20      F 6454
7     50      M 6500
8     39      M 1005
9     18      F 9001
10    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
1    21      M 1435
2    32      F 4352
3    24      M 2232
4    46      F 2820
5    33      F 4552
6    20      F 6454
# Podemos pedir para ver mais ou menos linhas.
head(dados, n = 4)
  idade genero  rts
1    21      M 1435
2    32      F 4352
3    24      M 2232
4    46      F 2820
head(dados, 8)
  idade genero  rts
1    21      M 1435
2    32      F 4352
3    24      M 2232
4    46      F 2820
5    33      F 4552
6    20      F 6454
7    50      M 6500
8    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.

6.3.3 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.

6.3.3.1 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("a"): unused argument ("a")
hello(3)
Error in hello(3): unused argument (3)

6.3.3.2 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

6.3.3.3 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 especificarmos o nome dos argumentos já não os temos que escrever na ordem em que os escrevemos quando criámos a função. Neste caso isso parece pouco útil. Contudo, quando estivermos a usar funções escritas por outros podemos saber os argumentos que levam mas não a ordem em que aparecem e especificando o nome nunca teremos problemas.

# 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

6.3.3.4 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(y = 2): argument "x" is missing, with no default

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

6.3.3.5 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

6.3.3.6 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 dentro doutras são executadas primeiro e os seus outputs são passados como argumentos às funções que estão de fora. No fundo isto só quer dizer que se resolve primeiro o que está dentro do parêntesis duma função que esteja dentro do 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.

Dito isto, as versões mais recentes do R permitem usar pipes, que se escrevem como |> que encaminham o output de uma função para servir de argumento à próxima.

# Sem usar pipes.
v1 <- c(1, 2, 3)
mean(v1)
[1] 2
# Ou ainda
mean(c(1, 2, 3))
[1] 2
# Com pipes.
c(1, 2, 3) |> mean()
[1] 2
# Ou seja, o pipe como os caracteres indicam envia o ouput duma função
# para "dentro" da outra.

Há quem use então esses pipes para encadear uma sequência de várias funções.

# Usar pipes para encadear mais funções
c("1", "2", "3") |> as.numeric() |> mean() |> print()
[1] 2
# A mesma coisa mas em linhas separadas para ser mais legível.
c("1", "2", "3") |>
    as.numeric() |>
        mean() |>
            print()
[1] 2
# Sem usar pipes ou sem usar variáveis intermédias fica menos legível.
print(mean(as.numeric(c("1", "2", "3"))))
[1] 2

Importa resalvar que antes dos pipes fazerem parte da linguagem R de forma oficial, já tinham sido implementados noutros pacotes, nomeadamente nos do tidyverse. No entanto a síntaxe dos pipes nessas outras versões era algo diferente, por exemplo, nos pacotes do tidyverse a sintaxe dos pipes é esta: %>%. Como os pacotes do tidyverse são muito populares, é possível que encontrem código na internet, ou nos scripts das pessoas com quem colaborem, com essa síntaxe, mas fiquem descansados que fazem o mesmo que |>.

6.3.3.7 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 = 1, maximo_da_escala = 5)
[1] 5 4 5 6 3 2 1

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

6.3.3.8 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

6.3.3.9 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 possiblidade 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, ou mesmo estudar uma linguagem puramente funcional como LISP ou Haskell.

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 exectar 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)
}

Asssim 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

6.3.3.10 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!!!

6.4 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.

6.4.1 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 ser muito estranho. Seja qual 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
# AVISO: Para fazer contas com vectores têm que ter o mesmo tamanho.
tmp <- c(3, 4, 2)

# Caso contrário só primeiro elemento será usado
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

6.4.2 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

6.4.3 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"

6.4.4 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

6.5 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.

6.5.1 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")

Notem que só precisam de instalar o pacote uma vez, depois para o usar basta carregarem o pacote e irem-no actualizando de vez em quando. Ou seja, esta é uma função que nem devem incluir nos scripts de análise de dados, mas sim correr no interpretador. Isto porque 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.

6.5.2 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). 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. A ideia é que não cometam esses erros e informem delicadamente as pessoas de porque é que não será boa ideia fazê-lo, tendo abertura para perceber se há alguma coisa que faz com que até nem seja má ideia no caso específico em que o estão a fazer.

6.5.3 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.

6.5.4 Remover Pacotes

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

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

6.6 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] <- v[i] * 2
}
Error: object 'v' not found

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

6.7 A Familía apply

ds <- data.frame(A = c(1, 2, 3, 4, 5), B = c(10, 20, 30, 40, 50))

6.7.1 apply

# Aplica a função `sum()` a cada LINHA de `ds`.
apply(ds, 1, sum)
[1] 11 22 33 44 55

# Aplica `sum()` a cada COLUNA de `ds`.
apply(ds, 2, sum)
  A   B 
 15 150 

6.7.2 lapply

# Soma todos os elementos da linha 2.
lapply(ds[2, ], sum)
$A
[1] 2

$B
[1] 20
# 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

6.7.3 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: sapply() é uma função utilitária para conveniência do utilizador, dado que lapply() já permite que o utilizador defina o argumento simplify como verdadeiro (e.g., lapply(v1, sum, simplify = TRUE)).

6.7.4 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

6.8 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"

6.9 Exercícios

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

6.9.1 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.

6.9.2 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

6.9.3 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

6.9.4 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

6.9.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.

6.9.6 Soluções

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

v1 * 10
[1] 73080

v1 / 174
[1] 42

6.9.7 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.

6.9.8 Soluções

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

v2 <- v1 * 2

v2 <- v2 / 2

6.9.9 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?

6.9.10 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
Warning in Ops.factor(v1, 2): '*' not meaningful for factors
[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.

6.9.11 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.

6.9.12 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

6.9.13 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.

6.9.14 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"

6.9.15 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).

6.9.15.1 Soluções

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

notas_rcd <- notas / 10

notas_rcd <- notas_rcd * 2



6.9.16 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?

6.9.16.1 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

AVISO:

  • 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.

6.9.17 Exercício 10

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

6.9.18 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 

6.9.19 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.

6.9.19.1 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
1    21    M

6.9.20 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.

6.9.21 Soluções

Clique para ver as soluções
mean(dados$idade)
[1] 30.3

summary(dados$sexo)
F M 
5 5 



6.9.22 Exercício 13

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

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

6.9.23 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.

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

6.9.25 Soluções

Clique para ver as soluções
# invoca a função sd com a variável sd 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))

6.9.26 Exercício 15

O que consegue inferir do seguinte script:

modelo <- rts ~ sexo

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

resultados <- summary(fit)

6.9.27 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!

6.9.28 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.

6.9.29 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.

7 Introdução aos data.frames

Neste capítulo vamos focar-nos em explicar uma classe de variáveis muito importante no R—os data.frames. Vamos ver que podemos representar a maioria das nossas bases de dados usando estas estruturas. Compreender bem como trabalhar com data.frames é essencial para poder importar, limpar, (re)formatar e analisar os nossos dados.

7.1 Vectores

Já vimos que a função c() combina os seus argumentos num vector e que podemos usá-la 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)

Também já vimos que o R nos permite fazer muitas coisas com os vectores, desde seleccionar valores que ficam em posições específicas, a realizar a mesma operação matemática em todos os elementos, a operações de lógica.

idade[1]   # pedir o primeiro elemento.
[1] 21
idade[3:5] # pedir todos os elementos entre 3 e 5.
[1] 24 46 33
idade[-2]  # pedir todos os elementos excepto o segundo.
[1] 21 24 46 33 20 50 39 18 20
idade * 3  # multiplicar todos os elementos por 3.
 [1]  63  96  72 138  99  60 150 117  54  60
idade > 5  # ver quais os elementos maiores que 5.
 [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
idade[idade > 20]  # pedir os elementos com maior que 20.
[1] 21 32 24 46 33 50 39

7.2 Noções Gerais

Se repararmos nos exemplos anteriores vemos que cada vector, pode conter vários elementos mas só tem uma dimensão. No entanto, as bases de dados com que estamos habituados a trabalhar têm duas dimensões—linhas e colunas. Normalmente temos um variável por coluna e depois uma ou mais linhas por sujeito/observação.

Um vector só tem uma dimensão mas um data.frame já tem duas—linhas e colunas—portanto podemos usar um data.frame como base de dados. Na maioria, dos casos vamos importar os dados de um ficheiro e guardá-lo numa variável do tipo data.frame.

Antes de fazermos isso, vamos ver alguns exemplos mais simples, que nos podem ajudar a perceber como os conhecimentos que temos sobre vectores nos ajudam a trabalhar com os data.frames. Mais concretamente, vamos ver que no fundo podemos trabalhar com cada dimensão do data.frame como se fosse um vector.

7.3 Criar um data.frame

Para criarmos um data.frame usamos a função data.frame(), essa função leva como argumentos os nomes das colunas e os seus valores.

teste <- data.frame(coluna1 = c("valores", "da", "primeira", "coluna"),
                    coluna2 = c("valores", "diferentes", "na", "segunda"))

print(teste)
   coluna1    coluna2
1  valores    valores
2       da diferentes
3 primeira         na
4   coluna    segunda

Se cada vector é unidimensional, podemos combinar vários vectores num só data.frame—um objecto bidimensional. Podemos fazê-lo de várias maneiras. Um exemplo intuitivo pode ser um caso em que tenhamos vários vectores como os de há pouco, cada um com os valores duma dada variável, que queremos combinar numa base de dados.

dados <- data.frame(idade = idade, genero = as.factor(genero), rts = rts)
# as.factor(genero) vai guardar a variável género como factorial.
print(idade)
 [1] 21 32 24 46 33 20 50 39 18 20
print(genero)
 [1] "M" "F" "M" "F" "F" "F" "M" "M" "F" "M"
print(rts)
 [1] 1435 4352 2232 2820 4552 6454 6500 1005 9001 2003

print(dados)
   idade genero  rts
1     21      M 1435
2     32      F 4352
3     24      M 2232
4     46      F 2820
5     33      F 4552
6     20      F 6454
7     50      M 6500
8     39      M 1005
9     18      F 9001
10    20      M 2003

7.4 Aceder às Linhas dum data.frame

Da mesma maneira que para acedermos ao primeiro valor do vector idade escrevíamos:

idade[1]
[1] 21

Para aceder à primeira linha do data.frame dados (que criámos há pouco) basta escrevermos:

dados[1, ]
  idade genero  rts
1    21      M 1435

Se reparem, a única diferença é que agora acrescentámos uma vírgula. Temos de fazer isso porque separamos cada dimensão dos objectos com uma vírgula. Assim sendo, o primeiro valor à esquerda da vírgula corresponde à linha. Portanto se quisermos a segunda linha, escrevemos:

dados[2, ]
  idade genero  rts
2    32      F 4352

Por outro lado, se quisermos todas as linhas menos a segunda, escrevemos:

dados[-2, ]
   idade genero  rts
1     21      M 1435
3     24      M 2232
4     46      F 2820
5     33      F 4552
6     20      F 6454
7     50      M 6500
8     39      M 1005
9     18      F 9001
10    20      M 2003

Da mesma maneira que podíamos pedir uma sequência de valores dum vector usando a notação início:fim, por exemplo:

idade[3:5]
[1] 24 46 33

Também podemos pedir várias linhas, usando a mesma notação, por exemplo:

dados[3:5, ]
  idade genero  rts
3    24      M 2232
4    46      F 2820
5    33      F 4552

No geral, vamos poder fazer tudo o que fazemos com os vectores, com as linhas dos data.frames.

idade * 3
 [1]  63  96  72 138  99  60 150 117  54  60
idade > 5
 [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

dados[1, ] * 3
Warning in Ops.factor(left, right): '*' not meaningful for factors
  idade genero  rts
1    63     NA 4305
dados[1, ] > 5
Warning in Ops.factor(left, right): '>' not meaningful for factors
  idade genero  rts
1  TRUE     NA TRUE

Nota: O R pode avisar-nos que algumas operações podem não fazer sentido para alguns tipos de variáveis (como foi o caso com o género), ou mesmo dar erro se a operação não for permitida.

7.5 Aceder às Colunas dum data.frame

Para trabalhar com as colunas dum data.frame podemos fazer tudo o que fazemos com as linhas, sendo que escrevemos o/s números da/s coluna/s à direita da vírgula.

dados[ ,1]
 [1] 21 32 24 46 33 20 50 39 18 20
dados[ ,2:3]
   genero  rts
1       M 1435
2       F 4352
3       M 2232
4       F 2820
5       F 4552
6       F 6454
7       M 6500
8       M 1005
9       F 9001
10      M 2003
dados[ ,-2]
   idade  rts
1     21 1435
2     32 4352
3     24 2232
4     46 2820
5     33 4552
6     20 6454
7     50 6500
8     39 1005
9     18 9001
10    20 2003

dados[ ,1] * 3
 [1]  63  96  72 138  99  60 150 117  54  60
dados[ ,1] > 5
 [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

Caso as colunas tenham nomes (como na maioria dos nossos casos) também podemos aceder às colunas usando o seu nome. Para isso podemos usar a notação base_de_dados$nome_da_coluna ou a notação base_de_dados[ ,"nome_da_coluna"].

Isso quer dizer que para vermos a coluna idade podemos escrever:

dados$idade
 [1] 21 32 24 46 33 20 50 39 18 20

# Ou

dados[ ,"idade"]
 [1] 21 32 24 46 33 20 50 39 18 20

Como podemos descobrir os nomes das colunas? Basta invocarmos a função colnames() usando o nosso data.frame como argumento.

colnames(dados)
[1] "idade"  "genero" "rts"   

Já vimos que o data.frame dados tem duas dimensões—linhas e colunas. Também já vimos como podemos aceder às suas linhas e colunas. Pensando que cada coluna (e linha) só tem uma dimensão, percebemos que podemos colunas como argumentos de algumas funções, que já conhecemos que aceitam, vectores como argumentos.

Exemplos:

mean(dados$idade)
[1] 30.3

mean(dados$rts)
[1] 4035.4
sd(dados$rts)
[1] 2619.45
max(dados$rts)
[1] 9001
summary(dados$rts)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1005    2060    3586    4035    5978    9001 

table(dados$genero)

F M 
5 5 
summary(dados$genero)
F M 
5 5 

Ao mesmo tempo, se pensarmos que cada coluna pode corresponder a uma variável do nosso estudo, começamos a ter uma noção de como vamos poder usar as funções do R nas nossas análises estatísticas…

Nota: Os data.frame também podem ter nomes de linhas (que aparecem à esquerda como se fossem uma coluna extra sem nome) mas normalmente esses nomes não nos dão jeito.

print(dados)
   idade genero  rts
1     21      M 1435
2     32      F 4352
3     24      M 2232
4     46      F 2820
5     33      F 4552
6     20      F 6454
7     50      M 6500
8     39      M 1005
9     18      F 9001
10    20      M 2003

Às vezes podem corresponder ao número da linha, que por vezes é o número do participante. Mesmo nesses casos, dá-nos mais jeito ter os números dos participantes numa coluna do que nos nomes da linha.

Se quisermos saber os nomes das linhas podemos usar a função row.names().

row.names(dados)
 [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"

row.names(dados) <- paste0("new_name", row.names(dados))

row.names(dados)
 [1] "new_name1"  "new_name2"  "new_name3"  "new_name4"  "new_name5" 
 [6] "new_name6"  "new_name7"  "new_name8"  "new_name9"  "new_name10"

Se quisermos apagar esses nomes para não nos confundirem podemos escrever:

row.names(dados) <- NULL

row.names(dados)
 [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"

Quando apagamos os nomes das linhas eles são substituídos apenas pelos números das linas, logo não ganhamos nada em tentar apagar os nomes das linhas se eles forem apenas os seus números. nome à esquerda.

row.names(dados) <- NULL

row.names(dados)
 [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"

# Por muito que apague os nomes das linhas eles continuam a ser
# substituídos pelos números das linhas.
row.names(dados) <- NULL

row.names(dados)
 [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"

7.6 Aceder às Linhas e Colunas dum data.frame

Já vimos que os data.frame têm duas dimensões [linhas, colunas]. Então para termos só uma célula basta acedermos a uma linha uma coluna [linha, coluna].

# Primeira célula da primeira coluna.
dados[1, 1]
[1] 21

# Segunda célula da terceira coluna.
dados[2, 3]
[1] 4352

Podemos também aceder apenas a alguns “bocados”, isto é conjuntos de linhas e colunas do data.frame. Por exemplo, para obtermos as primeiras três linhas da primeira coluna, escrevemos:

dados[1:3, 1]
[1] 21 32 24

Se quisermos a primeira linha da duas primeiras colunas, escrevemos:

dados[1, 1:2]
  idade genero
1    21      M

Podemos também usar um vector com os nomes das colunas que queremos:

dados[1, c("idade", "genero")]
  idade genero
1    21      M

Nota: Reparem que se quisermos mais que pôr os nomes de várias colunas temos mesmo de os colocar num vector. Se separarmos os nomes por vírgulas, sem usar a função c() o R vai dar erro, dado que as vírgulas dentro dos parêntesis rectos (e fora de funções) separam dimensões do objecto e os data.frames só têm duas.

dados[1, "idade", "genero"]
Error in drop && length(x) == 1L: invalid 'x' type in 'x && y'

7.7 Explorar um data.frame

Como podemos explorar a nossa base de dados, por exemplo, para saber as suas dimensões, estrutura, ou mesmo visualizá-la?

Para descobrir o número de colunas usamos a função ncol(), para o número de linhas a função nrow(), para sabermos as dimensões (i.e., as linhas e colunas) usamos dim().

# número de colunas.
ncol(dados)
[1] 3

# número de linhas.
nrow(dados)
[1] 10

# Os dois.
dim(dados)
[1] 10  3

Para espreitarmos o topo ou o fim da nossa base de dados podemos usar as funções head() e tail() respectivamente.

head(dados)
  idade genero  rts
1    21      M 1435
2    32      F 4352
3    24      M 2232
4    46      F 2820
5    33      F 4552
6    20      F 6454
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 usar a função str() para perceber a sua estrutura.

str(dados)
'data.frame':   10 obs. of  3 variables:
 $ idade : num  21 32 24 46 33 20 50 39 18 20
 $ genero: Factor w/ 2 levels "F","M": 2 1 2 1 1 1 2 2 1 2
 $ rts   : num  1435 4352 2232 2820 4552 ...

Para visualizarmos a base de dados numa spreadsheet parecida com as do Libreoffice Calc ou Microsoft Excel podemos usar a função View().

View(dados)

7.8 Alterar, Criar e Apagar Linhas, Colunas e Células

Já explorámos várias formas de como podemos trabalhar com as nossas bases de dados, mas se reparem, ainda não vimos nenhuma forma de como as alterar. Se o R só permitisse seleccionar células, fazer operações com linhas e colunas, mas não permitisse alterar, apagar, e criar colunas, linhas, ou mesmo células individuais, teríamos de estar sempre a abrir o Libreoffice Calc, o Microsoft Excel ou outro programa para fazer essas operações. Assim não teríamos como usufruir das vantagens do R para esses procedimentos, por exemplo, não ficaríamos com um script que registasse e permitisse reproduzir o processo de limpeza dos dados.

7.8.1 Alterar Valores

Felizmente, podemos usar o que já aprendemos para fazer essas operações. Por exemplo, podemos fazer operações matemáticas com uma coluna, guardando o resultado nessa própria coluna, alterando assim o seu valor. Parece complicado, mas é daquelas operações que é muito fácil de ler no código apesar de ser difícil de explicar com linguagem natural.

Imaginem que tínhamos registado os tempos de resposta em milissegundos e queríamos convertê-los para segundos.

Sendo que um segundo são 1000 milissegundos, basta dividirmos os milissegundos por 1000 para obtermos os segundos.

dados$rts / 1000
 [1] 1.435 4.352 2.232 2.820 4.552 6.454 6.500 1.005 9.001 2.003

Mas como não guardámos o resultado em nenhuma variável continuamos a ter os tempos de resposta em milissegundos na base de dados.

dados$rts
 [1] 1435 4352 2232 2820 4552 6454 6500 1005 9001 2003

Mas se guardarmos o resultado da conversão na própria coluna alteramos o seu valor.

dados$rts <- dados$rts / 1000

print(dados)
   idade genero   rts
1     21      M 1.435
2     32      F 4.352
3     24      M 2.232
4     46      F 2.820
5     33      F 4.552
6     20      F 6.454
7     50      M 6.500
8     39      M 1.005
9     18      F 9.001
10    20      M 2.003

Podemos usar a mesma lógica para alterar um valor duma linha. Por exemplo, para alterar os dados do primeiro participante.

dados[1, ] <- c(31, "F", 3.521)

print(dados)
   idade genero   rts
1     31      F 3.521
2     32      F 4.352
3     24      M 2.232
4     46      F  2.82
5     33      F 4.552
6     20      F 6.454
7     50      M   6.5
8     39      M 1.005
9     18      F 9.001
10    20      M 2.003

O mesmo raciocínio ajuda-nos a perceber como podemos alterar os valores duma só célula. Para alterar a idade do terceiro participante poderíamos escrever:

dados[3, "idade"] <- 52

print(dados)
   idade genero   rts
1     31      F 3.521
2     32      F 4.352
3     52      M 2.232
4     46      F  2.82
5     33      F 4.552
6     20      F 6.454
7     50      M   6.5
8     39      M 1.005
9     18      F 9.001
10    20      M 2.003

7.8.2 Apagar Colunas

Para apagarmos colunas basta atribuirmos o valor muito especial de NULL a essa coluna.

AVISO: Como as outras operações que alteram valores das variáveis, esta operação é irreversível.

Para podermos experimentar usar estas operações e não perdermos dados que precisaremos para exemplos futuros, podemos começar por copiar a nossa base de dados para uma nova variável.

teste <- dados

print(teste)
   idade genero   rts
1     31      F 3.521
2     32      F 4.352
3     52      M 2.232
4     46      F  2.82
5     33      F 4.552
6     20      F 6.454
7     50      M   6.5
8     39      M 1.005
9     18      F 9.001
10    20      M 2.003

Agora que criámos a variável teste, podemos usá-la em algumas experiências.

Por exemplo podemos apagar a primeira coluna.

teste[ ,1] <- NULL

Agora podemos confirmar se a variável teste se mantém parecida com a dados, sendo que a variável dados vai ter mais uma coluna.

ncol(dados)
[1] 3
ncol(teste)
[1] 2

print(dados)
   idade genero   rts
1     31      F 3.521
2     32      F 4.352
3     52      M 2.232
4     46      F  2.82
5     33      F 4.552
6     20      F 6.454
7     50      M   6.5
8     39      M 1.005
9     18      F 9.001
10    20      M 2.003
print(teste)
   genero   rts
1       F 3.521
2       F 4.352
3       M 2.232
4       F  2.82
5       F 4.552
6       F 6.454
7       M   6.5
8       M 1.005
9       F 9.001
10      M 2.003

Nota: A variável teste continua a ter uma primeira coluna.

teste[ ,1]
 [1] F F M F F F M M F M
Levels: F M

Mas essa primeira coluna tem agora os valores que estavam na segunda, e a segunda tem os valores da terceira.

Também podemos apagar colunas usando os seus nomes.

teste$rts <- NULL

print(teste)
   genero
1       F
2       F
3       M
4       F
5       F
6       F
7       M
8       M
9       F
10      M

7.8.3 Apagar Linhas

Para apagarmos linhas não lhes podemos atribuir o valor de NULL como fizemos para as colunas. O que podemos fazer então?

Antes de avançarmos vamos voltar a copiar a nossa base de dados para a variável teste.

teste <- dados

print(teste)
   idade genero   rts
1     31      F 3.521
2     32      F 4.352
3     52      M 2.232
4     46      F  2.82
5     33      F 4.552
6     20      F 6.454
7     50      M   6.5
8     39      M 1.005
9     18      F 9.001
10    20      M 2.003

Como podemos então apagar linhas? Temos de fazer alguma batota e dizer que a nossa base de dados é igual a ela própria sem essas linhas. Por exemplo, para apagar a linha 1.

teste <- teste[-1, ]

7.8.4 Criar Linhas e Colunas

Para criar colunas basta usarmos a notação base_de_dados$nome_da_coluna usando um nome duma coluna que não existe.

Por exemplo:

teste$nova_coluna <- c(1, 2, 3, 4, 5, 6, 7, 8, 9)

Para criarmos uma nova linha (algo menos frequente no dia a dia), basta indicarmos um ou vários números de linha que não existem.

Sabendo que o data.frame teste tem nove linhas:

nrow(teste)
[1] 9

Podemos acrescentar uma décima escrevendo:

teste[10, ] <- c(24, "M", 3.005, 10)

print(teste)
     idade genero   rts nova_coluna
2       32      F 4.352           1
3       52      M 2.232           2
4       46      F  2.82           3
5       33      F 4.552           4
6       20      F 6.454           5
7       50      M   6.5           6
8       39      M 1.005           7
9       18      F 9.001           8
10      20      M 2.003           9
10.1    24      M 3.005          10

Nota: Reparem que se não soubermos o número de linhas podemos simplesmente escrever:

teste[nrow(teste) + 1, ] <- c(66, "F", 1.020, 11)

7.8.5 Mudar os Nomes das Colunas.

Já vimos que para apagar os nomes das linhas podíamos escrever:

row.names(teste) <- NULL

Da mesma forma, se quisermos apagar os nomes das colunas, podemos escrever:

colnames(teste) <- NULL

Se repararem as colunas do data.frame teste já não têm nome.

print(teste)
                
1  32 F 4.352  1
2  52 M 2.232  2
3  46 F  2.82  3
4  33 F 4.552  4
5  20 F 6.454  5
6  50 M   6.5  6
7  39 M 1.005  7
8  18 F 9.001  8
9  20 M 2.003  9
10 24 M 3.005 10
11 66 F  1.02 11

Na prática, isto não é algo que queiramos fazer dado que normalmente os nomes das colunas correspondem a nomes das nossas variáveis. O que pode ser mais frequente é querermos mudar os nomes das colunas para algo que nos faça mais sentido, que seja mais rápido de escrever, etc…

Para mudarmos os nomes das colunas podemos escrever:

colnames(teste) <- c("idade", "genero", "rts_seg", "participante")

Nota: Ás vezes podemos querer mudar o nome de apenas uma ou algumas colunas. Seria cansativo termos de escrever todos os nomes das colunas para mudar apenas um. Para além disso, o nome que queríamos mudar ficava facilmente perdido no meio dos outros não sendo fácil de perceber qual era o nome da coluna que estávamos a mudar. Por isso, no capítulo seguinte, veremos como mudar os nomes de apenas uma ou algumas colunas.

7.8.6 Juntar data.frames

Uma das vantagens do R, é podermos trabalhar facilmente com vários data.frames (mesmo que sejam bases de dados distintas) ao mesmo tempo.

Se tivermos dois data.frames com o mesmo número de linhas podemos juntá-los lado a lado. Para isso basta usarmos a função cbind().

Como o data.frame dados tem uma linha a menos que o teste não podemos usar a função cbind() sem primeiro apagar uma linha.

dim(dados)
[1] 10  3
dim(teste)
[1] 11  4

# Apaga a última linha do data.frame teste.
teste <- teste[nrow(teste), ]

De seguida já podemos usar a função cbind() para combinar os data.frames.

cbind(dados, teste)
Warning in data.frame(..., check.names = FALSE): row names were found from a
short variable and have been discarded
   idade genero   rts idade genero rts_seg participante
1     31      F 3.521    66      F    1.02           11
2     32      F 4.352    66      F    1.02           11
3     52      M 2.232    66      F    1.02           11
4     46      F  2.82    66      F    1.02           11
5     33      F 4.552    66      F    1.02           11
6     20      F 6.454    66      F    1.02           11
7     50      M   6.5    66      F    1.02           11
8     39      M 1.005    66      F    1.02           11
9     18      F 9.001    66      F    1.02           11
10    20      M 2.003    66      F    1.02           11

Se guardarmos o output dessa função numa nova variável ficamos com mais um data.frame.

combinado <- cbind(dados, teste)
Warning in data.frame(..., check.names = FALSE): row names were found from a
short variable and have been discarded

print(combinado)
   idade genero   rts idade genero rts_seg participante
1     31      F 3.521    66      F    1.02           11
2     32      F 4.352    66      F    1.02           11
3     52      M 2.232    66      F    1.02           11
4     46      F  2.82    66      F    1.02           11
5     33      F 4.552    66      F    1.02           11
6     20      F 6.454    66      F    1.02           11
7     50      M   6.5    66      F    1.02           11
8     39      M 1.005    66      F    1.02           11
9     18      F 9.001    66      F    1.02           11
10    20      M 2.003    66      F    1.02           11

Se quisermos juntar data.frames que tenham as mesmas colunas (i.e., o mesmo número e o mesmo nome), podemos usar a função rbind(). A título de exemplo podemos invocar a função repetindo o mesmo data.frame para termos a certeza que tem as mesmas colunas.

rbind(dados, dados)
   idade genero   rts
1     31      F 3.521
2     32      F 4.352
3     52      M 2.232
4     46      F  2.82
5     33      F 4.552
6     20      F 6.454
7     50      M   6.5
8     39      M 1.005
9     18      F 9.001
10    20      M 2.003
11    31      F 3.521
12    32      F 4.352
13    52      M 2.232
14    46      F  2.82
15    33      F 4.552
16    20      F 6.454
17    50      M   6.5
18    39      M 1.005
19    18      F 9.001
20    20      M 2.003

Caso queiramos juntar data.frames lado a lado que tenham números de linhas diferentes, ou se quisermos juntar pelas linhas (i.e., rbind()) data.frames com colunas diferentes, teremos de seguir outras abordagens. O pacote dplyr traz funções que nos podem ajudar, mas deixaremos esses casos mais tarde.

7.9 Exercícios

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

7.9.1 Exercício 1

  • Crie uma variável do tipo data.frame com os seguintes dados.
  coluna1 coluna2 coluna3
1       1       a     100
2       2       b     200
3       3       c     300
4       4       d     400
5       5       e     500
  • Aceda à primeira linha do data.frame que criou.

  • Aceda à terceira coluna.

  • Aceda à primeira célula da segunda coluna.

  • Aceda às linhas dois a quatro da primeira e segunda colunas.

7.9.2 Soluções

Clique para ver as soluções
dados <- data.frame(coluna1 = c(1, 2, 3, 4, 5),
                    coluna2 = c("a", "b", "c", "d", "e"),
                    coluna3 = c(100, 200, 300, 400, 500))

dados[1, ]
  coluna1 coluna2 coluna3
1       1       a     100

dados[ , 3]
[1] 100 200 300 400 500

dados[1, 2]
[1] "a"

dados[2:4, 3]
[1] 200 300 400

7.9.3 Exercício 2

  • Crie uma nova coluna chamada metade que guarde os resultados da divisão dos valores da coluna3 por dois.

  • Apague a coluna metade.

  • Crie uma cópia do data.frame que não inclua as duas primeiras linhas.

  • Apague a última linha dessa cópia do data.frame.

7.9.4 Soluções

Clique para ver as soluções
dados$metade <- dados$coluna3 / 2

dados$metade <- NULL

dados2 <- dados[-c(1:2), ]

dados2 <- dados2[-nrow(dados2), ]



7.9.5 Exercício 3

  • Quais os valores da coluna3 que são superiores a 100?

  • Seleccione apenas as linhas do data.frame dados cujos valores na coluna coluna1 são inferiores a 3.

  • Responda à alínea anterior sem usar o caractere $.

  • Seleccione os valores da coluna1 referentes a participantes com valores superiores a 200 na coluna3.

  • Dê novos nomes às colunas do data.frame.

  • Verifique se os nomes foram alterados.

7.9.6 Soluções

Clique para ver as soluções
dados$coluna3[dados$coluna3 > 100]
[1] 200 300 400 500

dados[dados$coluna1 < 5, ]
  coluna1 coluna2 coluna3
1       1       a     100
2       2       b     200
3       3       c     300
4       4       d     400

dados[dados[ ,1] < 5, ]
  coluna1 coluna2 coluna3
1       1       a     100
2       2       b     200
3       3       c     300
4       4       d     400

dados[dados$coluna3 > 200, "coluna1"]
[1] 3 4 5

colnames(dados) <- c("novo1", "novo2", "novo3")

print(colnames(dados))
[1] "novo1" "novo2" "novo3"
# head(dados), tail(dados), print(dados), ou View(dados) também dariam.

8 Importação e Exportação de Dados

Para importarmos bases dados, precisamos duma função que saiba “ler” o formato do ficheiro em que temos os nossos dados e guardar o output dessa função, numa variável. Sempre que trabalharmos com a variável estamos a trabalhar com os dados que estavam no ficheiro. Contudo, as alterações que fizermos à variável não alteram o ficheiro. Para guardarmos as alterações num ficheiro temos de fazer o processo inverso. Ou seja, precisamos de uma função que saiba “escrever” a nossa variável num ficheiro do formato em que queiramos guardar os dados.

Em baixo podem ver um exemplo de um script que ilustra este processo.

# Cria a variável dados que guarda o output da função que lê o ficheiro.
dados <- read.csv("../data/demo_ds.csv")

# Depois viria o código que limpa, reformata, e analisa os dados.

# Executa a função que grava a variável dados num novo ficheiro.
write.csv(dados, "../data/novo_ficheiro.csv", row.names = FALSE)

Nota: row.names = FALSE evita que se grave nomes das linhas. Quando importamos um csv os nomes das linhas são o número das linhas e não é muito útil ter essa informação.

Ao longo deste tutorial vamos aprender os conceitos necessários para importar e exportar bases de dados com segurança. É possível que muitos dos conceitos explicados no tutorial sejam novos para vocês e pareçam complicados. Contudo, a maioria desses conceitos não são exclusivos ao R mas à forma como os computadores funcionam. Ou seja, não pensem que estão a ter dificuldades na estatística ou no uso do R, se os conceitos parecerem complicados. Essas serão dificuldades que podiam sentir a usar outros programas informáticos, e que ao ultrapassarem vos tornarão melhores utilizadores de todos os programas e não só do R.

8.1 Caminhos para Ficheiros

Nos exemplos anteriores invocámos a função read.csv() com o nome do nosso ficheiro como argumento.

Contudo, na maioria dos casos não podemos apenas usar o nome do ficheiro mas sim o caminho/localização do ficheiro. Não se assustem se não conhecerem os termos, o caminho ou localização do ficheiro é apenas a sequência de directórios/pastas pelos quais temos de passar para chegar ao ficheiro.

Por exemplo, imaginem que temos um ficheiro chamado exemplo.R, dentro dum directório/pasta chamado scripts_R, que por sua vez está dentro doutro chamado documentos. Se estivermos a trabalhar no directório documentos para chegarmos ao ficheiro só temos de “percorrer” o caminho scripts_R/exemplo.R. Porém se estivéssemos a trabalhar dentro do directório scripts_R, não haveria nenhum directório entre nós e o ficheiro, portanto bastaria usar o nome do ficheiro exemplo.R. Por outro lado, se estivéssemos dentro dum directório nome_de_utilizador, que tivesse lá dentro o directório documentos, para chegarmos ao ficheiro exemplo.R teríamos de “percorrer” o caminho documentos/scripts_R/exemplo.R.

8.2 Possíveis Problemas

Com base no exemplo anterior, podemos concluir que:

  1. Não basta saber o nome do ficheiro, precisamos de saber o caminho que temos de “percorrer” para chegar até ele

  2. O caminho que temos de “percorrer” para chegar até ao ficheiro é sempre relativo ao nosso ponto de partida, ou seja, o directório em que estamos a trabalhar.

Sabendo isto, é provável que vos surjam algumas perguntas:

  • Se os caminhos são sempre relativos ao directório em que estamos a trabalhar, como é que podemos ter a certeza que o nosso script vai funcionar?

  • Corremos sempre o risco que o R não encontre o ficheiro por lhe dar um caminho errado?

  • Achas mesmo que vou decorar o caminho para todas as minhas bases de dados? Podes tirar o cavalinho da chuva! Vou voltar para o SPSS!

Começando do fim para o início:

Achas mesmo que vou decorar o caminho para todas as minhas bases de dados?

Não acho…nem nunca o terás de o fazer.

Podes tirar o cavalinho da chuva! Vou voltar para o SPSS!

Tanta agressividade… Calma…o problema não é assim tão complicado e há soluções muito simples.

Corremos sempre o risco que o R não encontre o ficheiro por lhe dar um caminho errado?

Sim…mas usando qualquer uma das soluções abaixo reduzem muito a probabilidade de se depararem com esses problemas. Para além disso, veremos que isto não são limitações do R, mas características de todos os computadores.

Se os caminhos são sempre relativos ao directório em que estamos a trabalhar, como é que podemos ter a certeza que o nosso script vai funcionar?

Usando qualquer uma das soluções abaixo…

8.3 Possíveis Soluções

8.3.1 Opção 1

A solução talvez considerem mais simples é usar a função file.choose() para o R vos deixar procurar pelo ficheiro e gravar o caminho até lá.

caminho <- file.choose() # grava o caminho na variável caminho.
dados <- read_xlsx(caminho) # importa o ficheiro que está no caminho.

8.3.2 Opção 2

Podemos adaptar a Opção 1 de modo a só precisarmos de seleccionar o ficheiro enquanto estamos a escrever o código a primeira vez e nunca mais termos de repetir o processo.

Para isso quando estamos a escrever o código pela primeira vez executemos a função file.choose() no interpretador e copiemos o seu output para o script.

No interpretador:

file.choose()

# Possível output em UNIX (Linux, BSDs, macOS).
[1] "/home/nome_de_utilizador/documentos/data/demo_ds.csv"

# Possível output em Windows.
[1] "C://Users/nome_de_utilizador/documentos/data/demo_ds.csv"

No script:

dados <- read.csv("/home/nome_de_utilizador/documentos/data/demo_ds.csv")
# Ou em Windows:
dados <- read.csv("C://Users/nome_de_utilizador/documentos/data/demo_ds.csv)"

Desde que não alteremos o nome e/ou a localização do ficheiro, das próximas que vezes que executemos o script não precisaremos de seleccionar o ficheiro porque a sua localização já está no script.

8.3.3 Opção 3

Outra opção será trabalharmos sempre a partir do mesmo directório e escrever todos os caminhos tomando esse relatório como referência.

No R, para definirmos o working directory (i.e., o directório a partir do qual trabalhamos) usamos a função setwd() (set working directory).

# Sistemas UNIX (Linux, BSDs, macOS).
setwd("/home/nome_de_utilizador/documentos/")
# com interface gráfica
setwd(dirname(file.choose()))
# (basta seleccionarmos um qualquer ficheiro no directório documentos)

# Sistemas Windows.
setwd("C://Users/nome_de_utilizador/documentos/")
# com interface gráfica
setwd(dir.choose())

Nota: Os IDEs como o RStudio costumam ter funcionalidades que nos permitem definir o working directory usando uma interface gráfica. Deve bastar fazerem uma pesquisa rápida na internet para a descobrir (e.g., “Google: How to set working directory in RStudio”).

A partir do momento que temos o nosso working directory definido, podemos escrever todos os caminhos como partindo desse directório/pasta. Não nos podemos é esquecer que temos sempre de definir o nosso working directory antes de executarmos código que assume que os caminhos partem do working directory.

Imaginemos que dentro do nosso working directory temos um ficheiro chamado study1.csv. Nesse caso, poderemos dizer que o caminho até ao ficheiro é simplesmente o seu nome—"study1.csv"—dado que não temos que atravessar nenhum directório/pasta para lá chegar.

Nesse caso para lermos os dados contidos no ficheiro study1.csv e guardá-los na variável dados bastaria:

dados <- read.csv("study1.csv")

Imaginemos que afinal o ficheiro study1.csv não está directamente no nosso working directory, mas sim dentro duma pasta data que está dentro do working directory. Nesse caso, para chegar até ao ficheiro temos que passar pela pasta data. Temos então de especificar o caminho até ao ficheiro como "data/study1.csv".

Podemos reescrever o código anterior como:

dados <- read.csv("data/study1.csv")

Caso não saibam de cor o caminho até ao ficheiro mas tenham a certeza que está algures num directório/pasta do vosso working directory podem fazer o que aprendemos na “Opção 2”. Mais concretamente, podem usar a função file.choose() para descobrir o caminho através duma interface gráfica e colar o output no script. Desde que tenham definido o working directory antes de executar a função file.choose(), o output da função file.choose() deverá ser o caminho até ao ficheiro a partir pelo vosso working directory.

No interpretador (ou usando uma funcionalidade do IDE, e.g., RStudio):

setwd("/home/nome_de_utilizador/documentos/")

No interpretador:

file.choose()

# Possível output.
[1] "data/study1.csv"

No script:

dados <- read.csv("data/study1.csv")

8.3.4 Opção 4

Podemos adaptar a “Opção 3” ligeiramente, e em vez de definirmos um directório onde vamos trabalhar em todas as análises que alguma vez façamos, termos um directório onde vamos trabalhar em todas as análises dum projecto específico. Ou seja, passamos a definir um working directory para cada projecto/estudo em que trabalhamos.

Por exemplo, para os meus estudos costumo criar um directório stats que tem lá dentro três directórios data, analyzes, e results. Guardo todos os scripts do R no directório analyzes, as bases de dados no data e os outputs mais importantes das análises em results. Assim só tenho de executar os scripts a partir do directório analyzes desse projecto.

# Para um projecto X
setwd("/home/nome_de_utilizador/projectos/projecto_X/stats/analyzes")

# Para um projecto Y
setwd("/home/nome_de_utilizador/projectos/projecto_Y/stats/analyzes").`

Depois posso escrever os scripts de cada projecto como partindo do directório analyzes e usar a abreviatura ../ para chegar ao directório anterior (neste caso stats).

dados <- read.csv("../data/demo_ds.csv")
# Seria igual a: 
# dados <- read.csv("/home/nome_de_utilizador/projectos/projecto_X/stats/data/demo_ds.csv")

A vantagem desta abordagem é que se alguém copiar todo o directório do projecto_X para o seu computador só tem de definir como a pasta projecto_X/stats/analyzes como working directory que todos os scripts vão funcionar.

# Para o projecto X no computador doutra pessoa.
setwd("/home/o_seu_nome_de_utilizador/documentos/projectos_copiados/projecto_X/stats/analyzes")

Como o setwd() é diferente para cada pessoa, mas o restante código é igual, o setwd() não deve ficar no script que é partilhado. Portanto, nunca nos podemos esquecer de fazer setwd() antes de começar a trabalhar! Por outro lado, podemos usar as funções do nosso IDE (e.g., RStudio) para fazer esse passo sem problema.

8.4 Formatos/Tipos de Ficheiros

Para podermos ler um ficheiro precisamos de saber não só a sua localização mas o seu formato. Regra geral o formato/tipo do ficheiro pode ser inferido a partir da extensão dum ficheiro. Por exemplo, se um ficheiro termina em .csv podemos inferir que é um csv (comma separated values), se termina em .xlsx inferimos que é um ficheiro do Microsoft Excel, se termina em .sav inferimos que vem do SPSS.

O R traz consigo a possibilidade de ler ficheiros de texto simples em formatos como csv e tsv mas não ficheiros mais complexos como .xlsx ou .sav. Contudo, temos quase sempre disponíveis pacotes que podemos instalar para passarmos a ter funções capazes de ler esses formatos mais complexos. Aconselho-vos portanto a tentarem gravar as bases de dados em csv, sempre que possível. Nestes tutoriais esse será o formato usado. Dito isto, será muito fácil adaptarem o que aprenderem aqui para trabalhar com bases de dados noutros formatos.

Por exemplo, se os dados do ficheiro study1.csv também estivessem no formato xlsx, num ficheiro chamado study1.xlsx eu poderia usar a função read_xlsx(), do pacote readxl para os ler.

dados <- read_xlsx("../data/demo_ds.csv")

Claro que para utilizarmos um pacote temos primeiro que o instalar (e.g., install.packages("readxl")) e depois temos de o carregar para a sessão (e.g., library(readxl)). Vamos aprender a actualizar, instalar, e usar pacotes noutro tutorial, para já vamos ficar pelos csv e não vamos precisar de nenhum pacote especial.

8.5 Variávei/Objectos vs Ficheiros

Quando trabalharmos com ficheiros no R convém lembrarmo-nos de algumas regras, que vão parecer confusas ao início, mas que depois nos vamos habituar a cumprir sem ter de pensar nelas. Para percebermos o porquê dessas regras temos de perceber a diferença entre ambiente e ficheiros. Vamos por partes…

8.5.1 Regras

  1. Garantir que tenho sempre uma cópia/backup de bases de dados e ficheiros importantes.

  2. Tenho de guardar o output da função que lê o ficheiro numa variável.

  3. Os objectos/variáveis não são ficheiros e os ficheiros não são objectos/variáveis.

  4. Quando guardo uma coisa num objecto ou ficheiro apago a informação que lá estava e nunca mais terei acesso a ela.

Estas regras podem traduzir-se em recomendações mais concretas:

  1. Antes de trabalhar com dados importantes garanto que tenho uma cópia/backup num disco externo, numa pen/usb, noutro computador, ou nuvem/cloud (e.g., email, Google Drive). Caso não tenha essa cópia/backup aproveito para a criar antes de começar a trabalhar.

  2. Devo devo guardar sempre o output da função que o leu numa variável e trabalhar sempre com essa variável. Não vale a pena importar a base de dados sempre que a vou usar num script. Importo-a para uma variável no início do script e depois trabalho com essa variável.

  3. As alterações que eu faço ao objecto/variável que tem o output da função que leu os meus dados não alteram o ficheiro original. Ou seja, se apago uma coluna da variável/objecto que tem a base de dados essa coluna não desaparece do ficheiro. Ao mesmo tempo, se apagar uma coluna do ficheiro e não o voltar a importar para essa mesma a variável/objecto, essa coluna não desaparece da variável/objecto.

  4. Quando guardo alguma coisa numa variável/objecto apago toda a informação que lá estava. Do mesmo modo, quando guardo alguma coisa num ficheiro perco a informação que lá estava. Se eu quiser guardar as alterações que fiz a uma base de dados num ficheiro tenho de usar uma função para “escrever” a variável com as alterações para um novo ficheiro. Se eu usar uma função para escrever uma variável para um ficheiro que já exista perco a informação que estava originalmente no ficheiro. Isso pode ser algo muito útil ou catastrófico dependendo do meu objectivo. Por isso é que tenho sempre de cumprir a primeira regra e fazer cópias/backups.

8.5.2 Ambiente vs. Ficheiros

Quando trabalhamos no R vamos criando objectos/variáveis que ficam no nosso ambiente. O ambiente de trabalho funciona como uma bancada de trabalho onde vamos pondo as coisas (i.e., variáveis/objectos) em que estamos a trabalhar. Contudo ao contrário duma bancada de trabalho normal, no R quando acabamos de trabalhar o ambiente é completamente limpo indo tudo parar ao “lixo”. Por isso, se quisermos guardar algum objecto/variável (e.g., uma base de dados, resultado dum teste) para acedermos sem termos de abrir o R temos de escrever esse objecto/variável num ficheiro.

No nosso computador, quer tenhamos ou não o R instalado, temos vários ficheiros. Se eu tiver fotografias, vídeos, livros, etc. no meu computador esses ficheiros continuam lá quer estejam a ser usados ou não. Se eu estiver a ler um pdf esse pdf vai continuar lá depois de eu sair do programa que estou a usar para o ler. Quando os programas lêem um ficheiro é como se ficassem com os conteúdos no seu ambiente como o R faz. Quando abro um documento de texto posso fazer as alterações que eu quiser se fechar o programa sem gravar as alterações o o ficheiro vai continuar lá igual ao que estava. Caso eu grave as alterações o ficheiro deixa de ter a informação que tinha antes e passa a ter a informação alterada.

Nota: Se se lembram das aulas de TIC ou gostam de informática, podem lembrar-se que o ambiente do R fica na memória RAM enquanto temos o R aberto naquela sessão. Quando saímos do R essa memória é devolvida ao sistema operativo para ser usada por outros programas. Quando desligamos o computador essa memória é completamente apagada. Os ficheiros vivem no disco do nosso computador (seja um disco rígido ou um SSD) esse disco guarda a nossa informação para sempre (até se estragar). Podemos desligar o computador, arrancar o disco, inseri-lo noutro computador que a informação vai continuar lá.

Em suma, as variáveis/objectos podem ter a informação que está em ficheiros mas se eu as alterar não altero o ficheiro. Para alterar um ficheiro tenho de gravar uma variável/objecto nessa localização/caminho.

8.6 R vs. Outros Programas

Nesta altura podem pensar:

“O R é mesmo difícil! Eu não tenho de saber nada disto para trabalhar com os outros programas!”.

Compreendo o sentimento, contudo não corresponde totalmente à realidade. Reparem que os directórios e caminhos de ficheiros não foram inventados pelo R, nem são exclusivos do R, mas sim um conceito básico do sistema operativo.

Podem então responder:

“Há anos que uso computadores e nunca tive de saber nada disto!”.

Mais uma vez, compreendo o sentimento mas não corresponde totalmente à realidade. Cada vez que “abriram uma pasta” no computador ou “seleccionaram um ficheiro” estavam e especificar o caminhos até ficheiros.

De certeza que já clicaram duas vezes no ícone duma pasta (e.g., documentos) e clicaram mais duas no ícone doutra pasta que estivesse lá dentro (e.g., fotografias). Nesse caso tudo o que fizeram foi usar uma interface gráfica para dizer ao computador abre o directório documentos/fotografias. Quando seleccionaram um ficheiro (e.g., ParaPostarNoInsta.jpeg), ao clicar no seu ícone, depois de terem clicado em várias pastas até chegar até ao ficheiro, estavam a dizer ao computador abre o ficheiro no caminho documentos/fotografias/ParaPostarNoInsta.jpeg.

Quando abrem uma base de dados (e.g., study1.csv) no JASP no Jamovi ou SPSS estão a dizer a esse programa “abre o ficheiro que está no caminho que te vou indicar” (e.g.,/home/nome_de_utilizador/documentos/data/study1.csv).

Quando fazem “guardar” estão a dizer ao programa que estão a usar para guardar o/s objecto/s que tem no seu ambiente/memória no mesmo caminho que lhe indicaram quando abriram o ficheiro. Quando fazem “guardar como” estão a dizer ao programa para criar um ficheiro com o nome que escolheram no caminho que lhe indicaram.

O R não é diferente dos outros programas no que toca a abrir e guardar ficheiros. Assim como os outros programas, o R vai abrir ou guardar o ficheiro no caminho que indicarem. No R podemos habituar-nos a escrever o caminho para os ficheiros em vez dos indicarmos clicando em ícones de pastas e ficheiros. Mesmo assim, já viram que nada nos impede de usarmos funções como file.choose() para escolher os ficheiros com uma interface gráfica. A diferença é que no R temos de escolher a função que queremos usar para abrir cada ficheiro e decidir em que variável queremos guardar os dados extraídos do ficheiro.

8.7 Exercícios

8.7.1 Exercício 1

  • Descarregue o ficheiro demo_ds.csv clicando no link e guardando algures no seu computador (e.g., no ambiente de trabalho)

  • Importe o ficheiro usando a função apropriada para ler uma base de dados guardada neste tipo de ficheiro (reler o capítulo se necessário) e a função capaz de abrir uma interface gráfica para encontrar o ficheiro (reler capítulo se necessário). Corra head(ds) para confirmar que importou o ficheiro correctamente.

  • Use a função capaz de abrir uma interface gráfica que usou anteriormente, selecione o mesmo ficheiro e tome nota do caminho que ela retorna.

  • Use a mesma função capaz de ler tabelas csv para abrir o ficheiro mas desta vez dê-lhe como argumento a string com o caminho para o ficheiro que obteve anteriormente.

8.7.2 Soluções

Clique para ver as soluções
ds <- read.csv(file.choose())
head(ds)

file.choose()

ds <- read.csv("/home/random_user/demo_ds.csv")
# Ou em sistemas Windows algo deste estilo:
#ds <- read.csv("C://Users/random_user/Desktop/demo_ds.csv")

9 Limpeza de Bases de Dados

Neste tutorial vamos aprender a “limpar” bases de dados. A “limpeza” da base de dados é o primeiro passo fundamental na preparação duma base de dados. Podemos dividir a preparação duma base de dados em dois passos: (1) limpeza, (2) edição/reformatação. Na limpeza removemos da base de dados linhas, colunas, células que não queremos que lá estejam. Por exemplo, removemos dados que podem identificar os participantes, ou linhas extra que podem causar erros na importação da base de dados. Na edição/reformatação podemos recodificar variáveis, ou reorganizar a base de dados. Este tutorial foca-se apenas na primeira parte—a limpeza dos dados. Depois teremos outro tutorial que se vai focar na reformatação dos dados.

9.1 A “Prática” é Diferente da “Aula Prática”

Nas aulas de estatística normalmente dividimos o tempo entre teoria e prática. Nas aulas teóricas aprendemos quais as análises a usar, os seus pressupostos, as estatísticas/resultados que essas análises nos dão e aprendemos a interpretar esses resultados. Nas aulas práticas aprendemos a testar os pressupostos das análises, a fazer as análises e a interpretar os resultados no contexto para alguns exemplos de problemas de investigação. Ficamos então com a sensação que na prática da estatística, o que faremos será pegar na base de dados do problema de investigação, testar os pressupostos, fazer a análise e reportar os resultados. Contudo, na realidade, a prática é diferente das aulas práticas. Na prática raramente temos bases de dados bem arranjadinhas ou até as temos mas porque primeiro gastámos horas da nossa vida a arranjá-las! Na prática a quantidade de horas de vida que gastamos a arranjar a base de dados é muito superior às que gastamos a fazer as análises. Para além disso, aulas práticas o procedimento parecia linear, ou seja, primeiro testávamos pressupostos, depois analisávamos dados, e depois reportávamos os resultados. Na prática o processo é muito mais circular. Posso decidir fazer a análise primeiro—computar o modelo no R—e depois testar os pressupostos no objecto/variável que guarda o resultado dessa computação (o ajuste/fit do modelo). Posso estar a escrever a discussão dum artigo e lembrar-me duma análise exploratória que seria interessante fazer e voltar ao R para a fazer. De qualquer das formas, antes disso tudo tenho sempre de preparar a base de dados. Para preparar a base de dados, tenho primeiro de a limpar, que é o que vamos aprender a fazer neste tutorial.

9.2 O Que Limpar?

A “limpeza” das bases de dados refere-se àquelas operações que fazemos para remover observações (e.g., participantes), colunas (e.g., variáveis), ou outras coisas da base de dados. Antes de vermos como limpar os dados, vale a pena ver o que costumamos querer remover dos dados.

Aqui ficam alguns exemplos do que remover:

  • Participantes que não deram o seu consentimento para que analisássemos os seus dados.

  • Dados potencialmente identificativos dos participantes.

  • Respostas que não correspondam a participantes reais. Por exemplo, respostas de “teste” que correspondem a quando eu estava a testar se o software de recolha de dados executava a experiência e gravava os dados sem problemas.

  • Linhas ou colunas extra que o software de recolha de dados acrescenta mas que não são úteis para análise e que até podem causar erros na importação dos dados.

9.3 Como Limpar?

Para limparmos as nossas bases de dados, podemos usar o que aprendemos sobre data.frames. Já sabemos como apagar colunas, como apagar/seleccionar apenas as linhas que queremos, etc…

AVISO: Sugiro que guardem sempre a versão original (i.e., sem limpeza) da vossa base de dados. Para isso basta gravarem a vossa base de dados com um novo nome de ficheiro porque se usarem o mesmo nome de ficheiro a nova versão (i.e., limpa) substituirá a antiga.

Chega então a altura dum novo desafio! Será que conseguem resolver os exercícios abaixo com base no que já aprenderam?

9.4 Exercícios

Os exercícios que se seguem são muito semelhantes aos que já fizeram, mas as alíneas estão escritas numa linguagem menos técnica do R (e.g., exclua as linhas com valores maior que x na coluna y, ds <- subset(ds, y < x)) e mais próxima da usada em investigação (e.g., exclua os participantes menores de idade). Ou seja, os exercícios não pretendem tanto testar o vosso domínio do R e da sua terminologia, mas sim a vossa capacidade de usar o R no dia-a-dia. Portanto, tentem pensar de que maneira o que aprenderam pode ser usado para resolver cada desafio proposto.

9.4.0.1 Exercício 1

  • Importe a base de dados ../data/demo_ds.csv e guarde-a numa variável.

  • Exclua os participantes que não consentiram em participar.

  • Exclua os participantes menores de idade. Nota: já vos dei uma pista no início desta secção de exercícios.

  • Escreva uma só linha de código para resolver as duas alíneas anteriores.

  • Grave os dados “limpos” num novo ficheiro clean_demo.csv algures no seu computador.

Pergunta Bónus: Explique porque razão dados <- dados[!dados$age < 18, ] também seria uma solução válida para a terceira alínea.

9.4.0.1.1 Soluções
Clique para ver as soluções
dados <- read.csv("../data/demo_ds.csv")

dados <- dados[dados$age >= 18, ]
# dados <- dados[!dados$age < 18, ] também funcionaria.

dados <- dados[dados$consents == "Yes", ]

dados <- subset(dados, consents == "Yes" & age >= 18)

write.csv(dados, "../data/clean_demo.csv", row.names = FALSE)

dados <- dados[!dados$age < 18, ] também resolveria a segunda alínea porque ! nega o valor lógico que o sucede (e.g., !TRUE é FALSE), e os participantes que não têm menos de 18 anos são os que têm 18 ou mais anos.



9.4.1 Exercício 2

  • Importe a base de dados raw_qualtrics.csv para uma variável.

  • Descubra a classe (class()) de todas as variáveis dessa base de dados, indicando potenciais desvios do que esperava.

  • Descubra os cabeçalhos (headers) a mais e elimine-os da variável.

  • Grave os dados já sem esses cabeçalhos num ficheiro clean_qualtrics.csv algures no seu computador.

  • Importe esse novo ficheiro para a variável onde inicialmente tinha guardado os dados que importou do raw_qualtrics.csv.

  • Descubra novamente a classe de todas as variáveis, indicando as alterações e potenciais problemas.

Bónus: Explique porque razão acha que a classe das variáveis sofreu as alterações e padece ainda dos problemas que reportou.

9.4.1.1 Soluções

Clique para ver as soluções
dados <- read.csv("../data/raw_qualtrics.csv")
# dados <- read.csv(file.choose()) # para interface visual
str(dados)
'data.frame':   6 obs. of  38 variables:
 $ StartDate            : chr  "Start Date" "{\"ImportId\":\"startDate\",\"timeZone\":\"Europe/London\"}" "2021-11-24 09:55:35" "2021-11-24 09:57:17" ...
 $ EndDate              : chr  "End Date" "{\"ImportId\":\"endDate\",\"timeZone\":\"Europe/London\"}" "2021-11-24 09:57:05" "2021-11-24 09:58:22" ...
 $ Status               : chr  "Response Type" "{\"ImportId\":\"status\"}" "IP Address" "IP Address" ...
 $ Progress             : chr  "Progress" "{\"ImportId\":\"progress\"}" "100" "100" ...
 $ Duration..in.seconds.: chr  "Duration (in seconds)" "{\"ImportId\":\"duration\"}" "89" "64" ...
 $ Finished             : chr  "Finished" "{\"ImportId\":\"finished\"}" "True" "True" ...
 $ RecordedDate         : chr  "Recorded Date" "{\"ImportId\":\"recordedDate\",\"timeZone\":\"Europe/London\"}" "2021-11-24 09:57:05" "2021-11-24 09:58:23" ...
 $ ResponseId           : chr  "Response ID" "{\"ImportId\":\"_recordId\"}" "R_OCLWvHc3mmom6lP" "R_3JlTQBcMudLTmJr" ...
 $ DistributionChannel  : chr  "Distribution Channel" "{\"ImportId\":\"distributionChannel\"}" "anonymous" "anonymous" ...
 $ UserLanguage         : chr  "User Language" "{\"ImportId\":\"userLanguage\"}" "EN" "EN" ...
 $ consents             : chr  "Click to write the question text" "{\"ImportId\":\"QID1\"}" "Yes" "Yes" ...
 $ X1_rating_1          : chr  "[Field-1] - a - ${lm://Field/1}" "{\"ImportId\":\"1_QID4_1\"}" "83" "30" ...
 $ X2_rating_1          : chr  "[Field-1] - b - ${lm://Field/1}" "{\"ImportId\":\"2_QID4_1\"}" "69" "19" ...
 $ X3_rating_1          : chr  "[Field-1] - c - ${lm://Field/1}" "{\"ImportId\":\"3_QID4_1\"}" "87" "20" ...
 $ X4_rating_1          : chr  "[Field-1] - d - ${lm://Field/1}" "{\"ImportId\":\"4_QID4_1\"}" "17" "68" ...
 $ X5_rating_1          : chr  "[Field-1] - e - ${lm://Field/1}" "{\"ImportId\":\"5_QID4_1\"}" "80" "86" ...
 $ X6_rating_1          : chr  "[Field-1] - f - ${lm://Field/1}" "{\"ImportId\":\"6_QID4_1\"}" "36" "79" ...
 $ X7_rating_1          : chr  "[Field-1] - g - ${lm://Field/1}" "{\"ImportId\":\"7_QID4_1\"}" "40" "83" ...
 $ X8_rating_1          : chr  "[Field-1] - h - ${lm://Field/1}" "{\"ImportId\":\"8_QID4_1\"}" "87" "89" ...
 $ X9_rating_1          : chr  "[Field-1] - j - ${lm://Field/1}" "{\"ImportId\":\"9_QID4_1\"}" "75" "86" ...
 $ X10_rating_1         : chr  "[Field-1] - k - ${lm://Field/1}" "{\"ImportId\":\"10_QID4_1\"}" "21" "66" ...
 $ X11_rating_1         : chr  "[Field-1] - l - ${lm://Field/1}" "{\"ImportId\":\"11_QID4_1\"}" "27" "80" ...
 $ X12_rating_1         : chr  "[Field-1] - m - ${lm://Field/1}" "{\"ImportId\":\"12_QID4_1\"}" "83" "81" ...
 $ X13_rating_1         : chr  "[Field-1] - n - ${lm://Field/1}" "{\"ImportId\":\"13_QID4_1\"}" "56" "45" ...
 $ X14_rating_1         : chr  "[Field-1] - o - ${lm://Field/1}" "{\"ImportId\":\"14_QID4_1\"}" "75" "74" ...
 $ X15_rating_1         : chr  "[Field-1] - p - ${lm://Field/1}" "{\"ImportId\":\"15_QID4_1\"}" "" "87" ...
 $ X16_rating_1         : chr  "[Field-1] - q - ${lm://Field/1}" "{\"ImportId\":\"16_QID4_1\"}" "51" "33" ...
 $ X17_rating_1         : chr  "[Field-1] - r - ${lm://Field/1}" "{\"ImportId\":\"17_QID4_1\"}" "45" "24" ...
 $ X18_rating_1         : chr  "[Field-1] - s - ${lm://Field/1}" "{\"ImportId\":\"18_QID4_1\"}" "92" "83" ...
 $ X19_rating_1         : chr  "[Field-1] - t - ${lm://Field/1}" "{\"ImportId\":\"19_QID4_1\"}" "37" "29" ...
 $ X20_rating_1         : chr  "[Field-1] - u - ${lm://Field/1}" "{\"ImportId\":\"20_QID4_1\"}" "72" "20" ...
 $ X21_rating_1         : chr  "[Field-1] - v - ${lm://Field/1}" "{\"ImportId\":\"21_QID4_1\"}" "31" "" ...
 $ X22_rating_1         : chr  "[Field-1] - x - ${lm://Field/1}" "{\"ImportId\":\"22_QID4_1\"}" "41" "91" ...
 $ X23_rating_1         : chr  "[Field-1] - w - ${lm://Field/1}" "{\"ImportId\":\"23_QID4_1\"}" "84" "80" ...
 $ X24_rating_1         : chr  "[Field-1] - y - ${lm://Field/1}" "{\"ImportId\":\"24_QID4_1\"}" "89" "91" ...
 $ X25_rating_1         : chr  "[Field-1] - z - ${lm://Field/1}" "{\"ImportId\":\"25_QID4_1\"}" "72" "85" ...
 $ pp_age               : chr  "Indique, por favor, a sua idade (utilize apenas n\303\272meros na resposta)" "{\"ImportId\":\"QID6_TEXT\"}" "26" "17" ...
 $ pp_gender            : chr  "Indique, por favor, o seu g\303\251nero" "{\"ImportId\":\"QID7\"}" "Masculino" "Feminino" ...
#sapply(dados, class) também funciona

As variáveis estão todas a ser registadas como character, assim não poderemos fazer operações numéricas com as que são numéricas.

head(dados) 
                                            StartDate
1                                          Start Date
2 {"ImportId":"startDate","timeZone":"Europe/London"}
3                                 2021-11-24 09:55:35
4                                 2021-11-24 09:57:17
5                                 2021-11-24 09:58:25
6                                 2021-11-24 09:58:28
                                            EndDate                Status
1                                          End Date         Response Type
2 {"ImportId":"endDate","timeZone":"Europe/London"} {"ImportId":"status"}
3                               2021-11-24 09:57:05            IP Address
4                               2021-11-24 09:58:22            IP Address
5                               2021-11-24 09:58:27            IP Address
6                               2021-11-24 10:00:02            IP Address
                 Progress   Duration..in.seconds.                Finished
1                Progress   Duration (in seconds)                Finished
2 {"ImportId":"progress"} {"ImportId":"duration"} {"ImportId":"finished"}
3                     100                      89                    True
4                     100                      64                    True
5                     100                       2                    True
6                     100                      93                    True
                                            RecordedDate
1                                          Recorded Date
2 {"ImportId":"recordedDate","timeZone":"Europe/London"}
3                                    2021-11-24 09:57:05
4                                    2021-11-24 09:58:23
5                                    2021-11-24 09:58:27
6                                    2021-11-24 10:00:02
                ResponseId                DistributionChannel
1              Response ID               Distribution Channel
2 {"ImportId":"_recordId"} {"ImportId":"distributionChannel"}
3        R_OCLWvHc3mmom6lP                          anonymous
4        R_3JlTQBcMudLTmJr                          anonymous
5        R_28Sb2uxBnGAvdQH                          anonymous
6        R_2dWJC1L9ystwcv3                          anonymous
                 UserLanguage                         consents
1               User Language Click to write the question text
2 {"ImportId":"userLanguage"}              {"ImportId":"QID1"}
3                          EN                              Yes
4                          EN                              Yes
5                          EN                               No
6                          EN                              Yes
                      X1_rating_1                     X2_rating_1
1 [Field-1] - a - ${lm://Field/1} [Field-1] - b - ${lm://Field/1}
2         {"ImportId":"1_QID4_1"}         {"ImportId":"2_QID4_1"}
3                              83                              69
4                              30                              19
5                                                                
6                              41                              25
                      X3_rating_1                     X4_rating_1
1 [Field-1] - c - ${lm://Field/1} [Field-1] - d - ${lm://Field/1}
2         {"ImportId":"3_QID4_1"}         {"ImportId":"4_QID4_1"}
3                              87                              17
4                              20                              68
5                                                                
6                              30                              38
                      X5_rating_1                     X6_rating_1
1 [Field-1] - e - ${lm://Field/1} [Field-1] - f - ${lm://Field/1}
2         {"ImportId":"5_QID4_1"}         {"ImportId":"6_QID4_1"}
3                              80                              36
4                              86                              79
5                                                                
6                              53                              34
                      X7_rating_1                     X8_rating_1
1 [Field-1] - g - ${lm://Field/1} [Field-1] - h - ${lm://Field/1}
2         {"ImportId":"7_QID4_1"}         {"ImportId":"8_QID4_1"}
3                              40                              87
4                              83                              89
5                                                                
6                              57                              65
                      X9_rating_1                    X10_rating_1
1 [Field-1] - j - ${lm://Field/1} [Field-1] - k - ${lm://Field/1}
2         {"ImportId":"9_QID4_1"}        {"ImportId":"10_QID4_1"}
3                              75                              21
4                              86                              66
5                                                                
6                              88                              93
                     X11_rating_1                    X12_rating_1
1 [Field-1] - l - ${lm://Field/1} [Field-1] - m - ${lm://Field/1}
2        {"ImportId":"11_QID4_1"}        {"ImportId":"12_QID4_1"}
3                              27                              83
4                              80                              81
5                                                                
6                              70                              89
                     X13_rating_1                    X14_rating_1
1 [Field-1] - n - ${lm://Field/1} [Field-1] - o - ${lm://Field/1}
2        {"ImportId":"13_QID4_1"}        {"ImportId":"14_QID4_1"}
3                              56                              75
4                              45                              74
5                                                                
6                              92                              31
                     X15_rating_1                    X16_rating_1
1 [Field-1] - p - ${lm://Field/1} [Field-1] - q - ${lm://Field/1}
2        {"ImportId":"15_QID4_1"}        {"ImportId":"16_QID4_1"}
3                                                              51
4                              87                              33
5                                                                
6                              75                              28
                     X17_rating_1                    X18_rating_1
1 [Field-1] - r - ${lm://Field/1} [Field-1] - s - ${lm://Field/1}
2        {"ImportId":"17_QID4_1"}        {"ImportId":"18_QID4_1"}
3                              45                              92
4                              24                              83
5                                                                
6                              59                              78
                     X19_rating_1                    X20_rating_1
1 [Field-1] - t - ${lm://Field/1} [Field-1] - u - ${lm://Field/1}
2        {"ImportId":"19_QID4_1"}        {"ImportId":"20_QID4_1"}
3                              37                              72
4                              29                              20
5                                                                
6                              74                              82
                     X21_rating_1                    X22_rating_1
1 [Field-1] - v - ${lm://Field/1} [Field-1] - x - ${lm://Field/1}
2        {"ImportId":"21_QID4_1"}        {"ImportId":"22_QID4_1"}
3                              31                              41
4                                                              91
5                                                                
6                              81                              92
                     X23_rating_1                    X24_rating_1
1 [Field-1] - w - ${lm://Field/1} [Field-1] - y - ${lm://Field/1}
2        {"ImportId":"23_QID4_1"}        {"ImportId":"24_QID4_1"}
3                              84                              89
4                              80                              91
5                                                                
6                              81                              85
                     X25_rating_1
1 [Field-1] - z - ${lm://Field/1}
2        {"ImportId":"25_QID4_1"}
3                              72
4                              85
5                                
6                              76
                                                                       pp_age
1 Indique, por favor, a sua idade (utilize apenas n\303\272meros na resposta)
2                                                    {"ImportId":"QID6_TEXT"}
3                                                                          26
4                                                                          17
5                                                                            
6                                                                          19
                                pp_gender
1 Indique, por favor, o seu g\303\251nero
2                     {"ImportId":"QID7"}
3                               Masculino
4                                Feminino
5                                        
6                                Feminino

O Qualtrics acrescenta dois cabeçalhos extra desnecessários, nas duas primeiras linhas.

dados <- dados[-c(1:2), ]
write.csv(dados, "../data/clean_qualtrics.csv", row.names = FALSE)
# Com uma interface gráfica.
# write.csv(dados, file.choose(new = TRUE), row.names = FALSE)
dados <- read.csv("../data/clean_qualtrics.csv")
# dados <- read.csv(file.choose()) # para uma interface gráfica.
str(dados)
'data.frame':   4 obs. of  38 variables:
 $ StartDate            : chr  "2021-11-24 09:55:35" "2021-11-24 09:57:17" "2021-11-24 09:58:25" "2021-11-24 09:58:28"
 $ EndDate              : chr  "2021-11-24 09:57:05" "2021-11-24 09:58:22" "2021-11-24 09:58:27" "2021-11-24 10:00:02"
 $ Status               : chr  "IP Address" "IP Address" "IP Address" "IP Address"
 $ Progress             : int  100 100 100 100
 $ Duration..in.seconds.: int  89 64 2 93
 $ Finished             : chr  "True" "True" "True" "True"
 $ RecordedDate         : chr  "2021-11-24 09:57:05" "2021-11-24 09:58:23" "2021-11-24 09:58:27" "2021-11-24 10:00:02"
 $ ResponseId           : chr  "R_OCLWvHc3mmom6lP" "R_3JlTQBcMudLTmJr" "R_28Sb2uxBnGAvdQH" "R_2dWJC1L9ystwcv3"
 $ DistributionChannel  : chr  "anonymous" "anonymous" "anonymous" "anonymous"
 $ UserLanguage         : chr  "EN" "EN" "EN" "EN"
 $ consents             : chr  "Yes" "Yes" "No" "Yes"
 $ X1_rating_1          : int  83 30 NA 41
 $ X2_rating_1          : int  69 19 NA 25
 $ X3_rating_1          : int  87 20 NA 30
 $ X4_rating_1          : int  17 68 NA 38
 $ X5_rating_1          : int  80 86 NA 53
 $ X6_rating_1          : int  36 79 NA 34
 $ X7_rating_1          : int  40 83 NA 57
 $ X8_rating_1          : int  87 89 NA 65
 $ X9_rating_1          : int  75 86 NA 88
 $ X10_rating_1         : int  21 66 NA 93
 $ X11_rating_1         : int  27 80 NA 70
 $ X12_rating_1         : int  83 81 NA 89
 $ X13_rating_1         : int  56 45 NA 92
 $ X14_rating_1         : int  75 74 NA 31
 $ X15_rating_1         : int  NA 87 NA 75
 $ X16_rating_1         : int  51 33 NA 28
 $ X17_rating_1         : int  45 24 NA 59
 $ X18_rating_1         : int  92 83 NA 78
 $ X19_rating_1         : int  37 29 NA 74
 $ X20_rating_1         : int  72 20 NA 82
 $ X21_rating_1         : int  31 NA NA 81
 $ X22_rating_1         : int  41 91 NA 92
 $ X23_rating_1         : int  84 80 NA 81
 $ X24_rating_1         : int  89 91 NA 85
 $ X25_rating_1         : int  72 85 NA 76
 $ pp_age               : int  26 17 NA 19
 $ pp_gender            : chr  "Masculino" "Feminino" "" "Feminino"

As variáveis já não estão a todas ser registadas como characters. Contudo, ainda as variáveis factoriais são registadas como numéricas. As datas são registadas como character e não como datas.

O R não conseguiu reconhecer correctamente classe das variáveis porque ao importar os cabeçalhos extra como linhas normais passou a ter strings em todas as colunas e portanto todas tiveram de ser consideradas da classe character.



9.4.2 Exercício 3

  • Exclua da variável onde tem guardada a base de dados os participantes que cumpram qualquer um das seguintes condições: (1) não tenham consentido em participar, (2) tenham menos de 18 anos, (3) tenham realizado menos de 10% da experiência. Indique quantos participantes foram excluídos no processo.

  • Grave a base de dados, já sem os participantes que exclui na alínea anterior, no ficheiro clean_qualtrics.csv que criou anteriormente.

  • Indique potenciais vantagens deste workflow (i.e., forma de trabalhar) para a limpeza de participantes. Indique também potenciais limitações e sugira formas das colmatar.

Bónus: Exclua os participantes que tenham começado a experiência antes do dia 24 de Novembro de 2021.

9.4.2.1 Soluções

Clique para ver as soluções
antes <- nrow(dados)
dados <- subset(dados, consents == "Yes" & pp_age >= 18 & Progress > 10)
nrow(dados) - antes
[1] -2
write.csv(dados, "../data/clean_qualtrics.csv", row.names = FALSE)
# write.csv(dados, file.choose(), row.names = FALSE) # com interface gráfica.

Neste workflow as exclusões dos participantes ficam registadas num script que pode ser auditado, melhorado, testado, corrigido. Quando estivermos a escrever a secção de resultados podemos ler o script para reavivar a nossa memória. Temos também a certeza que nenhuns participantes que cumpram os critérios foram incluídos indevidamente, nem que participantes que não os cumpram foram excluídos indevidamente. Por outro lado, se o nosso script tiver erros semânticos (e.g., idade > 18 em vez de idade >= 18) podemos acabar por excluir participantes sem querer mas com uma falsa sensação de confiança só porque o fizemos com código. Para contornar a limitação devemos testar sempre o nosso código e explorar visualmente a base de dados para confirmar que os inputs e outputs são os esperados.

dados <- subset(dados,
                as.Date(StartDate, "%Y-%m-%d") >= as.Date("2021-11-24", "%Y-%m-%d"))

10 (Re)Formatação de Bases de Dados

Neste capítulo vamos aprender a reformatar bases de dados. Quando me refiro a reformatação de bases de dados, incluo todos os processos de preparação dos dados para análise que alterem o formato dos dados e ou acrescentem novas colunas/variáveis. No capítulo anterior aprendemos a limpar os dados, neste tutorial vamos assumir que os dados já estão “limpos” mas ainda precisam de ser preparados antes de os podermos analisar.

10.1 A “Prática” é MUITO Diferente da “Aula Prática”

Como vimos no tutorial anterior, ao contrário das aulas práticas das cadeiras de estatística, na prática raramente temos bases de dados bem arranjadinhas. Na prática muitas vezes perdemos mais tempo a preparar os dados do que a analisá-los.

10.1.1 A “Prática” no R é MUITO Diferente para Melhor!

Creio que a maioria das pessoas que não usa o R (ou python) para analisar os dados acaba por preparar os dados num programa como o Excel. A vantagem de usar o R para “limpar” os dados é ficarmos com todas as exclusões registadas num script, assegurando mais transparência e replicabilidade (no sentido em que podem replicar os nossos passos, não no sentido de aumentar a probabilidade dos nossos efeitos serem replicados). Apesar de eu considerar esta uma vantagem importante, creio que não trará grandes ganhos de eficiência por comparação ao Excel. No geral até é provável que demoremos mais tempo a escrever o script de “limpeza” dos dados do que se “limpássemos” os dados manualmente num programa como o Excel (e.g., Libreoffice Calc). No entanto, quando falamos de preparação dos dados, o uso do R, para além de continuar a assegurar a transparência e replicabilidade dos procedimentos, pode trazer enormes aumentos na nossa produtividade. Aliás, é possível até que o uso do R seja a única forma de conseguirmos preparar os nossos dados dentro do tempo que temos disponível. Por exemplo, imaginem que têm um estudo, com três condições intra-participantes, em que cada participante fez cem ensaios em cada condição. Imaginem ainda que o programa que usaram para recolher os dados registou a resposta dos participantes em colunas separadas, por condição e por ensaio. Isto quer dizer que neste caso a vossa base de dados terá no mínimo 300 colunas! Ora se tiverem que transformar essas colunas ou reformatar a estrutura da base de dados no Excel conseguem imaginar quanto tempo teriam que perder. O R permite-vos automatizar esse processo, evitando erros humanos e permitindo que poupem imenso tempo. Claro que até termos alguma prática com o R esse processo pode não parecer fácil e vamos perder algum tempo a escrever o script de reformatação dos dados. Mais tarde, com a prática, vão-se tornar cada vez a escrever esse script e os ganhos de eficiência vão aumentar. Para além disso, penso que será mais interessante e útil perdereminvestirem tempo a aprender a formatar dados no R que a fazer tarefas repetitivas no Excel… Notem que será injusto dizerem que perderam mais tempo a analisar os dados no R que no SPSS, se estiverem a somar o tempo que estiveram a reformatar dados ao tempo que estiveram a analisar dados, quando não somam o tempo que perderam no Excel ao tempo que perderam no SPSS.

Já chega de publicidade ao R não?

Mas…mas…o R tem tantas vantagens…

Era uma pergunta retórica… A resposta era NÃO!

Pronto…pronto…vamos a avançar…

10.2 O Que (re)formatar?

As reformatações mais simples que podemos fazer no R serão a transformação de algumas variáveis, o cálculo de novas a partir das existentes e a reconversão do tipo de variáveis (e.g., assumir que uma variável numérica contínua é tratada como tal). Outra transformação muito comum é passarmos uma base de dados do formato largo (i.e., wide) para o formato longo (i.e., long).

Quê??? Que é isso de formato largo e longo?

Eu ia explicar se não tivesses interrompido…

Eish… Cheira-me que alguém ficou sentido por eu ter interrompido a publicidade ao R…

Pois…talvez esse alguém tenha sentimentos…adiante…

Como eu ia explicar…uma base de dados no formato largo costuma ter uma linha por participante e uma coluna por cada medição/ensaio. Uma base de dados no formato longo, tem uma linha por cada medição/ensaio, acabando por ter várias linhas por participante. Para computarmos análises a dados com medidas repetidas (i.e., o mesmo participante realizou vários ensaios, ou respondeu à mesma pergunta mais que uma vez), normalmente dá-nos mais jeito termos os dados no formato longo do que no largo. Contudo, alguns programas de recolha de dados gravam os dados no formato largo. Nesses casos temos de converter do formato largo para longo. Da mesma forma que há programas de recolha de dados que gravam sempre os dados no formato largo, outros há que gravam sempre no longo. Nesses casos teremos então de converter do formato longo para largo.

10.3 Transformar Variáveis

Imaginemos uma base de dados em que cada linha é um/a participante e cada coluna corresponde aos tempos de resposta num ensaio.

head(ds)
    pp        t1        t2        t3
1 pp01 10033.131  9276.265 11634.775
2 pp02 10403.471  9593.608 10988.949
3 pp03  9616.129 11390.505 12789.715
4 pp04 10096.498 10041.672  9987.174
5 pp05 10683.462 10084.592 10841.866
6 pp06 10334.176 10329.596 11238.147

Podemos dizer que cada coluna corresponde a uma variável. A primeira contem o número do participante, nas três seguintes encontramos os tempos de resposta em cada ensaio. Com base nestes dados podemos acrescentar outras variáveis à nossa base de dados. Podemos também transformar estas variáveis em novas colunas ou alterar os valores presentes nelas. Obviamente que será impossível vermos tudo o que é possível fazer com estes dados, mas podemos mostrar alguns exemplos.

10.3.1 Acrescentar Variáveis

Imaginemos que eu sabia que na minha experiência metade dos participantes tinham sido colocados no grupo/condição de controlo e metade no grupo/condição experimental. Imaginemos ainda que a condição de cada participante não tinha sido registada na base de dados, mas tínhamos a certeza que a primeira metade dos dados correspondia aos participantes da condição controlo e a segunda metade aos da experimental.

Nesse caso, eu poderia criar uma nova coluna chamada cond que teria então a condição de cada participante. Sabendo que tinha 50 participantes e um participante por linha, saberia que as 25 primeiras linhas teriam nessa coluna o valor controlo e as últimas 25 linhas o valor experimental. Usando o que já aprendemos sobre data.frames poderia escrever as seguintes linhas de código.

# Atribui a condição controlo a todos os participantes.
ds$cond <- "controlo"
# Altera o valor de `cond`, nas últimas 25 linhas, para "experimental".
ds[26:50, "cond"] <- "experimental"

Já sabemos que podemos espreitar como ficou a base de dados usando as funções head() ou View(). Relembro que essas funções não devem ficar escritas nos nossos scripts por só serem úteis aquando da escrita do código e não em futuras execuções.

head(ds)
    pp        t1        t2        t3     cond
1 pp01 10033.131  9276.265 11634.775 controlo
2 pp02 10403.471  9593.608 10988.949 controlo
3 pp03  9616.129 11390.505 12789.715 controlo
4 pp04 10096.498 10041.672  9987.174 controlo
5 pp05 10683.462 10084.592 10841.866 controlo
6 pp06 10334.176 10329.596 11238.147 controlo

10.3.2 Conversões de Unidades

Muitas vezes podemos também converter os valores que estão nas nossas bases de dados para outras unidades, ou até inverter alguns itens duma escala. No exemplo que estamos a seguir, podemos ver que os tempos de resposta provavelmente estariam registados em milissegundos. Nesse caso poderíamos ter interesse em convertê-los para segundos. Podemos querer converter os valores que estão nas colunas ou podemos criar novas colunas com os valores alterados. Vamos assumir que queremos criar novas colunas. Nesse caso poderíamos escrever algo do género:

# Converte de milissegundos para segundos o valor de cada coluna
# guardando o resultado numa nova.
ds$t1_seg <- ds$t1 / 1000
ds$t2_seg <- ds$t2 / 1000
ds$t3_seg <- ds$t3 / 1000

Podemos voltar a ver como ficou com as mesmas funções de antes—head(), View()—(relembrando que essas funções não devem ficar nos scripts).

head(ds)
    pp        t1        t2        t3     cond    t1_seg    t2_seg    t3_seg
1 pp01 10033.131  9276.265 11634.775 controlo 10.033131  9.276265 11.634775
2 pp02 10403.471  9593.608 10988.949 controlo 10.403471  9.593608 10.988949
3 pp03  9616.129 11390.505 12789.715 controlo  9.616129 11.390505 12.789715
4 pp04 10096.498 10041.672  9987.174 controlo 10.096498 10.041672  9.987174
5 pp05 10683.462 10084.592 10841.866 controlo 10.683462 10.084592 10.841866
6 pp06 10334.176 10329.596 11238.147 controlo 10.334176 10.329596 11.238147

10.3.3 Centrar e Escalonar Variáveis

Um tipo de alteração/conversão frequente, especialmente quando queremos computar regressões lineares, é centrar e escalonar variáveis. O R tem a função scale() que nos permite fazer exactamente isso. Mais uma vez, podemos escolher se queremos alterar os valores que estão na coluna ou criar uma nova coluna com esses valores. À semelhança dos exemplos anteriores, vamos assumir que queremos preservar os valores originais nas colunas originais e criar novas colunas para guardar os valores transformados.

# Centra e escalona cada coluna guardando o resultado numa nova.
ds$t1_centrada <- scale(ds$t1)
ds$t2_centrada <- scale(ds$t2)
ds$t3_centrada <- scale(ds$t3)

Por defeito a função scale() centra e escalona as variáveis, mas podem definir como TRUE ou FALSE os argumentos center e scale para alterar o comportamento. Já sabem que podem aprender mais sobre a função consultando o seu manual (i.e., help(scale)).

# Centrar sem escalonar.
# Versão abreviada.
scale(ds$t1, scale = FALSE)
# Versão longa (igual mas mais descritiva).
scale(ds$t1, center = TRUE, scale = FALSE)

# Escalonar sem centrar.
scale(ds$t1, center = FALSE)
# Versão longa (igual mas mais descritiva).
scale(ds$t1, center = FALSE, scale = TRUE)

# Não escalonar nem centrar.
scale(ds$t1, center = FALSE, scale = FALSE)
# Mas isso é literalmente deixar a variável como está...
# Portanto é um pouco parvo...mas é possível...o R não vos impede...
# Eu também sou parvo...e sou possível...o R deixa-me existir também...

10.3.4 Transformações Logarítmicas

Em investigações experimentais em que sejam recolhidos tempos de resposta é frequente aplicarmos uma transformação logarítmica a esses valores. Isso é feito para que a distribuição dos erros dum modelo que os tenha como variável dependente siga uma distribuição mais próxima da normal.

Nota: Tudo isto pode parecer Chinês a quem nunca tenha ouvido falar destas coisas, pelo que esta secção será mais útil para quem trabalhe com estes dados. De qualquer das formas se lerem o livro até ao fim, nomeadamente a Parte III, as frases anteriores farão mais sentido.

Nos exemplos abaixo continuamos a assumir que queremos manter a coluna com os valores originais guardando os logaritmos numa nova coluna.

ds$t1_log <- log(ds$t1)
ds$t2_log <- log(ds$t2)
ds$t3_log <- log(ds$t3)

A função log() computa os logaritmos de todos os elementos do primeiro argumento (o argumento x). Nos casos anteriores passámos à função os valores das colunas t1, t2 e t3 como argumento x. A função log() aceita um segundo argumento (o argumento base) com a base do logaritmo a calcular, sendo que por defeito a base é o exponencial de um.

# Base exponencial de um.
# Versão abreviada.
log(ds$t1)
# Versão longa (igual mas mais descritiva).
log(ds$t1, base = exp(1))

# Base 2
# "Atalho"
log2(ds$t1)
# Versão longa
log(ds$t1, base = 2)

# Base 10
# "Atalho"
log10(ds$t1)
# Versão longa
log(ds$t1, base = 10)

# Outras bases sem atalhos
# Base 5
log(ds$t1, 5)
# Base 7
log(ds$t1, 7)

10.3.5 Computar Compostos

10.3.6 Substituir Variáveis

10.3.7 Alterar o Tipo/Classe

10.3.8 Renomear Factores

10.3.9 Ofuscar Dados Potencialmente Identificativos

10.4 (Re)formatar os Dados

Como vimos há alturas em que para além de transformar variáveis queremos mesmo alterar o formato dos dados. Mais concretamente, queremos passar os dados do formato largo para o formato longo, ou vice versa. O pacote tidyr, do projecto tidyverse, permite-nos passar os dados do formato largo para o formato longo, com a função pivot_longer(), bem como passar do formato longo para o largo, com a função pivot_wider(). Recomendo vivamente que leiam com atenção os tutoriais, exemplos e manuais das funções do pacote tidyr, disponíveis no site oficial do pacote. Relembro que se usarem um pacote, podem (e devem) citá-lo. Podem usar a função citation(), dando-lhe como argumento o nome do pacote que querem citar (entre parêntesis), para descobrir a citação recomendada pelos autores do pacote (e.g., citation("tidyr")).

Antes que nos esqueçamos vamos importar o pacote tidyr.

# Importar o pacote `tidyr`
library(tidyr)

IMPORTANTE: Nunca se esqueçam que para usarem as funções de um pacote têm primeiro de importar esse pacote. Tecnicamente podem importar o pacote em qualquer linha do vosso script que esteja antes da linha que usa alguma função do pacote. Por uma questão de estilo e organização recomendo vivamente que importem todos os pacotes que vão usar num script, no início desse script. Se enquanto escrevem um script se lembrarem que precisam de mais um pacote, voltem ao topo do dito script para o adicionarem. Lembrem-se também que para poderem importar um pacote têm de o ter instalado no computador que estão a usar. Podem instalar o pacote com install.packages("nome_do_pacote_entre_aspas"). Só precisam de instalar o pacote uma vez, portanto não incluam linhas de código que instalam pacotes nos scripts de análise de dados. Em vez disso, criem um script à parte em que instalam todos os pacotes e que só precisa de ser executado uma vez. Releiam a secção sobre pacotes, do capítulo A gramática do R.

10.4.1 Largo Para Longo

Voltemos à nossa base de dados, onde vemos que temos uma linha por participante e uma coluna por cada medição dos tempos de resposta. Ou seja, a base de dados está no formato largo.

head(ds)
    pp        t1        t2        t3     cond    t1_seg    t2_seg    t3_seg
1 pp01 10033.131  9276.265 11634.775 controlo 10.033131  9.276265 11.634775
2 pp02 10403.471  9593.608 10988.949 controlo 10.403471  9.593608 10.988949
3 pp03  9616.129 11390.505 12789.715 controlo  9.616129 11.390505 12.789715
4 pp04 10096.498 10041.672  9987.174 controlo 10.096498 10.041672  9.987174
5 pp05 10683.462 10084.592 10841.866 controlo 10.683462 10.084592 10.841866
6 pp06 10334.176 10329.596 11238.147 controlo 10.334176 10.329596 11.238147
  t1_centrada t2_centrada t3_centrada   t1_log   t2_log   t3_log
1  -0.5080499  -1.3229593  0.60001061 9.213648 9.135214 9.361754
2   0.7622432  -0.8507357 -0.24665705 9.249895 9.168852 9.304645
3  -1.9383958   1.8231437  2.11411937 9.171197 9.340535 9.456397
4  -0.2906978  -0.1839915 -1.55996937 9.219944 9.214499 9.209057
5   1.7226305  -0.1201245 -0.43948153 9.276452 9.218764 9.291170
6   0.5245548   0.2444543  0.08003689 9.243212 9.242768 9.327069

Para os dados ficarem no formato longo teremos então de ter uma base de dados com três linhas por participante, uma por cada ensaio. Essa base terá agora apenas duas colunas, uma com o identificador do participante e outra com os tempos de resposta (independentemente do ensaio).

ds <- pivot_longer(ds, c("t1", "t2", "t3"))

AVISO: as colunas adicionadas por pivot_longer() aparecem no fim da tabela. Se a base de dados tiver muitas colunas não vão aparecer quando fizermos print da variável. Daí que o melhor seja seleccionarmos a coluna com o identificador do participante e imprimirmos só essa e as criadas/transformadas por pivot_longer().

print(ds[ ,c("pp", "name", "value")])
# A tibble: 150 x 3
   pp    name   value
   <chr> <chr>  <dbl>
 1 pp01  t1    10033.
 2 pp01  t2     9276.
 3 pp01  t3    11635.
 4 pp02  t1    10403.
 5 pp02  t2     9594.
 6 pp02  t3    10989.
 7 pp03  t1     9616.
 8 pp03  t2    11391.
 9 pp03  t3    12790.
10 pp04  t1    10096.
# i 140 more rows

A função pivot_longer() permite especificarmos que nome queremos dar à coluna que vai conter os valores da variável dependente e a coluna que vai conter os níveis das medições repetidas. Os valores por defeito da função serão value e name respectivamente, mas podemos e devemos substituir por uns mais identificativos para quando formos analisar os dados o código ser mais legível.

Para podermos demonstrar vamos ter de inverter o que fizemos antes, ou seja, passar agora os dados do formato longo para o largo, mais à frente já vamos falar mais sobre isto, mas por agora podem ver que usamos a função oposta a pivot_longer() que dá pelo nome de pivot_wider().

# Passa os dados para o formato largo para voltarem ao estado inicial.
ds <- pivot_wider(ds)

Agora podemos voltar a passar para o formato longo especificando nomes diferentes (e.g., trial e response_time) dos usados por defeito (i.e., name e value).

ds <- pivot_longer(ds, c("t1", "t2", "t3"),
                   names_to = "trial", values_to = "response_time")

print(ds[ ,c("pp", "trial", "response_time")])
# A tibble: 150 x 3
   pp    trial response_time
   <chr> <chr>         <dbl>
 1 pp01  t1           10033.
 2 pp01  t2            9276.
 3 pp01  t3           11635.
 4 pp02  t1           10403.
 5 pp02  t2            9594.
 6 pp02  t3           10989.
 7 pp03  t1            9616.
 8 pp03  t2           11391.
 9 pp03  t3           12790.
10 pp04  t1           10096.
# i 140 more rows

Vamos mais uma vez desfazer o que fizemos, voltando a pôr a base de dados no seu estado original para podermos ver mais funcionalidades da função pivot_longer(). Notem que desta vez, como as colunas já não têm os nomes usados por pivot_longer() por defeito, porque usamos os argumentos names_to e values_to, para usarmos a função pivot_wider() temos de especificar os argumentos simétricos de names_from e values_from.

10.4.1.1 Usar Padrões nos Nomes das Colunas

Vimos que para passar os dados para o formato longo podemos escrever o nome de todas as colunas que vão passar a ser os valores da coluna name (sendo que os valores que estavam nessas colunas passarão para a coluna values). O contudo as funções do pacote tidyr, assim como outras da família tidyverse, permitem-nos poupar muito trabalho usando padrões para identificar as colunas, em vez de ter de as listar todas. Isto é muito útil, por exemplo, quando têm mesmo muitos ensaios/colunas, e precisariam de muito tempo e espaço para as listar manualmente. Não teremos espaço para listar todos os padrões, mas podemos ver alguns exemplos.

10.4.1.1.1 Padrões regex
colnames(ds) <- gsub("t(\\d$)", "t\\1_original", colnames(ds))

ds <- pivot_longer(ds, -c("pp", "cond"),
                   names_pattern = "(.*)_(.*)", names_to = c("trial", "var"),
                   values_to = "dv")

print(ds)
# A tibble: 600 x 5
   pp    cond     trial var             dv
   <chr> <chr>    <chr> <chr>        <dbl>
 1 pp01  controlo t1    original 10033.   
 2 pp01  controlo t2    original  9276.   
 3 pp01  controlo t3    original 11635.   
 4 pp01  controlo t1    seg         10.0  
 5 pp01  controlo t2    seg          9.28 
 6 pp01  controlo t3    seg         11.6  
 7 pp01  controlo t1    centrada    -0.508
 8 pp01  controlo t2    centrada    -1.32 
 9 pp01  controlo t3    centrada     0.600
10 pp01  controlo t1    log          9.21 
# i 590 more rows

10.4.2 Longo Para Largo

Já fomos usando a função pivot_wider() para desfazer o que fomos fazendo com pivot_longer(). Agora vamos falar um pouco melhor do que esta função faz.

10.4.2.1 Usar Padrões nos Nomes das Colunas

10.4.2.1.1 Padrões regex

10.4.3 Casos Mais Complexos

Em alguns casos mais específicos e complexos podemos ter de “alargar” os dados a mais, para depois “alongá-los” apenas um pouco, de forma a que fiquem apenas um pouco mais largos do que estavam. Noutros casos fazemos o oposto, “alongamos” demasiado, depois “alargamos” um pouco, de forma a ficarem apenas um pouco mais alargados do que estavam ao início. Temos casos ainda mais específicos, em que podemos usar as funções pivot_longer_spec() ou pivot_wider_spec(), para “alongar” ou “alargar” os dados de acordo com uma especificação concreta que queiramos, passo o pleonasmo, especificar.

10.4.3.1 Criar Mais que Duas Colunas ao Alongar

10.4.3.2 Alargar Várias Colunas

10.4.3.3 Alargar Para Depois Alongar

10.4.3.4 Alongar Para Depois Alargar

10.4.3.5 Usar Uma Especificação

10.4.4 Onde Procurar Ajuda

10.5 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

10.6 Exercícios

10.6.1 Exercício 1

  • Importe a base de dados ./data/demo_ds.csv e guarde-a numa variável.

  • Exclua os participantes que não consentiram em participar e os menores de idade, com apenas uma linha de código.

  • Grave os dados “limpos” num novo ficheiro clean_demo.csv algures no seu computador.

  • Importe a base de dados limpa e guarde-a numa variável que utilizará nos exercícios seguintes.

  • Releia o código usando a terminologia que já aprendemos (e.g., guarda o output da função…invocada com os argumentos…na variável…).

10.6.1.1 Soluções

Clique para ver as soluções
dados <- read.csv("../data/demo_ds.csv")
# dados <- read.csv(file.choose()) # Para interface gráfica.

dados <- subset(dados, consents == "Yes" & age >= 18)

write.csv(dados, "../data/clean_demo.csv", row.names = FALSE)
# Para interface gráfica:
#write.csv(dados, file.choose(new = TRUE), row.names = FALSE)

dados <- read.csv("../data/clean_demo.csv")
# dados <- read.csv(file.choose()) # Para interface gráfica.



10.6.2 Exercício 2

  • Crie uma nova coluna log_rt com os logaritmos naturais dos tempos de reposta. Clique para ver dica:

    A função log() computa o logaritmo natural.

  • Crie uma nova variável com a junção da variável cond e lab (no fundo uma variável com a interacção das duas).

    As funções paste() e paste0() permitem “colar” strings com e sem separadores entre elas, respectivamente.

  • Crie duas colunas i_phase1 e i_phase2 com os valores da phase1 e phase2 invertidos, respectivamente. Ou seja, se as respostas originais variam de 0 a 100, quem respondeu 0 deverá passar a ter 100 e quem respondeu 100 deverá passar a 0.

  • Crie uma nova coluna rcd_age com os valores da idade recodificados para intervalos menos identificativos.

10.6.2.1 Soluções

Clique para ver as soluções
dados$log_rt <- log(dados$rt)

dados$cond_lap <- paste0(dados$cond, dados$lab)

dados$i_phase1 <- (dados$phase1 - 100) * -1
dados$i_phase2 <- (dados$phase2 - 100) * -1

# Por exemplo intervalos em décadas (15-24, 25-34, 35-44, etc...).
dados$rcd_age <- round(dados$age / 10) * 10

10.6.3 Exercício 3

  • Instale (se não tiver instalado) e importe o pacote tidyr.

  • Passe a base de dados do formato largo (wide) para o formato longo (long), criando uma coluna com as resposta a phase1 e phase2 e outra com a medida (i.e., phase1 ou phase2). Ou seja, a base de dados passará a ter duas linhas por participante, uma com as suas resposta na phase1 e outra com as respostas na phase2. Clique para ver dica:

    A função pivot_longer() converte os dados para um formato mais longo.

  • Passe a base de dados para um formato mais largo revertendo o passo anterior usando a função pivot_wider().

  • Passe a base de dados para um formato ainda mais largo criando uma coluna diferente para os tempos de resposta em cada condição (cond).

  • Defina o que implica uma base de dados estar exclusivamente no formato longo (long), estar exclusivamente no formato largo (wide), ou estar num formato misto.

  • Liste uma vantagem e desvantagem de cada tipo de formato que descreveu anteriormente.

10.6.3.1 Soluções

Clique para ver as soluções

10.6.4 Exercício 4

10.6.4.1 Soluções

Clique para ver as soluções

Terceira Parte

Nesta terceira parte do livro vamos (re)introduzir a teoria estatística, começando pelas suas bases filosóficas e passando depois à sua lógica matemática. Assumimos que a maioria dos leitores já tenha tido algum contacto com a estatística. Dito isto, também assumimos que esse contacto possa não ter sido muito agradável e que o leitor tenha ficado com má impressão da estatística. A estatística não tem muitas competências sociais e tende a ser rude com as pessoas que está a conhecer pela primeira vez. Nós já conhecemos a estatística há alguns anos e ela mesmo assim ainda é rude para nós de vez quando. Dito isto como nós temos muito respeito pela estatística, ela já começa a ter algum respeito por nós. Por essa razão, vamos estar sempre presentes, a acompanhar os leitores nesta (re)introdução à estatística, fazendo sinal à estatística para que seja simpática e bem-educada. O nosso objectivo é que esta (re)introdução seja útil, tanto para quem já teve um primeiro (possivelmente mau contacto) com a estatística, como para quem está a contactar com a estatística pela primeira vez. Como nem sempre conseguimos o que queremos na vida é muito provável que este capítulo acabe por ser igualmente inútil para ambos. Pedimos que mesmo os leitores que já têm um bom domínio da estatística não saltem esta parte do livro, por dois motivos: (1) nesta parte apresentamos o quadro teórico das error statistics da Deborah Mayo, uma perspectiva ainda pouco conhecida; (2) apresentamos também a abordagem de comparação de modelos (Judd et al., 2017) que será essencial para perceber como construir, testar e interpretar o resultado dos modelos no R. Dito, isto claro, que leitores mais experientes podem ler estes capítulos mais na diagonal. Só não deixem de ler nas entrelinhas…

11 AVISO

Esta parte está INCOMPLETA, o trabalho nesta parte do livro continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que esta parte já tenha sido actualizada, deve considerar a informação apresentada como INCOMPLETA.!

12 Estatística: Porquê? Como? Como Assim?!?!!?

O objectivo deste capítulo é (re)introduzir os conceitos base da filosofia da estatística, duma forma acessível, que permita desconstruir alguns mitos e esclarecer alguns equívocos. Contudo, se até agora temos falhado nos nossos objectivos, os leitores não devem esperar que magicamente este passe a ser um livro, claro, conciso e relevante. Neste caso, é possível que nem saibamos explicar o básico, que expliquemos duma forma tão clara como vantablack, que consigamos dar dimensões épicas aos mitos, criando toda uma nova classe de equívocos. De qualquer forma, os leitores perceberão com mais clareza que cometeram um erro ao decidir ler este livro.

Este capítulo subdivide-se em três secções. Na primeira vamos brevemente mencionar alguns dos problemas que nos levam a precisar de métodos e testes estatísticos—o porquê de recorrermos à estatística. Na segunda vamos ver como se usam esses métodos. Na terceira e última secção apresentaremos as filosofias da estatística—as lógicas da inferência. É nessa secção que vamos tentar clarificar um dos conceitos mais controversos—o valor-p (p-value), levando o leitor a exclamar: como assim?!?!!?

AVISO: neste capítulo vamos abordar temas controversos da estatística, como os valores-p. O nosso posicionamento nos debates filosóficos não será neutro. Pelo contrário, vamos consistentemente tentar defender a proposta das error statistics da Deborah Mayo.

12.1 O Porquê?

Quando ouvimos falar de estatística, ou outros temas com que nos massacram na faculdade, uma pergunta que facilmente nos ocorrerá será “para que é que eu preciso disso?”. Tememos que alguns dos preconceitos contra a estatística comecem exactamente por nem sempre se dar uma boa resposta a essa pergunta, quando ela é colocada pela primeira vez por alunas e alunos. No nosso tempo… Ai os nossos dias de estudante! Descíamos de coche as pedras da calçada, exigindo que os cavalos galopassem mais de pressa, tal era o nosso entusiasmo por ir ouvir a prelecção dos lentes… Vá estamos a brincar não somos assim tão velhos… Voltando ao porquê da estatística. No nosso tempo, a resposta que muitas vezes era dada informalmente é que precisaríamos da estatística para a tese de mestrado. Como tínhamos as cadeiras de estatística no primeiro ano, nem sempre esse argumento era persuasivo o suficiente. Em nome da verdade, nas aulas, os Professores até explicavam os vários problemas que a estatística nos ajuda a resolver, mas dado que estávamos no primeiro ano, nem sempre esses problemas nos pareciam tão prementes como a escolha do local para a saída à noite desse dia/noite/madrugada. Será pertinente discutir quando e como se deve introduzir a estatística nos planos curriculares, mas essa discussão terá de ser tida noutro local, por pessoas mais competentes. Vamos assumir que os leitores precisam da estatística para alguma coisa daí estarem a passar pelo suplício de ler este livro. Mesmo assim, vamos tentar chamar a atenção para vários problemas que a estatística pode resolver. Vamos ainda aproveitar a ocasião para explicar de termos usados por áreas diferentes que acabam por ser sinónimos, mas aparentam ser simplesmente por essas áreas terem objectivos diferentes no seu uso da estatística.

12.1.1 Para Que Serve Então a Estatística?

Em traços gerais podemos listar alguns usos que podemos dar à estatística:

  • Sumariar e descrever dados

  • Encontrar padrões nos dados

  • Fazer previsões

  • Apoiar a tomada de decisão

  • Testar hipóteses

  • Apresentar os dados de forma compreensível e apelativa

  • Escolher as verdades que interessam/mentir citando números

Tentemos então perceber de que forma a estatística serve cada um desses propósitos.

12.1.2 Sumariar e Descrever Dados

Um dos primeiros objectivos da estatística é ajudar-nos a extrair sumários dos nossos dados que nos permitam descrevê-los de forma mais compreensível. Versões desta frase, com melhor redacção, costumam aparecer nos livros de estatística e nas aulas. Dado que os alunos não costumam ficar esclarecidos talvez valha a pena explicar doutra forma… Mais confusa que assim eles aprendem a não questionar os livros de estatística!!! Muah ah ahhh! Estamos a brincar… Queremos tentar explicar duma forma mais clara. Não devemos conseguir…mas não deixaremos de tentar!

Imaginemos que recolhemos dados para um estudo que estamos a fazer. Talvez o estudo procure estudar porque razão há pessoas que lêem este livro de estatística quando há outros tão melhores. Nesse caso, a recolha de dados poderia passar por pedir aos leitores que preencham um pequeno questionário. Não se preocupem este é apenas um caso hipotético. Não vamos pedir que preencham nenhum questionário. Deixaremos que se concentrem no tormento que é ler este livro. Imaginemos então que teríamos recolhido algumas respostas a esse questionário. Mesmo que o questionário tivesse poucas perguntas, bastaria que uma dúzia de pessoas respondesse para que fosse difícil reter os dados na nossa cabeça. Por exemplo, se tivéssemos recolhido as idades da dúzia de leitores que respondeu, teríamos um dúzia de idades. Em notação mais matemática poderíamos dizer: 12 participantes = 12 observações. Como podem ver, têm que ler o livro com atenção, para compreender as equações matemáticas complexas que nele se encontram. Se não soubermos como resumir (i.e., sumariar) estes dados, cada vez que quiséssemos falar das idades dos leitores teríamos que listar as 12 idades. O mesmo se pode dizer de qualquer outro dado que tivéssemos recolhido. Não sabendo como sumariar os dados, teríamos sempre de reportar cada observação. Isto não só seria uma chatice, como seria difícil tirar qualquer conclusão, a não ser que fossemos como o Rain Man. Nestes casos, a estatística ajuda-nos com ferramentas que nos permitem sumariar grandes (ou pequenos) conjuntos de observações num só valor. Claro que perdemos sempre informação ao fazer esse sumário. Afinal para fazermos um resumo (um sumário) perdemos sempre alguma informação. Contudo, essa é uma perda que temos de aceitar quando doutra forma não teríamos como compreender os dados. Podemos sempre tentar reduzir a informação perdida recorrendo a mais que um sumário dos dados. Claro que fará pouco sentido apresentar mais sumários dos dados do que o número de observações, mas pode ser sensato apresentar dois ou três sumários dos mesmos dados.

No caso das idades dos participantes, em vez de listarmos sempre todas as idades podemos listar a média das idades. Ao olharmos para a média das idades ficamos com uma ideia de se os nossos participantes, no caso do estudo hipotético, os nossos leitores, são muito jovens, se são de meia idade, ou se são mais velhos. Ao fazermos isto, deixamos de ter de listar sempre as idades dos 12 leitores, para passarmos a ter de apresentar só um número. Já vimos que perdemos informação ao fazer isto. Por exemplo, mesmo que a maioria dos leitores seja muito jovem, havendo um ou dois leitores de idade mais avançada a média das idades poderá ser elevada. Mesmo assim será mais fácil retirarmos informação duma média do que de todas as observações. Até porque em vez de 12 observações poderíamos ter 120, 500, 10000, ou mais. Mesmo que isso não aconteça no caso do questionário aos leitores do livro (porque algo de muito mau terá de se passar com a humanidade para termos sequer 12 leitores) poderá acontecer em estudos que os leitores desenvolvam ou trabalhos que tenham de fazer.

Também já dissemos que podemos tentar colmatar algumas limitações de um dado sumário dos dados, por exemplo, a média, recorrendo a outros sumários. No caso das idades, para além de reportarmos a média poderíamos tentar reportar algum sumário que desse a entender o quão diferentes ou semelhantes são as idades dos participantes. Por exemplo, poderíamos reportar intervalo das idades, ou seja, a idade miníma e a idade máxima, ou mesmo o desvio padrão. Este tipo de medidas que nos ajudam a perceber o quão próximos ou distantes as observações (e.g., idades) estão umas das outras, são designadas por medidas de dispersão. Em termos gerais, as medidas que nos permitem apresentar sumários que descrevem os dados são chamadas de estatísticas descritivas.

Apresentarmos as várias estatísticas descritivas, listando as suas vantagens e desvantagens vai para além do âmbito desta secção. Mesmo assim podemos ver que o exemplo das idades bastou para perceber que a média é muito sensível a valores extremos (idades muito jovens ou muito avançadas). Se em vez das idades olharmos para o distrito do país em que vivem os leitores, rapidamente percebemos que nem todas as estatísticas descritivas se adequam a todo o tipo de variáveis. Por exemplo, não teríamos como calcular a média, o intervalo, ou o desvio padrão dos nomes dos distritos. Como o objectivo não era dissertar sobre estatística descritiva, mas apenas explicar como a estatística ajuda a sumariar/resumir os dados, achamos que já podemos avançar para outro propósito da estatística.

12.1.3 Apresentar/Representar Dados

  • Gráficos

12.1.4 Encontrar Padrões

A estatística permite-nos fazer mais do que resumir os dados, dando-nos ferramentas para os explorar e descobrir padrões que motivem reflexão. Continuemos com o exemplo do estudo sobre o que leva as pessoas a ler este livro quando há outros tão melhores. Assim damos alguma consistência ao livro, que só tem conseguido ser consistentemente mau.

Dado que este livro é mesmo, mesmo mau, se no nosso estudo recolhêssemos dados de 120 leitores, em vez de 12, a reacção mais natural seria de perplexidade. Talvez fosse difícil encontrar alguma teoria ou hipótese que explicasse os dados. Talvez nem todo o conhecimento da psique humana alguma vez alcançado pela ciência pudesse ser capaz de explicar o fenómeno. Nesse caso não teríamos propriamente hipóteses para testar, estaríamos apenas desejosos de querer compreender o que aconteceu.

Assumindo que sabemos fazer questionários melhor do que sabemos escrever livros (algo fácil dado que não sabemos claramente escrever livros), teríamos então esperança de que os dados do questionário nos dessem pistas para compreender esse estranho fenómeno. Decidiríamos então partir à descoberta de algum padrão ou padrões nos dados que nos dessem pistas para reflectir. A este tipo de abordagem, em que não procuramos testar teorias/hipóteses muito concretas, mas a encontrar padrões (que depois até podem dar-nos ideias para hipóteses a testar no futuro) podemos dar o nome de análise exploratória.

Voltando ao estudo dos leitores do livro, poderíamos começar por procurar padrões nas idades dos participantes. Já vimos que há algumas estatísticas que nos ajudam a sumariar os dados, incluindo as idades. Agora podemos olhar para outras estatísticas que nos permitem procurar por padrões. Por exemplo, mesmo sem ter hipóteses muito concretas para testar, poderíamos pensar que os leitores do livro pertencem a grupos/clusters distintos.

Talvez algumas pessoas estivessem a ler o livro porque já tinham lido todos os outros à face da Terra, tendo pensado: “bem só sobra este”. Por outro lado, talvez houvesse pessoas que tivessem lido tão poucos (mas tão poucos) livros na vida que não tenham tido muita forma de reparar que este livro é mesmo, mesmo, muito pior que os outros. Ainda sem grande teoria, poderíamos especular que quem já leu todos os livros do Mundo tivesse uma idade mais avançada do que quem ainda quase não leu nenhum. Podíamos então pensar que se agrupássemos as idades dos participantes em grupos/clusters—elas se poderiam agrupar bem em dois grupos/clusters. Recorreríamos então a técnicas de agrupamento por k-médias (k-means clustering), que como o nome indica agrupam os dados por proximidade a um número k de médias. Diríamos então ao R ou a outro software estatístico para agrupar os dados em dois grupos/clustersk = 2. Experimentávamos correr novamente a análise com valores diferentes para k de forma a ver se o agrupamento em dois grupos/cluster é o que melhor se adequa aos dados.

Reparem que o importante não é a técnica estatística usada. Em vez de técnicas complexas de agrupamento/clustering poderíamos estar a falar de técnicas mais simples como correlações. Por exemplo, o questionário poderia incluir uma medida de bem estar dos leitores, bem como o número de secções que já leram do livro. Podíamos então querer explorar se o bem-estar se correlaciona negativamente com o número de secções lidas. Ou seja, pensarmos que quanto mais lêem o livro pior as pessoas se sentem, ou que só quem esteja muito mal é que contínua a ler o livro e não para por não reparar que é mau. Notem, que neste caso não temos uma previsão específica para a direcção da causalidade (i.e., ser o livro a causar o mal-estar, ou o mal-estar a levar à leitura continuada do livro), apenas esperamos que os fenómenos estejam relacionados.

Em suam, o que define então a abordagem exploratória é a procura de padrões sem hipóteses muito definidas que queiramos testar. Nesses casos há várias ferramentas estatísticas exploratórias que nos permitem procurar por padrões nos dados, recorrendo a métodos matemáticos e não apenas a intuições pessoais subjectivas.

12.1.5 Fazer Previsões

Podemos usar os modelos estatísticos que desenvolvemos para fazer previsões. Esta ideia pode parecer algo mágica mas esse não é de todo o caso. Na realidade a forma como os modelos podem fazer previsões é bastante simples e nada misteriosa. Por exemplo, os modelos lineares são a grande família de modelos estatísticos que iremos tratar neste livro. A explicação mais detalhada destes modelos será apresentada no próximo capítulo. Neste capítulo o nosso foco é apenas compreender os princípios filosóficos subjacentes à estatística, nomeadamente ao teste estatístico. Dito isto, para podermos compreender esses conceitos, convém termos alguma noção do que é um modelo estatístico. Felizmente, o conceito geral de modelos linear, é muito fácil de explicar e de entender. Os modelos lineares são, com o nome indica, uma linha—uma recta. Ou seja, em traços gerais os modelos lineares são um traço num gráfico. Essa linha é definida por uma equação. A fórmula e as propriedades matemáticas dessa equação não interessam muito agora, o importante é saber que para qualquer valor de \(x\) o modelo dá-nos um valor para \(y\). Mais concretamente, para qualquer valor da nossa variável independente (ou variáveis independentes) o modelo prevê um valor para a variável dependente. Qualquer um desses valores será um ponto na recta que é o modelo. Assim conseguimos rapidamente perceber algumas das limitações da forma como os modelos fazem previsões. A primeira limitação é que a previsão da variável dependente é específica de cada modelo e feita apenas em função das variável/is independente/s que integra/m o modelo. Por exemplo, um modelo linear que tenha como variável independente apenas a idade da pessoa e como variável dependente o seu rendimento mensal, prevê quanto dinheiro a pessoa vai receber apenas em função da sua idade. Se não soubermos a idade da pessoa então o modelo não consegue prever nada. Se tivermos muito mais informações para além da idade, o modelo não as usará para o cálculo. Outra propriedade importante dos modelos estatísticos é que eles são estimados a partir de um conjunto de dados. Ou seja, a formula do modelo/recta é estimada a partir dum conjunto de dados. Como o modelo é uma recta, e uma recta não tem princípio nem fim, para qualquer valor de \(x\) (i.e., variável independente), mesmo que esse valor não exista nos dados, conseguimos prever ou estimar o valor de \(y\) (i.e., variável dependente). Por exemplo, com o modelo \(y = x + 1\) (a sofisticação matemática deste livro é mesmo impressionante), conseguimos estimar o valor de \(y\) para qualquer \(x\). Podem pensar que é um modelo muito parvo—têm razão—mas este modelo é excelente a prever qual a idade que uma pessoa terá para o ano que vem, sabendo a idade que ela tem este ano. Se soubermos que uma pessoa tem 20 anos (\(x = 20\)), com base neste modelo, conseguimos prever que para o ano terá 21 anos (\(y = 20 + = 21\)). Notem que os modelo não vêm dotados de bom senso, não nos avisam se pedirmos para preverem algo que não faz sentido no contexto do problema. Por exemplo, se estivermos a prever idades humanas, não fará muito sentido tentar prever quantos anos terá uma pessoa com -30 anos daqui a um ano, porque as idades humanas não são negativas. Como o modelo não sabe disso o modelo irá prever que essa pessoa terá -29 anos para o ano (\(y = -30 + 1 = -29\)). Como dissemos anteriormente, os parâmetros dos modelos estatísticos são estimados a partir duma dada amostra de dados. Isto quer dizer que a qualidade das previsões dum modelo está dependente da amostra usada para estimar os seus parâmetros. Um modelo de memória, estimado a partir duma amostra constituída maioritariamente por jovens adultos, pode não fazer boas previsões para a memória de adultos de idade mais avançada, ou para a memória de bebés.

Com o exemplo do modelo, de alta complexidade matemática, \(y = x + 1\), percebemos claramente que temos de ter muito cuidado ao interpretar e usar o termo prever para falar do nosso modelo. Efectivamente, para qualquer valor da variável independente o modelo é capaz de fazer uma previsão para o valor da variável dependente. O problema não está na frase ser falsa, mas no risco que frases como estas têm de interpretadas de forma muito elogiosa para os nossos modelos. Se dissermos que o nosso modelo é capaz de somar um a qualquer valor da variável independente, o modelo deixa de parecer um adivinho e passa a parecer a máquina de calcular mais inútil de sempre. Infelizmente, o termo prever não é o único termo que dá facilmente azo a interpretações erróneas na estatística. Não temos tempo de rever todos os termos potencialmente problemáticos, mas fica o aviso que o termo explicar é capaz de ser ainda mais problemático que o termo prever. Em estatística considera-se que um modelo “explica” os dados quando o erro das previsões do modelo é inferior ao erro das previsões dum modelo mais simples com o qual é comparado. Por exemplo, poderíamos comparar o modelo do exemplo anterior (\(y = x + 1\)), com a média das idades (\(y = M\)). Ou seja, poderíamos ver qual o modelo que tem maior erro a prever a idade duma pessoa daqui a um ano, o modelo que prevê que a pessoa terá mais um ano (\(y = x + 1\)), ou o modelo que diz que a idade de qualquer pessoa daqui a um ano é igual à média das idades dos participantes na nossa amostra. Obviamente que vamos acertar mais vezes na idade que alguém terá daqui a um ano se adicionar um à idade que tem agora, do que se dissermos que a idade dessa pessoa será igual à média das idades da nossa amostra. Por exemplo, se a média das idades na nossa amostra for 20 (\(M = 20\)), ambos os modelos prevêem que uma pessoa que agora tenha 19 anos para o ano terá 20 (\(y = 19 + 1 = 20\); \(y = M = 20\)). Neste caso nenhum dos modelos tem erro porque a previsão que fazem é exactamente igual à realidade observada. Contudo se quisermos prever a idade que uma pessoa de 30 anos terá para o ano um dos modelos prevê que será 31 e outro contínua a prever que seja 20 (\(y = 30 + 1 = 31\) e \(y = M = 20\)). Isto significa que um modelo contínua a ter zero erro (\(previsão = 31\), \(dados/realidade = 31\), \(erro = dados - previsão\), \(erro = 31 - 31 = 0\)), enquanto o outro modelo tem um erro de -11 na sua estimativa (\(previsão = 20\), \(dados = 31\), \(erro = 20 - 31 = -11\)). Teríamos então direito a dizer que o modelo \(y = x + 1\) “explica” melhor os dados que o modelo \(y = M\). Tomem então cuidado para não interpretar o termo “explicar” usado na estatística de modo a imaginarem os modelos como grandes pensadores da história da humanidade.

Vejamos outro exemplo. Existe uma correlação positiva entre o número de afogamentos nas praias e o consumo de gelados. Isto quer dizer que quando o número de gelados consumidos aumenta, o número de afogamentos aumenta e vice versa. Portanto podemos prever o número de afogamentos a partir dos gelados consumidos, ou então podemos prever o número de gelados consumidos a partir do número de afogamentos. Podemos então esperar que um modelo de correlação tenha menos erro a prever as mortes por afogamento num mês com base no número de gelados consumidos nesse mês, que um modelo que use apenas a média das mortes por afogamento em todo o ano anterior, para prever as mortes por afogamento num dado mês deste ano. Neste sentido, poderíamos dizer que o modelo de correlação “explica” melhor os dados. A questão é que ambos os modelos mais uma vez não parecem estar a explicar seja o que for. Aliás o facto do número de mortes por afogamento se correlacionar com o número de gelados consumido é que carece de explicação. Essa correlação é facilmente explicada, não por um modelo estatístico, mas por compreendermos que é normal que se consumam mais gelados quando o tempo está mais quente e que esse será também o período em que as pessoas mais vão à praia ou à piscina nadar (quanto mais pessoas forem nadar mais provável é que haja mortes por afogamento). Portanto se formos uma marca de gelados podemos tentar prever as vendas para este mês com base no número de mortes por afogamento, mas talvez possamos simplesmente consultar a meteorologia (que será um exercício menos macabro e mais útil).

12.1.6 Apoiar a Tomada de decisão

Sendo que podemos usar os modelos estatísticos para fazer previsões, podemos usá-los para apoiar a tomada de decisão. Já vimos que temos de ter expectativas realistas quanto à capacidade que os nossos modelos têm de fazer previsões. Por isso, temos também de ser cautelosos quando os usamos no apoio à tomada de decisão. Voltemos ao exemplo anterior. Vimos que há uma correlação entre o número de afogamentos e número de gelados vendidos. Nesse sentido se quisermos tomar uma decisão quanto ao número de gelados que vamos comprar para o nosso bar ao pé da praia, podemos tentar ver qual o número de afogamentos num período análogo do ano anterior. Ao mesmo tempo, se quisermos saber quanto devemos apostar na prevenção de afogamentos, podemos consultar o número de gelados vendidos em período análogo do ano anterior. Sendo que a correlação entre os afogamentos e o consumo dos gelados é real (mesmo que não seja perfeita), se tomarmos as nossas decisões com base nesses dados podemos esperar ter mais sucesso do que se as tomarmos aos calhas. No geral sempre que baseamos as nossas decisões num indicador correlacionado com o objectivo que queremos atingir, vamos ter melhor desempenho do que se tomarmos decisões ao acaso. A questão é que provavelmente também não tomamos decisões ao acaso. Se trabalhamos num dada área há uns anos é possível que baseemos a nossa intuição na nossa experiência. Assim sendo é possível que a nossa intuição seja enviesada, mas também é possível que tenha uma performance superior ao acaso. Nesses casos basearmos a nossa decisão em maus indicadores até pode ser pior do que a basear a decisão na nossa intuição. A vantagem de usarmos os dados e os indicadores é que a nossa abordagem é mais sistemática, mais organizada e poderá ser mais facilmente alvo de escrutínio e melhoria no futuro. Claro que isto implica que olhemos com sentido crítico para a estatística e não como algo mágico que nos diz sempre a verdade.

12.1.7 Testar Hipóteses

  • Temos de definir a nossa hipótese de forma matemática

  • Depois podemos testar o quão incompatíveis os nossos dados são com a hipótese contrária

Num quadro bayesiano (já veremos o que o termo quer dizer), a lógica é diferente. Temos na mesma de ser capazes de definir a nossa hipótese, mas o que fazemos é usar os dados para actualizar crenças prévias sobre uma hipótese. Se os dados forem muito compatíveis com a nossa hipótese passamos a acreditar mais na nossa hipótese, se os dados forem incompatíveis com a nossa hipótese passamos a acreditar menos nela.

12.1.8 Mentir Citando Números

Escolhem uma estatística/indicador que é favorável ao que querem dizer, ignorando todas as que são contrárias, mesmo que tudo indique que estão errados. No fundo não querem aprender com os dados por já terem tomado a decisão, mas depois interessa mostrar que essa decisão é “apoiada” nos dados. Isso é parecido com a anedota do cowboy que quando as pessoas iam ao rancho dele viam que fosse qual fosse o alvo ele tinha acertado no centro. Perguntaram-lhe como conseguia tais feitos de precisão e ele explicou que o segredo era disparar primeiro, ver onde o tiro acertava, e depois desenhar o alvo à volta dessa localização. Quem usa a estatística para enganar acaba por muitas vezes fazer isso. Toma a decisão. Depois encomenda um estudo e selecciona os resultados para justificar a decisão que já tinha tomado, desenhando assim o alvo à volta da decisão que tomou para dar a entender que acertou na mouche. Neste livro não vamos ensinar ninguém a mentir usando números (nem devemos conseguir ensinar seja o que for). Vamos antes tentar dar as competências necessárias aos leitores para se defenderem de quem os tentar enganar com números. A quem queira aprender a mentir citando números recomendamos um cuidado exame de consciência. Mentir é feio e mentir usando números não deixa de ser feio e dá mais trabalho.

12.1.9 Para Que Não Serve a Estatística?

  • Para resolver problemas na forma como recolhemos os dados

  • Para testar hipóteses que não podem ser testadas empiricamente

  • Para provar teorias de forma definitiva

12.1.10 Nomes Diferentes para as Mesmas Coisas

  • preditor = variável independente

  • outcome = variável dependente

  • grupo = condição

  • amostras dependentes = medidas repetidas = intra-sujeitos = intra-participantes = dados correlacionados

  • amostras independentes = entre-sujeitos = entre-participantes = dados não correlacionados

12.2 Como?

12.2.1 Variáveis

12.2.1.1 Classificações Teóricas/Causais

  • Variáveis dependentes

  • Variáveis independentes

  • Variáveis estranhas

  • Variáveis controladas

  • Variáveis moderadoras

  • Variáveis mediadoras

12.2.1.2 Classificações Matemáticas

  • Variáveis categóricas:
    • Nominais:
      • Dictómicas (k = 2)
      • Vários niveis (k > 2)
    • Ordinais
  • Variáveis quantitativas:
    • Intervalares
    • De razão

Podemos ainda classificar as variáveis quantitativas quanto a serem discretas (só números inteiros sem casas decimais, e.g., contagens, como o número de filhos) ou contínuas (podem ter valores intermédios, e.g., 1.75 metros).

12.2.1.3 Apontamentos Sobre Medidas

  • Erro de medida

  • Kelvin vs. Centígrados

  • Validade do construto

  • Precisão

  • Fiabilidade

12.2.2 Distribuições

12.2.2.1 Normal

12.2.2.2 Binomial

12.2.2.3 Exponencial

Tempos de resposta, pandemias…

12.2.2.4 Logarítmica

Normalização de tempos de resposta, avaliar aceleração da infecção.

12.2.2.5 Quadrática

Fenómeno exponencial.

12.2.2.6 Cúbica

Fenómeno ainda mais exponencial.

12.2.2.7 Trignométricas

Só para complicar…

12.2.2.8 Harmónica

Para quem gostar de música ou engenharia…

12.2.2.9 Área = Probabilidade

Quanto maior a área maior a probabilidade.

A base da inferência estatística assenta aqui, mas só perceberemos isso mais à frente.

12.2.3 Estatística Descritiva

12.2.3.1 Tendência Central

  • Moda

  • Mediana

  • Média (aritmética)

  • Média ponderada

12.2.3.1.1 Alternativas Robustas
  • Média aparada

Já que as traduções para Português dos próximos métodos tendem a manter o termo inglês, mais vale usar maus termos Portugueses mas que pelo menos tenham piada.

  • Tesourada na média (Winsorized mean)

  • Naifada na média (Jacknifed mean)

  • Média da bota (Bootstraped mean)

12.2.3.2 Quantis

  • Quartis

  • Percentis

  • Quantis

12.2.3.3 Dispersão

  • Amplitude total

  • Amplitude interquartil

  • Amplitude

12.2.3.4 Assimetria e Curtose

  • Viés esquerda direita vs. simetria.

  • Estreitamento (leptocúrtica), normalidade, achatamento (platicúrtica)

Existem várias medidas que tentam estimar se uma dada distribuição duma variável está enviesada para a direita ou esquerda. Existem também medidas que tentam estimar se a distribuição está estreitada ou achatada. Quando se fala de desvios perante uma distribuição unimodal simétrica como a distribuição normal, muitas dessas estatísticas funcionam bem. Nesses casos até podemos confiar em regras de polegar mais simples como olhar para a ordenação da média, mediana e moda. Lembrem-se que a média não é robusta a valores extremos, logo será a primeira a afastar-se do centro da distribuição (que é aquilo que deveria estimar). Depois da média irá a mediana (que é mais robusta, sendo uma média aparada a 50%) e só mais tarde a moda. Por essa razão aquilo que vários estimadores da assimetria fazem é ver a direcção e magnitude das diferenças entre estes parâmetros. O problema é que estas regras de polegar falham quando consideramos desvios para outros tipos de distribuição, ou alguns desvios mais específicos (ver Hippel, 2005).

Estando nós a estudar estatística para aprender a analisar dados e não para sermos estatísticos de profissão e produzindo o R gráficos tão bonitos, será mais olharmos para a distribuição das nossas variáveis graficamente que perder horas a seleccionar e estudar as várias medidas de assimetria e curtose.

12.2.4 Gráficos

12.2.4.1 Circulares

  • Variáveis categóricas nominais ou ordinais

Sendo as variáveis ordinais devemos ordenar as categorias na ordem da menor para a maior.

12.2.4.2 Barras

  • Variáveis categóricas (nominais ou ordinais) ou quantitativas

Sendo as variáveis ordinais devemos ordenar as categorias na ordem da menor para a maior.

12.2.4.3 Boxplots

  • Variáveis categóricas ordinais ou variáveis quantitativas

Podendo criar vários boxplots em função duma variável categórica.

12.2.4.4 Histogramas

  • Variáveis quantitativas

12.2.4.5 Densidade

  • Variáveis quantitativas

12.2.4.6 Violino

  • Variáveis quantitativas

Podendo criar vários violinos em função duma variável categórica.

12.2.4.7 Scatter Plots

  • Variáveis categóricas ordinais e ou variáveis quantitativas

12.3 Como Assim?!?!!?

Nesta secção vamos focar-nos na filosofia da estatística, mais concretamente na filosofia da estatística de teste.

12.3.1 Modelos

12.3.2 Fisher

Fisher
Fisher

12.3.3 Neyman & Pearson

Neyman Konrad Jacobs, Erlangen, Copyright: MFO - Mathematisches Forschungsinstitut Oberwolfach,https://opc.mfo.de/detail?photo_id=3044; License CC BY-SA 2.0 de

Pearson
Pearson

12.3.4 Bayesianismo

\(P(A \mid B) = \frac{P(B \mid A) P(A)}{P(B)}\)

12.3.5 Deborah Mayo

Deborah Mayo
Deborah Mayo

12.4 Valor-p

Veremos o que diz a associação americana de estatísticos, o equivalente à APA para os estatísticos:

The ASA Statement on p-Values: Context, Process, and Purpose

Over the course of many months, group members discussed what format the statement should take, tried to more concretely visualize the audience for the statement, and began to find points of agreement.

That turned out to be relatively easy to do, but it was just as easy to find points of intense disagreement.

it was just as easy to find points of intense disagreement.

intense

12.4.1 Definição

Informally, a p-value is the probability under a specified statistical model that a statistical summary of the data […] would be equal to or more extreme than its observed value.

Informally…

Imagino se fosse formalmente… Piada à parte a definição do valor-p é mesmo esta. Um valor-p não é mais nem menos do que isto. Se tentarmos dizer que é mais acabamos por dizer que o valor-p é capaz de nos dizer coisas de que não é capaz. Se tentarmos dizer menos, ignoramos aspectos importantes do valor-p e acabamos por não o interpretar com os cuidados necessários.

13 O Que Testam os Testes?

Este tutorial tem como objectivo explicar a teoria dos testes estatísticos visualmente. Não vamos explicar todos os testes estatísticos que existem mas veremos alguns dos mais comuns na Psicologia, nomeadamente t-testes, ANOVAs, ANCOVAs, correlações e regressões lineares. Para isso vamos usar a abordagem de comparação de modelos de Judd e colaboradores que nos diz que todos esses testes são na realidade modelos lineares e podem ser todos escritos com a mesma equação.

13.1 AVISO

Ao contrário dos outros tutoriais, este tutorial é principalmente teórico. Ao longo do tutorial, vamos ver várias representações visuais dos modelos e usar o R para ir demonstrando a abordagem de comparação de modelos de Judd e colaboradores. Contudo, as demonstrações são isso mesmo—demonstrações—não correspondendo à forma como fazemos normalmente as análises no R. Quando analisamos dados procuramos ser eficientes e computar as análises da forma mais clara e rápida possível. Neste tutorial vamos computar as análises de várias formas e olhar para várias estatísticas que não são necessárias no dia a dia mas são úteis para nos ajudarmos a perceber a teoria.

Isto quer dizer que não é importante conseguirem replicar as análises ou perceberem o código R usado neste tutorial mas sim perceberem as conclusões teóricas que podem ser extraídas dos exemplos usados.

13.2 A Equação Fundamental

Quanto estamos a analisar dados temos uma equação fundamental (ver Judd e colaboradores):

\(dados = modelo + erro\)

A nossa missão é reduzir ao máximo o erro sem complexificar muito o modelo (ver Judd e colaboradores).

Os testes estatísticos testam se se justifica aceitarmos a complexidade dum modelo mais complexo com menor erro do que dum mais simples com menos erro (Judd e colaboradores). Estas ideias podem parecer muito estranhas agora mas no fim deste tutorial ficarão claras.

Voltando à equação…

Em modelos com uma só variável dependente (VD), os dados que queremos explicar (ou prever) são a nossa VD, logo:

\(VD = modelo + erro\)

Quando só temos uma VD e uma variável independente (VI) a equação pode ser simplicada para:

\(VD = intercepto + declive * VI + erro\)

Logo o nosso modelo é:

\(intercepto + declive * VI\)

Talvez se lembrem deste mesmo modelo, escrito de outra forma, das aulas de matemática da escola:

\(y = b0 + bx\)

O que é então o intercepto e o declive?

13.3 Interceptos e Declives

Não é que eu não saiba imensa matemática para continuar para escrever todo o tutorial em fórmulas…até parece que só vim para Psicologia porque chumbei no exame nacional de Matemática do 12º…

Mas há pessoas…no geral…que possam não ter gostado muito de matemática na escola e sintam que fica tudo mais compreensível com gráficos…

Por favor, abra a aplicação desta secção numa janela à parte e siga as intruções abaixo.

13.3.1 Interceptos

Clique no separador “Intercepts”. A recta azul é uma recta com declive de zero e com um intercepto (intercept) igual ao valor definido pela barra azul.

Experimente aumentar o valor do intercepto deslizando a barra para a direita.

Experimente ir definindo vários valores positivos para o intercepto, mantendo sempre a barra à direita do zero.

Agora, experimente ir deslocando a barra para a esquerda do zero.

Como pode reparar o intercepto é o valor em que a linha azul intercepta o eixo do y quando x é zero…daí chamar-se intercepto… Por favor, confirme que sente que entendeu bem o que é o intercepto antes de prosseguir, continue a repetir os passos anteriores até sentir que domina o conceito.

13.3.2 Declives

Clique agora no separador dos declives—“Slopes”. Assim como fez anteriormente, desloque a barra para definir o valor do declive (slope). Mais uma vez, comece por arrastar a barra para várias posições à direita do zero. Depois, experimente ver o que acontece quando o declive da recta é negativo, arrastando a barra para a esquerda do zero.

O intercepto da recta azul será sempre zero e o seu declive corresponderá ao valor definido pela barra.

Como pode reparar quanto maior o valor absoluto do declive maior é a inclinação da recta. Quando o valor do declive é positivo os valores de y vão aumentando assim que os valores de x aumentam. Quando o valor é negativo os valores de y vão diminuindo à medida que os de x aumentam.

13.3.3 Interceptos + Declives

Clique agora no separador dos interceptos mais declives—“Intercepts + Slopes”.

Neste separador poderá ver o que acontece quando o intercepto e o declive são ambos diferente de zero.

Repare que o intercepto contínua a ser o valor de y quando x é igual a zero e o declive continua a definir a inclinação da recta azul. A diferença é que quando o intercepto não é zero a linha azul vai interceptar o eixo do y e do x em momentos diferentes.

Vá experimentando vários valores para o intercepto e para o declive até sentir que ganhou uma boa compreensão intuitiva do que acontece à linha azul em função desses valores.

13.3.4 Resumo

13.4 Modelos Sem VIs

13.4.1 Problema de Investigação

Como é que podemos saber se, em média, as pessoas (população) apreciam a música da Taylor Swift?

Recolhemos uma amostra representativa da população com um tamanho adequado.

Usamos uma escala capaz de medir a apreciação pela música da Taylor Swift. O índice de apreciação é a média dos cinco itens da escala. Esse índice vai desde -5 a 5. Quanto mais elevado mais as pessoas gostam da música. Quanto mais baixo mais as pessoas desgostam da música. O centro da escala, zero, corresponderá a ser indiferente quanto à música da Taylor Swift.

13.4.2 Hipóteses

Se a média da população for zero, sabemos que as pessoas são indiferentes à música da Taylor (hipótese nula: H0). Logo, se apreciarem a música a média da população não poderá ser zero (hipótese alternativa: H1).

13.4.3 Dados

Não temos acesso à população toda, só à nossa amostra. Vamos então conhecê-la melhor usando estatística descritiva e gráficos.

Tracejado vermelho indica a média (M = 0.57, SD = 1.1)

13.4.4 Como Inferir a Partir dos Dados?

Como saber se as pessoas (população) gostam da música da Taylor Swift, sendo que só temos acesso à amostra?

Vamos pensar estes problemas através da abordagem de comparação de modelos (Judd et al., 2017)).

13.4.5 Equação Fundamental

\(Dados = Modelo + Erro\)

\(Dados = Love4Taylor\)

Faltam os modelos e os seus erros…

13.4.6 Que Modelos Comparar?

Já vimos que vamos inferir a partir da comparação de modelos. Mais concretamente, vamos comparar o modelo proposto com o modelo nulo

13.4.7 Modelos vs Realidade

13.4.8 Modelos

\(VD = Modelo + Erro\)

\(Modelo = \beta0 + \beta1 VI\)

β0 = intercepto: ponto de partida/previsão quando VI é 0 β1 = declive: inclinação da recta

Erro = Aquilo que o modelo não explica/prevê. A diferença entre o que esperamos e realidade que encontramos

13.4.9 Modelo Nulo

Previsão na ausência do efeito esperado teoricamente. Neste caso, a previsão se as pessoas forem indiferentes à Taylor (i.e., \(Love4Taylor = 0\)). Este é um modelo mais simples sem parâmetros, dado que os parâmetros a existir seriam zero (\(\beta0 = 0 e \beta1 = 0\)).

Adicionemos uma coluna com a previsão (m0) e erro (E_m0) aos nossos dados.

Notem que a previsão é sempre 0, logo os dados são todos erro sem explicação.

13.4.10 Modelo Proposto

Previsão na presença do efeito esperado teoricamente. Neste caso o modelo proposto será a média amostral (\(Modelo = \beta0 = M = 0.57\)). É importante ter em conta que é um modelo mais complexo porque implica calcular a média.

Acrescentemos então a previsão e os erros do modelo proposto.

Notem que o erro deixa de ser igual aos dados, passamos a ter uma proposta para o explicar.

13.4.11 Erros

Notem que o modelo nulo tem mais erro.

13.4.12 Método dos Mínimos Quadrados

Se somarmos diferenças com valores positivos e negativos, podemos acabar com totais próximos de zero que nos levariam a crer erradamente que não há diferenças (e.g., \(erro = -1 + 1 + -3 + 2 + 3 + -2 = 0\)).

Os parâmetros estimados para os modelos que vamos estudar são estimados de forma a minimizar o quadrado dos erros.

13.4.13 Erros Quadrados

Acrescentemos então o quadrado dos erros (E2_m0, E2_m1)

13.4.14 Comparação de Modelos

Mas como podemos comparar os modelos? Vamos considerar a quantidade de erro (somatório dos erros quadrados), em relação à complexidade introduzida (graus de liberdade).

Já temos os erros quadrados

Precisamos dos somar. No modelo nulo a soma dá-nos 74.75. No modelo proposto o somatório dá 58.78. Concluímos assim que o modelo proposto tem menos erro (quadrado).

13.4.15 Graus de Liberdade

13.4.15.1 Fator

Nº parâmetros modelo nulo = 0 Nº parâmetros modelo proposto (np_m1) = 1 (o intercepto)

glfator = diferença no nº de parâmetros; glfator = 1 - 0 = 1

13.4.15.2 Erro

Fórmula: glerro = N - np_m1. No caso: 50 - 1 = 49

A intuição subjacente é que sabendo a soma dos erros e os erros para todas as observações excepto uma, consigo determinar o valor que falta.

13.4.16 Comparação de Modelos

Mas como vamos mesmo comparar os modelos em função dos erros e da complexidade? Já sabemos que precisamos de ponderar a redução do erro em função da complexidade acrescentada, punindo a complexidade e beneficiando a redução do erro. Não sabemos é como fazer essa ponderação.

13.4.17 Estatística de Teste

Genericamente, uma estatística de teste diz-nos o quão distantes estamos do que seria de esperar se não houvesse efeito (hipótese nula). Quanto maior o valor da estatística de teste, mais evidência temos contra a hipótese nula (a ausência de efeito). Há várias estatísticas de teste, dependendo dos modelos. Vamos vamos focar-nos no F de Snedcor e no t de student.

A estatística de teste F é o que nos vai dar a ponderação da redução do erro em função da complexidade dos modelos e do erro que fica por explicar pelo modelo proposto. A estatística de teste F é genérica o suficiente para nos permitir comparar praticamente qualquer par de modelos. Vamos usá-la para comparar o modelo nulo com o proposto.

13.4.18 F

F(1, 49)

A distribuição de F é o quadrado da distribuição t. Dependendo do output o R pode reportar uma outra, mas ficamos já a saber que são equivalentes.

\(F = \frac{MSR}{MSE}\)

\(MSR = \frac{SSE(m0) - SSE(m1)}{gl_{fator}}\) \(MSE = \frac{SSE(m1)}{gl_{erro}}\)

  • MSR = Média da redução do erro (ao passar do nulo para o proposto)
  • MSE = Média do erro do modelo proposto

\[MSR = \frac{SSE(m0) - SSE(m1)}{gl_{fator}} = \frac{74.75 - 58.78}{1} = \frac{15.97}{1} = 15.97\]

\(MSE = \frac{SSE(m1)}{gl_{erro}} = \frac{58.78}{49} = 1.2\)

\[F = \frac{MSR}{MSE} = \frac{15.97}{1.2} = 13.32\]

Quando reportamos o F reportamos os graus de liberdade do fator e do erro.

APA: F(glfator, glerro) = valor do F. Ou seja, F(1, 49) = 13.32.

13.4.19 t

t(49)

Aproximação de Wald:

\(t = \frac{Estimativa}{Erro}\)

\(t = \frac{Parâmetro}{Erro Padrão}\)

\(t = \frac{M}{SE}\)

Parâmetro: β0 = Média (o intercepto)

SE = Erro de estimativa da média da população

\(M = 0.57\)

\(SE = \frac{s'}{\sqrt{N}}\)

\(t = \frac{M}{SE} = \frac{0.57}{\frac{1.1}{\sqrt{50}}} = 3.65\)

Mas o que fazemos com este valor?

13.4.20 Revisitando as Filosofias da Estatística

13.4.20.1 Fisher

Se os dados são muito incompatíveis com a ausência de efeito (H0, com as pessoas serem indiferentes à Taylor), então podemos ter alguma confiança que H0 é falsa (que as pessoas não serão indiferentes)

13.4.20.2 Neyman-Pearson

Decisão / Realidade H0 verdadeira H1 verdadeira
Aceitar H0 Decisão correcta Erro Tipo II
Rejeitar H0 Erro Tipo I Decisão correcta
Decisão / Realidade Indiferentes1 Gostam/Desgostam1
Dizer que são indiferentes1 Correcto Erro Tipo II
Dizer que não são indiferentes1 Erro Tipo I Correcto
  • 1: Quanto à música da Taylor

Fixamos uma probabilidade tolerável de falsos positivos: alfa; α. Podemos também falar de uma probabilidade tolerável de falsos negativos: beta; β. Tradicionalmente usamos α = .05 e β = .20.

13.4.20.3 Expectativa a Longo Prazo

Imaginemos que as pessoas eram indiferentes à Taylor Swift. Nós não sabíamos se as pessoas eram ou não indiferentes, portanto íamos fazer estudos para perceber. Imaginemos então que conduzíamos 100 estudos usando α = .05, para testar se eram mesmo indiferentes (sabendo os leitores que na realidade as pessoas eram indiferentes). Em 95 estudos aceitaríamos H0, dizendo que eram indiferentes, cometendo assim um erro tipo I, um falso positivo.

A área vermelha é bem mais pequena que a azul, a longo prazo espera-se que seja 5% do total e a azul 95%

Já sabemos que fixamos um limite para os falsos positivos que vamos tolerar, α = .05, mas como sabemos que vamos cumprir esse limite?

13.4.21 Distribuições

As distribuições estatísticas têm uma forma definida matematicamente, como acontece com um quadrado, rectângulo, circunferência, etc… Assim sendo, é possível calcular com exactidão a sua área. A parte mais confusa é que sendo distribuições de probabilidades, uma área dessa distribuição corresponde a uma probabilidade.

13.4.22 Estatística de Teste

Já vimos as distribuições de t e F. Essas eram as distribuições de t e F, quando a hipótese nula é verdadeira (quando não há efeito; indiferença à Taylor). Vamos decidir com base numa área dessas distribuições que dá pelo nome de valor-p.

13.4.23 Significância Estatística

Queremos saber a probabilidade de obter o valor da estatística de teste, ou outro mais extremo, assumindo a hipótese nula

Essa probabilidade é o valor-p, se \(p \le \alpha\) rejeitamos H0, se \(p \gt \alpha\) aceitamos H0. Se a probabilidade de obter o nosso t ou F, se as pessoas forem indiferentes à Taylor, for inferior ou igual a alfa (e.g., 0.05), vamos rejeitar H0. Ou seja, vamos dizer que temos evidência estatísticamente significativa de que as pessoas não são indiferentes à música da Taylor

13.4.24 Valor-p

A probabilidade de encontrarmos os resultados que encontrámos, ou valores mais extremos se não houvesse efeito (e os pressupostos do modelo forem verdadeiros, ver Wasserstein & Lazar, 2016)

Qual a probabilidade de obter F(1, 49) = 13.32 (t(49) = 3.65), ou valores mais extremos, se as pessoas fossem neutras quanto à música da Taylor Swift

Perguntemos ao computador… Computador, qual a probabilidade de \(F(1, 49) \ge 13.32\)?

\(p = 0.0006\)

\(\alpha = .05\)

\(p \lt \alpha\)

Rejeitamos H0!

As pessoas não são indiferentes à música da Taylor!

\(p = P(t \ge 3.65) + P(t \le -3.65) = 0\)

\(p = \lt .001\)

\(p \lt 0.1%\)

O nosso p é tão pequeno que nem conseguimos ver a área que representa nas distribuições do t e do F. Mas e se o nosso p fosse, p = .050?

13.4.25 O Que Concluímos?

Com o p que obtivemos de facto (p < .001) sabemos que se as pessoas não gostassem da música da Taylor Swift, seria extremamente improvável obter estes dados, p < 0.1%. Temos então evidência estatísticamente significativa que as pessoas não são indiferentes à música da Taylor Swift, t(49) = 3.65, p < .001.

13.4.26 Secção de Resultados

Ao realizarmos um teste-t de student a uma amostra, concluímos que, em média, as pessoas apreciam a música da Taylor Swift, t(49) = 3.65, p < .001; M = 0.57, DP = 1.1.

13.5 Modelos Com Uma VI Contínua

Como podemos testar hipóteses de relação entre duas variáveis?

Já vimos o que acontece com apenas uma variável “dependente” (VD), até é abusivo o uso do termo porque não tem nada de que depender. Vamos ver agora modelos com uma variável dependente e outra independente.

O modelo proposto vai ser o da regressão linear (já veremos o que isso é).

\(VD = \beta0 + \beta1 VI + Erro\)

13.5.1 Problema de Investigação (1)

Será que o ler este livro prevê a depressão?

13.5.2 Dados (1)

Imaginemos que sabemos quantas páginas deste livro as pessoas leram e que temos uma escala capaz de medir a sintomatologia depressiva (de 0 a 10). Zero será a ausência do construto e dez a presença mais elevada que a escala consegue detectar.

Valores dos participantes em cada escala (N = 40):

Ler este livro

Sintomatologia depressiva

Vejamos o diagrama de dispersão.

13.5.3 Equação Fundamental

\(Dados = Modelo + Erro\)

Quais serão os modelos?

13.5.4 Modelo Proposto

\(Depressão = \beta0 + \beta1 Ler este livro + Erro\)

Porque é que é este o modelo proposto? Porque é este que dá uma equação matemática para o efeito teoricamente esperado.

Vamos usar sempre o computador para calcular os parâmetros a partir dos dados, para nós nos podermos focar na interpretação e podermos ir usando modelos mais complexos com amostras maiores, sem perder horas a fazer contas.

Computador, quais os parâmetros da nossa regressão?

\(\beta0 = 1.93\)

\(\beta1 = 0.07\)

\(Depressão = 1.93 + 0.07 Ler este livro + Erro\)

Adicionemos uma coluna com a previsão do modelo nulo (m1) e o erro deste modelo (E_m1)

13.5.5 Modelo Nulo

  • Previsão na ausência do efeito esperado teoricamente

  • Previsão se ler este livro não tiver qualquer relação com a depressão

  • Para ler este livro (a VI) não ter relação com a depressão (VD) o seu declive terá de ser zero

  • Mas o valor da depressão não tem de ser zero (pode haver depressão)

  • Queremos então um modelo que preveja a depressão, mas não use o valor da VI para isso

  • O modelo nulo será então um modelo com um intercepto, mas sem declive

  • Esse intercepto será a média amostral da VD

Modelo nulo = 4.05

Depressão = 4.05 + Erro

  • Adicionemos uma coluna com a previsão do modelo nulo (m0) e o erro deste modelo (E_m0)

13.5.6 Erros

13.5.7 Erros Quadrados

Acrescentemos então colunas com o quadrado dos erros de cada modelo (E2_m0, E2_m1).

13.5.8 Comparação de Modelos

  • Como podemos então comparar os modelos?

Temos os erros quadrados

  • Falta somá-los:

  • Soma no modelo nulo: 197.5

  • Soma no modelo proposto: 176.91

13.5.9 Graus de Liberdade

13.5.9.1 Fator

Nº de parâmetros: - modelo nulo = 1 (β0) + modelo proposto (np_m1) = 2 (β0 e β1)

glfator = 2 - 1 = 1

13.5.9.2 Erro

glerro = N - np_m1

No caso: glerro = N - 2 = 40 - 2 = 38

13.5.10 F

\(F = \frac{MSR}{MSE}\)

\(MSR = \frac{SSE(m0) - SSE(m1)}{gl_{fator}}\) \(MSE = \frac{SSE(m1)}{gl_{erro}}\)

  • MSR = Média da redução do erro (ao passar do nulo para o proposto)
  • MSE = Média do erro do modelo proposto

\[MSR = \frac{SSE(m0) - SSE(m1)}{gl_{fator}} = \frac{197.5 - 176.91}{1} = \frac{20.6}{1} = 20.6\]

\(MSE = \frac{SSE(m1)}{gl_{erro}} = \frac{176.91}{38} = 4.66\)

\[F = \frac{MSR}{MSE} = \frac{20.6}{4.66} = 4.42\]

13.5.11 Valor-p

  • Vamos deixar o computador encontrar o valor-p por nós

  • p = .042

  • Estilo APA: F(1, 39) = 4.42, p = .042

13.5.12 O Que Concluir?

  • Prever a depressão com base em quanto se leu deste livro reduz significativamente o erro (p < .05)

  • Ou seja, ler este livro poderá causar depressão

13.5.13 t de Student

Formula genérica:

\(t = \frac{Parâmetro}{Erro Padrão}\)

Podemos então calcular o t quer para o intercepto quer para o declive do modelo proposto

13.5.13.1 Intercepto

β0 = 1.93

\(SE_{intercepto} = 1.06\)

\(t_{intercepto} = \frac{1.93}{1.06} = 1.81\)

13.5.13.2 Declive

\(\beta0 = 0.07\)

\(SE_{declive} = 0.03\)

\(t_{declive} = \frac{0.07}{0.03} = 2.1\)

13.5.14 Valor-p

O R calcula os valores-p por nós.

Parameter Coefficient SE CI CI_low CI_high t df_error p
(Intercept) 1.93 1.06 0.95 -0.22 4.09 1.8 38 0.08
ReadingMOC 0.07 0.03 0.95 0.00 0.13 2.1 38 0.04

13.5.15 O Que Concluir?

  • Em termos de teste de hipóteses, concluímos que o declive da variável ler este livro é diferente de zero (rejeitamos H0)

  • Ou seja, será que ler este livro prediz a depressão

  • Concluímos também que o intercepto não é estatisticamente diferente de zero

  • O intercepto neste caso é a previsão para a depressão quando o ler este livro é zero

  • Ou seja, não há evidências de que na população, quando o ler este livro é zero a depressão seja diferente de zero

  • Na regressão linear normalmente estamos interessados em testar se o declive difere de zero (se a VI tem efeito)

  • Acabamos por testar também se o intercepto difere de zero, mas não costumamos ter interesse nesse teste (no t/F do intercepto)

13.6 Modelos Com Uma VI Dictómica

  • E se a nossa hipótese disser respeito a uma VI dictómica?

13.6.1 Problema de Investigação (2)

  • Será que os benfiquistas vão mais vezes ao estádio que os sportinguistas?

13.6.2 Dados (2)

13.6.3 O Que Fazer aos Clubes?

  • A variável clube é dictómica, mas nós queremos usar a regressão linear…

  • Temos de encontrar uma forma de a transformar em “quantitativa”…

  • Podemos atribuir zero a um clube e um a outro (codificação dummy)

  • Mas como queremos contrastar os grupos, vamos usar outra codificação

  • Atribui -1 a um grupo e 1 a outro

13.6.4 Codificação Soma = 0

Adicionemos então esta codificação/contrastes

13.6.5 Modelo Nulo

O modelo nulo continua a ser a ausência de efeito. Neste caso, ambos os grupos terem a mesma média de bilhetes comprados

\(\beta0 = M\)

\(\beta0 = 7.58\)

13.6.5.1 Modelo Proposto

O modelo proposto será haver diferença entre as médias dos grupos. Ou seja, os Benfiquistas e os Sportinguistas irem ao estádio com frequências diferentes.

\(Modelo = \beta0 + \beta1 x Clube_C\)

Modelo = Média das médias + 2 x diferença entre médias x Club_C

Em modelos com variáveis categóricas os declives são mais difíceis de interpretar e o nosso foco está mais em perceber se as diferenças são significativas e quais as médias estimadas para cada grupo.

Acrescentemos as previsões, os erros e seus quadrados para cada modelo

13.6.6 Graus de Liberdade e Soma de Erros

  • glfator = 1; glerro = n - np_m1 = 80 - 2 = 78

  • As somas dos quadrados dos erros:

    • Modelo nulo: 299.55
    • Modelo proposto: 297.75
    • Muito semelhantes desta vez…
  • O computador calcula tudo o resto para pouparmos tempo

13.6.7 Tabela da ANOVA

Sum Sq Df F value Pr(>F)
(Intercept) 4590.4 1 1202.54 0.00
Clube 1.8 1 0.47 0.49
Residuals 297.8 78 NA NA

13.6.8 Parâmetros

Parameter Coefficient SE t p
(Intercept) 7.575 0.21844 34.67760 0.00000
Clube1 0.150 0.21844 0.68669 0.49432

13.6.9 O Que Concluir?

Não há vantagem em usar o clube para prever o número de bilhetes comprados (p = .494; p > .05)

Não há diferenças significativas entre a média de bilhetes comprados entre adeptos do Sporting e do Benfica, t(78) < 1; MBenfica = 7.73, SE = 0.31; MSporting = 7.43, SE = 0.31.

13.7 Modelos Com Múltiplas VIs Contínuas

13.7.1 Questão de Partida

  • Como testar hipóteses de relação entre uma VD e várias VIs?

13.7.2 Ponto de Situação

  • Aula passada: Uma VD e uma VI

  • Hoje vamos generalizar a regressão linear para várias VIs

  • Também veremos o que fazer com uma VI categórica com mais de dois níveis

13.7.3 Modelo Proposto

\(VD = \beta0 + \beta1 x VI1 + \beta2 x VI2 + Erro\) + Para x VIs contínuas temos x declives

  • Abstracto, precisamos dum problema de investigação…

13.7.4 Problema de Investigação (1)

  • Será que o número de filhos e o conflito familiar prevêem o bem estar?

13.7.5 Dados (1)

  • Imaginemos que temos uma escala capaz de medir o bem-estar (de 0 a 100)

  • Temos outra capaz de medir o conflito familiar (de 0 a 30)

  • Para o número de filhos não precisamos de escala…

    • Basta pedir aos participantes que indiquem

Valores dos participantes em cada escala (N = 60)

Variável dependente = Bem-estar

Bem-estar em função do número de filhos

Bem-estar em função do conflito familiar

13.7.6 Modelo Proposto

\(Bem-estar = \beta0 + \beta1 NFilhos + \beta2 Conflito + Erro\)

  • Esta é a proposta teórica logo é o modelo proposto

  • A ordem das variáveis não interessa

    • Tentamos ser consistentes para não confundir o leitor

Já vimos que vamos confiar que o computador sabe determinar os valores de cada β.

  • Computador, quais os parâmetros da nossa regressão?
    • Computando…
  • Intercepto/β0 = 48.76
  • NFilhos/β1 = 5.35
  • Conflito/β2 = -2.38

\[BemEstar = 48.76 + 5.35 NFilhos + \\ -2.38 Conflito + Erro\]

  • A previsão da VD passa a ter em conta tanto o número de filhos como o conflito familiar

  • O declive de conflito familiar é negativo, quanto mais conflito menos bem-estar

  • Tentem resolver a equação para:

    • NFilhos = 0, NFilhos = 1, NFilhos = 2, com Conflito sempre = 0
    • Conflito = 0, 1, 2, com NFilhos sempre = 0
    • NFilhos = 1 e Conflito = 1; NFilhos = 2 e ApoioFamiliar = 2
  • Deixamos o computador resolver a equação para os valores de cada participante

  • Adicionamos a previsão ao modelo, bem como os erros

13.7.7 Dados

13.7.8 Modelo Nulo

  • Previsão na ausência do efeito esperado teoricamente

  • Qual o modelo nulo neste caso?

    • Depende…
  • Se quisermos testar se usar as duas variáveis melhoram a previsão, comparado com não usar nenhuma delas

  • Modelo nulo: β0 = Média da VD = 34.94

  • Se quisermos testar se adicionar o nº filhos a um modelo que já tenha em conta o conflito familiar, reduz o erro

  • Modelo nulo: β0 + β1 x Conflito

  • Se quisermos testar se adicionar o conflito familiar a um modelo que já tenha em conta o número de filhos, reduz o erro

  • Modelo nulo: β0 + β1 x NFilhos

13.7.9 Comparaçãoões de Modelos

  • Na prática o R faz estas três comparações

  • Uma dá-nos um F global para o modelo

  • Outra dá-nos um F/t para cada VI/declive

  • Cada comparação testa hipóteses diferentes

  1. F global: modelo como um todo reduz o erro?

  2. F NFilhos: efeito da VI1/NFilhos controlando para VI2/Conflito?

  3. F Conflito: efeito da VI2/Conflito controlando para VI1/NFilhos?

  • As hipóteses 2 e 3 também são testadas na tabela com os ts dos declives

13.7.10 F Global

  • Nº parâmetros:

    • proposto = β0 + β1 + β2 = 3
    • nulo = β0
  • Graus de liberdade: \(gl_{fator} = 3 - 1 = 2; gl_~erro~ = N - 3 = 60 - 3 = 57\)

  • Soma quadrados:

    • Modelo proposto: 52294.02
    • Modelo nulo (só β0): 12834.23

\[MSR = \frac{SSE(m0) - SSE(m1)}{gl_{fator}} = \frac{12834.23 - 52294.02}{2} = \frac{-39459.79}{2} = -19729.9\]

\[MSE = \frac{SSE(m1)}{gl_{erro}} = \frac{52294.02}{57} = 917.44\]

\[F = -21.51\]

\[P(F \ge 87.63) \lt .001 ; p \lt .001\]

13.7.11 O Que Concluir?

  • O modelo como um todo reduz bastante o erro (quadrado), mesmo sendo o modelo mais complexo que já estudámos

  • Usar o número de filhos que uma pessoa tem e o conflito familiar que ela experiência ajuda-nos a prever o seu bem-estar!

  • Mas será que preciso de ambas as variáveis ou só uma delas é que está a reduzir o erro significativamente?

13.7.12 Tabela da ANOVA

Sum Sq Df F value Pr(>F)
(Intercept) 33422 1 148 0
NFilhos 7464 1 33 0
Conflito 28077 1 125 0
Residuals 12834 57 NA NA
  • Três comparações de modelos (β0, β1, β2)
    • Duas de interesse (β1/NFilhos e β2/Conflito)
  • Linha NFilhos: Adicionar NFilhos reduz o erro?
    • O efeito de NFilhos é significativo?
  • Linha : Adicionar Conflito reduz o erro?
    • O efeito de Conflito é significativo?

13.7.13 Graus de Liberdade do Fator

  • Cada linha compara o nosso modelo proposto com um modelo nulo diferente

  • O modelo proposto tem três parâmetros (β0, β1, β2)

  • Os modelos nulos têm dois parâmetros (β0 e um declive)

  • Logo: glfator = 3 - 2 = 1

    • Mas no F global, glfator 3 - 1 = 2.

13.7.14 Tabela da ANOVA

Sum Sq Df F value Pr(>F)
(Intercept) 33422 1 148 0
NFilhos 7464 1 33 0
Conflito 28077 1 125 0
Residuals 12834 57 NA NA

13.7.15 O Que Concluir?

  • Ambas as variáveis reduzem significativamente o erro, justificando-se a complexidade de incluir ambas no modelo

  • O bem-estar duma pessoa parece depender tanto do número de filhos que tem como do conflito familiar que experiência

13.7.16 Parâmetros

Dão-nos a mesma informação, mas conseguimos ver a estimativa dos declives

Parameter Coefficient SE t df_error p
(Intercept) 48.8 4.00 12.2 57 0
NFilhos 5.3 0.93 5.8 57 0
Conflito -2.4 0.21 -11.2 57 0

13.7.17 O Que Concluir?

  • Prevê-se que ter mais um filho aumente o bem-estar em β1 = 5.35 pontos

  • Por cada ponto adicional na escala de conflito familiar prevê-se uma diminuição de β2 = -2.38 pontos na escala de bem-estar

13.7.18 Resumo

  • Os resultados duma regressão linear múltipla mostram que o nosso modelo é significativo, F(2, 57) = 87.63, p < .001. O número de filhos teve um efeito significativo, β = 5.35; t(57) = 5.76, p < .001, sendo que pessoas com mais filhos têm maior bem estar. A variável conflito familiar também teve efeito significativo, levando a menor bem estar, β = -2.38; t(57) = -11.17, p < .001.

13.8 Modelos Com Uma VI Categórica Com Mais de Dois Níveis

  • E se a nossa hipótese disser respeito a uma VI categórica com vários níveis?

  • A VD continua a ser quantitativa

13.8.1 Problema de Investigação (2)

  • Será que alunos de diferentes áreas de estudos têm diferentes capacidades de leitura?

13.8.2 Dados (2)

  • Imaginemos que recolhemos dados de estudantes de ciências, artes e letras

  • Avaliámos a capacidade de leitura numa escala de 0 a 10.

13.8.3 Como “Quantificar” a VI?

13.8.4 Dummy

  • Exemplo: 1 a um grupo (Letras), 0 a outros dois (Artes, Ciências)

  • Repetimos k - 1 vezes onde k = níveis da VI

  • Não é a que queremos usar

13.8.5 Soma = 0

  • Exemplo: atribuímos 1 a um grupo (Letras), -1 a outro (Artes), 0 ao restante (Ciências)

  • Repetimos k - 1 vezes onde k = níveis da VI

    • k = 3 (Letras, Artes, Ciências), repetimos 3 - 1 = 2
  • É a queremos usar

13.8.6 Como “Quantificar” a VI?

  • Não precisamos de codificar manualmente, o computador faz isso

  • Independentemente do esquema, temos de repetir o processo k - 1 vezes, onde k são os níveis da VI

  • Só criando k - 1 variáveis é que vamos conseguimos avergiuar se, pelo menos um grupo, difere dos outros

  • Compreender o que cada declive testa em cada codificação não é muito fácil (ver Protopapas, 2015)

  • O importante é perceber que testamos se pelo menos um grupo difere dos outros

13.8.7 Modelo Proposto

  • Se temos k - 1, contrastes (C1..Cx), temos k-1 declives
    • No caso, nº declives = 3 - 1 = 2

\(Modelo = \beta0 + \beta1 Curso_{C1} + \beta2 Curso_{C2}\)

  • Mas conceptualmente só temos uma VI de interesse

13.8.8 Modelo Nulo

  • Definimos o modelo nulo como tendo apenas um intercepto

  • \(Modelo = \beta0\)

13.8.9 Comparação de Modelos

  • O modelo proposto tem todos os declives necessários para vermos se pelo menos um dos grupos difere do outro

  • O modelo nulo não tem nenhum desses declives

  • O F dessa comparação diz-nos se há evidências de que pelo menos um dos grupos difere do outro

  • Ou seja, queremos saber se pelo menos um dos grupos/cursos difere significativamente (na populção) dos restantes

13.8.10 Testes Omnibus

  • A comparação de modelos feita assim, só nos diz se pelo menos um grupo difere dos outros

  • Evitamos assim fazer muitas comparações e inflaccionar os falsos positivos (erro tipo I)

    • Mas não nos diz quais os pares que diferem entre si
  • Chamamos a esta comparação um teste omnibus (do latim todos)

13.8.11 Testes Post-Hoc/Comparações Múltiplas

  • Se virmos que pelo menos um grupo difere dos outros podemos fazer vários testes, tantos quantos os pares possíveis, para saber quais os pares com diferenças significativas

  • Devemos então corrigir/ajustar os valores-p para não aumentar os falsos positivos

  • Há várias correcções possíveis que o computador pode fazer—eu recomendo a de Tukey

13.8.12 Resultados

  • Para poupar tempo vamos deixar o computador calcular todos os parâmetros, previsões e erros quadrados dos modelos

  • Mas vamos rever os graus de liberdade

13.8.13 Graus de Liberdade

13.8.13.1 Fator

Nº de parâmetros:

  • Proposto: β0 + (k - 1) βs; 1 + (3-1) = 3

  • Nulo: β0; 1

  • \(gl_{fator} = 3 - 1 = 2\)

13.8.13.2 Erro

  • Parâmetros proposto (npm1):
    • 1 + (k - 1) = 3
  • \(gl_{erro} = N - np_{m1} = 90 - 3 = 87\)

13.8.14 Resultados

Vamos olhar para:

  • O F do efeito omnibus - Tabela da ANOVA

  • As médias marginais estimadas pelo modelo para cada grupo

  • As comparações múltiplas com correcção de Tukey

  • Não nos vamos preocupar com os ts dos declives do modelo proposto, dado que os declives não são muito fáceis de interpretar

13.8.15 Tabela da ANOVA

Sum Sq Df F value Pr(>F)
(Intercept) 2890 1 694.9 0
Curso 48 2 5.8 0
Residuals 362 87 NA NA

13.8.16 O Que Concluir?

  • Há vantagens em usar o Curso para prever a Leitura, F(2, 87) = 5.8, p = .004

  • Ou seja, há diferenças significativas entre pelo menos um dos grupos, F(2, 87) = 5.8, p = .004.

  • Mas quais os grupos que diferem? E quais os que lêm melhor?

13.8.17 Médias Mariginais Estimadas

  • Podemos calcular a média e desvio padrão da leitura em cada grupo

  • Mas o R é capaz de estimar médias a partir do modelo

  • Prefiro a segunda opção, mas ambas são válidas

Curso emmean SE df lower.CL upper.CL
Artes 5.2 0.37 87 4.5 5.9
Ciencias 5.1 0.37 87 4.4 5.8
Letras 6.7 0.37 87 6.0 7.4
  • Semelhante ao que vimos no boxplot

13.8.18 Comparações Múltiplas

contrast estimate SE df t.ratio p.value
Artes - Ciencias 0.1 0.53 87 0.19 .98
Artes - Letras -1.5 0.53 87 -2.85 .015
Ciencias - Letras -1.6 0.53 87 -3.04 .009
  • ps ajustados com correcção de Tukey

13.8.19 O Que Concluir?

  • As pessoas de áreas diferentes, diferem significativamente entre si na capacidade de leitura, F(2, 113) = 5.8, p = .004. Mais concretamente, quem cursa em letras (M = 6.7, SE = 0.37) lê melhor que quem cursa em artes (M = 5.2, SE = 0.37) e em ciências (M = 5.1, SE = 0.37), t(87)letras-artes = 2.85, p = .015; t(87)letras-ciências = 3.04, p = .009. No entanto, não há diferenças na capacidade de leitura entre cursos de artes e de ciências, t < 1.

13.9 Exercícios

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

13.9.1 Exercício 1

  • Sem consultar os apontamentos ou as soluções, complete a tabela.

Nota: VIF denota uma VI factorial/categórica, VIN denota uma VI numérica. X em VIX (e.g., VI1) denota mais que uma variável no modelo.

Nome Fórmula
t-test de uma amostra
Regressão/Correlação VD ~ VI
t-test*/One-Way ANOVA
VD ~ VIN1 * VIN2 * VINx
ANCOVA
Moderação
VD ~ VI1F * VI2F

(Tabela adaptada de Barr; 2020, capítulo 6; ver Lindeløv, 2019; Judd et al., 2017); para modelos mistos ver Singman & Kellen, 2019).

  • Altere o esquema de contrastes para variáveis factoriais para os contrastes cuja soma dá zero.

    • Dica: Procure por “contr.sum” nos tutoriais ou motor de busca.
  • Importe a base de dados ./data/demo_ds.csv e guarde-a numa variável.

  • Usando a função lm() compute pelo menos três dos modelos da tabela usando variáveis dessa base de dados.

  • Usando a função Anova() do pacote car gere a tabela da Anova com somas dos quadrados tipo III para cada modelo que computou.

    • Dica: Use install.packages("car") para instalar o pacote, se não o tiver instalado, e library(car) para o colocar no seu ambiente.
  • Indique qual a comparação de modelos correspondente a cada linha de cada tabela da ANOVA que gerou.

13.9.1.1 Soluções

Clique para ver as soluções
Nome Fórmula
t-test de uma amostra VD ~ 1
Regressão/Correlação VD ~ VIN
t-test*/One-Way ANOVA VD ~ VIF
Regressão múltipla VD ~ VIN1 * VIN2 * VINx
ANCOVA VD ~ VIF + VIN
Moderação VD ~ VIN * VIF
ANOVA 2X2 VD ~ VIF1 * VIF2

(Tabela adaptada de Barr; 2020, capítulo 6; ver Lindeløv, 2019; Judd et al., 2017); para modelos mistos ver Singman & Kellen, 2019).

13.9.2 Exercício 2

  • Se não copiou a tabela do exercício 1 para uma folha de papel, faça-o agora.

  • Nessa folha de papel escreva cinco vezes a seguinte frase: “São tudo regressões lineares!”

  • Nessa folha de papel escreva cinco vezes esta outra frase: “Só vou usar esta tabela para traduzir o que outras pessoas me peçam ou para lhes explicar o que fiz. Para mim são tudo regressões lineares”.

13.9.2.1 Soluções

Clique para ver as soluções
Nome Fórmula
t-test de uma amostra VD ~ 1
Regressão/Correlação VD ~ VIN
t-test*/One-Way ANOVA VD ~ VIF
Regressão múltipla VD ~ VIN1 * VIN2 * VINx
ANCOVA VD ~ VIF + VIN
Moderação VD ~ VIN * VIF
ANOVA 2X2 VD ~ VIF1 * VIF2

(Tabela adaptada de Barr; 2020, capítulo 6; ver Lindeløv, 2019; Judd et al., 2017); para modelos mistos ver Singman & Kellen, 2019).

São tudo regressões lineares! São tudo regressões lineares! São tudo regressões lineares! São tudo regressões lineares! São tudo regressões lineares!

Só vou usar esta tabela para traduzir o que outras pessoas me peçam ou para lhes explicar o que fiz. Para mim são tudo regressões lineares. Só vou usar esta tabela para traduzir o que outras pessoas me peçam ou para lhes explicar o que fiz. Para mim são tudo regressões lineares. Só vou usar esta tabela para traduzir o que outras pessoas me peçam ou para lhes explicar o que fiz. Para mim são tudo regressões lineares. Só vou usar esta tabela para traduzir o que outras pessoas me peçam ou para lhes explicar o que fiz. Para mim são tudo regressões lineares. Só vou usar esta tabela para traduzir o que outras pessoas me peçam ou para lhes explicar o que fiz. Para mim são tudo regressões lineares.

13.9.3 Exercício 3

  • Caso não o tenha instalado, instale o pacote afex.

  • Usando a função aov_car() compute três modelos, com pelo menos uma VI, usando as variáveis da base de dados.

  • Compute os mesmos três modelos usando a função aov_ez().

  • Escreva cinco vezes a seguinte frase: “Só vou usar a função aov_ez() para fazer o desmame do SPSS, depois nunca mais a vou usar.”

  • Compute os mesmos três modelos usando a função aov_4().

13.9.3.1 Soluções

Clique para ver as soluções

14 Pressupostos

Os testes-t, ANOVAs, ANCOVAs, regressões lineares, correlações, são todos modelos lineares, então partilham os mesmos pressupostos. Mais concretamente, todos partilham o pressuposto de que os resíduos (os erros) do modelo seguem a distribuição normal—pressuposto da normalidade. Para os testes com uma ou mais variáveis independentes categóricas, aplica-se ainda o pressuposto de que a variância da variável dependente é semelhante em todos os níveis da VI—pressuposto da homocedasticidade. Para os testes com medidas repetidas, aplica-se o pressuposto de que a variância em cada uma das medidas repetidas é semelhante—esfericidade (Wikipedia, 2021) e não se aplica o da homocedasticidade.

Os livros de estatística tradicionais dizem que devemos testar se os nossos dados cumprem os pressupostos e usar estatística não–paramétrica caso não cumprem. Contudo, uma abordagem mais sensata pode ser usar logo à partida testes estatísticos robustos a violação de pressupostos (Field & Wilcox, 2017). Se os testes robustos não diferirem dos testes convencionais podemos reportar os resultados dos testes convencionais só para manter a secção de resultados mais acessível para os leitores. Se os testes diferirem no padrão de resultados podemos citar (Field & Wilcox, 2017) para explicar que essa diferença indica que os pressupostos foram violados e que por isso reportamos os resultados dos testes robustos.

14.1 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

14.2 Exercícios

Ainda por escrever…

15 AVISO

Esta parte está INCOMPLETA, o trabalho nesta parte do livro continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que esta parte já tenha sido actualizada, deve considerar a informação apresentada como INCOMPLETA.!

16 Estatística Descriptiva no R

17 Modelos lineares no R

17.1 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

17.2 One Formula to Rule them All

O nosso trabalho de geração e teste de hipóteses estatísticas, como explicado no capítulo teórico, pode ser entendido como a criação, comparação e teste de modelos. Em termos muito gerais vimos que na abordagem de comparação de modelos (Judd et al., 2017) temos uma equação fundamental: \(Dados = Modelo + Erro\). Se nos focarmos no modelo a equação pode ser reescrita como: \(Modelo = Dados - Erro\). O nosso objectivo será reduzir o erro, portanto queremos que o nosso modelo seja o mais parecido possível com os dados. Ao mesmo tempo, temos uma forte preferência por modelos simples, só aceitando complexidade adicional quando ela traz uma grande redução do erro. Na prática, nos modelos lineares gerais (inclusive nos modelos mistos ou multinível), o termo dos \(Dados\) na equação corresponde à nossa variável dependente. Por sua vez, o termo do \(Modelo\) acaba por corresponder a um cálculo efectuado com base na/s variável/is indedependnete/s. Sabendo que queremos reduzir o erro, acrescentando o mínimo de complexidade, podemos redefinir o nosso objectivo

Os testes estatísticos comparam o nosso modelo com outro mais simples para testar se a complexidade adicional do nosso modelo é justificada pela redução do erro que ele consegue em relação ao modelo mais simples (Judd e colabodores). Quando a complexidade adicional de incluir um dado preditor/VI no modelo é justificada pelo erro que ele reduz, dizemos que o efeito desse preditor/VI é significativo (Judd e colaboradores).

17.3 One Formula Syntax to Rule Them All

No R podemos usar a mesma sintaxe para escrever todos os modelos lineares possíveis (e.g, testes-t, ANOVAs, ANCOVAs, regressões lineares, correlações). Na minha opinião isto é das maiores vantagens que o R proporciona em relação a outros softwares. Isto também torna o R a ferramenta ideal para ensinar e aprender a abordagem de comparação de modelos (ver Judd et al., 2017) que apresentámos na parte teórica do livro.

17.3.1 Sintaxe das Fórmulas

17.3.1.1 Traços Gerais

Se usarmos VD como abreviatura de variável dependente e VI como abreviatura de variável independente, em traços gerais um modelo com uma variável e dependente e uma independente escreve-se assim:

m <- VD ~ VI

Podemos ler este código dizendo: “o modelo m é um objecto da class()e formula que descreve a variável dependente VD em função (~) da variável independente VI”. Na prática, quando escrevermos modelos para analisar bases de dados mais concretas os termos VD e VI serão substituídos pelos nomes das colunas que contêm a variável dependente e a variável independente. Por exemplo:

dados <- read.csv("../data/demo_ds.csv")

# Espreitamos os dados
head(ds)
    ID  Curso Leitura
1 pp01 Letras       9
2 pp02 Letras       6
3 pp03 Letras       7
4 pp04 Letras       9
5 pp05 Letras      10
6 pp06 Letras       7
# Sabendo o nome das colunas da VD e da VI, reescrevemos o modelo.
m <- rts ~ cond

17.3.1.2 Intercepto

Podemos ter um modelo só com uma variável dependente, sem variável independente e com o intercepto fixo no zero. Na prática é algo que raramente fazemos, mas corresponde ao modelo 0 do teste-t a uma amostra (ver parte teórica).

m <- VD ~ 0

Podemos também deixar explícito que queremos um modelo com um intercepto, acrescentando o número 1 no início do modelo, por exemplo:

# Modelo com apenas um intercepto (teste-t a uma amostra).
m <- VD ~ 1

Notem que isto não quer dizer que estamos a pedir para fixar o intercepto mesmo no 1, isto é apenas para o R saber que queremos que o modelo possa ter um intercepto que não seja 0.

17.3.1.3 Declive/Efeito Principal

m <- VD ~ VI

Reparem também que este 1 que acrescentámos está implícito por defeito em modelos com uma ou mais variáveis independentes. Ou seja, mesmo que não escrevamos o 1 o R vai permitir que o modelo tenha um intercepto diferente de 0. Aliás, estando este 1 implícito, se quisermos fixar o intercepto do modelo em 0, é que temos que acrescentar o 0 no início do modelo.

m <- VD ~ VI
# Igual a:
m <- VD ~ 1 + VI
# Ambos são diferentes de:
m <- VD ~ 0 + VI

17.3.1.4 Vários Declives/Efeitos Principais

m <- VD ~ VI1 + VI2
m <- VD ~ VI1 + VI2 + VI3 + VIX

17.3.1.5 Juntando as Interacções

m <- VD ~ VI1 + VI2 + VI1:VI2

# Igual a:
m <- VD ~ VI1 * VI2
# Efeitos principais + Interacções duplas + triplas + quádrupla
m <- VD ~ VI1 + VI2 + VI3 + VIX +
     VI1:VI2 + VI1:VI3 + VI1:VIX + VI2:VI3 + VI2:VIX + VI3:VIX +
     VI1:VI2:VI3 + VI1:VI2:VIX + VI1:VI3:VIX + VI2:VI3:VIX +
     VI1:VI2:VI3:VIX
# Igual a:
m <- VD ~ VI1 * VI2 * VI3 * VIX
# Um pouco mais rápido de escrever (e ler) diria eu...

17.4 Tutorial

# Usando uma interface gráfica file.choose() para encontrar o ficheiro.
#dados <- read.csv(file.choose()) 

# Especificando o caminho para o ficheiro e o nome do ficheiro.
dados <- read.csv("../data/demo_ds.csv")

Variável Dependente (VD) em FUNÇÃO (~) da Variável Independente (VI):

# modelo <- VD ~ VI 

Exemplo: tempos de resposta (rts) em FUNÇÃO (~) do género (gender).

m <- rts ~ gender

Variável dependente em função dos efeitos principais das VIs:

# modelo <- VD ~ VI1 + VI2 + VIx

Exemplo: rts (rts) em função (~) do efeito principal do género (gender) + efeito principal da condição experimental (cond).

m <- rts ~ gender + cond

VD em função dos efeito principais das VIs + efeitos de interacção entre as VIs

# modelo <- VD ~ VI1 + VI2 + VIX + VI1:VI2 + VI1:VIX + VI2:VIX

Exemplo: rts (rts) em função (~) dos efeitos principais do género (gender) e condição (cond) + efeito de interacção entre género e condição (gender:cond).

m <- rts ~ gender + cond + gender:cond

Na sintaxe de fórmulas do R há uma forma resumida de incluir todos os efeitos principais das VIs e todas as interacções possíveis entre as VIs.

VD em função dos efeito principais das VIs + efeitos de interacção entre as VIs

# modelo <- VD ~ VI1 * VI2 * VIx

Exemplo; rts (rts) em função dos efeitos principais do género e condição + efeito de interacção entre género e condição.(genero * cond).

m <- rts ~ gender * cond

17.5 One Function to Rule Them All

Todos os modelos lineares podem ser computados como modelos lineares (lm) no R (ver teoria.html/pdf) .

# fit <- lm(modelo, dados)
f <- lm(m, dados)

# Obter os resultados.
summary(f)

17.5.1 Cheat Sheet (sem medidas repetidas)

Modelos com SÓ UMA VD contínua.

Adapt. Barr (2020, capítulo 6; ver Singman & Kellen, 2019; ver também Judd e colaborades):

Nome Fórmula
t-test de uma amostra VD ~ 1
Regressão/Correlação VD ~ VI
t-test*/One-Way ANOVA VD ~ VI1
Regressão múltipla VD ~ VI1 * VI2 * VIx
ANCOVA VD ~ VI1 * VI2 * VIx_cat + COV
Moderação VD ~ VI * VI
ANOVA 2X2 VD ~ VI1 * VI2

17.6 Cheat Sheet (Medidas Repetidas: MR)

Modelos com SÓ UMA VD contínua e MEDIDAS REPETIDAS.

Adapt. Barr (2020, capítulo 6; ver Singman & Kellen, 2019; ver também Judd e colaborades):

Nome Fórmula
t-test*/One-Way ANOVA VD ~ VIW + Error(pp_number/ VIW)
ANOVA 2X2 Mista VD ~ VIW * VIB + Error(pp_number/ VIW)
ANOVA 2X2 MR VD ~ VI1W * VI2W + Error(pp_number/ (VI1W * VI2W))

Legenda: VIW = Variável Independente Within, VIB = Variável Independente Between, pp_number = Variável com o número ou outro identificador dos sujeitos.

17.7 Cheat Sheet (MR - modelos mistos)

Modelos com SÓ UMA VD contínua e MEDIDAS REPETIDAS com abordagem de modelos mistos.

Adapt. Barr (2020, capítulo 6; ver Singman & Kellen, 2019; ver também Judd e colaborades):

Nome Fórmula
t-test MR VD ~ VI1 + (1 | pp_number)
t-test*/One-Way ANOVA MR VD ~ VI1 + (1 | pp_number)
Regressão/Correlação VD ~ VI1 + (1 | pp_number)
Regressão múltipla VD ~ VI1 * VI2 * VIx
ANOVA Mista VD ~ VIW * VIB + (VIW | pp_number)
ANOVA 2X2 VD ~ VIW * VI2W + (VIW * VIW | pp_number)
ANOVA 2X2 + efeitos item VD ~ VIW * VIW + (VIW * VIW | pp_number) + (VIW * VIW | item)

Legenda: VIW = Variável Independente Within, VIB = Variável Independente Between, pp_number = Variável com o número/identificador dos participantes, item = Variável com o nome/indentificador dos itens.

17.8 Nuances

  1. Para dados com temos medidas intra-participantes temos de especificar um termo de erro.

  2. O R implica com missings em ANOVAs com medidas intra-pps.

  3. O R usa outro tipo de contrastes que não os cuja soma dá 0.

  4. O R usa soma de quadros Tipo I e não Tipo III (os do SPSS). Esta questão só é relevante quando os grupos não têm todos o mesmo tamanho mas isso é frequente nos nossos estudos.

  5. O R não reporta automaticamente dimensões dos efeitos.

Dado estas nuances, na prática para o tipo de análises que fazemos pode ser mais útil usarmos a função aov() (que na realidade invoca a função lm()).

Mais cómodo ainda será a função afex::aov_car() (também invoca essas funções mas tem defaults mais ajustados à análise de dados experimentais).

Seguem-se três opções de como usar alguns pacotes para nos ajudar a resolver estes problemas. Assim podemos continuar a usar a mesma sintaxe para escrever todos os nossos modelos.

Spoiler alert: a Opção 3 é a mais simples (menos linhas de código).

17.9 Soluções

Para ficarem claras as diferenças entre cada opção comecemos por escrever a parte do código que será comum a todas.

# Contrastes correctos para soma de quadrados tipo II/III.
options(contrasts = c("contr.sum", "contr.poly"))

# dados <- read.csv(file.choose()) # com interface gráfica
dados <- read.csv("../data/demo_ds.csv")

17.9.1 Opção 1

Podemos usar a função Anova() do pacote car (Fox & Weisberg, 2019) para obtermos uma tabela da ANOVA com o tipo de somas de quadrados que queremos.

Depois podemos usar a função eta_squared() do pacote effectsize (Shachar et al., 2020) para nos dar os eta quadrados parciais desse modelo.

library(car) # Para Anova() com somas de quadrados tipo II e III.
library(effectsize) # Para eta_squared() cohens_d() entre outros.

# Fit do modelo usando a função lm ou a função aov
modelo <- lm(rts ~ gender * cond, dados)
# modelo <- aov(rts ~ gender * cond, dados) também podem

# Tabela da ANOVA, soma de quadrados tipo 3.
resultados <- Anova(modelo, type = 3)

# Etas quadrados parciais e os seus intervalos de confiança a 95%.
es <- eta_squared(resultados, partial = TRUE, ci = .95)

# Juntar os dois outputs numa tabela.
# es[ ,-1] para não termos duas colunas com nomes das VIs
resultados <- cbind(resultados, es[ ,-1])

17.9.2 Opção 2

Em vez da função Anova() do pacote car (Fox & Weisberg, 2019), podemos usar a função parameters() ou model_parameters() do pacote parameters (Lüdecke et al., 2020) para obtermos uma tabela da ANOVA com o tipo de somas de quadrados que queremos.

Depois podemos usar outra vez a função eta_squared() do pacote effectsize para nos dar os eta quadrados parciais desse modelo.

library(parameters) # Para parameters/model_parameters() de vários modelos 
library(effectsize) # Para eta_squared() cohens_d() entre outros

# Fit do modelo usando a função aov
modelo <- aov(rts ~ gender * cond, dados)

# Tabela da ANOVA, soma de quadrados tipo 3.
resultados <- parameters(modelo, type = 3)

es <- eta_squared(resultados, partial = TRUE, ci = .95)

# Juntar os dois outputs numa tabela.
resultados <- cbind(resultados, es[ ,-1])

17.9.3 Opção 3 (mais simples)

Podemos simplificar muito a nossa vida usando a função aov_car() do pacote afex (Singman et al., 2021).

library(afex) # Para aov_car()

afex_options(es_aov = "pes") # Para eta-quadrados parciais (vs, gerais)

# aov_car precisa sempre do termo de erro mesmo para análises entre-pps. 
resultados <- aov_car(rts ~ genero * cond + Error(pp_number), ds)

A função aov_car() do pacote afex (Singman et al., 2021) confirma automaticamente se as variáveis categóricas estão codificadas com o esquema de contrastes dummy ("contr.sum"), reporta a tabela da ANOVA com os eta parciais. Caso tenhamos medidas repetidas aov_car() agrega automaticamente (faz a média) essas medidas. Para além disso aov_car() lida melhor com missings mesmo quando o R se recusa a computar o modelo com as outras funções.

Nota: Mesmo segundo os autores do pacote (Singman et al., 2021) continua a ser boa ideia incluir options(contrasts = c("contr.sum", "contr.poly")), just in case.

17.10 Pacotes Úteis

install.packages("afex") # Para "analysis of factorial experiments"
install.packages("car") # ANOVAs Within e soma de quadrados tipo III
install.packages("effectsize") # Obtem e converte dimensões de efeitos
install.packages("emmeans") # Para comparações post-hoc
install.packages("lme4") # Para modelos multinível
install.packages("lmerTest") # Calcula p-values em modelos multinível
install.packages("ggplot2") # Para os gráficos mais bonitos de sempre!
install.packages("parameters") # Tabelas bonitas de resultados
install.packages("tidyverse") # Para todo um conjunto magnífico!
install.packages("WRS2") # Para estatística robusta

18 Modelos com Medidas Repetidas

Quando temos medidas repetidas podemos usar ANOVAs de medidas repetidas, ou usarmos modelos mistos, também conhecidos como multinível ou hierárquicos.

Seguem-se exemplos de como será a sintaxe geral destas análises usado as funções do pacote afex (Singman et al., 2021) que muito nos facilitam a vida.

18.1 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

18.2 O Que São Medidas Repetidas?

18.3 Construção dos Modelos

18.3.1 ANOVA de Medidas Repetidas

# Assumindo que os dados estão no formato .csv e já em formato longo
#dados <- read.csv(file.choose())

#raov <- aov_car(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#                Error(pp_number / (VI1_Intra-pps * VI2_Intra-pps),
#                dados)

# Para fazer ANOVAs com com a sintaxe dos modelos mistos do lme4::lmer()
# raov <- aov_4(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#               (VI1_Intra-pps * VI2_Intra-pps | pp_number),
#               dados)

18.3.2 ANOVA Mista

18.3.3 Trailer para Modelos Mistos

No R podemos facilmente analisar os nossos dados com modelos mistos equivalente a uma ANOVA de medidas repetidas. Os modelos mistos ou hierárquicos serão o foco do próximo mas podemos deixar aqui um pequeno teaser…

# Assumindo que os dados estão no formato .csv e já em formato longo
#dados <- read.csv(file.choose())

# Com o pacote lme4
# fmlm <- lmer(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps
#              (VI1_Intra-pps * VI2_Intra-pps | pp_number),
#              dados)

# Com o pacote afex (usa o pacote lme4 e lmerTest).
# rmlm <- mixed(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#               (VI1_Intra-pps * VI2_Intra-pps | pp_number),
#               dados)

Recomendo usarem a função mixed() do pacote afex (Singman et al., 2021) confirma automaticamente que temos os contrastes dummy para variáveis factoriais, reporta os dados numa tabela da ANOVA.

Para além disso, permite criar modelos sem correlações nos efeitos aleatórios mesmo para variáveis factoriais, permite computação paralela (mais rápida) e o uso de vários algoritmos de optimização (escolhendo o melhor, ver Singman et al., 2021).

18.3.4 Efeitos Aleatórios por Estímulo e Participante

# Se quisermos adicionar um intercepto variável por estímulo
# rmlm <- mixed(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#               (VI1_Intra-pps * VI2_Intra-pps | pp_number) + (1 | stimuli),
#               dados)

# Se tivermos VIs "nested" no estímulo (intercepto mais slopes por estímulo)
# rmlm <- mixed(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#               (VI1_Intra-pps * VI2_Intra-pps | pp_number) +
#               (VI1_Intra-pps * VI2_Intra-pps | stimuli), dados)

18.4 Exemplo (Passo-a-Passo)

Começamos por importar os pacotes que vamos usar o tidyr (Wickman, 2021)e o afex (Singman et al., 2021) e definir os defaults “do costume”.

library(afex)
library(tidyr)

options(contrasts = c("contr.sum", "contr.poly"))
afex_options(es_aov = "pes")

18.4.1 Formato Longo

Se tivermos os dados no formato largo/“wide”—cada linha corresponde a um participante e cada medida repetida tem várias colunas, uma por cada medição. a uma coluna—temos que primeiro os passar para o formato longo.

No formato longo cada participante terá mais que uma na linha na base de dados, uma linha por medição repetida (ensaio) e uma só coluna por variável.

library(tidyr)

# Importa os dados
# dados <- read.csv(file.choose()) # interface gráfica
dados <- read.csv("../data/demo_ds.csv")

# Converte o número de participante para factor/categórica
dados$pp_number <- as.factor(dados$pp_number)

# Passa os dados para o formato longo 
dados <- pivot_longer(dados, c("phase1", "phase2"), names_to = "phase")

18.4.2 ANOVA e Modelo Misto

# Se todos os participantes fizeram todas as fases.
raov <- aov_car(rts ~ phase * gender +
                Error(pp_number / phase), dados)

# O modelo misto equivalente (intercepts e slopes por condição e pp).
rmlm <- mixed(rts ~ phase * gender + (phase | pp_number), dados)

18.5 Testes Post-Hoc

Tecnicamente falando não precisamos de nenhuma função especial para computar testes pós-hoc.

Por exemplo, se tivermos três condições experimentais podemos computar todos os pares possíveis de testes-t excluindo os participantes de cada grupo de cada vez e corrigindo os valores-p.

Mesmo assim pode ser útil fazer isso automaticamente ou seguindo o teste de Tukey. Podemos fazer isso usando apenas funções base do R (Opção 1) ou podemos usar mais alguns pacotes para nos ajudarem (Opção 2).

18.5.1 Opção 1

raov <- aov(rts ~ genero * cond, ds)
# Ou com aov_car do pacote afex
# raov <- aov_car(rts ~ genero * cond + Error(pp_number), ds) 

# Todos os pares possíveis de testes-t.
# (por default aplica a correcção holm aos valores-p mas faz outras).
pairwise.t.test(ds$rts, ds$cond)

# Testes tukey e respectiva correcção (só funciona em objectos aov)
TukeyHSD(raov$aov, "cond")

18.5.2 Opção 2

library(afex) # Para aov_car
library(emmeans) # Para testes post-hoc (inclusivé para multinível)

raov <- aov_car(rts ~ genero * cond + Error(pp_number), ds)

# Calcula as médias por grupo e respectivos Intervalos de confiança.
psthc_IC <- emmeans(raov, ~ genero)

# Computa os testes post-hoc de todos os pares possíveis.
psthc_ps <- pairs(psthc_IC)

Recomendo esta opção porque o pacote emmeans (Russel & Lenth, 2021) permite continuarmos a usar a mesma sintaxe para especificar fórmulas dos testes post-hoc . Para além disso, este pacote também produz as médias por grupo (respectivos intervalos de confiança e testes pós-hoc) quando lhe fornecemos outputs de modelos mistos (mas os testes pós-hoc são computados da mesma forma que seriam para uma ANOVA).

18.6 Exercícios

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

Evitem usar as dicas a não ser que estejam mesmo perdidos.

18.6.1 Exercício 1

  • Complete a tabela, preenchendo as células em falta.
    • Dica: consulte os apontamentos e as referências.

Nota: VIF denota uma VI factorial/categórica, VIN denota uma VI numérica. X em VIX (e.g., VI1) denota mais que uma variável no modelo.

Nome Fórmula
t-test a amostras dependentes
One-Way repeated measures ANOVA
One-Way repeated measures mixed model
VD ~ VIF1 * VIF2 + (VI1F * VI2F | pp)
VD ~ VIF1 * VIF2 + Error(pp / VIF1)
VD ~ VI1F + VI2N + (VI1F | pp)

(Tabela adaptada de Barr; 2020, capítulo 6; ver Lindeløv, 2019; Judd et al., 2017); para modelos mistos ver Singman & Kellen, 2019).

18.6.1.1 Soluções

Clique para ver as soluções
Nome Fórmula
t-test a amostras dependentes aov(VD ~ VI + Error(pp / VI), dados)
One-Way ANOVA de medidas repetidas (MR) aov(VD ~ VI + Error(pp / VI), dados)
Modelo misto equivalente a One-Way ANOVA (MR) mixed(VD ~ VI + (VI | pp)
Modelo misto equivalente a Two-Way ANOVA (MR) VD ~ VIF1 * VIF2 + (VI1F * VI2F | pp)
Two-Way ANOVA mista VD ~ VIF1 * VIF2 + Error(pp / VIF1)
Modelo misto equivalente a ANCOVA com medidas repetidas VD ~ VI1F + VI2N + (VI1F | pp)

(Tabela adaptada de Barr; 2020, capítulo 6; ver Lindeløv, 2019; Judd et al., 2017); para modelos mistos ver Singman & Kellen, 2019).

18.6.2 Exercício 2

Neste exercício o desafio será fazer uma ANOVA tradicional de medidas repetidas.

AVISO: Cada alínea assume que solucionou correctamente a alínea anterior.

  • Importe os pacotes tidyr e afex que serão usados mais tarde…suspense…

  • Altere o esquema de contrastes para variáveis factoriais para os contrastes cuja soma dá zero.

    • Dica: Procure por “contr.sum” nos tutoriais ou motor de busca.
  • Importe a base de dados ./data/demo_ds.csv e guarde-a numa variável.

  • Passe a base de dados para o formato longo.

    • Dica: reveja os exercícios de reformatação de dados…
    • Dica: phase1 e phase2 devem tornar-se em duas colunas uma com a fase e outra com as respostas…
    • Dica: pivot_longer()
  • Garanta que o número de participante é tratado como variável factorial.

  • Usando a função aov() compute uma one-way ANOVA de medidas repetidas para testar o efeito da fase.

  • Use a função summary() para gerar a tabela da ANOVA (somas de quadrados tipo I).

  • Indique qual a comparação de modelos correspondente a cada linha de cada tabela da ANOVA que gerou.

  • Usando a função aov() compute uma ANOVA mista em que acrescenta uma variável medida e/ou manipulada entre-sujeitos ao modelo da ANOVA anterior.

  • Indique qual a comparação de modelos correspondente a cada linha desta nova tabela que gerou.

  • Repita as duas análises (one-way ANOVA de medidas repetidas e ANOVA mista) usando a função aov_car() do pacote afex.

18.6.2.1 Soluções

Clique para ver as soluções
library(afex)
library(tidyr)

options(contrasts = c("contr.sum", "contr.poly"))

dados <- read.csv("../data/demo_ds.csv")

dados <- pivot_longer(dados, cols = c("phase1", "phase2"),
                      names_to = "fase", values_to = "resposta")

# Duas hipóteses:
# 1. A variável passa a ser igual a ela própria tratada como factorial.
dados$pp_number <- as.factor(dados$pp_number)
# 2. Inserir uma string para garantir que não será tratado como número.
dados$pp_number <- paste0("pp", dados$pp_number)

one_way <- aov(resposta ~ fase + Error(pp_number / fase), dados)

summary(one_way)

Error: pp_number
           Df Sum Sq Mean Sq F value Pr(>F)
Residuals 119  14486     122               

Error: pp_number:fase
           Df Sum Sq Mean Sq F value Pr(>F)
fase        1    138     138     0.1   0.75
Residuals 119 166829    1402               
# Modelo só com um intercepto a modelo com declive da fase.
# Descontando a variabilidade de cada participante (interceptos e slopes
# aleatórios).

mista <- aov(resposta ~ fase * cond + Error(pp_number / fase), dados)

summary(mista)

Error: pp_number
           Df Sum Sq Mean Sq F value       Pr(>F)    
cond        2   4261    2131    24.4 0.0000000014 ***
Residuals 117  10225      87                         
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Error: pp_number:fase
           Df Sum Sq Mean Sq F value              Pr(>F)    
fase        1    138     138    1.19                0.28    
fase:cond   2 153231   76616  659.22 <0.0000000000000002 ***
Residuals 117  13598     116                                
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# O efeito da acrescentar um declive para fase quando já contamos com
# o da condição.
# O efeito de acrescentar um declive para condição quando já contamos
# com o da fase.

# Basta trocar a função aov por aov_car
one_way <- aov_car(resposta ~ fase + Error(pp_number / fase), dados)

mista <- aov_car(resposta ~ fase * cond + Error(pp_number / fase), dados)

# A tabela da ANOVA (soma de quadrados tipo III) foi automaticamente
# calculada.
# Usamos a função nice para imprimir uma tabela bonitinha (i.e., nice).
nice(one_way)
Anova Table (Type 3 tests)

Response: resposta
  Effect     df     MSE    F   ges p.value
1   fase 1, 119 1401.92 0.10 <.001    .754
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1

nice(mista)
Anova Table (Type 3 tests)

Response: resposta
     Effect     df    MSE          F  ges p.value
1      cond 2, 117  87.39  24.38 *** .152   <.001
2      fase 1, 117 116.22       1.19 .006    .278
3 cond:fase 2, 117 116.22 659.22 *** .865   <.001
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1

19 Modelos Mistos

19.1 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

19.2 Set Up

library(afex)

options(contrasts = c("contr.sum", "contr.treatment"))

19.3 Interceptos Aleatórios

classic <- aov(rts ~ cond + Error(lab / 1), ds)
Error in eval(predvars, data, env): object 'lab' not found

mlm <- mixed(rts ~ cond + (1 | lab), ds)
Error in `[.data.frame`(data, , i): undefined columns selected

print(classic)
Error in h(simpleError(msg, call)): error in evaluating the argument 'x' in selecting a method for function 'print': object 'classic' not found

print(mlm)
Error in h(simpleError(msg, call)): error in evaluating the argument 'x' in selecting a method for function 'print': object 'mlm' not found

19.4 Interceptos e Declives Aleatórios

classic <- aov(rts ~ cond + Error(lab / cond), ds)
Error in eval(predvars, data, env): object 'lab' not found

# Ou usando a função mágica aov_car do pacote afex
classic <- aov_car(rts ~ cond + Error(lab / cond), ds)
Error: variable(s) `rts`, `cond`, `lab` not in `ds`

mlm <- mixed(rts ~ cond + (cond | lab), ds)
Error in `[.data.frame`(data, , i): undefined columns selected

print(classic)
Error in h(simpleError(msg, call)): error in evaluating the argument 'x' in selecting a method for function 'print': object 'classic' not found

print(mlm)
Error in h(simpleError(msg, call)): error in evaluating the argument 'x' in selecting a method for function 'print': object 'mlm' not found

19.5 Tutorial

library(tidyr)

ds <- read.csv("../data/demo_ds.csv")

ds <- pivot_longer(ds, c("phase1", "phase2"),
                   names_to = "phase", values_to = "score")

ds$pp_number <- as.factor(ds$pp_number)
classic <- aov(score ~ phase + Error(pp_number / 1), ds)

mlm <- mixed(score ~ phase + (1 | pp_number), ds)

print(summary(classic))

Error: pp_number
           Df Sum Sq Mean Sq F value Pr(>F)
Residuals 119  14486     122               

Error: Within
           Df Sum Sq Mean Sq F value Pr(>F)
phase       1    138     138     0.1   0.75
Residuals 119 166829    1402               

print(mlm)
Warning: lme4 reported (at least) the following warnings for 'full':
  * boundary (singular) fit: see help('isSingular')
Mixed Model Anova Table (Type 3 tests, S-method)

Model: score ~ phase + (1 | pp_number)
Data: ds
  Effect     df    F p.value
1  phase 1, 238 0.18    .671
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
classic <- aov(score ~ phase + Error(pp_number / phase), ds)

# ERROR
mlm <- mixed(score ~ phase + (phase | pp_number), ds)
Error: number of observations (=240) <= number of random effects (=240) for term (phase | pp_number); the random-effects parameters and the residual variance (or scale parameter) are probably unidentifiable

print(summary(classic))

Error: pp_number
           Df Sum Sq Mean Sq F value Pr(>F)
Residuals 119  14486     122               

Error: pp_number:phase
           Df Sum Sq Mean Sq F value Pr(>F)
phase       1    138     138     0.1   0.75
Residuals 119 166829    1402               

print(mlm)
Warning: lme4 reported (at least) the following warnings for 'full':
  * boundary (singular) fit: see help('isSingular')
Mixed Model Anova Table (Type 3 tests, S-method)

Model: score ~ phase + (1 | pp_number)
Data: ds
  Effect     df    F p.value
1  phase 1, 238 0.18    .671
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
# Não há equivalente em ANOVA clássica
mlm <- mixed(score ~ phase * cond + (phase | pp_number) + (phase * cond | lab), ds)
Error: number of observations (=240) <= number of random effects (=240) for term (phase | pp_number); the random-effects parameters and the residual variance (or scale parameter) are probably unidentifiable
mlm <- mixed(score ~ phase * cond + (phase | pp_number) + (phase * cond || lab), ds,
             expand_re = TRUE)
Error: number of observations (=240) <= number of random effects (=240) for term (1 + re1.phase1 | pp_number); the random-effects parameters and the residual variance (or scale parameter) are probably unidentifiable
mlm <- mixed(score ~ phase * cond + (phase || pp_number) + (phase * cond | lab), ds,
             expand_re = TRUE)
Warning: Model failed to converge with 1 negative eigenvalue: -9.4e-06

print(mlm)
Warning: lme4 reported (at least) the following warnings for 'full':
  * boundary (singular) fit: see help('isSingular')
Mixed Model Anova Table (Type 3 tests, S-method)

Model: score ~ phase * cond + (phase || pp_number) + (phase * cond | 
Model:     lab)
Data: ds
      Effect      df         F p.value
1      phase 1, 9.46      2.21    .170
2       cond    2, 2   16.64 +    .057
3 phase:cond    2, 2 492.19 **    .002
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
mlm <- mixed(score ~ phase * cond + (phase || pp_number) + (phase * cond || lab), ds,
             expand_re = TRUE)

print(mlm)
Warning: lme4 reported (at least) the following warnings for 'full':
  * boundary (singular) fit: see help('isSingular')
Mixed Model Anova Table (Type 3 tests, S-method)

Model: score ~ phase * cond + (phase || pp_number) + (phase * cond || 
Model:     lab)
Data: ds
      Effect        df         F p.value
1      phase 1, 116.00      2.31    .132
2       cond      2, 2   18.13 +    .052
3 phase:cond      2, 2 572.54 **    .002
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
mlm <- mixed(score ~ phase * cond + (phase || pp_number) + (phase + cond || lab), ds,
             expand_re = TRUE)
Warning: Model failed to converge with 1 negative eigenvalue: -1.2e-05

print(mlm)
Warning: lme4 reported (at least) the following warnings for 'full':
  * boundary (singular) fit: see help('isSingular')
Mixed Model Anova Table (Type 3 tests, S-method)

Model: score ~ phase * cond + (phase || pp_number) + (phase + cond || 
Model:     lab)
Data: ds
      Effect        df          F p.value
1      phase 1, 117.00       2.29    .133
2       cond      2, 2    18.13 +    .052
3 phase:cond 2, 117.00 659.22 ***   <.001
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
mlm <- mixed(score ~ phase * cond + (phase || pp_number) + (1 | lab), ds,
             expand_re = TRUE)
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
unable to evaluate scaled gradient
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
Model failed to converge: degenerate Hessian with 1 negative eigenvalues

print(mlm)
Warning: lme4 reported (at least) the following warnings for 'full':
  * unable to evaluate scaled gradient
  * Model failed to converge: degenerate  Hessian with 1 negative eigenvalues
Mixed Model Anova Table (Type 3 tests, S-method)

Model: score ~ phase * cond + (phase || pp_number) + (1 | lab)
Data: ds
      Effect        df          F p.value
1      phase 1, 117.00       2.29    .133
2       cond 2, 116.00  25.38 ***   <.001
3 phase:cond 2, 117.00 659.22 ***   <.001
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
mlm <- mixed(score ~ phase * cond + (1 | pp_number) + (1 | lab), ds,
             expand_re = TRUE)

print(mlm)
Warning: lme4 reported (at least) the following warnings for 'full':
  * boundary (singular) fit: see help('isSingular')
Mixed Model Anova Table (Type 3 tests, S-method)

Model: score ~ phase * cond + (1 | pp_number) + (1 | lab)
Data: ds
      Effect        df          F p.value
1      phase 1, 233.00       2.66    .104
2       cond 2, 233.00  21.27 ***   <.001
3 phase:cond 2, 233.00 765.00 ***   <.001
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
mlm <- mixed(rts ~ phase * cond + (1 | pp_number) + (1 | lab), ds)
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
Model failed to converge with max|grad| = 3.66601 (tol = 0.002, component 1)
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, : Model is nearly unidentifiable: very large eigenvalue
 - Rescale variables?
Warning: Model failed to converge with 1 negative eigenvalue: -1.6e-05
mlm <- mixed(rts ~ phase * cond + (1 | pp_number) + (1 | lab), ds)
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
Model failed to converge with max|grad| = 3.66601 (tol = 0.002, component 1)
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, : Model is nearly unidentifiable: very large eigenvalue
 - Rescale variables?
Warning: Model failed to converge with 1 negative eigenvalue: -1.6e-05

20 Decompor Efeitos

20.1 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

20.2 Médias Marginais Estimadas

20.3 Testes Post-Hoc

20.4 Contrastes Planeados

20.4.1 Tendências Marginais Estimadas

21 Estatística Robusta

Em termos gerais o termo estatística robusta refere-se a modelos e testes estatísticos que procuram ser mais robustos à violação de alguns dos pressupostos dos modelos mais convencionais.

21.1 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

21.2 Pressupostos

Os testes-t, ANOVAs, ANCOVAs, regressões lineares, correlações, são todos modelos lineares, então partilham os mesmos pressupostos. Mais concretamente, todos partilham o pressuposto de que os resíduos (os erros) do modelo seguem a distribuição normal—pressuposto da normalidade. Para os testes com uma ou mais variáveis independentes categóricas, aplica-se ainda o pressuposto de que a variância da variável dependente é semelhante em todos os níveis da VI—pressuposto da homocedasticidade. Para os testes com medidas repetidas, aplica-se o pressuposto de que a variância em cada uma das medidas repetidas é semelhante—esfericidade (Wikipedia, 2021) e não se aplica o da homocedasticidade.

Os livros de estatística tradicionais dizem que devemos testar se os nossos dados cumprem os pressupostos e usar estatística não–paramétrica caso não cumprem. Contudo, uma abordagem mais sensata pode ser usar logo à partida testes estatísticos robustos a violação de pressupostos (Field & Wilcox, 2017). Se os testes robustos não diferirem dos testes convencionais podemos reportar os resultados dos testes convencionais só para manter a secção de resultados mais acessível para os leitores. Se os testes diferirem no padrão de resultados podemos citar (Field & Wilcox, 2017) para explicar que essa diferença indica que os pressupostos foram violados e que por isso reportamos os resultados dos testes robustos.

21.2.1 Testar Pressupostos

library(afex) # para aov_car(), test_levene() e test_spherecity()

ds <- read.csv("../data/demo_ds.csv")

# A função aov_car precisa do termo de erro mesmo sem medidas repetidas.
modelo <- aov_car(rts ~ genero * cond + Error(pp_number), ds)
Error: variable(s) `genero` not in `ds`

erros <- residuals(modelo)

# Pressuposta 1 - normalidade dos resíduos
hist(erros) # histograma para inspecção visual
Error in hist.default(erros): 'x' must be numeric
qqnorm(erros) # QQ-plot  para inspecção visual
Error in qqnorm.default(erros): y is empty or has only NAs
shapiro.test(erros) # teste de Shapiro-Wilk à normalidade 
Error in shapiro.test(erros): is.numeric(x) is not TRUE

# Pressuposto 2 - Homocedasticidade - teste de Levene
test_levene(modelo) # só aplicável para medidas entre-participantes
Warning in test_levene(modelo): Functionality has moved to the 'performance' package.
Calling 'performance::check_homogeneity()'.
OK: There is not clear evidence for different variances across groups (Bartlett Test, p = 0.895).

# Pressuposto 3 - Esfericidade  - teste de Maulchy
#test_sphericity(modelo) # só aplicável para medidas repetidas

21.3 Estatísticas Robustas

21.3.1 Exemplo (Passo–a–Passo)

# Alternativas robustas (adapt. Field & Wilcox, 2017)
library(WRS2) # Testes robustos
library(robustbase) # lmrob() alternativa robusta a lm()

options(contrasts = c("contr.sum", "contr.poly"))

dados <- read.csv("../data/demo_ds.csv")

m <- rts ~ genero * cond

# Modelo de ANOVA tradicional (estimador OLS)
aov(m, dados)
Error in model.frame.default(formula = m, data = dados, drop.unused.levels = TRUE): variable lengths differ (found for 'genero')

# ANOVA factorial às medianas (e não às médias)
t2way(m, dados)
Error in model.frame.default(formula, data): variable lengths differ (found for 'genero')

# Modelo linear com estimador robustos
f <- lmrob(m, dados, settings = "KS2014")
Error in model.frame.default(formula = m, data = dados, drop.unused.levels = TRUE): variable lengths differ (found for 'genero')
summary(f)
     sample            F            crit          
 Min.   :    1   Min.   : 0.0   Length:10000      
 1st Qu.: 2501   1st Qu.: 0.1   Class :character  
 Median : 5000   Median : 0.5   Mode  :character  
 Mean   : 5000   Mean   : 1.0                     
 3rd Qu.: 7500   3rd Qu.: 1.3                     
 Max.   :10000   Max.   :16.8                     

21.3.2 Outros Modelos

Para que não estejam listados nestas cheat sheets podemos recorrer à função lmrob() do pacote robustbase (Maechler et al., 2021) para todos os modelos lineares sem medidas repetidas (Field & Wilcox, 2017). Para modelos com medidas repetidas podemos especificar o modelo como modelo misto, depois em vez de usarmos a função lmer() ou mixed() usamos a função rlmer() do pacote robustlmm (Koller, 2016, ver Field & Wilcox, 2017).

Em ambos os casos temos de garantir que definimos o tipo de contrates correcto para as variáveis factoriais—options(contrasts = c("contr.sum", "contr.poly"))

No caso de modelos do lmrob() não conheço forma de obter a tabela da ANOVA para estes modelos com somas de quadrados tipo III. Mesmo assim, as diferenças entre resultados com as diferentes somas de quadrados só devem ser relevantes em casos em que os grupos não estão balanceados.

No caso de modelos rlmer(), podemos obter a tabela da ANOVA dos efeitos fixos com a função parameters() ou model_parameters() do pacote parameters (Lüdecke et al., 2020). Não posso garantir mas penso que esses Fs e valores-p serão computados usando a soma de quadrados tipo III. No entanto, não sei de nenhuma forma de computar etas parciais quadrados para estes modelos.

Ainda no caso dos modelos mistos, se (1) usaram a função mixed() ou lmer_alt do pacote afex (Singman et al., 2021) para computar o vosso modelo misto, (2) esse vosso modelo inclui “||” para suprimir as correlações num termo de efeitos aleatórios com variáveis factorais, (3) usando o argumento expand_re = TRUE, então não poderão usar a mesma fórmula do modelo e dados na função rlmer(). Mesmo que estas três condições estejam reunidas ainda podem computar o vosso modelo usando rlmer(). Para isso têm de ver o atributo data do output da função mixed e fixar as colunas que comecem por re1 (ou reX X = número do termo aleatório). Depois terão de usar essas variáveis para especificar o modelo no termo de erro equivalente na função rlmer() e substituir os dados pelo atributo data do output da função mixed(). Dado que é um caso muito específico entrem em contacto com o RUGGED para que vos possamos ajudar.

21.4 Exercícios

Ainda por escrever…

22 AVISO

Esta parte está INCOMPLETA, o trabalho nesta parte do livro continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que esta parte já tenha sido actualizada, deve considerar a informação apresentada como INCOMPLETA.!

23 Gráficos

23.1 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

24 RMarkdown

Author: Mariona Pascual Peñas

24.1 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

24.2 ACAB: All Codes Are Beautiful

If you arrived here, perhaps your evil lecturer is forcing you to show milestone achievements and the current status of the research, and you found yourself scanning through a way to save time and avoid infinite versions of the same document, or having to copy-paste that data into tables. R Markdown allows you to present what is going on, and update it easily without having to copy, download, format, and update all new changes offline.

For that, R Markdown is your data make-over: it allows you to generate a report that will draw directly from the code and the data, and thus, if your analysis changes, the report just needs to be updated.

Whether it is for a PDF, text document, a presentation for a conference, or a web to show in front of your colleagues, this is a fantastic way to at least save you time redoing the results, and to be able to export whatever you what to show from your code and the results, beautifully.

24.2.1 Some considerations


  1. With R Markdown you are configuring a document, so you will need to select from the package which format or “template” are you working on. To do so, the first few steps may seem the least attractive, but you will need to pre-set and select the styling of the document. Do not worry, those are a few lines, and then you carry on with your analysis.

  2. RMarkdown lives from the balance between text and chunks. The text will be everything that you will want displayed as it is, like this, and the chunks are those snippets of code that you will want to either just call for everything to work, or just for display in your majestic report.

  3. This RMarkdown chapter assumes that you are working on RStudio.Please bear in mind that if you are using R directly on your console this might not have a 1:1 correspondence. RStudio now has a function that allows you to format your text and insert tables, figures, and code in a “drag and select” kind of way. How amazing is that?! This is a major advance for those who are not so comfortable with formatting. It is so awesome that we won’t use it in this chapter so you don’t get used to it being automatically done.

  4. Remember Chapter 10? Keep that analysis close, we will work on it.

  5. In this Chapter, we will try to use and show the formatting options, and thus, usually, everything will be set to TRUE. Please bear in mind that for everything that you don’t like, you can “turn it off” by just setting the options to FALSE.

24.2.1.1 Let’s start this party

Let’s do this! How do I start my beautiful, beautiful report? Go to the RStudio Menu. There you will decide how you want your RMD file: Is it a document? A presentation? A plane? Wonderwoman?

File ▶ New File ▶ RMarkdown Document ▶ HTML

24.3 Text & Display

24.3.1 Output Configuration

The RMD script that has popped up on your screen has slight differences from our usual scripts: now we have new functions.

Knit: This comes from the knitr package that will help us display our report in its full final form. Notice that we are able to knit in PDF or Word format, even when we have selected HTML.

Run: Run now comes now with a drop-down menu, that will allow us to run only specific chunks of our code

By default, you will see a brief example of text and chunks, as well as a YAML header (that text between lines —).

What is that?! Should I DELETE it?
No.

Briefly, those are the precious lines that will tell your document how it should be displayed. Yes, this is what makes your report beautiful and where you take important stylish, graphic designer decisions.

---
title: "The Report: OMG, this is it"
author: My name
date: March 2, 2023
output: #comes inherited from the pop-up menu. You have previously defined that you wanted an HTML
  html_document:
   code_folding: "hide" #do you want to show your code firsthand or only unfold it when asked?
    toc: true #do you want an index?
    toc_float: true #do you want it to unfold?
    toc_depth: 2 #how many headers do you want to display? (default is 3)
    number_sections: true #do you want your sections numbered? 
    theme: cosmo #personal favorite theme
    highlight: default
---

24.3.1.1 Theme and Highlight

  1. Theme and Highlight are the two functions where you can expend a lot of time on, or just download a presetted template. The RMarkdown comes with themes and colors for highlighting your text: I would recommend you to leave it in the default option, and after having completed your first draft you can see better those changes.
Options List
Themes bootstrap, cerulean, cosmo, darkly,default,flatly, journal, lumen, paper, readable, sandstone, simplex, spacelab, united, yeti
Highlighters breezedark, default, espresso, haddock, kate, monochrome, pygments, tango, textmate, zenburn

For more, explore Bootswatch by Thomas Park, prettydoc by Yixuan Qiu, or rmdformats by Julien Barnier.

24.3.2 Basic text display

R Markdown is just like any other text processor, but instead of doing drag and drop, or formatting automatically, you need to tell it to do so. Here there are a few examples of text formatting

24.3.2.1 Text format

As a text processor, RMarkdown allows you to change textual features of your text by adding asterisks or different graphic signs:

**bold** *italic* [underline]{.underline} ^superscript^ ~subscript~
~~strikeout~~ [small caps]{.smallcaps}

Lists

-   bullet 1
-   bullet 2

[x] tickbox 1 
[ ] tickbox 2

1.  numbered 1
2.  numbered 2

### Headers

Sections are easily done with '#'.

# Header 1
## Header 2
### Header 3
#### Header 4

Text-based tables are easily inserted. Nonetheless, there are several options to include tables from source. We will see them in the section Chunks.

| Column 1 | Column 2 |
|----------|----------|
| Row 2    | I only act like I know everything. |
| Row 3    | These aren't the droids you are looking for  |

24.3.2.2 Text highlight

Text that is included within <div> and </div > can be displayed wit background and alignment configurations:

  • class= text-center, text-left, text-right

  • style="background-color: insert hexadecimal reference"

<div class=text-center style="background-color: #d0ece7">
Have you noticed how many supervillains have PhDs?
</div>

24.3.3 Adding separators


With three stars you can display separating lines in your sections, in case it helps you organize better the output.

***

Also, with <br> you will be able to add a white space between sections

<br>

24.3.4 Tabs

When thinking of an html display, tabs are your best friend because they allow you to avoid the infinite scroll for displaying results. For example, when reporting post-hoc tests, you can just display them in tabs, one next to each other, to avoid having to go back and forth. Some other personalized functions of {.tabset} are: - {.tabset-fade}, which allows you to highlight only the selected tab. - {.tabset-pills}, that display the tab as buttons

In this case, what you need to do is add {.tabset} in the immediate prior header where you want your tabs to appear:

  ### HEADER {.tabset .tabset-fade.tabset-pills}
  #### Tab 1
  #### Tab 2

If you want to know how to customize these buttons, check out the Pimp my RMD website (Holz, 2018)

24.4 Chunks

24.4.1 The chunks

When we open chunks, we are instructing R to execute our code. This is made by giving the output a delimited space to execute the code:

```{r}
# My precious! Oh my precious code! 
```

You can call or display any snippet of code within the chunk (from the libraries that were used, to graphics, images or just the specific syntax that is needed for your analysis).

24.4.2 Chunks have names

Naming chunks is an option for those who like to keep track of their script. This won’t be displayed anywhere, but it will help you navigate the chunks. Your RMD file will most surely be long. Name them for your sanity.

```{r random chunk name,} 
``` 

24.4.3 Display options

Not all chunks serve the same purpose, so there are several options for the display configuration.

  • Only showing the code and not running it:

    eval = FALSE

  • Only showing the results without showing the code:

    echo = FALSE

  • Not showing the code nor results but running that chunk:

    include = FALSE

  • Not showing warnings nor messages that are usually displayed in the console:

    message = FALSE, warning = FALSE

  • There are different options for the result display:

    results = "markup" "markup", "asis", "hold", or "hide"

  • If we include graphics or figures, we can configure their position, width, height and caption in the chunk.

    fig.align="center", fig.width=3, fig.height=2, fig.cap="Figure1."

24.4.4 Examples of chunk display options

Call the libraries and display their names, but not the messages and warnings:

```{r, libraries,results ='hide', warning = FALSE, message= FALSE}
library(afex)
library(emmeans)
library(ggplot2)
library(RColorBrewer)
library(tidyverse)
```

Then we want to specify options for our library and call our data, but we do not want it in our output with include = FALSE:

```{r, specify options and calling data, include= FALSE}
options(contrasts = c("contr.sum", "contr.poly"))
afex_options(es_aov = "pes")
dados <- read.csv("../data/demo_ds.csv")
```

And then, we want to show off our formula:

```{r, THE formula, warning= FALSE, message = FALSE}
resultados <- aov_car(rts ~ gender * cond + Error(pp_number), dados)
```

Should we also add a graphic?

```{r, Plot, warning= FALSE, results='asis', 
    fig.align="center", fig.width=3, fig.height=2}
emmip(resultados,  gender ~ cond,  xlab= "Condition", CIs = TRUE)
```

24.4.5 Table chunks

We have already seen text tables, but with knitr we can work on having a better looking display

```{r, results, warning= FALSE, results='asis'}
knitr::kable(nice(resultados))
```

24.4.6 Graphic chunks

Including graphics generated within your script is just a matter of embedding the chunk. As you saw, with emmip (or afexplot) the result is quite immediate. Nonetheless, if you are working with ggplot, the best procedure is to first generate the graphic without including it (include= FALSE) and then only display the function print

```{r, echo = FALSE, message= FALSE, warning= FALSE}
desc <- summarize(group_by(dados, cond, gender),
                   M= mean(rts,na.rm=TRUE),
                   SE= sd(rts)/sqrt(length(rts)))
```
```{r, an example of a ggplot, results = 'asis',fig.align = 'center'} 
ExamplePlot <-ggplot(desc, aes(cond, M, colour = gender)) +
  xlab ("Condition") +
  ylab ("Reaction Time") +
  scale_colour_brewer(palette = "Paired")+
  geom_point(size = 6) +
    theme_minimal()
```
```{r, results = 'asis',fig.align = 'center'} 
print (ExamplePlot)
```

24.5 Final steps

Now you only need to knit your output and let the aesthetics magic work by itself. Do note that you can knit at any moment, to see how you are advancing.

Yay! Your file is ready!

## Challenge time!

Ok, now it’s your time to shine. Let’s prepare an RMarkdown with the following requirements:
My first RMD
  1. It has to be beautiful, please.
  2. Title and name
  3. Build 3 sections
    • The random introduction
    • Descriptives
    • Analysis
  4. In introduction:
    • In a subsection called “The nerds”:
      • Make a list of the book/movie quotes that you found in this Chapter
      • Say Mischief Managed in bold and italics, and put your name in superscript
      • BONUS POINTS if you box the two text sections in two different colors
  5. In Descriptives:
    • Make a table with mean RT per group.
      PRO TIP: here is where the chunk options start to play
  6. In Analysis, with all of it IN TABS
    • The libraries you used
    • The formula for your analysis
    • The results of your analysis
    • A cute graphic
    • A brief explanation of the graphic in a yellow box

24.6 Resources

Did you think we learned to do that all by ourselves? Unfortunately knowledge does not happen by osmosis. Check out these amazing references and further resources from people who is also working on RMarkdown.You will find a lot of options for further customizing your RMD.

https://bookdown.org/yihui/rmarkdown-cookbook/

https://elastic-lovelace-155848.netlify.app/highlighters

https://github.com/juba/rmdformats.

https://holtzy.github.io/Pimp-my-rmd/#theme

https://monashdatafluency.github.io/r-rep-res/yaml-header.html#final-look-1

https://r4ds.had.co.nz/r-markdown.html#chunk-name

https://www.rstudio.com/wp-content/uploads/2015/02/rmarkdown-cheatsheet.pdf

25 AVISO

Esta parte está INCOMPLETA, o trabalho nesta parte do livro continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que esta parte já tenha sido actualizada, deve considerar a informação apresentada como INCOMPLETA.!

26 Workflow

26.1 AVISO

Capítulo INCOMPLETO, o trabalho neste capítulo continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que o capítulo já tenha sido actualizado, deve considerar a informação apresentada como INCOMPLETA.!

26.2 Pipelines de Scripts

26.2.1 Introdução

26.2.2 Script de Instalação de Pacotes

Exemplo de script de instalação de pacotes usados neste livro:

packages <- c("afex", "bookdown", "car", "DT", "faux", "effectsize", "emmeans",
              "ggplot2", "knitr", "nortest", "parameters", "performance",
              "readxl", "revealjs", "rmarkdown", "robustbase", "tidyr", "WRS2")
packages <- setdiff(packages, row.names(installed.packages()))
# Instala pacotes a partir do mirror global da CRAN.
# Assim o/a utilizador/a não tem de seleccionar o mirror.
# Nota: CRAN = Comprehensive R Archive Network.
#       Esta será a fonte mais oficial dos pacotes.
install.packages(packages, repos = c("CRAN" = "https://cloud.r-project.org/"))

O script cria uma variável packages com a lista de todos os pacotes. Depois exclui os pacotes já instalados no sistema para poupar tempo na instalação. Instala depois os pacotes escolhendo um mirror global da CRAN que redirecciona para o mirror melhor mais rápido para o utilizador (Comprehensive R Archive Network, a fonte mais oficial dos pacotes). Assim os utilizadora não têm de escolher o mirror quando podem nem saber o que isso é.

26.3 Diferenças com Outros Softwares

Dependendo de como é classificada a estatística pode ser um ramo da matemática. Os testes estatísticos e os seus resultados são produto de equações matemáticas bem definidas. Assim sendo, da mesma forma que o resultado duma conta não deve ser diferente consoante a calculadora que é usada, o resultado dum teste estatístico também não deverá ser diferente consoante o software que é usado (e.g., R, Python, JAPS, Jamovi, SAS, SPSS, etc…). Contudo, por vezes há algoritmos/fórmulas ligeiramente diferentes para o mesmo teste e/ou opções que devem ser tomadas no cálculo (e.g., como codificar variáveis categóricas). Assim sendo, é possível que diferentes softwares produzam resultados diferentes, mesmo que nenhum deles tenha bugs (i.e., erros informáticos). Nesta secção listamos algumas diferenças conhecidas e formas de as ultrapassar para poderem comparar os valores obtidos no R com os obtidos noutros softwares.

26.3.1 SPSS

Por defeito (default) o SPSS toma algumas opções e usa alguns algoritmos diferentes do R. Importa então ver algumas das diferenças e o que podemos fazer para obter valores comparáveis.

26.3.1.1 Codificação de Variáveis Categóricas

Como vimos anteriormente, o SPSS por defeito codifica as variáveis categóricas com um esquema de contrastes cuja soma é igual a zero. Por muitos defeitos que o SPSS possa ter (na minha opinião são mesmo muitos), neste caso o SPSS acaba por tomar opção mais útil para a análise de dados mais típicos na psicologia (ver UCLA, 2011; Protopapas, 2015).

De qualquer das formas é fácil alterar esta opção no R, para um valor igual ao do SPSS e mais útil para a maioria dos nossos problemas de investigação, basta incluir a próxima linha de código no topo dos nossos scripts:

options(contrasts = c("contr.sum", "contr.treatment"))

26.3.1.2 Testes de Normalidade

O R não vem com uma função que compute facilmente um teste de normalidade de Kormogorov-Smirnov e aplique a correcção de Lilliefors, como recomendado por alguns autores para amostras com mais de 50 observações (e.g., Marôco, 2018). Para podermos computar este teste no R o mais fácil poderá ser instalarmos o pacote nortest (i.e., install.pacakges(nortest)) que disponibiliza a função lillie.test() que nos permite então testar a normalidade das variáveis.

Notem que se quiserem confirmar a normalidade duma variável dependente nos vários níveis duma variável independente, ou nas várias células dum delineamento factorial, podem recorrer às funções group_by() e summarise() do pacote dplyr para o fazer.

# Por exemplo, podemos testar se a variável tempos de resposta (rts)
# tem uma distribuição próxima da normal em todas as combinações
# de condição e laboratório.
library(dplyr)
library(nortest)

KS <- summarise(group_by(ds, cond, lab),
                KS_pvalue = lillie.test(rts)$p.value)

print(KS)
# A tibble: 6 x 3
# Groups:   cond [3]
  cond  lab   KS_pvalue
  <chr> <chr>     <dbl>
1 high  A        0.575 
2 high  B        0.0867
3 low   A        0.134 
4 low   B        0.872 
5 med   A        0.870 
6 med   B        0.318 

26.3.1.3 Quantis

Existem vários algoritmos para calcular os valores dos quartis duma dada variável. Isto talvez seja algo de quem nem sempre temos consciência porque os quartis são muitas vezes falados nas unidades curriculares de introdução à estatística. É então fácil que nos esqueçamos da possibilidade de divergências entre algoritmos, assim como é possível que não se fale muito disso numa cadeira introdutória para evitar complexidade desnecessária. Felizmente o R permite calcular os quantis (quartis, percentis, etc…), bem como a amplitude inter-quartil recorrendo a vários métodos. Para obtermos o mesmo valor do SPSS devemos usar o método 7 como é especificado no manual da função quantile() (para ver o manual basta correr a função de ajuda help(quantile)).

Ou seja, para obter o mesmo valor que no SPSS, teremos de usar as funções quantile() e IQR() com o argumento type = 6.

# Quartis a la SPSS para as idades dos participantes
SPSS_quantiles <- quantile(ds$age, type = 6)
# Intervalo inter-quartil a la SPSS para as idades dos participantes.
SPSS_IQR <- IQR(ds$age, type = 6)

print(SPSS_quantiles)
  0%  25%  50%  75% 100% 
  18   19   25   31   47 
print(SPSS_IQR)
[1] 12

27 AVISO

Esta parte está INCOMPLETA, o trabalho nesta parte do livro continuará. Assim que existir um primeiro rascunho completo este aviso desaparecerá. Se está a ler este aviso, mesmo que esta parte já tenha sido actualizada, deve considerar a informação apresentada como INCOMPLETA.!

28 Checklist de Conhecimentos

  1. A pergunta de investigação.
    • Qual o tema da minha tese…
    • Conhecer a literatura da área.
  2. O design da minha investigação.
    • O que é que eu fiz para a tese…
    • Qual o procedimento.
    • Quais as variáveis.
  3. A operacionalização da hipótese.
    • A hipótese propriamente dita…
    • Que variáveis espero que afectem quais e como.
  4. Quais os modelos a comparar (abordagem de comparação de modelos).
    • Qual o modelo se cada variável não tiver efeito.
    • Qual o modelo se cada variável tiver efeito.
    • Quais as propriedades desses modelos.
  5. Compreendo as ideias gerais das inferências estatísticas?
    • Que inferências posso fazer?
    • Que inferências NÃO posso fazer?
    • Qual a lógica da inferência?
  6. Qual a estrutura da minha base de dados.
    • Como estão formatados os dados?
    • Preciso de os limpar?
    • Preciso de os reorganizar?
    • Sei o que os nomes das colunas querem dizer?
  7. Como comparo os modelos que quero no sofware que quero usar?
    • Onde guardei a base de dados?
    • Como usar o Jamovi/JASP/SPSS/R?
    • Como usar o Jamovi/JASP/SPSS/R para a/s análise/s que quero fazer?
    • Como guardo os resultados?
    • Onde guardei os resultados?
    • Que nome dei ao ficheiro?
  8. Como interpreto os resultados que obtive?
    • O que me dizem os resultados desse tipo de análise?
    • O que me dizem os resultados no contexto da minha investigação?
      • Rever pontos 3 a 4!
    • Que mais modelos posso comparar para testar outras hipóteses?
      • Voltar ao ponto 3!
      • Possívelmente voltar ao ponto 1.

29 Apontamentos

29.1 Índice

  1. One Formula to Rule Them All: Mini–resumo téorico

  2. One Formula Syntax to Rule Them All: Explicação da sintaxe das fórmulas do R

  3. One Function to Rule Them All: lm() computa todos os modelos lineares

  • Cheat Sheet (sem medidas repetidas): tabela - nome do teste | computação no R - lm()
  1. Sheet (Medidas Repetidas: MR): tabela - nome do teste | computação no R - aov()

  2. Cheat Sheet (MR - modelos mistos): tabela - nome do teste | modelo misto equivalente

  3. Nuances: Defaults problemáticos do R

  4. Soluções: Como redefinir ou contornar defaults problemáticos

  • Opção 1: com os pacotes car e effectsize
  • Opção 2: com os pacotes parameters e effectsize
  • Opção 3 (mais simples): só com o pacote afex
  1. Pacotes Úteis: Lista de pacotes e respectiva utilidade

  2. Medidas Repetidas: Análise de medidas repetidas no R

  • ANOVAs: sintaxe das ANOVAs MR com aov_car()
  • Modelos Mistos: modelo misto equivalente
  • Exemplo (passo–a–passo): script decomposto de uma análise MR completa.
  1. Testes Post-Hoc: como os computar no R
  • Opção 1: usando as funções base do R
  • Opção 2: usando o pacote emmeans
  1. Pressupostos: Explicação teórica dos pressupostos de modelos lineares
  • Testar Pressupostos: como testar pressupostos no R
  • Robustas (exemplos): alternativas robustas a testes clássicos
    • Exemplo (passo–a–passo): tutorial passo–a–passo
    • Cheat Sheet (Robustas sem MR): tabela - teste clássico | alternativa robusta
    • Cheat Sheet (Robustas com MR): tabela - teste clássico | alternativa robusta
    • Robustas (outros modelos): alternativas robustas para outros modelos
  1. Recursos: Lista de recursos (livros, tutoriais, sites)

29.2 One Formula to Rule them All

Adaptado de Judd e colaboradores:

Dados = Modelo + Erro

Erro = Dados - Modelo

Modelo = Dados - Erro

Se o modelo fosse perfeito, ou seja, se não tivesse erro (erro = 0):

Modelo = Dados - 0 <=> Modelo = Dados

29.2.1 Temos Uma Missão

Diminuir o Erro*!

Para isso queremos encontrar o modelo que melhor explica os nossos dados. Contudo, como preferimos explicações simples a explicações mais complexas, preferimos modelos simples em detrimento de modelos complexos.

Ou seja, a nossa missão é encontrar o modelo mais simples que explique melhor os nossos dados.

Os testes estatísticos comparam o nosso modelo com outro mais simples para testar se a complexidade adicional do nosso modelo é justificada pela redução do erro que ele consegue em relação ao modelo mais simples (Judd e colabodores). Quando a complexidade adicional de incluir um dado preditor/VI no modelo é justificada pelo erro que ele reduz, dizemos que o efeito desse preditor/VI é significativo (Judd e colaboradores).

*Nota: Tipicamente queremos reduzir o Erro quadrado (erros grandes são mais penalizados que erros pequenos; estimador Ordinal Least Squares—OLS; ver Judd e colaboradores).

29.3 One Formula Syntax to Rule Them All

No R podemos usar a mesma sintaxe para escrever todos os modelos lineares possíveis (e.g, testes-t, ANOVAs, ANCOVAs, regressões lineares, correlações).

Vejamos então como podemos usar esta sintaxe.

Nota: os exemplos que se seguem assumem que temos uma base de dados guardada numa variável chamada dados com uma variável gender com o género dos participantes, uma variável cond com a condição entre–participantes atribuída aos participantes e uma variável rts com tempos de resposta.

Para tal podemos importar a base de dados (gerada por simulação) que está na pasta dados.

# Usando uma interface gráfica file.choose() para encontrar o ficheiro.
#dados <- read.csv(file.choose()) 

# Especificando o caminho para o ficheiro e o nome do ficheiro.
dados <- read.csv("../data/demo_ds.csv")

Variável Dependente (VD) em FUNÇÃO (~) da Variável Independente (VI):

# modelo <- VD ~ VI 

Exemplo: tempos de resposta (rts) em FUNÇÃO (~) do género (gender).

m <- rts ~ gender

Variável dependente em função dos efeitos principais das VIs:

# modelo <- VD ~ VI1 + VI2 + VIx

Exemplo: rts (rts) em função (~) do efeito principal do género (gender) + efeito principal da condição experimental (cond).

m <- rts ~ gender + cond

VD em função dos efeito principais das VIs + efeitos de interacção entre as VIs

# modelo <- VD ~ VI1 + VI2 + VIX + VI1:VI2 + VI1:VIX + VI2:VIX

Exemplo: rts (rts) em função (~) dos efeitos principais do género (gender) e condição (cond) + efeito de interacção entre género e condição (gender:cond).

m <- rts ~ gender + cond + gender:cond

Na sintaxe de fórmulas do R há uma forma resumida de incluir todos os efeitos principais das VIs e todas as interacções possíveis entre as VIs.

VD em função dos efeito principais das VIs + efeitos de interacção entre as VIs

# modelo <- VD ~ VI1 * VI2 * VIx

Exemplo; rts (rts) em função dos efeitos principais do género e condição + efeito de interacção entre género e condição.(genero * cond).

m <- rts ~ gender * cond

29.4 One Function to Rule Them All

Todos os modelos lineares podem ser computados como modelos lineares (lm) no R (ver teoria.html/pdf) .

# fit <- lm(modelo, dados)
f <- lm(m, dados)

# Obter os resultados.
summary(f)

29.4.1 Cheat Sheet (sem medidas repetidas)

Modelos com SÓ UMA VD contínua.

(Tabela adaptada de Barr; 2020, capítulo 6; ver Lindeløv, 2019; Judd et al., 2017); para modelos mistos ver Singman & Kellen, 2019).

Nome Fórmula
t-test de uma amostra VD ~ 1
Regressão/Correlação VD ~ VI
t-test*/One-Way ANOVA VD ~ VI1
Regressão múltipla VD ~ VI1 * VI2 * VIx
ANCOVA VD ~ VI1 * VI2 * VIx_cat + COV
Moderação VD ~ VI * VI
ANOVA 2X2 VD ~ VI1 * VI2

29.5 Cheat Sheet (Medidas Repetidas: MR)

Modelos com SÓ UMA VD contínua e MEDIDAS REPETIDAS.

Adapt. Barr (2020, capítulo 6; ver Singman & Kellen, 2019; ver também Judd e colaborades):

Nome Fórmula
t-test*/One-Way ANOVA VD ~ VIW + Error(pp_number/ VIW)
ANOVA 2X2 Mista VD ~ VIW * VIB + Error(pp_number/ VIW)
ANOVA 2X2 MR VD ~ VI1W * VI2W + Error(pp_number/ (VI1W * VI2W))

Legenda: VIW = Variável Independente Within, VIB = Variável Independente Between, pp_number = Variável com o número ou outro identificador dos sujeitos.

29.6 Cheat Sheet (MR - modelos mistos)

Modelos com SÓ UMA VD contínua e MEDIDAS REPETIDAS com abordagem de modelos mistos.

Adapt. Barr (2020, capítulo 6; ver Singman & Kellen, 2019; ver também Judd e colaborades):

Nome Fórmula
t-test MR VD ~ VI1 + (1 | pp_number)
t-test*/One-Way ANOVA MR VD ~ VI1 + (1 | pp_number)
Regressão/Correlação VD ~ VI1 + (1 | pp_number)
Regressão múltipla VD ~ VI1 * VI2 * VIx
ANOVA Mista VD ~ VIW * VIB + (VIW | pp_number)
ANOVA 2X2 VD ~ VIW * VI2W + (VIW * VIW | pp_number)
ANOVA 2X2 + efeitos item VD ~ VIW * VIW + (VIW * VIW | pp_number) + (VIW * VIW | item)

Legenda: VIW = Variável Independente Within, VIB = Variável Independente Between, pp_number = Variável com o número/identificador dos participantes, item = Variável com o nome/indentificador dos itens.

29.7 Nuances

  1. Para dados com temos medidas intra-participantes temos de especificar um termo de erro.

  2. O R implica com missings em ANOVAs com medidas intra-pps.

  3. O R usa outro tipo de contrastes que não os cuja soma dá 0.

  4. O R usa soma de quadros Tipo I e não Tipo III (os do SPSS). Esta questão só é relevante quando os grupos não têm todos o mesmo tamanho mas isso é frequente nos nossos estudos.

  5. O R não reporta automaticamente dimensões dos efeitos.

Dado estas nuances, na prática para o tipo de análises que fazemos pode ser mais útil usarmos a função aov() (que na realidade invoca a função lm()).

Mais cómodo ainda será a função afex::aov_car() (também invoca essas funções mas tem defaults mais ajustados à análise de dados experimentais).

Seguem-se três opções de como usar alguns pacotes para nos ajudar a resolver estes problemas. Assim podemos continuar a usar a mesma sintaxe para escrever todos os nossos modelos.

Spoiler alert: a Opção 3 é a mais simples (menos linhas de código).

29.8 Soluções

Para ficarem claras as diferenças entre cada opção comecemos por escrever a parte do código que será comum a todas.

# Contrastes correctos para soma de quadrados tipo II/III.
options(contrasts = c("contr.sum", "contr.poly"))

# dados <- read.csv(file.choose()) # com interface gráfica
dados <- read.csv("../data/demo_ds.csv")

29.8.1 Opção 1

Podemos usa a função Anova() do pacote car (Fox & Weisberg, 2019) para obtermos uma tabela da ANOVA com o tipo de somas de quadrados que queremos.

Depois podemos usar a função eta_squared() do pacote effectsize (Shachar et al., 2020) para nos dar os eta quadrados parciais desse modelo.

library(car) # Para Anova() com somas de quadrados tipo II e III.
library(effectsize) # Para eta_squared() cohens_d() entre outros.

# Fit do modelo usando a função lm ou a função aov
modelo <- lm(rts ~ gender * cond, dados)
# modelo <- aov(rts ~ gender * cond, dados) também podem

# Tabela da ANOVA, soma de quadrados tipo 3.
resultados <- Anova(modelo, type = 3)

# Etas quadrados parciais e os seus intervalos de confiança a 95%.
es <- eta_squared(resultados, partial = TRUE, ci = .95)

# Juntar os dois outputs numa tabela.
# es[ ,-1] para não termos duas colunas com nomes das VIs
resultados <- cbind(resultados, es[ ,-1])

29.8.2 Opção 2

Em vez da função Anova() do pacote car (Fox & Weisberg, 2019), podemos usar a função parameters() ou model_parameters() do pacote parameters (Lüdecke et al., 2020) para obtermos uma tabela da ANOVA com o tipo de somas de quadrados que queremos.

Depois podemos usar outra vez a função eta_squared() do pacote effectsize para nos dar os eta quadrados parciais desse modelo.

library(parameters) # Para parameters/model_parameters() de vários modelos 
library(effectsize) # Para eta_squared() cohens_d() entre outros

# Fit do modelo usando a função aov
modelo <- aov(rts ~ gender * cond, dados)

# Tabela da ANOVA, soma de quadrados tipo 3.
resultados <- parameters(modelo, type = 3)

es <- eta_squared(resultados, partial = TRUE, ci = .95)

# Juntar os dois outputs numa tabela.
resultados <- cbind(resultados, es[ ,-1])

29.8.3 Opção 3 (mais simples)

Podemos simplificar muito a nossa vida usando a função aov_car() do pacote afex (Singman et al., 2021).

library(afex) # Para aov_car()

afex_options(es_aov = "pes") # Para eta-quadrados parciais (vs, gerais)

# aov_car precisa sempre do termo de erro mesmo para análises entre-pps. 
resultados <- aov_car(rts ~ genero * cond + Error(pp_number), ds)

A função aov_car() do pacote afex (Singman et al., 2021) confirma automaticamente se as variáveis categóricas estão codificadas com o esquema de contrastes dummy ("contr.sum"), reporta a tabela da ANOVA com os eta parciais. Caso tenhamos medidas repetidas aov_car() agrega automaticamente (faz a média) essas medidas. Para além disso aov_car() lida melhor com missings mesmo quando o R se recusa a computar o modelo com as outras funções.

Nota: Mesmo segundo os autores do pacote (Singman et al., 2021) continua a ser boa ideia incluir options(contrasts = c("contr.sum", "contr.poly")), just in case.

29.9 Pacotes Úteis

install.packages("afex") # Para "analysis of factorial experiments"
install.packages("car") # ANOVAs Within e soma de quadrados tipo III
install.packages("effectsize") # Obtem e converte dimensões de efeitos
install.packages("emmeans") # Para comparações post-hoc
install.packages("lme4") # Para modelos multinível
install.packages("lmerTest") # Calcula p-values em modelos multinível
install.packages("ggplot2") # Para os gráficos mais bonitos de sempre!
install.packages("parameters") # Tabelas bonitas de resultados
install.packages("tidyverse") # Para todo um conjunto magnífico!
install.packages("WRS2") # Para estatística robusta

29.10 Medidas Repetidas

Como vimos pelas Cheat Sheets, quando temos medidas repetidas podemos usar ANOVAs de medidas repetidas, ou usarmos modelos mistos.

Seguem-se exemplos de como será a sintaxe geral destas análises usado as funções do pacote afex (Singman et al., 2021) que muito nos facilitam a vida.

29.10.1 ANOVAs

# Assumindo que os dados estão no formato .csv e já em formato longo
#dados <- read.csv(file.choose())

#raov <- aov_car(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#             Error(pp_number / (VI1_Intra-pps * VI2_Intra-pps),
#             dados)

# Para fazer ANOVAs com com a sintaxe dos modelos mistos do lme4::lmer()
# raov <- aov_4(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#               (VI1_Intra-pps * VI2_Intra-pps | pp_number),
#               dados)

29.11 Modelos Mistos

29.11.1 Equivalentes à ANOVA

# Assumindo que os dados estão no formato .csv e já em formato longo
#dados <- read.csv(file.choose())

# Com o pacote lme4
# fmlm <- lmer(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps
#              (VI1_Intra-pps * VI2_Intra-pps | pp_number),
#              dados)

# Com o pacote afex (usa o pacote lme4 e lmerTest).
# rmlm <- mixed(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#               (VI1_Intra-pps * VI2_Intra-pps | pp_number),
#               dados)

Recomendo usarem a função mixed() do pacote afex (Singman et al., 2021) confirma automaticamente que temos os contrastes dummy para variáveis factoriais, reporta os dados numa tabela da ANOVA.

Para além disso, permite criar modelos sem correlações nos efeitos aleatórios mesmo para variáveis factoriais, permite computação paralela (mais rápida) e o uso de vários algoritmos de optimização (escolhendo o melhor, ver Singman et al., 2021).

29.11.2 Estímulos Como Efeitos Aleatórios

# Se quisermos adicionar um intercepto variável por estímulo
# rmlm <- mixed(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#               (VI1_Intra-pps * VI2_Intra-pps | pp_number) + (1 | stimuli),
#               dados)

# Se tivermos VIs "nested" no estímulo (intercepto mais slopes por estímulo)
# rmlm <- mixed(VD ~ VI1_Intra-pps * VI2_Intra-pps * VI3_Entre-pps +
#               (VI1_Intra-pps * VI2_Intra-pps | pp_number) +
#               (VI1_Intra-pps * VI2_Intra-pps | stimuli), dados)

29.12 Exemplo (passo–a–passo)

Começamos por importar os pacotes que vamos usar o tidyr (Wickman, 2021)e o afex (Singman et al., 2021) e definir os defaults “do costume”.

library(afex)
library(tidyr)

options(contrasts = c("contr.sum", "contr.poly"))
afex_options(es_aov = "pes")

29.12.1 Formato Longo

Se tivermos os dados no formato largo/“wide”—cada linha corresponde a um participante e cada medida repetida tem várias colunas, uma por cada medição. a uma coluna—temos que primeiro os passar para o formato longo.

No formato longo cada participante terá mais que uma na linha na base de dados, uma linha por medição repetida (ensaio) e uma só coluna por variável.

library(tidyr)

# Importa os dados
# dados <- read.csv(file.choose()) # interface gráfica
dados <- read.csv("../data/demo_ds.csv")

# Converte o número de participante para factor/categórica
dados$pp_number <- as.factor(dados$pp_number)

# Passa os dados para o formato longo 
dados <- pivot_longer(dados, c("phase1", "phase2"), names_to = "phase")

29.12.2 ANOVA e Modelo Misto

# Se todos os participantes fizeram todas as fases.
raov <- aov_car(rts ~ phase * gender +
                Error(pp_number / phase), dados)

# O modelo misto equivalente (intercepts e slopes por condição e pp).
rmlm <- mixed(rts ~ phase * gender + (phase | pp_number), dados)

29.13 Testes Post-Hoc

Tecnicamente falando não precisamos de nenhuma função especial para computar testes pós-hoc.

Por exemplo, se tivermos três condições experimentais podemos computar todos os pares possíveis de testes-t excluindo os participantes de cada grupo de cada vez e corrigindo os valores-p.

Mesmo assim pode ser útil fazer isso automaticamente ou seguindo o teste de Tukey. Podemos fazer isso usando apenas funções base do R (Opção 1) ou podemos usar mais alguns pacotes para nos ajudarem (Opção 2).

29.13.1 Opção 1

raov <- aov(rts ~ genero * cond, ds)
# Ou com aov_car do pacote afex
# raov <- aov_car(rts ~ genero * cond + Error(pp_number), ds) 

# Todos os pares possíveis de testes-t.
# (por default aplica a correcção holm aos valores-p mas faz outras).
pairwise.t.test(ds$rts, ds$cond)

# Testes tukey e respectiva correcção (só funciona em objectos aov)
TukeyHSD(raov$aov, "cond")

29.13.2 Opção 2

library(afex) # Para aov_car
library(emmeans) # Para testes post-hoc (inclusivé para multinível)

raov <- aov_car(rts ~ genero * cond + Error(pp_number), ds)

# Calcula as médias por grupo e respectivos Intervalos de confiança.
psthc_IC <- emmeans(raov, ~ genero)

# Computa os testes post-hoc de todos os pares possíveis.
psthc_ps <- pairs(psthc_IC)

Recomendo esta opção porque o pacote emmeans (Russel & Lenth, 2021) permite continuarmos a usar a mesma sintaxe para especificar fórmulas dos testes post-hoc . Para além disso, este pacote também produz as médias por grupo (respectivos intervalos de confiança e testes pós-hoc) quando lhe fornecemos outputs de modelos mistos (mas os testes pós-hoc são computados da mesma forma que seriam para uma ANOVA).

29.14 Pressupostos

Se todos os testes-t, ANOVAs, ANCOVAs, regressões lineares, correlações, são todos modelos lineares, então partilham os mesmos pressupostos.

Mais concretamente, todos partilham o pressupostos de que os resíduos (os erros) do modelo seguem a distribuição normal—pressuposto da normalidade.

Para os testes com VIs categóricas, aplica-se ainda o pressupostos de que a variância da VD é semelhante em todos os níveis da VI—pressuposto da homocedasticidade.

Para os testes com medidas repetidas, aplica-se o pressuposto de que a variância nas várias medidas repetidas são semelhantes—esfericidade (Wikipedia, 2021) e não se aplica o da homocedasticidade.

Os livros de estatística tradicionais dizem que devemos testar se os nossos dados cumprem os pressupostos e usar estatística não–paramétrica caso não cumprem. Contudo, uma abordagem mais sensata pode ser usar logo à partida testes estatísticos robustos a violação de pressupostos (Field & Wilcox, 2017). Se os testes robustos não diferirem dos testes convencionais podemos reportar os resultados dos testes convencionais só para manter a secção de resultados mais acessível para os leitores. Se os testes diferirem no padrão de resultados podemos citar (Field & Wilcox, 2017) para explicar que essa diferença indica que os pressupostos foram violados e que por isso reportamos os resultados dos testes robustos.

Nota: Neste último parágrafo descrevi o que me parece ser uma abordagem sensata à questão mas não tenho ideia de como seria recebida por revisores duma revista científica.

29.14.1 Testar Pressupostos

library(afex) # para aov_car(), test_levene() e test_spherecity()

# A função aov_car precisa do termo de erro mesmo sem medidas repetidas.
modelo <- aov_car(rts ~ genero * cond + Error(pp_number), ds)

erros <- residuals(modelo)

# Pressuposta 1 - normalidade dos resíduos
hist(erros) # histograma para inspecção visual
qqnorm(erros) # QQ-plot  para inspecção visual
shapiro.test(erros) # teste de Shapiro-Wilk à normalidade 

# Pressuposto 2 - Homocedasticidade - teste de Levene
test_levene(modelo) # só aplicável para medidas entre-participantes

# Pressuposto 3 - Esfericidade  - teste de Maulchy
#test_sphericity(modelo) # só aplicável para medidas repetidas

29.15 Robustas (exemplos)

29.15.1 Exemplo (passo–a–passo)

# Alternativas robustas (adapt. Field & Wilcox, 2017)
library(WRS2) # Testes robustos
library(robustbase) # lmrob() alternativa robusta a lm()

options(contrasts = c("contr.sum", "contr.poly"))

m <- rts ~ genero * cond

# Modelo de ANOVA tradicional (estimador OLS)
aov(m, dados)

# ANOVA factorial às medianas (e não às médias)
t2way(m, dados)

# Modelo linear com estimador robustos
f <- lmrob(m, dados, settings = "KS2014")
summary(f)

29.15.2 Cheat Sheet (Robustas sem MR)

Adapt. (Field & Wilcox, 2017)

Todas as funções para estas estatísticas robustas vêm do pacote WRS2 (Mair & Wilcox, 2020).

Nome convencional robusta com bootstrapping nome
t-test de uma amostra lm(VD ~ 1) lmrob(VD ~ VI) onesampb(VD) one-sample percentile bootstrap
t-test duas amostras lm(VD ~ VI) yuen(VD ~ VI) yuenbt(VD ~ VI) Teste Yuen às medias aparadas
One-Way ANOVA lm(VD ~ VI1_cat) t1way(VD ~ VI_cat) t1waybt(VD ~ VI_cat) ANOVA hetereocedática a médias aparadas
ANOVA 2X2 lm(VD ~ VI1_cat * VI2_cat) t2way(VD ~ VI1_cat * VI2_cat) X ANOVA robusta a médias aparadas, estimadores M ou medianas
ANCOVA 1 VI + 1 Cov lm(VD ~ VI + Cov) ancova(VD ~ VI + Cov) ancboot(VD ~ VI + Cov) ANCOVA robusta a médias aparadas

Nota: É necessário acrescentar um argumento data = variave_com_os_dados em quase todas as funções mencionadas.

29.15.3 Cheat Sheet (Robustas com MR)

Adapt. (Field & Wilcox, 2017)

Todas as funções para estas estatísticas robustas vêm do pacote WRS2 (Mair & Wilcox, 2020).

Nome convencional robusta nome
t-test duas amostras emparelhadas aov(VD ~ VI + Error(pp_number/VI)) yuend(VD ~ VI) Teste Yuen às medias aparadas
One-Way ANOVA aov(VD ~ VI1 + Error(pp_number / VI)) rmanova(dados\(VD, dados\)VI_cat, dados$pp_number) ANOVA hetereocedática a médias aparadas
ANOVA 2X2 Mista (uma VI entre-pp) avo(VD ~ VI1_intra * VI2_entre + Error(pp_number / VI_intra)) bwtrim(VD ~ VI1_cat * VI2_cat) ANOVA 2X2 mista a médias aparadas

29.15.4 Robustas (outros modelos)

Para que não estejam listados nestas cheat sheets podemos recorrer à função lmrob() do pacote robustbase (Maechler et al., 2021) para todos os modelos lineares sem medidas repetidas (Field & Wilcox, 2017). Para modelos com medidas repetidas podemos especificar o modelo como modelo misto, depois em vez de usarmos a função lmer() ou mixed() usamos a função rlmer() do pacote robustlmm (Koller, 2016, ver Field & Wilcox, 2017).

Em ambos os casos temos de garantir que definimos o tipo de contrates correcto para as variáveis factoriais—options(contrasts = c("contr.sum", "contr.poly"))

No caso de modelos do lmrob() não conheço forma de obter a tabela da ANOVA para estes modelos com somas de quadrados tipo III. Mesmo assim, as diferenças entre resultados com as diferentes somas de quadrados só devem ser relevantes em casos em que os grupos não estão balanceados.

No caso de modelos rlmer(), podemos obter a tabela da ANOVA dos efeitos fixos com a função parameters() ou model_parameters() do pacote parameters (Lüdecke et al., 2020). Não posso garantir mas penso que esses Fs e valores-p serão computados usando a soma de quadrados tipo III. No entanto, não sei de nenhuma forma de computar etas parciais quadrados para estes modelos.

Ainda no caso dos modelos mistos, se (1) usaram a função mixed() ou lmer_alt do pacote afex (Singman et al., 2021) para computar o vosso modelo misto, (2) esse vosso modelo inclui “||” para suprimir as correlações num termo de efeitos aleatórios com variáveis factorais, (3) usando o argumento expand_re = TRUE, então não poderão usar a mesma fórmula do modelo e dados na função rlmer(). Mesmo que estas três condições estejam reunidas ainda podem computar o vosso modelo usando rlmer(). Para isso têm de ver o atributo data do output da função mixed e fixar as colunas que comecem por re1 (ou reX X = número do termo aleatório). Depois terão de usar essas variáveis para especificar o modelo no termo de erro equivalente na função rlmer() e substituir os dados pelo atributo data do output da função mixed(). Dado que é um caso muito específico entrem em contacto comigo se isso acontecer que eu tento ajudar.

29.16 Recursos

29.16.2 Sites Úteis

29.16.3 Shiny Apps

29.16.4 Outros Temas