Как рассчитать среднее значение для набора циклических данных? - PullRequest
132 голосов
/ 29 января 2009

Я хочу рассчитать среднее значение для набора циклических данных. Например, у меня может быть несколько примеров чтения компаса. Проблема, конечно же, заключается в том, как бороться с циклом. Тот же алгоритм может быть полезен для циферблата.

Фактический вопрос более сложен - что означает статистика на сфере или в алгебраическом пространстве, которое «оборачивается», например аддитивная группа мод н. Ответ может быть не уникальным, например среднее значение 359 градусов и 1 градус может составлять 0 градусов или 180, но статистически 0 выглядит лучше.

Это настоящая проблема для меня, и я пытаюсь сделать так, чтобы она не выглядела просто как математическая.

Ответы [ 30 ]

2 голосов
/ 11 января 2015

Вот полное решение C ++:

#include <vector>
#include <cmath>

double dAngleAvg(const vector<double>& angles) {
    auto avgSin = double{ 0.0 };
    auto avgCos = double{ 0.0 };
    static const auto conv      = double{ 0.01745329251994 }; // PI / 180
    static const auto i_conv    = double{ 57.2957795130823 }; // 180 / PI
    for (const auto& theta : angles) {
        avgSin += sin(theta*conv);
        avgCos += cos(theta*conv);
    }
    avgSin /= (double)angles.size();
    avgCos /= (double)angles.size();
    auto ret = double{ 90.0 - atan2(avgCos, avgSin) * i_conv };
    if (ret<0.0) ret += 360.0;
    return fmod(ret, 360.0);
}

Он принимает углы в виде вектора двойных значений и возвращает среднее значение просто как двойной. Углы должны быть в градусах, и, конечно, среднее значение также в градусах.

2 голосов
/ 12 декабря 2017

В питоне, с углами между [-180, 180)

def add_angles(a, b):
  return (a + b + 180) % 360 - 180

def average_angles(a, b):
  return add_angles(a, add_angles(-a, b)/2)

подробности:

Для среднего значения двух углов есть два средних значения на 180 ° друг от друга, но нам может потребоваться более близкое среднее значение.

Визуально, среднее значение синего ( b ) и зеленого ( a ) дает точку замерзания:

Original

Углы «обтекать» (например, 355 + 10 = 5), но стандартная арифметика игнорирует эту точку ветвления. Однако, если угол b противоположен точке ветвления, то ( b + g ) / 2 дает ближайшую среднюю: чирную точку.

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

rotatedreturned

1 голос
/ 10 июля 2011

Нет единого «правильного ответа». Я рекомендую прочитать книгу, К. В. Мардия и П. Е. Джапп, «Направленная статистика» (Wiley, 1999) для тщательного анализа.

1 голос
/ 29 января 2009

Представим эти углы точками на окружности круга.

Можно ли предположить, что все эти точки попадают на одну и ту же половину круга? (В противном случае, нет очевидного способа определить «средний угол». Подумайте о двух точках на диаметре, например, 0 и 180 градусов - это в среднем 90 или 270 градусов? равномерно распределить точки?)

С этим допущением мы выбираем произвольную точку на этом полукруге в качестве «начала координат» и измеряем заданный набор углов относительно этого начала координат (назовем это «относительным углом»). Обратите внимание, что относительный угол имеет абсолютное значение строго менее 180 градусов. Наконец, возьмите среднее значение этих относительных углов, чтобы получить желаемый средний угол (относительно нашего происхождения, конечно).

1 голос
/ 29 января 2009

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

Еще одна идея: найти самый большой зазор между заданными углами. Найдите точку, которая делит ее пополам, а затем выберите противоположную точку на круге в качестве контрольного нуля, чтобы вычислить среднее значение.

1 голос
/ 22 октября 2018

Ну, я очень опоздал на вечеринку, но подумал, что добавлю свои 2 цента, потому что не смог найти окончательного ответа. В конце я реализовал следующую Java-версию метода Mitsuta, которая, я надеюсь, предоставляет простое и надежное решение. В частности, стандартное отклонение обеспечивает как дисперсию измерения, так и, если sd == 90, указывает, что входные углы приводят к неоднозначному среднему значению.

РЕДАКТИРОВАТЬ: На самом деле я понял, что моя первоначальная реализация может быть еще более упрощена, на самом деле, на удивление просто, учитывая все разговоры и тригонометрию, происходящие в других ответах.

/**
 * The Mitsuta method
 *
 * @param angles Angles from 0 - 360
 * @return double array containing
 * 0 - mean
 * 1 - sd: a measure of angular dispersion, in the range [0..360], similar to standard deviation.
 * Note if sd == 90 then the mean can also be its inverse, i.e. 360 == 0, 300 == 60.
 */
public static double[] getAngleStatsMitsuta(double... angles) {
    double sum = 0;
    double sumsq = 0;
    for (double angle : angles) {
        if (angle >= 180) {
            angle -= 360;
        }
        sum += angle;
        sumsq += angle * angle;
    }

    double mean = sum / angles.length;
    return new double[]{mean <= 0 ? 360 + mean: mean, Math.sqrt(sumsq / angles.length - (mean * mean))};
}

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

Arrays.stream(angles).map(angle -> angle<180 ? angle: (angle-360)).sum() / angles.length;
1 голос
/ 26 августа 2012

На английском:

  1. Создайте второй набор данных со всеми углами, смещенными на 180.
  2. Возьмите дисперсию обоих наборов данных.
  3. Возьмите среднее значение для набора данных с наименьшей дисперсией.
  4. Если это среднее значение из смещенного набора, то снова сдвиньте ответ на 180.

В питоне:

Массив #numpy NX1

if np.var(A) < np.var((A-180)%360):
    average = np.average(A)

else:
    average = (np.average((A-180)%360)+180)%360
1 голос
/ 26 апреля 2017

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

Это математически эквивалентно добавлению смещения, которое сдвигает значения в диапазон (0, 180), вычислению среднего значения и затем вычитанию смещения.

Комментарии описывают, какой диапазон может принимать конкретное значение в любой момент времени

// angles have to be in the range [0, 360) and within 180° of each other.
// n >= 1
// returns the circular average of the angles int the range [0, 360).
double meanAngle(double* angles, int n)
{
    double average = angles[0];
    for (int i = 1; i<n; i++)
    {
        // average: (0, 360)
        double diff = angles[i]-average;
        // diff: (-540, 540)

        if (diff < -180)
            diff += 360;
        else if (diff >= 180)
            diff -= 360;
        // diff: (-180, 180)

        average += diff/(i+1);
        // average: (-180, 540)

        if (average < 0)
            average += 360;
        else if (average >= 360)
            average -= 360;
        // average: (0, 360)
    }
    return average;
}
0 голосов
/ 24 февраля 2018

Вот некоторый Java-код для усреднения углов, я думаю, что он достаточно надежный.

public static double getAverageAngle(List<Double> angles)
{
    // r = right (0 to 180 degrees)

    // l = left (180 to 360 degrees)

    double rTotal = 0;
    double lTotal = 0;
    double rCtr = 0;
    double lCtr = 0;

    for (Double angle : angles)
    {
        double norm = normalize(angle);
        if (norm >= 180)
        {
            lTotal += norm;
            lCtr++;
        } else
        {
            rTotal += norm;
            rCtr++;
        }
    }

    double rAvg = rTotal / Math.max(rCtr, 1.0);
    double lAvg = lTotal / Math.max(lCtr, 1.0);

    if (rAvg > lAvg + 180)
    {
        lAvg += 360;
    }
    if (lAvg > rAvg + 180)
    {
        rAvg += 360;
    }

    double rPortion = rAvg * (rCtr / (rCtr + lCtr));
    double lPortion = lAvg * (lCtr / (lCtr + rCtr));
    return normalize(rPortion + lPortion);
}

public static double normalize(double angle)
{
    double result = angle;
    if (angle >= 360)
    {
        result = angle % 360;
    }
    if (angle < 0)
    {
        result = 360 + (angle % 360);
    }
    return result;
}
0 голосов
/ 31 января 2009

Альнитак имеет правильное решение. Решение Ника Фортескью функционально одинаково.

Для особого случая, где

(sum (x_component) = 0.0 && sum (y_component) = 0.0) // например, 2 угла 10 и 190 градусов.

используйте 0,0 градусов в качестве суммы

В вычислительном отношении вы должны протестировать этот случай, так как atan2 (0, 0) не определено и выдаст ошибку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...