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?
¿Fue útil?

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.

buen resultado

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.

Odd Resultado

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top