【发布时间】:2011-04-16 08:06:39
【问题描述】:
我有一个程序需要重复计算数据集的近似百分位数(顺序统计),以便在进一步处理之前删除异常值。我目前正在通过对值数组进行排序并选择适当的元素来做到这一点;这是可行的,但它在配置文件中是一个明显的亮点,尽管它只是该程序的一个相当小的部分。
更多信息:
- 该数据集包含多达 100000 个浮点数,并假定“合理”分布 - 特定值附近的密度不太可能重复或出现巨大峰值;如果由于某种奇怪的原因分布是奇怪的,那么近似值不太准确是可以的,因为数据可能无论如何都搞砸了,进一步的处理也很可疑。但是,数据不一定是均匀分布或正态分布的;它不太可能退化。
- 一个近似的解决方案会很好,但我确实需要了解该近似如何引入错误以确保它是有效的。
- 由于目标是消除异常值,我一直在计算相同数据的两个百分位数:例如一个为 95%,一个为 5%。
- 该应用程序使用 C# 语言,在 C++ 中进行了一些繁重的工作;伪代码或预先存在的库都可以。
- 一种完全不同的去除异常值的方法也可以,只要它是合理的。
- 更新:看来我正在寻找一个近似的selection algorithm。
虽然这一切都是在一个循环中完成的,但数据每次都(略有)不同,因此像 for this question 那样重用数据结构并不容易。
实施的解决方案
使用 Gronim 建议的维基百科选择算法将这部分运行时间减少了大约 20 倍。
由于我找不到 C# 实现,这就是我想出的。即使对于小输入,它也比 Array.Sort 更快;在 1000 个元素时,它的速度提高了 25 倍。
public static double QuickSelect(double[] list, int k) {
return QuickSelect(list, k, 0, list.Length);
}
public static double QuickSelect(double[] list, int k, int startI, int endI) {
while (true) {
// Assume startI <= k < endI
int pivotI = (startI + endI) / 2; //arbitrary, but good if sorted
int splitI = partition(list, startI, endI, pivotI);
if (k < splitI)
endI = splitI;
else if (k > splitI)
startI = splitI + 1;
else //if (k == splitI)
return list[k];
}
//when this returns, all elements of list[i] <= list[k] iif i <= k
}
static int partition(double[] list, int startI, int endI, int pivotI) {
double pivotValue = list[pivotI];
list[pivotI] = list[startI];
list[startI] = pivotValue;
int storeI = startI + 1;//no need to store @ pivot item, it's good already.
//Invariant: startI < storeI <= endI
while (storeI < endI && list[storeI] <= pivotValue) ++storeI; //fast if sorted
//now storeI == endI || list[storeI] > pivotValue
//so elem @storeI is either irrelevant or too large.
for (int i = storeI + 1; i < endI; ++i)
if (list[i] <= pivotValue) {
list.swap_elems(i, storeI);
++storeI;
}
int newPivotI = storeI - 1;
list[startI] = list[newPivotI];
list[newPivotI] = pivotValue;
//now [startI, newPivotI] are <= to pivotValue && list[newPivotI] == pivotValue.
return newPivotI;
}
static void swap_elems(this double[] list, int i, int j) {
double tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
感谢 Gronim,为我指明了正确的方向!
【问题讨论】:
-
我知道这是旧的,但我已经实现了这个,但是我如何实际执行它以获得第 5 个和第 95 个百分位数?
-
如果你想要第 5 个百分位,
QuickSelect(values, (int)(values.Length*0.05+0.5))。如果您想要第 95 个百分位数,QuickSelect(values, (int)(values.Length*0.95+0.5))- 请注意,您必须将 0.05 和 0.95 的小数索引四舍五入到整个索引,至少除非您的列表长度是 20 的倍数。如果您的列表很短,您可以考虑插值而不是仅仅选择一个索引,但对于大多数用法我怀疑它是否重要 - 如果你不关心只选择 5%,你可能不在乎它实际上是 4.8% 还是其他 - 无论如何都没有确切的第 5 个百分位数,一般来说。
标签: c# c++ algorithm percentile