Outubro de 2016

Volume 31 – Número 10

Execução de Teste - ANOVA com C#

Por James McCaffrey

James McCaffreyA análise de variação (ANOVA) é uma técnica de estatística clássica usada para inferir se as médias de três ou mais grupos são todas iguais em situações em que você só tem dados de exemplo. Por exemplo, suponha que haja três turmas introdutórias de ciência da computação diferentes em uma universidade. As aulas são dadas pelo mesmo professor, mas têm um livro didático e uma filosofia de ensino diferentes. Você quer saber se o desempenho dos alunos é o mesmo ou não.

Você tem um exame que avalia a proficiência em ciência da computação em uma escala de 1 a 15, mas como o exame é muito caro e demanda muito tempo, você só pode aplicar o exame para seis alunos escolhidos aleatoriamente em cada turma. Você aplica o exame e realiza a ANOVA nos exemplos para inferir se as médias das três turmas são as mesmas ou não.

Se você for novo na ANOVA, o nome da técnica pode ser ligeiramente confuso porque a meta é analisar a média dos conjuntos de dados. A ANOVA tem este nome porque, nos bastidores, ela analisa as variações para fazer inferências sobre as médias.

Uma boa maneira de ter uma noção do que é a ANOVA e ver onde este artigo pretende chegar é examinar o programa de demonstração na Figura 1. A demonstração estabelece pontuações codificadas para três grupos. Observe que há apenas quatro pontuações no Grupo 1 e apenas cinco pontuações no Grupo 3. É bem comum que os tamanhos dos exemplos sejam desiguais porque os sujeitos do teste podem desistir ou os dados podem ser perdidos ou corrompidos.

ANOVA com Programa de Demonstração de C#
Figura 1 ANOVA com Programa de Demonstração de C#

Há duas etapas principais na ANOVA. Na primeira etapa, um valor de estatística F e um par de valores chamado “graus de liberdade” (degrees of freedom - df) são calculados usando os dados do exemplo. Na segunda etapa, os valores de F e df são usados para determinar a probabilidade de que as médias de toda a população sejam as mesmas (o valor p). A primeira etapa é relativamente fácil. A segunda etapa é muito difícil.

Na demonstração, o valor de F é 15,884. Em geral, quanto maior é F, menos provavelmente as médias de toda a população serão iguais. Vou explicar rapidamente porque df = (2,12). Usando F e df, o valor p é calculado para ser 0,000425. Isto é muito pouco, portanto, você concluiria que a média da população provavelmente não é a mesma. Neste ponto, você poderia realizar testes estatísticos adicionais para determinar quais médias da população são diferentes umas da outra. Para os dados de demonstração, parece que o Grupo 1 (média do exemplo = 4,50) é pior do que o Grupo 2 (média = 9,67) e o Grupo 3 (média = 10,60).

O programa de demonstração

Para criar o programa de demonstração, eu iniciei o Visual Studio, cliquei em Arquivo | Novo | Projeto e selecionei a opção do aplicativo do console do C#. O programa de demonstração não tem dependências significativas do .NET, portanto, funcionará em qualquer versão do Visual Studio. Após o código de modelo ser carregado no editor, na janela Gerenciador de Soluções, eu cliquei com o botão direito no arquivo Program.cs e renomeei como AnovaProgram.cs e permiti que o Visual Studio renomeasse automaticamente a classe Program para mim.

Na parte superior da janela Editor, excluí tudo o que era desnecessário usando instruções, exceto o que faz referência ao namespace do Sistema de nível superior. A estrutura geral do programa é exibida na Figura 2. O programa de demonstração é um pouco longo para ser apresentado por completo, mas o código-fonte demonstrativo está disponível no download que acompanha este artigo.

Figura 2 Estrutura do programa de demonstração

using System;
namespace Anova
{
  class AnovaProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Begin ANOVA using C# demo");
      // Set up sample data
      // Use data to calculate F and df
      // Use F and df to calculate p-value
      Console.WriteLine("End ANOVA demo");
    }
    static double Fstat(double[][] data, out int[] df) { . . }
    static double LogGamma(double z) { . . }
    static double BetaIncCf(double a, double b, double x) { . . }
    static double BetaInc(double a, double b, double x) { . . }
    static double PF(double a, double b, double x) { . . }
    static double QF(double a, double b, double x) { . . }
    static void ShowData(double[][] data, string[] colNames) { . . }
  }
}

O método estático Fstat computa e retorna uma estatística F baseada nos dados armazenados em um objeto matriz de matrizes. O método também calcula e retorna dois valores df em um parâmetro fora da matriz. A função ShowData é uma função auxiliar para exibir as médias dos exemplos.

Os cinco métodos restantes são utilizados para calcular o valor p. O método QF é o método principal. Ele chama o método PF que, por sua vez, chama o método BetaInc que, por sua vez, chama o método BetaIncCf e o LogGamma.

Após algumas mensagens WriteLine preliminares, o método principal estabelece e exibe os dados de exemplo:

double[][] data = new double[3][]; // 3 groups
data[0] = new double[] { 3, 4, 6, 5 };
data[1] = new double[] { 8, 12, 9, 11, 10, 8 };
data[2] = new double[] { 13, 9, 11, 8, 12 };
string[] colNames = new string[] {  "Group1", "Group2", "Group3" };
ShowData(data, colNames);

Em um cenário que não seja de demonstração, seus dados provavelmente seriam armazenados em um arquivo de texto e você escreveria uma função auxiliar para ler e carregar os dados em uma matriz de matrizes.

A estatística F e df são calculados da seguinte forma:

int[] df = null;
double F = Fstat(data, out df);

Para a ANOVA, o df para um conjunto de dados é um par de valores. O primeiro valor é K - 1, onde K é o número de grupos, e o segundo valor é N - K, onde N é o número total de valores de exemplo. Então, para os dados de demonstração, df = (K-1, N-K) = (3-1, 15-3) = (2,12).

O valor p é computado e exibido assim:

double pValue = QF(df[0], df[1], F);
Console.Write("p-value = ");

Resumindo, ao executar uma ANOVA, os demonstrativos de chamada são muito simples. Mas há muito trabalho nos bastidores.

Calculando a Estatística F

Calcular o valor de uma estatística F tem diversas subetapas. Suponha que os valores dos dados de exemplo sejam aqueles da demonstração:

Group1: 3.00, 4.00, 6.00, 5.00
Group2: 8.00, 12.00, 9.00, 11.00, 10.00, 8.00
Group3: 13.00, 9.00, 11.00, 8.00, 12.00

A primeira subetapa é calcular a média de cada grupo e a média geral de todos os valores de exemplos. Para os dados de demonstração:

means[0] = (3.0 + 4.0 + 6.0 + 5.0) / 4 = 4.50
means[1] = (8.0 + 12.0 + 9.0 + 11.0 + 10.0 + 8.0) / 6 = 9.67
means[2] = (13.0 + 9.0 + 11.0 + 8.0 + 12.0) / 5 = 10.60
gMean = (3.0 + 4.0 + . . . + 12.0) / 15 = 8.60

A definição do método Fstat começa por:

static double Fstat(double[][] data, out int[] df)
{
  int K = data.Length; // Number groups
  int[] n = new int[K]; // Number items each group
  int N = 0; // total number data points
  for (int i = 0; i < K; ++i) {
    n[i] = data[i].Length;
    N += data[i].Length;
  }
...

Neste ponto, a matriz local n tem o número de valores em cada grupo, K tem o número de grupos e N é o número total de valores em todos os grupos. Em seguida, as médias dos grupos são calculadas em uma matriz chamada médias e a grande média geral é calculada em um gMean variável:

double[] means = new double[K];
double gMean = 0.0;
for (int i = 0; i < K; ++i) {
  for (int j = 0; j < data[i].Length; ++j) {
    means[i] += data[i][j];
    gMean += data[i][j];
  }
  means[i] /= n[i];
}
gMean /= N;

A próxima subetapa é calcular a “soma dos quadrados entre os grupos” (SSb) e o “quadrado médio entre os grupos” (MSb). SSb é a soma ponderada das diferenças quadradas entre a média de cada grupo e a média geral. MSb = SSb / (K-1), onde K é o número de grupos. Para os dados de demonstração:

SSb = (4 * (4.50 - 8.60)^2) +
 (6 * (9.67 - 8.60)^2) +
 (5 * (10.60 - 8.60)^2) = 94.07
MSb = 94.07 / (3-1) = 47.03

O código que calcula SSb e MSb é:

double SSb = 0.0;
for (int i = 0; i < K; ++i)
  SSb += n[i] * (means[i] - gMean) * (means[i] - gMean);
double MSb = SSb / (K - 1);

A próxima subetapa é calcular a “soma dos quadrados dentro dos grupos” (SSw) e o “quadrado médio dentro dos grupos” (MSw). SSw é a soma das diferenças quadradas entre cada valor de exemplo e a média do seu grupo. MSw = SSw / (N-K). Para os dados de demonstração:

SSw = (3.0 - 4.50)^2 + . . + (8.0 - 9.67)^2 +
 . . + (12.0 - 10.60)^2 = 35.53
MSw = 35.53 / (15-3) = 2.96

O código que calcula SSw e MSw é:

double SSw = 0.0;
for (int i = 0; i < K; ++i)
  for (int j = 0; j < data[i].Length; ++j)
    SSw += (data[i][j] - means[i]) * (data[i][j] - means[i]);
double MSw = SSw / (N - K);

A subetapa final é calcular os dois valores df e a estatística F. Os dois valores df são K - 1 e N - K. E F = MSb / MSw. Para os dados de demonstração:

df = (K-1, N-K) = (3-1, 15-3) = (2, 12)
F = 47.03 / 2.96 = 15.88.

O código de demonstração que calcula df e F é:

...
  df = new int[2];
  df[0] = K - 1;
  df[1] = N - K;
  double F = MSb / MSw;
  return F;
} // Fstat

Acho que você concordará que calcular uma estatística F e os valores df de um conjunto de dados é mecânico e relativamente fácil se você conhecer as equações matemáticas.

Calculando o valor p

Converter uma estatística F e valores df em um valor p que diz a você a probabilidade de que todas as médias da população sejam iguais com base nos dados de exemplo que produzem F e df é simples, na teoria, mas extremamente difícil na prática. Vou explicar isso o mais rapidamente possível, deixando de lado uma enorme quantidade de detalhes que exigiriam uma imensa quantidade de explicações adicionais. Dê uma olhada no gráfico na Figura 3.

Calculando o Valor p de uma Estatística F e Valores df
Figura 3 Calculando o Valor p de uma Estatística F e Valores df

Cada par possível de valores df determina um gráfico chamado de distribuição F. A forma de uma distribuição F pode variar amplamente com base nos valores de df. O gráfico da Figura 3 mostra uma distribuição F para df = (4, 12). Eu usei df = (4, 12) em vez de df = (2, 12) a partir dos dados de demonstração porque a forma da distribuição F do df = (2, 12) é muito atípica.

A área total em qualquer distribuição F é exatamente 1,0. Se você souber o valor da estatística F, o valor p será a área sob a distribuição F de F até o infinito positivo. De certa forma confusa, a área sob a distribuição F de zero até a estatística F frequentemente é chamada de PF, e a área sob a distribuição F da estatística F até o infinito positivo (representando o valor p) é frequentemente chamada de QF. Como a área total sob a distribuição é 1, PF + QF = 1. Acontece que é um pouco mais fácil calcular PF do que QF, então, para encontrar o valor p (QF), normalmente você calcula PF e depois extrai o substrato de 1 para obter QF.

Calcular PF é extremamente difícil, mas, por sorte, as mágicas equações de estimativa são conhecidas há décadas. Essas equações matemáticas e centenas de outras podem ser encontradas em uma referência famosa, o “Handbook of Mathematical Functions” (Manual de Funções Matemáticas) de M. Abramowitz e I.A. Stegun. O livro frequentemente é chamado apenas de “A e S” entre os programadores científicos. Cada equação de A e S tem um número de identificação.

Na demonstração, o método PF é realmente apenas um wrapper ao redor do método BetaInc:

static double PF(double a, double b, double x)
{
  double z = (a * x) / (a * x + b);
  return BetaInc(a / 2, b / 2, z);
}

O nome do método BetaInc significa “Beta incompleto”. O método BetaInc utiliza as equações A e S 6.6.2 e 26.5.8. Essas equações chamam uma função LogGamma e uma função BetaIncCf. A função LogGamma é extremamente difícil de explicar e implementar. Resumidamente, a função matemática Gama estende a noção de fatorial a números de valores reais. Assim como os fatoriais, os valores da função Gama podem se tornar astronomicamente grandes, portanto, para manipulá-los, é comum computar o log de Gama para manter os valores menores.

Calcular o LogGamma é muito complicado e há diversos algoritmos que você pode usar. O programa de demonstração usa um algoritmo chamado a aproximação de Lanczos com (g=5, n=7). A referência A e S possui diferentes algoritmos que podem calcular o LogGamma, mas a aproximação de Lanczos, que não era conhecida quando o A e S foi publicado, oferece resultados mais precisos.

O nome do método BetaIncCf significa “Beta incompleto computado por fração continuada”. O programa de demonstração usa uma equação A e S 26.5.8 para o método BetaIncCf.

Conclusão

Um teste ANOVA faz três suposições matemáticas: que os itens de dados do grupo são matematicamente independentes, que os conjuntos de dados da população são distribuídos normalmente (como na distribuição Gaussian) e que os conjuntos de dados da população têm variâncias iguais.

Você pode testar essas suposições de diversas maneiras, mas interpretar seus resultados é desafiador. O problema é que é altamente improvável que dados do mundo real sejam exatamente normais e tenham variâncias exatamente iguais, embora a ANOVA ainda funcione quando os dados, de alguma forma, não são normais ou têm variâncias desiguais. A conclusão é que é extremamente difícil provar as suposições da ANOVA, então você deve ser bastante cauteloso ao interpretar os resultados.

A ANOVA é intimamente ligada ao teste t. O teste t determina se a média populacional de exatamente dois grupos é igual, em situações em que você tem apenas dados de exemplo. Então, se você tiver três grupos, como na demonstração, em vez de usar a ANOVA, pode, de forma plausível, realizar três testes t, comparando os grupos 1 e 2, os grupos 1 e 3 e os grupos 2 e 3. Entretanto, essa abordagem não é recomendada porque introduz o que é chamado de erro tipo 1 (um falso positivo).

O tipo de ANOVA explicado neste artigo é chamado de ANOVA de uma via (ou de um fator). Uma técnica diferente, chamada de ANOVA de duas vias, é usada quando há dois fatores.

A ANOVA é baseada no valor calculado de uma estatística F de um conjunto de dados. Há outros testes de estatística que usam a estatística F. Por exemplo, você pode usar uma estatística F para inferir se as variâncias de dois grupos de dados são as mesmas.


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 aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Chris Lee e Kirk Olynk