ackb benar bahwa solusi berbasis vektor ini tidak dapat dianggap sebagai rata-rata sudut yang sebenarnya, mereka hanya rata-rata dari rekan unit vektor. Namun, solusi yang disarankan ackb tampaknya tidak terdengar secara matematis.
Berikut ini adalah solusi yang secara matematis berasal dari tujuan meminimalkan (sudut [i] - avgAngle) ^ 2 (di mana perbedaan dikoreksi jika perlu), yang menjadikannya rata-rata aritmatika sebenarnya dari sudut.
Pertama, kita perlu melihat secara tepat kasus mana perbedaan antara sudut berbeda dengan perbedaan antara rekan-rekan nomor normalnya. Pertimbangkan sudut x dan y, jika y> = x - 180 dan y <= x + 180, maka kita dapat menggunakan perbedaan (xy) secara langsung. Kalau tidak, jika kondisi pertama tidak terpenuhi maka kita harus menggunakan (y + 360) dalam perhitungan alih-alih y. Sejalan dengan itu, jika kondisi kedua tidak terpenuhi maka kita harus menggunakan (y-360) daripada y. Karena persamaan kurva kita meminimalkan hanya perubahan pada titik-titik di mana ketidaksetaraan ini berubah dari true ke false atau sebaliknya, kita dapat memisahkan rentang penuh [0,360) menjadi satu set segmen, dipisahkan oleh titik-titik ini. Kemudian, kita hanya perlu menemukan minimum dari masing-masing segmen ini, dan kemudian minimum dari minimum setiap segmen, yang merupakan rata-rata.
Berikut adalah gambar yang menunjukkan di mana masalah terjadi dalam menghitung perbedaan sudut. Jika x terletak di area abu-abu maka akan ada masalah.
Untuk meminimalkan suatu variabel, tergantung pada kurva, kita dapat mengambil turunan dari apa yang ingin kita perkecil dan kemudian kita menemukan titik balik (yang merupakan tempat turunan = 0).
Di sini kita akan menerapkan gagasan meminimalkan perbedaan kuadrat untuk memperoleh rumus rata-rata aritmatika yang umum: jumlah (a [i]) / n. Kurva y = jumlah ((a [i] -x) ^ 2) dapat diminimalkan dengan cara ini:
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
Sekarang menerapkannya ke kurva dengan perbedaan kami yang disesuaikan:
b = himpunan bagian di mana perbedaan yang benar (sudut) a [i] -xc = himpunan bagian di mana perbedaan (sudut) yang benar (a [i] -360) -x cn = ukuran cd = bagian dari di mana perbaiki (sudut) yang benar (a [i] +360) -x dn = ukuran 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
Ini saja tidak cukup untuk mendapatkan minimum, sementara itu bekerja untuk nilai normal, yang memiliki set tidak terikat, sehingga hasilnya pasti akan berada dalam kisaran set dan oleh karena itu valid. Kami membutuhkan minimum dalam rentang (ditentukan oleh segmen). Jika minimum kurang dari batas bawah segmen kami maka minimum segmen itu harus di batas bawah (karena kurva kuadrat hanya memiliki 1 titik balik) dan jika minimum lebih besar dari batas atas segmen kami maka minimum segmen adalah pada batas atas. Setelah kami memiliki minimum untuk setiap segmen, kami hanya menemukan yang memiliki nilai terendah untuk apa yang kami meminimalkan (jumlah ((b [i] -x) ^ 2) + jumlah (((c [i] -360 ) -b) ^ 2) + jumlah (((d [i] +360) -c) ^ 2)).
Berikut ini adalah gambar untuk kurva, yang menunjukkan bagaimana itu berubah pada titik di mana x = (a [i] +180)% 360. Kumpulan data yang dimaksud adalah {65,92.230.320.250}.
Berikut ini adalah implementasi dari algoritma di Jawa, termasuk beberapa optimisasi, kompleksitasnya adalah O (nlogn). Ini dapat dikurangi menjadi O (n) jika Anda mengganti jenis berbasis perbandingan dengan jenis berbasis non perbandingan, seperti jenis radix.
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_;
}
Mean aritmatika dari serangkaian sudut mungkin tidak setuju dengan ide intuitif Anda tentang apa yang seharusnya rata-rata. Sebagai contoh, rata-rata aritmatika dari himpunan {179.179.0.181.181} adalah 216 (dan 144). Jawaban yang segera Anda pikirkan mungkin 180, namun diketahui bahwa rata-rata aritmatika sangat dipengaruhi oleh nilai tepi. Anda juga harus ingat bahwa sudut bukan vektor, semenarik yang tampak ketika berhadapan dengan sudut terkadang.
Algoritma ini tentu saja juga berlaku untuk semua kuantitas yang mematuhi aritmatika modular (dengan penyesuaian minimal), seperti waktu dalam sehari.
Saya juga ingin menekankan bahwa meskipun ini adalah rata-rata sudut sebenarnya, tidak seperti solusi vektor, itu tidak berarti bahwa itu adalah solusi yang harus Anda gunakan, rata-rata vektor satuan yang sesuai mungkin adalah nilai yang Anda sebenarnya harus menggunakan.