คุณจะคำนวณค่าเฉลี่ยของชุดข้อมูลวงกลมได้อย่างไร


147

ฉันต้องการคำนวณค่าเฉลี่ยของชุดข้อมูลแบบวงกลม ตัวอย่างเช่นฉันอาจมีหลายตัวอย่างจากการอ่านเข็มทิศ ปัญหาของหลักสูตรคือวิธีจัดการกับปัญหาที่เกิดขึ้น อัลกอริทึมเดียวกันอาจมีประโยชน์สำหรับหน้าปัดนาฬิกา

คำถามจริงมีความซับซ้อนมากขึ้น - สถิติหมายถึงอะไรในทรงกลมหรือในพื้นที่พีชคณิตซึ่ง "ล้อมรอบ" เช่นกลุ่มเสริม mod n คำตอบอาจไม่ซ้ำกันเช่นค่าเฉลี่ยของ 359 องศาและ 1 องศาอาจเป็น 0 องศาหรือ 180 แต่สถิติ 0 ดูดีกว่า

นี่เป็นปัญหาการเขียนโปรแกรมจริงสำหรับฉันและฉันพยายามทำให้มันดูไม่เหมือนปัญหาคณิตศาสตร์


1
โดยเฉลี่ยแล้วฉันถือว่าคุณต้องการแบริ่งเฉลี่ย มุมมีอยู่ระหว่างสองบรรทัดตลับลูกปืนคือทิศทางของเส้นเดียว ในกรณีนี้ starblue พูดถูก
SmacL

@Nick Fortescue: คุณสามารถอัปเดตคำถามของคุณให้มีความเฉพาะเจาะจงมากขึ้น: คุณหมายถึงมุมหรือมุมหรือไม่?
มิทช์ข้าวสาลี

1
จริง ๆ แล้วฉันต้องการบางสิ่งที่ซับซ้อนกว่าเล็กน้อย (แต่คล้ายกับตลับลูกปืน) และพยายามทำให้คำถามง่ายขึ้นและทำให้ซับซ้อนขึ้นตามปกติ ผมพบคำตอบที่ฉันต้องการที่catless.ncl.ac.uk/Risks/7.44.html#subj4 ฉันจะแก้ไข qn อีกครั้ง
Nick Fortescue

ความเสี่ยงที่คำตอบนั้นเป็นสิ่งที่ผมเสนอยกเว้นว่ามันอาจจะประสบปัญหาเมื่อตัวหารเป็น 0
starblue

บทความที่น่าสนใจเกี่ยวกับความหมายของมุม: twistedoakstudios.com/blog/?p=938
starblue

คำตอบ:


99

คำนวณเวกเตอร์หน่วยจากมุมและหามุมของค่าเฉลี่ย


8
นั่นไม่ได้ผลหากเวกเตอร์ยกเลิกซึ่งกันและกัน ค่าเฉลี่ยยังคงมีความหมายในกรณีนี้ขึ้นอยู่กับคำจำกัดความที่แน่นอน
David Hanak

21
@ David, ทิศทางเฉลี่ยของสองตลับลูกปืน 180 องศานั้นไม่ได้ถูกกำหนดไว้ สิ่งนี้ไม่ได้ทำให้คำตอบของ Starblue ผิด แต่เป็นกรณีพิเศษที่เกิดขึ้นกับปัญหาทางธรณีวิทยาหลายประการ
SmacL

5
@smacl: ฉันเห็นด้วยถ้ามุมเป็นตัวแทนของทิศทาง แต่ถ้าคุณคิดถึงจำนวนเชิงซ้อนตัวอย่างเช่นและนิยามค่าเฉลี่ยว่า "อะไรคืออาร์กิวเมนต์ของ c เช่นนั้น c c == a b" โดยที่ a และ b มีโมดูลัสของ 1 แล้วค่าเฉลี่ยของ 0 และ 180 คือ 90
David Hanak

3
ดูเพิ่มเติมmath.stackexchange.com/questions/14530/…
starblue

5
@PierreBdR: ถ้าฉันใช้สองขั้นตอนในทิศทาง 0deg และหนึ่งทิศทาง 90deg ฉันจะย้ายไปในทิศทาง 26.56 องศาเทียบกับตำแหน่งที่ฉันเริ่ม ในแง่นี้ 26.56 มีความหมายมากกว่าเพราะทิศทางเฉลี่ย {0,0,90} องศามากกว่า 30 องศา ค่าเฉลี่ยพีชคณิตเป็นเพียงหนึ่งในค่าเฉลี่ยที่เป็นไปได้จำนวนมาก (ดูen.wikipedia.org/wiki/Mean ) - และดูเหมือนว่าไม่เกี่ยวข้องมากสำหรับวัตถุประสงค์ของทิศทางเฉลี่ย (เช่นเดียวกับที่คนอื่น ๆ ทำ)
Janus

60

คำถามนี้มีการตรวจสอบโดยละเอียดในหนังสือ: "สถิติเกี่ยวกับดาวฤกษ์", Geoffrey S. Watson, บันทึกการบรรยายของมหาวิทยาลัยอาร์คันซอในวิชาคณิตศาสตร์, 1983 John Wiley & Sons, Inc. ตามที่กล่าวไว้ที่http: //catless.ncl ac.uk/Risks/7.44.html#subj4โดย Bruce Karsh

วิธีที่ดีในการประมาณมุมเฉลี่ย A จากชุดการวัดมุม a [i] 0 <= i

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

วิธีการของ starblue นั้นเทียบเท่ากับการคำนวณ แต่เหตุผลของเขานั้นชัดเจนและมีประสิทธิภาพมากกว่าโดยทางโปรแกรมและทำงานได้ดีในกรณีที่เป็นศูนย์ดังนั้นขอชื่นชมเขา

ตอนนี้ผู้สำรวจได้ศึกษารายละเอียดเพิ่มเติมเกี่ยวกับ Wikipediaและการใช้งานอื่น ๆ เช่นชิ้นส่วน


8
ซึ่งก็เหมือนกันกับอัลกอริทึมที่ฉันโพสต์ในเวลาเดียวกันกับคุณ คุณจะต้องใช้ atan2 มากกว่า Atan ธรรมดา แต่เนื่องจากมิฉะนั้นคุณจะไม่สามารถบอกได้ว่า Quadrant คำตอบคือใน.
Alnitak

คุณยังสามารถท้ายด้วยคำตอบที่ไม่แน่นอน เช่นเดียวกับในตัวอย่าง 0, 180 ดังนั้นคุณยังต้องตรวจสอบเคสขอบ นอกจากนี้มักจะมีฟังก์ชั่น atan2 ซึ่งอาจจะเร็วกว่าในเคสของคุณ
Loki

50

ฉันเห็นปัญหา - เช่นถ้าคุณมีมุม 45 'และมุม 315' ค่าเฉลี่ย "ธรรมชาติ" จะเท่ากับ 180 'แต่ค่าที่คุณต้องการคือ 0'

ฉันคิดว่าสตาร์บลูเป็นบางสิ่งบางอย่าง เพียงคำนวณพิกัดคาร์ทีเซียน (x, y) สำหรับแต่ละมุมแล้วเพิ่มเวกเตอร์ที่ได้มาเหล่านั้นเข้าด้วยกัน การชดเชยเชิงมุมของเวกเตอร์สุดท้ายควรเป็นผลลัพธ์ที่คุณต้องการ

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

ตอนนี้ฉันไม่สนใจว่าเข็มทิศส่วนหัวเริ่มต้นที่ทิศเหนือและหมุนตามเข็มนาฬิกาในขณะที่พิกัดคาร์ทีเซียนแบบ "ปกติ" เริ่มต้นที่ศูนย์ตามแนวแกน X จากนั้นหมุนทวนเข็มนาฬิกา คณิตศาสตร์ควรทำงานในลักษณะเดียวกันโดยไม่คำนึงถึง


13
ห้องสมุดคณิตศาสตร์ของคุณอาจใช้เรเดียนสำหรับมุม อย่าลืมแปลง
Martin Beckett

2
บางทีมันอาจจะสายเกินไปในตอนกลางคืน แต่ใช้ตรรกะนี้ฉันก็ได้มุมเฉลี่ย 341.8947 ... แทนที่จะเป็น 342 สำหรับมุมของ [320, 330, 340, 350, 10,] ใครเห็นผิดพลาดของฉัน
Alex Robinson

1
@AlexRobinson ไม่ใช่ตัวพิมพ์ผิดมันเป็นเพราะมุมสุดท้ายเป็นเพียงมุมมองสุดท้ายที่ได้จากการทำตามขั้นตอนในแต่ละมุม
Alnitak

1
@AlexRobinson ที่จะเฉพาะเจาะจงมากขึ้น: cos(), sin()และatan2()ให้การประมาณ (คนดี แต่ยังคงปิดโดย 1 หรือ 2 ulps) ดังนั้นยิ่งคุณเฉลี่ยข้อผิดพลาดมากกว่าที่คุณ ได้แก่
Matthieu

23

สำหรับกรณีพิเศษของสอง ANGLES:

คำตอบ((A + B) mod 360) / 2คือผิด สำหรับมุม 350 และ 2 จุดที่ใกล้ที่สุดคือ 356 ไม่ใช่ 176

โซลูชันเวกเตอร์และตรีโกณมิติอาจมีราคาแพงเกินไป

สิ่งที่ฉันได้จากการซ่อมแซมเล็กน้อยคือ:

diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180
angle = (360 + b + ( diff / 2 ) ) mod 360
  • 0, 180 -> 90 (สองคำตอบสำหรับสิ่งนี้: สมการนี้ใช้คำตอบตามเข็มนาฬิกาจาก a)
  • 180, 0 -> 270 (ดูด้านบน)
  • 180, 1 -> 90.5
  • 1, 180 -> 90.5
  • 20, 350 -> 5
  • 350, 20 -> 5 (ตัวอย่างต่อไปนี้กลับด้านทั้งหมดถูกต้องเช่นกัน)
  • 10, 20 -> 15
  • 350, 2 -> 356
  • 359, 0 -> 359.5
  • 180, 180 -> 180

ต่อไปนี้จะได้รับการเพิ่มประสิทธิภาพโดยใช้ BAMS นี้: stackoverflow.com/questions/1048945/...
ดาร์รอน

ไม่เลว. บรรทัดแรกคำนวณมุมสัมพัทธ์ของ a เทียบกับ b ในช่วง [-180, 179] ส่วนที่สองคำนวณมุมกลางจากนั้น ฉันจะใช้ b + diff / 2 แทน a - diff / 2 เพื่อความชัดเจน
starblue

1
ฉันพลาดอะไรไปรึเปล่า? ผมทำจะได้รับ 295
ดาร์รอน

อ่าฉันเข้าใจแล้ว ตัวดำเนินการ mod Matlab ของ wraps -10 ถึง 350 ฉันจะเปลี่ยนรหัส มันเป็นเรื่องง่าย ๆ เพิ่มเติม 360
darron

อีกคุณสมบัติที่ดีของวิธีนี้คือมันง่ายที่จะใช้ค่าเฉลี่ยถ่วงน้ำหนักของทั้งสองมุม ในบรรทัดที่สองให้คูณความแตกต่างกับน้ำหนักของมุมแรกและแทนที่ 2 ในส่วนด้วยผลรวมของน้ำหนัก มุม = (360 + b + (น้ำหนัก [a] * diff / (น้ำหนัก [a] + น้ำหนัก [b])))) mod 360
oosterwal

14

ackb นั้นถูกต้องว่าโซลูชันแบบเวกเตอร์เหล่านี้ไม่สามารถพิจารณาค่าเฉลี่ยจริงของมุมได้พวกมันเป็นเพียงค่าเฉลี่ยของหน่วยเวกเตอร์หน่วย อย่างไรก็ตามวิธีการแก้ปัญหาที่แนะนำของ ackb นั้นไม่ได้มีเสียงทางคณิตศาสตร์

ต่อไปนี้เป็นวิธีการแก้ปัญหาที่ได้มาทางคณิตศาสตร์จากเป้าหมายของการย่อเล็กสุด (angle [i] - avgAngle) ^ 2 (ซึ่งความแตกต่างได้รับการแก้ไขหากจำเป็น) ซึ่งทำให้มันเป็นค่าเฉลี่ยเลขคณิตจริงของมุม

อันดับแรกเราต้องดูให้แน่ชัดว่ากรณีใดความแตกต่างระหว่างมุมต่างกับความแตกต่างระหว่างจำนวนปกติ พิจารณามุม x และ y ถ้า y> = x - 180 และ y <= x + 180 จากนั้นเราสามารถใช้ความแตกต่าง (xy) โดยตรง มิฉะนั้นหากเงื่อนไขแรกไม่เป็นไปตามนั้นเราจะต้องใช้ (y + 360) ในการคำนวณแทน y หากเงื่อนไขที่สองไม่เป็นไปตามเงื่อนไขเราต้องใช้ (y-360) แทน y เนื่องจากสมการของเส้นโค้งเราจึงลดการเปลี่ยนแปลงเฉพาะจุดที่ความไม่เท่าเทียมกันเหล่านี้เปลี่ยนจากจริงเป็นเท็จหรือในทางกลับกันเราสามารถแยกช่วง [0,360) ทั้งหมดออกเป็นชุดเซ็กเมนต์โดยคั่นด้วยจุดเหล่านี้ จากนั้นเราจะต้องค้นหาค่าต่ำสุดของแต่ละส่วนเหล่านี้แล้วเลือกค่าต่ำสุดของค่าต่ำสุดของแต่ละส่วนซึ่งก็คือค่าเฉลี่ย

นี่คือภาพที่แสดงให้เห็นถึงปัญหาที่เกิดขึ้นในการคำนวณความแตกต่างของมุม หาก x อยู่ในพื้นที่สีเทาแสดงว่ามีปัญหา

การเปรียบเทียบมุม

ในการลดตัวแปรให้น้อยที่สุดขึ้นอยู่กับเส้นโค้งเราสามารถหาอนุพันธ์ของสิ่งที่เราต้องการย่อให้เล็กที่สุดแล้วหาจุดเปลี่ยน (ซึ่งเป็นที่มาของอนุพันธ์ = 0)

ที่นี่เราจะใช้แนวคิดของการลดความแตกต่างกำลังสองเพื่อให้ได้สูตรทางคณิตศาสตร์ทั่วไป: sum (a [i]) / n เส้นโค้ง y = ผลรวม ((a [i] -x) ^ 2) สามารถย่อให้เล็กสุดด้วยวิธีนี้:

y = sum((a[i]-x)^2)
= sum(a[i]^2 - 2*a[i]*x + x^2)
= sum(a[i]^2) - 2*x*sum(a[i]) + n*x^2

dy\dx = -2*sum(a[i]) + 2*n*x

for dy/dx = 0:
-2*sum(a[i]) + 2*n*x = 0
-> n*x = sum(a[i])
-> x = sum(a[i])/n

ตอนนี้ใช้มันกับส่วนโค้งที่มีความแตกต่างที่ปรับแล้วของเรา:

b = เซตย่อยของจุดที่ความแตกต่างที่ถูกต้อง (มุม) a [i] -xc = ชุดย่อยของตำแหน่งที่ความแตกต่างที่ถูกต้อง (มุม) (a [i] -360) -x cn = ขนาดของ cd = ชุดย่อยของตำแหน่งที่ ความแตกต่างที่ถูกต้อง (เชิงมุม) (a [i] +360) -x dn = ขนาด d

y = sum((b[i]-x)^2) + sum(((c[i]-360)-b)^2) + sum(((d[i]+360)-c)^2)
= sum(b[i]^2 - 2*b[i]*x + x^2)
  + sum((c[i]-360)^2 - 2*(c[i]-360)*x + x^2)
  + sum((d[i]+360)^2 - 2*(d[i]+360)*x + x^2)
= sum(b[i]^2) - 2*x*sum(b[i])
  + sum((c[i]-360)^2) - 2*x*(sum(c[i]) - 360*cn)
  + sum((d[i]+360)^2) - 2*x*(sum(d[i]) + 360*dn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*(sum(b[i]) + sum(c[i]) + sum(d[i]))
  - 2*x*(360*dn - 360*cn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*sum(x[i])
  - 2*x*360*(dn - cn)
  + n*x^2

dy/dx = 2*n*x - 2*sum(x[i]) - 2*360*(dn - cn)

for dy/dx = 0:
2*n*x - 2*sum(x[i]) - 2*360*(dn - cn) = 0
n*x = sum(x[i]) + 360*(dn - cn)
x = (sum(x[i]) + 360*(dn - cn))/n

เพียงอย่างเดียวนี้ไม่เพียงพอที่จะรับค่าต่ำสุดในขณะที่ทำงานกับค่าปกติที่มีชุดที่ไม่ได้ จำกัด ดังนั้นผลลัพธ์จะอยู่ในช่วงของชุดและแน่นอนจึงถูกต้อง เราต้องการขั้นต่ำภายในช่วง (กำหนดโดยกลุ่ม) หากค่าต่ำสุดนั้นน้อยกว่าขอบเขตล่างของเซกเมนต์ของเราดังนั้นค่าต่ำสุดของเซกเมนต์นั้นจะต้องอยู่ที่ขอบเขตล่าง (เพราะเส้นโค้งกำลังสองมีเพียง 1 จุดหักเห) และถ้าค่าต่ำสุดมากกว่าขอบเขตบนของเซ็กเมนต์ของเรา ขอบบน หลังจากที่เรามีค่าต่ำสุดสำหรับแต่ละเซกเมนต์เราก็จะหาอันที่มีค่าต่ำสุดสำหรับสิ่งที่เรากำลังย่อให้เล็กสุด (ผลรวม ((b [i] -x) ^ 2) + ผลรวม (((c [i] -360 ) -b) ^ 2) + ผลรวม (((d [i +360) -c) ^ 2))

นี่คือรูปภาพของเส้นโค้งซึ่งแสดงให้เห็นว่ามันเปลี่ยนแปลงไปอย่างไร ณ จุดที่ x = (a [i] +180)% 360 ชุดข้อมูลที่เป็นปัญหาคือ {65,92,230,320,250}

เส้นโค้ง

นี่คือการใช้อัลกอริทึมใน Java รวมถึงการเพิ่มประสิทธิภาพบางอย่างความซับซ้อนของมันคือ O (nlogn) มันสามารถลดลงเป็น O (n) หากคุณแทนที่การจัดเรียงตามการเปรียบเทียบด้วยการเรียงลำดับที่ไม่ใช่การเปรียบเทียบเช่นการเรียงตามฐาน

static double varnc(double _mean, int _n, double _sumX, double _sumSqrX)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX;
}
//with lower correction
static double varlc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            + 2*360*_sumC + _nc*(-2*360*_mean + 360*360);
}
//with upper correction
static double varuc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            - 2*360*_sumC + _nc*(2*360*_mean + 360*360);
}

static double[] averageAngles(double[] _angles)
{
    double sumAngles;
    double sumSqrAngles;

    double[] lowerAngles;
    double[] upperAngles;

    {
        List<Double> lowerAngles_ = new LinkedList<Double>();
        List<Double> upperAngles_ = new LinkedList<Double>();

        sumAngles = 0;
        sumSqrAngles = 0;
        for(double angle : _angles)
        {
            sumAngles += angle;
            sumSqrAngles += angle*angle;
            if(angle < 180)
                lowerAngles_.add(angle);
            else if(angle > 180)
                upperAngles_.add(angle);
        }


        Collections.sort(lowerAngles_);
        Collections.sort(upperAngles_,Collections.reverseOrder());


        lowerAngles = new double[lowerAngles_.size()];
        Iterator<Double> lowerAnglesIter = lowerAngles_.iterator();
        for(int i = 0; i < lowerAngles_.size(); i++)
            lowerAngles[i] = lowerAnglesIter.next();

        upperAngles = new double[upperAngles_.size()];
        Iterator<Double> upperAnglesIter = upperAngles_.iterator();
        for(int i = 0; i < upperAngles_.size(); i++)
            upperAngles[i] = upperAnglesIter.next();
    }

    List<Double> averageAngles = new LinkedList<Double>();
    averageAngles.add(180d);
    double variance = varnc(180,_angles.length,sumAngles,sumSqrAngles);

    double lowerBound = 180;
    double sumLC = 0;
    for(int i = 0; i < lowerAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle > lowerAngles[i]+180)
            testAverageAngle = lowerAngles[i];

        if(testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        lowerBound = lowerAngles[i];
        sumLC += lowerAngles[i];
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*lowerAngles.length)/_angles.length;
        //minimum is inside segment range
        //we will test average 0 (360) later
        if(testAverageAngle < 360 && testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,lowerAngles.length,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double upperBound = 180;
    double sumUC = 0;
    for(int i = 0; i < upperAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle < upperAngles[i]-180)
            testAverageAngle = upperAngles[i];

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        upperBound = upperAngles[i];
        sumUC += upperBound;
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*upperAngles.length)/_angles.length;
        //minimum is inside segment range
        //we test average 0 (360) now           
        if(testAverageAngle < 0)
            testAverageAngle = 0;

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,upperAngles.length,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double[] averageAngles_ = new double[averageAngles.size()];
    Iterator<Double> averageAnglesIter = averageAngles.iterator();
    for(int i = 0; i < averageAngles_.length; i++)
        averageAngles_[i] = averageAnglesIter.next();


    return averageAngles_;
}

ค่าเฉลี่ยเลขคณิตของชุดของมุมอาจไม่เห็นด้วยกับความคิดของคุณว่าค่าเฉลี่ยควรเป็นเท่าไหร่ ตัวอย่างเช่นค่าเฉลี่ยเลขคณิตของชุด {179,179,0,181,181} คือ 216 (และ 144) คำตอบที่คุณคิดในทันทีอาจเป็น 180 อย่างไรก็ตามเป็นที่ทราบกันดีว่าค่าเฉลี่ยเลขคณิตได้รับผลกระทบอย่างมากจากค่าขอบ คุณควรจำไว้ว่ามุมนั้นไม่ใช่เวกเตอร์, น่าดึงดูดอย่างที่อาจดูเหมือนเมื่อต้องรับมือกับมุมในบางครั้ง

แน่นอนว่าอัลกอริธึมนี้ยังนำไปใช้กับปริมาณทั้งหมดที่เชื่อฟังเลขคณิตแบบโมดูลาร์ (ด้วยการปรับค่าน้อยที่สุด) เช่นเวลาของวัน

ฉันอยากจะย้ำด้วยว่าแม้ว่านี่จะเป็นค่าเฉลี่ยมุมที่แท้จริงซึ่งแตกต่างจากวิธีแก้ปัญหาแบบเวกเตอร์ซึ่งไม่ได้แปลว่าเป็นวิธีแก้ปัญหาที่คุณควรใช้ค่าเฉลี่ยของเวกเตอร์หน่วยที่สอดคล้องกันอาจเป็นค่าที่คุณจริง ๆ ควรจะใช้


วิธี Mitsuta ให้มุมเริ่มต้น + ค่าเฉลี่ยของการหมุนจากมุมเริ่มต้น เพื่อให้ได้วิธีที่คล้ายกันการบัญชีสำหรับการวัดความผิดพลาดจากนั้นคุณจะต้องดูการหมุนที่เกิดขึ้นและประเมินข้อผิดพลาดสำหรับสิ่งเหล่านั้น ฉันคิดว่าคุณจะต้องมีการแจกแจงการหมุนเพื่อประเมินข้อผิดพลาดสำหรับพวกเขา
ว่องไว

6

คุณต้องกำหนดค่าเฉลี่ยให้แม่นยำยิ่งขึ้น สำหรับกรณีเฉพาะของสองมุมฉันสามารถนึกถึงสถานการณ์ที่แตกต่างกันสองสถานการณ์:

  1. ค่าเฉลี่ย "ที่แท้จริง" คือ (a + b) / 2% 360
  2. มุมที่ชี้ "ระหว่าง" อีกสองมุมขณะอยู่ในครึ่งวงกลมเดียวกันเช่น 355 และ 5 นี่จะเป็น 0 ไม่ใช่ 180 ถ้าต้องการทำสิ่งนี้คุณต้องตรวจสอบว่าความแตกต่างระหว่างมุมทั้งสองนั้นใหญ่กว่า 180 หรือไม่ หรือไม่. ถ้าเป็นเช่นนั้นให้เพิ่มมุมเล็กลง 360 ก่อนใช้สูตรด้านบน

ฉันไม่เห็นว่าทางเลือกที่สองสามารถวางนัยสำหรับกรณีที่มีมากกว่าสองมุมได้อย่างไร


ในขณะที่คำถามหมายถึงมุมมันเป็นความคิดที่ดีกว่าเป็นทิศทางเฉลี่ยและเป็นปัญหาการนำทางที่พบบ่อย
SmacL

คะแนนที่ดีเดวิด ตัวอย่างเช่นค่าเฉลี่ยของมุม180ºและมุม540ºคือเท่าใด เป็น360ºหรือ180ºหรือไม่
Baltimark

3
@ Ballimark ฉันคิดว่ามันขึ้นอยู่กับสิ่งที่คุณกำลังทำ หากการนำทางอาจหลัง ถ้ามันกระโดดแฟนซีสโนว์บอร์ดอาจจะเป็นอดีต;)
SmacL

ดังนั้นค่าเฉลี่ย "ที่แท้จริง" ของ 1 และ 359 คือ (360/2)% 360 = 180 ?? ผมคิดว่าไม่.
Die in Sente

1
@Die ใน Sente: พูดเป็นตัวเลขแน่นอน ตัวอย่างเช่นหากมุมเป็นตัวแทนไม่ใช่ทิศทางดังนั้นค่าเฉลี่ยของ 359 และ 1 คือ 180 อย่างแน่นอนมันเป็นเรื่องของการตีความ
David Hanak

4

เช่นเดียวกับค่าเฉลี่ยทั้งหมดคำตอบขึ้นอยู่กับตัวเลือกของการวัด สำหรับเมตริก M ที่กำหนดค่าเฉลี่ยของมุม a_k ใน [-pi, pi] สำหรับ k ใน [1, N] คือมุม a_M ซึ่งลดผลรวมของระยะทางกำลังสอง d ^ 2_M (a_M, a_k) สำหรับค่าเฉลี่ยถ่วงน้ำหนักเพียงหนึ่งรวมอยู่ในน้ำหนักรวม w_k (เช่น sum_k w_k = 1) นั่นคือ,

a_M = หาเรื่อง min_x sum_k w_k d ^ 2_M (x, a_k)

ตัวเลือกทั่วไปสองอย่างของตัวชี้วัดคือตัวชี้วัด Frobenius และ Riemann สำหรับตัวชี้วัด Frobenius มีสูตรโดยตรงที่สอดคล้องกับแนวคิดทั่วไปของการแบกในสถิติวงกลม ดู "วิธีการและค่าเฉลี่ยในกลุ่มการหมุนเวียน", เฮอร์โมเคอร์, วารสารสยามเกี่ยวกับการวิเคราะห์และการประยุกต์ใช้เมทริกซ์, เล่มที่ 24, ฉบับที่ 1, 2002, สำหรับรายละเอียด
http://link.aip.org/link/?SJMAEL/24/1/1

นี่คือฟังก์ชั่นสำหรับ GNU Octave 3.2.4 ที่ทำการคำนวณ:

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

ฉันต้องการแบ่งปันวิธีการที่ฉันใช้กับไมโครคอนโทรลเลอร์ซึ่งไม่มีความสามารถในจุดลอยตัวหรือตรีโกณมิติ ฉันยังจำเป็นต้องอ่านค่าแบริ่งดิบ "เฉลี่ย" 10 ครั้งเพื่อให้การเปลี่ยนแปลงราบรื่น

  1. ตรวจสอบว่าตลับลูกปืนแรกอยู่ในช่วง 270-360 หรือ 0-90 องศา (สองภาคเหนือ)
  2. ถ้าเป็นเช่นนั้นให้หมุนค่านี้และค่าที่อ่านต่อ ๆ ไปทั้งหมด 180 องศารักษาค่าทั้งหมดในช่วง 0 <= การแบก <360 มิฉะนั้นให้อ่านค่าตามที่มา
  3. เมื่อมีการอ่าน 10 ครั้งคำนวณค่าเฉลี่ยตัวเลขโดยสมมติว่าไม่มีการพัน
  4. หากการหมุนแบบ 180 องศามีผลบังคับใช้แล้วให้หมุนค่าเฉลี่ยที่คำนวณได้ 180 องศาเพื่อกลับไปใช้ตลับลูกปืนที่ "จริง"

มันไม่เหมาะ มันสามารถทำลาย ฉันได้ไปกับมันในกรณีนี้เพราะอุปกรณ์หมุนช้ามาก ฉันจะเอามันออกไปในกรณีที่คนอื่นพบว่าตัวเองทำงานภายใต้ข้อ จำกัด ที่คล้ายกัน


3

เป็นภาษาอังกฤษ:

  1. สร้างชุดข้อมูลที่สองโดยปรับมุมทุกมุมเป็น 180
  2. รับความแปรปรวนของชุดข้อมูลทั้งสอง
  3. ใช้ค่าเฉลี่ยของชุดข้อมูลที่มีค่าความแปรปรวนน้อยที่สุด
  4. หากค่าเฉลี่ยนี้มาจากชุดที่เลื่อนไปแล้วให้เลื่อนคำตอบอีกครั้งด้วย 180

ในหลาม:

อาร์เรย์มุม #numpy NX1

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

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

นี่เป็นวิธีที่ยอดเยี่ยมในการบรรลุผลลัพธ์ที่ได้โดยไม่ต้องใช้ฟังก์ชั่นตรีโกณมิติมันง่ายและใช้งานง่าย
เอียนเมอร์เซอร์

ใช้ได้กับข้อมูลวงกลมทุกช่วง เพียงแค่เลื่อนครึ่งวงกลม คำตอบที่ดี!
กัปตัน Fantastic

3

นี่คือวิธีการแก้ปัญหาเต็มรูปแบบ: (อินพุตเป็นอาร์เรย์ของแบริ่งเป็นองศา (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;
}

ปัญหานี้ทำให้ฉันงุนงงอยู่ครู่หนึ่งโซลูชันของคุณใช้งานได้ (โดยใช้ Arduino ดังนั้นการเปลี่ยนแปลงรหัสของคุณ แต่ไม่มีอะไรมาก) ฉันกำลังแสดงการอ่านเข็มทิศและการอ่านทุก ๆ 50 มิลลิวินาทีและเก็บไว้ในอาร์เรย์อ่านขนาด 16 x ซึ่งฉันใช้ ในฟังก์ชั่นของคุณด้านบนปัญหา 0-360 สรุปได้รับการแก้ไข! ขอบคุณ :)
Andology

3

ในหลามกับมุมระหว่าง [-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)

รายละเอียด:

สำหรับค่าเฉลี่ยของมุมทั้งสองนั้นจะมีค่าเฉลี่ยสองค่าแยกกัน 180 ° แต่เราอาจต้องการค่าเฉลี่ยที่ใกล้กว่า

สายตาค่าเฉลี่ยของสีน้ำเงิน ( b ) และสีเขียว ( a ) ให้ผลเป็นจุดน้า:

เป็นต้นฉบับ

มุมที่ล้อมรอบ (เช่น 355 + 10 = 5) แต่เลขคณิตมาตรฐานจะไม่สนใจจุดย่อยนี้ อย่างไรก็ตามถ้ามุมbตรงข้ามกับจุดสาขาแล้ว ( b + g ) / 2 ให้ค่าเฉลี่ยที่ใกล้ที่สุด: จุดน้าน

สำหรับสองมุมใด ๆ เราสามารถหมุนปัญหาเพื่อให้มุมใดมุมหนึ่งอยู่ตรงข้ามกับจุดสาขาดำเนินการหาค่าเฉลี่ยมาตรฐานแล้วหมุนกลับ

หมุนกลับ


2

ฉันจะใช้วิธีเวกเตอร์โดยใช้จำนวนเชิงซ้อน ตัวอย่างของฉันเป็น Python ซึ่งมีจำนวนเชิงซ้อนในตัว:

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 ไม่จำเป็นต้องสร้างรายการเวกเตอร์ใหม่ชั่วคราวทั้งหมดข้างต้นสามารถทำได้ในขั้นตอนเดียว ฉันเพิ่งเลือกวิธีนี้เพื่อประมาณรหัสหลอกที่ใช้กับภาษาอื่นด้วย


2

นี่คือโซลูชัน C ++ ที่สมบูรณ์:

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

มันใช้มุมในรูปแบบของเวกเตอร์ของ doubles และส่งกลับค่าเฉลี่ยเพียงเป็น double มุมต้องเป็นหน่วยองศาและแน่นอนว่าค่าเฉลี่ยอยู่ในหน่วยองศาเช่นกัน


avgCosคือค่าเฉลี่ยขององค์ประกอบ x และavgSinเป็นค่าเฉลี่ยขององค์ประกอบ y atan2( y, x )พารามิเตอร์สำหรับฟังก์ชั่นอาร์กแทนเจนต์ที่มี ดังนั้นรหัสของคุณไม่ควรเป็น: atan2( avgSin, avgCos ) ?
Mike Finch

ฉันได้อัลกอริธึมนี้จากที่ไหนสักแห่งฉันไม่ได้มาด้วยตัวเองดังนั้นฉันคิดว่ามันถูกต้องตามที่มันเป็น รวมทั้งให้ผลลัพธ์ที่ถูกต้องเช่นกัน
adam10603

2

ตามคำตอบของ Alnitakฉันได้เขียนวิธี Java สำหรับการคำนวณค่าเฉลี่ยของหลายมุม:

หากมุมของคุณเป็นเรเดียน:

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

หากมุมของคุณอยู่ในองศา:

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

นี่คือแนวคิด: สร้างค่าเฉลี่ยซ้ำ ๆ โดยการคำนวณค่าเฉลี่ยของมุมที่อยู่ใกล้กันที่สุดเสมอโดยรักษาน้ำหนักไว้

ความคิดอื่น: ค้นหาช่องว่างที่ใหญ่ที่สุดระหว่างมุมที่กำหนด ค้นหาจุดที่แบ่งออกเป็นสองส่วนแล้วเลือกจุดตรงข้ามบนวงกลมเป็นศูนย์อ้างอิงเพื่อคำนวณค่าเฉลี่ย


ฉันไม่แนะนำคำตอบของฉัน แต่คำตอบที่ได้รับการจัดอันดับสูงของ starblue การสังเกตที่สำคัญคือการคิดว่าจุดศูนย์กลางของเข็มทิศเป็นจุด 0,0
จอห์นกับวาฟเฟิล

1

ลองแทนมุมเหล่านี้ด้วยจุดบนเส้นรอบวงของวงกลม

เราสามารถสันนิษฐานได้ว่าคะแนนทั้งหมดเหล่านี้ตกอยู่ในครึ่งเดียวกันของวงกลมหรือไม่? (ไม่เช่นนั้นไม่มีวิธีที่ชัดเจนในการกำหนด "มุมเฉลี่ย" ลองนึกถึงจุดสองจุดบนเส้นผ่านศูนย์กลางเช่น 0 องศาและ 180 องศา --- คือค่าเฉลี่ย 90 องศาหรือ 270 องศาจะเกิดอะไรขึ้นเมื่อเรามี 3 หรือมากกว่า กระจายคะแนนเท่า ๆ กัน?)

ด้วยสมมติฐานนี้เราเลือกจุดโดยพลการในครึ่งวงกลมนั้นในฐานะ "ต้นกำเนิด" และวัดชุดของมุมที่กำหนดด้วยความเคารพต่อต้นกำเนิดนี้ (เรียกสิ่งนี้ว่า "มุมญาติ") โปรดทราบว่ามุมสัมพัทธ์มีค่าสัมบูรณ์อย่างน้อยกว่า 180 องศา สุดท้ายใช้ค่าเฉลี่ยของมุมญาติเหล่านี้เพื่อให้ได้มุมเฉลี่ยที่ต้องการ (เทียบกับจุดกำเนิดของเราแน่นอน)


1

ไม่มี "คำตอบที่ถูกต้อง" เดียว ฉันแนะนำให้อ่านหนังสือ KV Mardia และ PE Jupp, "Directional Statistics", (Wiley, 1999) เพื่อการวิเคราะห์อย่างละเอียด


1

(เพียงแค่ต้องการแบ่งปันมุมมองของฉันจากทฤษฎีการประมาณหรือการอนุมานเชิงสถิติ)

การทดลองของ Nimble นั้นเพื่อให้ได้ค่าประมาณ MMSE ^ ของชุดมุม แต่เป็นหนึ่งในตัวเลือกที่จะหาทิศทาง "เฉลี่ย" คุณสามารถหาค่า MMAE ^ ที่คาดการณ์ไว้หรือค่าประมาณอื่น ๆ ที่จะเป็นทิศทาง "เฉลี่ย" และขึ้นอยู่กับข้อผิดพลาดของการวัดปริมาณของทิศทางของคุณ หรือมากกว่าโดยทั่วไปในทฤษฎีการประมาณความหมายของฟังก์ชันต้นทุน

^ MMSE / MMAE สอดคล้องกับข้อผิดพลาดกำลังสองเฉลี่ย / ต่ำสุด

ackb กล่าวว่า "มุมเฉลี่ย phi_avg ควรมีคุณสมบัติที่ sum_i | phi_avg-phi_i | ^ 2 กลายเป็นน้อยที่สุด ... พวกเขาเฉลี่ยบางสิ่ง แต่ไม่ใช่มุม"

---- คุณหาจำนวนข้อผิดพลาดในความหมายเฉลี่ยกำลังสองและเป็นหนึ่งในวิธีที่พบได้บ่อยที่สุดไม่ใช่ไม่ใช่วิธีเดียว คำตอบที่คนส่วนใหญ่ชื่นชอบ (เช่นผลรวมของเวกเตอร์หน่วยและมุมของผลลัพธ์) จริง ๆ แล้วเป็นหนึ่งในวิธีแก้ปัญหาที่สมเหตุสมผล มันคือ (สามารถพิสูจน์ได้) ตัวประมาณค่า ML ที่ทำหน้าที่เป็นทิศทาง "เฉลี่ย" ที่เราต้องการหากทิศทางของเวกเตอร์นั้นเป็นแบบจำลองเป็นการแจกแจงฟอนไมเซส การกระจายนี้ไม่ได้เป็นความแฟนซีและเป็นเพียงการกระจายตัวอย่างที่เป็นระยะจาก Guassian 2D ดูสมการ (2.179) ในหนังสือของบิชอป "การจดจำรูปแบบและการเรียนรู้ของเครื่อง" อีกครั้งโดยไม่ได้เป็นวิธีเดียวที่ดีที่สุดในการเป็นตัวแทนของทิศทาง "เฉลี่ย" แต่ก็ค่อนข้างสมเหตุสมผลซึ่งมีเหตุผลทางทฤษฎีที่ดีและใช้งานง่าย

ว่องไวกล่าวว่า "ackb นั้นถูกต้องว่าโซลูชั่นแบบเวกเตอร์เหล่านี้ไม่สามารถพิจารณาค่าเฉลี่ยจริงของมุมพวกมันเป็นเพียงค่าเฉลี่ยของหน่วยเวกเตอร์คู่หน่วย"

----นี่ไม่เป็นความจริง. "เวกเตอร์หน่วยคู่" แสดงข้อมูลของทิศทางของเวกเตอร์ มุมคือปริมาณโดยไม่คำนึงถึงความยาวของเวกเตอร์และเวกเตอร์หน่วยเป็นอะไรที่มีข้อมูลเพิ่มเติมว่าความยาวคือ 1 คุณสามารถกำหนดเวกเตอร์ "หน่วย" ของคุณให้มีความยาว 2 ได้มันไม่สำคัญเลย


1

นี่คือวิธีการแก้ปัญหาทางคณิตศาสตร์อย่างสมบูรณ์โดยใช้ค่าเฉลี่ยเคลื่อนที่และการดูแลเพื่อทำให้ค่าปกติ มันรวดเร็วและให้คำตอบที่ถูกต้องหากมุมทั้งหมดอยู่ในด้านหนึ่งของวงกลม (ภายใน 180 °ของกันและกัน)

มันมีความเทียบเท่าทางคณิตศาสตร์ในการเพิ่มออฟเซ็ตซึ่งเลื่อนค่าลงในช่วง (0, 180) ปรับขนาดเฉลี่ยแล้วลบออฟเซ็ต

ความคิดเห็นอธิบายว่าช่วงค่าเฉพาะสามารถใช้ในเวลาใดก็ตาม

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

แต่ฉันคิดว่าฉันจะเพิ่ม 2 เซ็นต์ของฉันเพราะฉันไม่สามารถหาคำตอบที่ชัดเจนได้ ในที่สุดฉันก็ใช้วิธี Mitsuta เวอร์ชั่น Java ต่อไปซึ่งฉันหวังว่าจะให้วิธีแก้ปัญหาที่ง่ายและมีประสิทธิภาพ โดยเฉพาะอย่างยิ่งในส่วนเบี่ยงเบนมาตรฐานจะให้ทั้งการกระจายตัวของการวัดและหาก sd == 90 บ่งชี้ว่ามุมการป้อนข้อมูลส่งผลให้ค่าเฉลี่ยที่ไม่ชัดเจน

แก้ไข:จริง ๆ แล้วฉันตระหนักว่าการใช้งานดั้งเดิมของฉันสามารถทำให้ง่ายขึ้นยิ่งขึ้นจริง ๆ แล้วก็น่ากังวลอย่างง่าย ๆ เมื่อพิจารณาจากการสนทนาและตรีโกณมิติที่เกิดขึ้นในคำตอบอื่น ๆ

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

... และสำหรับทุกคน (Java) คุณก็สามารถใช้วิธีการข้างต้นเพื่อให้ได้มุมเฉลี่ยในหนึ่งบรรทัด

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

ฉันเชื่อว่าคุณพลาดอะไรบางอย่างจากวิธีของมิตซูดะ โปรดดูคำตอบที่โพสต์โดย Lior Kogan stackoverflow.com/a/1828222/9265852
kykzk46

0

Alnitak มีทางออกที่ถูกต้อง โซลูชันของ Nick Fortescue นั้นใช้งานได้เหมือนกัน

สำหรับกรณีพิเศษของที่ไหน

(ผลรวม (x_component) = 0.0 && sum (y_component) = 0.0) // เช่น 2 มุม 10 และ 190 องศา ea

ใช้ 0.0 องศาเป็นผลรวม

คุณต้องทดสอบกรณีนี้เนื่องจาก atan2 (0., 0. ) ไม่ได้ถูกกำหนดและจะสร้างข้อผิดพลาด


บน glibc 'atan2' ถูกกำหนดสำหรับ (0, 0) - ผลลัพธ์คือ 0
Alnitak

0

มุมเฉลี่ย phi_avg ควรมีคุณสมบัติที่ sum_i | phi_avg-phi_i | ^ 2 น้อยที่สุดซึ่งความแตกต่างจะต้องอยู่ใน [-Pi, Pi) (เพราะมันอาจจะสั้นกว่าที่จะไปทางอื่น ๆ !) สิ่งนี้สามารถทำได้อย่างง่ายดายโดยการปรับค่าอินพุตทั้งหมดให้เป็น [0, 2Pi) ทำให้ phi_run เฉลี่ยที่ใช้งานอยู่ทำงานอยู่และเลือก normalizing | phi_i-phi_run | ถึง [-Pi, Pi) (โดยการเพิ่มหรือลบ 2Pi) คำแนะนำส่วนใหญ่ข้างต้นทำอย่างอื่นที่ไม่ได้ มีคุณสมบัติที่น้อยที่สุดนั่นคือพวกเขาเฉลี่ยบางสิ่งแต่ไม่ใช่มุม


0

ฉันแก้ไขปัญหาด้วยความช่วยเหลือของคำตอบจาก @David_Hanak ในขณะที่เขากล่าวว่า:

มุมที่ชี้ "ระหว่าง" อีกสองมุมขณะอยู่ในครึ่งวงกลมเดียวกันเช่น 355 และ 5 นี่จะเป็น 0 ไม่ใช่ 180 ถ้าต้องการทำสิ่งนี้คุณต้องตรวจสอบว่าความแตกต่างระหว่างมุมทั้งสองนั้นใหญ่กว่า 180 หรือไม่ หรือไม่. ถ้าเป็นเช่นนั้นให้เพิ่มมุมเล็กลง 360 ก่อนใช้สูตรด้านบน

สิ่งที่ฉันทำคือคำนวณค่าเฉลี่ยของทุกมุม จากนั้นมุมทั้งหมดที่น้อยกว่านี้เพิ่มพวกมัน 360 องศาจากนั้นคำนวณค่าเฉลี่ยโดยการเพิ่มพวกมันทั้งหมดแล้วหารด้วยความยาว

        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;

ทำงานได้อย่างสมบูรณ์แบบ


0

ฟังก์ชั่นหลาม:

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

คุณสามารถใช้ฟังก์ชั่นนี้ใน 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 

อัลกอริทึมดูเหมือนจะทำงานได้ แต่ในความเป็นจริงมันอาจล้มเหลวอย่างน่าสังเวชในโลกแห่งความจริง ให้ค่ามุมที่อยู่ในทิศทางตรงกันข้ามกับมุมที่กำหนด
tothphu

0

คุณสามารถดูวิธีแก้ปัญหาและคำอธิบายเล็กน้อยในลิงค์ต่อไปนี้สำหรับภาษาการเขียนโปรแกรมใด ๆ : https://rosettacode.org/wiki/A Average/Mean_angle

ตัวอย่างเช่นโซลูชัน 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;
}

เอาท์พุท:

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

หรือโซลูชัน 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

0

ในขณะที่คำตอบของ starblue ให้มุมของเวกเตอร์หน่วยโดยเฉลี่ยคุณสามารถขยายแนวคิดของค่าเฉลี่ยเลขคณิตเป็นมุมถ้าคุณยอมรับว่าอาจมีมากกว่าหนึ่งคำตอบในช่วง 0 ถึง 2 * pi (หรือ 0 °ถึง 360 °) ตัวอย่างเช่นค่าเฉลี่ย 0 °และ 180 °อาจเป็น 90 °หรือ 270 °

ค่าเฉลี่ยเลขคณิตมีคุณสมบัติของการเป็นค่าเดียวกับผลรวมขั้นต่ำของระยะทางยกกำลังสองกับค่าอินพุต ระยะทางตามวงกลมหน่วยระหว่างเวกเตอร์สองหน่วยสามารถคำนวณได้ง่ายเช่นโคไซน์ผกผันของผลคูณจุด ถ้าเราเลือกเวกเตอร์หน่วยโดยลดผลรวมของโคไซน์ผกผันกำลังสองของผลคูณของจุดของเวกเตอร์ของเราและเวกเตอร์หน่วยอินพุตแต่ละอันเราก็จะมีค่าเฉลี่ยที่เทียบเท่ากัน โปรดจำไว้ว่าอาจมีขั้นต่ำสองกรณีขึ้นไปในกรณีพิเศษ

แนวคิดนี้สามารถขยายไปถึงจำนวนมิติใด ๆ เนื่องจากระยะทางตามทรงกลมของหน่วยสามารถคำนวณได้ในลักษณะเดียวกับระยะทางตามวงกลมหน่วย - ค่าผกผันโคไซน์ของผลคูณจุดของเวกเตอร์สองหน่วย

สำหรับแวดวงเราสามารถหาค่าเฉลี่ยนี้ได้หลายวิธี แต่ฉันเสนออัลกอริทึม O (n ^ 2) ต่อไปนี้ (มุมเป็นเรเดียนและฉันหลีกเลี่ยงการคำนวณเวกเตอร์หน่วย):

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

หากมุมทั้งหมดอยู่ภายใน 180 °ของกันและกันเราสามารถใช้อัลกอริทึม O (n) + O (เรียงลำดับ) ที่ง่ายกว่า (อีกครั้งโดยใช้เรเดียนและหลีกเลี่ยงการใช้เวกเตอร์หน่วย):

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

หากต้องการใช้องศาเพียงแค่แทนที่ pi ด้วย 180 หากคุณวางแผนที่จะใช้มิติข้อมูลมากขึ้นคุณจะต้องใช้วิธีวนซ้ำในการหาค่าเฉลี่ย


0

ปัญหาง่ายมาก 1. ตรวจสอบให้แน่ใจว่ามุมทั้งหมดอยู่ระหว่าง -180 ถึง 180 องศา 2. a เพิ่มมุมที่ไม่เป็นลบทั้งหมดเอาค่าเฉลี่ยและ COUNT เท่าไหร่ 2. b. เพิ่มมุมลบทั้งหมดเอาค่าเฉลี่ยและ COUNT เท่าไร 3. นำส่วนต่างของ pos_average ลบ neg_average หากความแตกต่างมากกว่า 180 จากนั้นเปลี่ยนความต่างเป็น 360 ลบต่าง มิฉะนั้นเพียงเปลี่ยนสัญลักษณ์ของความแตกต่าง โปรดทราบว่าความแตกต่างนั้นไม่ใช่แบบลบเสมอ Average_Angle เท่ากับ pos_average บวกความแตกต่างคูณด้วย "น้ำหนัก" จำนวนลบหารด้วยผลรวมของจำนวนลบและจำนวนบวก


0

นี่คือโค้ดจาวาต่อมุมเฉลี่ย, ฉันคิดว่ามันแข็งแกร่งพอสมควร

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

-3

ฉันมีวิธีการที่แตกต่างจาก @Starblue ที่ให้คำตอบ "ถูกต้อง" กับบางมุมที่ระบุไว้ข้างต้น ตัวอย่างเช่น:

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

มันใช้ผลรวมมากกว่าความแตกต่างระหว่างมุมที่ต่อเนื่องกัน รหัส (ใน 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

1
รหัสของคุณส่งคืนคำตอบที่ต่างกันสำหรับ[-90,90,40]และ[90,-90,40]; ฉันไม่คิดว่าค่าเฉลี่ยที่ไม่ใช่การสับเปลี่ยนเป็นค่าที่มีประโยชน์มาก
musiphil
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.