Compartir a través de



Julio de 2015

Volumen 30, número 7

Lenguaje de programación R: introducción a R para programadores de C#

Por James McCaffrey

Los científicos de datos y programadores usan el lenguaje R para el cálculo estadístico. En parte, debido a una cantidad creciente de datos recopilados por los sistemas de software y la necesidad de analizar datos, R es una de las tecnologías de mayor crecimiento entre mis colegas que usan C#. Estar familiarizado con R puede ser una adición valiosa a su conjunto de habilidades técnicas.

El lenguaje R es un proyecto de GNU y software gratuito. R se derivó de un lenguaje llamado S (por "estadísticas" en inglés), que se creó en Bell Laboratories en la década de los setenta. Existen muchos tutoriales excelentes en línea sobre R, pero en la mayoría de ellos se supone que se trata de un estudiante universitario que estudia estadística. Este artículo está destinado a ayudar a los programadores de C# a ponerse al día con R tan pronto como sea posible.

La mejor forma de ver hacia dónde se dirige este artículo es echar un vistazo a la sesión de ejemplo de R en la figura 1. La sesión de ejemplo tiene dos temas relacionados. El primer conjunto de algunos de los comandos muestran lo que se llama "prueba de chi cuadrado" para la distribución uniforme. El segundo conjunto de comandos muestra un ejemplo de regresión lineal, que, en mi opinión, es la técnica de Hello World del cálculo estadístico.

Una sesión de ejemplo de R
Figura 1 Una sesión de ejemplo de R

El sitio web de R se encuentra en r project.org. El sitio incluye vínculos a varios sitios espejados donde pueden descargar e instalar R. La instalación es un ejecutable autoextraíble simple. R se admite oficialmente en Windows XP y versiones posteriores, y también en las plataformas no Windows más comunes. He instalado R en equipos con Windows 7 y Windows 8 sin ningún problema. De manera predeterminada, el proceso de instalación proporciona versiones de 32 bits y 64 bits.

En este artículo se supone que tienen al menos un nivel intermedio de conocimientos de programación C# (de manera que puedan comprender las explicaciones de las similitudes y diferencias entre C# y R). Sin embargo, no se supone que tiene conocimiento alguno sobre R. Se presenta programa de demostración de C# en su totalidad, que también está disponible en el archivo de descarga que acompaña este artículo.

Prueba de chi cuadrado con R

Si examinan la figura 1, lo primero que deben observar es que el uso de R es un poco diferente del uso de C#. Aunque es posible escribir scripts de R, R suele usarse en modo interactivo en un shell de comandos. El primer ejemplo de R es un análisis para ver si un dado de seis lados normal es justo o no. Cuando se lanzan muchas veces, se espera que un dado justo de aproximadamente el mismo recuento para cada uno de los seis posibles resultados.

El símbolo del sistema de R se indica mediante el token (>) en el shell. La primera instrucción que se escribió en la figura 1 comienza con el carácter (#), que es el token de R para indicar un comentario.

El primer comando real en la sesión de ejemplo es:

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

Esto crea un vector denominado observed con la función c (para concatenar). Un vector contiene objetos con el mismo tipo de datos. Una instrucción de C# casi equivalente sería:

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

El primer valor (20) es el número de veces que se produjo un punto, 28 es el número de veces que se produjo dos puntos y así sucesivamente. La suma de los recuentos de seis puntos es 20 + 28 +. . + 36 = 150. Se podría esperar que un dado justo tenga aproximadamente 150/6 = 25 recuentos de cada resultado posible. Sin embargo, el número de tres puntos observados (12) parece ser sospechosamente bajo.

Después de crear el vector, se realiza una prueba de chi cuadrado mediante la función chisq.test:

> chisq.test(observed)

En R, se suele usar más a menudo el carácter de punto (.) en lugar del carácter de subrayado (_) para crear nombres de variable y función que son más fáciles de leer. El resultado de llamar a la función chisq.test es un fragmento relativamente grande de texto:

Prueba de chi cuadrado para probabilidades dadas

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

Desde el punto de vista de C#, la mayoría de las funciones de R devuelven una estructura de datos que se puede pasar por alto, pero también contiene muchas instrucciones Console.WriteLine equivalentes que exponen la salida. Observen que es su responsabilidad descifrar el significado de la salida de. Más adelante en este artículo, les mostraré cómo crear la prueba de chi cuadrado equivalente mediante código C# sin formato (sin bibliotecas).

En este ejemplo, el valor "X-cuadrado" de 15.28 es la estadística chi cuadrada calculada (la letra griega chi es similar a una X mayúscula). Un valor de 0.0 indica que los valores observados son exactamente lo que cabría esperar si el dado era justo. Valores mayores de chi cuadrado indican una creciente probabilidad de que los recuentos observados no podrían haber ocurrido al azar si el dado fuera justo. El valor df de 5 es "grados de libertad", que es uno menos que el número de valores observados. El df no es demasiado importante para este análisis.

El valor p de 0.009231 es la probabilidad de que los recuentos observados podrían haberse producido al azar si el dado fuera justo. Puesto que el valor de p es tan pequeño, menos del 1 %, se podría concluir que es muy poco probable que los valores observados se habrían producido al azar y, por tanto, hay pruebas estadísticas de que probablemente el dado en cuestión tiene una inclinación.

Análisis de regresión lineal con R

El segundo conjunto de instrucciones de la figura 1 muestra un ejemplo de regresión lineal. La regresión lineal es una técnica estadística que se usa para describir la relación entre una variable numérica (denominada la variable dependiente en la estadística) y una o más variables explicativas (denominadas variables independientes) que pueden ser numéricas o categóricas. Cuando hay una sola variable explicativa/predictora independiente, la técnica se denomina regresión lineal simple. Cuando hay dos o más variables independientes, como en el ejemplo de demostración, la técnica se denomina regresión lineal múltiple.

Antes de hacer el análisis de regresión lineal, he creado un archivo de texto delimitado por comas con ocho elementos denominado DummyData.txt en el directorio C:\IntroToR con este contenido:

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

Se supone que este archivo representa los datos sobre flores con el color de la flor, la longitud y el ancho del pétalo y la velocidad de crecimiento. La idea es predecir valores de velocidad (en la última columna) a partir de los valores de color, longitud y ancho. Después de una instrucción de comentario, los tres primeros comandos R del análisis de regresión lineal son los siguientes:

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

El primer comando establece el directorio de trabajo para no tener que calificar completamente la ruta de acceso al archivo de datos de origen. En lugar de usar el token (\), como ocurre con C#, podría haber usado (/) como es habitual en plataformas que no son de Windows.

El segundo comando carga los datos en la memoria en un objeto de tabla denominado datos. Observen que R usa parámetros con nombre. El parámetro de encabezado indica si la primera línea es la información de encabezado (TRUE o T en forma abreviada) o no (FALSE o F). R distingue mayúsculas de minúsculas y normalmente puede usar el operador (<-) para asignar valores o el operador (=). La opción es principalmente una cuestión de preferencia personal. Por lo general, uso (<-) para la asignación de objetos y (=) para la asignación de valores de parámetro.

El parámetro sep (separador) indica cómo se separan los valores en cada línea. Por ejemplo, (\t) indicaría valores delimitados por tabulaciones y ("") podría indicar valores delimitados por espacios.

La función print muestra la tabla de datos en la memoria. La función print tiene muchos parámetros opcionales. Observen que la salida de la figura 1 muestra los índices de elemento de datos empieza por 1. Para los índices de matriz y objetos, R es un lenguaje basado en 1, en lugar de 0, como el lenguaje C#.

El análisis de regresión lineal se realiza con estos dos comandos R:

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

Se puede interpretar el primer comando como, "almacenar en un objeto denominado model el resultado del análisis de la función lm (modelo lineal) donde la variable dependiente para predecir es la columna Rate en el objeto de tabla (data$Rate) y las variables independientes de predictor son Color, Length y Width". El segundo comando significa, "mostrar solo los resultados básicos del análisis almacenado en el objeto denominado model".

La función lm genera una gran cantidad de información. Supongamos que quieren predecir el valor de velocidad cuando los valores de entrada son Color = pink, Length = 5.0 y Width = 1.9. (Observen que esto corresponde al elemento de datos [4], que tiene un valor de Rate real de 0.4). Para realizar una predicción, se usarían los valores de la columna 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 *

Si X representa los valores de variable independiente e Y representa la velocidad predicha, entonces:

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

Observen que la velocidad predicha, 0.43, es bastante cerca de la velocidad real, 0.40.

En palabras, para hacer una predicción con el modelo, se calcula una suma lineal de los productos de los valores Estimate multiplicados por sus valores X correspondientes. El valor Intercept es una constante que no está asociada a ninguna variable. Si hay variables explicativas categóricas, se quita uno de los valores (en este caso blue).

La información de la parte inferior de la pantalla de salida indica lo bien que las variables independientes, Color, Length y Width, explican la variable dependiente, Rate:

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

El valor de R cuadrado múltiple (0.9927) es el porcentaje de variación en la variable dependiente explicada por la combinación lineal de las variables independientes. En otras palabras, R cuadrado es un valor entre 0 y 1 donde los valores más altos significan un mejor modelo predictivo. Aquí, el valor de R cuadrado es muy alto, lo que indica Color, Length y Width pueden predecir Rate de manera muy precisa. La estadística F, el valor de R cuadrado ajustado y el valor de p son otras medidas de ajuste del modelo.

Uno de los puntos de este ejemplo es que cuando se programan con R, el mayor desafío es, sin duda, comprender las estadísticas de las funciones de lenguaje. La mayoría de la gente aprende R de forma incremental, agregando conocimiento de una técnica a la vez, según sea necesario para responder a algunas preguntas. Una analogía de C# sería aprender sobre los distintos objetos de colección en el espacio de nombres Collections.Generic, tales como las clases Hashtable y Queue. La mayoría de los desarrolladores aprenden sobre una estructura de datos a la vez en lugar de intentar memorizar información sobre todas las clases antes de usarlas en un proyecto.

Otra prueba de chi cuadrado

El tipo de prueba de chi cuadrado de la figura 1 a menudo llama prueba para la distribución uniforme porque comprueba si todos los datos observados tienen recuentos iguales, es decir, si los datos se distribuyen de manera uniforme. Hay varios otros tipos de pruebas de chi cuadrado, incluso uno llamado prueba de chi cuadrado de independencia.

Supongamos que hay un grupo de 100 personas y les interesa saber si el sexo (hombre o mujer) es independiente de la afiliación política (demócrata, republicano, otro). Imaginen que los datos están en una matriz de contingencia, tal como la que se muestra en la figura 2. Parece que quizás los hombres son más propensos a ser republicanos que las mujeres.

Figura 2 Matriz de contingencia

  Dem Rep Otro  
Masculino 15 25 10 50
Femenino 30 15 5 50
  45 40 15 100

Para usar R con el fin de comprobar si los dos factores, el sexo y la afiliación, son estadísticamente independientes, primero se deben colocar los datos en una matriz numérica con este comando:

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

Observen que en R, los datos de la matriz se almacenan por columnas (de arriba a abajo y de izquierda a derecha) en lugar de filas (de izquierda a derecha y de arriba a abajo) al igual que en C#.

El comando de prueba chi cuadrado de R es:

> chisq.test(cm)

El resultado del valor p de la prueba es 0.01022, lo que indica que en el nivel de importancia del 5 %, los dos factores no son independientes. En otras palabras, hay una relación estadística entre sexo y afiliación.

Observen que en el primer ejemplo sobre el chi cuadrado de los dados, el parámetro de entrada es un vector, pero en el segundo ejemplo sobre la afiliación y sexo, la entrada es una matriz. La función chisq.test tiene un total de siete parámetros, uno es obligatorio (un vector o una matriz), seguido de seis parámetros con nombre opcionales.

Como muchos métodos de C# en los espacios de nombres de Microsoft .NET Framework, la mayoría de las funciones de R están muy sobrecargadas. En C#, la sobrecarga se suele implementar mediante varios métodos con el mismo nombre pero con distintos parámetros. El uso de genéricos también es una forma de sobrecarga. En R, la sobrecarga se implementa con una sola función y muchos parámetros con nombre opcionales.

Gráficos con R

Como programador de C#, cuando quiero crear un gráfico de algunos datos de salida del programa, suelo ejecutar mi programa, copiar los datos de salida, usar Ctrl+V para pegar los datos en el Bloc de notas con el fin de quitar los caracteres de control extraños, copiar los datos, pegarlos en Excel y luego crear un gráfico con Excel. Esto es algo engorroso, pero funciona correctamente en la mayoría de las situaciones.

Uno de los puntos fuertes del sistema R es su capacidad propia para generar gráficos. Observemos un ejemplo en la figura 3. Se trata de un tipo de gráfico que no es posible en Excel sin tener que instalar algún tipo de complemento.

Gráfico 3D con R
Figura 3 Gráfico 3D con R

Además del programa de shell que se muestra en la figura 1, R también cuenta con una interfaz semi GUI (RGui.exe) que se puede usar cuando quieran crear gráficos.

En el gráfico de la figura 3 se muestra la función z = f(x,y) = x * e^(-(x^2 + y^2)). Los tres primeros comandos R para generar el gráfico son:

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

La función rm elimina un objeto del área de trabajo actual en la memoria. El comando used es un conjuro mágico de R para eliminar todos los objetos. Los comandos dos y tres crean vectores de 25 valores, espaciados de manera uniforme, de -2 a + 2, incluidos. Podría haber usado la función c en su lugar.

Los dos comandos siguientes son:

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

El primer comando muestra cómo definir una función en R con la palabra clave function. La función integrada de R llamada outer crea una matriz de valores mediante vectores X e Y, así como una definición de la función f. El resultado es una matriz de 25 x 25 donde el valor de cada celda es el valor de la función f que corresponde a X e Y.

Los dos comandos siguientes son:

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

Las funciones nrow y ncol devuelven el número de filas o el número de columnas en su argumento de matriz. En este caso, ambos valores serían 25.

El siguiente comando de R usa la función colorRampPalette para crear una paleta de gradientes de colores personalizada para pintar el gráfico:

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

Al escribir una línea larga en R, si presionan la tecla <Intro>, el sistema bajará el cursor a la línea siguiente y colocará un carácter + como símbolo del sistema para indicar que el comando todavía no está completado. Siguiente:

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

El resultado de estos dos comandos es un vector llamado color que contiene 64 valores de color diferentes que varían de azul muy oscuro, pasando por verde y amarillo, hasta rojo muy oscuro. Siguiente:

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

Si examinan detenidamente el gráfico de la figura 3, verán que la superficie está formada pequeños cuadros, o facetas, de 25 x 25 = 625. Los dos comandos anteriores crean una matriz de 25 x 25 donde el valor de cada celda es uno de los 64 colores que se usarán para la faceta de superficie correspondiente.

Por último, se muestra el gráfico 3D mediante la función 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)

La función persp cuenta con muchos parámetros opcionales con nombre. El parámetro col es el o los colores que se van a usar. Los parámetros theta y phi establecen el ángulo de visión (izquierda y derecha, y arriba y abajo) del gráfico. El parámetro ticktype controla cómo se muestran los valores de los ejes X, Y y Z. Los parámetros d y r controlan la distancia de ojo percibida hasta el gráfico y el efecto 3D percibido. El parámetro shade controla el sombreado simulado desde una fuente de luz virtual. El parámetro expand controla la relación entre el alto y el ancho del gráfico. La función persp tiene muchos más parámetros, pero los que se usan aquí serán suficientes en la mayoría de las situaciones.

En este ejemplo se destaca que R cuenta con eficaces y flexibles funcionalidades nativas de creación de gráficos, pero tienden a ser de bajo nivel y requieren una gran cantidad de esfuerzo.

Prueba de chi cuadrado en C#

Para comprender las similitudes y diferencias entre los análisis de lenguaje R y la programación de lenguaje C#, es útil examinar una implementación de C# de una prueba de chi cuadrado. Además, el código C# puede ser una adición de utilidad a su biblioteca de código personal. Observen el programa de demostración de C# en la figura 4.

Prueba de chi cuadrado con C#
Figura 4 Prueba de chi cuadrado con C#

El programa de demostración se aproxima a la prueba de dados de chi cuadrado en el lenguaje R que se muestra en la figura 1. Observen que los valores de salida de la demostración de C# son exactamente los mismos que en la sesión R.

Para crear el programa de demostración, inicié Visual Studio y creé un nuevo proyecto de aplicación de consola de C# llamado ChiSquare. Después de cargar el código de plantilla en el editor, en la ventana Explorador de soluciones hice clic con el botón derecho en el archivo Program.cs y le cambié el nombre a ChiSquareProgram.cs. Permití que Visual Studio cambie el nombre automáticamente de clase Program a ChiSquareProgram.

El programa de demostración completado de C# se muestra en la figura 5. Observarán que el código fuente es más largo de lo esperado. Implementar la programación estadística con código C# sin formato no es muy difícil, pero el código suele ser bastante largo.

Figura 5 Programa de demostración de chi cuadrado de 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

El método Main consiste principalmente en instrucciones WriteLine. El de código de llamada esencial es el siguiente:

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

La prueba de chi cuadrado de C# acepta una matriz de valores observados, calcula el valor estadístico de chi cuadrado y lo muestra, calcula y muestra los grados de libertad, y calcula y devuelve el valor p.

El método ChiSquareTest llama a tres métodos auxiliares:

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

El método ShowVector muestra el vector de entrada, de forma similar al método que usa la función chisq.test de R de crear un eco de los valores de parámetro de entrada. El método ChiSquareStatistic devuelve el chi cuadrado calculado ("X cuadrado" en la salida de R) y el método ChiSquareProb usa el valor devuelto de ChiSquareStatistic para calcular una probabilidad (el "valor p" en la salida de R).

El método ChiSquareStatistic es una prueba sencilla de la distribución uniforme. La ecuación de estadísticas para la estadística de chi cuadrado es la siguiente:

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

Por lo tanto, antes de calcular el chi cuadrado, es necesario calcular los valores esperados asociados a los valores observados. Como expliqué anteriormente, para ello se agrega el valor de recuento en la matriz observada (150 en la demostración) y se divide esa suma por el número de valores de la matriz (6 en la demostración), para obtener el valor esperado (25) de todas las celdas.

La parte más difícil de escribir una prueba de chi cuadrado es el cálculo del valor p. Si echan un vistazo a la definición del método ChiSquareProb de la figura 5, rápidamente se darán cuenta de que requiere un profundo conocimiento especializado. Además, el método llama a un método auxiliar denominado Gauss que es igual de complejo.

La codificación de funciones numéricas complicadas es bastante sencillo, porque la mayoría de las funciones están resueltas desde hace décadas. Dos de los recursos que uso con más frecuencia son el repositorio Association for Computing Machinery (ACM) Collected Algorithms y "Handbook of Mathematical Functions" de Abramowitz y Stegun (Dover Publications, 1965): tan conocidos que a menudo se llama simplemente "A y S". Ambas referencias están disponibles libremente en varios sitios de Internet.

Por ejemplo, el método ChiSquareProb implementa el algoritmo 299 de ACM y el método auxiliar, Gauss, implementa el algoritmo 209 de ACM. Una alternativa al algoritmo 209 de ACM es la ecuación 7.1.26 de A y S más una sencilla instrucción de ajuste.

Muchas de las funciones de los algoritmos de ACM y A y S se implementan en bibliotecas de C# existentes. Sin embargo, la mayoría de las bibliotecas son bastante grandes. Cuando sea posible, prefiero escribir código desde cero, usando solo los métodos que necesito y evitando dependencias externas.

Algunos comentarios

En este artículo solo se muestra una pequeña fracción del lenguaje R, pero debería ser suficiente como para que puedan empezar a usarlo. Los programadores de C# tienden a entrar en contacto con R de dos maneras. En primer lugar, pueden usar R para realizar sus propios análisis estadísticos. Como se muestra en este artículo, el uso de R es bastante fácil, suponiendo que se comprenden las estadísticas subyacentes. En segundo lugar, es posible que tengan que interactuar con una persona que usa R como lenguaje principal y, por lo tanto, necesitan comprender el entorno y la terminología de R.

Hay algunas herramientas de desarrollo integradas de lenguaje R que pueden usar. Un proyecto conocido se llama RStudio. Yo prefiero usar la consola de R nativa. Hay varios productos de Microsoft que funcionan con R. Por ejemplo, el Servicio de aprendizaje automático de Microsoft Azure puede usar scripts de lenguaje R y sospecho que, en algún momento, se agregará compatibilidad con R a Visual Studio.


Dr. James McCaffrey trabaja para Microsoft Research en Redmond, Washington. Ha colaborado en el desarrollo de varios productos de Microsoft como, por ejemplo, Internet Explorer y Bing. Dr. Puede ponerse en contacto James en jammc@microsoft.com.

Gracias al siguiente experto técnico de Microsoft Research por revisar este artículo: Dan Liebling y Robin Reynolds-Haertle
Robin Reynolds-Haertle escribe documentación para el desarrollo multiplataforma con Visual Studio. Sus intereses actuales son el núcleo. NET, C#, Swift y R