Compartir a través de


Ejecución de pruebas

Agrupación de datos en clústeres mediante la utilidad de la categoría

James McCaffrey

Descargar el ejemplo de código

La agrupación de datos en clústeres es el proceso de disponer elementos de datos dentro de diferentes grupos, clústeres, de forma tal que los elementos de un grupo determinado se parezcan entre sí y sean diferentes de los elementos de otros grupos. La agrupación en clústeres es una técnica de aprendizaje automático que tiene muchos usos prácticos importantes. Por ejemplo, el análisis de clústeres sirve para determinar los tipos de elementos que frecuentemente se compran juntos, para poder realizar una publicidad dirigida a los consumidores, o para determinar qué empresas se parecen, para poder predecir las cotizaciones en las bolsas de valores.

La forma más básica de agrupación en clústeres emplea el algoritmo k-means. (Consulte mi artículo “Detección de datos anormales con la agrupación en clústeres k-means” en msdn.microsoft.com/magazine/jj891054.) Desgraciadamente, la agrupación mediante k-means solo se puede emplear en situaciones donde los elementos de los datos son totalmente numéricos. La agrupación de datos categóricos (por ejemplo, color, que puede adoptar valores como “rojo” y “azul”) es un problema difícil. En este artículo presento un algoritmo de agrupación en clústeres inédito (hasta donde pude determinar) que diseñé para operar con elementos de datos categóricos o numéricos, o con una mezcla de ambos. Llamé a este algoritmo de Agrupación aglutinadora voraz mediante la utilidad de la categoría (Agglomerate Category Utility Clustering, GACUC), para distinguirlo de la gran variedad de algoritmos de agrupación existentes. Este artículo le entregará toda la información necesaria para que pueda llevar a cabo experimentos con la agrupación de datos, agregar funciones de agrupación a una aplicación o sistema, o crear una poderosa herramienta independiente de agrupación de datos en clústeres.

La mejor forma de entender lo que es la agrupación en clústeres y de ver lo que pretendo con este artículo es echar una mirada a la Figura 1. El programa de demostración que aparece en la ilustración agrupa un pequeño conjunto de cinco elementos de datos artificiales. Los elementos de datos a veces se llaman tuplas en la terminología de la agrupación en clústeres. Cada tupla tiene tres atributos categóricos: longitud, color y rigidez. El color puede asumir cualquiera de cuatro posibles valores: Red, Blue, Green o Yellow. La longitud puede ser Short, Medium o Long. La rigidez puede ser False o True.

Clustering Categorical Data in ActionFigura 1 Agrupación de datos categóricos en clústeres en acción

El programa de demostración convierte los datos de cadenas sin formato en números enteros, para realizar un procesamiento más eficiente. Para el color, Red, Blue, Green y Yellow se codifican como 0, 1, 2 y 3, respectivamente. Para la longitud, Short, Medium y Long se codifican como 0, 1 y 2, respectivamente. Para la rigidez, False se codifica como 0 y True se codifica como 1. Por lo tanto, el primer elemento de datos sin procesar “Red Short True” se codifica como “0 0 1”.

En muchos algoritmos de agrupación, GACUC entre ellos, se debe especificar la cantidad de clústeres. En este caso, la cantidad de clústeres se estableció en 2. El programa de demostración luego usa GACUC para encontrar la mejor agrupación de los datos. De puertas para adentro, el algoritmo comienza por colocar las tuplas iniciales 0 y 4 en los clústeres 0 y 1, respectivamente. El algoritmo de agrupación luego recorre las tuplas y asigna cada una al clúster que genera el mejor resultado global. Como no emplea ningún tipo de datos de entrenamiento, la agrupación en clústeres se llama aprendizaje sin supervisión. Después de un paso de agrupación preliminar, el algoritmo GACUC realiza un paso de refinamiento para tratar de mejorar la agrupación. En este ejemplo no se encontró ninguna mejora.

Internamente, el programa de demostración define una agrupación como una matriz de enteros. El índice de la matriz indica el índice de una tupla y el valor de la celda de la matriz indica un clúster. En la Figura 1, la mejor agrupación obtenida por el algoritmo es [0,0,1,1,1], lo que significa que la tupla 0 (“Red Short True”) se encuentra en el clúster 0; la tupla 1 (“Red Long False”) en el clúster 0; la tupla 2 (“Blue Medium True”) en el clúster 1; etcétera. El programa de demostración presenta la mejor agrupación final en formato de cadena para su mejor comprensión y también presenta una métrica llamada utilidad de la categoría (CU) de la mejor agrupación, que en este ejemplo es 0,3733. En seguida veremos que la utilidad de la categoría es la clave del algoritmo de agrupación GACUC.

Este artículo presupone que usted cuenta con conocimientos avanzados de programación en un lenguaje de la familia C, pero no exige que tenga conocimientos sobre la agrupación de datos en clústeres. Codifiqué el programa de demostración en el lenguaje C# sin usar objetos, pero no debería resultarle difícil refactorizar este código de demostración a OOP o a un lenguaje distinto. Para mayor claridad, eliminé todas las comprobaciones de error normales. El programa de demostración es demasiado largo para presentarlo completo en este artículo, así que me concentré en el algoritmo GACUC, para que pueda modificar el código de demostración y adaptarlo según sus propias necesidades. El código fuente completo del programa de demostración está disponible en archive.msdn.microsoft.com/mag201305TestRun.

Utilidad de la categoría

La agrupación de datos en clústeres implica solucionar dos problemas centrales. El primero es definir exactamente qué es una agrupación buena de los datos. El segundo problema es determinar una técnica eficaz de búsqueda en todas las combinaciones posibles de agrupación, para encontrar la mejor. La utilidad de la categoría aborda el primer problema. La CU es una métrica ingeniosa que define la bondad de la agrupación. Valores pequeños de la CU indican una agrupación deficiente y valores grandes indican una agrupación mejor. Hasta donde he podido determinar, M. Gluck y J. Corter definieron la CU por primera vez el año 1985, en un trabajo de investigación llamado “Information, Uncertainty, and the Utility of Categories”.

La ecuación matemática de la CU resulta un poco intimidante a primera vista:

Pero, en verdad, la ecuación es más sencilla de lo que parece. En la ecuación, la C mayúscula es una agrupación global; m minúscula es la cantidad de clústeres; P mayúscula significa “probabilidad de”; A mayúscula significa atributo (p. ej. color); V mayúscula significa valor del atributo (p. ej. rojo). Los que no somos matemáticos, entendemos el cálculo de la utilidad de la categoría mejor con un ejemplo. Supongamos que el conjunto de datos que se agrupará es el que aparece en la Figura 1 y que deseamos calcular la CU de la agrupación:

k = 0
Red      Short    True
Red      Long     False
k = 1
Blue     Medium   True
Green    Medium   True
Green    Medium   False

El primer paso consiste en calcular P(Ck), que son las probabilidades de cada clúster. Para k = 0, debido a que hay cinco tuplas en el conjunto de datos y dos de estas se encuentran en el clúster 0, P(C0) = 2/5 = 0,40. En forma análoga, P(C1) = 3/5 = 0,60.

El segundo paso consiste en calcular la última doble suma de la ecuación, llamada término de probabilidad incondicional. El cálculo es la suma de los N términos, donde N es la cantidad total de valores de atributo diferentes en el conjunto de datos y es:

Red: (2/5)^2 = 0.1600
Blue: (1/5)^2 = 0.0400
Green: (2/5)^2 = 0.1600
Yellow: (0/5)^2 = 0.0000
Short: (1/5)^2 = 0.0400
Medium: (3/5)^2 = 0.3600
Long: (1/5)^2 = 0.0400
False: (2/5)^2 = 0.1600
True: (3/5)^2 = 0.3600
Unconditional sum = 1.3200

El tercer paso consiste en calcular la doble suma central, llamada términos de probabilidad condicional. Hay m sumas (donde m es la cantidad de clústeres), cada uno tiene N términos.

Para k = 0, el cálculo es el siguiente:

Red: (2/2)^2 = 1.0000
Blue: (0/2)^2 = 0.0000
Green: (0/2)^2 = 0.0000
Yellow: (0/2)^2 = 0.0000
Short: (1/2)^2 = 0.2500
Medium: (0/2)^2 = 0.0000
Long: (1/2)^2 = 0.2500
False: (1/2)^2 = 0.2500
True: (1/2)^2 = 0.2500
Conditional k = 0 sum = 2.0000

Para k = 1, el cálculo es:

Red: (0/3)^2 = 0.0000
Blue: (1/3)^2 = 0.1111
Green: (2/3)^2 = 0.4444
Yellow: (0/3)^2 = 0.0000
Short: (0/3)^2 = 0.0000
Medium: (3/3)^2 = 1.0000
Long: (0/3)^2 = 0.0000
False: (1/3)^2 = 0.1111
True: (2/3)^2 = 0.4444
Conditional k = 1 sum = 2.1111

El último paso consiste en combinar las sumas calculadas según la ecuación de la CU:

CU = 1/2 * [ 0.40 * (2.0000 - 1.3200) + 0.60 * (2.1111 - 1.3200) ]
   = 0.3733

La explicación pormenorizada de por qué exactamente la CU mide la bondad de una agrupación es fascinante, pero desgraciadamente no cabe dentro del marco de este artículo. El punto clave es que para cualquier agrupación de un conjunto de datos con datos categóricos, se puede calcular un valor que describe la bondad de esta agrupación.

Búsqueda de la mejor agrupación

Después de definir una forma de medir la bondad de la agrupación, el segundo problema que hay que resolver en cualquier algoritmo de agrupación en clústeres es encontrar una técnica para buscar en todas las agrupaciones posibles la mejor. En general, no se puede examinar cada agrupación posible de un conjunto de datos. Por ejemplo, en el caso de un conjunto de datos con solo 100 tuplas y m = 2 clústeres, existen 2^100 / 2! = 2^99 agrupaciones posibles. Incluso si pudiéramos examinar de alguna forma un billón de agrupaciones por segundo, tardaríamos aproximadamente 19 mil millones de años en revisar cada agrupación posible. (Como referencia, se calcula que la edad del universo tiene aproximadamente 14 mil millones de años.)

Los diferentes algoritmos de agrupación emplean diferentes técnicas para realizar la búsqueda en todas las agrupaciones posibles. El algoritmo GACUC usa lo que se llama un método aglutinador voraz. La idea es comenzar por inicializar cada clúster con una sola tupla y luego, para cada tupla restante determinar qué clúster k’, si la tupla actual se colocara en este, entregaría la agrupación global mejor. Luego, la tupla actual se asigna efectivamente al clúster k’. Esta técnica no garantiza que se encuentre la agrupación óptima, pero diferentes experimentos han demostrado que la técnica generalmente produce una agrupación de calidad aceptable. La agrupación final generada por el algoritmo GACUC depende de las m tuplas que se seleccionan como tuplas iniciales y del orden en el que las tuplas restantes se asignan a los clústeres.

Pero resulta que la selección de una tupla inicial para cada clúster no es una tarea trivial. Una forma ingenua sería seleccionar simplemente tuplas aleatorias como valores de inicialización. Pero si las tuplas iniciales se parecen entre sí, entonces la agrupación resultante será deficiente. Una forma mejor para seleccionar las tuplas iniciales es elegir m tuplas que sean lo más diferentes posibles entre sí. Aquí la utilidad de la categoría nuevamente resulta muy práctica: la CU de cualquier conjunto de posibles tuplas iniciales se puede calcular y se puede usar el conjunto de tuplas con la mejor CU (el valor más grande, es decir, más diferentes) para las tuplas iniciales. Igual que antes, generalmente no es posible examinar todos los conjuntos posibles de tuplas iniciales, así que una forma es seleccionar repetidas veces m tuplas aleatorias, calcular la CU de estas tuplas aleatorias y usar luego el conjunto con la mejor CU como valor de inicialización.

Estructura general del programa

El método Main del programa de demostración que se muestra en la Figura1, donde se eliminaron algunas instrucciones WriteLine y comentarios, se muestra en la Figura 2. Usé Visual Studio 2010 con Microsoft .NET Framework 4, pero el código de demostración no tiene ninguna dependencia significativa y cualquier versión de Visual Studio compatible con .NET Framework 2.0 o posterior debería funcionar perfectamente. Para simplificar las cosas, creé una aplicación de consola única en C# llamada Clustering­Categorical; posiblemente le convenga implementarla como biblioteca de clases.

Figura 2 Estructura general de programa

using System;
using System.Collections.Generic;
namespace ClusteringCategorical
{
  class ClusteringCategoricalProgram
  {
    static Random random = null;
    static void Main(string[] args)
    {
      try
      {
        random = new Random(2);
        Console.WriteLine("\nBegin category utility demo\n");
        string[] attNames = new string[] { "Color", "Length", "Rigid" };
        string[][] attValues = new string[attNames.Length][];
        attValues[0] = new string[] { "Red", "Blue", "Green", "Yellow" };
        attValues[1] = new string[] { "Short", "Medium", "Long" };
        attValues[2] = new string[] { "False", "True" };
        string[][] tuples = new string[5][];
        tuples[0] = new string[] { "Red", "Short", "True" };
        tuples[1] = new string[] { "Red", "Long", "False" };
        tuples[2] = new string[] { "Blue", "Medium", "True" };
        tuples[3] = new string[] { "Green", "Medium", "True" };
        tuples[4] = new string[] { "Green", "Medium", "False" };
        Console.WriteLine("Tuples in raw (string) form:\n");
        DisplayMatrix(tuples);
        int[][] tuplesAsInt = TuplesToInts(tuples, attValues);
        Console.WriteLine("\nTuples in integer form:\n");
        DisplayMatrix(tuplesAsInt);
        int numClusters = 2;
        int numSeedTrials = 10;
        Console.WriteLine("Initializing clustering result array");
        int[] clustering = InitClustering(tuplesAsInt);
        int[][][] valueCounts = InitValueCounts(tuplesAsInt, attValues,
          numClusters);
        int[][] valueSums = InitValueSums(tuplesAsInt, attValues);
        int[] clusterCounts = new int[numClusters];
        Console.WriteLine("\nBeginning clustering routine");
        Cluster(tuplesAsInt, attValues, clustering, valueCounts,
          valueSums, clusterCounts, numSeedTrials);
        double cu = CategoryUtility(valueCounts, valueSums,
          clusterCounts);
        Console.WriteLine("\nCategory Utility of clustering = " +
          cu.ToString("F4"));
        DisplayVector(clustering);
        Refine(20, tuplesAsInt, clustering, valueCounts, valueSums,
          clusterCounts);
        Console.WriteLine("Refining complete");
        DisplayVector(clustering);
        Console.WriteLine("\nFinal clustering in string form:\n");
        DisplayClustering(numClusters, clustering, tuples);
        cu = CategoryUtility(valueCounts, valueSums, clusterCounts);
        Console.WriteLine("\nFinal clustering CU = " + cu.ToString("F4"));
        Console.WriteLine("\nEnd demo\n");
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
    } // Main
    // All other methods here
  } // class Program
} // ns

En la Figura 2, el método Cluster realiza una iteración del algoritmo básico de agrupación GACUC. La variable numSeedTrials se establece en 10 y se pasa a la rutina que determina las tuplas iniciales que se asignan a cada clúster. El método Refine realiza pasos de post-agrupación en los datos, para tratar de encontrar la agrupación que genere la mejor utilidad de la categoría.

Las estructuras de dato claves

Aunque la utilidad de la categoría de un conjunto de datos se puede calcular sobre la marcha al iterar por cada tupla del conjunto de datos, como el método de agrupación debe calcular la utilidad de la categoría muchas veces, conviene mucho más almacenar los valores de atributo de las tuplas que están asignadas a los clústeres en cualquier momento dado. En la Figura 3 se ven algunas estructuras de dato claves empleadas por el algoritmo GACUC.

Key Data Structures
Figura 3 Estructuras de datos claves

La matriz valueCounts contiene la cantidad de valores de atributo por clúster. Por ejemplo, valueCounts[0][2][1] contiene la cantidad de tuplas con el atributo 0 (Color) y valor 2 (Green) que actualmente están asignados al clúster 1. La matriz valueSums contiene la suma de los recuentos, en todos los clústeres, para cada valor de atributo. Por ejemplo, valueSums[0][2] contiene la cantidad total de tuplas con el atributo 0 (Color) y el valor 2 (Green) que están asignadas a todos los clústeres. La matriz clusterCounts contiene la cantidad de tuplas que actualmente están asignadas a cada clúster. Por ejemplo, si numClusters = 2 y clusterCounts = [2,2], entonces hay dos tuplas asignadas al clúster 0 y dos tuplas asignadas al clúster 1. La matriz clustering codifica la asignación de las tuplas a los clústeres. El índice de clustering representa una tupla y el valor de una celda representa un clúster y un valor de -1 indica que la tupla asociada todavía no se asignó a ningún clúster. Por ejemplo, si clustering[2] = 1, entonces la tupla 2 está asignada al clúster 1.

Codificación del método CategoryUtility

El código del método que calcula la utilidad de la categoría no presenta dificultades conceptuales, pero es un poco complicado. La definición del método comienza con:

static double CategoryUtility(int[][][] valueCounts, 
  int[][] valueSums,
  int[] clusterCounts)
{
  int numTuplesAssigned = 0;
  for (int k = 0; k < clusterCounts.Length; ++k)
    numTuplesAssigned += clusterCounts[k];
  int numClusters = clusterCounts.Length;
  double[] clusterProbs = new double[numClusters];   // P(Ck)
  for (int k = 0; k < numClusters; ++k)
    clusterProbs[k] = (clusterCounts[k] * 1.0) / numTuplesAssigned;

Se supone que las tres matrices de entrada valueCounts, valueSums y clusterCounts contienen valores válidos que reflejan la agrupación actual, tal como se describió en la sección anterior y en la Figura 3. El método comienza por recorrer la matriz clusterCounts para calcular la cantidad total de tuplas que actualmente están asignadas a los clústeres. La cantidad de clústeres se deduce de la propiedad Length de la matriz clusterCounts, y las probabilidades de los clústeres luego se calculan y almacenan en la matriz local clusterProbs.

El siguiente paso es calcular la probabilidad única incondicional de la agrupación actual:

double unconditional = 0.0;
for (int i = 0; i < valueSums.Length; ++i)
{
  for (int j = 0; j < valueSums[i].Length; ++j)
  {
    double p = (valueSums[i][j] * 1.0) / numTuplesAssigned;
    unconditional += (p * p);
  }
}

Una vez que se calculó la probabilidad incondicional, el siguiente paso consiste en calcular una probabilidad condicional para cada clúster:

double[] conditionals = new double[numClusters];
for (int k = 0; k < numClusters; ++k)
{
  for (int i = 0; i < valueCounts.Length; ++i)
  {
    for (int j = 0; j < valueCounts[i].Length; ++j)
    {
      double p = (valueCounts[i][j][k] * 1.0) / clusterCounts[k];
      conditionals[k] += (p * p);
    }
  }
}

Ahora que tenemos las probabilidades de cada clúster en la matriz clusterProbs, el término de la probabilidad incondicional en la variable unconditional y los términos de probabilidad condicional en la matriz conditionals, podemos determinar la utilidad de la categoría de la agrupación:

double summation = 0.0;
for (int k = 0; k < numClusters; ++k)
  summation += clusterProbs[k] * 
    (conditionals[k] - unconditional);
return summation / numClusters;
}

Una forma práctica de entender el comportamiento de la función de la CU es experimentar con el código de demostración, al especificar agrupaciones preestablecidas. Por ejemplo, podemos modificar el código de la Figura 2 del siguiente modo:

string[] attNames = new string[] { "Color", "Length", "Rigid" };
// And so on, as in Figure 1
int[][] tuplesAsInt = TuplesToInts(tuples, attValues);
int[] clustering[] = new int[] { 0, 1, 0, 1, 0 };
// Hardcoded clustering
double cu = CategoryUtility(valueCounts, valueSums, clusterCounts);
Console.WriteLine("CU of clustering = " + cu.ToString("F4"));

Implementación del método Cluster

Con un método de utilidad de la categoría a mano, la implementación de un método para agrupar datos es relativamente sencillo. En pseudocódigo de alto nivel, el método Cluster sería:

define an empty clustering with all tuples unassigned
determine m good (dissimilar) seed tuples
assign one good seed tuple to each cluster
loop each unassigned tuple t
  compute the CU for each cluster if t were actually assigned
  determine the cluster that gives the best CU
  assign tuple t to best cluster
end loop (each tuple)
return clustering

Para simplificar las cosas, el método Cluster recorre cada tupla que no está asignada en forma lineal, partiendo por la tupla [0]. Esto, de hecho, asigna una ponderación mayor a las tuplas con índices más bajos. Un método alternativo que uso frecuentemente es recorrer en orden aleatorio u orden revuelto las tuplas que no están asignadas, mediante un generador congruencial lineal de ciclo completo.

Una parte sorprendentemente complicada del algoritmo es encontrar m tuplas iniciales satisfactorias. En pseudocódigo de alto nivel, el método GetGoodIndexes sería:

loop numSeedTrials times
  create a mini-tuples array with length numClusters
  pick numClusters random tuple indexes
  populate the mini-tuples with values from data set
  compute CU of mini data set
  if CU > bestCU
    bestCU = CU
    indexes of best mini data set = indexes of data set
  end if
end loop
return indexes of best mini data set

Este método genera los índices aleatorios de las tuplas mediante un sistema de fuerza bruta que genera la prueba si no está en uso; esto me ha servido perfectamente en la práctica.

Como el método Cluster generalmente devolverá una agrupación buena pero no óptima, el algoritmo GACUC opcionalmente llama un método refine que intenta reorganizar las asignaciones de las tuplas a los clústeres, para tratar de encontrar una agrupación con un valor mejor de la utilidad de la categoría. En pseudocódigo, el método Refine sería:

loop numRefineTrials times
  select a random tuple that is in a cluster with at least two tuples
  determine the random tuple's cluster
  select a second cluster that is different from curr cluster
  unassign tuple from curr cluster
  assign tuple to different cluster
  compute new CU
  if new CU > old CU
    leave the cluster switch alone
  else
    unassign tuple from different cluster
    assign tuple back to original cluster
  end if
end loop

Existen muchas modificaciones de postprocesamiento con las que se pueden experimentar. La modificación que vimos conserva una cantidad fija de clústeres. Una posibilidad es definir un método de modificación que permite que la cantidad de clústeres aumente o disminuya.

Un método auxiliar clave en el algoritmo de agrupación GACUC es el que asigna una tupla a un clúster:

static void Assign(int tupleIndex, int[][] tuplesAsInt, 
  int cluster,  int[] clustering, 
  int[][][] valueCounts, int[][] valueSums,
  int[] clusterCounts)
{
  clustering[tupleIndex] = cluster;  // Assign
  for (int i = 0; i < valueCounts.Length; ++i)  // Update
  {
    int v = tuplesAsInt[tupleIndex][i];
    ++valueCounts[i][v][cluster];
    ++valueSums[i][v];
  }
  ++clusterCounts[cluster];  // Update
}

Observe que el método Assign acepta varios parámetros; esto es una deficiencia general del uso de métodos estáticos en vez de emplear un enfoque OOP. El método Assign modifica la matriz clustering y luego actualiza las matrices que contienen los recuentos de cada valor de atributo por clúster (valueCounts), los recuentos de cada valor de atributo en todos los clústeres (valueSums) y la cantidad de tuplas asignadas a cada clúster (clusterCounts). El método Unassign es esencialmente lo opuesto de Assign. Las tres instrucciones clave del método Unassign son:

clustering[tupleIndex] = -1;  // Unassign
--valueCounts[i][v][cluster]; // Update
--valueSums[i][v];            // Update
--clusterCounts[cluster];     // Update

En resumen

La descarga de código que acompaña este artículo, junto con la explicación del algoritmo de agrupación en clústeres GACUC que se presenta aquí, debería servirle para ponerse en marcha si desea experimentar con la agrupación de datos, agregar funciones de agrupación a una aplicación o crear una herramienta de utilidad para la agrupación en clústeres. A diferencia de muchos algoritmos de agrupación en clústeres (inclusive k-means), el algoritmo GACUC se puede modificar fácilmente para operar con datos numéricos o datos numéricos y categóricos mixtos. La idea es realizar un procesamiento previo de los datos numéricos, al disponerlos dentro de categorías. Por ejemplo, si los datos sin procesar contienen las estaturas de personas en pulgadas, entonces podríamos agrupar la estatura de tal forma que los valores inferiores a 60,0 sean “bajo”, los valores entre 60,0 y 74,0 sean “medio” y los valores mayores que 74,0 sean “alto”.

Existen muchos algoritmos para agrupar datos categóricos en clústeres; entre estos, los algoritmos que usan la utilidad de la categoría para definir la bondad de la agrupación. Pero el algoritmo que se presenta aquí es relativamente sencillo, ha funcionado bien en la práctica, se puede aplicar a datos numéricos y categóricos, y se adapta a la escala de conjuntos de datos enormes. La agrupación de datos en clústeres con GACUC puede ser un nuevo elemento valioso para su conjunto de herramientas del desarrollador.

El Dr. James McCaffrey trabaja en Volt Information Sciences Inc., donde está a cargo del entrenamiento técnico de los ingenieros informáticos que trabajan en el campus de Microsoft en Redmond, Washington. Ha colaborado en el desarrollo de varios productos de Microsoft como, por ejemplo, Internet Explorer y MSN Search. Es el autor de “.NET Test Automation Recipes” (Apress, 2006) y se puede ubicar en la dirección de correo electrónico jammc@microsoft.com.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Dan Liebling (Microsoft Research)