Bagaimana Anda menghitung rata-rata satu set data sirkuler?

147

Saya ingin menghitung rata-rata satu set data sirkuler. Sebagai contoh, saya mungkin memiliki beberapa sampel dari pembacaan kompas. Masalahnya tentu saja adalah bagaimana menangani sampulnya. Algoritma yang sama mungkin berguna untuk tampilan jam.

Pertanyaan yang sebenarnya lebih rumit - apa arti statistik pada suatu bola atau dalam ruang aljabar yang "membungkus", misalnya grup aditif mod n. Jawabannya mungkin tidak unik, misalnya rata-rata 359 derajat dan 1 derajat bisa 0 derajat atau 180, tetapi secara statistik 0 terlihat lebih baik.

Ini adalah masalah pemrograman yang nyata bagi saya dan saya berusaha membuatnya tidak terlihat seperti masalah matematika saja.

Nick Fortescue
sumber
1
Dengan sudut rata-rata, saya menganggap bantalan yang Anda inginkan sebenarnya jahat. Ada sudut antara dua garis, bantalan adalah arah dari satu garis. Dalam hal ini, starblue benar.
SmacL
@Nick Fortescue: dapatkah Anda memperbarui pertanyaan Anda menjadi lebih spesifik: apakah maksud Anda sudut atau arah?
Mitch Wheat
1
Saya sebenarnya menginginkan sesuatu yang sedikit lebih rumit (tetapi analog dengan bantalan) dan mencoba menyederhanakan untuk membuat pertanyaan lebih mudah, dan seperti biasa membuatnya lebih rumit. Saya menemukan jawaban yang saya inginkan di catless.ncl.ac.uk/Risks/7.44.html#subj4 . Saya akan mengedit ulang qn.
Nick Fortescue
Jawaban Risiko pada dasarnya adalah apa yang saya usulkan, kecuali bahwa hal itu dapat mengalami masalah ketika penyebutnya adalah 0. #
starblue
Artikel menarik tentang arti sudut: twistedoakstudios.com/blog/?p=938
starblue

Jawaban:

99

Hitung satuan vektor dari sudut dan ambil sudut rata-rata.

starblue
sumber
8
Itu tidak berfungsi jika vektor membatalkan satu sama lain. Rata-rata masih bisa bermakna dalam kasus ini, tergantung pada definisi pastinya.
David Hanak
21
@ David, arah rata-rata dua bantalan 180 derajat tidak ditentukan. Ini tidak membuat jawaban starblue salah, itu hanya kasus luar biasa, seperti yang terjadi pada banyak masalah geomterik.
SmacL
5
@smacl: Saya setuju, jika sudut mewakili arah. Tetapi jika Anda memikirkan bilangan kompleks, misalnya, dan mendefinisikan rata-rata sebagai "apa argumen c, sehingga c c == a b", di mana a dan b memiliki modulus 1, maka rata-rata 0 dan 180 adalah 90.
David Hanak
5
@IerreBdR: Jika saya mengambil dua langkah ke arah 0deg dan satu di arah 90deg saya akan pindah ke arah 26,56 deg relatif ke tempat saya mulai. Dalam pengertian ini 26,56 lebih masuk akal sebagai arah rata-rata {0,0,90} deg daripada 30 deg. Rata-rata aljabar hanyalah salah satu dari banyak kemungkinan rata-rata (lihat en.wikipedia.org/wiki/Mean ) - dan tampaknya cukup tidak relevan untuk tujuan rata-rata arah (seperti halnya untuk banyak lainnya).
Janus
60

Pertanyaan ini diperiksa secara rinci dalam buku: "Statistics On Spheres", Geoffrey S. Watson, Catatan Kuliah Universitas Arkansas dalam Ilmu Matematika, 1983 John Wiley & Sons, Inc. sebagaimana disebutkan di http: //catless.ncl. ac.uk/Risks/7.44.html#subj4 oleh Bruce Karsh.

Cara yang baik untuk memperkirakan sudut rata-rata, A, dari serangkaian pengukuran sudut a [i] 0 <= i

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

Metode yang diberikan oleh starblue adalah setara secara komputasi, tetapi alasannya lebih jelas dan mungkin lebih efisien secara program, dan juga bekerja dengan baik dalam kasus nol, jadi pujian baginya.

Subjek sekarang dieksplorasi lebih rinci di Wikipedia , dan dengan kegunaan lain, seperti bagian fraksional.

Nick Fortescue
sumber
8
yang juga sama dengan algoritma yang saya posting pada saat yang sama dengan Anda. Anda akan perlu menggunakan atan2 daripada atan biasa, karena jika tidak, Anda tidak bisa mengatakan di kuadran mana jawabannya.
Alnitak
Anda masih bisa mendapatkan jawaban yang tidak pasti. Seperti pada 0, 180 sampel. Jadi, Anda masih harus memeriksa untuk kasus tepi. Juga, biasanya ada fungsi atan2 yang tersedia yang mungkin lebih cepat dalam kasus Anda.
Loki
50

Saya melihat masalah - misalnya, jika Anda memiliki sudut 45 'dan sudut 315', rata-rata "alami" akan menjadi 180 ', tetapi nilai yang Anda inginkan sebenarnya 0'.

Saya pikir Starblue ke sesuatu. Hitung saja koordinat kartesian (x, y) untuk setiap sudut, dan tambahkan vektor-vektor yang dihasilkan bersamaan. Offset sudut dari vektor akhir harus menjadi hasil yang Anda inginkan.

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

Saya mengabaikan sekarang bahwa arah kompas dimulai di utara, dan bergerak searah jarum jam, sedangkan koordinat kartesian "normal" dimulai dengan nol di sepanjang sumbu X, dan kemudian pergi berlawanan arah jarum jam. Bagaimanapun, matematika harus bekerja dengan cara yang sama.

Alnitak
sumber
13
Perpustakaan matematika Anda mungkin menggunakan radian untuk sudut pandang. Ingatlah untuk bertobat.
Martin Beckett
2
Mungkin sudah larut malam, tetapi menggunakan logika ini, saya mendapatkan sudut rata-rata 341.8947 ... bukannya 342 untuk sudut [320, 330, 340, 350, 10,]. Adakah yang melihat kesalahan ketik saya?
Alex Robinson
1
@AlexRobinson itu bukan kesalahan ketik, itu karena sudut akhir hanyalah sudut akhirnya yang diperoleh dengan mengambil serangkaian langkah dari masing-masing sudut secara individual.
Alnitak
1
@AlexRobinson, untuk lebih spesifik: cos(), sin()dan atan2()memberikan perkiraan (yang baik, tapi masih off oleh 1 atau 2 ULP) sehingga semakin banyak Anda rata-rata, semakin kesalahan Anda termasuk.
Matthieu
23

UNTUK KASUS KHUSUS DUA GILA:

Jawaban ((a + b) mod 360) / 2 adalah SALAH . Untuk sudut 350 dan 2, titik terdekat adalah 356, bukan 176.

Vektor unit dan solusi trigonometri mungkin terlalu mahal.

Yang saya dapat dari sedikit bermain-main adalah:

diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180
angle = (360 + b + ( diff / 2 ) ) mod 360
  • 0, 180 -> 90 (dua jawaban untuk ini: persamaan ini mengambil jawaban searah jarum jam dari a)
  • 180, 0 -> 270 (lihat di atas)
  • 180, 1 -> 90,5
  • 1, 180 -> 90,5
  • 20, 350 -> 5
  • 350, 20 -> 5 (semua contoh berikut juga membalik dengan benar)
  • 10, 20 -> 15
  • 350, 2 -> 356
  • 359, 0 -> 359.5
  • 180, 180 -> 180
darron
sumber
Ini lebih lanjut dapat dioptimalkan dengan menggunakan BAMS: stackoverflow.com/questions/1048945/…
darron
Tidak buruk. Baris pertama menghitung sudut relatif a terhadap b dalam kisaran [-180, 179], baris kedua menghitung sudut tengah dari itu. Saya akan menggunakan b + diff / 2 bukannya - diff / 2 untuk kejelasan.
starblue
1
Apakah saya melewatkan sesuatu? Saya DO mendapatkan 295.
Darron
Ah .. aku mengerti. Operator mod Matlab membungkus -10 hingga 350. Saya akan mengubah kodenya. Ini 360 tambahan sederhana.
darron
Fitur bagus lain dari metode ini adalah mudah untuk menerapkan rata-rata tertimbang dari kedua sudut. Di baris kedua, kalikan beda dengan bobot sudut pertama dan ganti 2 pada penyebut dengan jumlah bobot. angle = (360 + b + (BERAT [a] * diff / (BERAT [a] + BERAT [b]))) mod 360
oosterwal
14

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.

Perbandingan sudut

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}.

Melengkung

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.

Gesit
sumber
Metode Mitsuta sebenarnya memberikan sudut awal + rata-rata rotasi dari sudut awal. Jadi untuk mendapatkan metode yang sama, menghitung kesalahan pengukuran maka Anda harus melihat rotasi yang terjadi dan memperkirakan kesalahan untuk itu. Saya pikir Anda akan memerlukan distribusi untuk rotasi untuk memperkirakan kesalahan bagi mereka.
Cekatan
6

Anda harus mendefinisikan rata-rata lebih akurat. Untuk kasus khusus dari dua sudut, saya dapat memikirkan dua skenario yang berbeda:

  1. Rata-rata "benar", yaitu (a + b) / 2% 360.
  2. Sudut yang menunjuk "di antara" dua lainnya sementara tetap dalam setengah lingkaran yang sama, misalnya untuk 355 dan 5, ini akan menjadi 0, bukan 180. Untuk melakukan ini, Anda perlu memeriksa apakah perbedaan antara kedua sudut lebih besar dari 180 atau tidak. Jika demikian, tambah sudut yang lebih kecil sebesar 360 sebelum menggunakan rumus di atas.

Saya tidak melihat bagaimana alternatif kedua dapat digeneralisasi untuk kasus lebih dari dua sudut.

David Hanak
sumber
Sementara pertanyaan mengacu pada sudut, lebih baik dianggap sebagai arah yang berarti, dan merupakan masalah navigasi yang umum.
SmacL
Poin bagus, David. Misalnya, berapa rata-rata sudut 180º dan sudut 540º? Apakah 360º atau 180º?
Baltimark
3
@ Taltimark, saya kira itu tergantung pada apa yang Anda lakukan. Jika navigasinya, mungkin yang terakhir. Jika lompatan snowboarding yang mewah, mungkin yang pertama;)
SmacL
Jadi rata-rata "benar" 1 dan 359 adalah (360/2)% 360 = 180 ?? Saya pikir tidak.
Mati di Sente
1
@Buat di Sente: berbicara secara numerik, pasti. Misalnya, jika sudut mewakili belokan, bukan arah, maka rata-rata 359 dan 1 pasti 180. Ini semua masalah interpretasi.
David Hanak
4

Seperti semua rata-rata, jawabannya tergantung pada pilihan metrik. Untuk metrik M yang diberikan, rata-rata beberapa sudut a_k dalam [-pi, pi] untuk k dalam [1, N] adalah sudut a_M yang meminimalkan jumlah jarak kuadrat d ^ 2_M (a_M, a_k). Untuk nilai rata-rata tertimbang, cukup masukkan jumlah bobot w_k (sedemikian rupa sehingga sum_k w_k = 1). Itu adalah,

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

Dua pilihan metrik yang umum adalah metrik Frobenius dan Riemann. Untuk metrik Frobenius, ada rumus langsung yang sesuai dengan gagasan bantalan rata-rata dalam statistik sirkuler. Lihat "Cara dan Rata-rata dalam Grup Rotasi", Maher Moakher, Jurnal SIAM tentang Analisis dan Aplikasi Matriks, Volume 24, Edisi 1, 2002, untuk perinciannya.
http://link.aip.org/link/?SJMAEL/24/1/1

Berikut adalah fungsi untuk GNU Octave 3.2.4 yang melakukan perhitungan:

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, [email protected]
%     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/'.
Rob Johnson
sumber
4

Saya ingin berbagi metode yang saya gunakan dengan mikrokontroler yang tidak memiliki kemampuan floating point atau trigonometri. Saya masih perlu "rata-rata" 10 pembacaan bantalan mentah untuk memuluskan variasi.

  1. Periksa apakah bantalan pertama adalah kisaran 270-360 atau 0-90 derajat (dua kuadran utara)
  2. Jika ya, putar ini dan semua bacaan berikutnya dengan 180 derajat, jaga semua nilai dalam kisaran 0 <= bearing <360. Jika tidak, ambil bacaan saat datang.
  3. Setelah 10 pembacaan diambil, hitung rata-rata numerik dengan asumsi tidak ada sampul
  4. Jika rotasi 180 derajat telah berlaku maka putar rata-rata yang dihitung sebesar 180 derajat untuk kembali ke bantalan "benar".

Itu tidak ideal; itu bisa pecah. Saya lolos dengan ini dalam kasus ini karena perangkat hanya berputar sangat lambat. Saya akan meletakkannya di sana kalau-kalau ada orang lain menemukan diri mereka bekerja di bawah pembatasan yang sama.

thombles
sumber
3

Dalam Bahasa Inggris:

  1. Buat kumpulan data kedua dengan semua sudut bergeser 180.
  2. Ambil varians dari kedua set data.
  3. Ambil rata-rata kumpulan data dengan varians terkecil.
  4. Jika rata-rata ini dari himpunan bergeser maka geser jawabannya lagi 180.

Dengan python:

Larik sudut NX1 #numpy

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

else:
    average = (np.average((A-180)%360)+180)%360
Jason
sumber
Ini adalah cara yang bagus untuk mencapai hasil akhir tanpa fungsi trigonometri, sederhana dan mudah diimplementasikan.
Ian Mercer
ini bekerja untuk semua rentang data sirkuler; hanya bergeser setengah rentang melingkar; jawaban bagus!
Captain Fantastic
3

Inilah solusi lengkapnya: (input adalah larik bantalan dalam derajat (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;
}
DuduArbel
sumber
Masalah ini telah membingungkan saya untuk sementara waktu, solusi Anda bekerja (menggunakan Arduino, jadi beberapa perubahan pada kode Anda tetapi tidak banyak), saya menunjukkan kompas membaca dan mengambil bacaan setiap 50ms dan menyimpan ke dalam 16 x array membaca, yang kemudian saya gunakan dalam fungsi Anda di atas, masalah 0-360 membungkus terpecahkan! terima kasih :)
Andologi
3

Dengan python, dengan sudut antara [-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)

Detail:

Untuk rata-rata dua sudut ada dua rata-rata terpisah 180 °, tetapi kita mungkin menginginkan rata-rata yang lebih dekat.

Secara visual, rata-rata biru ( b ) dan hijau ( a ) menghasilkan titik teal:

Asli

Sudut 'membungkus' (mis. 355 + 10 = 5), tetapi aritmatika standar akan mengabaikan titik cabang ini. Namun jika sudut b berlawanan dengan titik cabang, maka ( b + g ) / 2 memberikan rata-rata terdekat: titik teal.

Untuk dua sudut manapun, kita dapat memutar masalah sehingga salah satu sudut berlawanan dengan titik cabang, melakukan rata-rata standar, lalu memutar kembali.

diputardikembalikan

Brad Saund
sumber
2

Saya akan menggunakan cara vektor menggunakan bilangan kompleks. Contoh saya adalah dalam Python, yang memiliki bilangan kompleks bawaan:

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)

Perhatikan bahwa Python tidak perlu membuat daftar vektor sementara yang baru, semua hal di atas dapat dilakukan dalam satu langkah; Saya baru saja memilih cara ini untuk memperkirakan kode semu yang berlaku untuk bahasa lain juga.

tzot
sumber
2

Inilah solusi C ++ lengkap:

#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);
}

Dibutuhkan sudut dalam bentuk vektor ganda, dan mengembalikan rata-rata hanya sebagai ganda. Sudut harus dalam derajat, dan tentu saja rata-rata dalam derajat juga.

adam10603
sumber
avgCosadalah rata-rata komponen x, dan avgSinmerupakan rata-rata komponen y. Parameter untuk fungsi arctangent adalah atan2( y, x ). Jadi, bukankah seharusnya kode Anda menjadi: atan2( avgSin, avgCos ) ??
Mike Finch
Saya mendapatkan algoritme ini dari suatu tempat, saya tidak membuat sendiri, jadi saya menganggap itu benar seperti itu. Plus itu memberikan hasil yang benar juga.
adam10603
2

Berdasarkan jawaban Alnitak , saya telah menulis metode Java untuk menghitung rata-rata dari beberapa sudut:

Jika sudut Anda berada di radian:

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);
}

Jika sudut Anda dalam derajat:

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));
}
Stevoisiak
sumber
1

Inilah sebuah ide: bangun rata-rata secara iteratif dengan selalu menghitung rata-rata sudut yang paling dekat satu sama lain, dengan menjaga bobot.

Gagasan lain: temukan celah terbesar di antara sudut yang diberikan. Temukan titik yang membagi dua, dan kemudian pilih titik yang berlawanan pada lingkaran sebagai nol referensi untuk menghitung rata-rata.

John dengan wafel
sumber
Saya tidak merekomendasikan jawaban saya, melainkan jawaban berperingkat tinggi dari starblue. Pengamatan utama di sana adalah memikirkan pusat kompas sebagai titik 0,0.
John dengan wafel
1

Mari kita mewakili sudut-sudut ini dengan titik-titik pada keliling lingkaran.

Bisakah kita mengasumsikan bahwa semua titik ini jatuh pada setengah lingkaran yang sama? (Kalau tidak, tidak ada cara yang jelas untuk mendefinisikan "sudut rata-rata". Pikirkan dua titik pada diameter, misalnya 0 deg dan 180 deg --- adalah rata-rata 90 deg atau 270 deg? Apa yang terjadi ketika kita memiliki 3 atau lebih menyebarkan poin secara merata?)

Dengan asumsi ini, kami memilih titik sembarang pada setengah lingkaran itu sebagai "asal", dan mengukur kumpulan sudut yang diberikan sehubungan dengan asal ini (sebut ini "sudut relatif"). Perhatikan bahwa sudut relatif memiliki nilai absolut yang ketat kurang dari 180 derajat. Akhirnya, ambil rata-rata sudut relatif ini untuk mendapatkan sudut rata-rata yang diinginkan (relatif terhadap asal usul kita tentu saja).

Zach Scrivena
sumber
1

Tidak ada "jawaban yang benar". Saya merekomendasikan membaca buku, KV Mardia dan PE Jupp, "Directional Statistics", (Wiley, 1999), untuk analisis menyeluruh.

cffk
sumber
1

(Hanya ingin berbagi sudut pandang saya dari Teori Estimasi atau Inferensi Statistik)

Uji coba Nimble adalah untuk mendapatkan estimasi MMSE ^ dari serangkaian sudut, tetapi ini adalah salah satu pilihan untuk menemukan arah "rata-rata"; seseorang juga dapat menemukan perkiraan MMAE ^, atau perkiraan lain sebagai arah "rata-rata", dan itu tergantung pada kesalahan arah pengukuran metrik Anda; atau lebih umum dalam teori estimasi, definisi fungsi biaya.

^ MMSE / MMAE sesuai dengan rata-rata kuadrat minimum / kesalahan absolut.

ackb berkata "Sudut rata-rata phi_avg harus memiliki properti yang sum_i | phi_avg-phi_i | ^ 2 menjadi minimal ... mereka rata-rata sesuatu, tetapi bukan sudut"

---- Anda mengukur kesalahan dalam arti kuadrat dan itu salah satu cara yang paling umum, namun, bukan satu-satunya cara. Jawaban yang disukai oleh kebanyakan orang di sini (yaitu, penjumlahan vektor satuan dan mendapatkan sudut hasilnya) sebenarnya adalah salah satu solusi yang masuk akal. Ini (dapat dibuktikan) penduga ML yang berfungsi sebagai arah "rata-rata" yang kita inginkan, jika arah vektor dimodelkan sebagai distribusi von Mises. Distribusi ini tidak mewah, dan hanya distribusi sampel secara berkala dari Guassian 2D. Lihat Persamaan. (2.179) dalam buku Bishop "Pengenalan Pola dan Pembelajaran Mesin". Sekali lagi, tidak berarti itu satu-satunya yang terbaik untuk mewakili arah "rata-rata", namun, itu cukup masuk akal yang memiliki justifikasi teoretis yang baik dan implementasi sederhana.

Nimble berkata "ackb benar bahwa solusi berbasis vektor ini tidak dapat dianggap sebagai rata-rata sudut sejati, mereka hanya rata-rata rekan-rekan vektor satuan"

----ini tidak benar. "Unit vector counterpart" mengungkapkan informasi arah vektor. Sudut adalah kuantitas tanpa mempertimbangkan panjang vektor, dan vektor satuan adalah sesuatu dengan informasi tambahan bahwa panjangnya adalah 1. Anda dapat menentukan vektor "satuan" Anda dengan panjang 2, tidak masalah.

dunia air
sumber
1

Berikut ini adalah solusi aritmatika sepenuhnya menggunakan moving average dan berhati-hati untuk menormalkan nilai-nilai. Cepat dan memberikan jawaban yang benar jika semua sudut berada di satu sisi lingkaran (dalam 180 ° satu sama lain).

Secara matematis sama dengan menambahkan offset yang menggeser nilai ke dalam kisaran (0, 180), menghitung rata-rata dan kemudian mengurangi offset.

Komentar menggambarkan kisaran nilai apa yang dapat diambil pada waktu tertentu

// angles have to be in the range [0, 360) and within 180° of each other.
// n >= 1
// returns the circular average of the angles int the range [0, 360).
double meanAngle(double* angles, int n)
{
    double average = angles[0];
    for (int i = 1; i<n; i++)
    {
        // average: (0, 360)
        double diff = angles[i]-average;
        // diff: (-540, 540)

        if (diff < -180)
            diff += 360;
        else if (diff >= 180)
            diff -= 360;
        // diff: (-180, 180)

        average += diff/(i+1);
        // average: (-180, 540)

        if (average < 0)
            average += 360;
        else if (average >= 360)
            average -= 360;
        // average: (0, 360)
    }
    return average;
}
bgp2000
sumber
1

Yah saya sangat terlambat ke pesta, tetapi saya pikir saya akan menambah nilai 2 sen saya karena saya tidak bisa menemukan jawaban yang pasti. Pada akhirnya saya mengimplementasikan versi Java berikut dari metode Mitsuta yang, saya harap, memberikan solusi yang sederhana dan kuat. Terutama karena Deviasi Standar memberikan dispersi ukuran dan, jika sd == 90, menunjukkan bahwa sudut input menghasilkan rata-rata yang ambigu.

EDIT: Sebenarnya saya menyadari bahwa implementasi asli saya dapat lebih disederhanakan, sebenarnya sangat sederhana mengingat semua percakapan dan trigonometri terjadi pada jawaban lainnya.

/**
 * 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))};
}

... dan untuk Anda semua (Java) Geeks di luar sana, Anda dapat menggunakan pendekatan di atas untuk mendapatkan sudut rata-rata dalam satu baris.

Arrays.stream(angles).map(angle -> angle<180 ? angle: (angle-360)).sum() / angles.length;
neilireson
sumber
Saya yakin Anda melewatkan sesuatu dari metode Mitsuda. Silakan lihat jawaban yang diposting oleh Lior Kogan stackoverflow.com/a/1828222/9265852
kykzk46
0

Alnitak memiliki solusi yang tepat. Solusi Nick Fortescue secara fungsional sama.

Untuk kasus khusus di mana

(jumlah (x_komponen) = 0,0 && jumlah (y_komponen) = 0,0) // mis. 2 sudut 10 dan 190. derajat ea.

gunakan 0,0 derajat sebagai jumlah

Secara komputasional Anda harus menguji untuk kasus ini karena atan2 (0., 0.) tidak ditentukan dan akan menghasilkan kesalahan.

jeffD
sumber
pada glibc 'atan2' didefinisikan untuk (0, 0) - hasilnya 0
Alnitak
0

Sudut rata-rata phi_avg harus memiliki properti yang sum_i | phi_avg-phi_i | ^ 2 menjadi minimal, di mana perbedaannya harus dalam [-Pi, Pi) (karena mungkin lebih pendek untuk pergi sebaliknya!). Ini mudah dicapai dengan menormalkan semua nilai input ke [0, 2Pi), menjaga phi_run rata-rata berjalan, dan memilih normalisasi | phi_i-phi_run | ke [-Pi, Pi) (dengan menambahkan atau mengurangi 2Pi). Kebanyakan saran di atas melakukan sesuatu yang lain yang tidak memiliki properti minimal, yaitu, mereka rata-rata sesuatu , tetapi tidak sudut.

ackb
sumber
0

Saya memecahkan masalah dengan bantuan jawaban dari @David_Hanak. Seperti yang ia nyatakan:

Sudut yang menunjuk "di antara" dua lainnya sementara tetap dalam setengah lingkaran yang sama, misalnya untuk 355 dan 5, ini akan menjadi 0, bukan 180. Untuk melakukan ini, Anda perlu memeriksa apakah perbedaan antara kedua sudut lebih besar dari 180 atau tidak. Jika demikian, tambah sudut yang lebih kecil sebesar 360 sebelum menggunakan rumus di atas.

Jadi yang saya lakukan adalah menghitung rata-rata semua sudut. Dan kemudian semua sudut yang kurang dari ini, tingkatkan sebesar 360. Kemudian hitung ulang rata-rata dengan menambahkan semuanya dan membaginya dengan panjangnya.

        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;

Bekerja dengan sempurna.

konsnos
sumber
0

Fungsi python:

from math import sin,cos,atan2,pi
import numpy as np
def meanangle(angles,weights=0,setting='degrees'):
    '''computes the mean angle'''
    if weights==0:
         weights=np.ones(len(angles))
    sumsin=0
    sumcos=0
    if setting=='degrees':
        angles=np.array(angles)*pi/180
    for i in range(len(angles)):
        sumsin+=weights[i]/sum(weights)*sin(angles[i])
        sumcos+=weights[i]/sum(weights)*cos(angles[i])
    average=atan2(sumsin,sumcos)
    if setting=='degrees':
        average=average*180/pi
    return average
E.Rooijen
sumber
0

Anda dapat menggunakan fungsi ini di 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 
Martin006
sumber
Algoritme tampaknya berfungsi, tetapi dalam kenyataannya mungkin gagal total di dunia nyata. Memberi Anda nilai sudut yang berlawanan dari sudut yang diberikan.
tothphu
0

Anda dapat melihat solusi dan sedikit penjelasan di tautan berikut, untuk bahasa pemrograman APAPUN: https://rosettacode.org/wiki/Averages/Mean_angle

Misalnya, solusi 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;
}

Keluaran:

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

Atau solusi 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
Gines Hidalgo
sumber
0

Sementara jawaban starblue memberikan sudut vektor satuan rata-rata, dimungkinkan untuk memperluas konsep rata-rata aritmatika ke sudut jika Anda menerima bahwa mungkin ada lebih dari satu jawaban dalam kisaran 0 hingga 2 * pi (atau 0 ° hingga 360 °). Misalnya, rata-rata 0 ° dan 180 ° dapat 90 ° atau 270 °.

Rata-rata aritmatika memiliki properti sebagai nilai tunggal dengan jumlah minimum jarak kuadrat ke nilai input. Jarak sepanjang lingkaran satuan antara dua vektor satuan dapat dengan mudah dihitung sebagai cosinus terbalik dari produk titik mereka. Jika kita memilih vektor satuan dengan meminimalkan jumlah cosinus terbalik kuadrat dari produk titik vektor kita dan setiap vektor satuan input, maka kita memiliki rata-rata yang setara. Sekali lagi, perlu diingat bahwa mungkin ada dua atau lebih minimum dalam kasus luar biasa.

Konsep ini dapat diperluas ke sejumlah dimensi, karena jarak di sepanjang unit sphere dapat dihitung dengan cara yang sama persis dengan jarak di sepanjang lingkaran unit - cosinus terbalik dari produk titik dari dua vektor satuan.

Untuk lingkaran kita dapat memecahkan rata-rata ini dalam beberapa cara, tetapi saya mengusulkan algoritma O (n ^ 2) berikut (sudut dalam radian, dan saya menghindari menghitung vektor satuan):

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

Jika semua sudut berada dalam 180 ° satu sama lain, maka kita bisa menggunakan algoritma O (n) + O (sort) yang lebih sederhana (lagi-lagi menggunakan radian dan menghindari penggunaan vektor satuan):

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

Untuk menggunakan derajat, cukup ganti pi dengan 180. Jika Anda berencana untuk menggunakan lebih banyak dimensi maka Anda kemungkinan besar harus menggunakan metode berulang untuk menyelesaikan rata-rata.

John Thoits
sumber
0

Masalahnya sangat sederhana. 1. Pastikan semua sudut berada di antara -180 dan 180 derajat. 2. a Tambahkan semua sudut non-negatif, ambil rata-rata, dan COUNT berapa banyak 2. b. Tambahkan semua sudut negatif, ambil rata-rata dan COUNT berapa banyak. 3. Ambil perbedaan pos_average minus neg_average Jika perbedaan lebih dari 180 maka ubah perbedaan menjadi 360 perbedaan minus. Kalau tidak, ubah saja tanda perbedaannya. Perhatikan bahwa perbedaan selalu non-negatif. Average_Angle sama dengan pos_average plus selisih kali "berat", jumlah negatif dibagi dengan jumlah negatif dan positif

DynamicChart
sumber
0

Berikut adalah beberapa kode java untuk sudut rata-rata, saya pikir cukup kuat.

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;
}
Robert Sutton
sumber
-3

Saya memiliki metode yang berbeda dari @Starblue yang memberikan jawaban yang "benar" untuk beberapa sudut yang diberikan di atas. Sebagai contoh:

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

Ini menggunakan jumlah atas perbedaan antara sudut berurutan. Kode (dalam 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
Barak Schiller
sumber
1
Kode Anda mengembalikan jawaban berbeda untuk [-90,90,40]dan [90,-90,40]; Saya tidak berpikir rata-rata non-komutatif adalah yang sangat berguna.
musiphil