我需要生成 bin 来计算直方图。语言是C#。基本上我需要接受一个十进制数字数组并从中生成一个直方图。
还没有找到一个像样的库来直接完成此操作,所以现在我只是在寻找一个库或算法来帮助我对数据进行分箱。
所以...
这是我使用的一个简单的桶函数。遗憾的是,.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;
}
我使用@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 个随机双精度值时,一切正常。每个桶具有大致相同数量的值,鉴于
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;
}
现在唯一的问题是,如果输入数据集有很多最小值和最大值,分箱方法将排除其中许多值,并且生成的图表将歪曲数据集。
我能够让它发挥作用。我的水桶有问题。我创建了一个最大存储桶大小的数组。它的工作原理如下 - 取大于最小存储桶大小的值并且<= max bucket size.
例如,如果您的存储桶范围是 0.98 到 1.00,则您希望值 > 0.98 并且 <= 1.00
顶部数组创建最大桶大小的数组。然后你只需迭代和比较。就我而言,只有 15 个桶。在现代机器上,额外的开销是微不足道的。
For i As Double = minValue To maxValue Step binSize
maxbinValues(n) = i
n = n + 1
newBuckets(n) = 0
Next
For Each value In source
If value < minValue Then Continue For
If value > maxValue Then Continue For
Dim foundbucketindex As Integer = 0
' new code
For i As Integer = 0 To n
If value <= maxbinValues(i) Then
newBuckets(i) = newBuckets(i) + 1
foundbucketindex = i
Exit For
End If
Next
Next