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