MOC
Como Usar o R na Psicologia e Áreas Relacionadas
2024-12-10
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:
- Introdução ao Livro (estamos aqui)
-
Introdução ao
R
- Teoria
-
Análises no
R
-
Fazer Coisas Lindas no
R
-
Trabalhar Eficazmente no
R
- 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
Abra o
R
numa janela e coloque-o lado a lado da janela do programa onde está a ler este livro.Leia este capítulo com atenção. Quando vir blocos de código destacados:
Escreva, no R
, sem fazer copy-paste, exactamente o que está no
bloco.
Por favor, evite mesmo fazer copy paste do código para o
R
. A ideia é praticar escrever correctamente os comandos noR
sem cometer erros.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.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:
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.
Vemos que a função mean computa a média do objecto que lá colocarmos.
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).
Neste caso o R
não dá erro nenhum mas o resultado não está certo.
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:
Não comecem com um número (e.g.,
var1
nunca1var
).Não coloquem espaços no nome da variável (e.g.,
base_de_dados
nuncabase de dados
).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…”
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:
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:
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
.
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.
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á só ao cinema, só 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.
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()
.
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.
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!”.
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.
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.
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.
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.
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.
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.
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
).
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.
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.
À 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.
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.
Agora que há um default para y
podemos invocar a função
especificando apenas o valor de x
.
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.
Contudo, sendo que x
não tem um valor default teremos sempre de
especificar x
, se especificarmos só y
dá erro.
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.
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
.
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()
.
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.
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.
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.
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.
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.
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.
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:
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
ouFALSE
).
Depois podiam ir ler o manual da função para tirar as dúvidas.
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).
Eu poderia também escrever uma função equivalente para o desvio padrão.
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
.
Asssim sendo eu posso usar essa nova função para substituir as anteriores.
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.frame
s. 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 TRUE
s ou FALSE
s 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.
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.
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")
.
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.
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.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.
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.
6.7 A Familía apply
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.
Notem que isto evita ainda mais a necessidade de for
loops porque com
o mapply()
anterior escrevemos numa só linha o seguinte for
loop.
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.
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
oux^y
.
- dica:
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.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.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.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:
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.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.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.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
eidade[2] * 2
?
6.9.16.1 Soluções
Clique para ver as soluções
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()
- dica:
- Compute o desvio padrão dos tempos de resposta.
- dica:
sd()
- dica:
- Compute o
t.test()
dos tempos de resposta.
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
- dica:
Obtenha as primeira linha das duas primeiras colunas dos
dados
.
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
dodata.frame
dados.
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:
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.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.frame
s. Vamos ver que podemos representar
a maioria das nossas bases de dados usando estas estruturas.
Compreender bem como trabalhar com data.frame
s é 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:
Para aceder à primeira linha do data.frame
dados
(que criámos há
pouco) basta escrevermos:
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:
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:
Também podemos pedir várias linhas, usando a mesma notação, por exemplo:
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.
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:
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.
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:
Se quisermos a primeira linha da duas primeiras colunas, escrevemos:
Podemos também usar um vector com os nomes das colunas que queremos:
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.frame
s só têm duas.
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()
.
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.
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.
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:
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.
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.
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.
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.
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:
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:
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:
7.8.5 Mudar os Nomes das Colunas.
Já vimos que para apagar os nomes das linhas podíamos escrever:
Da mesma forma, se quisermos apagar os nomes das colunas, podemos escrever:
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:
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.frame
s (mesmo que sejam bases de dados distintas) ao mesmo tempo.
Se tivermos dois data.frame
s 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.frame
s.
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.frame
s 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.frame
s lado a lado que tenham números de
linhas diferentes, ou se quisermos juntar pelas linhas (i.e., rbind()
)
data.frame
s 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.3 Exercício 2
Crie uma nova coluna chamada
metade
que guarde os resultados da divisão dos valores dacoluna3
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.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 colunacoluna1
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 nacoluna3
.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:
Não basta saber o nome do ficheiro, precisamos de saber o caminho que temos de “percorrer” para chegar até ele
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á.
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 IDE
s 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
Garantir que tenho sempre uma cópia/backup de bases de dados e ficheiros importantes.
Tenho de guardar o output da função que lê o ficheiro numa variável.
Os objectos/variáveis não são ficheiros e os ficheiros não são objectos/variáveis.
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:
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.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.
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.
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.
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.frame
s. 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.
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 10322.433 10719.820 11795.980
2 pp02 9837.182 8758.178 9542.775
3 pp03 9275.711 8084.972 9542.563
4 pp04 9950.420 11769.172 13360.829
5 pp05 9579.795 12437.993 11671.804
6 pp06 9699.295 9599.901 9837.668
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.frame
s
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.
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 10322.433 10719.820 11795.980 controlo 10.322433 10.719820 11.795980
2 pp02 9837.182 8758.178 9542.775 controlo 9.837182 8.758178 9.542775
3 pp03 9275.711 8084.972 9542.563 controlo 9.275711 8.084972 9.542563
4 pp04 9950.420 11769.172 13360.829 controlo 9.950420 11.769172 13.360829
5 pp05 9579.795 12437.993 11671.804 controlo 9.579795 12.437993 11.671804
6 pp06 9699.295 9599.901 9837.668 controlo 9.699295 9.599901 9.837668
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.
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.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
.
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 10322.433 10719.820 11795.980 controlo 10.322433 10.719820 11.795980
2 pp02 9837.182 8758.178 9542.775 controlo 9.837182 8.758178 9.542775
3 pp03 9275.711 8084.972 9542.563 controlo 9.275711 8.084972 9.542563
4 pp04 9950.420 11769.172 13360.829 controlo 9.950420 11.769172 13.360829
5 pp05 9579.795 12437.993 11671.804 controlo 9.579795 12.437993 11.671804
6 pp06 9699.295 9599.901 9837.668 controlo 9.699295 9.599901 9.837668
t1_centrada t2_centrada t3_centrada t1_log t2_log t3_log
1 1.7193599 0.2130562 0.4126344 9.242075 9.279850 9.375514
2 0.1406540 -1.0999892 -1.0516477 9.193925 9.077743 9.163540
3 -1.6860233 -1.5506063 -1.0517853 9.135155 8.997762 9.163517
4 0.5090622 0.9154507 1.4295771 9.205370 9.373239 9.500082
5 -0.6967232 1.3631331 0.3319372 9.167411 9.428511 9.364931
6 -0.3079439 -0.5365733 -0.8600065 9.179808 9.169508 9.193974
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).
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 10322.
2 pp01 t2 10720.
3 pp01 t3 11796.
4 pp02 t1 9837.
5 pp02 t2 8758.
6 pp02 t3 9543.
7 pp03 t1 9276.
8 pp03 t2 8085.
9 pp03 t3 9543.
10 pp04 t1 9950.
# 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()
.
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 10322.
2 pp01 t2 10720.
3 pp01 t3 11796.
4 pp02 t1 9837.
5 pp02 t2 8758.
6 pp02 t3 9543.
7 pp03 t1 9276.
8 pp03 t2 8085.
9 pp03 t3 9543.
10 pp04 t1 9950.
# 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 10322.
2 pp01 controlo t2 original 10720.
3 pp01 controlo t3 original 11796.
4 pp01 controlo t1 seg 10.3
5 pp01 controlo t2 seg 10.7
6 pp01 controlo t3 seg 11.8
7 pp01 controlo t1 centrada 1.72
8 pp01 controlo t2 centrada 0.213
9 pp01 controlo t3 centrada 0.413
10 pp01 controlo t1 log 9.24
# 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.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.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
elab
(no fundo uma variável com a interacção das duas).As funções
paste()
epaste0()
permitem “colar”strings
com e sem separadores entre elas, respectivamente.Crie duas colunas
i_phase1
ei_phase2
com os valores daphase1
ephase2
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.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
ephase2
e outra com a medida (i.e.,phase1
ouphase2
). Ou seja, a base de dados passará a ter duas linhas por participante, uma com as suas resposta naphase1
e outra com as respostas naphase2
. 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.
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.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/clusters—k = 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
- Nominais:
- 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.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.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.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.3 Neyman & Pearson
Konrad Jacobs, Erlangen, Copyright: MFO - Mathematisches Forschungsinstitut Oberwolfach,https://opc.mfo.de/detail?photo_id=3044; License CC BY-SA 2.0 de
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.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.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.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.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.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.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.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.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.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.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.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.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.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.7 Modelos Com Múltiplas VIs Contínuas
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.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
F global: modelo como um todo reduz o erro?
F NFilhos: efeito da VI1/NFilhos controlando para VI2/Conflito?
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.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.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.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 pacotecar
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, elibrary(car)
para o colocar no seu ambiente.
- Dica: Use
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()
.
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.
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.!
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:
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:
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).
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:
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
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.
17.3.1.5 Juntando as Interacções
# 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
):
Exemplo: tempos de resposta (rts
) em FUNÇÃO (~
) do género (gender
).
Variável dependente em função dos efeitos principais das VIs:
Exemplo: rts (rts
) em função (~
) do efeito principal do género
(gender
) + efeito principal da condição experimental (cond
).
VD em função dos efeito principais das VIs + efeitos de interacção entre as VIs
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
).
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
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
).
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) .
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
Para dados com temos medidas intra-participantes temos de especificar um termo de erro.
O R implica com missings em ANOVAs com medidas intra-pps.
O R usa outro tipo de contrastes que não os cuja soma dá 0.
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.
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.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.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.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
eafex
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
ephase2
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 pacoteafex
.
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.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
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.
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.!
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
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.
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.
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.
Remember Chapter 10? Keep that analysis close, we will work on it.
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.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
- 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.
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
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:
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:
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.
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?
24.4.5 Table chunks
We have already seen text tables, but with knitr
we can work on having a better looking display
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)))
```
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
- It has to be beautiful, please.
- Title and name
- Build 3 sections
- The random introduction
- Descriptives
- Analysis
- 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
- In a subsection called “The nerds”:
- In Descriptives:
- Make a table with mean RT per group.
PRO TIP: here is where the chunk options start to play
- Make a table with mean RT per group.
- 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.2 Script de Instalação de Pacotes
Exemplo de script de instalação de pacotes usados neste livro:
packages <- c("afex", "bookdown", "car", "DT", "effectsize", "emmeans",
"ggplot2", "knitr", "nortest", "palmerpenguins", "parameters",
"patchwork", "performance", "psych", "readxl", "revealjs",
"rmarkdown", "robustbase", "see", "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:
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
.
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
- A pergunta de investigação.
- Qual o tema da minha tese…
- Conhecer a literatura da área.
- O design da minha investigação.
- O que é que eu fiz para a tese…
- Qual o procedimento.
- Quais as variáveis.
- A operacionalização da hipótese.
- A hipótese propriamente dita…
- Que variáveis espero que afectem quais e como.
- 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.
- 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?
- 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?
- 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?
- 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
One Formula to Rule Them All: Mini–resumo téorico
One Formula Syntax to Rule Them All: Explicação da sintaxe das fórmulas do R
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()
Sheet (Medidas Repetidas: MR): tabela - nome do teste | computação no R -
aov()
Cheat Sheet (MR - modelos mistos): tabela - nome do teste | modelo misto equivalente
Nuances: Defaults problemáticos do R
Soluções: Como redefinir ou contornar defaults problemáticos
- Opção 1: com os pacotes
car
eeffectsize
- Opção 2: com os pacotes
parameters
eeffectsize
- Opção 3 (mais simples): só com o pacote
afex
Pacotes Úteis: Lista de pacotes e respectiva utilidade
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.
- 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
- 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
- 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
):
Exemplo: tempos de resposta (rts
) em FUNÇÃO (~
) do género (gender
).
Variável dependente em função dos efeitos principais das VIs:
Exemplo: rts (rts
) em função (~
) do efeito principal do género
(gender
) + efeito principal da condição experimental (cond
).
VD em função dos efeito principais das VIs + efeitos de interacção entre as VIs
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
).
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
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
).
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) .
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
Para dados com temos medidas intra-participantes temos de especificar um termo de erro.
O R implica com missings em ANOVAs com medidas intra-pps.
O R usa outro tipo de contrastes que não os cuja soma dá 0.
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.
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.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.1 Gerais
Judd e colaboradores RECOMENDO
Lindeløv, 2019 RECOMENDO
-
- PsyTeachR level 3 RECOMENDO
Singman e Kellen (2019) RECOMENDO
Modelos Lineares - Shiny App RECOMENDO
29.16.2 Sites Úteis
- Tutoriais:
- Q&As:
- Youtube