Быстрый алгоритм вычисления процентилей для устранения выбросов - PullRequest
17 голосов
/ 23 сентября 2010

У меня есть программа, которая должна многократно вычислять приблизительный процентиль (статистику заказа) набора данных, чтобы удалить выбросы перед дальнейшей обработкой.В настоящее время я делаю это, сортируя массив значений и выбирая соответствующий элемент;это выполнимо, но это заметный всплеск в профилях, несмотря на то, что это довольно незначительная часть программы.

Дополнительная информация:

  • Набор данных содержит порядка до100000 чисел с плавающей точкой, и предполагается, что они «разумно» распределены - вряд ли будут дубликаты или огромные скачки плотности вблизи определенных значений;и если по какой-то нечетной причине распределение является нечетным, то нормально, чтобы аппроксимация была менее точной, поскольку данные, возможно, все равно испорчены и дальнейшая обработка сомнительна.Однако данные не обязательно распределены равномерно или нормально;это просто очень маловероятно, чтобы вырождаться.
  • Приблизительное решение было бы хорошо, но мне действительно нужно понять , как аппроксимация вносит ошибку, чтобы убедиться, что это действительно.
  • цель состоит в том, чтобы устранить выбросы, я всегда вычисляю два процентиля над одними и теми же данными: например, один на 95% и один на 5%.
  • Приложение на C # с частями тяжелой работы на C ++;подойдет псевдокод или ранее существовавшая библиотека в любом из них.
  • Совершенно другой способ удаления выбросов тоже подойдет, если это разумно.
  • Обновление: Кажется, я ищу примерный алгоритм выбора .

Хотя все это делается в цикле, данные (немного) различаются каждый разтак что нелегко повторно использовать структуру данных, как это было сделано для этого вопроса .

Реализованное решение

Использование алгоритма выбора из Википедии, предложенного 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;
}

Спасибо, Гроним, за то, что указал мне правильное направление!

Ответы [ 10 ]

8 голосов
/ 23 сентября 2010

Гистограмма от Хенрика будет работать. Вы также можете использовать алгоритм выбора, чтобы эффективно найти k самых больших или самых маленьких элементов в массиве из n элементов в O (n). Чтобы использовать это для 95-го процентиля, установите k = 0,05n и найдите k самых больших элементов.

Справка:

http://en.wikipedia.org/wiki/Selection_algorithm#Selecting_k_smallest_or_largest_elements

6 голосов
/ 23 сентября 2010

Согласно его создателю SoftHeap может использоваться для:

вычисления точных или приблизительных медиан и процентили оптимально .Это также полезно для приблизительной сортировки ...

4 голосов
/ 23 сентября 2010

Я использовал для определения выбросов путем расчета стандартное отклонение . Все, что на расстоянии более чем в 2 (или 3) раза от стандартного отклонения от среднего значения, является выбросом. 2 раза = около 95%.

Поскольку вы рассчитываете среднее значение, также очень легко рассчитать стандартное отклонение очень быстро.

Вы также можете использовать только часть ваших данных для вычисления чисел.

4 голосов
/ 23 сентября 2010

Вы можете оценить свои процентили только из части вашего набора данных, как первые несколько тысяч точек.

Теорема Гливенко-Кантелли гарантирует, что это будет довольно хорошей оценкой, если вы предположите, что ваши точки данных независимы.

3 голосов
/ 23 сентября 2010

Разделите интервал между минимумом и максимумом ваших данных на (скажем) 1000 бинов и рассчитайте гистограмму. Затем создайте частичные суммы и посмотрите, где они впервые превысят 5000 или 95000.

1 голос
/ 23 сентября 2010

Вы можете отфильтровать 2 или 3 стандартных отклонения, даже если данные не распределены нормально;по крайней мере, это будет сделано согласованным образом, что должно быть важно.

Когда вы удаляете выбросы, std dev будет меняться, вы можете сделать это в цикле, пока изменение в std dev не станет минимальным,Хотите ли вы сделать это, зависит от того, почему вы манипулируете данными таким образом.Есть некоторые оговорки некоторых статистиков в отношении удаления выбросов.Но некоторые убирают выбросы, чтобы доказать, что данные достаточно нормально распределены.

1 голос
/ 23 сентября 2010

Хороший общий ответ на вашу проблему: RANSAC . Учитывая модель и некоторые зашумленные данные, алгоритм эффективно восстанавливает параметры модели.
Вам нужно будет выбрать простую модель, которая может отображать ваши данные. Все гладкое должно быть хорошо. Пусть скажут смесь нескольких гауссов. RANSAC установит параметры вашей модели и оценит набор вкладышей одновременно. Затем выбросьте все, что не подходит к модели.

1 голос
/ 23 сентября 2010

Есть пара основных подходов, которые я могу придумать. Сначала необходимо вычислить диапазон (путем нахождения наибольшего и наименьшего значений), спроецировать каждый элемент на процентиль ((x - min) / диапазон) и отбросить любой, который оценивается ниже 0,05 или выше 0,95.

Второе - вычислить среднее и стандартное отклонение. Интервал в 2 стандартных отклонения от среднего (в обоих направлениях) будет охватывать 95% нормально распределенного пространства выборки, что означает, что ваши выбросы будут в <2,5 и> 97,5 процентилей. Вычисление среднего значения ряда является линейным, как и стандартное значение dev (квадратный корень из суммы разности каждого элемента и среднего значения). Затем вычтите 2 сигмы из среднего значения и добавьте 2 сигмы к среднему значению, и вы получите свои пределы выбросов.

Оба из них будут вычисляться примерно за линейное время; первый требует два прохода, второй - три (когда у вас есть ограничения, вам все равно придется отбрасывать выбросы). Поскольку это операция на основе списка, я не думаю, что вы найдете что-либо с логарифмической или постоянной сложностью; любое дальнейшее повышение производительности потребует либо оптимизации итерации и расчета, либо внесения ошибки путем выполнения вычислений на подвыборке (например, на каждом третьем элементе).

0 голосов
/ 23 сентября 2010

Один набор данных из 100 тыс. Элементов почти не тратит время на сортировку, поэтому я предполагаю, что вам придется делать это неоднократно.Если набор данных такой же, только слегка обновленный, лучше всего построить дерево (O(N log N)), а затем удалять и добавлять новые точки по мере их поступления (O(K log N), где K - количество измененных точек).В противном случае k по величине из уже упомянутых решений дает вам O(N) для каждого набора данных.

0 голосов
/ 23 сентября 2010

Не эксперт, но моя память подсказывает:

  • чтобы точно определить процентильные точки, вам нужно отсортировать и сосчитать
  • взятие выборки из данных и вычисление значений процентиля звучит как хороший план для достойного приближения, если вы можете получить хорошую выборку
  • если нет, то, как предлагает Хенрик, вы можете избежать полной сортировки, если вы сделаете ведра и посчитаете их
...