Teste t de Student bilateral para uma média com variância desconhecida – com Python

Em um post recente nós estudamos com detalhes como aplicar o teste t de Student bilateral para comparar uma média, com distribuição Normal e variância desconhecida, com um valor esperado ou conhecido.  Também já vimos como fazer estes cálculos utilizando a linguagem R e também com ExcelNeste artigo utilizamos outra a melhor linguagem de programação, e vamos aprender sobre como aplicar o teste t para uma média com Python! Aqui vamos focar apenas na aplicação do teste, mas caso você tenha dúvidas sobre o teste t, conjunto de dados e premissas adotadas, veja o post original.

Neste artigo você encontra:

Os códigos foram escritos utilizando Python  3.10.12  com as bibliotecas:

matplotlib v. 3.7.1

numpy v. 1.22.4

scipy v. 1.10.1

O notebook do Google Colab com este exemplo pode ser acessado neste link. Para você utiliza-lo, irá precisar fazer um cópia do arquivo original para uma conta do Google Drive.

Importações

As bibliotecas acima devem ser importadas para o ambiente Colab. É recomendado fazer isto logo nas primeiras linhas do código:

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

Entrada de dados

x = np.array([369, 368, 367, 388, 379, 371, 399])
mu_zero = 374
alpha = 0.05 # entre 0 e 1

Aplicando o teste

O teste de médias t de Student para uma amostra com variância desconhecida pode ser realizado utilizando a biblioteca SciPy (VIRTANEN, 2020). Nesta biblioteca temos a scipy.stats.ttest_1samp(), que aplica o teste (veja a documentação para detalhes específicos). Esta função deve receber pelo menos dois parâmetros:

  • O primeiro é o conjunto de dados armazenado em um array.
  • O segundo é o valor esperado armazenado como um número (int ou float). 

Temos ainda o parâmetro alternative que controla qual é a hipótese alternativa do teste. Por padrão, alternative=”two-sided” que  irá retornar o resultado para o teste bilateral. As outras são opções são “less” (teste unilateral à esquerda) ou “greater” (teste unilateral à direita). Como o padrão é “two-sided”, não precisamos passar o parâmetro alternative.

A função scipy.stats.ttest_1samp() retorna um object com diversos atributos. O atributo statistic contém a estatística do teste ($t_{0}$).

result = stats.ttest_1samp(x, mu_zero)
print(result.statistic)
>>> 0.7140011364639592

Cálculo do grau de liberdade

O objeto retornado da função scipy.stats.ttest_1samp() contém o atributo df que traz o gl do teste.

print(result.df)
>>> 6

Obtenção do valor crítico

Os valores críticos da distribuição t de Student podem ser obtidos com a função stats.stats.t.ppf() (veja maiores detalhes na documentação). Esta função recebe como primeiro parâmetro o nível de significância adotado e como segundo parâmetro ela recebe o grau de liberdade do teste. Porém, o nível de significância é unilateral. Por isto, devemos utilizar $1-\alpha/2$ para o obter o valor correto e positivo.

t_critico = stats.t.ppf(1-alpha/2, result.df)
print(t_critico)
>>> 2.4469118487916806

Obtenção do p-valor

O objeto retornado da função scipy.stats.ttest_1samp() contém o atributo pvalue que traz o p-valor do teste.

print(result.pvalue)
>>> 0.40978280474713114

Conclusão

Comparando o valor crítico ($t_{critico}$) com a estatística do teste ($t_{0}$)

Para concluir o teste utilizando Python basta criar uma lógica condicional if e else , seguindo os critérios determinados pelo teste.

if np.abs(result.statistic) > t_critico:
    print(f"Rejeitamos a H₀ ({x.mean()} ≠ {mu_zero}, adotando {100*(1-alpha)}% de confiança )")
else:
    print(f"Falhamos em rejeitar H₀ ({x.mean()} = {mu_zero}, adotando {100*(1-alpha)}% de confiança )")
>>> Falhamos em rejeitar H₀ (378.7142857142857 = 374, adotando 95.0% de confiança )

Comparando a probabilidade ($p-valor$) com o nível de significância adotado ($\alpha$)

De forma análoga ao utilizado acima, concluímos o teste seguindo os critérios determinados pelo teste:

if result.pvalue < alpha:
    print(f"Rejeitamos a H₀ ({x.mean()} ≠ {mu_zero}, adotando {100*(1-alpha)}% de confiança )")
else:
    print(f"Falhamos em rejeitar H₀ ({x.mean()} = {mu_zero}, adotando {100*(1-alpha)}% de confiança )")
>>> Falhamos em rejeitar H₀ (378.7142857142857 = 374, adotando 95.0% de confiança )

Gráfico da distribuição t de Student

odemos desenhar o gráfico da distribuição t de Student identificando a área referente ao $p-valor$, a estatística do teste ($t_{0}$) e o valor crítico ($t_{critico}$). Porém, é preciso criar uma função, que foi definida em student_two_side_plot(). Esta função deve receber quatro (4) parâmetros:

  • axes : um instância de objeto matplotlib.axes.SubplotBase ;
  • tcalc: a estatística do teste (float);
  • gl: o grau de liberdade do teste (int);
  • alpha: o nível de significância adotado (float).

Também podemos passar o parâmetro style, que controla o estilo do gráfico. Se style=None, o gráfico é desenhado colorido. Caso style=”grayscale”, o gráfico é desenhado em escala de cinza. Também é possível passar um dict com uma série de chaves que controlam cada elemento do gráfico.

A função retorna uma instância de matplotlib.axes.SubplotBase que pode ser adicionada em uma figura do matplotlib.

def student_two_side_plot(axes, tcalc, gl, alpha=0.05, style=None):
    """This function is experimental and its behavior may not be ideal.
    """
    default_style = {
        "dist_line_color": "k",
        "dist_line_style": "-",
        "critical_line_color": "b",
        "critical_line_style": "--",
        "marker_ecolor": "r",
        "marker_facecolor": "r",
        "fill_color": "salmon",
        "fill_transparency": 0.5,
    }
    if style is None:
        style = default_style
    elif style == "grayscale":
        style = {
        "dist_line_color": "k",
        "dist_line_style": "-",
        "critical_line_color": "gray",
        "critical_line_style": "--",
        "marker_ecolor": "k",
        "marker_facecolor": "white",
        "fill_color": "lightgray",
        "fill_transparency": 1,
        }
    else:
        checkers._check_is_dict(style, "style")
        if "dist_line_color" not in style.keys():
            style["dist_line_color"] = default_style["dist_line_color"]
        if "dist_line_style" not in style.keys():
            style["dist_line_style"] = default_style["dist_line_style"]
        if "critical_line_color" not in style.keys():
            style["critical_line_color"] = default_style["critical_line_color"]
        if "critical_line_style" not in style.keys():
            style["critical_line_style"] = default_style["critical_line_style"]
        if "marker_ecolor" not in style.keys():
            style["marker_ecolor"] = default_style["marker_ecolor"]
        if "marker_facecolor" not in style.keys():
            style["marker_facecolor"] = default_style["marker_facecolor"]
        if "fill_color" not in style.keys():
            style["fill_color"] = default_style["fill_color"]
        if "fill_transparency" not in style.keys():
            style["fill_transparency"] = default_style["fill_transparency"]

    t_critico = stats.t.ppf(1-alpha/2, gl)

    if np.abs(tcalc) < t_critico:
        x_lim = 6
    else:
        x_lim = np.abs(tcalc)*1.3

    # criando valores para a distribuição t de Student com gl
    x = np.linspace(-1*x_lim, x_lim, 1000)
    y = stats.t.pdf(x, gl)
    label = "$gl=" + str(gl) + "$"
    axes.plot(x, y, c=style["dist_line_color"], ls=style["dist_line_style"], label=label)

    # adicionando linhas com o valor crítico do teste
    label= '$t_{tab}=' + str(round(t_critico, 2)) + '$'
    axes.axvline(stats.t.ppf(1-alpha/2, gl), 0, 1, label=label, color=style["critical_line_color"], ls=style["critical_line_style"])
    axes.axvline(stats.t.ppf(alpha/2, gl), 0, 1, color=style["critical_line_color"], ls=style["critical_line_style"])

    # adicionando o valor da estatística do teste
    label = "$t_{calc}=" + str(round(tcalc, 3)) + '$'
    axes.scatter(tcalc, stats.t.pdf(np.abs(tcalc), gl), label=label, facecolor=style["marker_facecolor"], edgecolor=style["marker_ecolor"], s=50, zorder=10)

    # preenchendo o lado esquero
    x = np.linspace(-1*x_lim, -1*abs(tcalc), 1000)
    pvalue = (1 - stats.t.cdf(np.abs(tcalc), gl))*2
    if pvalue < 0.001:
        label = "$pvalue<0.001$"
    else:
        label = "$pvalue=" + f"{round(pvalue, 3)}" + "$"
    axes.fill_between(x, stats.t.pdf(x, gl), label=label, color=style["fill_color"], alpha=style["fill_transparency"])

    # preenchendo o lado direito
    x = np.linspace(abs(tcalc), x_lim, 1000)
    axes.fill_between(x,stats.t.pdf(x, gl, loc=0, scale=1), color=style["fill_color"], alpha=style["fill_transparency"])

    axes.set_xlim(-1*x_lim - .1, x_lim + .1)
    axes.set_ylim(bottom=0.0)
    axes.set_xlabel("$x$")
    axes.set_ylabel("$Density$")
    axes.legend(loc=1, fontsize=8)

    return axes

O gráfico pode ser desenhado utilizando as linhas abaixo.

fig, ax = plt.subplots(figsize=(8,4))
student_two_side_plot(axes=ax, tcalc=result.statistic, gl=result.df, alpha=alpha, style=None)
# plt.savefig("student-plot-t.png", bbox_inches='tight')
plt.show()
Gráfico da distribuição t de student para seis graus de liberdade, com a área bilateral preenchida na cor salmão, valores críticos delimitando o ponto de decisão do teste na cor azul e o ponto com o valor da estatística em vermelho, indicando que o teste falha em rejeitar a hipótese nula.

Conclusão geral

Neste artigo vimos de forma bem prática como aplicar o teste t de Student bilateral para comparar uma média com um valor especificado utilizando Python. Os passos são relativamente simples, porém as etapas não são tão intuitivas como é no R. Porém, basta o pesquisador não ficar com receio de utilizar uma linguagem de programação que irá conseguir fazer este tipo de análise (e análises mais complexas).

Referências

VIRTANEN, P. et al. SciPy 1.0: Fundamental Algorithms for Scientific Computing in Python. Nature Methods, v. 17, p. 261–272, 2020.

STUDENT. The Probable Error of a Mean. Biometrika, v. 6, n. 1, p. 1, mar. 1908. DOI: https://doi.org/10.2307/2331554.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *