11

ヒストグラムを計算するためにビンを生成する必要があります。言語はC#です。基本的に、10進数の配列を取り込んで、それらからヒストグラムプロットを生成する必要があります。

これを完全に行うための適切なライブラリを見つけることができなかったので、今はデータのビニングを行うのに役立つライブラリまたはアルゴリズムのいずれかを探しています。

それで...

  • 10進データの配列を取り込んでビン化されたヒストグラムを出力するC#ライブラリはありますか?
  • 生成されたヒストグラムで使用されるビンを構築するための一般的なアルゴリズムはありますか?
4

2 に答える 2

16

これが私が使っている簡単なバケット関数です。残念ながら、.NETジェネリックスは数値型の制約をサポートしていないため、decimal、int、doubleなどに対して次の関数の異なるバージョンを実装する必要があります。

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;
}
于 2010-03-05T15:53:25.700 に答える
8

@JakePearsonが受け入れた回答を使用して奇妙な結果が得られました。それはエッジケースと関係があります。

これが彼のメソッドをテストするために使用したコードです。拡張メソッドを少し変更し、の代わりにを返し、int[]受け入れます。doubledecimal

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

0から100(排他的)の間の10,000,000のランダムなdouble値を使用すると、すべてがうまく機能します。Random各バケットにはほぼ同じ数の値があります。これは、正規分布を返すことを考えると理にかなっています。

良い結果

しかし、私が値生成ラインをから変更したとき

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

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

次の結果が得られます。これは、最後のバケットを二重にカウントします。

奇妙な結果

値がバケットの境界の1つと同じである場合、記述されたコードはその値を誤ったバケットに配置するようです。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;
    }

現在の唯一の問題は、入力データセットに多くの最小値と最大値がある場合、ビニング方法はそれらの値の多くを除外し、結果のグラフはデータセットを誤って表現することです。

于 2014-05-28T17:46:32.967 に答える