ackb, bu vektör tabanlı çözümlerin açıların gerçek ortalamaları olarak kabul edilemeyeceği konusunda haklıdır, bunlar yalnızca birim vektör karşılıklarının ortalamasıdır. Ancak, ackb'nin önerdiği çözüm matematiksel olarak sağlam görünmüyor.
Aşağıdaki, onu açıların gerçek bir aritmetik ortalaması haline getiren, (açı [i] - ortalama açı) ^ 2'yi (burada fark gerekirse düzeltilir) en aza indirme hedefinden matematiksel olarak türetilen bir çözümdür.
İlk olarak, tam olarak hangi durumlarda açılar arasındaki farkın normal sayı karşılıkları arasındaki farktan farklı olduğuna bakmamız gerekir. X ve y açılarını düşünün, eğer y> = x - 180 ve y <= x + 180 ise, o zaman farkı (xy) doğrudan kullanabiliriz. Aksi takdirde, ilk koşul karşılanmazsa, hesaplamada y yerine (y + 360) kullanmalıyız. Buna karşılık, ikinci koşul karşılanmazsa, y yerine (y-360) kullanmalıyız. Eğrinin denklemi, yalnızca bu eşitsizliklerin doğrudan yanlışa veya tersi yönde değiştiği noktalardaki değişiklikleri en aza indirdiğimiz için, tam [0,360) aralığı bu noktalarla ayrılmış bir bölümler kümesine ayırabiliriz. Daha sonra, bu segmentlerin her birinin yalnızca minimumunu ve ardından her segmentin minimum olan minimumunu bulmamız gerekir, bu da ortalama.
İşte açı farklarının hesaplanmasında sorunların nerede ortaya çıktığını gösteren bir resim. Eğer x gri alanda bulunuyorsa bir sorun olacaktır.
Bir değişkeni en aza indirmek için, eğriye bağlı olarak, küçültmek istediğimizin türevini alabiliriz ve sonra dönüm noktasını bulabiliriz (burada türev = 0).
Burada, ortak aritmetik ortalama formülünü türetmek için kare farkın en aza indirilmesi fikrini uygulayacağız: toplam (a [i]) / n. Y = toplam ((a [i] -x) ^ 2) eğrisi şu şekilde en aza indirilebilir:
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
Şimdi onu ayarlanmış farklılıklarımızla eğrilere uyguluyoruz:
b = doğru (açısal) farkın olduğu a'nın alt kümesi a [i] -xc = doğru (açısal) farkın olduğu a'nın alt kümesi (a [i] -360) -x cn = cd'nin boyutu = a'nın alt kümesi doğru (açısal) fark (a [i] +360) -x dn = d boyutu
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
Sınırsız bir kümeye sahip normal değerler için çalışırken bu tek başına minimumu elde etmek için yeterli değildir, bu nedenle sonuç kesinlikle kümenin aralığı içinde yer alır ve bu nedenle geçerlidir. Bir aralık içinde minimuma ihtiyacımız var (segment tarafından tanımlanan). Minimum, segmentimizin alt sınırından daha küçükse, o segmentin minimumunun alt sınırda olması gerekir (çünkü ikinci dereceden eğrilerin yalnızca 1 dönüm noktası vardır) ve minimum, segmentimizin üst sınırından büyükse, o zaman segmentin minimumu, üst sınır. Her segment için minimum değeri elde ettikten sonra, küçülttüğümüz şey için en düşük değere sahip olanı buluruz (sum ((b [i] -x) ^ 2) + sum (((c [i] -360 ) -b) ^ 2) + toplam (((d [i] +360) -c) ^ 2)).
İşte eğrinin x = (a [i] +180)% 360 olduğu noktalarda nasıl değiştiğini gösteren bir resim. Veri seti söz konusu {65,92,230,320,250}.
İşte algoritmanın bazı optimizasyonları da içeren Java'daki bir uygulaması, karmaşıklığı O (nlogn). Karşılaştırmaya dayalı sıralamayı, taban sıralaması gibi karşılaştırmaya dayalı olmayan bir sıralama ile değiştirirseniz, O (n) 'ye indirgenebilir.
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_;
}
Bir dizi açının aritmetik ortalaması, ortalamanın ne olması gerektiğine dair sezgisel fikrinizle uyuşmayabilir. Örneğin, {179,179,0,181,181} kümesinin aritmetik ortalaması 216'dır (ve 144). Hemen aklınıza gelen cevap muhtemelen 180'dir, ancak aritmetik ortalamanın kenar değerlerinden büyük ölçüde etkilendiği iyi bilinmektedir. Bazen açılarla uğraşırken göründüğü kadar çekici göründüğü kadar, açıların vektör olmadığını da unutmamalısınız.
Bu algoritma, tabii ki günün saati gibi modüler aritmetiğe (minimum ayarlama ile) uyan tüm miktarlar için de geçerlidir.
Ayrıca, vektör çözümlerinin aksine, bu gerçek bir açı ortalaması olsa da, bunun kullanmanız gereken çözüm olduğu anlamına gelmez, karşılık gelen birim vektörlerin ortalamasının aslında sizin değeriniz olabileceğini de vurgulamak isterim. kullanıyor olmalı.