# Carregando pacotes necessários:
library(tidyverse) # Impossível não precisar dos pacotes dentro do Tidyverse
library(gt) # Permite criar melhores tabelas e salvar em word ou pdf
library(gtExtras) # É uma extensão do pacote gt que fornece funções adicionais
library(gtsummary) # Utilizado para criar tabelas estatísticas de forma simples
library(labelled) # Funções para trabalhar com dados que contêm rótulos
library(gridExtra) # Organizar múltiplos gráficos em uma única página.
library(psych) # Faz análise descritiva robusta e sem muita programação
library(ggpubr) # Ajuda a encontrar valores da equação de regressão
library(patchwork) # Permite juntar gráficos na mesma janela
library(flextable) # Facilita o acesso para trablhar com tabelas
library(dlookr) # Ênfase em diagnóstico de qualidade dos dados em EDA
library(ggstatsplot)# Extensão do ggplot2 com gráficos de saídas + estatísticas
library(SmartEDA) # Simplificar e automatiza a EDA.
# Baixando o banco de dados em um dataframe direto do github
<- read.csv(
HRV "https://raw.githubusercontent.com/GleidsonUERN/LAFEX/main/VFC.csv")
Análise Descritiva Exploratória
Realizando uma análise exploratória de dados (Exploratory Data Analysis - EDA).
Cenário
Nos dias atuais temos um vasto volume de informações que, se devidamente analisados, podem revelar padrões, tendências e insights valiosos que auxiliam na tomada de decisões estratégicas. No entanto, para extrair o máximo potencial desses dados, é fundamental realizar uma análise descritiva e exploratória minuciosa.
Análise descritiva.
A análise descritiva e exploratória de dados tem como principal objetivo resumir e interpretar as características principais de um conjunto de dados. Este tipo de análise é um passo essencial em qualquer projeto de ciência de dados, pois permite um entendimento profundo do cenário estudado, identificando comportamentos comuns, outliers, e relações entre variáveis.
Nesta análise, abordaremos um banco de dados específico (dados fictícios) mas que simulam dados originais. Exploreremos suas diversas através de técnicas estatísticas e ferramentas de visualização de dados, buscaremos responder perguntas como:
Quais são as principais características dos dados?
Como as variáveis se distribuem e quais são suas medidas de tendência central ou medidas de resumo (média, mediana, moda, etc.)?
Existem padrões ou tendências evidentes?
As variáveis dependentes são diferentes quando se toma uma variável categórica como independente?
Este processo não apenas facilita a compreensão do conjunto de dados, mas também orienta as etapas subsequentes de modelagem e predição. A análise descritiva e exploratória é, portanto, um pilar fundamental para qualquer estudo que vise transformar dados brutos em informações acionáveis e relevantes. Neste documento, apresentaremos os resultados dessa análise inicial, destacando os principais achados e preparando o terreno para análises mais aprofundadas. Convidamos você a embarcar conosco nesta jornada de descoberta e compreensão dos dados, onde cada gráfico, tabela e estatística, revelará um pouco mais sobre o universo que estamos investigando.
Carregando o dataset e os pacotes necessários
Certifique-se que você está online para fazer o download diretamente pelo script do R. Do contrário, faça o downloado do arquivo em https://github.com/GleidsonUERN/LAFEX/blob/main/VFC.csv e salve no seu diretório pessoal.
A seguir, temos os pacotes que poderão ser utilizados neste documento. Certifique-se que você tem todos eles instalados. Do contrário, use: install.packages("nome_pacote")
para instalar àqueles que ainda não estão em sua máquina.
# Baixando o banco de dados em um dataframe direto do seu computador
<- read.csv("C://pastas/subpastas/arquivo.csv") HRV
Observe que a quantidade de pastas, subpastas bem como o disco rígido declarado na função read.csv() dependerá do local onde você salvou seu arquivo. Fique atento para passar a localização corretamente.
Conhecendo as variáveis do dataset
Agora vamos conhecer um pouco sobre o banco de dados e fazer algumas alterações.
# Exibindo as 6 primeiras linhas do dataframe
head(HRV)
sex phys_Act age mRR SDNN rMSSD LF LFnu HF HFnu LF_HF
1 M Sedentário 28 836.38 75.22 47.92 632.49 37.52 1352.57 27.16 1.26
2 M Não Sedentário 27 832.40 55.15 28.46 193.53 45.03 82.82 19.74 2.34
3 M Não Sedentário 28 924.38 56.56 72.48 549.74 77.98 891.48 62.05 1.26
4 F Não Sedentário 30 914.10 49.83 46.35 342.29 51.63 1329.44 42.31 1.26
5 M Não Sedentário 37 785.21 65.38 26.62 991.16 61.13 2368.29 43.76 1.26
6 F Não Sedentário 30 1019.65 56.90 19.72 1079.52 50.15 362.07 28.07 2.98
# Exibindo os nomes e os tipos das variáveis da base de dados.
::glimpse(HRV) dplyr
Rows: 500
Columns: 11
$ sex <chr> "M", "M", "M", "F", "M", "F", "F", "F", "M", "M", "F", "F", "…
$ phys_Act <chr> "Sedentário", "Não Sedentário", "Não Sedentário", "Não Sedent…
$ age <int> 28, 27, 28, 30, 37, 30, 35, 33, 29, 23, 28, 28, 30, 36, 40, 3…
$ mRR <dbl> 836.38, 832.40, 924.38, 914.10, 785.21, 1019.65, 948.48, 1143…
$ SDNN <dbl> 75.22, 55.15, 56.56, 49.83, 65.38, 56.90, 32.83, 32.83, 46.95…
$ rMSSD <dbl> 47.92, 28.46, 72.48, 46.35, 26.62, 19.72, 25.91, 93.15, 27.98…
$ LF <dbl> 632.49, 193.53, 549.74, 342.29, 991.16, 1079.52, 1114.68, 377…
$ LFnu <dbl> 37.52, 45.03, 77.98, 51.63, 61.13, 50.15, 58.10, 51.47, 65.64…
$ HF <dbl> 1352.57, 82.82, 891.48, 1329.44, 2368.29, 362.07, 2482.94, 18…
$ HFnu <dbl> 27.16, 19.74, 62.05, 42.31, 43.76, 28.07, 51.33, 58.39, 55.28…
$ LF_HF <dbl> 1.26, 2.34, 1.26, 1.26, 1.26, 2.98, 1.26, 1.26, 1.26, 12.98, …
Repare que as variáveis sex e phys_Act estão alocadas como character
. Desse modos, é importante que mudemos para uma variável categórica (factor
). Existem inúmeras formas de se fazer isso. Vejamos abaixo uma forma bem elementar.
# Primeiro a variável sex
$sex <- factor(HRV$sex)
HRV
# Agora vamos para a variável phys_Act
$phys_Act <- factor(HRV$phys_Act)
HRV
# Verificando o resultado
::glimpse(HRV) dplyr
Rows: 500
Columns: 11
$ sex <fct> M, M, M, F, M, F, F, F, M, M, F, F, F, M, F, M, F, M, M, M, M…
$ phys_Act <fct> Sedentário, Não Sedentário, Não Sedentário, Não Sedentário, N…
$ age <int> 28, 27, 28, 30, 37, 30, 35, 33, 29, 23, 28, 28, 30, 36, 40, 3…
$ mRR <dbl> 836.38, 832.40, 924.38, 914.10, 785.21, 1019.65, 948.48, 1143…
$ SDNN <dbl> 75.22, 55.15, 56.56, 49.83, 65.38, 56.90, 32.83, 32.83, 46.95…
$ rMSSD <dbl> 47.92, 28.46, 72.48, 46.35, 26.62, 19.72, 25.91, 93.15, 27.98…
$ LF <dbl> 632.49, 193.53, 549.74, 342.29, 991.16, 1079.52, 1114.68, 377…
$ LFnu <dbl> 37.52, 45.03, 77.98, 51.63, 61.13, 50.15, 58.10, 51.47, 65.64…
$ HF <dbl> 1352.57, 82.82, 891.48, 1329.44, 2368.29, 362.07, 2482.94, 18…
$ HFnu <dbl> 27.16, 19.74, 62.05, 42.31, 43.76, 28.07, 51.33, 58.39, 55.28…
$ LF_HF <dbl> 1.26, 2.34, 1.26, 1.26, 1.26, 2.98, 1.26, 1.26, 1.26, 12.98, …
Note que as 2 variáveis agora são do tipo fator (factor), cada uma com 2 levels (categorias). Isso será muito importante para definirmos os grupos que irão confrontar as variáveis contínuas.
Plotando os primeiros gráficos
(Inspeção visual da distribuição)
Agora vamos plotar alguns gráficos do tipo histograma para visualizarmos a distribuição de algumas das variáveis contínuas.
# Primeiro a variável "age" (Idade)
<- hist(HRV$age,
plot_age col = "blue",
xlab = "Idade em anos",
main = "Histograma",
freq = F)
# Agora com a variável mRR (média dos intervalos R-R)
hist(HRV$mRR,
col = "red",
xlab = "Média dos Intervalos R-R em ms",
main = "Histograma",
freq = F)
Usando a função hist
, nativa do R, podemos ir gerando um a um, todos os histogramas de cada uma das variáveis. Observe que não foi separado por grupo (Ex.: sex) e todos os casos da variável estão juntos. Teríamos que adicionar também a função curve(dnorm(x, mean, sd, add))
em adição à cada um dos gráficos. Isso não é nada amigável.
Podemos usar o pacote ggplot2
para criar algo mais eficiente e elegante. Vamos criar um histograma usando a função geom_histogram()
, vamos adicionar a curva normal usando a função stat_function()
, depois vamos criar um boxplot em cima do histograma usando a função geom_boxplot()
. A função stat_boxplot()
apenas adicionará linhas transversais nas extremidades dos bigodes do boxplot (barra de erros) e a função facet_grid()
irá separar as plotagens (figuras) em função do sexo.
library(ggplot2)
ggplot(HRV, aes(x = age)) +
geom_histogram(aes(x = age, y = after_stat(density)),
fill = "lightblue",
color = "black",
bins = max(HRV$age) - min(HRV$age)) +
stat_function(fun = dnorm, args = list(mean = mean(HRV$age), sd = sd(HRV$age))) +
geom_boxplot(data = HRV, aes(x = age),
color = "red",
size = 0.9,
width = max(density(HRV$age)$y)/3,
outlier.colour = "red",
outlier.size = 4) +
stat_boxplot(geom = "errorbar", width = max(density(HRV$age)$y)/5) +
facet_grid(sex ~ .)
Veja como ficou elegante. Temos um histograma plotado abaixo de um boxplot da mesma variável e do mesmo grupo. Com esse tipo de visualização já é possível ver que temos um outlier na idade do grupo Masculino. Ainda assim, visualmente podemos crer que a distribuição destas 2 variáveis respeitam os limites da curva normal para que possamos chamá-las de paramétrica.
Otimizando a saída dos gráficos para datasets grandes
Pense que independente de usarmos a função hist()
nativa do R ou funções mais densas como as do ggplot()
, em princípio teríamos que passar todas as variáveis manualmente para criarmos todos os gráficos. Então, porque não otimizarmos isso para que com um pouco mais de esforço (entendimento do código), tenhamos um resultado melhor.
Se você não está muito disposto de avançar na escrita de código e resolução de problemas e deseja continuar plotando os gráficos 1 à 1, então é melhor pular este tópico.
Faremos agora uma função que irá plotar todos os gráficos que precisamos do nosso dataframe. O que faremos é usar o trecho de código acima como parte principal da função. Adicionaremos um argumento que levará em conta todos os nomes das variáveis numéricas do dataframe. Também irei modificar o argumento aes()
para aes_string()
já que utilizaremos uma string para declarar o nome da variável. Tente ler o código e pensar o que ele está executanto. Se tiver dificuldades de reproduzir este código, aplicando às características do seu dataframe, use o trecho de código anterior modificando manualmente o nome das variáveis (uma por vez) de acordo com o seu dataframe.
Bem, vamos criar a função que executará repetidamente a criação do gráfico mostrado anteriormente baseado em 1 parâmetro que precisaremos estabelecer. O parâmetro var_name
usará os nomes das variáveis do dataframe para rodar a função adequadamente. Aqui neste exemplo, continuarei usando o nosso dataframe HRV, então note que várias passagens do código já está inserido o nome do dataframe HRV e da variável sex
. Desse modo, ao tentar copiar o código e utilizar em seu próprio dataframe, você precisará modificar no código da função: a fonte dos dados e a variável que irá realizar a divisão pelo facet_grid()
.
# Unindo várias funções ggplot para criar um gráfico que represente vários
# aspectos da distribuição da variável.
<- function(var_name) {
criando_graficos # !!sym foi necessário pois o argumento precisará ser dinâmico
ggplot(HRV, aes(x = !!sym(var_name))) +
# Aqui temos o histograma
geom_histogram(aes(y = after_stat(density)),
fill = "lightblue",
color = "black",
bins = 30) +
# Aqui temos a adição da curva normal
stat_function(fun = dnorm,
args = list(mean = mean(HRV[[var_name]],
na.rm = TRUE),
sd = sd(HRV[[var_name]],
na.rm = TRUE)),
color = "blue", linewidth = 1) +
# Aqui temos a adição do boxplot com parâmetros que se adequará à cada
# amplitude das variáveis.
geom_boxplot(aes(y = 0),
color = "red",
size = 0.9,
width = max(density(HRV[[var_name]])$y)/3,
outlier.colour = "red",
outlier.size = 3) +
# Aqui estamos adicionando a barra de erros no boxplot
stat_boxplot(geom = "errorbar", width = max(density(HRV[[var_name]])$y)/5) +
# Aqui estamos fazendo a plotagem em função da variável "sex"
facet_grid(sex ~ .) +
labs(x = var_name, y = "Densidade", title = paste("Distribuição de", var_name))
}
É importante lembrar que a função acima foi criada por nós e, portanto, não se refere à nenhum pacote específico. Ela permanecerá no ambiente global enquanto o script
estiver ativo. Considerando nosso dataframe HRV, já sabemos que ele tem variáveis que são numéricas e categóricas. Para a criação de histogramas e boxplots, somente as variáveis numéricas nos interessam. Vamos usar a função sapply()
para guardar apenas as variáveis numéricas.
# Selecionar as variáveis numéricas
<- names(HRV)[sapply(HRV, is.numeric)]
numeric_vars
print(numeric_vars)
[1] "age" "mRR" "SDNN" "rMSSD" "LF" "LFnu" "HF" "HFnu" "LF_HF"
Note que a variável criada (numeric_vars
) abriga os nomes das variáveis do dataframe HRV que são numéricas. Os nomes estão em formato de string (por isso utilizamos !!sym()
no argumento do ggplot
na função que criamos).
purrr
antes de continuar
Imagine agora que você tem uma lista de coisas para fazer e quer aplicar a mesma ação em cada item dessa lista. Ou seja, temos uma lista de variáveis e queremos aplicar a função de ciação do gráfico em cada uma dessas variáveis. Fazer isso manualmente seria bem chato, né?
Aí que entra a função map()
do pacote purrr
. Ela é tipo um super assistente que pega essa sua lista de coisas e aplica uma função em cada item dessa lista, de um jeito muito mais rápido e fácil. Ele vai lá, pega cada número/string da lista, posiciona no local determinado dentro da função e te devolve uma nova lista com tudo já bonitinho.
O legal é que você pode usar o map()
para fazer várias coisas, não só com números, mas com listas de textos, data frames, ou o que você imaginar. Quer transformar uma lista de nomes para maiúsculas? O map()
faz isso pra você. Quer aplicar uma função que você criou em cada coluna de um data frame? O map()
resolve. Resumindo: o map()
é tipo aquele amigo super eficiente que te ajuda a aplicar uma função em cada item de uma lista de forma rápida e sem complicação. Então vamos lá!
Como criamos nossa função anteriormente, então agora é hora de aplicar a nossa função como um argumento dentro da função map()
juntamente com o vetor (numeric_vars
) que será passado linearmente dentro da função.
# Criando os gráficos para todas as variáveis numéricas
<- purrr::map(numeric_vars, criando_graficos)
meus_plots
# A função map() retorna uma lista. No nosso caso foi uma lista com 9 plots
# Então vamos exibí-los em uma única figura com o seguinte layout (3 x 3). Fique
# atento a quantidade de variáveis que voçê tem para que possa modificar
# corretamente a quantidade de linhas (nrow) e colunas (ncol).
grid.arrange(grobs = meus_plots, nrow = 3, ncol = 3)
Outras alternativas com os pacotes gt
e gtExtras
.
Se você chegou até aqui, deve estar apresentando algum nível de cansaço ou está avaliando se o resultado gráfico exibido até agora justificam tantas linhas de código. Bem, a ideia é fazer você perceber a versatilidade e plasticidade que temos para criar gráficos da maneira que bem queremos. Algo bem diferente dos gráficos clássicos feitos em Excel ou mesmo em GraphPad. Contudo, se você é do tipo que está pouco se importando com algum requinte gráfico para exibição dos dados então aí vai um alento: veja o que a função gt_plt_summary()
do pacote gtExtras
pode fazer.
|> gtExtras::gt_plt_summary(title = "Análise descritiva da base de dados HRV") HRV
Note que a saída nos informou diversas características da base de dados (dataframe). Na parte esquerda, temos ícones que descrevem o tipo da variável. Podemos observar que há uma diferença entre as variáveis categóricas e as variáveis contínuas. Em seguida o nome da variável conforme consta no data frame. Depois temos um miniplot em forma de histograma para cada uma das variáveis com destaque para as variáveis contínuas que apresentam um pequeno histograma com a média posicionada (linha vertical de cor preta) em cada uma delas. Podemos ainda verificar dados ausentes (missings) e algumas medidas tais como média (MEAN), mediana (MEDIAN) e desvio padrão (SD). Para quem trabalha com base de dados grandes, este comando é muito eficiente para que tenhamos uma visão geral das variáveis que vamos trabalhar sem muito esforço.
Visualizando informações em tabelas
Vamos agora considerar uma visualização em tabela para exploração dos dados. Muitos são os pacotes envolvidos nestas tarefas. Pacotes como o psych
que tem uma função describe()
(semelhante à mesma função do pacote dlookr
). Cada um, com suas potencialidades e limitações. Aqui em especial utilizaremos a função describe()
do pacote dlookr
.
# O pacote `dlookr` desconsidera as variáveis categóricas na análise.
# Isso pode ser interessante quando não tempos grupos a serem considerados.
::describe(HRV) |>
dlookr::select(described_variables, mean, sd, se_mean, skewness, kurtosis) |>
dplyr::flextable() flextable
described_variables | mean | sd | se_mean | skewness | kurtosis |
---|---|---|---|---|---|
age | 30.11000 | 4.539064 | 0.2029931 | -0.13430470 | -0.051492409 |
mRR | 928.73874 | 88.535808 | 3.9594417 | 0.08666669 | 0.007503591 |
SDNN | 50.63884 | 14.219739 | 0.6359261 | 0.50400889 | -0.461228198 |
rMSSD | 42.80574 | 19.685768 | 0.8803743 | 0.72543429 | 0.024277849 |
LF | 539.99948 | 246.192526 | 11.0100645 | 0.47005279 | -0.398464768 |
LFnu | 51.86088 | 9.727337 | 0.4350197 | 0.04795421 | -0.169713146 |
HF | 735.64148 | 613.161768 | 27.4214279 | 0.76203099 | -0.227598678 |
HFnu | 39.54764 | 9.981022 | 0.4463649 | 0.17960129 | 0.246069653 |
LF_HF | 2.69504 | 2.742045 | 0.1226280 | 2.08759979 | 3.763881690 |
# Vamos salvar as informações em um dataframe
<- dlookr::describe(HRV) |>
minha_EDA ::select(described_variables, mean, sd, se_mean, skewness, kurtosis) |>
dplyr::mutate("Coef. Variação" = (sd/mean)*100) dplyr
A saída da função describe()
do pacote dlookr
tem muitas colunas (cada uma delas com um tipo de dado estatístico diferente). Desse modo, utilizamos a função select()
do dplyr
para escolher apenas estatísticas de interesse. Foram elas: as próprias variaéis (described_variables), a média (mean), desvio padrão (sd) a assimetria (skewness) e o achatamento (kurtosis). Se você passar a função sozinha (dlookr::describe(HRV)
) verá o que isso quer dizer.
Conhecendo um pouco sobre Assimetria e Achatamento
Skewness, ou assimetria, é uma medida que descreve a simetria dos dados em torno da média. Ela indica se a distribuição dos dados é simétrica ou inclinada para algum dos lados. Existem três tipos principais de skewness como veremos a seguir.
Assimetria Positiva (Right-Skewed)
- A cauda direita da distribuição é mais longa ou “gorda” do que a cauda esquerda. A maioria dos valores está concentrada no lado esquerdo, com alguns valores extremos à direita. Exemplo: Renda de uma população onde poucas pessoas ganham muito mais do que a maioria.
Assimetria Negativa (Left-Skewed)
- A cauda esquerda da distribuição é mais longa ou “gorda” do que a cauda direita. A maioria dos valores está concentrada no lado direito, com alguns valores extremos à esquerda. Exemplo: Idade de aposentadoria onde poucas pessoas se aposentam muito antes do comum.
Assimetria Nula (Symmetric)
- A distribuição é simétrica em torno da média. A cauda direita e a cauda esquerda têm aproximadamente o mesmo comprimento. Exemplo: Altura de uma população onde os valores se distribuem igualmente ao redor da média.
A skewness é importante na análise de dados porque uma distribuição assimétrica pode afetar muitas técnicas estatísticas que assumem uma distribuição normal. Compreender a skewness ajuda a decidir se é necessário aplicar transformações nos dados para aproximá-los de uma distribuição de referência como a normal.
Assimetria (Skewness)
Existem limites estabelecidos por diferentes autores que ajudam a identificar quando uma distribuição pode ser considerada normal. Aqui estão os valores geralmente aceitos:
Valores de Assimetria (Skewness)
Assimetria entre -0.5 e 0.5: Considerada simétrica.
Assimetria entre -1 e -0.5 ou entre 0.5 e 1: Levemente assimétrica.
Assimetria menor que -1 ou maior que 1: Fortemente assimétrica.
Curtose, por sua vez, é uma medida que descreve a forma das caudas de uma distribuição em comparação com uma distribuição normal. Ela indica a presença de outliers e a “gordura” das caudas. Existem três tipos principais de kurtosis:
Leptocúrtica (Leptokurtic)
- Distribuições com caudas mais pesadas ou “gordas” e um pico mais alto e estreito do que a distribuição normal. Isso indica a presença de muitos outliers. Exemplo: Notas de um exame onde a maioria dos alunos tira notas muito altas ou muito baixas, mas poucos ficam na média.
Mesocúrtica (Mesokurtic)
- Distribuições com caudas e pico semelhantes aos da distribuição normal. Exemplo: Altura de uma população, se a distribuição for semelhante à distribuição normal.
Platicúrtica (Platykurtic)
- Distribuições com caudas mais leves ou “finas” e um pico mais baixo e largo do que a distribuição normal. Isso indica menos outliers. Exemplo: Peso de uma população onde a maioria dos valores está concentrada em torno da média, com poucas variações extremas.
Kurtosis (Curtose ou Achatamento)
A kurtosis é importante na análise de dados porque indica a probabilidade de ocorrência de valores extremos (outliers). Distribuições com alta kurtosis (leptocúrticas) têm maior probabilidade de ter outliers, o que pode afetar a robustez de várias análises estatísticas. Aqui estão os valores geralmente aceitos:
Valores de Curtose (Kurtosis)
A curtose excessiva é medida comparando-se a curtose da distribuição dos dados com a curtose da distribuição normal (que é 3).
Curtose entre -1 e 1: Considerada aceitável para a normalidade.
Curtose entre -2 e 2: Muitas vezes ainda considerada razoável.
Curtose menor que -2 ou maior que 2: Indicativa de uma distribuição não normal, com caudas muito leves ou muito pesadas (Curtose excessiva).
Os limites para considerar uma distribuição normal variam ligeiramente entre diferentes autores, mas a faixa de assimetria entre -1 e +1 e de curtose excessiva entre -2 e +2 é amplamente aceita na literatura como razoável para indicar normalidade. Esses parâmetros ajudam a orientar a interpretação de dados e a escolha de transformações quando necessário (Bulmer 1979; Hair 2009; Kline 2023; Mallery e George 2000; Ptaszyński 2022).
Baseado nisso podemos criar uma coluna para classificar os valores de Assimetria e Curtose que temos até agora e tomar alguma decisão.
# Criando uma função para classificar as variáveis com base em skewness e kurtosis
<- function(skewness, kurtosis) {
classificacao_variavel if (skewness >= -1 & skewness <= 1 & kurtosis >= -2 & kurtosis <= 2) {
return("Paramétrica")
else {
} return("Não-Paramétrica")
}
}
# Aplicando a função para criar a coluna de classificação
<- minha_EDA %>%
minha_EDA mutate(classificacao = mapply(classificacao_variavel, skewness, kurtosis))
|> flextable::flextable() minha_EDA
described_variables | mean | sd | se_mean | skewness | kurtosis | Coef. Variação | classificacao |
---|---|---|---|---|---|---|---|
age | 30.11000 | 4.539064 | 0.2029931 | -0.13430470 | -0.051492409 | 15.074940 | Paramétrica |
mRR | 928.73874 | 88.535808 | 3.9594417 | 0.08666669 | 0.007503591 | 9.532908 | Paramétrica |
SDNN | 50.63884 | 14.219739 | 0.6359261 | 0.50400889 | -0.461228198 | 28.080697 | Paramétrica |
rMSSD | 42.80574 | 19.685768 | 0.8803743 | 0.72543429 | 0.024277849 | 45.988618 | Paramétrica |
LF | 539.99948 | 246.192526 | 11.0100645 | 0.47005279 | -0.398464768 | 45.591252 | Paramétrica |
LFnu | 51.86088 | 9.727337 | 0.4350197 | 0.04795421 | -0.169713146 | 18.756599 | Paramétrica |
HF | 735.64148 | 613.161768 | 27.4214279 | 0.76203099 | -0.227598678 | 83.350624 | Paramétrica |
HFnu | 39.54764 | 9.981022 | 0.4463649 | 0.17960129 | 0.246069653 | 25.237971 | Paramétrica |
LF_HF | 2.69504 | 2.742045 | 0.1226280 | 2.08759979 | 3.763881690 | 101.744137 | Não-Paramétrica |
De acordo com as condições que foram estabelecidas, apenas a variável LF_HF se apresentou como uma variável não-paramétrica. Isso implicará em uma utilização de teste estatístico diferente do teste utilizado com as variáveis paramétricas. Note ainda que a função describe()
tomou como conjunto de dados, todas as entradas do dataframe e não as separou por nenhuma variável categórica. Desse modo, temos que calcular as mesmas medidas levando em conta cada variável categórica que utilizaremos em uma comparação. Atualizarei, no futuro, este script com funções que permitirão exibir a tabela separado por determinadas variáveis de interesse (variáveis categóricas). Outras funções do pacote dlookr
merecem ser mencionadas para o uso frequente desse pacote.
# Verificar o tipo das variáveis e dados ausentes
::diagnose(HRV) |> flextable::flextable() dlookr
variables | types | missing_count | missing_percent | unique_count | unique_rate |
---|---|---|---|---|---|
sex | factor | 0 | 0 | 2 | 0.004 |
phys_Act | factor | 0 | 0 | 2 | 0.004 |
age | integer | 0 | 0 | 26 | 0.052 |
mRR | numeric | 0 | 0 | 497 | 0.994 |
SDNN | numeric | 0 | 0 | 416 | 0.832 |
rMSSD | numeric | 0 | 0 | 397 | 0.794 |
LF | numeric | 0 | 0 | 445 | 0.890 |
LFnu | numeric | 0 | 0 | 459 | 0.918 |
HF | numeric | 0 | 0 | 381 | 0.762 |
HFnu | numeric | 0 | 0 | 461 | 0.922 |
LF_HF | numeric | 0 | 0 | 192 | 0.384 |
# Análise descritiva mantendo as variáveis contínuas originais em linha
::diagnose_numeric(HRV) |> flextable::flextable() dlookr
variables | min | Q1 | mean | median | Q3 | max | zero | minus | outlier |
---|---|---|---|---|---|---|---|---|---|
age | 17.00 | 27.0000 | 30.11000 | 30.000 | 33.0000 | 42.00 | 0 | 0 | 1 |
mRR | 712.03 | 864.5575 | 928.73874 | 931.440 | 985.4350 | 1,231.13 | 0 | 0 | 4 |
SDNN | 30.35 | 38.0750 | 50.63884 | 50.025 | 59.6225 | 92.31 | 0 | 0 | 1 |
rMSSD | 18.76 | 25.2125 | 42.80574 | 40.300 | 56.0175 | 117.55 | 0 | 0 | 2 |
LF | 191.28 | 350.4300 | 539.99948 | 512.880 | 713.4925 | 1,319.57 | 0 | 0 | 1 |
LFnu | 29.63 | 45.8175 | 51.86088 | 51.405 | 58.2025 | 78.54 | 0 | 0 | 3 |
HF | 80.08 | 105.0300 | 735.64148 | 621.660 | 1,148.9875 | 2,658.77 | 0 | 0 | 0 |
HFnu | 15.11 | 32.8175 | 39.54764 | 39.635 | 46.0700 | 74.46 | 0 | 0 | 4 |
LF_HF | 0.92 | 1.2600 | 2.69504 | 1.260 | 2.4700 | 14.77 | 0 | 0 | 94 |
# Análise descritiva mantendo as variáveis categóricas originais em linha
::diagnose_category(HRV) |> flextable::flextable() dlookr
variables | levels | N | freq | ratio | rank |
---|---|---|---|---|---|
sex | M | 500 | 261 | 52.2 | 1 |
sex | F | 500 | 239 | 47.8 | 2 |
phys_Act | Sedentário | 500 | 254 | 50.8 | 1 |
phys_Act | Não Sedentário | 500 | 246 | 49.2 | 2 |
# Verificando os outliers e gerando uma tabela flextable
::diagnose_outlier(HRV) |> flextable::flextable() dlookr
variables | outliers_cnt | outliers_ratio | outliers_mean | with_mean | without_mean |
---|---|---|---|---|---|
age | 1 | 0.2 | 17.000000 | 30.11000 | 30.136273 |
mRR | 4 | 0.8 | 1,200.590000 | 928.73874 | 926.546391 |
SDNN | 1 | 0.2 | 92.310000 | 50.63884 | 50.555331 |
rMSSD | 2 | 0.4 | 113.965000 | 42.80574 | 42.519960 |
LF | 1 | 0.2 | 1,319.570000 | 539.99948 | 538.437214 |
LFnu | 3 | 0.6 | 78.053333 | 51.86088 | 51.702777 |
HF | 0 | 0.0 | 735.64148 | 735.641480 | |
HFnu | 4 | 0.8 | 71.535000 | 39.54764 | 39.289677 |
LF_HF | 94 | 18.8 | 7.784043 | 2.69504 | 1.516798 |
Destaco a função diagnose_outlier()
que nos apresenta o total de outliers, a frequência que eles representam do conjunto de dados e o cálculo da média COM (with) e SEM (without) os outliers. Podemos até plotar um gráfico para visualizar essas informações. Vejamos.
library(dlookr)
plot_outlier(HRV)
Outras alternativas para visualização em tabelas incluem a família dos pacotes relacionados ao pacote gt
tais como o gtExtras
e o gtsummary
. Estes 3 pacotes permitem uma infinidade de funções para se trabalhar com tabelas descritivas e inferenciais.
|>
HRV ::tbl_summary(by = sex) |>
gtsummary::add_overall(last = T) gtsummary
Characteristic | F, N = 2391 | M, N = 2611 | Overall, N = 5001 |
---|---|---|---|
phys_Act | |||
Não Sedentário | 118 (49%) | 128 (49%) | 246 (49%) |
Sedentário | 121 (51%) | 133 (51%) | 254 (51%) |
age | 31 (28, 34) | 30 (27, 33) | 30 (27, 33) |
mRR | 936 (863, 984) | 930 (867, 986) | 931 (865, 985) |
SDNN | 50 (39, 60) | 50 (37, 59) | 50 (38, 60) |
rMSSD | 40 (23, 56) | 40 (27, 56) | 40 (25, 56) |
LF | 519 (378, 711) | 506 (336, 712) | 513 (350, 713) |
LFnu | 51 (45, 58) | 52 (46, 59) | 51 (46, 58) |
HF | 554 (116, 1,176) | 640 (83, 1,114) | 622 (105, 1,149) |
HFnu | 40 (33, 46) | 39 (33, 46) | 40 (33, 46) |
LF_HF | 1.26 (1.26, 2.64) | 1.26 (1.26, 2.34) | 1.26 (1.26, 2.47) |
1 n (%); Median (IQR) |
Com o pacote labelled
podemos criar labels para os nomes das variáveis do dataframe. Desse modo, a visualização de informações em gráficos e tabelas utilizará os labels em lugar do nome original. Veja abaixo.
# Vamos salvar os labels no dataframe
<- HRV %>%
HRV ::set_variable_labels(sex = "Sexo",
labelledphys_Act = "Nível de Atividade Física",
age = "Idade (anos)",
LF_HF = "LF/HF")
# Reseta definições de tema
reset_gtsummary_theme()
# Traduz informações estatísticas para o Port.
theme_gtsummary_language("pt")
|>
HRV ::tbl_summary(by = sex) |>
gtsummary::add_overall(last = T) gtsummary
Características | F, N = 2391 | M, N = 2611 | Total, N = 5001 |
---|---|---|---|
Nível de Atividade Física | |||
Não Sedentário | 118 (49%) | 128 (49%) | 246 (49%) |
Sedentário | 121 (51%) | 133 (51%) | 254 (51%) |
Idade (anos) | 31 (28, 34) | 30 (27, 33) | 30 (27, 33) |
mRR | 936 (863, 984) | 930 (867, 986) | 931 (865, 985) |
SDNN | 50 (39, 60) | 50 (37, 59) | 50 (38, 60) |
rMSSD | 40 (23, 56) | 40 (27, 56) | 40 (25, 56) |
LF | 519 (378, 711) | 506 (336, 712) | 513 (350, 713) |
LFnu | 51 (45, 58) | 52 (46, 59) | 51 (46, 58) |
HF | 554 (116, 1,176) | 640 (83, 1,114) | 622 (105, 1,149) |
HFnu | 40 (33, 46) | 39 (33, 46) | 40 (33, 46) |
LF/HF | 1.26 (1.26, 2.64) | 1.26 (1.26, 2.34) | 1.26 (1.26, 2.47) |
1 n (%); Mediana (AIQ) |
O default dessa função utiliza a mediana. Vamos usar um tema que utiliza o desvio padrão e a média.
::reset_gtsummary_theme()
gtsummary::theme_gtsummary_language("pt")
gtsummary::theme_gtsummary_mean_sd()
gtsummary
|>
HRV ::tbl_summary(by = sex, digits = everything() ~ 2) |>
gtsummary::add_overall(last = T) gtsummary
Características | F, N = 2391 | M, N = 2611 | Total, N = 5001 |
---|---|---|---|
Nível de Atividade Física | |||
Não Sedentário | 118.00 (49.37%) | 128.00 (49.04%) | 246.00 (49.20%) |
Sedentário | 121.00 (50.63%) | 133.00 (50.96%) | 254.00 (50.80%) |
Idade (anos) | 30.40 (4.47) | 29.85 (4.60) | 30.11 (4.54) |
mRR | 928.63 (93.35) | 928.84 (84.06) | 928.74 (88.54) |
SDNN | 51.07 (14.45) | 50.24 (14.03) | 50.64 (14.22) |
rMSSD | 42.63 (20.03) | 42.97 (19.40) | 42.81 (19.69) |
LF | 546.19 (241.24) | 534.34 (250.97) | 540.00 (246.19) |
LFnu | 51.39 (9.74) | 52.29 (9.72) | 51.86 (9.73) |
HF | 732.92 (624.89) | 738.13 (603.41) | 735.64 (613.16) |
HFnu | 39.67 (10.06) | 39.44 (9.93) | 39.55 (9.98) |
LF/HF | 2.73 (2.76) | 2.66 (2.73) | 2.70 (2.74) |
1 n (%); Média (Desvio Padrão) |
Testes específicos para análise da distribuição dos dados.
Até agora, foi possível conhecer vários aspectos das variáveis de nosso dataset. Tanto pela inspeção visual dos gráficos como pelas medidas de ajuste da curva de distribuição dos dados é possível tomar algumas decisões. Apesar disso, nos dias atuais, muitas revistas e revisores querem se deparar com testes que apoiem a classificação da distribuição dos seus dados. Então é hora de ir além! Vamos investigar objetivamente a distribuição das variáveis com testes de hipóteses para tal fim. Os testes de Shapiro-Wilk e Kolmogorov-Smirnov são ambos usados para avaliar a normalidade de uma distribuição de dados, mas possuem diferenças significativas em termos de aplicação e funcionamento (Shapiro e Wilk 1965; Lilliefors 1967; Moore, McCabe, e Craig 2009).
Teste de Shapiro-Wilk
O teste calcula uma estatística que compara a amostra de dados com uma distribuição normal ideal. Ele mede a correlação entre os dados da amostra e os valores normais teóricos esperados. Quanto maior a correlação, mais próxima a distribuição da amostra está de uma distribuição normal.
É mais adequado para amostras menores (há muita divergência sobre isso mas, a maioria dos autores concordam que a sensibilidade do teste é boa com até 50 amostras) (Gupta et al. 2019).
Interpretação:
- Hipótese nula (H0): A amostra segue uma distribuição normal.
- Hipótese nula (H1): Se o valor p resultante do teste for menor que um nível de significância escolhido (por exemplo, 0,05), rejeitamos a hipótese nula, indicando que a amostra não segue uma distribuição normal.
Teste de Kolmogorov-Smirnov (K-S)
Compara uma amostra de dados com uma distribuição de referência (por exemplo, normal, exponencial) ou comparar duas amostras entre si. Baseia-se na distância máxima (estatística D) entre a função de distribuição acumulada (CDF) empírica da amostra e a CDF da distribuição de referência. Essa distância é usada para avaliar se a amostra difere significativamente da distribuição de referência (frequentemente se usa a distribuição normal como sendo referência). Pode ser usado para qualquer tipo de distribuição de referência, não se limitando à normalidade.
Adequado para amostras maiores que 50 unidades e também para comparar duas amostras. Menos sensível em detectar desvios de normalidade em amostras pequenas comparado ao teste de Shapiro-Wilk (Gupta et al. 2019).
Interpretação
- Hipótese nula (H0): A distribuição da amostra corresponde à distribuição de referência (ou as duas amostras vêm da mesma distribuição).
- Hipótese alternativa (H1): Se o valor p resultante do teste for menor que um nível de significância escolhido (por exemplo, 0,05), rejeitamos a hipótese nula, indicando que a distribuição da amostra difere da distribuição de referência (ou as duas amostras não vêm da mesma distribuição).
Como o tamanho amostral no dataset HRV
é superior a 50 unidades em qualquer saída de variável dependente em função de uma variável categórica. Apesar da amostra total ser n = 500, temos que analisar as distribuições dos tamanhos que irão compor os grupos ao qual se formam de acordo com as variáveis independentes (geralmente categóricas). Desse modo, vamos utilizar a variável sexo (sex
) como variável independente e analisar todas as outras variáveis exceto a variável phys_Act
(Nível de Atividade Física) que também é uma variável categórica.
# Calculando o K-S teste no sexo Feminino. O resultado da função ks.teste será
# uma lista com os resultados do teste.
<- HRV |> dplyr::filter(sex == "F") |>
ks_fem ::select(where(is.numeric)) |>
dplyr::map(~ ks.test(.x, "pnorm", mean = mean(.x), sd = sd(.x)))
purrr
# Calculando o K-S teste no sexo Masculino
<- HRV |> dplyr::filter(sex == "M") |>
ks_masc ::select(where(is.numeric)) |>
dplyr::map(~ ks.test(.x, "pnorm", mean = mean(.x), sd = sd(.x)))
purrr
# Guardando as informações relevantes da lista em um dataframe
library(tibble)
<- tibble("Variáveis" = names(ks_fem),
df_fem "Estatística" = round(purrr::map_dbl(ks_fem, ~ .x$statistic), 4),
"P valor" = round(purrr::map_dbl(ks_fem, ~ .x$p.value), 4))
<- tibble("Variáveis" = names(ks_masc),
df_masc "Estatística" = round(purrr::map_dbl(ks_masc, ~ .x$statistic), 4),
"P valor" = round(purrr::map_dbl(ks_masc, ~ .x$p.value), 4))
# Criando um classificador do resultado com base em critérios estabelecidos
<- df_fem |>
df_fem ::mutate(
dplyr"Classificação Fem" = dplyr::case_when(`P valor` <= 0.05 ~ "Não Paramétrico",
TRUE ~ "Paramétrico")) |>
print()
# A tibble: 9 × 4
Variáveis Estatística `P valor` `Classificação Fem`
<chr> <dbl> <dbl> <chr>
1 age 0.0921 0.0345 Não Paramétrico
2 mRR 0.046 0.694 Paramétrico
3 SDNN 0.0777 0.112 Paramétrico
4 rMSSD 0.118 0.0026 Não Paramétrico
5 LF 0.0913 0.0373 Não Paramétrico
6 LFnu 0.0453 0.712 Paramétrico
7 HF 0.148 0.0001 Não Paramétrico
8 HFnu 0.0383 0.875 Paramétrico
9 LF_HF 0.284 0 Não Paramétrico
<- df_masc |>
df_masc ::mutate(
dplyr"Classificação Masc" = dplyr::case_when(`P valor` <= 0.05 ~ "Não Paramétrico",
TRUE ~ "Paramétrico")) |>
print()
# A tibble: 9 × 4
Variáveis Estatística `P valor` `Classificação Masc`
<chr> <dbl> <dbl> <chr>
1 age 0.0566 0.372 Paramétrico
2 mRR 0.036 0.888 Paramétrico
3 SDNN 0.087 0.0385 Não Paramétrico
4 rMSSD 0.109 0.0039 Não Paramétrico
5 LF 0.0861 0.0416 Não Paramétrico
6 LFnu 0.0394 0.813 Paramétrico
7 HF 0.139 0.0001 Não Paramétrico
8 HFnu 0.0298 0.974 Paramétrico
9 LF_HF 0.312 0 Não Paramétrico
# Exibindo um resumo com as informações do teste
<- tibble::tibble("Variáveis Dependentes" = df_fem$Variáveis,
resumo "Amostra Feminina" = df_fem$`Classificação Fem`,
"Amostra Masculina" = df_masc$`Classificação Masc`)
|> gt::gt() |>
resumo ::cols_align(align = c("center")) gt
Variáveis Dependentes | Amostra Feminina | Amostra Masculina |
---|---|---|
age | Não Paramétrico | Paramétrico |
mRR | Paramétrico | Paramétrico |
SDNN | Paramétrico | Não Paramétrico |
rMSSD | Não Paramétrico | Não Paramétrico |
LF | Não Paramétrico | Não Paramétrico |
LFnu | Paramétrico | Paramétrico |
HF | Não Paramétrico | Não Paramétrico |
HFnu | Paramétrico | Paramétrico |
LF_HF | Não Paramétrico | Não Paramétrico |
Observe que temos uma variedade de situações baseadas nas classficações das distribuições. Algumas das comparações poderão ser feitas considerando que ambos os grupos apresentaram distribuição normal.Desse modo, o teste estatístico poderá ser da família t (escolhendo adequadamente de acordo com o tipo da comparação e se as variâncias forem iguais). Contudo, se um ou os dois grupos apresenta uma distribuição não-paramétrica, então teremos que utilizar um teste equivalente não-paramétrico para realizar o teste de comparação.
Escolhendo o teste estatístico adequado para as comparações (2 grupos)
Distribuição | Teste | Aplicação |
---|---|---|
Paramétrico | Teste t de Student para amostras independentes | Compara as médias de dois grupos independentes quando a distribuição dos dados é normal e as variâncias são iguais. |
Paramétrico | Teste t de Student para amostras pareadas | Compara as médias de dois grupos pareados ou dependentes quando a distribuição dos dados é normal. |
Paramétrico | Teste t de Welch | Compara as médias de dois grupos independentes quando a distribuição dos dados é normal, mas as variâncias são diferentes. |
Não Paramétrico | Teste de Mann-Whitney | Compara as medianas de dois grupos independentes quando a distribuição dos dados não é normal. |
Não Paramétrico | Teste de Wilcoxon para amostras pareadas | Compara as medianas de dois grupos pareados ou dependentes quando a distribuição dos dados não é normal. |
Não Paramétrico | Teste de Kolmogorov-Smirnov | Compara as distribuições de dois grupos independentes, sem assumir uma distribuição específica. |
Não Paramétrico | Teste de Fisher | Compara proporções entre dois grupos independentes quando as amostras são pequenas. |
Desse modo, podemos atribuir a seguinte sequência de testes para as comparações.
|> dplyr::mutate(
resumo "Teste Estatístico" = dplyr::case_when(
`Amostra Feminina` == "Paramétrico" & `Amostra Masculina` == "Paramétrico" ~ "Teste da Família t",
TRUE ~ "Teste de Mann-Whitney ou Wilcoxon")) |>
::gt() |>
gt::tab_header(gt::md("**Escolhendo o teste estatístico com base na distribuição dos dados**"),
gtsubtitle = "Prof. Dr. Gleidson Rebouças") |>
::opt_align_table_header(align = "left") |>
gt::cols_label(`Amostra Feminina` = "Feminina",
gt`Amostra Masculina` = "Masculina") |>
::tab_spanner(label = "Amostra", columns = c(2,3)) |>
gt::tab_style(style = gt::cell_text(weight = "bold"),
gtlocations = list(gt::cells_column_labels(),
::cells_column_spanners())) |>
gt::cols_align(align = "center", columns = 1) |>
gt::tab_options(table.width = "100%") gt
Escolhendo o teste estatístico com base na distribuição dos dados | |||
Prof. Dr. Gleidson Rebouças | |||
Variáveis Dependentes | Amostra | Teste Estatístico | |
---|---|---|---|
Feminina | Masculina | ||
age | Não Paramétrico | Paramétrico | Teste de Mann-Whitney ou Wilcoxon |
mRR | Paramétrico | Paramétrico | Teste da Família t |
SDNN | Paramétrico | Não Paramétrico | Teste de Mann-Whitney ou Wilcoxon |
rMSSD | Não Paramétrico | Não Paramétrico | Teste de Mann-Whitney ou Wilcoxon |
LF | Não Paramétrico | Não Paramétrico | Teste de Mann-Whitney ou Wilcoxon |
LFnu | Paramétrico | Paramétrico | Teste da Família t |
HF | Não Paramétrico | Não Paramétrico | Teste de Mann-Whitney ou Wilcoxon |
HFnu | Paramétrico | Paramétrico | Teste da Família t |
LF_HF | Não Paramétrico | Não Paramétrico | Teste de Mann-Whitney ou Wilcoxon |
Com base na tabela acima podemos estabelecer uma série de decisões. Por exemplo, quais os testes que vamos utilizar e se precisaremos de mais alguma verificação que seja exigência ao teste como por exemplo o teste de igualdade de variâncias de Levene.
Já sabemos que vamos utilizar o teste t para amostras independentes em 3 comparações em função do sexo (mRR, LFnu e HFnu). Em todas as demais iremos utilizar o teste de Mann-Whitney que é o equivalente paramétrico para o teste t independente. Então vamos conhecer algumas informações sobre esse teste antes de continuar.
O teste de Mann-Whitney e o teste de Wilcoxon rank-sum são testes não-paramétricos equivalentes que comparam duas amostras independentes para determinar se elas vêm da mesma distribuição. Este último ainda é diferente do teste de Wilconxon (tradicional) que é utilizado para amostras pareadas como alternativa não paramétrica ao teste t pareado. (Lehmann 2011; Hollander, A. Wolfe, e Chicken 2015; Wilcoxon 1945; Mann e Whitney 1947)
Ambos os testes são matematicamente equivalentes e frequentemente usados de forma intercambiável.Teste de Mann-Whitney enfatiza a estatística U, enquanto o teste de Wilcoxon enfatiza a soma dos ranks.
Teste de Mann-Whitney
- Desenvolvedores: Henry B. Mann e Donald R. Whitney, 1947.
- Outro nome: Teste U de Mann-Whitney.
- Objetivo: Comparar duas amostras independentes.
- Enfoque: Estatística U, baseada nas somas das posições (ranks) das observações nas duas amostras.
Teste de Wilcoxon Rank-Sum
- Desenvolvedor: Frank Wilcoxon, 1945.
- Objetivo: Comparar duas amostras independentes.
- Enfoque: Soma das posições (ranks) das observações nas amostras.
O pacote gtsummary
tem uma função que realiza testes de comparação de acordo com a quantidade de grupos a serem comparados. Contudo, os testes realizados como default nem sempre satisfazem nossas intenções. Vejamos abaixo.
::reset_gtsummary_theme()
gtsummary::theme_gtsummary_language("pt")
gtsummary
|> dplyr::select(-phys_Act) |>
HRV ::tbl_summary(by = sex) |>
gtsummary::add_p(pvalue_fun = ~ gtsummary::style_pvalue(.x, digits = 3)) |>
gtsummary::as_gt() |>
gtsummary::tab_options(table.width = "100%") gt
Características | F, N = 2391 | M, N = 2611 | Valor p2 |
---|---|---|---|
Idade (anos) | 31 (28, 34) | 30 (27, 33) | 0.082 |
mRR | 936 (863, 984) | 930 (867, 986) | 0.898 |
SDNN | 50 (39, 60) | 50 (37, 59) | 0.621 |
rMSSD | 40 (23, 56) | 40 (27, 56) | 0.732 |
LF | 519 (378, 711) | 506 (336, 712) | 0.526 |
LFnu | 51 (45, 58) | 52 (46, 59) | 0.262 |
HF | 554 (116, 1,176) | 640 (83, 1,114) | 0.941 |
HFnu | 40 (33, 46) | 39 (33, 46) | 0.786 |
LF/HF | 1.26 (1.26, 2.64) | 1.26 (1.26, 2.34) | 0.465 |
1 Mediana (AIQ) | |||
2 Teste de soma de postos de Wilcoxon |
Repare que todas as comparações foram realizadas com o teste de soma dos postos de Wilcoxon e, confome já falamos antes, existem 3 comparações que gostaríamos de realizar com o teste t de Student (mRR, LFnu e HFnu). Desse modo, temos que implementar um parâmetro para informar ao gtsummary
que desejamos alterar o teste default para alguma das comparações. Veja abaixo!
::reset_gtsummary_theme()
gtsummary::theme_gtsummary_language("pt")
gtsummary
|> dplyr::select(-phys_Act) |>
HRV ::tbl_summary(by = sex) |>
gtsummary::add_p(pvalue_fun = ~ gtsummary::style_pvalue(.x, digits = 3),
gtsummarytest = list(mRR ~ "t.test", LFnu ~ "t.test", HFnu ~ "t.test")) |>
::as_gt() |>
gtsummary::tab_options(table.width = "100%") gt
Características | F, N = 2391 | M, N = 2611 | Valor p2 |
---|---|---|---|
Idade (anos) | 31 (28, 34) | 30 (27, 33) | 0.082 |
mRR | 936 (863, 984) | 930 (867, 986) | 0.979 |
SDNN | 50 (39, 60) | 50 (37, 59) | 0.621 |
rMSSD | 40 (23, 56) | 40 (27, 56) | 0.732 |
LF | 519 (378, 711) | 506 (336, 712) | 0.526 |
LFnu | 51 (45, 58) | 52 (46, 59) | 0.303 |
HF | 554 (116, 1,176) | 640 (83, 1,114) | 0.941 |
HFnu | 40 (33, 46) | 39 (33, 46) | 0.799 |
LF/HF | 1.26 (1.26, 2.64) | 1.26 (1.26, 2.34) | 0.465 |
1 Mediana (AIQ) | |||
2 Teste de soma de postos de Wilcoxon; Teste t com correção de Welch |
Conforme pode ser observado no rodapé, temos agora a execução de 2 testes. Se você comparar as 2 tabelas, verá que somente as 3 variáveis que foram informadas quanto à utilização de um outro teste (teste t) houve mudanças nos valores de p. Perceba ainda que o teste t empregado foi com a correção de Welch. Este teste levou em consideração que não houve igualdade de variâncias nos grupos. O teste de homogeneidade de variância de Levene com o pacote car
pode nos revelar se a aplicação da correção foi adequada.
Hipóteses do Teste de Levene
- H0: As variâncias dos grupos são homogêneas (p > 0,05)
- H1: As variâncias dos grupos não são homogêneas (p < 0,05)
library(car)
::leveneTest(mRR ~ sex, HRV, center = mean) car
Levene's Test for Homogeneity of Variance (center = mean)
Df F value Pr(>F)
group 1 1.5886 0.2081
498
::leveneTest(LFnu ~ sex, HRV, center = mean) car
Levene's Test for Homogeneity of Variance (center = mean)
Df F value Pr(>F)
group 1 0.2726 0.6018
498
::leveneTest(HFnu ~ sex, HRV, center = mean) car
Levene's Test for Homogeneity of Variance (center = mean)
Df F value Pr(>F)
group 1 0.0425 0.8368
498
O teste de Levene revelou que as variâncias foram iguais e, portanto, não era necessário a correção de Welch. Sendo assim, podemos mudar parâmetros dentro da função add_p()
que informará que as variâncias são iguais. Observe os parâmetros test =
e test.args =
.
::reset_gtsummary_theme()
gtsummary::theme_gtsummary_language("pt")
gtsummary::theme_gtsummary_mean_sd()
gtsummary
|> dplyr::select(-phys_Act) |>
HRV ::tbl_summary(by = sex,
gtsummarydigits = list(everything() ~ 2)) |>
::add_p(
gtsummarytest = list(
~ "wilcox.test",
age ~ "t.test",
mRR ~ "wilcox.test",
SDNN ~ "wilcox.test",
rMSSD ~ "wilcox.test",
LF ~ "t.test",
LFnu ~ "wilcox.test",
HF ~ "t.test",
HFnu ~ "wilcox.test"),
LF_HF test.args = list(
mRR = list(var.equal = TRUE),
LFnu = list(var.equal = TRUE),
HFnu = list(var.equal = TRUE)),
pvalue_fun = ~ gtsummary::style_pvalue(.x, digits = 3)) |>
::as_gt() |>
gtsummary::tab_options(table.width = "100%") gt
Características | F, N = 2391 | M, N = 2611 | Valor p2 |
---|---|---|---|
Idade (anos) | 30.40 (4.47) | 29.85 (4.60) | 0.082 |
mRR | 928.63 (93.35) | 928.84 (84.06) | 0.979 |
SDNN | 51.07 (14.45) | 50.24 (14.03) | 0.621 |
rMSSD | 42.63 (20.03) | 42.97 (19.40) | 0.732 |
LF | 546.19 (241.24) | 534.34 (250.97) | 0.526 |
LFnu | 51.39 (9.74) | 52.29 (9.72) | 0.303 |
HF | 732.92 (624.89) | 738.13 (603.41) | 0.941 |
HFnu | 39.67 (10.06) | 39.44 (9.93) | 0.798 |
LF/HF | 2.73 (2.76) | 2.66 (2.73) | 0.465 |
1 Média (Desvio Padrão) | |||
2 Teste de soma de postos de Wilcoxon; Teste t para amostras independentes |
Note que nas notas de rodapé, não consta mais a correção de Welch, dado o fato de que informamos não haver diferenças nas variâncias.
Observando agora os valores de p para cada uma das comparações, podemos perceber que não encontramos diferenças significativas nas comparações de média para todas as variáveis.
Se por acaso não ficou claro a utilização do teste t dentro dos parâmetros do pacote gtsummary
, você pode realizar os testes separadamente usando a função t.test
nativa do R e em seguida copiar e alimentar uma tabela à medida que for obtendo as informações. Veja abaixo como fazer:
# Função nativa do R para o teste t de student
t.test(mRR ~ sex, HRV, var.equal = TRUE)
Two Sample t-test
data: mRR by sex
t = -0.02588, df = 498, p-value = 0.9794
alternative hypothesis: true difference in means between group F and group M is not equal to 0
95 percent confidence interval:
-15.79459 15.38389
sample estimates:
mean in group F mean in group M
928.6315 928.8369
t.test(LFnu ~ sex, HRV, var.equal = TRUE)
Two Sample t-test
data: LFnu by sex
t = -1.0319, df = 498, p-value = 0.3026
alternative hypothesis: true difference in means between group F and group M is not equal to 0
95 percent confidence interval:
-2.6095687 0.8123241
sample estimates:
mean in group F mean in group M
51.39180 52.29042
t.test(HFnu ~ sex, HRV, var.equal = TRUE)
Two Sample t-test
data: HFnu by sex
t = 0.25553, df = 498, p-value = 0.7984
alternative hypothesis: true difference in means between group F and group M is not equal to 0
95 percent confidence interval:
-1.528773 1.985883
sample estimates:
mean in group F mean in group M
39.66695 39.43839
# Função nativa do R para o teste t de Wilcoxon
wilcox.test(age ~ sex, HRV)
Wilcoxon rank sum test with continuity correction
data: age by sex
W = 33989, p-value = 0.08216
alternative hypothesis: true location shift is not equal to 0
wilcox.test(SDNN ~ sex, HRV)
Wilcoxon rank sum test with continuity correction
data: SDNN by sex
W = 31987, p-value = 0.6212
alternative hypothesis: true location shift is not equal to 0
wilcox.test(rMSSD ~ sex, HRV)
Wilcoxon rank sum test with continuity correction
data: rMSSD by sex
W = 30638, p-value = 0.7321
alternative hypothesis: true location shift is not equal to 0
wilcox.test(LF ~ sex, HRV)
Wilcoxon rank sum test with continuity correction
data: LF by sex
W = 32212, p-value = 0.5263
alternative hypothesis: true location shift is not equal to 0
wilcox.test(HF ~ sex, HRV)
Wilcoxon rank sum test with continuity correction
data: HF by sex
W = 31070, p-value = 0.9406
alternative hypothesis: true location shift is not equal to 0
wilcox.test(LF_HF ~ sex, HRV)
Wilcoxon rank sum test with continuity correction
data: LF_HF by sex
W = 32319, p-value = 0.4647
alternative hypothesis: true location shift is not equal to 0
Apresentação gráfica dos resultados
Apesar de não termos diferenças estatísticas em nenhuma das comparações, iresmo agora exibir gráficos para melhor visualização disso considerando que, como um tutorial, devemos estar prontos para utilizar de gráficos para uma melhor exibição dos resultados. Para mais informações acesse: https://www.data-to-viz.com/
De acordo com o algorítmo acima, temos algumas possibilidades de exição para as nossas variáveis do dataset HRV. Como já fizemos exibição de linhas de código que envolvem o boxplot anteriormente, iremos fazer a exibição utilizando um gráfico violino (violin plot).
# Criando uma função personalizada
<- function(var_name) {
criando_graficos2 <- HRV |>
medias_HRV ::group_by(sex) |>
dplyr::summarize(media = mean(HRV[[var_name]], na.rm = TRUE))
dplyr# !!sym foi necessário pois o argumento precisará ser dinâmico
ggplot(HRV, aes(x = sex, y = !!sym(var_name), fill = sex)) +
geom_violin(trim = FALSE) +
geom_boxplot(width = 0.1) +
scale_fill_brewer(palette = "Greens") +
theme(legend.position = "none")
}
# Selecionar as variáveis numéricas
<- names(HRV)[sapply(HRV, is.numeric)]
numeric_vars2
# Criando os gráficos para todas as variáveis numéricas
<- purrr::map(numeric_vars2, criando_graficos2)
meus_plots2
# Juntando os gráficos
::grid.arrange(grobs = meus_plots2, nrow = 3, ncol = 3) gridExtra