ackbは、これらのベクトルベースのソリューションを真の角度の平均と見なすことはできません。これらは、対応する単位ベクトルの平均にすぎません。ただし、ackbが提案する解決策は、数学的に正しいようには見えません。
以下は、(angle [i]-avgAngle)^ 2(必要に応じて差が修正される)を最小化するという目標から数学的に導出されたソリューションであり、角度の真の算術平均になります。
まず、角度の違いが通常の数値の違いとどのケースが異なるかを正確に調べる必要があります。角度xとyを考えます。y> = x-180であり、y <= x + 180である場合、差(xy)を直接使用できます。それ以外の場合、最初の条件が満たされない場合、計算ではyではなく(y + 360)を使用する必要があります。対応して、2番目の条件が満たされない場合は、yの代わりに(y-360)を使用する必要があります。曲線の方程式は、これらの不等式がtrueからfalseに、またはその逆に変化するポイントでの変化のみを最小化しているため、[0,360)の全範囲をこれらのポイントで区切られた一連のセグメントに分離できます。次に、これらの各セグメントの最小値を見つけ、次に各セグメントの最小値の最小値、つまり平均を見つけるだけです。
次の画像は、角度差の計算で問題が発生する場所を示しています。xが灰色の領域にある場合、問題があります。
変数を最小化するには、曲線に応じて、最小化したいものの導関数を取得し、転換点(導関数= 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のサブセットa [i] -xc =正しい(角度の)差があるaのサブセット(a [i] -360)-x cn = cdのサイズ= aのサブセット正しい(角度の)差(a [i] +360)-x 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
これだけでは最小値を得るには十分ではありませんが、制限のないセットを持つ通常の値に対しては機能するため、結果は確実にセットの範囲内にあり、したがって有効です。(セグメントで定義された)範囲内の最小値が必要です。最小値がセグメントの下限よりも小さい場合、そのセグメントの最小値は下限になければなりません(2次曲線には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}です。
これは、いくつかの最適化を含む、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ですが、算術平均がエッジ値に大きく影響されることはよく知られています。また、角度はベクトルではないことも覚えておく必要があります。角度を扱うときに見られるのと同じくらい魅力的です。
もちろん、このアルゴリズムは、時刻など、モジュラー演算(最小限の調整)に従うすべての数量にも適用されます。
また、これは角度の真の平均ですが、ベクトルソリューションとは異なり、必ずしも使用するソリューションであるとは限らないことを強調しておきます。対応する単位ベクトルの平均は実際の値である可能性があります。使用する必要があります。