Я получил странные результаты, используя принятый ответ @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
возвращает нормальное распределение.
Но когда я изменил строку генерации значений с
double value = rand.NextDouble() * (maxValue+1);
до
double value = rand.Next(0, maxValue + 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;
}
Единственная проблема теперь заключается в том, что если во входном наборе данных много значений min и max, метод binning исключит многие из этих значений, а полученный график будет искажать набор данных.