如何计算一组循环数据的平均值?

时间:2009-01-29 14:15:19

标签: algorithm math geometry

我想计算一组循环数据的平均值。例如,我可能会从阅读指南中获得几个样本。问题当然是如何处理环绕。相同的算法可能对表盘有用。

实际问题更为复杂 - 统计数据在球体或代数空间中的含义是什么?添加剂组模型。答案可能不是唯一的,例如平均359度和1度可以是0度或180度,但统计上0看起来更好。

这对我来说是一个真正的编程问题,我试图让它看起来不像是一个数学问题。

30 个答案:

答案 0 :(得分:85)

从角度计算单位矢量并取其平均值的角度。

答案 1 :(得分:52)

本书详细研究了这个问题: “球体统计”,阿肯色大学Geoffrey S. Watson讲座 数学科学中的注释,1983 John Wiley& Sons,Inc。如Bruce Karsh在http://catless.ncl.ac.uk/Risks/7.44.html#subj4所述。

从一组角度测量值估算平均角度A的好方法 a [i] 0< = i

                   sum_i_from_1_to_N sin(a[i])
a = arctangent ---------------------------
                   sum_i_from_1_to_N cos(a[i])

starblue给出的方法在计算上是等价的,但是他的理由更清晰,并且可能在程序上更有效率,并且在零情况下也能很好地工作,所以对他有好感。

现在更详细地探讨这个主题on Wikipedia,以及其他用途,例如小数部分。

答案 2 :(得分:47)

我看到了问题 - 例如,如果你有一个45'角和315'角,“自然”平均值将是180',但你想要的值实际上是0'。

我认为Starblue正在做点什么。只需计算每个角度的(x,y)笛卡尔坐标,并将这些结果矢量相加。最终矢量的角度偏移应该是您所需的结果。

x = y = 0
foreach angle {
    x += cos(angle)
    y += sin(angle)
}
average_angle = atan2(y, x)

我现在忽略罗盘标题从北方开始,顺时针方向,而“正常”笛卡尔坐标沿X轴从零开始,然后逆时针方向。无论如何,数学应该以相同的方式运作。

答案 3 :(得分:19)

关于两个角度的特殊情况:

答案((a + b)mod 360)/ 2 错误。对于角度350和2,最近的点是356,而不是176。

单位矢量和触发解决方案可能过于昂贵。

我从一点点的修补中得到的是:

diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180
angle = (360 + b + ( diff / 2 ) ) mod 360
  • 0,180 - > 90(两个答案:这个等式从a顺时针回答)
  • 180,0 - > 270(见上文)
  • 180,1 - > 90.5
  • 1,180 - > 90.5
  • 20,350 - > 5
  • 350,20> 5(以下所有示例也正确反转)
  • 10,20-> 15
  • 350,2 - > 356
  • 359,0 - > 359.5
  • 180,180 - > 180

答案 4 :(得分:14)

ackb是正确的,这些基于矢量的解决方案不能被视为真正的角度平均值,它们只是单位矢量对应物的平均值。但是,ackb的建议解决方案似乎没有数学上的声音。

以下是从最小化(angle [i] - avgAngle)^ 2(其中必要时纠正差异)的目标数学推导出的解决方案,这使得它成为角度的真正算术平均值。

首先,我们需要确切地考虑哪些情况下角度之间的差异与正常数字对应物之间的差异不同。考虑角度x和y,如果y> = x - 180且y <= x + 180,则我们可以直接使用差值(x-y)。否则,如果不满足第一个条件,那么我们必须在计算中使用(y + 360)而不是y。相应的,如果不满足第二个条件,那么我们必须使用(y-360)而不是y。由于曲线方程我们只是最小化这些不等式从真变为假的点的变化,反之亦然,我们可以将整个[0,360]范围分成由这些点分开的一组段。然后,我们只需要找到每个段的最小值,然后找到每个段的最小值,即平均值。

这是一张图像,展示了计算角度差异时出现问题的位置。如果x位于灰色区域,则会出现问题。

Angle comparisons

为了最小化变量,根据曲线,我们可以得到我们想要最小化的导数,然后我们找到转折点(这是导数= 0的位置)。

这里我们将应用最小化平方差的概念来推导出通用的算术平均公式:sum(a [i])/ n。曲线y = sum((a [i] -x)^ 2)可以这种方式最小化:

y = sum((a[i]-x)^2)
= sum(a[i]^2 - 2*a[i]*x + x^2)
= sum(a[i]^2) - 2*x*sum(a[i]) + n*x^2

dy\dx = -2*sum(a[i]) + 2*n*x

for dy/dx = 0:
-2*sum(a[i]) + 2*n*x = 0
-> n*x = sum(a[i])
-> x = sum(a[i])/n

现在将它应用于具有我们调整后差异的曲线:

b =正确(角度)差a [i] -x的a的子集 c =正确(角度)差(a [i] -360)-x的a的子集 cn = c的大小 d =正确(角度)差异(a [i] +360)-x的a的子集 dn = d的大小

y = sum((b[i]-x)^2) + sum(((c[i]-360)-b)^2) + sum(((d[i]+360)-c)^2)
= sum(b[i]^2 - 2*b[i]*x + x^2)
  + sum((c[i]-360)^2 - 2*(c[i]-360)*x + x^2)
  + sum((d[i]+360)^2 - 2*(d[i]+360)*x + x^2)
= sum(b[i]^2) - 2*x*sum(b[i])
  + sum((c[i]-360)^2) - 2*x*(sum(c[i]) - 360*cn)
  + sum((d[i]+360)^2) - 2*x*(sum(d[i]) + 360*dn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*(sum(b[i]) + sum(c[i]) + sum(d[i]))
  - 2*x*(360*dn - 360*cn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*sum(x[i])
  - 2*x*360*(dn - cn)
  + n*x^2

dy/dx = 2*n*x - 2*sum(x[i]) - 2*360*(dn - cn)

for dy/dx = 0:
2*n*x - 2*sum(x[i]) - 2*360*(dn - cn) = 0
n*x = sum(x[i]) + 360*(dn - cn)
x = (sum(x[i]) + 360*(dn - cn))/n

仅仅这一点不足以获得最小值,而它适用于具有无界集的正常值,因此结果肯定位于集合的范围内,因此是有效的。我们需要一个范围内的最小值(由段定义)。如果最小值小于我们的段的下限,那么该段的最小值必须在下限(因为二次曲线只有1个转折点),如果最小值大于我们的段的上限,则该段的最小值在上限。在我们得到每个段的最小值之后,我们只需找到一个具有最小值的值(sum((b [i] -x)^ 2)+ sum(((c [i] -360) )-b)^ 2)+ sum(((d [i] +360)-c)^ 2))。

这是曲线的图像,显​​示它在x =(a [i] +180)%360的点处如何变化。有问题的数据集是{65,92,230,320,250}。

Curve

这是Java中算法的一种实现,包括一些优化,其复杂度为O(nlogn)。如果将基于比较的排序替换为基于非比较的排序(例如基数排序),则可以将其减少为O(n)。

static double varnc(double _mean, int _n, double _sumX, double _sumSqrX)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX;
}
//with lower correction
static double varlc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            + 2*360*_sumC + _nc*(-2*360*_mean + 360*360);
}
//with upper correction
static double varuc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            - 2*360*_sumC + _nc*(2*360*_mean + 360*360);
}

static double[] averageAngles(double[] _angles)
{
    double sumAngles;
    double sumSqrAngles;

    double[] lowerAngles;
    double[] upperAngles;

    {
        List<Double> lowerAngles_ = new LinkedList<Double>();
        List<Double> upperAngles_ = new LinkedList<Double>();

        sumAngles = 0;
        sumSqrAngles = 0;
        for(double angle : _angles)
        {
            sumAngles += angle;
            sumSqrAngles += angle*angle;
            if(angle < 180)
                lowerAngles_.add(angle);
            else if(angle > 180)
                upperAngles_.add(angle);
        }


        Collections.sort(lowerAngles_);
        Collections.sort(upperAngles_,Collections.reverseOrder());


        lowerAngles = new double[lowerAngles_.size()];
        Iterator<Double> lowerAnglesIter = lowerAngles_.iterator();
        for(int i = 0; i < lowerAngles_.size(); i++)
            lowerAngles[i] = lowerAnglesIter.next();

        upperAngles = new double[upperAngles_.size()];
        Iterator<Double> upperAnglesIter = upperAngles_.iterator();
        for(int i = 0; i < upperAngles_.size(); i++)
            upperAngles[i] = upperAnglesIter.next();
    }

    List<Double> averageAngles = new LinkedList<Double>();
    averageAngles.add(180d);
    double variance = varnc(180,_angles.length,sumAngles,sumSqrAngles);

    double lowerBound = 180;
    double sumLC = 0;
    for(int i = 0; i < lowerAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle > lowerAngles[i]+180)
            testAverageAngle = lowerAngles[i];

        if(testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        lowerBound = lowerAngles[i];
        sumLC += lowerAngles[i];
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*lowerAngles.length)/_angles.length;
        //minimum is inside segment range
        //we will test average 0 (360) later
        if(testAverageAngle < 360 && testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,lowerAngles.length,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double upperBound = 180;
    double sumUC = 0;
    for(int i = 0; i < upperAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle < upperAngles[i]-180)
            testAverageAngle = upperAngles[i];

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        upperBound = upperAngles[i];
        sumUC += upperBound;
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*upperAngles.length)/_angles.length;
        //minimum is inside segment range
        //we test average 0 (360) now           
        if(testAverageAngle < 0)
            testAverageAngle = 0;

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,upperAngles.length,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double[] averageAngles_ = new double[averageAngles.size()];
    Iterator<Double> averageAnglesIter = averageAngles.iterator();
    for(int i = 0; i < averageAngles_.length; i++)
        averageAngles_[i] = averageAnglesIter.next();


    return averageAngles_;
}

一组角度的算术平均值可能与您对平均值应该是什么的直观概念不一致。例如,集合{179,179,0,181,181}的算术平均值是216(和144)。您立即想到的答案可能是180,但众所周知,算术平均值受边缘值的影响很大。你还应该记住,角度不是矢量,有时候在处理角度时看起来很吸引人。

该算法当然也适用于所有服从模数运算(最小调整)的量,例如一天中的时间。

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

答案 5 :(得分:6)

您必须更准确地定义平均值。对于两个角度的具体情况,我可以想到两种不同的情况:

  1. “真实”平均值,即(a + b)/ 2%360。
  2. 在保持同一个半圆的同时指向“另外两个”之间的角度,例如对于355和5,这将是0,而不是180.为此,您需要检查两个角度之间的差异是否大于180。如果是这样,请在使用上述公式之前将较小的角度增加360。
  3. 我不知道第二种替代方案如何在两个以上角度的情况下推广。

答案 6 :(得分:4)

与所有平均值一样,答案取决于指标的选择。对于给定度量M,对于[1,N]中的k,[-pi,pi]中的一些角度a_k的平均值是角度a_M,其最小化平方距离之和d ^ 2_M(a_M,a_k)。对于加权平均值,简单地在总和中包括权重w_k(使得sum_k w_k = 1)。也就是说,

a_M = arg min_x sum_k w_k d ^ 2_M(x,a_k)

两种常见的度量选择是Frobenius和Riemann度量。对于Frobenius度量,存在直接公式,其对应于循环统计中的平均方位的通常概念。有关详细信息,请参见“旋转组中的平均值和平均值”,Maher Moakher,SIAM Journal on Matrix Analysis and Applications,Volume 24,Issue 1,2002。 http://link.aip.org/link/?SJMAEL/24/1/1

这是GNU Octave 3.2.4的一个函数,用于计算:

function ma=meanangleoct(a,w,hp,ntype)
%   ma=meanangleoct(a,w,hp,ntype) returns the average of angles a
%   given weights w and half-period hp using norm type ntype
%   Ref: "Means and Averaging in the Group of Rotations",
%   Maher Moakher, SIAM Journal on Matrix Analysis and Applications,
%   Volume 24, Issue 1, 2002.

if (nargin<1) | (nargin>4), help meanangleoct, return, end 
if isempty(a), error('no measurement angles'), end
la=length(a); sa=size(a); 
if prod(sa)~=la, error('a must be a vector'); end
if (nargin<4) || isempty(ntype), ntype='F'; end
if ~sum(ntype==['F' 'R']), error('ntype must be F or R'), end
if (nargin<3) || isempty(hp), hp=pi; end
if (nargin<2) || isempty(w), w=1/la+0*a; end
lw=length(w); sw=size(w); 
if prod(sw)~=lw, error('w must be a vector'); end
if lw~=la, error('length of w must equal length of a'), end
if sum(w)~=1, warning('resumming weights to unity'), w=w/sum(w); end

a=a(:);     % make column vector
w=w(:);     % make column vector
a=mod(a+hp,2*hp)-hp;    % reduce to central period
a=a/hp*pi;              % scale to half period pi
z=exp(i*a); % U(1) elements

% % NOTA BENE:
% % fminbnd can get hung up near the boundaries.
% % If that happens, shift the input angles a
% % forward by one half period, then shift the
% % resulting mean ma back by one half period.
% X=fminbnd(@meritfcn,-pi,pi,[],z,w,ntype);

% % seems to work better
x0=imag(log(sum(w.*z)));
X=fminbnd(@meritfcn,x0-pi,x0+pi,[],z,w,ntype);

% X=real(X);              % truncate some roundoff
X=mod(X+pi,2*pi)-pi;    % reduce to central period
ma=X*hp/pi;             % scale to half period hp

return
%%%%%%

function d2=meritfcn(x,z,w,ntype)
x=exp(i*x);
if ntype=='F'
    y=x-z;
else % ntype=='R'
    y=log(x'*z);
end
d2=y'*diag(w)*y;
return
%%%%%%

% %   test script
% % 
% % NOTA BENE: meanangleoct(a,[],[],'R') will equal mean(a) 
% % when all abs(a-b) < pi/2 for some value b
% % 
% na=3, a=sort(mod(randn(1,na)+1,2)-1)*pi;
% da=diff([a a(1)+2*pi]); [mda,ndx]=min(da);
% a=circshift(a,[0 2-ndx])    % so that diff(a(2:3)) is smallest
% A=exp(i*a), B1=expm(a(1)*[0 -1; 1 0]), 
% B2=expm(a(2)*[0 -1; 1 0]), B3=expm(a(3)*[0 -1; 1 0]),
% masimpl=[angle(mean(exp(i*a))) mean(a)]
% Bsum=B1+B2+B3; BmeanF=Bsum/sqrt(det(Bsum)); 
% % this expression for BmeanR should be correct for ordering of a above
% BmeanR=B1*(B1'*B2*(B2'*B3)^(1/2))^(2/3);
% mamtrx=real([[0 1]*logm(BmeanF)*[1 0]' [0 1]*logm(BmeanR)*[1 0]'])
% manorm=[meanangleoct(a,[],[],'F') meanangleoct(a,[],[],'R')]
% polar(a,1+0*a,'b*'), axis square, hold on
% polar(manorm(1),1,'rs'), polar(manorm(2),1,'gd'), hold off

%     Meanangleoct Version 1.0
%     Copyright (C) 2011 Alphawave Research, robjohnson@alphawaveresearch.com
%     Released under GNU GPLv3 -- see file COPYING for more info.
%
%     Meanangle is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or (at
%     your option) any later version.
%
%     Meanangle is distributed in the hope that it will be useful, but
%     WITHOUT ANY WARRANTY; without even the implied warranty of
%     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%     General Public License for more details.
%
%     You should have received a copy of the GNU General Public License
%     along with this program.  If not, see `http://www.gnu.org/licenses/'.

答案 7 :(得分:3)

以下是完整的解决方案: (输入是一个以度为单位的轴承数组(0-360)

public static int getAvarageBearing(int[] arr)
{
    double sunSin = 0;
    double sunCos = 0;
    int counter = 0;

    for (double bearing : arr)
    {
        bearing *= Math.PI/180;

        sunSin += Math.sin(bearing);
        sunCos += Math.cos(bearing);
        counter++; 
    }

    int avBearing = INVALID_ANGLE_VALUE;
    if (counter > 0)
    {
        double bearingInRad = Math.atan2(sunSin/counter, sunCos/counter);
        avBearing = (int) (bearingInRad*180f/Math.PI);
        if (avBearing<0)
            avBearing += 360;
    }

    return avBearing;
}

答案 8 :(得分:3)

我想与微控制器共用一种方法,该方法没有浮点或三角函数。我仍然需要“平均”10个原始轴承读数以平滑变化。

  1. 检查第一个轴承的范围是270-360还是0-90度(北两个象限)
  2. 如果是,则将此和所有后续读数旋转180度,使所有值保持在0 <=轴承<0的范围内。 360.否则,请将读数记录下来。
  3. 一旦取得10个读数,假设没有环绕
  4. ,计算数字平均值
  5. 如果180度旋转已生效,则将计算出的平均值旋转180度,以恢复“真实”轴承。
  6. 这不理想;它可以打破。在这种情况下,我逃脱了,因为设备只能非常缓慢地旋转。我会把它放在那里以防其他人发现自己在类似的限制下工作。

答案 9 :(得分:2)

在python中,角度在[-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

Angles'环绕'(例如355 + 10 = 5),但标准算术将忽略此分支点。 但是,如果角度 b 与分支点相反,则( b + g )/ 2会给出最接近的平均值:青色点。 / p>

对于任何两个角度,我们可以旋转问题,使其中一个角度与分支点相反,执行标准平均,然后向后旋转。

rotated returned

答案 10 :(得分:2)

这是一个完整的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);
}

它采用双向量矢量形式的角度,并将平均值简单地返回为double。角度必须以度为单位,当然平均值也以度为单位。

答案 11 :(得分:2)

我会使用复数的矢量方式。我的例子是Python,它有内置的复数:

import cmath # complex math

def average_angle(list_of_angles):

    # make a new list of vectors
    vectors= [cmath.rect(1, angle) # length 1 for each vector
        for angle in list_of_angles]

    vector_sum= sum(vectors)

    # no need to average, we don't care for the modulus
    return cmath.phase(vector_sum)

请注意,Python不需要来构建临时的新向量列表,所有上述操作都可以在一个步骤中完成;我只是选择这种方式来近似适用于其他语言的伪代码。

答案 12 :(得分:1)

英文:

  1. 制作第二个数据集,所有角度都移动180°。
  2. 取两个数据集的差异。
  3. 取最小方差的数据集的平均值。
  4. 如果此平均值来自移位集,则将答案再次移动180。
  5. 在python中:

    #numpy NX1角度数组

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

答案 13 :(得分:1)

好吧,我晚会很晚,但以为我要加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;

答案 14 :(得分:1)

这是一个完全算术解决方案,使用移动平均线并注意标准化值。如果所有角度都在圆的一侧(彼此相差180°),它速度很快,并提供正确的答案。

它在数学上等效于添加将值移动到范围(0,180)中的偏移量,计算平均值然后减去偏移量。

评论描述了特定值在任何给定时间可以采取的范围

set

答案 15 :(得分:1)

没有单一的“正确答案”。我建议你读这本书, K. V. Mardia和P. E. Jupp,“定向统计”,(Wiley,1999), 进行彻底的分析。

答案 16 :(得分:1)

让我们用圆周上的点来表示这些角度。

我们可以假设所有这些点落在圆圈的同一半吗? (否则,没有明显的方法来定义“平均角度”。想想直径上的两个点,例如0度和180度---平均90度或270度?当我们有3个或更多时会发生什么?均匀分布点?)

通过这个假设,我们在该半圆上选择一个任意点作为“原点”,并测量相对于该原点的给定角度集(称之为“相对角度”)。注意,相对角度的绝对值严格小于180度。最后,取这些相对角度的平均值来得到所需的平均角度(相对于我们的原点)。

答案 17 :(得分:1)

这是一个想法:通过始终计算最接近的角度的平均值来保持重量,从而迭代地建立平均值。

另一个想法:找到给定角度之间的最大间隙。找到将其平分的点,然后选取圆上的相反点作为参考零来计算平均值。

答案 18 :(得分:0)

(只是想从估算理论或统计推断中分享我的观点)

Nimble的试验是获得MMSE ^估计一组角度,但它是找到“平均”方向的选择之一;我们也可以找到MMAE ^估计值,或者其他一些估计值是“平均”方向,它取决于量化方向误差的度量标准;或者更一般地说,在估算理论中,成本函数的定义。

^ MMSE / MMAE对应于最小均方/绝对误差。

ackb说“平均角度phi_avg应该具有sum_i | phi_avg-phi_i | ^ 2变得最小的属性......它们是平均值,但不是角度”

----你量化了均方意义上的误差,这是最常见的方式之一,然而,并非唯一的方法。这里大多数人喜欢的答案(即单位向量的总和并得到结果的角度)实际上是合理的解决方案之一。如果向量的方向被建模为von Mises分布,那么(可以证明)ML估计器用作我们想要的“平均”方向。这种分布并不花哨,只是来自2D Guassian的周期性采样分布。见Eqn。 (2.179)在Bishop的书“模式识别和机器学习”中。同样,它绝不是代表“平均”方向的唯一最佳方法,但是,它具有良好的理论上的理由和简单的实施是非常合理的。

Nimble说“ackb是正确的,这些基于矢量的解决方案不能被视为真正的角度平均值,它们只是单位矢量对应物的平均值”

----这不是真的。 “单位矢量对应物”揭示了矢量方向的信息。角度是一个不考虑向量长度的数量,单位向量是具有长度为1的附加信息的东西。您可以将“单位”向量定义为长度为2,这并不重要。

答案 19 :(得分:0)

平均角度phi_avg应具有sum_i | phi_avg-phi_i | ^ 2变为最小的属性,其中差异必须在[-Pi,Pi]中(因为反过来可能会更短!) 。这很容易通过将所有输入值归一化为[0,2Pi],保持运行平均值phi_run并选择归一化| phi_i-phi_run |来实现。至[-Pi,Pi) (通过添加或减去2Pi)。上面的大多数建议做了的其他事情 拥有最小的属性,即他们平均某事,但不是角度。

答案 20 :(得分:0)

我借助@David_Hanak的答案解决了这个问题。 正如他所说:

  

指向&#34;之间的角度&#34;另外两个人住在同一个半圆里,例如对于355和5,这将是0,而不是180.为此,您需要检查两个角度之间的差异是否大于180。如果是这样,请在使用上述公式之前将较小的角度增加360。

所以我所做的是计算所有角度的平均值。然后所有小于此的角度,将它们增加360.然后通过将它们全部加上并将它们除以它们的长度来重新计算平均值。

        float angleY = 0f;
        int count = eulerAngles.Count;

        for (byte i = 0; i < count; i++)
            angleY += eulerAngles[i].y;

        float averageAngle = angleY / count;

        angleY = 0f;
        for (byte i = 0; i < count; i++)
        {
            float angle = eulerAngles[i].y;
            if (angle < averageAngle)
                angle += 360f;
            angleY += angle;
        }

        angleY = angleY / count;

完美无缺。

答案 21 :(得分:0)

您可以在Matlab中使用此功能:

function retVal=DegreeAngleMean(x) 

len=length(x);

sum1=0; 
sum2=0; 

count1=0;
count2=0; 

for i=1:len 
   if x(i)<180 
       sum1=sum1+x(i); 
       count1=count1+1; 
   else 
       sum2=sum2+x(i); 
       count2=count2+1; 
   end 
end 

if (count1>0) 
     k1=sum1/count1; 
end 

if (count2>0) 
     k2=sum2/count2; 
end 

if count1>0 && count2>0 
   if(k2-k1 >= 180) 
       retVal = ((sum1+sum2)-count2*360)/len; 
   else 
       retVal = (sum1+sum2)/len; 
   end 
elseif count1>0 
    retVal = k1; 
else 
    retVal = k2; 
end 

答案 22 :(得分:0)

对于任何编程语言,您可以在以下链接中看到解决方案和一些解释: https://rosettacode.org/wiki/Averages/Mean_angle

例如, C ++解决方案

#include<math.h>
#include<stdio.h>

double
meanAngle (double *angles, int size)
{
  double y_part = 0, x_part = 0;
  int i;

  for (i = 0; i < size; i++)
    {
      x_part += cos (angles[i] * M_PI / 180);
      y_part += sin (angles[i] * M_PI / 180);
    }

  return atan2 (y_part / size, x_part / size) * 180 / M_PI;
}

int
main ()
{
  double angleSet1[] = { 350, 10 };
  double angleSet2[] = { 90, 180, 270, 360};
  double angleSet3[] = { 10, 20, 30};

  printf ("\nMean Angle for 1st set : %lf degrees", meanAngle (angleSet1, 2));
  printf ("\nMean Angle for 2nd set : %lf degrees", meanAngle (angleSet2, 4));
  printf ("\nMean Angle for 3rd set : %lf degrees\n", meanAngle (angleSet3, 3));
  return 0;
}

输出:

Mean Angle for 1st set : -0.000000 degrees
Mean Angle for 2nd set : -90.000000 degrees
Mean Angle for 3rd set : 20.000000 degrees

Matlab解决方案

function u = mean_angle(phi)
    u = angle(mean(exp(i*pi*phi/180)))*180/pi;
end

 mean_angle([350, 10])
ans = -2.7452e-14
 mean_angle([90, 180, 270, 360])
ans = -90
 mean_angle([10, 20, 30])
ans =  20.000

答案 23 :(得分:0)

虽然starblue的答案给出了平均单位向量的角度,但是如果你接受在0到2 * pi范围内可能有多个答案,则可以将算术平均值的概念扩展到角度(或者0°至360°)。例如,0°和180°的平均值可以是90°或270°。

算术平均值具有单个值的属性,其具有与输入值的最小平方距离和。两个单位矢量之间沿单位圆的距离可以很容易地计算为它们的点积的反余弦。如果我们通过最小化矢量和每个输入单位矢量的点积的平方反余弦的总和来选择单位矢量,那么我们有一个等价的平均值。同样,请记住,在特殊情况下可能有两个或更多的最小值。

这个概念可以扩展到任意数量的维度,因为沿着单位球的距离可以用与沿着单位圆的距离完全相同的方式计算 - 两个单位向量的点积的反余弦。

对于圆圈,我们可以通过多种方式求解这个平均值,但我提出了以下O(n ^ 2)算法(角度是弧度,我避免计算单位向量):

var bestAverage = -1
double minimumSquareDistance
for each a1 in input
    var sumA = 0;
    for each a2 in input
        var a = (a2 - a1) mod (2*pi) + a1
        sumA += a
    end for
    var averageHere = sumA / input.count
    var sumSqDistHere = 0
    for each a2 in input
        var dist = (a2 - averageHere + pi) mod (2*pi) - pi // keep within range of -pi to pi
        sumSqDistHere += dist * dist
    end for
    if (bestAverage < 0 OR sumSqDistHere < minimumSquareDistance) // for exceptional cases, sumSqDistHere may be equal to minimumSquareDistance at least once. In these cases we will only find one of the averages
        minimumSquareDistance = sumSqDistHere
        bestAverage = averageHere
    end if
end for
return bestAverage

如果所有角度都在180°之内,那么我们可以使用更简单的O(n)+ O(排序)算法(再次使用弧度并避免使用单位向量):

sort(input)
var largestGapEnd = input[0]
var largestGapSize = (input[0] - input[input.count-1]) mod (2*pi)
for (int i = 1; i < input.count; ++i)
    var gapSize = (input[i] - input[i - 1]) mod (2*pi)
    if (largestGapEnd < 0 OR gapSize > largestGapSize)
        largestGapSize = gapSize
        largestGapEnd = input[i]
    end if
end for
double sum = 0
for each angle in input
    var a2 = (angle - largestGapEnd) mod (2*pi) + largestGapEnd
    sum += a2
end for
return sum / input.count

要使用度数,只需将pi替换为180.如果您打算使用更多维度,那么您很可能必须使用迭代方法来求解平均值。

答案 24 :(得分:0)

Alnitak拥有正确的解决方案。 Nick Fortescue的解决方案在功能上是相同的。

对于

的特殊情况

(sum(x_component)= 0.0&amp;&amp; sum(y_component)= 0.0)//例如2个角度为10.和190.度ea。

使用0.0度作为总和

计算上你必须测试这种情况,因为atan2(0。,0。)是未定义的并且会产生错误。

答案 25 :(得分:0)

基于Alnitak's answer,我编写了一种Java方法来计算多个角度的平均值:

如果你的角度是弧度:

public static double averageAngleRadians(double... angles) {
    double x = 0;
    double y = 0;
    for (double a : angles) {
        x += Math.cos(a);
        y += Math.sin(a);
    }

    return Math.atan2(y, x);
}

如果你的角度是度数:

public static double averageAngleDegrees(double... angles) {
    double x = 0;
    double y = 0;
    for (double a : angles) {
        x += Math.cos(Math.toRadians(a));
        y += Math.sin(Math.toRadians(a));
    }

    return Math.toDegrees(Math.atan2(y, x));
}

答案 26 :(得分:0)

问题非常简单。 1.确保所有角度都在-180到180度之间。 2. a添加所有非负角度,取其平均值,并计算多少 2. b。添加所有负角度,取其平均值和COUNT个多少。 3.取pos_average减去neg_average的差值    如果差异大于180,则将差异更改为360减去差异。否则只需改变差异的标志。请注意,差异始终是非负的。 Average_Angle等于pos_average加上差值乘以“weight”,负数除以负数和正数之和

答案 27 :(得分:0)

这是一些平均角度的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;
}

答案 28 :(得分:0)

如果有人在寻找 JavaScript 解决方案,我已经翻译了维基百科页面Mean of circular quantities中给出的示例(Nick's answer中也提到了该示例) mathjs library的帮助下,将其转换为JavaScript / NodeJS代码。

如果您的角度为

const maths = require('mathjs');

getAverageDegrees = (array) => {
    let arrayLength = array.length;

    let sinTotal = 0;
    let cosTotal = 0;

    for (let i = 0; i < arrayLength; i++) {
        sinTotal += maths.sin(array[i] * (maths.pi / 180));
        cosTotal += maths.cos(array[i] * (maths.pi / 180));
    }

    let averageDirection = maths.atan(sinTotal / cosTotal) * (180 / maths.pi);

    if (cosTotal < 0) {
        averageDirection += 180;
    } else if (sinTotal < 0) {
        averageDirection += 360;
    }

    return averageDirection;
}

此解决方案对我来说非常有效,可以从一组罗盘方向中找到平均方向。我已经在各种定向数据(0-360度)上对此进行了测试,它看起来非常健壮。

或者,如果您的角度为弧度

const maths = require('mathjs');
getAverageRadians = (array) => {
    let arrayLength = array.length;

    let sinTotal = 0;
    let cosTotal = 0;

    for (let i = 0; i < arrayLength; i++) {
        sinTotal += maths.sin(array[i]);
        cosTotal += maths.cos(array[i]);
    }

    let averageDirection = maths.atan(sinTotal / cosTotal);

    if (cosTotal < 0) {
        averageDirection += 180;
    } else if (sinTotal < 0) {
        averageDirection += 360;
    }

    return averageDirection;
}

希望这些解决方案对面临与我类似的编程挑战的人有所帮助。

答案 29 :(得分:-3)

我有一个与@Starblue不同的方法,它给出了上面给出的一些角度的“正确”答案。例如:

  • angle_avg([350,10])= 0
  • angle_avg([ - 90,90,40])= 13.333
  • angle_avg([350,2])= 356

它使用连续角度之间差异的总和。 代码(在Matlab中):

function [avg] = angle_avg(angles)
last = angles(1);
sum = angles(1);
for i=2:length(angles)
    diff = mod(angles(i)-angles(i-1)+ 180,360)-180
    last = last + diff;
    sum = sum + last;
end
avg = mod(sum/length(angles), 360);
end