为了计算直方图,我需要生成 bin。语言是 C#。基本上我需要接受一组十进制数字并从中生成直方图。
还没有找到一个像样的库来彻底做到这一点,所以现在我只是在寻找一个库或一种算法来帮助我对数据进行分箱。
所以...
- 是否有任何 C# 库可以接收十进制数据数组并输出分箱直方图?
- 是否有用于构建用于生成直方图的箱的通用算法?
为了计算直方图,我需要生成 bin。语言是 C#。基本上我需要接受一组十进制数字并从中生成直方图。
还没有找到一个像样的库来彻底做到这一点,所以现在我只是在寻找一个库或一种算法来帮助我对数据进行分箱。
所以...
这是我使用的一个简单的桶函数。遗憾的是,.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;
}
我使用@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;
}
现在唯一的问题是,如果输入数据集有很多最小值和最大值,分箱方法将排除其中许多值,并且生成的图将错误地表示数据集。