Julho de 2015

Número 7 do Volume 30

Linguagem de Programação R: Introdução ao R para Programadores de C#

Por James McCaffrey

A linguagem R é muito usada por programadores e cientistas de dados para computar estatísticas. Em parte devido a quantidade cada vez maior de dados coletados por sistemas de software, e pela necessidade de analisar esses dados, o R é uma das tecnologias com crescimento mais rápido entre meus colegas que usam C#. A familiaridade com o R certamente será uma novidade valiosa em seu conjunto de habilidades técnicas.

A linguagem R é um projeto GNU, e é software livre. O R derivou de uma linguagem chamada S (de "statistics"), criada na Bell Laboratories nos anos 70. Há vários tutoriais online excelentes para o R, mas a maioria desses tutoriais pressupõe que você seja um estudante universitário de estatísticas. Este artigo destina-se a ajudar programadores de C# a compreender o R o mair rápido possível.

A melhor maneira de demonstrar o rumo que este artigo tomará é examinar a sessão de exemplo de R na Figura 1. A sessão de exemplo tem dois tópicos independentes. O primeiro conjunto de comandos demostram o chamado teste qui-quadrado para uma distribuição uniforme. O segundo conjunto de comandos demonstra um exemplo de regressão linear, que, na minha opinião, é a técnica de Hello World da computação de estatísticas.

Exemplo de uma sessão em R
Figura 1: exemplo de uma sessão em R

O site da Web de R é o r-project.org. O site contém links para vários sites espelhados, dos quais você pode baixar e instalar o R, através de um executável auto-extraível simples. O R é oficialmente suportado no Windows XP e posteriores, e também nas plataformas não Windows mais comuns. Eu já instalei o R em computadores com Windows 7 e Windows 8 sem problemas. Por padrão, o processo de instalação fornece versões de 32 bits e 64 bits.

Este artigo pressupõe que você tenha pelo menos uma habilidade intermediária de programação em C# (para que você possa entender as explicações sobre as semelhanças e diferenças entre o C# e o R), mas não pressupõe que tenha qualquer conhecimento sobre o R. Vou apresentar um programa de demonstração em C# em sua totalidade, que também está disponível no arquivo de download que acompanha este artigo.

O teste de qui-quadrado usando o R

Ao analisar a Figura 1, a primeira coisa que se nota é que o uso de R é muito diferente do uso de C#. Embora seja possível escrever scripts de R, o R é geralmente usado no modo interativo em um shell de comando. O primeiro exemplo de R é uma análise para ver se um dado normal de seis lados é honesto ou não. Ao ser lançado muitas vezes, espera-se que um dado honesto retorne aproximadamente a mesma contagem para cada um dos seis resultados possíveis.

O prompt de R é indicado pelo token (>) no shell. A primeira instrução digitada na Figura 1 inicia com o caractere (#), que é o token de R para indicar um comentário.

O primeiro comando real na sessão de exemplo é:

> observed <- c(20, 28, 12, 32, 22, 36)

Isso cria um vetor chamado observado usando a função c (de concatenar). Um vetor contém objetos com o mesmo tipo de dados. Uma instrução levemente equivalente em C# seria:

var observed = new int[] { 20, 28, 12, 32, 22, 36 };

O primeiro valor, 20, é o número de ocorrências com o resultado um, 28 é o número de ocorrências com o resultado dois, e assim por diante. A soma das seis contagens é 20 + 28 + . . + 36 = 150. Assim, espera-se que um dado honesto tenha 150/6 = 25 contagens de cada resultado possível. Mas a contagem observada para o resultado três (12) parece baixa demais.

Após criar o vetor, um teste de qui-quadrado é executado usando a função chisq.test:

> chisq.test(observed)

Em R, o caractere de ponto (.) geralmente é usado em vez do caractere de sublinhado (_) para facilitar a leitura dos nomes de variáveis e de funções. O resultado da chamada da função chisq.test é uma boa quantidade de texto:

Teste qui-quadrado para probabilidades determinadas

data:  observed
X-squared = 15.28, df = 5, p-value = 0.009231

Em termos de C#, a maior parte das funções de R retornam uma estrutura de dados que pode ser ignorada, mas também contêm muitas instruções equivalentes de Console.WriteLine que expõem a saída. Observe que cabe a você decifrar o significado da saída de R. Ainda neste artigo, mostrarei como criar o teste qui-quadrado equivalente usando código C# bruto (sem bibliotecas).

Neste exemplo, o valor "X-quadrado" de 15,28 é a estatística calculada de qui-quadrado (a letra grega qui lembra o X maiúsculo). O valor 0,0 indica que os valores observados são exatamente os que se esperam de um dado honesto. Valores maiores de qui-quadrado indicam uma probabilidade crescente de que as contagens observadas não poderiam ter ocorrido aleatoriamente se o dado fosse honesto. O valor df de 5 está dentro dos "graus de liberdade," sendo um a menos do que o número de valores observados. O df não é muito importante para esta análise.

O valor p de 0,009231 é a probabilidade de que as contagens observadas poderiam ter ocorrido aleatoriamente se o dado fosse honesto. Como o valor de p é muito pequeno, menos de 1%, você deve concluir que é muito improvável que os valores observados ocorreram aleatoriamente e que, portanto, há evidência estatística que o dado em questão é desonesto.

Análise de regressão linear usando o R

O segundo conjunto de instruções na Figura 1 demonstra um exemplo de regressão linear. A regressão linear é uma técnica estatística usada para descrever a relação entre uma variável numérica (na estatística, chamada de variável dependente) e uma ou mais variáveis explicativas (chamadas de variáveis independentes) que podem ser numéricas ou categóricas. Quando há apenas uma única variável explicativa/de previsão, a técnica é chamada de regressão linear simples. Quando houver duas ou mais variáveis independentes, como no exemplo de demonstração, a técnica é chamada de regressão linear múltipla.

Antes de fazer a análise de regressão linear, criei um arquivo de texto com oito itens delimitados por vírgula denominado DummyData.txt no diretório C:\IntroToR com este conteúdo:

Color,Length,Width,Rate
blue, 5.4, 1.8, 0.9
blue, 4.8, 1.5, 0.7
blue, 4.9, 1.6, 0.8
pink, 5.0, 1.9, 0.4
pink, 5.2, 1.5, 0.3
pink, 4.7, 1.9, 0.4
teal, 3.7, 2.2, 1.4
teal, 4.2, 1.9, 1.2

Esse arquivo representa dados de flores com a cor da flor, o comprimento e a largura da pétala e a taxa de crescimento. A ideia é prever valores da Taxa (Rate) (a última coluna) dos valores de Cor (Color), Comprimento (Length) e Largura (Width). Depois de uma instrução de comentário, os três primeiros comandos de R na análise de regressão linear são:

> setwd("C:\\IntroToR")
> data <- read.table("DummyData.txt",
  header=TRUE, sep=",")
> print(data)

O primeiro comando define o diretório de trabalho para que não precise qualificar totalmente o caminho para o arquivo de dados de origem. Em vez de usar o token (\\), como é comum em C#, eu poderia ter usado o (/) como é comum em plataformas não-Windows.

O segundo comando carrega os dados na memória em um objeto de tabela chamado data. Observe que o R usa parâmetros nomeados. O parâmetro de cabeçalho informa se a primeira linha é de informações de cabeçalho (TRUE ou T de forma abreviada) ou não (FALSE ou F). O R diferencia maiúsculas de minúsculas e você geralmente pode usar o operador (<-) para atribuir valores, ou o operador (=). Essa escolha é uma questão de preferência pessoal. Tipicamente, eu uso (<-) para atribuição de objetos e (=) para atribuição de valores de parâmetro.

O parâmetro sep (separador) indica como os valores em cada linha são separados. Por exemplo, (\t) indica valores delimitados por tabulação, e (" ") indica valores delimitados por espaço.

A função de impressão exibe a tabela de dados na memória. A função de impressão tem muitos parâmetros opcionais. Observe que a saída na Figura 1 exibe índices de item de dados, começando em 1. Para índices de matriz e objeto, o R é uma linguagem de base 1, ao invés de base 0 como a linguagem C#.

A análise de regressão linear é executada com estes dois comandos de R:

> model <- lm(data$Rate ~ (data$Color + data$Length + data$Width))
> summary(model)

Você pode interpretar o primeiro comando como "o resultado da análise da função de ml (modelo linear) em que a variável dependente a ser prevista é a coluna Taxa no objeto de tabela (data$Rate), e as variáveis independentes de previsão são Cor, Comprimento e Largura". O segundo comando significa "exibir apenas os resultados básicos da análise armazenada no objeto chamado modelo".

A função de ml gera uma grande quantidade de informações. Suponha que você queria prever o valor da Taxa quando os valores de entrada são Cor = rosa (pink), Comprimento = 5,0 e Largura = 1,9. (Observe que isso corresponde ao item de dados [4], que tem um valor real de Taxa de 0,4). Para fazer uma previsão, você usaria os valores na coluna Previsão (Estimate):

Coefficients:
               Estimate Std. Error t value Pr(>|t|)
(Intercept)    -0.14758    0.48286  -0.306  0.77986
data$Colorpink -0.49083    0.04507 -10.891  0.00166 **
data$Colorteal  0.35672    0.09990   3.571  0.03754 *
data$Length     0.04159    0.07876   0.528  0.63406
data$Width      0.45200    0.11973   3.775  0.03255 *

Se X representa os valores de variável independente e Y representa a previsão de Taxa, então:

X = (blue = NA, pink = 1, teal = 0, Length = 5.0, Width = 1.9)
Y = -0.14758 + (-0.49083)(1) + (0.35672)(0) + (0.04159)(5.0) + (0.45200)(1.9)
  = -0.14758 + (-0.49083) + (0) + (0.20795) + (0.85880)
  = 0.42834

Observe que a previsão de Taxa de 0,43 é bem próxima à taxa real de 0,40.

Em palavras, para fazer uma previsão usando o modelo, você calcula uma soma linear de produtos dos valores da Previsão multiplicados pelos seus valores correspondentes em X . O valor de interceptação (Intercept) é uma constante não associada a qualquer variável. Quando você tem variáveis explicativas categóricas, um dos valores é descartado; nesse caso, azul (blue).

As informações na parte inferior da tela de saída indicam como as variáveis independentes Cor, Comprimento e Largura, explicam a variável dependente Taxa:

Residual standard error: 0.05179 on 3 degrees of freedom
Multiple R-squared:  0.9927,    Adjusted R-squared:  0.9829
F-statistic: 101.6 on 4 and 3 DF,  p-value: 0.00156

O valor múltiplo de qui-quadrado (0,9927) é a porcentagem de variação na variável dependente explicada pela combinação linear das variáveis independentes. De uma maneira levemente diferente, R-quadrado é um valor entre 0 e 1, em que os valores mais altos significam um modelo de previsão melhor. Aqui, o valor R-quadrado é extremamente alto, indicando que Cor, Comprimento e Largura podem prever a Taxa com bastante precisão. A estatística F, o valor R-quadrado ajustado e o valor de p são outras medidas de ajuste do modelo.

Um dos pontos deste exemplo é que, ao programar usando o R, sem dúvida o maior desafio é compreender as estatísticas por detrás das funções de linguagem. A maior parte das pessoas aprendem o R de maneira incremental, acumulando conhecimentos uma técnica por vez, conforme a necessidade de responder uma questão específica. Uma analogia do C# seria conhecer os vários objetos de coleção no namespace Collections.Generic, como as classes de Tabela de Hash e Fila. A maior parte dos desenvolvedores aprendem uma estrutura de dados por vez, ao invés de tentar memorizar as informações de todas as classes antes de usar qualquer uma em um projeto.

Outro teste qui-quadrado

O tipo de teste qui-quadrado na Figura 1 geralmente é chamado de teste para distribuição uniforme porque testa se todos os dados observados têm contagens iguais; ou seja, se os dados são distribuídos de maneira uniforme. Há vários outros tipos de testes qui-quadrados, incluindo uma chamado de um teste qui-quadrado de independência.

Suponha que você tenha um grupo de 100 pessoas e quiser descobrir se o sexo masculino (male) ou feminino (female) possui independência em relação à filiação ao partido político Democrata (Democrat), Republicano (Republican) ou Outros (Other). Imagine que seus dados estejam em uma matriz de contingência conforme a Figura 2. Parece que a probabilidade de homens serem Republicanos é maior do que a de mulheres.

Figura 2: matriz de contingência

  Dem Rep Outro  
Masculino 15 25 10 50
Feminino 30 15 5 50
  45 40 15 100

Para usar o R para testar se os dois fatores, sexo e filiação, são estatisticamente independentes, primeiro você colocaria os dados em uma matriz numérica com este comando:

> cm <- matrix( c(15,30,25,15,10,5), nrow=2, ncol=3 )

Observe que, em R, os dados de matriz são armazenados por colunas (de cima para baixo, da esquerda para a direita) em vez de linhas (da esquerda para a direita, de cima para baixo), como em C#.

O comando de teste qui-quadrado de R é:

> chisq.test(cm)

O resultado do valor de p do teste é 0,01022, que indica que, no nível de significância de 5 por cento, os dois fatores não são independentes. Em outras palavras, há uma relação estatística entre sexo e filiação.

Observe que no primeiro exemplo de dados de qui-quadrado, o parâmetro de entrada é um vetor, mas no segundo exemplo filiação-sexo, a entrada é uma matriz. A função chisq.test tem um total de sete parâmetros, sendo que um é obrigatório (uma matriz ou um vetor), seguido por seis parâmetros nomeados opcionais.

Como muitos métodos C# nos namespaces do Microsoft .NET Framework, a maior parte das funções de R são extremamente sobrecarregadas. Em C#, a sobrecarga normalmente é implementada usando vários métodos com o mesmo nome, mas com parâmetros diferentes. O uso de genéricos também é uma forma de sobrecarregar. Em R, a sobrecarga é implementada usando uma função simples com vários parâmetros nomeados opcionais.

Gráficos com R

Como um programador de C#, quando quero fazer um gráfico de algum programa de saída de dados, normalmente executo o meu programa, copio os dados de saída, colo com Ctrl + V os dados no bloco de notas para remover caracteres estranhos de controle, copio os dados, colo no Excel e, em então, crio um gráfico usando o Excel. Isso é pouco complicado, mas funciona bem na maioria das situações.

Um dos pontos fortes do sistema R é sua habilidade nativa para gerar gráficos. Vamos examinar um exemplo na Figura 3. Este é um tipo de gráfico que não é possível no Excel sem algum tipo de suplemento.

um gráfico 3D usando R
Figura 3: um gráfico 3D usando R

Além do programa de shell mostrado na Figura 1, R também tem uma interface semi-GUI, RGui.exe, para quando você quiser criar gráficos.

O gráfico na Figura 3 mostra a função z = f(x,y) = x * e^(-(x^2 + y^2)). Os três primeiros comandos R para gerar o gráfico são:

> rm(list=ls())
> x <- seq(-2, 2, length=25)
> y <- seq(-2, 2, length=25)

A função rm exclui um objeto do espaço de trabalho atual na memória. O comando usado é um encanto mágico de R para excluir todos os objetos. O segundo e o terceiro comandos criam vetores de 25 valores, com espaçamento uniforme, de -2 para + 2 inclusivo. Ao invés disso, eu poderia ter usado a função c.

Os próximos dois comandos são:

> f <- function(x,y) { x * exp(-(x^2
  + y^2)) }
> z <- outer(x,y,f)

O primeiro comando mostra como definir uma função em R usando a palavra-chave function. A função R interna com o nome outer cria uma matriz de valores usando vetores de x e y e uma definição de função f. O resultado é uma matriz de 25 x 25 em que o valor de cada célula é o valor da função f que corresponde a x e y.

Os próximos dois comandos são:

> nrz <- nrow(z)
> ncz <- ncol(z)

As funções nrow e ncol retornam o número de linhas ou o número de colunas no seu argumento de matriz. Aqui, os dois valores seriam 25.

O próximo comando de R usa a função colorRampPalette para criar uma paleta de cores gradiente personalizada para pintar o gráfico:

> jet.colors <- colorRampPalette(c("midnightblue", "blue",
+ "cyan", "green", "yellow", "orange", "red", "darkred"))

Ao digitar uma linha longa em R, se você pressionar a tecla <Enter>, o sistema pulará o cursor para a próxima linha e colocará um caractere + como um prompt, para indicar que seu comando ainda não está concluído. Próximo:

> nbcol <- 64
> color <- jet.colors(nbcol)

O resultado desses dois comandos é um vetor chamado cor (color), que contém 64 diferentes valores de cores, de um azul muito escuro, passando por verde e amarelo, até um vermelho muito escuro. Próximo:

> zfacet <- z[-1,-1] + z[-1,-ncz] + z[-nrz,-1] + z[-nrz,-ncz]
> facetcol <- cut(zfacet,nbcol)

Se você examinar atentamente o gráfico na Figura 3, você verá que a superfície é feita de 25 x 25 = 625 pequenos quadrados ou facetas. Os dois comandos anteriores criam uma matriz de 25 x 25, em que o valor de cada célula é uma das 64 cores a ser usada para a faceta de superfície correspondente.

Por fim, o gráfico 3D é exibido usando a função persp (gráfico de perspectiva):

> persp(x,y,z, col=color[facetcol], phi=20, theta=-35,
+ ticktype="detailed", d=5, r=1, shade=0.1, expand=0.7)

A função persp possui muitos parâmetros opcionais nomeados. O parâmetro da coluna é a cor (ou cores) a ser usada. Os parâmetros theta e phi definem o ângulo de exibição (à esquerda e à direita e para cima e para baixo) do gráfico. O parâmetro ticktype controla como os valores nos eixos x, y e z são exibidos. Os parâmetros r e d controlam a distância percebida pelo olho ao gráfico, e o efeito percebido em 3D. O parâmetro denominado shade controla o sombreamento simulado de uma fonte de luz virtual. O parâmetro denominado expand controla a proporção entre a altura e largura do gráfico. A função persp tem muitos outros parâmetros, mas aqueles usados aqui serão suficientes na maior parte das situações.

Este exemplo indica que R tem recursos de gráficos nativos muito poderosos e flexíveis, mas que tendem a um nível relativamente baixo e exigem uma boa quantidade de esforço.

O teste de qui-quadrado em C#

Para compreender as semelhanças e diferenças entre as análises de linguagem R e a linguagem de programação C#, será útil examinamos uma implementação em C# de um teste de qui-quadrado. Além disso, o código C# pode vir a ser um importante acréscimo à sua biblioteca pessoal de códigos. Vejamos o programa de demonstração de C# na Figura 4.

Teste qui-quadrado usando C#
Figura 4: Teste qui-quadrado usando C#

O programa de demonstração é semelhante ao teste qui-quadrado de um dado na linguagem R da Figura 1. Observe que os valores de saída da demonstração em C# são exatamente os mesmos da sessão em R.

Para criar o programa de demonstração, iniciei o Visual Studio e criei um novo projeto de aplicativo no console C# denominado ChiSquare. 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 ChiSquareProgram.cs e permiti que o Visual Studio automaticamente renomeasse a classe Program para ChiSquareProgram.

O programa de demonstração em C# completo está listado na Figura 5. Você observará que o código-fonte é maior do que o esperado. Implementar programação de estatísticas usando C# bruto não é tão difícil assim, mas a tendência é que o código seja bem longo.

Figure 5: programa de demonstração de qui-quadrado em C#

using System;
namespace ChiSquare
{
  class ChiSquareProgram
  {
    static void Main(string[] args)
    {
      try
      {
        Console.WriteLine("\nBegin Chi-square test using C# demo\n");
        Console.WriteLine(
          "Goal is to see if one die from a set of dice is biased or not\n");
        int[] observed = new int[] { 20, 28, 12, 32, 22, 36 };
        Console.WriteLine("\nStarting chi-square test");
        double p = ChiSquareTest(observed);
        Console.WriteLine("\nChi-square test complete");
        double crit = 0.05;
        if (p < crit)
        {
          Console.WriteLine("\nBecause p-value is below critical value of " +
            crit.ToString("F2"));
          Console.WriteLine("the null hypothsis is rejected and we conclude");
          Console.WriteLine("the data is unlikely to have happened by chance.");
        }
        else
        {
          Console.WriteLine("\nBecause p-value is not below critical value of " +
            crit.ToString("F2"));
          Console.WriteLine(
            "the null hypothsis is accepted (not rejected) and we conclude");
          Console.WriteLine("the observed data could have happened by chance.");
        }
        Console.WriteLine("\nEnd\n");
        Console.ReadLine();
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
      }
    } // Main
    static void ShowVector(int[] v)
    {
      for (int i = 0; i < v.Length; ++i)
        Console.Write(v[i] + " ");
      Console.WriteLine("");
    }
    static double ChiSquareTest(int[] observed)
    {
      Console.WriteLine("Observed frequencies are: ");
      ShowVector(observed);
      double x = ChiSquareStatistic(observed);
      Console.WriteLine("\nX-squared = " + x.ToString("F2"));
      int df = observed.Length - 1;
      Console.WriteLine("\ndf = " + df);
      double p = ChiSquareProb(x, df);
      Console.WriteLine("\np-value = " + p.ToString("F6"));
      return p;
    }
    static double ChiSquareStatistic(int[] observed)
    {
      double sumObs = 0.0;
      for (int i = 0; i < observed.Length; ++i)
        sumObs += observed[i];
      double expected = (int)(sumObs / observed.Length);
      double result = 0.0;
      for (int i = 0; i < observed.Length; ++i)
      {
        result += ((observed[i] - expected) *
         (observed[i] - expected)) / expected;
      }
      return result;
    }
    public static double ChiSquareProb(double x, int df)
    {
      // x = a computed chi-square value. df = degrees of freedom.
      // output = prob. the x value occurred by chance.
      // So, for example, if result < 0.05 there is only a 5% chance
      // that the x value occurred by chance and, therefore,
      // we conclude that the actual data which produced x is
      // NOT the same as the expected data.
      // This function can be used to create a ChiSquareTest procedure.
      // ACM Algorithm 299 and update ACM TOMS June 1985.
      // Uses custom Exp() function below.
      if (x <= 0.0 || df < 1)
        throw new Exception("parameter x must be positive " +
        "and parameter df must be 1 or greater in ChiSquaredProb()");
      double a = 0.0; // 299 variable names
      double y = 0.0;
      double s = 0.0;
      double z = 0.0;
      double e = 0.0;
      double c;
      bool even; // Is df even?
      a = 0.5 * x;
      if (df % 2 == 0) even = true; else even = false;
      if (df > 1) y = Exp(-a); // ACM update remark (4)
      if (even == true) s = y; else s = 2.0 * Gauss(-Math.Sqrt(x));
      if (df > 2)
      {
        x = 0.5 * (df - 1.0);
        if (even == true) z = 1.0; else z = 0.5;
        if (a > 40.0) // ACM remark (5)
        {
          if (even == true) e = 0.0;
          else e = 0.5723649429247000870717135;
          c = Math.Log(a); // log base e
          while (z <= x)
          {
            e = Math.Log(z) + e;
            s = s + Exp(c * z - a - e); // ACM update remark (6)
            z = z + 1.0;
          }
          return s;
        } // a > 40.0
        else
        {
          if (even == true) e = 1.0;
          else e = 0.5641895835477562869480795 / Math.Sqrt(a);
          c = 0.0;
          while (z <= x)
          {
            e = e * (a / z); // ACM update remark (7)
            c = c + e;
            z = z + 1.0;
          }
          return c * y + s;
        }
      } // df > 2
      else
      {
        return s;
      }
    } // ChiSquare()
    private static double Exp(double x) // ACM update remark (3)
    {
      if (x < -40.0) // ACM update remark (8)
        return 0.0;
      else
        return Math.Exp(x);
    }
    public static double Gauss(double z)
    {
      // input = z-value (-inf to +inf)
      // output = p under 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;
    } // Gauss()
  } // Program class
} // ns

O método principal consiste principalmente de instruções WriteLine. O código de chamada essencial é:

int[] observed = new int[] { 20, 28, 12, 32, 22, 36 };
double p = ChiSquareTest(observed);
double crit = 0.05;
if (p < crit) {
  // Messages
} else {
  // Messages
}

O teste qui-quadrado em C# aceita uma matriz de valores observados, calcula o valor estatístico de qui-quadrado e o exibe; calcula e exibe os graus de liberdade; e calcula e retorna o valor de p.

O método ChiSquareTest chama três métodos auxiliares:

ShowVector(observed);
double x = ChiSquareStatistic(observed);
int df = observed.Length - 1;
double p = ChiSquareProb(x, df);

O método ShowVector exibe o vetor de entrada, semelhante à abordagem usada pela função de R chisq.test de ecoar valores de parâmetro de entrada. O método ChiSquareStatistic retorna o qui-quadrado calculado ("X-quadrado" na saída R), e o método ChiSquareProb usa o retorno de ChiSquareStatistic para calcular a probabilidade (o "valor de p" na saída R).

O método ChiSquareStatistic é um teste simples para distribuição uniforme. A equação estatística para a estatística qui-quadrada é:

chi-squared = Sum( (observed - expected)^2 / expected )

Portanto, antes de calcular qui-quadrado, você precisa calcular os valores esperados associados aos valores observados. Conforme explicado anteriormente, para fazer isso, você adiciona o valor de contagem da matriz observada (150 na demonstração), e então divide essa soma pelo número de valores na matriz (6 na demonstração), para fornecer o valor esperado (25) para todas as células.

A parte mais difícil de escrever um teste qui-quadrado é o cálculo do valor de p. Se você examinar a definição do método ChiSquareProb na Figura 5, você logo perceberá que são necessários profundos conhecimentos especializados. Além disso, o método chama um método auxiliar denominado Gauss igualmente complexo.

É muito fácil codificar funções numéricas complicadas, porque a maior parte das funções já foram solucionados há décadas. Dois dos recursos que uso com maior frequência são a Association for Computing Machinery (ACM) e o "Handbook of Mathematical Functions with Formulas, Graphs, and Mathematical Tables" (Manual de Funções Matemáticas com Fórmulas, Gráficos e Tabelas Matemáticas) de Abramowitz e Stegun (Dover Publications, 1965), tão famosos que são chamados apenas de "A & S". Ambas estão disponíveis gratuitamente em vários lugares na Web.

Por exemplo, o método ChiSquareProb implementa o algoritmo nº 299 da ACM e o método auxiliar, Gauss, o nº 209. Uma alternativa ao nº 209 da ACM é a equação nº 7.1.26 de A & S, além de uma simples instrução de wrapping.

Muitas das funções dos algoritmos ACM e A & S são implementadas em bibliotecas C# existentes; no entanto, a maior parte dessas bibliotecas são muito grandes. Sempre que possível, prefiro escrever códigos a partir do começo, usando apenas os métodos que preciso e evitando a dependência externa.

Alguns comentários

Este artigo cobre apenas uma pequena fração da linguagem R, o suficiente para que você possa compreendê-lo. A tendência é que programadores de C# entrem em contato com o R de duas maneiras. Primeiro, usando o R para executar suas próprias análises estatísticas. Como mostra este artigo, usar o R é bem fácil, se você possui domínio sobre as estatísticas subjacentes. Em segundo, ao interagir com alguém que usa o R como linguagem primária e, portanto, com a necessidade de compreender o ambiente R e a terminologia.

Existem algumas ferramentas de desenvolvimento de linguagem integrada à R que você pode usar. Um projeto bastante conhecido é o RStudio. Eu geralmente prefiro usando o console nativo de R. Vários produtos da Microsoft trabalham com R. Por exemplo, o serviço de aprendizado de máquina do Microsoft Azure pode usar scripts de linguagem R, e suspeito que suporte para R acabe sendo adicionado ao Visual Studio em algum momento.


Dr. James McCaffrey* trabalha 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: Dan Liebling e Robin Reynolds-Haertle
Robin Reynolds-Haertle escreve documentação para o desenvolvimento de plataforma cruzada com o Visual Studio. Seus interesses atuais são o .NET Core, C#, Swift e R