Novembro de 2015

Volume 30, Número 12

Execução de Teste - Teste T Usando C#

Por James McCaffrey

James McCaffreyO teste t é uma das formas mais fundamentais de análise estatística. Sua meta é determinar se os meios (médias) de dois conjuntos de números são iguais quando você tem apenas amostras dos dois conjuntos. A ideia é melhor explicada por um exemplo. Suponhamos que você esteja investigando a capacidade matemática de homens e mulheres no ensino médio em um grande distrito escolar. O teste de capacidade é expansivo e demorado, portanto você não pode aplicar o teste a todos os alunos. Em vez disso, você seleciona, aleatoriamente, uma amostra de 10 homens e 10 mulheres e aplica o teste de matemática. Por meio dos resultados da amostra, você poderá executar um teste t para inferir se a pontuação média verdadeira de todos os homens é igual à pontuação média verdadeira de todas as mulheres.

Há diversas ferramentas autônomas, incluindo o Excel, que podem executar um teste t. Mas, se você desejar integrar a funcionalidade de teste t diretamente a um sistema de software, pode ser difícil ou impossível fazer isso usando ferramentas autônomas, além de poder envolver direitos autorais ou outros problemas legais. Este artigo explica como executar um teste t usando código C# bruto (sem bibliotecas externas).

A melhor maneira de ter uma noção do que é o teste T e ver onde quero chegar com esse artigo, é examinar o programa de demonstração na Figura 1. O primeiro conjunto de dados é { 88, 77, 78, 85, 90, 82, 88, 98, 90 }. Você pode imaginar que essas são as pontuações do teste de 10 homens, em que um deles desistiu por algum motivo, deixando apenas nove pontos.

Demonstração do Teste T Usando C#
Figura 1 - Demonstração do Teste T Usando C#

O segundo conjunto de dados é { 81, 72, 67, 81, 71, 70, 82, 81 }. Você pode imaginar que essas são as pontuações do teste de 10 mulheres, em que uma delas desistiu por algum motivo, deixando apenas oito pontos. O meio do primeiro conjunto de dados é 86.22 e o do segundo conjunto de dados é 75.63, sugerindo que o meio dos dois grupos não é o mesmo, pois há uma diferença de quase 11 pontos. Mas, mesmo se a pontuação média geral dos dois grupos (todos os homens e todas as mulheres) fosse a mesma, já que somente amostras estão sendo usadas, a diferença na média de amostras pode ter ocorrido por acaso.

Usando os dois conjuntos de dados de amostra, o programa de demonstração calcula uma “estatística t” (t) com o valor 3,4233 e “graus de liberdade” (comumente abreviados como df ou indicados pela letra grega minúscula nu, v) valor de 14,937. Em seguida, usando valores t e df, um valor de probabilidade (valor p) é calculado com o valor 0,00379. Há diversas formas do teste t. Provavelmente, a mais comum é denominada teste t de Alunos. A demonstração usa uma variação aprimorada chamada teste t Welch.

O valor p é a probabilidade de que as médias verdadeiras das duas populações (todos os homens e todas as mulheres) são as mesmas, de acordo com a pontuação de amostra e, portanto, que a diferença observada de cerca de 11 pontos ocorreu por acaso. Neste caso, o valor p é muito pequeno, portanto, seria possível concluir que as médias verdadeiras de todos os homens e de todas as mulheres não são iguais. Na maioria dos problemas, o valor p crítico para comparação com o valor p calculado é definido arbitrariamente para ser 0,01 ou 0,05.

Ao colocar ligeiramente diferente, se as pontuações médias verdadeiras de todos os homens e todas as mulheres fossem as mesmas, a probabilidade de você ver a diferença observada de cerca de 11 pontos nas duas médias de amostra do tamanho nove e oito é somente de 0,00379, extremamente improvável.

Este artigo pressupõe que você tenha habilidades de programação que sejam, pelo menos, intermediárias, mesmo que não tenha familiaridade alguma com teste t. A demonstração é codificada usando C#, mas você não deve ter muita dificuldade para refatorar o código em outra linguagem, como Visual Basic .NET ou JavaScript.

Entendendo a Distribuição T

O teste t é baseado na distribuição t. E a distribuição t está extremamente relacionada à distribuição normal (também chamada de gaussiana ou em formato de sino). A forma de um conjunto de dados de distribuição normal depende do meio e do desvio padrão dos dados. O desvio padrão é um valor que mede o espalhamento ou a variação dos dados. Um caso especial ocorre quando o meio (comumente indicado pela letra grega mu, µ) é 0 e o desvio padrão (comumente abreviado em inglês como sd, ou indicado pela letra grega sigma, σ) é 1. A distribuição normal com o meio = 0 e sd = 1 é chamada de distribuição normal padrão. Seu gráfico é mostrado na Figura 2.

Distribuição Normal Padrão
Figura 2 - Distribuição Normal Padrão

Na Figura 2, a equação que define a distribuição normal padrão é chamada de função de densidade da probabilidade. A distribuição t se parece com a distribuição normal. A forma de uma distribuição t depende de um único valor chamado “graus de liberdade”. A distribuição t com df = 5 é exibida na Figura 3.

Na Figura 3, a equação que define a distribuição t envolve a função Gama, que é indicada pela letra maiúscula grega gama (Γ). Para executar um teste t, você precisa calcular e somar duas áreas idênticas sob a curva da distribuição t. Esta área combinada é o valor p. Por exemplo, na Figura 3, se o valor de t for 2,0, as áreas combinadas sob a curva que você precisa vão de -infinito a -2,0 e +2,0 a +infinito. Neste caso, a área combinada, que é o valor p, é 0,101939. Para o programa de demonstração, quando t = 3,4233, a área combinada é 0,00379.

Distribuição T
Figura 3 - Distribuição T

Certo, mas como a área sob a distribuição t é calculada? Há diversas abordagens para este problema, mas a técnica mais comum é calcular uma única área associada sob a curva da distribuição normal padrão e usá-la para calcular o valor p. Por exemplo, na Figura 2, se z (e equivalente normal de t) tiver o valor -2,0, você pode calcular a área de -infinito a -2,0, que é 0,02275. Esta área sob a curva normal pode, então, ser usada para calcular a área correspondente sob a distribuição t.

Para resumir, para executar um teste t, você deve calcular e somar duas áreas (iguais) sob uma distribuição t. Esta área é chamada de valor p. Para fazer isso, é possível calcular uma única área sob a distribuição normal padrão e, em seguida, usar tal área para obter o valor p.

Calculando a Área na Distribuição Normal Padrão

Há diversas formas de calcular a área sob a curva de distribuição normal padrão. Este é um dos problemas mais antigos da ciência da computação. Meu método preferido é usar o chamado algoritmo da ACM nº 209. A ACM (Association for Computing Machinery) publicou diversos algoritmos fundamentais para computação numérica e estatística.

Uma implementação C# do algoritmo nº 209 é apresentada na Figura 4 como uma função Gauss. A função aceita um valor, z, entre -infinito e +infinito e retorna uma aproximação próxima à área sob a distribuição normal padrão de -infinito a z.

Figura 4 - Calculando a Área na Distribuição Normal Padrão

public static double Gauss(double z)
{
  // input = z-value (-inf to +inf)
  // output = p under Standard Normal curve from -inf to z
  // e.g., if z = 0.0, function returns 0.5000
  // ACM Algorithm #209
  double y; // 209 scratch variable
  double p; // result. called 'z' in 209
  double w; // 209 scratch variable
  if (z == 0.0)
    p = 0.0;
  else
  {
    y = Math.Abs(z) / 2;
    if (y >= 3.0)
    {
      p = 1.0;
    }
    else if (y < 1.0)
    {
      w = y * y;
      p = ((((((((0.000124818987 * w
        - 0.001075204047) * w + 0.005198775019) * w
        - 0.019198292004) * w + 0.059054035642) * w
        - 0.151968751364) * w + 0.319152932694) * w
        - 0.531923007300) * w + 0.797884560593) * y * 2.0;
    }
    else
    {
      y = y - 2.0;
      p = (((((((((((((-0.000045255659 * y
        + 0.000152529290) * y - 0.000019538132) * y
        - 0.000676904986) * y + 0.001390604284) * y
        - 0.000794620820) * y - 0.002034254874) * y
        + 0.006549791214) * y - 0.010557625006) * y
        + 0.011630447319) * y - 0.009279453341) * y
        + 0.005353579108) * y - 0.002141268741) * y
        + 0.000535310849) * y + 0.999936657524;
    }
  }
  if (z > 0.0)
    return (p + 1.0) / 2;
  else
    return (1.0 - p) / 2;
}

Mesmo uma rápida análise no código da Figura 4 deve convencer você de que usar um algoritmo existente, como o ACM nº 209, é muito mais fácil do que codificar sua própria implementação partindo do zero. Uma alternativa ao ACM nº 209 é usar uma leve modificação da equação 7.1.26 do “Handbook of Mathematical Functions” de Milton Abramowitz e Irene A. Stegun (Dover Publications, 1965).

Calculando a Área na Distribuição T

Com uma implementação da função Gauss em mãos, a área sob a distribuição t pode ser calculada usando o algoritmo da ACM nº 395. Uma implementação C# de algoritmo nº 395 é apresentada na Figura 5 como uma função Aluno. A função aceita um valor t e um valor df e retorna a área combinada de -infinito a t+ a +infinito.

Figura 5 - Calculando a Área na Distribuição T

public static double Student(double t, double df)
{
  // for large integer df or double df
  // adapted from ACM algorithm 395
  // returns 2-tail p-value
  double n = df; // to sync with ACM parameter name
  double a, b, y;
  t = t * t;
  y = t / n;
  b = y + 1.0;
  if (y > 1.0E-6) y = Math.Log(b);
  a = n - 0.5;
  b = 48.0 * a * a;
  y = a * y;
  y = (((((-0.4 * y - 3.3) * y - 24.0) * y - 85.5) /
    (0.8 * y * y + 100.0 + b) + y + 3.0) / b + 1.0) *
    Math.Sqrt(y);
  return 2.0 * Gauss(-y); // ACM algorithm 209
}

O algoritmo nº 395 tem duas formas. Uma delas aceita o parâmetro df como um valor inteiro e a segunda forma aceita df como um valor de tipo duplo. Na maioria dos problemas de estatística, os graus de liberdade são valores inteiros, mas o teste t Welch usa um valor de tipo duplo.

O programa de demonstração

Para criar o programa de demonstração, iniciei o Visual Studio e criei um novo aplicativo do console C# TTest. A demonstração não tem dependências significativas da versão do .NET, portanto, funcionará em qualquer versão do Visual Studio. Após o código de amostra ser carregado no editor, excluí todas as instruções em uso, exceto pela única referência para o namespace do Sistema de nível superior. Na janela Gerenciador de Soluções, renomeei o arquivo Program.cs para TTestProgram.cs e permiti ao Visual Studio renomear a classe Program para mim automaticamente.

O programa de demonstração é um pouco longo para ser apresentado por completo aqui, mas o código-fonte completo pode ser encontrado no download de arquivo que acompanha este artigo. O método Principal começa com a definição e a exibição de dois conjuntos de dados de amostra:

Console.WriteLine("\nBegin Welch's t-test using C# demo\n");
var x = new double[] { 88, 77, 78, 85, 90, 82, 88, 98, 90 };
var y = new double[] { 81, 72, 67, 81, 71, 70, 82, 81 };
Console.WriteLine("\nThe first data set (x) is:\n");
ShowVector(x, 0);
Console.WriteLine("\nThe second data set (y) is:\n");
ShowVector(y, 0);

Todo o trabalho é executado em um método chamado TTest:

Console.WriteLine("\nStarting Welch's t-test using C#\n");
TTest(x, y);
Console.WriteLine("\nEnd t-test demo\n");
Console.ReadLine();

A definição do método TTest começa ao somar os valores em cada conjunto de dados:

public static void TTest(double[] x, double[] y)
{
  double sumX = 0.0;
  double sumY = 0.0;
  for (int i = 0; i < x.Length; ++i)
    sumX += x[i];
  for (int i = 0; i < y.Length; ++i)
    sumY += y[i];
...

Em seguida, as somas são usadas para calcular os dois meios de amostra:

int n1 = x.Length;
int n2 = y.Length;
double meanX = sumX / n1;
double meanY = sumY / n2;

Depois, as somas são usadas para calcular as duas variações de amostra:

double sumXminusMeanSquared = 0.0; // Calculate variances
double sumYminusMeanSquared = 0.0;
for (int i = 0; i < n1; ++i)
  sumXminusMeanSquared += (x[i] - meanX) * (x[i] - meanX);
for (int i = 0; i < n2; ++i)
  sumYminusMeanSquared += (y[i] - meanY) * (y[i] - meanY);
double varX = sumXminusMeanSquared / (n1 - 1);
double varY = sumYminusMeanSquared / (n2 - 1);

A variação de um conjunto de dados é o quadrado do desvio padrão; portanto, o desvio padrão é a raiz quadrada da variação e o teste t funciona com variações. Em seguida, a estatística t é calculada:

double top = (meanX - meanY);
double bot = Math.Sqrt((varX / n1) + (varY / n2));
double t = top / bot;

Isso quer dizer que a estatística t é a diferença entre os dois meios de amostra, dividida pela raiz quadrada da soma das variações divididas pelos tamanhos de amostra associados. Em seguida, os graus de liberdade são calculados:

double num = ((varX / n1) + (varY / n2)) *
  ((varX / n1) + (varY / n2));
double denomLeft = ((varX / n1) * (varX / n1)) / (n1 - 1);
double denomRight = ((varY / n2) * (varY / n2)) / (n2 - 1);
double denom = denomLeft + denomRight;
double df = num / denom;

O cálculo dos graus de liberdade para o teste t Welch é, de certa forma, difícil e a equação não é óbvia. Felizmente, você nunca precisará modificar este cálculo. O Método TTest é concluído ao computar o valor p e exibir todos os valores calculados:

...
  double p = Student(t, df); // Cumulative two-tail density
  Console.WriteLine("mean of x = " + meanX.ToString("F2"));
  Console.WriteLine("mean of y = " + meanY.ToString("F2"));
  Console.WriteLine("t = " + t.ToString("F4"));
  Console.WriteLine("df = " + df.ToString("F3"));
  Console.WriteLine("p-value = " + p.ToString("F5"));
  Explain();
}

O método definido pelo programa denominado Explicar exibe informações explicando a interpretação do valor p, como mostrado na Figura 1.

Alguns comentários

Há, na verdade, diversos tipos diferentes de problemas de estatística que envolvem o teste t. O tipo de problema descrito neste artigo é, às vezes, chamado de um teste t não emparelhado, pois não há conexão conceitual entre os valores de dados em cada conjunto de dados de amostra. Outro tipo de teste t é chamado de teste de amostra emparelhado, que pode ser usado quando você tiver algum tipo de dado de antes e depois, como uma pontuação de teste antes de obter instruções seguidas por uma pontuação de teste após a instrução. Aqui, cada par de pontuação está relacionado de modo conceitual.

O teste t Welch apresentado aqui é superior ao teste t Aluno mais comum na maioria dos cenários. O teste t Aluno costuma exigir um número igual ao de pontos de dados em cada um dos dois conjuntos de dados de amostra e exige que as variações das duas amostras sejam, aproximadamente, iguais. O teste t Welch pode funcionar com tamanhos de amostra desiguais e é robusto, mesmo quando as variações de amostra diferirem.

O tipo de teste t explicado neste artigo é denominado um teste bicaudal. Isso quer dizer quase a mesma coisa que um problema em que a meta é determinar se os meios de dois grupos são os mesmos. Um teste t unicaudal pode ser usado em situações em que a meta é determinar se o meio de um grupo é maior que o meio do segundo grupo. Ao executar um teste t unicaudal, você divide o valor p bicaudal por 2.

Você deve ser muito conservador ao interpretar os resultados de um teste t. Uma conclusão nas linhas de “Com base em um valor p do teste t calculado de 0,008, concluo que é improvável que os meios da população verdadeira de homens e mulheres sejam os mesmos”, é muito melhor que “O valor p de 0,008 significa que a pontuação média de homens é maior que a de mulheres”.

Uma opção alternativa ao teste t é denominada teste U Mann-Whitney. Ambas as técnicas inferem se os meios das duas populações são iguais ou não com base em amostras, mas o teste U Mann-Whitney faz menos suposições estatísticas, levando a conclusões mais conservadoras (você tem menos chances de concluir que os meios analisados são diferentes).

O teste t é limitado a situações em que há dois grupos. Para problemas ao examinar os meios de três ou mais grupos, você usaria uma análise denominada teste F.


Dr. James McCaffreytrabalha para a Microsoft Research em Redmond, Washington. Ele trabalhou em vários produtos da Microsoft, incluindo Internet Explorer e Bing. Entre em contato com o Dr. McCaffrey pelo email jammc@microsoft.com.

Agradecemos ao seguinte especialista técnico da Microsoft Research pela revisão deste artigo: Kirk Olynyk