Teste de hipótese — Ep. 2

Teste de Wilcoxon com amostras pareadas

Bruno Carloto
13 min readMay 25, 2022

Introdução

Esse presente episódio da série Teste de hipótese é continuação do primeiro episódio — Teste de hipótese: teste t não pareado. Para melhor compreensão acerca do teste de hipótese, sugiro que leia o primeiro episódio.

Mas, podemos resumir que o teste de hipótese busca validar (ou refutar) uma hipótese. No universo da Estatística, a grosso modo falando, serve para evidenciar estatisticamente se uma dada peça de carro é melhor do que outra, se um remédio é melhor do que outro, se uma campanha de marketing foi melhor do que outra. Em suma, envolve a comparação. A ideia da comparação pode ser igual a, diferente de, maior que, menor que, melhor que, pior que, similar a. Os termos estão a depender da questão de negócio e do público a ser comunicado acerca do resultado.

Quando um estudo de caso está sendo desenvolvido, o “estatiquês” entra em ação. Quando o reportamento dos resultados tomam lugar, então, entra em ação o bom senso e o storytelling. Aqui, precisamos ser cuidadosos no tocante às expressões, pois, ainda que estejamos transformando termos técnicos em termos acessíveis a leigos, não podemos nos afastar do espírito estatístico e probabilístico. Nada é 100% de certeza.

Sobre a aplicação de teste no estudo de caso

Seguindo o modelo do primeiro episódio, o segundo episódio foi desenvolvido sem se ter em mente qual teste seria abordado ao fim. A ideia é justamente demonstrar todas as etapas de decisão, no tocante à seleção de técnicas, isso é, os testes (teste de normalidade, teste de variância e teste de hipótese).

Inicialmente, faz-se necessário saber se as amostras são dependentes ou independentes. Posteriormente, entra em campo o teste de normalidade. A depender do resultado, entra o teste de variância e, por fim, podemos aplicar o teste de hipótese.

Com o trabalho já concluído, sabemos que se trata de um estudo de caso em que o teste de Wilcoxon foi aplicado juntamente com o teste t para amostras pareadas.

Estudo de caso

O estudo de caso simula um experimento desenvolvido pela empresa fictícia Gut, uma empresa de carros. A empresa solicitou o desenvolvimento de uma peça para tornar seus carros mais econômicos, no tocante ao consumo de combustível.

50 carros, com a peça antiga (x1), foram postos para rodar com o tanque cheio. Ao fim, foram observados quantos quilômetros os carros andaram. Após, os mesmos 50 carros foram postos para rodar, porém, com a nova peça (x2), sendo observados os quilômetros rodados.

Espera-se que, com a nova peça, os carros tenham rodado mais, com o mesmo tanto de combustível.

A pergunta da empresa, portanto, é:

  • Com a nova peça, os carros se tornaram mais econômicos?

Entre outras palavras, o que a empresa quer saber é se a média de km’s rodados pelos carros com a peça x2 é maior do que a média de km’s rodados pelos carros com a peça x1. Porém, a depender do teste, a média será substituida pela mediana.

Sendo assim, nosso H0 é:

  • Média/Mediana x2 ≤ Média/Mediana x1

e nosso H1 é:

  • Média/Mediana x2 > Média/Mediana x1

Conteúdo

  • Introdução
  • Construindo o dataframe
  • Análise exploratória dos dados
  • Teste de normalidade
  • Teste de variância
  • Teste de hipótese
  • Conclusão

Desenvolvimento do estudo de caso

1. Construindo o dataframe

O primeiro trabalho a ser feito é importar as bibliotecas básicas.

#Importando as bibliotecas
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

Em seguida, armazeno os resultados em um dicionário, a fim de gerar, posteriormente, uma tabela com os resultados.

#Criando dicionário com observações e experimentos
resultados = {'peca_x1':[410, 380, 350, 410, 420, 410, 409, 358, 390, 405, 406, 407, 409, 358, 390, 320, 350, 420, 415,
450, 320, 340, 405, 406, 390, 380, 381, 385, 401, 405, 390, 375, 390, 350, 401, 450, 420, 390,
370, 400, 402, 402, 401, 411, 450, 430, 310, 390, 310, 400],
'peca_x2':[450, 390, 360, 410, 421, 410, 415, 419, 380, 395, 399, 400, 410, 385, 400, 351, 375, 420, 419,
451, 319, 330, 407, 408, 400, 389, 370, 390, 402, 405, 391, 388, 391, 351, 402, 439, 420, 388,
372, 401, 400, 399, 388, 412, 451, 400, 320, 391, 309, 401]}

A criação da tabela é feita através da biblioteca Pandas. Ao criar a tabela, exibo suas dez primeiras linhas, usando a função head .

#Gerando dataframe
dt = pd.DataFrame(resultados)
#Observando dataframe
dt.head(10)

A coluna peca_x1 trata-se dos Km’s rodados pelos carros com a peça x1, enquanto a coluna peca_x2 trata-se dos km’s rodados pelos carros com a peça x2. Lembrando que os carros iniciaram o experimento com o tanque cheio.

Em seguida, tive o interesse de saber quantos carros tornaram-se mais econômicos, quantos carros tornaram-se menos econômicos e quantos carros não sofreram algum impacto com a troca de peça. Para isso, criei uma nova mostrando esse desempenho.

A criação iniciou-se com a instrução for.

#Gerando nova lista com valores de melhora e piora
v = []
for i in range(len(dt)):
if dt.peca_x2[i] < dt.peca_x1[i]:
v.append(-1)
elif dt.peca_x2[i] == dt.peca_x1[i]:
v.append(0)
elif dt.peca_x2[i] > dt.peca_x1[i]:
v.append(1)
#Gerando nova coluna no dataframe
dt['comparacao'] = v

O código acima informa que se o km rodado pelo carro com a peça x2 for menor do que seu km com a peça x1, então, esse carro recebe -1, na nova coluna. Se não houver diferença entre o carro com e sem a peça x2, então, esse carro recebe 0, na nova coluna. Porém, se o km rodado pelo carro com a peça x2 for maior do que seu km com a peça x1, então, esse carro recebe 1.

Após, exibo as 10 primeiras linhas da tabela com a nova coluna.

dt.head(10)

Em seguida, busco a contagem de ocorrência para cada performance, sabendo que as performances são: menos econômico (-1), neutro (0) e mais econômico (1).

2. Análise exploratória dos dados

#Gerando gráfico de contagem
plt.figure(figsize=(10,4))
sns.countplot(x=dt.comparacao)plt.title('Contagem dos resultados comparativos', fontsize=15)
plt.ylabel('Contagem')
plt.xlabel('Resultado')
plt.show()

Conforme o gráfico, a peça x2 tornou mais econômico a maior parte dos carros. Porém, há de se considerar que cerca de 15 carros foram tornados menos econômicos. Cerca de cinco carros não apresentaram melhoras ou pioras.

Para melhor compreensão, gero um gráfico de pizza demonstrando a distribuição percentual por performance.

#Gerando gráfico de pizza
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])
ax.axis('equal')
performance = ['-econômico', 'neutro', '+econômico']
contagem = np.unique(dt.comparacao, return_counts=True)[1]
ax.pie(contagem, labels = performance,autopct='%1.2f%%')plt.title('Percentual por performance', fontsize=15)plt.show()

De acordo com o gráfico, 62% dos carros se tornaram mais econômicos com a peça x2, enquanto 28% se tornaram menos econômicos. 10% dos carros não apresentou melhoras ou pioras.

Comparando as duas curvas de distribuição, isso é, a curva dos km’s dos carros com a peça x1 e em relação à curva dos km’s dos carros com a peça x1, tem-se:

#Comparação de curvas
plt.figure(figsize=(10,4))
sns.kdeplot(data=dt, x='peca_x1', label='peça 1', shade=True)
sns.kdeplot(data=dt, x='peca_x2', label='peça 2', shade=True)
plt.title('Performance por peça', fontsize=15)
plt.ylabel('Densidade')
plt.xlabel('Km')
plt.legend()plt.show()

Conforme as curvas, há uma aparente similaridade entre ambas, tanto em forma quanto em números. Olhando para o eixo x, em torno dos 350 km, com a peça x2, os carros tenderam a tornar-se menos econômicos. Em torno dos 400 km, os carros tenderam a tornar-se mais econômicos. Outra informação é que as curvas apresentam uma assimetria negativa. Isso as afasta da distribuição normal.

Seguinte à análise gráfico, apresento a análise estatística.

#Buscando descrição estatística
dt.describe()

Olhando, inicialmente, para a média, a peça x2 fez com que os carros, em média, rodasse aproximadamente 394 km, cerca de 4km a mais do que os carros com a peça x1. O desvio padrão evidencia maior consistência para o desempenho dos carros com a segunda peça, sendo, para os carros com a peça x2, em torno de 31 km para cima e para baixo, enquanto para os carros com a peça x1, cerca de 33 km para cima e para baixo. Com a peça x2, houve o maior percurso corrido, no valor de 451 km, contudo, houve também o menor percurso corrido, no valor de 309 km.

3. Teste de normalidade

Sabe-se que os dois grupos (carros com a peça x1 e carros com a peça x2) são dependentes, porquanto, trata-se dos mesmos carros, porém, com peças diferentes, configurando em experimento.

Essa primeira informação serve para decisão a respeito de qual teste de hipótese utilizar. Porém, outras informações são necessárias. Estou interessado em saber acerca das distribuições das curvas: se é normal ou não normal.

A configuração da curva define se o teste será paramétrico ou não paramétrico.

Segundo o já visto, as curvas apresentam uma assimetria negativa. Para validar se as curvas aproximam-se da distribuição normal, utilizo alguns testes de normalidade.

Considerando o tamanho das amostras, aplico, inicialmente, o teste de Lilliefors sobre o conjunto de dados da variável peça x1.

#Importando o teste de Lilliefors
from
statsmodels.stats.diagnostic import lilliefors
#Aplicando o teste de Lilliefors
_, p_valor_peca_x1_lilliefors = lilliefors(dt.peca_x1)
#Conferindo o resultado
alpha = 0.05 #Definindo o valor de alpha
if p_valor_peca_x1_lilliefors >= alpha:
print('Aceita H0: A distribuição segue a normal')
elif p_valor_peca_x1_lilliefors < alpha:
print('Rejeita H0: A distribuição não segue a normal')

De acordo com o teste de Lilliefors, a distribuição dos dados da peça x1 não é normal.

O mesmo teste é aplicado para a distribuição dos dados da variável da peça x2.

#Aplicando o teste de Lilliefors
_, p_valor_peca_x2_lilliefors = lilliefors(dt.peca_x2)
#Conferindo o resultado
alpha = 0.05 #Definindo o valor de alpha
if p_valor_peca_x2_lilliefors >= alpha:
print('Aceita H0: A distribuição segue a normal')
elif p_valor_peca_x2_lilliefors < alpha:
print('Rejeita H0: A distribuição não segue a normal')

Conforme o retornado, a distribuição dos dados da variável da peça x2, a distribuição não segue a distribuição normal.

Outro teste a ser utilizado, para comparação, é o Jarque-Bera. Sendo assim, o importo.

#Importando o teste Jarque-bera
from
statsmodels.stats.stattools import jarque_bera

Em seguida, aplico o teste sobre os dados da variável peça x1.

p_valor_peca_x1_jb = jarque_bera(dt.peca_x1)
p_valor_peca_x1_jb[1], p_valor_peca_x1_jb[2], p_valor_peca_x1_jb[3]

Os três valores exibidos representam, respectivamente, o p-valor, simetria e curtose. A assimetria é negativa, próxima de 0. A curva normal apresenta assimetria igual a zero. Dessa forma, a curva apresenta proximidade para com a distribuição normal considerando a assimetria.

Focando na curtose, a curva normal apresenta curtose igual a 3. A curva dos dados da variável peça x1 possui um pico maior do que a curva normal. No entanto, os valores apresentam proximidade.

Aplicando o p-valor, tem-se:

#Testando a normalidade
alpha = 0.05
if p_valor_peca_x1_jb[1] >= alpha:
print('Aceita H0: A distribuição segue a normal')
elif p_valor_peca_x1_jb[1] < alpha:
print('Rejeita H0: A distribuição não segue a normal')

De acordo com o p-valor de Jarque-Bera, a distribuição dos dados da variável peça x1 é normal. O resultado difere do apresentado pelo teste de Lilliefors. Olhando para a curtose (3.41) e para a assimetria (-0,74), os valores se aproximam das configurações de curtose e assimetria da normal padrão. Portanto, decido aceitar a evidência fornecida pelo teste de Jarque-Bera.

O seguinte passo é aplicar o teste de Jarque-Bera sobre os dados da variável peça x2.

#Aplicando Jarque-Bera
p_valor_peca_x2_jb = jarque_bera(dt.peca_x2)
p_valor_peca_x2_jb[1], p_valor_peca_x2_jb[2], p_valor_peca_x2_jb[3]

Focando primeiramente na assimetria, a curva da variável peça x2 é negativa, mais distante da distribuição normal do que a curva da variável peça x1. No tocante à curtose, a curva também se distancia da distribuição normal.

Conferindo o p-valor, tem-se que:

alpha = 0.05if p_valor_peca_x2_jb[1] >= alpha:
print('Aceita H0: A distribuição segue a normal')
elif p_valor_peca_x2_jb[1] < alpha:
print('Rejeita H0: A distribuição não segue a normal')

De acordo com o p-valor, a variável peça x2 não apresenta proximidade para com a curva normal padrão.

Ambos os testes retornaram que a distribuição da variável peça px2 não apresenta uma curva normal padrão. A está para a variável peça x1.

Por haver optado pela conclusão do teste de Jarque-Bera, evidenciando que uma das curvas se aproxima da distribuição normal, realizo o teste de variância, porquanto, dois testes de hipótese serão aplicados, se os dois grupos apresentarem mesma variância: o teste de Wilcoxon para amostras pareadas e o teste t para amostras pareadas.

4. Teste de variância — Homocedasticidade

O teste seguinte é de variância. Esse teste validará se os dados das variáveis peça x1 e peça x2 possuem mesma variância.

Para esse teste, utilizo o teste de Levene.

#Importando algoritmo levene
from scipy.stats import levene

Após importá-lo, o aplico com diferentes configurações.

#Aplicando os testes
teste_levene = levene(dt.peca_x1, dt.peca_x2, center='mean')
print('Teste de Levene')
print(np.round(teste_levene, 4))
print('')
brown_forsythe_trimmed = levene(dt.peca_x1, dt.peca_x2, center='trimmed')
print('Teste Brown-Forsythe - Trimmed')
print(np.round(brown_forsythe_trimmed, 4))
print('')
brown_forsythe_mediana = levene(dt.peca_x1, dt.peca_x2, center='median')
print('Teste Brown-Forsythe - Mediana')
print(np.round(brown_forsythe_mediana, 4))
print('')

Cada um dos testes de variância apresenta dois valores. Focaremos no segundo valor da resposta de cada teste. Esse valor funciona como o p-valor. Se ele for maior ou igual a 0,05, então, aceitamos a hipótese nula, de que as variâncias das notas das turmas são iguais entre si.

Primeiramente, testo o algoritmo levene com a média como parâmetro.

alfa = 0.05
if teste_levene[1] >= alfa:
print('Aceita H0: Os grupos apresentam variâncias iguais.')
elif teste_levene[1] < alfa:
print('Rejeita H0: Os grupos não apresentam variâncias iguais.')

Considerando a média, de acordo com o teste de Levene, os dois grupos apresentam variâncias iguais.

Em seguida, testo a homocedasticidade, utilizando a configuração trimmed para o parâmetro center.

alfa = 0.05
if brown_forsythe_trimmed[1] >= alfa:
print('Aceita H0: Os grupos apresentam variâncias iguais.')
elif brown_forsythe_trimmed[1] < alfa:
print('Rejeita H0: Os grupos não apresentam variâncias iguais.')

De acordo com o teste de Levene, com configuração trimmed, as variâncias dos dois grupos apresentam variâncias iguais.

Por fim, testo a última configuração, em que a mediana se torna o norte de decisão do algoritmo.

alfa = 0.05
if brown_forsythe_mediana[1] >= alfa:
print('Aceita H0: Os grupos apresentam variâncias iguais.')
elif brown_forsythe_mediana[1] < alfa:
print('Rejeita H0: Os grupos não apresentam variâncias iguais.')

Conforme o resultado, a hipótese nula novamente foi aceita. Sendo assim, pode-se concluir que os km’s de ambos os grupos apresentam variâncias iguais entre si.

Com isso, aplico os dois testes já mencionados. A espectativa é de que os resultados de ambos os testes sejam iguais, porquanto, evidenciariam, assim, o mesmo fato estatístico.

5. Aplicação dos testes de hipótese

O primeiro teste a ser aplicado é o teste de Wilcoxon para amostras pareadas. Ele foi selecionado devido a dois critérios:

1 — Os grupos são dependentes

2 — Ao menos uma das curvas não se aproxima da normal padrão

Considerado isso, inicio a importação do teste.

#Importando teste de Wilcoxon
from scipy.stats import wilcoxon

Em seguida, o aplico e crio uma condição para o retorno da resposta.

#Aplicando o teste
_, p_valor_wilcoxon = wilcoxon(dt.peca_x1, dt.peca_x2)
#Aplicando condição para retorno da resposta
if p_valor_wilcoxon >= alpha:
print("Aceita H0: a mediana 400,00(peça x2) <= a mediana 400,50 (peça x1)")
elif p_valor_wilcoxon < alpha:
print("Rejeita H1: a mediana 400,00 (peça x2) > a mediana 400,50 (peça x1)")

De acordo com a resposta retornada do teste de Wilcoxon para amostras pareadas, com um nível de significância de 5%, a média de km’s percorridos pelos carros quando usavam a peça x2 não é estatisticamente maior do que a média de km’s percorridos quando usavam a peça x1.

Após, aplico o teste t para amostras pareadas. Esse teste foi selecionado devido a três aspectos:

1 — Os grupos são dependentes

2 — De acordo com o teste de Jarque-Bera, ao menos uma das curvas se aproxima da normal padrão

3 — Ambos os dados dos grupos apresentam mesma variância

Considerado isso, importo o teste.

#Importando teste de Teste t para amostras pareadas
from scipy.stats import ttest_rel

Após a importação, aplico o teste e crio uma condição para o retorno da resposta.

#Aplicando o teste
_, p_valor_ttest_rel = ttest_rel(dt.peca_x1, dt.peca_x2)
#Aplicando condição para retorno da resposta
if p_valor_ttest_rel >= alpha:
print("Aceita H0: a média 393.88 (peça x2) <= a média 390.44 (peça x1)")
elif p_valor_ttest_rel < alpha:
print("Rejeita H1: a média 393.88 (peça x2) > a média 390.44 (peça x1)")

De acordo com a resposta retornada do teste t para amostras pareadas, com um nível de significância de 5%, a média de km’s percorridos pelos carros com a peça x2 não é estatisticamente maior do que a média de km’s percorridos com a peça x1.

Conclusão

Utilizando o teste de Wilcoxon para amostras pareadas e o teste t para amostras pareadas, dentro das ponderações já realizadas, através de ambos os testes, com um nível de significância de 5%, não há evidência de que a peça x2 torne os carros mais econômicos.

--

--

Bruno Carloto

Bem-vindo ao Deep Analytics, um blog que aborda de forma técnica o mundo Analytics | LinkedIn: www.linkedin.com/in/bruno-rodrigues-carloto