Bir dizi dairesel verinin ortalamasını nasıl hesaplarsınız? [kapalı]


147

Bir dizi dairesel verinin ortalamasını hesaplamak istiyorum. Örneğin, bir pusula okumasından birkaç örnek alabilirim. Elbette sorun, sarma ile nasıl başa çıkılacağı. Aynı algoritma, bir saat yüzü için yararlı olabilir.

Asıl soru daha karmaşıktır - istatistik, bir kürede veya "etrafını saran" cebirsel bir uzayda ne anlama gelir, örneğin, toplama grubu mod n. Cevap benzersiz olmayabilir, örneğin ortalama 359 derece ve 1 derece 0 derece veya 180 olabilir, ancak istatistiksel olarak 0 daha iyi görünüyor.

Bu benim için gerçek bir programlama problemi ve bunu bir Matematik problemi gibi göstermemeye çalışıyorum.


1
Ortalama açıya göre, aslında ortalama yön istediğinizi varsayıyorum. İki çizgi arasında bir açı vardır, bir kerteriz tek bir çizginin yönüdür. Bu durumda starblue haklı.
SmacL

@Nick Fortescue: Sorunuzu daha spesifik olacak şekilde güncelleyebilir misiniz: açıları mı yoksa bir yönü mü kastediyorsunuz?
Mitch Wheat

1
Aslında biraz daha karmaşık bir şey istedim (ancak rulmanlara benzer) ve soruyu kolaylaştırmak için basitleştirmeye çalışıyordum ve her zamanki gibi daha karmaşık hale getirdi. İstediğim yanıtı catless.ncl.ac.uk/Risks/7.44.html#subj4 adresinde buldum . Qn'yi yeniden düzenleyeceğim.
Nick Fortescue

Riskler yanıtı, temelde önerdiğim şeydir, ancak payda 0 olduğunda sorun
yaşanabilir.

Açıların anlamı üzerine ilginç bir yazı: twistedoakstudios.com/blog/?p=938
starblue

Yanıtlar:


102

Açılardan birim vektörleri hesaplayın ve ortalamalarının açısını alın.


8
Vektörler birbirini götürürse bu işe yaramaz. Kesin tanımına bağlı olarak ortalama bu durumda yine de anlamlı olabilir.
David Hanak

22
@David, iki rulmanın 180 derece dıştaki ortalama yönü tanımsız. Bu, starblue'nun cevabını yanlış yapmaz, birçok jeomterik problemde olduğu gibi sadece istisnai bir durumdur.
SmacL

5
@smacl: Eğer açılar yönleri temsil ediyorsa, katılıyorum. Ancak, örneğin karmaşık sayıları düşünürseniz ve ortalamayı " c == a b olacak şekilde c'nin argümanı nedir" olarak tanımlarsanız , burada a ve b'nin modülü 1 ise, o zaman ortalama 0 ve 180 90.
David Hanak


5
@PierreBdR: Eğer 0 derece yönünde iki adım ve 90 derece yönünde bir adım atarsam, başladığım yere göre 26.56 derece yönünde hareket etmiş olurum. Bu anlamda 26.56, 30 dereceden {0,0,90} derece ortalama yön olarak çok daha anlamlıdır. Cebirsel ortalama pek çok olası ortalamadan sadece biridir (bkz. En.wikipedia.org/wiki/Mean ) - ve ortalama yönleri için oldukça alakasız görünüyor (tıpkı diğerleri için olduğu gibi).
Janus

63

Bu soru, http: //catless.ncl adresinde bahsedildiği gibi, "Statistics On Spheres", Geoffrey S. Watson, University of Arkansas Lecture Notes in the Mathematical Sciences, 1983 John Wiley & Sons, Inc. adlı kitapta ayrıntılı olarak incelenmiştir. ac.uk/Risks/7.44.html#subj4 , Bruce Karsh tarafından.

A [i] 0 <= i bir dizi açı ölçümünden ortalama bir açıyı (A) tahmin etmenin iyi bir yolu

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

Starblue tarafından verilen yöntem sayısal olarak eşdeğerdir, ancak nedenleri daha açık ve muhtemelen programatik olarak daha verimli ve sıfır durumunda da iyi işliyor, bu yüzden onu tebrik ediyor.

Konu şimdi Wikipedia'da ve kesirli parçalar gibi diğer kullanımlarla daha ayrıntılı olarak inceleniyor .


8
bu da sizinle aynı zamanda yayınladığım algoritma ile büyük ölçüde aynı. Düz bir atan yerine atan2 kullanmanız gerekir, çünkü aksi takdirde cevabın hangi çeyrekte olduğunu
bilemezsiniz

Hala bazı belirsiz cevaplarla karşılaşabilirsiniz. 0, 180 örneğindeki gibi. Yani yine de uç durumları kontrol etmeniz gerekiyor. Ayrıca, genellikle sizin durumunuzda daha hızlı olabilecek bir atan2 işlevi vardır.
Loki

50

Sorunu görüyorum - örneğin, 45 'açınız ve 315' açınız varsa, "doğal" ortalama 180 'olacaktır, ancak istediğiniz değer gerçekte 0'dır.

Sanırım Starblue bir şeylerin peşinde. Her açı için (x, y) kartezyen koordinatlarını hesaplayın ve elde edilen bu vektörleri toplayın. Son vektörün açısal uzaklığı, gerekli sonucunuz olmalıdır.

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

Şimdilik bir pusula yönünün kuzeyden başlayıp saat yönünde ilerlediğini görmezden geliyorum, oysa "normal" kartezyen koordinatlar X ekseni boyunca sıfırla başlıyor ve sonra saat yönünün tersine gidiyor. Matematik ne olursa olsun aynı şekilde çalışmalıdır.


14
Matematik kitaplığınız muhtemelen açılar için Radyan kullanır. Dönüştürmeyi unutma.
Martin Beckett

2
Belki gece çok geç, ama bu mantığı kullanarak, [320, 330, 340, 350, 10,] açıları için 342 yerine ... ortalama 341.8947 açı elde ediyorum. Yazım hatamı gören var mı?
Alex Robinson

1
@AlexRobinson bu bir yazım hatası değil, çünkü son açı basitçe bu açıların her birinin bir dizi adımı ayrı ayrı atılarak elde edilen nihai açıdır.
Alnitak

1
@AlexRobinson, daha spesifik olmak: cos(), sin()ve atan2()yaklaştırmaların vermek (iyi olanları, ama yine de 1 ya da 2 ulps tarafından kapalı) daha sen ortalama, daha hatalar şunlardır yüzden.
Matthieu

27

İKİ AÇI ÖZEL DURUMU İÇİN:

Cevap / 2 ((a + b) 360 mod) bir YANLIŞ . 350 ve 2 açılar için en yakın nokta 176 değil 356'dır.

Birim vektör ve trigonometrik çözümler çok pahalı olabilir.

Küçük bir tamirattan elde ettiğim şey:

diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180
angle = (360 + b + ( diff / 2 ) ) mod 360
  • 0, 180 -> 90 (bunun için iki cevap: bu denklem saat yönündeki cevabı a'dan alır)
  • 180, 0 -> 270 (yukarıya bakın)
  • 180, 1 -> 90,5
  • 1, 180 -> 90,5
  • 20, 350 -> 5
  • 350, 20 -> 5 (aşağıdaki tüm örnekler de doğru şekilde tersine çevrilir)
  • 10, 20 -> 15
  • 350, 2 -> 356
  • 359, 0 -> 359,5
  • 180, 180 -> 180

Bu, BAMS kullanımıyla daha da optimize edilebilir: stackoverflow.com/questions/1048945/…
darron

Fena değil. İlk satır, [-180, 179] aralığında a'nın b'ye göre bağıl açısını hesaplar, ikincisi bundan orta açıyı hesaplar. Netlik için a - diff / 2 yerine b + diff / 2 kullanırdım.
starblue

1
Bir şey mi kaçırıyorum? Ben DO 295. olsun
DARRON

1
Ah .. anladım. Matlab'ın mod operatörü -10'u 350'ye çevirir. Kodu değiştireceğim. Bu basit bir ek 360.
darron

Bu yöntemin bir başka güzel özelliği, iki açının ağırlıklı ortalamasını uygulamanın kolay olmasıdır. İkinci satırda, diff'i birinci açının ağırlığıyla çarpın ve paydadaki 2'yi ağırlıkların toplamıyla değiştirin. açı = (360 + b + (AĞIRLIK [a] * fark / (AĞIRLIK [a] + AĞIRLIK [b]))) mod 360
2019

15

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.

Açı karşılaştırmaları

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

Eğri

İş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ı.


Mitsuta yöntemi aslında başlangıç ​​açısı + başlangıç ​​açısından dönüşlerin ortalamasını verir. Öyleyse benzer bir yöntem elde etmek için, ölçüm hatasını hesaplamak için, meydana gelen rotasyonlara bakmanız ve bunlar için hatayı tahmin etmeniz gerekir. Bence onlar için bir hata tahmin edebilmek için rotasyonlar için bir dağıtıma ihtiyacınız olacak.
Nimble

6

Ortalamayı daha doğru tanımlamalısınız . İki açının özel durumu için, iki farklı senaryo düşünebilirim:

  1. "Gerçek" ortalama, yani (a + b) /% 2 360.
  2. Aynı yarım daire içinde kalırken diğer ikisi "arasını" gösteren açı, örneğin 355 ve 5 için, bu 180 değil, 0 olacaktır. Bunu yapmak için, iki açı arasındaki farkın 180'den büyük olup olmadığını kontrol etmeniz gerekir. ya da değil. Öyleyse, yukarıdaki formülü kullanmadan önce küçük açıyı 360 artırın.

Yine de ikiden fazla açıdan ikinci alternatifin nasıl genelleştirilebileceğini anlamıyorum.


Soru açılarla ilgili olsa da, ortalama yön olarak düşünülmesi daha iyidir ve yaygın bir navigasyon sorunudur.
SmacL

İyi puan David. Örneğin, 180º'lik bir açı ve 540º'lik bir açının ortalaması nedir? 360 ° mi yoksa 180 ° mi?
Baltimark

3
@Baltimark, ne yaptığınıza bağlı sanırım. Navigasyonu ise, muhtemelen ikincisi. Süslü bir snowboard atlayışıysa, belki eski;)
SmacL

Yani 1 ve 359'un "gerçek" ortalaması (360/2)% 360 = 180 ?? Bence değil.
Sente'de

1
@Die in Sente: sayısal olarak konuşursak, kesinlikle. Örneğin, açılar yönleri değil dönüşleri temsil ediyorsa, o zaman ortalama 359 ve 1 kesinlikle 180'dir. Bu tamamen bir yorumlama meselesidir.
David Hanak

5

Kayan nokta veya trigonometri yetenekleri olmayan bir mikrodenetleyici ile kullandığım bir yöntemi paylaşmak istiyorum. Varyasyonları düzeltmek için hala 10 ham rulman okumasının "ortalamasını" almam gerekiyordu.

  1. İlk kerterizin 270-360 veya 0-90 derece aralığında olup olmadığını kontrol edin (kuzey iki kadran)
  2. Eğer öyleyse, bunu ve sonraki tüm okumaları 180 derece döndürün ve tüm değerleri 0 <= yön <360 aralığında tutun. Aksi takdirde, okumaları geldikçe alın.
  3. 10 okuma yapıldıktan sonra, herhangi bir sarma olmadığını varsayarak sayısal ortalamayı hesaplayın
  4. 180 derece dönüş geçerli olsaydı, hesaplanan ortalamayı 180 derece döndürerek "gerçek" yöne dönün.

İdeal değil; kırılabilir. Bu durumdan kurtuldum çünkü cihaz çok yavaş dönüyor. Başkalarının kendilerini benzer kısıtlamalar altında çalışırken bulması durumunda oraya koyacağım.


4

Tüm ortalamalar gibi, cevap da metrik seçimine bağlıdır. Belirli bir M ölçüsü için, [1, N] 'deki k için [-pi, pi]' deki bazı a_k açılarının ortalaması, d ^ 2_M (a_M, a_k) kare mesafelerinin toplamını en aza indiren a_M açısıdır. Ağırlıklı bir ortalama için, w_k ağırlıkları toplamına dahil edilir (öyle ki sum_k w_k = 1). Yani,

a_M = arg min_x toplamı_k w_k d ^ 2_M (x, a_k)

İki yaygın metrik seçeneği, Frobenius ve Riemann ölçümleridir. Frobenius metriği için, döngüsel istatistiklerde normal ortalama yatak kavramına karşılık gelen doğrudan bir formül mevcuttur. Ayrıntılar için bkz. "Rotasyon Grubundaki Ortalamalar ve Ortalama Alma", Maher Moakher, SIAM Journal on Matrix Analysis and Applications, Cilt 24, Sayı 1, 2002.
http://link.aip.org/link/?SJMAEL/24/1/1

İşte GNU Octave 3.2.4 için hesaplama yapan bir fonksiyon:

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/'.

4

İşte tam çözüm: (girdi, derece cinsinden bir yatak dizisidir (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;
}

Bu problem beni bir süredir şaşırttı, çözümünüz çalışıyor (Arduino kullanarak, kodunuzda birkaç değişiklik var ama fazla bir şey yok), her 50 ms'de bir pusula okuma ve okuma yapıyorum ve 16 x okuma dizisine kaydediyorum, daha sonra kullanıyorum Yukarıdaki fonksiyonunuzda, 0-360 etrafı sarma sorunu çözüldü! teşekkürler :)
Andology

3

İngilizcede:

  1. Tüm açıları 180 kaydırılmış ikinci bir veri kümesi yapın.
  2. Her iki veri kümesinin varyansını alın.
  3. En küçük varyansa sahip veri kümesinin ortalamasını alın.
  4. Bu ortalama kaydırılmış kümedense, yanıtı tekrar 180 kaydırın.

Python'da:

#Numpy NX1 açı dizisi

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

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

Bu, trigonometrik fonksiyonlar olmadan nihai sonuca ulaşmanın harika bir yoludur, basit ve uygulanması kolaydır.
Ian Mercer

bu, herhangi bir dairesel veri aralığı için işe yarar; sadece dairesel aralığın yarısı kadar kaydırın; mükemmel cevap!
Kaptan Fantastik

3

Python'da, [-180, 180) arasındaki açılarla

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)

Detaylar:

Ortalama iki açı için vardır iki ortalama 180 ° ayrı olan, ancak yakın ortalama isteyebilir.

Görsel olarak, mavi ( b ) ve yeşilin ( a ) ortalaması deniz mavisi noktasını verir:

Orijinal

Açılar 'etrafını sarar' (örneğin, 355 + 10 = 5), ancak standart aritmetik bu dallanma noktasını yok sayacaktır. Ancak, b açısı dallanma noktasının tersi ise, ( b + g ) / 2 en yakın ortalamayı verir: deniz mavisi noktası.

Herhangi iki açı için, sorunu, açılardan biri dallanma noktasına zıt olacak şekilde döndürebilir, standart ortalama gerçekleştirebilir ve sonra geri döndürebiliriz.

döndürülmüşiade


2

Karmaşık sayıları kullanarak vektör yoluna giderdim. Örneğim, yerleşik karmaşık sayılara sahip Python'da:

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'un geçici yeni bir vektör listesi oluşturmasına gerek olmadığını unutmayın , yukarıdakilerin tümü tek adımda yapılabilir; Diğer diller için de geçerli olan sözde kodu yaklaşık olarak bu yolu seçtim.


2

İşte eksiksiz bir C ++ çözümü:

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

Bir çift vektör biçiminde açıları alır ve ortalamayı basitçe bir çift olarak döndürür. Açılar derece cinsinden olmalı ve tabii ki ortalama da derece cinsindendir.


avgCosx bileşenlerinin avgSinortalaması ve y bileşenlerinin ortalamasıdır. Arktanjant işlevinin parametreleri şunlardır atan2( y, x ). Öyleyse, kodunuz bunun yerine şöyle olmamalıdır: atan2( avgSin, avgCos ) ??
Mike Finch

Bu algoritmayı bir yerden aldım, kendim bulmadım, bu yüzden olduğu gibi doğru olduğunu varsayıyorum. Ayrıca doğru sonuçlar da verir.
adam10603

2

Alnitak'ın cevabına dayanarak, birden çok açının ortalamasını hesaplamak için bir Java yöntemi yazdım:

Açılarınız radyan cinsindeyse:

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

Açılarınız derece cinsindeyse:

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

1

İşte bir fikir: Birbirine en yakın açıların ortalamasını her zaman hesaplayarak ve bir ağırlık koruyarak ortalamayı yinelemeli olarak oluşturun.

Başka bir fikir: Verilen açılar arasındaki en büyük boşluğu bulun. Onu ikiye bölen noktayı bulun ve ardından ortalamayı hesaplamak için referans sıfır olarak çemberdeki zıt noktayı seçin.


Cevabımı önermiyorum, bunun yerine starblue'nun yüksek dereceli cevabı. Buradaki temel gözlem, pusulanın merkezini 0,0 noktası olarak düşünmektir.
John waffle ile

1

Bu açıları çemberin çevresindeki noktalarla temsil edelim.

Tüm bu noktaların çemberin aynı yarısına denk geldiğini varsayabilir miyiz? (Aksi takdirde, "ortalama açıyı" tanımlamanın bariz bir yolu yoktur. Çapta iki nokta düşünün, örneğin 0 derece ve 180 derece --- ortalama 90 derece veya 270 derece mi? 3 veya daha fazlasına sahip olduğumuzda ne olur eşit olarak dağılmış noktalar?)

Bu varsayımla, "başlangıç" olarak bu yarım daire üzerinde rastgele bir nokta seçeriz ve bu orijine göre verilen açı kümesini ölçeriz (buna "göreli açı" diyoruz). Bağıl açının mutlak değeri kesinlikle 180 dereceden az olduğuna dikkat edin. Son olarak, istenen ortalama açıyı elde etmek için bu göreceli açıların ortalamasını alın (tabii ki kökenimize göre).


1

Tek bir "doğru cevap" yok. Kapsamlı bir analiz için KV Mardia ve PE Jupp, "Yön İstatistikleri" (Wiley, 1999) adlı kitabı okumanızı tavsiye ederim.


1

(Sadece Tahmin Teorisi veya İstatistiksel Çıkarımdan bakış açımı paylaşmak istiyorum)

Nimble'ın denemesi, bir dizi açının MMSE ^ tahminini elde etmektir, ancak "ortalama" bir yön bulma seçeneklerinden biridir; ayrıca bir MMAE ^ tahmini veya "ortalama" yön için başka bir tahmin de bulunabilir ve bu, metriğin ölçüm yön hatasına bağlıdır; veya daha genel olarak tahmin teorisinde, maliyet fonksiyonunun tanımı.

^ MMSE / MMAE, minimum ortalama kare / mutlak hataya karşılık gelir.

ackb, "phi_avg ortalama açısı sum_i | phi_avg-phi_i | ^ 2 minimum hale gelme özelliğine sahip olmalıdır ... bir şeyi ortalarlar, ancak açıları değil"

---- hataları ortalama kare anlamında ölçüyorsunuz ve bu en yaygın yollardan biri, ancak tek yol değil. Buradaki çoğu insanın tercih ettiği cevap (yani, birim vektörlerin toplamı ve sonucun açısını alın) aslında makul çözümlerden biridir. Vektörlerin yönleri von Mises dağılımı olarak modellenmişse, istediğimiz "ortalama" yön olarak hizmet eden ML tahmincisidir (kanıtlanabilir). Bu dağıtım süslü değildir ve sadece 2D bir Guassian'dan periyodik olarak örneklenmiş bir dağıtımdır. Bkz. Eqn. (2.179) Bishop'un "Örüntü Tanıma ve Makine Öğrenimi" kitabında. Yine, hiçbir şekilde "ortalama" yönü temsil eden tek en iyisi bu değildir, ancak hem iyi teorik gerekçelere hem de basit uygulamaya sahip olan oldukça mantıklıdır.

Nimble, "ackb, bu vektör tabanlı çözümlerin açıların gerçek ortalamaları olarak kabul edilemeyeceği konusunda haklı, bunlar yalnızca birim vektör karşılıklarının ortalamasıdır" dedi.

----Bu doğru değil. "Birim vektör karşılıkları", bir vektörün yön bilgisini ortaya çıkarır. Açı, vektörün uzunluğunu dikkate almayan bir niceliktir ve birim vektör, uzunluğun 1 olduğu gibi ek bilgi içeren bir şeydir. "Birim" vektörünüzü 2 uzunluğunda tanımlayabilirsiniz, gerçekten önemli değil.


1

İşte hareketli ortalamaları kullanan ve değerleri normalleştirmeye özen gösteren tamamen aritmetik bir çözüm. Hızlıdır ve tüm açılar dairenin bir tarafındaysa (birbirlerinin 180 ° dahilinde) doğru yanıtlar verir.

Değerleri aralığa (0, 180) kaydıran ofseti eklemeye, ortalamayı hesaplamaya ve ardından ofseti çıkarmaya matematiksel olarak eşdeğerdir.

Yorumlar, belirli bir değerin herhangi bir zamanda hangi aralığı alabileceğini açıklar.

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

1

Partiye çok geç kaldım ama kesin bir cevap bulamadığım için 2 sent değerimi ekleyeceğimi düşündüm. Sonunda Mitsuta yönteminin aşağıdaki Java sürümünü uyguladım ve umarım bu basit ve sağlam bir çözüm sağlar. Özellikle Standart Sapma hem bir ölçü dağılımı sağladığından hem de sd == 90 ise, giriş açılarının belirsiz bir ortalamaya neden olduğunu belirtir.

DÜZENLEME: Aslında, orijinal uygulamamın daha da basitleştirilebileceğini, aslında diğer yanıtlarda devam eden tüm konuşma ve trigonometri düşünüldüğünde endişe verici derecede basit olduğunu fark ettim.

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

... ve oradaki tüm (Java) meraklıları için, ortalama açıyı bir satırda elde etmek için yukarıdaki yaklaşımı kullanabilirsiniz.

Arrays.stream(angles).map(angle -> angle<180 ? angle: (angle-360)).sum() / angles.length;

Mitsuda yönteminden bir şeyi kaçırdığına inanıyorum. Lütfen Lior Kogan stackoverflow.com/a/1828222/9265852
kykzk46

0

Alnitak doğru çözüme sahiptir. Nick Fortescue'nun çözümü işlevsel olarak aynıdır.

Özel durum için nerede

(sum (x_component) = 0.0 && sum (y_component) = 0.0) // örn. 2 açı 10. ve 190. ea.

toplam olarak 0,0 derece kullanın

Atan2 (0., 0.) tanımsız olduğundan ve bir hata oluşturacağından, sayısal olarak bu durumu test etmelisiniz.


glibc'de 'atan2' (0, 0) için tanımlanmıştır - sonuç 0'dır
Alnitak

0

Ortalama açı phi_avg, sum_i | phi_avg-phi_i | ^ 2 minimum hale gelme özelliğine sahip olmalıdır, burada fark [-Pi, Pi) olmalıdır (çünkü tersine gitmek daha kısa olabilir!). Bu, tüm giriş değerlerini [0, 2Pi) 'ye normalleştirerek, ortalama bir phi_run sürdürerek ve normalleştirmeyi | phi_i-phi_run | [-Pi, Pi) 'ye (2Pi ekleyerek veya çıkararak). Yukarıdaki önerilerin çoğu , bu minimum özelliğe sahip olmayan başka bir şey yapar , yani bir şeyi ortalarlar , ancak açıları değil.


0

@David_Hanak'tan gelen cevabın yardımıyla sorunu çözdüm. Dediği gibi:

Aynı yarım daire içinde kalırken diğer ikisi "arasını" gösteren açı, örneğin 355 ve 5 için, bu 180 değil, 0 olacaktır. Bunu yapmak için, iki açı arasındaki farkın 180'den büyük olup olmadığını kontrol etmeniz gerekir. ya da değil. Öyleyse, yukarıdaki formülü kullanmadan önce küçük açıyı 360 artırın.

Yaptığım şey tüm açıların ortalamasını hesaplamaktı. Ve bundan daha küçük olan tüm açılar, onları 360 artırın. Sonra hepsini ekleyerek ve uzunluklarına bölerek ortalamayı yeniden hesaplayın.

        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;

Mükemmel çalışıyor.


0

Python işlevi:

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

0

Bu işlevi Matlab'da kullanabilirsiniz:

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 

Algoritma işe yarıyor gibi görünüyor, ancak gerçekte gerçek dünyada sefil bir şekilde başarısız olabilir. Size verilen açıların tersi yönündeki açı değerlerini verir.
tothphu

0

HERHANGİ bir programlama dili için aşağıdaki bağlantıda bir çözüm ve küçük bir açıklama görebilirsiniz: https://rosettacode.org/wiki/Averages/Mean_angle

Örneğin, C ++ çözümü :

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

Çıktı:

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

Veya Matlab çözümü :

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

0

Starblue'nun cevabı ortalama birim vektörün açısını verirken, 0 ila 2 * pi (veya 0 ° ila) aralığında birden fazla yanıt olabileceğini kabul ederseniz, aritmetik ortalama kavramını açılara genişletmek mümkündür. 360 °). Örneğin, 0 ° ve 180 ° 'nin ortalaması 90 ° veya 270 ° olabilir.

Aritmetik ortalama, giriş değerlerine olan minimum kare mesafelerin toplamı ile tek değer olma özelliğine sahiptir. İki birim vektör arasındaki birim çember boyunca mesafe, iç çarpımlarının ters kosinüsü olarak kolayca hesaplanabilir. Vektörümüzün iç çarpımının ve her bir girdi birim vektörünün ters kosinüsünün karesinin toplamını en aza indirerek bir birim vektör seçersek, o zaman eşdeğer bir ortalamamız olur. Yine, istisnai durumlarda iki veya daha fazla minimum olabileceğini unutmayın.

Bu kavram herhangi bir sayıda boyuta genişletilebilir, çünkü birim küre boyunca mesafe, birim çember boyunca olan mesafeyle tam olarak aynı şekilde hesaplanabilir - iki birim vektörün iç çarpımının ters kosinüsü.

Daireler için bu ortalamayı birkaç şekilde çözebiliriz, ancak aşağıdaki O (n ^ 2) algoritmasını öneriyorum (açılar radyan cinsindendir ve birim vektörleri hesaplamaktan kaçınıyorum):

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

Tüm açılar birbirine 180 ° dahilindeyse, daha basit bir O (n) + O (sıralama) algoritması kullanabiliriz (yine radyan kullanarak ve birim vektörlerin kullanımından kaçınarak):

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

Dereceleri kullanmak için pi'yi 180 ile değiştirin. Daha fazla boyut kullanmayı planlıyorsanız, büyük olasılıkla ortalamayı çözmek için yinelemeli bir yöntem kullanmanız gerekecektir.


0

Sorun son derece basit. 1. Tüm açıların -180 ile 180 derece arasında olduğundan emin olun. 2. a Negatif olmayan tüm açıları toplayın, ortalamalarını alın ve kaç tane COUNT 2. b. Tüm negatif açıları ekleyin, ortalamalarını alın ve kaç tane COUNT alın. 3. pos_average eksi neg_average farkını alın Eğer fark 180'den büyükse, farkı 360 eksi fark olarak değiştirin. Aksi takdirde, sadece farkın işaretini değiştirin. Farkın her zaman negatif olmadığını unutmayın. Average_Angle, pos_ortage artı fark çarpı "ağırlık", negatif sayının negatif ve pozitif sayım toplamına bölünmesiyle elde edilir


0

İşte ortalama açılara bazı java kodları, bence oldukça sağlam.

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

0

Buna bir JavaScript çözümü arayan biri varsa , wikipedia sayfasında döngüsel miktarların ortalaması ( Nick'in cevabında da bahsedilmiştir ) verilen örneği , mathjs kitaplığından yardım alarak JavaScript / NodeJS koduna çevirdim. .

Açılarınız derece cinsindeyse :

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

Bir dizi pusula yönünden ortalama yönü bulmak için bu çözüm benim için gerçekten işe yaradı . Bunu çok çeşitli yön verileri (0-360 derece) üzerinde test ettim ve çok sağlam görünüyor.

Alternatif olarak, açılarınız radyan cinsindeyse :

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

Umarım bu çözümler benim için benzer bir programlama sorunuyla karşı karşıya kalan birine yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.