Ищем алгоритм гистограммы Биннинга для десятичных данных - PullRequest
9 голосов
/ 05 марта 2010

Мне нужно сгенерировать ячейки для целей расчета гистограммы. Язык C #. По сути, мне нужно взять массив десятичных чисел и сгенерировать гистограмму из них.

Не удалось найти приличную библиотеку, чтобы сделать это сразу, поэтому сейчас я просто ищу либо библиотеку, либо алгоритм, который бы помог мне выполнить сбор данных.

Итак ...

  • Существуют ли какие-либо библиотеки C #, которые будут принимать массив десятичных данных и выводить гистограмму в двоичном виде?
  • Существует ли общий алгоритм построения бинов, который будет использоваться при создании гистограммы?

Ответы [ 2 ]

15 голосов
/ 05 марта 2010

Вот простая функция, которую я использую. К сожалению, обобщение .NET не поддерживает числовые противоречия типа, поэтому вам придется реализовать другую версию следующей функции для десятичной, целой, двойной и т. Д.

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;
}
6 голосов
/ 28 мая 2014

Я получил странные результаты, используя принятый ответ @JakePearson.Это связано с крайним регистром.

Вот код, который я использовал для проверки его метода.Я немного изменил метод расширения, возвращая int[] и принимая double вместо 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;
    }
}

Все работает хорошо при использовании 10 000 000 случайных двойных значений от 0 до 100 (не включая),Каждый сегмент имеет примерно одинаковое количество значений, что имеет смысл, учитывая, что Random возвращает нормальное распределение.

Good Result

Но когда я изменил строку генерации значений с

double value = rand.NextDouble() * (maxValue+1);              

до

double value = rand.Next(0, maxValue + 1);

, и вы получите следующий результат, который дважды учитывает последнее ведро.

Odd Result

Похоже, что когдазначение совпадает с одной из границ сегмента, код при его написании помещает значение в неправильный интервал.Этот артефакт, кажется, не возникает со случайными значениями double, так как вероятность того, что случайное число будет равно границе сегмента, редка и не будет очевидной.

Способ, которым я исправил этоопределить, какая сторона границы сегмента является включающей и исключающей.

Подумайте о

0< x <=1 1< x <=2 ... 99< x <=100

против

0<= x <1 1<= x <2 ... 99<= x <100

Вы не можете иметь обе границы включительно, поскольку метод не будет знать, в какое ведро его поместить, если у вас есть точное значениеравен границе.

    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;
    }

Единственная проблема теперь заключается в том, что если во входном наборе данных много значений min и max, метод binning исключит многие из этих значений, а полученный график будет искажать набор данных.

...