Agora que já temos uma base sobre manipulação de dados no R, vamos ver algumas análises de dados, normalmente utilizadas em estudos ecológicos.
- Análises de variância;
- Correlações
- Regressões
Iris é um conjunto de dados morfológicos de três espécies de plantas do gênero Iris disponível no R muito acessível para testes e treinamentos.
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa
Para um conjunto de dados com várias espécies (ou outro tipo de grupos) a função agragate facilita o cáculo de diferentes estatísticas
## Group.1 x
## 1 setosa 5.006
## 2 versicolor 5.936
## 3 virginica 6.588
## Group.1 x
## 1 setosa 0.1242490
## 2 versicolor 0.2664327
## 3 virginica 0.4043429
## Group.1 x
## 1 setosa 0.3524897
## 2 versicolor 0.5161711
## 3 virginica 0.6358796
Visualização dos dados: Um plot violino básico
Primeiramente vamos visitar o site: https://www.data-to-viz.com/ Aqui temos uma coleção de tipos de gráficos e os códigos para fazê-los no R.
##
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
##
## combine
Também podemos usar plots violinos para representar duas variáveis. Vamos usar novamente os dados das plantas Iris para visualizar:
aes: função que determina a estética do seu plot fill: argumento de preenchimento do plot - os grupos de interesse.
Podemos melhorar o nosso gráfico:
p <- ggplot(iris, aes(x=Species, y=Sepal.Length, fill=Species)) +
geom_violin()+
xlab("")+
ylab("comprimento")+
annotate("text", x=1, y =7.7, label = "Sépala", size = 5)+
theme(legend.position = "none")
p2 <- ggplot(iris, aes(x=Species, y=Petal.Length, fill=Species)) +
geom_violin()+
xlab("")+
ylab("")+
annotate("text", x=1, y =6.55, label = "Pétala", size = 5)+
theme(legend.position = "none")## png
## 2
Testes de hipóteses
1. One-way Anova
ANOVA one-way (unidirecional): Quando há uma única variável independente categórica (também conhecida como fator) e uma única variável dependente contínua. Uma análise de variância procura testar variações significativas nas médias da variável dependente (y) ao longo dos níveis da variável independente (x).
Vamos testar se diferentes dietas em galinhas tem efeito no peso dos animais:
## weight Time Chick Diet
## 1 42 0 1 1
## 2 51 2 1 1
## 3 59 4 1 1
## 4 64 6 1 1
## 5 76 8 1 1
## 6 93 10 1 1
## [1] 578 4
## Classes 'nfnGroupedData', 'nfGroupedData', 'groupedData' and 'data.frame': 578 obs. of 4 variables:
## $ weight: num 42 51 59 64 76 93 106 125 149 171 ...
## $ Time : num 0 2 4 6 8 10 12 14 16 18 ...
## $ Chick : Ord.factor w/ 50 levels "18"<"16"<"15"<..: 15 15 15 15 15 15 15 15 15 15 ...
## $ Diet : Factor w/ 4 levels "1","2","3","4": 1 1 1 1 1 1 1 1 1 1 ...
## - attr(*, "formula")=Class 'formula' language weight ~ Time | Chick
## .. ..- attr(*, ".Environment")=<environment: R_EmptyEnv>
## - attr(*, "outer")=Class 'formula' language ~Diet
## .. ..- attr(*, ".Environment")=<environment: R_EmptyEnv>
## - attr(*, "labels")=List of 2
## ..$ x: chr "Time"
## ..$ y: chr "Body weight"
## - attr(*, "units")=List of 2
## ..$ x: chr "(days)"
## ..$ y: chr "(gm)"
dados_ch$peso_idade<-dados_ch$weight/dados_ch$Time #criando uma nova coluna de dados
hist(dados_ch$peso_idade) #chacando a distribução dos dados criadosEncontramos um erro, o modelo não pode ser criado porque a variável y possui valores não-finitos. Vamos checar:
##
## FALSE TRUE
## 50 528
50 valores não podem ser analisados como estão. Uma solução é a transformação de dados. Outra é a remoção destas amostras, que é o que vamos fazer
#Vamos transformar todos os dados em NA
dados_ch$peso_idade[!is.finite(dados_ch$peso_idade)] <- NA
#E remover os NAs de todo o conjunto de dados
dados_ch<-na.omit(dados_ch)
# Testando se funcionou:
table(is.finite(dados_ch$peso_idade))##
## TRUE
## 528
E agora sim podemos criar nosso modelo usando a função aov:
model <- aov(dados_ch$peso_idade ~ dados_ch$Diet)
# E ver os resultados usando a função summary:
summary(model)## Df Sum Sq Mean Sq F value Pr(>F)
## dados_ch$Diet 3 756 252.0 11.35 3.19e-07 ***
## Residuals 524 11634 22.2
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
O ajuste dos dados ao modelo indica o quanto o tipo da dieta explica sobre a variabilidade no ganho de peso. Olhando os resultados acima e considerando os seguintes resultados:
Df: Os graus de liberdade do modelo Sum Sq: soma dos quadrados - é a variância que o modelo não explicou. Mean Sq: media ao quadrado - é a variância explicada por cada componente . F-value: estatística F - é a medida usada para comparar as médias ao quadrados dentro e entre os grupos. Pr(>F): Tvalor de p da estatística F = significância estatística Residuals: Desvio relativo da média do grupo.
Nós podemos concluir, portanto que existem diferenças significativas entre os tipos de tratamento.
Comparações múltiplas pareadas entre as médias de grupo:
Um p-value significativo indica a diferença entre os grupos, mas ainda precisamos definir estes grupos. As comparações pareadas são usadas para identificarmos as diferenças nas médias entre certos pares de grupos e se estas são estatisticamente significativas.
Uma das formas de realizarmos estas comparações par-a-par é utilizando o teste de Tukey HSD (Tukey Honest Significant Differences) com a função TukeyHSD
Apenas a diferença entre os grupos 1-3 e 1-4 são significativamente diferentes.
Outra forma de testarmos as diferenças entre as médias dos grupos é utilizando um teste T
##
## Pairwise comparisons using t tests with pooled SD
##
## data: dados_ch$peso_idade and dados_ch$Diet
##
## 1 2 3
## 2 0.036 - -
## 3 8.3e-06 0.036 -
## 4 8.3e-06 0.036 0.945
##
## P value adjustment method: BH
A utilização de cada um destes métodos (e de outros) depende muito dos seus dados, principalmente da distribuição. Nesta página temos um resumo sobre as diferentes casos para aplicação dos testes: https://statplace.com.br/blog/qual-o-melhor-teste-para-a-comparacao-de-medias/
Plotando os resultados da ANOVA
Para plotar este resultados vamos um pouquinho mais longe. A função multcompLetters4 (do pacote…) cria letras de acordo com as categorias de similaridades baseadas nos resultados da ANOVA e do teste Tukey.
## $`dados_ch$Diet`
## 3 4 2 1
## "a" "a" "ab" "b"
Agora vamos criar uma tabela com os fatores
Tk <- group_by(dados_ch, Diet) %>%
summarise(mean=mean(peso_idade), quant = quantile(peso_idade, probs = 0.75)) %>%
arrange(desc(mean))E incluir o display de letras na tabela criada
ggplot(dados_ch, aes(Diet, peso_idade)) +
geom_boxplot()+
labs(x="Tipo de dieta", y="Peso por idade") +
theme_bw() +
theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
geom_text(data = Tk, aes(x = Diet, y = quant, label = cat), size = 4, vjust=-1, hjust =-1)Testando os resíduos
Homogeneidade das variâncias
Normalidade
Levene’s Test (center = median)
2. Anova two-way
Um teste de ANOVA bivariada é usada para avaliar o efeito simultâneo de duas variáveis preditoras categóricas (x) em uma variável resposta (y)
Tratamentos: control, A, B. gender: F, M phase: pre, post, fup.
##
## pre post fup
## control 25 25 25
## A 20 20 20
## B 35 35 35
A construção do seu modelo depende das suas hipóteses!
## Df Sum Sq Mean Sq F value Pr(>F)
## treatment 2 186.7 93.37 24.39 2.38e-10 ***
## phase 2 167.5 83.75 21.87 1.94e-09 ***
## Residuals 235 899.8 3.83
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Df Sum Sq Mean Sq F value Pr(>F)
## treatment 2 186.7 93.37 26.216 5.48e-11 ***
## phase 2 167.5 83.75 23.514 5.07e-10 ***
## treatment:phase 4 77.0 19.25 5.405 0.000354 ***
## Residuals 231 822.8 3.56
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## Df Sum Sq Mean Sq F value Pr(>F)
## phase 2 167.5 83.75 27.32 2.22e-11 ***
## treatment 2 186.7 93.37 30.46 1.80e-12 ***
## gender 1 58.3 58.29 19.01 1.95e-05 ***
## treatment:gender 2 130.2 65.12 21.24 3.38e-09 ***
## Residuals 232 711.2 3.07
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
ggplot(OBrienKaiserLong) +
aes(x = treatment, y = score, fill = phase) +
geom_boxplot()+
scale_fill_brewer(viridis(3))+
guides(fill=guide_legend(title="Phases"))3. Correlação e Regressão
Enquanto uma correlação mede a força ou grau de relação entre duas variáveis. A regressão é uma equação (modelo) que descreve essa relação com valores específicos.
Testando a regra de Bergman em Aves.
A regra de Bergman identifica um padrão de diminuição do tamanho corporal entre as espécies com a diminuição da latitude. Espécies com latitudes mais próximas de zero teriam menor tamanho de corpo.
Dados utilizados: <AVONET: morphological, ecological and geographical data for all birds>[https://onlinelibrary.wiley.com/doi/full/10.1111/ele.13898]
aves<-read.csv("C:/Users/raque/OneDrive - ufpr.br/Área de Trabalho/Curso_R_basico_CAEB/Aves_dados.csv")Vamos criar um conjunto de dados somente com os traits que vamos utilizar.
Vamos checar se existe algum nome duplicado na mossa coluna com os nomes das espécies:
##
## FALSE
## 9855
Conferindo a dimensão dos nossos dados:
## [1] 9855 4
Temos 9855 especies para realizar as análises
Teste de Correlação
Para o teste vamos utilizar a função cor_res:
##
## Pearson's product-moment correlation
##
## data: abs(dat$Centroid.Latitude) and log(dat$Mass)
## t = 17.11, df = 9853, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.1506242 0.1889728
## sample estimates:
## cor
## 0.1698628
Legal! Encontramos uma correlação entre as variáveis. Agora, uma regreção vai nos mostrar a direção desta correlação (para sabermos se nossa hipótese pode ser aceita) e principalmente o quanto um modelo linear explica o padrão dos dados.
3. Regressão linear
A função lm calcula uma equação (um modelo linear) com base nos dados e, então com base no ajuste dos dados, consegue demonstrar o quanto uma variável (x) pode explicar da outra (y).
O ~ é utilizado para separar as variáveis y e x, nesta ordem. No nosso caso, imaginamos que a latitude (correspondente as variações climaticas e ambientais) terá uma influência no tamanho das espécies. Então latitude=X, Massa=Y
##
## Call:
## lm(formula = log(dat$Mass) ~ abs(dat$Centroid.Latitude))
##
## Residuals:
## Min 1Q Median 3Q Max
## -3.4593 -1.1857 -0.3099 0.9317 7.9517
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.606679 0.023519 153.35 <2e-16 ***
## abs(dat$Centroid.Latitude) 0.017756 0.001038 17.11 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.553 on 9853 degrees of freedom
## Multiple R-squared: 0.02885, Adjusted R-squared: 0.02875
## F-statistic: 292.7 on 1 and 9853 DF, p-value: < 2.2e-16
Apesar de observarmos uma correlação significativa, algumas estatísticas nos mostram que o poder de explicação da latitude no tamanho dos organismos é muito baixo (R²=2.8%). O valor do estimate também é baixo (0.017756) mas positivo, mostrando um aumento na massa, com o aumento da latitude central da espécie.
Plotando
plot(abs(dat$Centroid.Latitude), log(dat$Mass), xlab= "Latitude central absoluta", ylab = "Massa corporal (log)", pch=20)
abline(reg_res, col="red", lwd= 2)Para fins exploratórios, vamos fazer um plot único com todas as retas de regressão para cada ordem.
ggplot(dat,aes(abs(Centroid.Latitude), log(Mass), colour=factor(order))) +
geom_point() +
geom_smooth(method='lm')+
theme(legend.position = "none") #este comando remove a legenda## Warning in qt((1 - level)/2, df): NaNs produzidos
## Warning in qt((1 - level)/2, df): NaNs produzidos
## Warning in qt((1 - level)/2, df): NaNs produzidos
## Warning in max(ids, na.rm = TRUE): nenhum argumento não faltante para max;
## retornando -Inf
## Warning in max(ids, na.rm = TRUE): nenhum argumento não faltante para max;
## retornando -Inf
## Warning in max(ids, na.rm = TRUE): nenhum argumento não faltante para max;
## retornando -Inf
Existe uma varição entre as retas calculadas pelo modelo linear para cada ordem. Vamos então usar um loop para calcular as estatísticas (p-value, slope e R²) para cada ordem com um número mínimo de espécies (50 espécies)
Primeiro vamos filtrar as ordens com mais de 50 espécies:
## [1] 19
Criando uma tabela para guardar os resultados:
tab_res<-data.frame(matrix(ncol = 4, nrow = 19))
colnames(tab_res) <- c("Order", "p_value", "slope", "R2")
rownames(tab_res) <- orders$Var1
tab_res## Order p_value slope R2
## Accipitriformes NA NA NA NA
## Anseriformes NA NA NA NA
## Apodiformes NA NA NA NA
## Bucerotiformes NA NA NA NA
## Caprimulgiformes NA NA NA NA
## Charadriiformes NA NA NA NA
## Columbiformes NA NA NA NA
## Coraciiformes NA NA NA NA
## Cuculiformes NA NA NA NA
## Falconiformes NA NA NA NA
## Galliformes NA NA NA NA
## Gruiformes NA NA NA NA
## Passeriformes NA NA NA NA
## Pelecaniformes NA NA NA NA
## Piciformes NA NA NA NA
## Procellariiformes NA NA NA NA
## Psittaciformes NA NA NA NA
## Strigiformes NA NA NA NA
## Suliformes NA NA NA NA
Em loop simples uma variável pode ser substituida por uma sequencia de (no nosso caso) números, a cada rodada o i é substituído pelo número seguinte.
Note que nós poderíamos fazer o mesmo código abaixo 19 vezes, substituindo o nome das ordens e assim preencher a tabela com os resultados:
dat_o<-subset(aves, order=="Accipitriformes") #esta ordem é a primeira da lista
reg<-lm(log(dat_o$Mass)~abs(dat_o$Centroid.Latitude))
res<-summary(reg)
# cada uma dos seguintes códigos vai salvar o objeto seguinte ao <- na respectiva coluna ($Order, $p_value, $slope e $R2) e linha correspondente ordem (linha 1, neste caso).
tab_res$Order[1]<- "Accipitriformes"
tab_res$p_value[1] <- res$coefficients[2,4]
tab_res$slope[1] <- res$coefficients[2,1]
tab_res$R2[1] <- res$r.squared*100
res$coefficients## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 6.3920230 0.099990768 63.926132 3.878778e-153
## abs(dat_o$Centroid.Latitude) 0.0130809 0.004212123 3.105535 2.126697e-03
A parte res\(coefficients corresponde ao local onde está o p-value dentro do resultado da regressão (linha 2, coluna 4). res\)coefficients[2,1] é onde está o valor do estimate (slope) dentro do resultado da regressão (linha 2, coluna 1) res$r.squared é onde está o valor do R² (slope) dentro do resultado da regressão. Podemos multiplicar o valor por 100 e obter a % de explicação.
Veja que só a primeira linha foi preenchida:
## Order p_value slope R2
## Accipitriformes Accipitriformes 0.002126697 0.0130809 3.847822
## Anseriformes <NA> NA NA NA
## Apodiformes <NA> NA NA NA
## Bucerotiformes <NA> NA NA NA
## Caprimulgiformes <NA> NA NA NA
## Charadriiformes <NA> NA NA NA
## Columbiformes <NA> NA NA NA
## Coraciiformes <NA> NA NA NA
## Cuculiformes <NA> NA NA NA
## Falconiformes <NA> NA NA NA
## Galliformes <NA> NA NA NA
## Gruiformes <NA> NA NA NA
## Passeriformes <NA> NA NA NA
## Pelecaniformes <NA> NA NA NA
## Piciformes <NA> NA NA NA
## Procellariiformes <NA> NA NA NA
## Psittaciformes <NA> NA NA NA
## Strigiformes <NA> NA NA NA
## Suliformes <NA> NA NA NA
Como temos 19 ordens para refazer a mesma análise sendo que a única mudança será o nome da ordem - e a posição dos resultados na tabela que podem corresponder ao mesmo número de objetos na lista de ordens. Nestes casos é muito simples construir um loop, substintuindo o i pelos valores de 1 a 19 a cada rodada:
for (i in 1:19){
ord<-as.character(orders$Var1[i])
dat_o<-subset(aves, order==ord)
reg<-lm(log(dat_o$Mass)~abs(dat_o$Centroid.Latitude))
res<-summary(reg)
tab_res$Order[i]<- ord
tab_res$p_value[i] <- res$coefficients[2,4]
tab_res$slope[i] <- res$coefficients[2,1]
tab_res$R2[i] <- res$r.squared*100} Agora podemos ver e comparar as estatpisticas para cada ordem:
## Order p_value slope R2
## Accipitriformes Accipitriformes 0.0021266973 0.0130808961 3.847822218
## Anseriformes Anseriformes 0.0008585930 0.0097858620 6.810821840
## Apodiformes Apodiformes 0.1285208290 0.0070326704 0.526671773
## Bucerotiformes Bucerotiformes 0.9028599144 0.0030349813 0.024217482
## Caprimulgiformes Caprimulgiformes 0.5834112248 -0.0030746377 0.287383811
## Charadriiformes Charadriiformes 0.0369858106 0.0049205511 1.189853556
## Columbiformes Columbiformes 0.9235173862 -0.0004018295 0.003046816
## Coraciiformes Coraciiformes 0.4393613121 0.0058355076 0.401847535
## Cuculiformes Cuculiformes 0.3226731165 -0.0069997871 0.724553229
## Falconiformes Falconiformes 0.0319743560 0.0137397474 7.206017836
## Galliformes Galliformes 0.3893321824 0.0031043661 0.261966098
## Gruiformes Gruiformes 0.0004893399 0.0255380624 7.661439986
## Passeriformes Passeriformes 0.5074520380 -0.0005285277 0.007471743
## Pelecaniformes Pelecaniformes 0.0772727872 0.0133255654 2.970705069
## Piciformes Piciformes 0.0026419488 0.0107875495 2.193874312
## Procellariiformes Procellariiformes 0.0013405839 0.0231958641 7.929340084
## Psittaciformes Psittaciformes 0.0409523232 0.0097201439 1.191687920
## Strigiformes Strigiformes 0.0083820099 0.0130137354 3.440629939
## Suliformes Suliformes 0.0236785302 0.0076625715 10.013556434
Exercício
Escolha um conjunto de dados (no pacote datasets) ou utilize seus próprios dados para testar uma hipótese de sua escolha.