Подтвердить что ты не робот

Быстрый алгоритм вычисления процентилей для удаления выбросов

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

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

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

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

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

Используя алгоритм выбора википедии, предложенный Гронимом, сократил эту часть времени выполнения примерно на 20 раз.

Поскольку я не мог найти реализацию С#, вот что я придумал. Это быстрее даже для небольших входов, чем 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 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;
}

Performance Graph

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

4b9b3361

Ответ 1

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

Ссылка:

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

Я использовал идентификаторы outliers, вычисляя стандартное отклонение . Все с расстоянием больше, чем на 2 (или 3) раза, стандартное отклонение от avarage - это выброс. 2 раза = около 95%.

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

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

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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