平均角度...再次

时间:2009-11-28 19:19:13

标签: algorithm math geometry

我想计算一组角度的平均值,表示源轴承(0到360度) - (类似于风向)

我知道之前已经讨论过(好几次)。接受的答案是从角度计算单位向量并取其平均值的角度

然而,这个答案以非直观的方式定义了平均值。 0,0和90的平均值将是 atan((sin(0)+ sin(0)+ sin(90))/(cos(0)+ cos(0)+ cos(90)))= atan(1/2)= 26.56度

我希望0,0和90的平均值为30度。

所以我认为再问这个问题是公平的:你如何计算平均值,所以这些例子会给出直观的预期答案。

修改2014:

在提出这个问题后,我发布了an article on CodeProject,提供了全面的分析。本文探讨了以下参考问题:

  • 考虑到2000年美国每次出生的时间[00:00-24:00] - 计算平均出生时间
  • 给定从固定发射器到固定接收器的多方向测量,使用具有包裹的正态分布误差的测量技术 - 估计方向。
  • 鉴于两个点之间的多方位角估计值,由“普通”人类制作(假设受到包裹的截断正态分布误差) - 估计方向。

12 个答案:

答案 0 :(得分:18)

[注意 OP的问题(但不是标题)似乎已经变成了一个相当专业的问题(“...每个连续添加不是角度的SEQUENCE的平均值与运行平均值相差超过指定数量。“) - 请参阅@MaR评论和我的。我的以下答案涉及OP的标题以及与之相关的大部分讨论和答案。]

这不是逻辑或直觉的问题,而是定义的问题。在没有任何真正共识的情况下,已在SO上对此进行了讨角度应该在一个范围内定义(可能是-PI到+ PI,或0到2 * PI或者可能是-Inf到+ Inf。每种情况下答案都不同。

世界“角度”会引起混乱,因为它意味着不同的东西。 视角是无符号数量(并且通常是PI> theta> 0.在这种情况下,“正常”平均值可能有用。旋转角度(例如如果滑冰运动员可能会或可能不会签名并且可能包括theta> 2 * PI和theta< -2 * PI。

这里定义的是 angle = direction ,需要矢量。如果您使用“方向”而不是“角度”这个词,您将捕获OP(明显的原始)意图,这将有助于摆脱标量。

当角度被循环定义时,维基百科显示了正确的方法

theta = theta+2*PI*N = theta-2*PI*N

平均值的答案不是标量而是矢量。 OP可能不觉得这很直观,但它是唯一有用的正确方法。我们不能将-4的平方根重新定义为-2,因为它更具有本质 - 它必须是+ -2 * i。类似地,轴承-90度和+90度的平均值是零长度的矢量,而不是0.0度。

维基百科(http://en.wikipedia.org/wiki/Mean_of_circular_quantities)有一个特殊的部分和状态(方程式是LaTeX,可以在维基百科中看到):

  

大多数常用方法都失败了   圆形量,如角度,   白天,真实的小数部分   数字。对于您需要的数量   平均数量的平均值。

     

因为算术平均值不是   对角度有效,以下   方法可以用来获得两者   平均值和衡量标准   角度的方差:

     

将所有角度转换为相应的角度   单位圆上的点,例如α到   (cosα,sinα)。那是转换极地   坐标到笛卡尔坐标。   然后计算算术平均值   这些要点。结果点将会   躺在单位磁盘上。转换那个   指回极坐标。该   角度是一个合理的平均值   输入角度。得到的半径   如果所有角度相等,则为1。如果   角度均匀分布   在圈子上,然后由此产生   半径为0,没有   圆形意思。换句话说,   半径测量浓度   角度。

     

鉴于角度   \ alpha_1,\ dots,\ alpha_n的意思是   由

计算
M \alpha = \operatorname{atan2}\left(\frac{1}{n}\cdot\sum_{j=1}^n
     

\罪\ alpha_j,   \压裂{1} {N} \ CDOT \ sum_ {J = 1} ^ N   \ cos \ alpha_j \ right)

     

使用atan2变种   反正切函数,或

M \alpha = \arg\left(\frac{1}{n}\cdot\sum_{j=1}^n
     

\ exp(i \ cdot \ alpha_j)\ right)

     

使用复数。

请注意,在OP的问题中,角度0完全是任意的 - 从0到0而不是180的风来说没有什么特别的(除了在这个半球,它在自行车上更冷)。尝试将0,0,90更改为289,289,379,并查看简单算法如何不再有效。

一些分布,其中0和PI的角度具有特殊意义,但它们不在此范围内)。

以下是一些激烈的先前讨论,反映了当前的观点传播: - )

http://mathforum.org/library/drmath/view/53924.html

How do you calculate the average of a set of circular data?

http://forums.xkcd.com/viewtopic.php?f=17&t=22435

http://www.allegro.cc/forums/thread/595008

答案 1 :(得分:9)

谢谢大家帮助我更清楚地看到我的问题。

我找到了我要找的东西。 它被称为 Mitsuta方法

输入和输出的范围为[0..360]。

此方法适用于平均使用恒定采样间隔采样的数据。

该方法假设连续样本之间的差异小于180度(这意味着如果我们不能足够快地采样,则采样信号中的330度变化将被错误地检测为另一个的30度变化方向并将错误插入计算中)。 奈奎斯特 - 香农采样定理任何人?

这是一个c ++代码:

double AngAvrg(const vector<double>& Ang)
{
    vector<double>::const_iterator iter= Ang.begin();

    double fD   = *iter;
    double fSigD= *iter;

    while (++iter != Ang.end())
    {
        double fDelta= *iter - fD;

             if (fDelta < -180.) fD+= fDelta + 360.;
        else if (fDelta >  180.) fD+= fDelta - 360.;
        else                     fD+= fDelta       ;

        fSigD+= fD;
    }

    double fAvrg= fSigD / Ang.size();

    if (fAvrg >= 360.) return fAvrg -360.;
    if (fAvrg <  0.  ) return fAvrg +360.;
                       return fAvrg      ;
}

第51页http://www.epa.gov/scram001/guidance/met/mmgrma.pdf

对此进行了解释

感谢MaR发送链接作为评论。

如果采样数据不变,但我们的采样设备不准确Von Mises distribution,则单位矢量计算将是合适的。

答案 2 :(得分:3)

每个级别都不正确。

根据向量加法的规则添加向量。 “直观,预期”的答案可能不那么直观。

采用以下示例。如果我有一个单位向量(1,0),其中(0,0)的原点指向+ x方向,而另一个(-1,0)的原点位于(0,0)指向的位置-x方向,“平均”角度应该是多少?

如果我只是添加角度并除以2,我可以说“平均值”是+90或-90。你认为应该是哪一个?

如果我根据向量加法规则(逐个组件)添加向量,我会得到以下结果:

  

(1,0)+( - 1,0)=(0,0)

在极坐标中,这是一个零幅度和零角度的矢量。

那么“平均”角度应该是多少?对于一个简单的案例,我在这里有三个不同的答案。

我认为答案是,向量不遵循与数字相同的直觉,因为它们既有大小又有方向。也许你应该描述你正在解决的问题。

无论您决定采用何种解决方案,我都建议您将其基于矢量。这样总是正确的。

答案 3 :(得分:3)

平均来源轴承的含义是什么?首先回答这个问题,你将更接近于通过平均角度定义你的意思。

在我看来,正切角等于1/2的角度是正确的答案。如果我有一个单位力向我推动向量方向(1,0),另一个力向我推动向量方向(1,0),第三个力向我推动向量方向(0,1 ),然后产生的力(这些力的总和)是推动我朝向(1,2)方向的力。这些矢量代表0度,0度和90度的轴承。由矢量(1,2)表示的角度具有等于1/2的正切。

回复您的第二次修改:

  

假设我们正在测量风向。我们的3次测量分别为0度,0度和90度。由于所有测量都是等效可靠的,为什么我们对风向的最佳估计不应该是30度?设置为25.56度是偏向0 ...

好的,这是一个问题。角度为0的单位矢量不具有与实数0相同的数学特性。使用符号0v表示角度为0的向量,请注意

0v + 0v = 0v

是假的但是

0 + 0 = 0

对于实数来说是正确的。因此,如果0v表示单位速度和角度为0的风,那么0v + 0v是具有双单位速度和角度0的风。然后如果我们有第三个风向量(我将使用表示法表示) 90v)具有角度90和单位速度,然后由这些矢量之和产生的风确实存在偏差,因为它在水平方向上以两倍的单位速度行进,而在垂直方向上仅以单位速度行进。 / p>

答案 4 :(得分:2)

编辑:等效,但更强大的算法(更简单):

  1. 将角度分为2组,[0-180]和[180-360]
  2. 对两组进行数值平均
  3. 平均具有适当权重的2组平均值
  4. 如果发生环绕,请更正180˚
  5. 这是有效的,因为如果所有角度都在同一个半圆中,则数值平均“逻辑”起作用。然后我们延迟得到环绕错误,直到最后一步,它很容易被检测和纠正。我还提出了一些用于处理相反角度情况的代码。如果平均值相反,我们支持具有更多角度的半球,并且在两个半球中角度相等的情况下,我们返回None,因为没有平均值是有意义的。

    新代码:

    def averageAngles2(angles):
        newAngles = [a % 360 for a in angles];
        smallAngles = []
        largeAngles = []
        # split the angles into 2 groups: [0-180) and [180-360)
        for angle in newAngles:
            if angle < 180:
                smallAngles.append(angle)
            else:
                largeAngles.append(angle)
        smallCount = len(smallAngles)
        largeCount = len(largeAngles)
        #averaging each of the groups will work with standard averages
        smallAverage = sum(smallAngles) / float(smallCount) if smallCount else 0
        largeAverage = sum(largeAngles) / float(largeCount) if largeCount else 0
        if smallCount == 0:
            return largeAverage
        if largeCount == 0:
            return smallAverage
        average = (smallAverage * smallCount + largeAverage * largeCount) / \
            float(smallCount + largeCount)
        if largeAverage < smallAverage + 180:
            # average will not hit wraparound
            return average
        elif largeAverage > smallAverage + 180:
            # average will hit wraparound, so will be off by 180 degrees
            return (average + 180) % 360
        else:
            # opposite angles: return whichever has more weight
            if smallCount > largeCount:
                return smallAverage
            elif smallCount < largeCount:
                return largeAverage
            else:
                return None
    

    >>> averageAngles2([0, 0, 90])
    30.0
    >>> averageAngles2([30, 350])
    10.0
    >>> averageAngles2([0, 200])
    280.0
    

    <击> 这是一个稍微天真的算法:

    1. 从列表中删除所有对位角度
    2. 采取一对角度
    3. 将它们旋转到第一和第二象限并将它们平均
    4. 将平均角度旋转相同的数量
    5. 对于每个剩余角度,以相同方式平均,但随着复合角度的连续增加的重量
    6. 一些python代码(步骤1未实现)

      def averageAngles(angles):
          newAngles = [a % 360 for a in angles];
          average = 0
          weight = 0
          for ang in newAngles:
              theta = 0
              if 0 < ang - average <= 180:
                  theta = 180 - ang
              else:
                  theta = 180 - average
              r_ang = (ang + theta) % 360
              r_avg = (average + theta) % 360
              average = ((r_avg * weight + r_ang) / float(weight + 1) - theta) % 360
              weight += 1
          return average
      

      <击>

答案 5 :(得分:2)

在我看来,这是关于角度,而不是矢量。因此,360和0的平均值确实为180。 一转并且没有转弯的平均值应该是半圈。

答案 6 :(得分:2)

以下是我对同一问题的回答:

How do you calculate the average of a set of circular data?

它给出了OP所说的他想要的答案,但应该注意这一点:

“我还要强调,即使这是一个真正的角度平均值,与矢量解决方案不同,这并不一定意味着它是你应该使用的解决方案,相应的单位矢量的平均值可能很好你实际应该使用的价值。“

答案 7 :(得分:0)

将这组角度作为实际值并仅计算这些数字的算术平均值有什么问题?然后你会得到直观的(0 + 0 + 90)/ 3 = 30度。

编辑:感谢有用的评论并指出角度可能会超过360.我相信答案可能是正常的算术平均值减少“模数”360:我们将所有值相加,除以角度数,然后减去/加上360的倍数,使结果位于区间[0..360]。

答案 8 :(得分:0)

你可以这样做:假设你在数组angle中有一组角度,然后先计算数组:angle[i] = angle[i] mod 360,现在对数组执行一个简单的平均值。所以当你有360,10,20时,你的平均分为0,10和20--结果很直观。

答案 9 :(得分:0)

我认为问题源于你如何处理大于180的角度(以及大于360的角度)。如果在将角度添加到总数之前将角度减小到+180到-180的范围,您会得到更合理的结果:

int AverageOfAngles(int angles[], int count)
{
    int total = 0;
    for (int index = 0; index < count; index++)
    {
        int angle = angles[index] % 360;
        if (angle > 180) { angle -= 360; }
        total += angle;
    }

    return (int)((float)total/count);
}

答案 10 :(得分:0)

也许您可以将角度表示为四元数并取这些四元数的平均值并将其转换回角度。

我不知道它是否能给你你想要的东西,因为四元数是旋转而不是角度。我也不知道它是否会给你带来与矢量解决方案不同的东西。

2D中的四元数简化为复数,所以我猜它只是矢量,但是当简化为2D时,像http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20070017872_2007014421.pdf这样的一些有趣的四元数平均算法的表现会比矢量平均值更好。

答案 11 :(得分:0)

您在这里!参考是https://www.wxforum.net/index.php?topic=8660.0

def avgWind(directions):
    sinSum = 0
    cosSum = 0
    d2r = math.pi/180 #degree to radian
    r2d = 180/math.pi
       
    for i in range(len(directions)):
        sinSum += math.sin(directions[i]*d2r)
        cosSum += math.cos(directions[i]*d2r)
      
    return ((r2d*(math.atan2(sinSum, cosSum)) + 360) % 360)
a= np.random.randint(low=0, high=360, size=6)
print(a)
avgWind(a)