Buscando un algoritmo de agrupación de datos de histograma decimales
-
24-09-2019 - |
Pregunta
necesito para generar contenedores para los efectos del cálculo de un histograma. El lenguaje es C #. Básicamente necesito tomar en una matriz de números decimales y generar un histograma de esos.
no han sido capaces de encontrar una biblioteca decente de hacer esto de plano por lo que ahora sólo estoy buscando, ya sea para una biblioteca o un algoritmo para ayudar a hacer el binning de los datos.
Así que ...
- ¿Hay alguna C # bibliotecas por ahí que se llevará en una matriz de datos decimales y la salida de un histograma desechado?
- ¿Hay algoritmo genérico para la construcción de los contenedores para ser utilizado en un histograma generado?
Solución
Esta es una función simple balde que uso. Lamentablemente, .NET genéricos no soporta un tipo contraint numérica por lo que tendrá que poner en práctica una versión diferente de la siguiente función para decimal, int, double, etc.
public static List<int> Bucketize(this IEnumerable<decimal> source, int totalBuckets)
{
var min = source.Min();
var max = source.Max();
var buckets = new List<int>();
var bucketSize = (max - min) / totalBuckets;
foreach (var value in source)
{
int bucketIndex = 0;
if (bucketSize > 0.0)
{
bucketIndex = (int)((value - min) / bucketSize);
if (bucketIndex == totalBuckets)
{
bucketIndex--;
}
}
buckets[bucketIndex]++;
}
return buckets;
}
Otros consejos
Me consiguió resultados extraños utilizando @JakePearson aceptadas respuesta. Tiene que ver con un caso extremo.
Aquí está el código que utiliza para probar su método. He cambiado el método de extensión muy ligeramente, volviendo un int[]
y aceptar double
en lugar de decimal
.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Random rand = new Random(1325165);
int maxValue = 100;
int numberOfBuckets = 100;
List<double> values = new List<double>();
for (int i = 0; i < 10000000; i++)
{
double value = rand.NextDouble() * (maxValue+1);
values.Add(value);
}
int[] bins = values.Bucketize(numberOfBuckets);
PointPairList points = new PointPairList();
for (int i = 0; i < numberOfBuckets; i++)
{
points.Add(i, bins[i]);
}
zedGraphControl1.GraphPane.AddBar("Random Points", points,Color.Black);
zedGraphControl1.GraphPane.YAxis.Title.Text = "Count";
zedGraphControl1.GraphPane.XAxis.Title.Text = "Value";
zedGraphControl1.AxisChange();
zedGraphControl1.Refresh();
}
}
public static class Extension
{
public static int[] Bucketize(this IEnumerable<double> source, int totalBuckets)
{
var min = source.Min();
var max = source.Max();
var buckets = new int[totalBuckets];
var bucketSize = (max - min) / totalBuckets;
foreach (var value in source)
{
int bucketIndex = 0;
if (bucketSize > 0.0)
{
bucketIndex = (int)((value - min) / bucketSize);
if (bucketIndex == totalBuckets)
{
bucketIndex--;
}
}
buckets[bucketIndex]++;
}
return buckets;
}
}
Todo funciona bien cuando el uso de 10.000.000 de valores dobles aleatorios entre 0 y 100 (exclusivo). Cada cubo tiene aproximadamente el mismo número de valores, que tiene sentido dado que Random
devuelve una distribución normal.
Pero cuando cambié la línea de generación de valor a partir de
double value = rand.NextDouble() * (maxValue+1);
a
double value = rand.Next(0, maxValue + 1);
y se obtiene el resultado siguiente, que los recuentos dobles el último cubo.
Parece que cuando un valor es igual a uno de los límites de un cubo, el código como está escrito pone el valor en el cubo incorrecta. Este artefacto no parece ocurrir con los valores double
aleatorios como la posibilidad de un número aleatorio que es igual a un límite de un cubo es raro y no sería obvia.
La forma corregí esto es definir de qué lado de la frontera cubo es inclusiva exclusiva vs.
Piense
0< x <=1
1< x <=2
... 99< x <=100
vs.
0<= x <1
1<= x <2
... 99<= x <100
No se puede tener ambas fronteras de inclusión, ya que el método no sabría qué cubeta para ponerlo en si tiene un valor que es exactamente igual a un límite.
public enum BucketizeDirectionEnum
{
LowerBoundInclusive,
UpperBoundInclusive
}
public static int[] Bucketize(this IList<double> source, int totalBuckets, BucketizeDirectionEnum inclusivity = BucketizeDirectionEnum.UpperBoundInclusive)
{
var min = source.Min();
var max = source.Max();
var buckets = new int[totalBuckets];
var bucketSize = (max - min) / totalBuckets;
if (inclusivity == BucketizeDirectionEnum.LowerBoundInclusive)
{
foreach (var value in source)
{
int bucketIndex = (int)((value - min) / bucketSize);
if (bucketIndex == totalBuckets)
continue;
buckets[bucketIndex]++;
}
}
else
{
foreach (var value in source)
{
int bucketIndex = (int)Math.Ceiling((value - min) / bucketSize) - 1;
if (bucketIndex < 0)
continue;
buckets[bucketIndex]++;
}
}
return buckets;
}
El único problema ahora es si el conjunto de datos de entrada tiene una gran cantidad de valores mínimo y máximo, el método de intervalos excluirá muchos de esos valores y el gráfico resultante será falsificar el conjunto de datos.